Repository: rook/rook
Pull request #9484 (2021-12-22 13:59:12)

csi: change rook-ceph-csi-config to expose clusterID for subvolume
**Description of your changes:**

The rook-ceph-csi-config config map now includes CephFS subvolumegroups
when a subvolumegroup CR is created. It is useful for ceph-csi to
understand which subvolumegroup to use for a given cluster.

Signed-off-by: Sébastien Han <seb@redhat.com>

<!-- Please take a look at our [Contributing](https://rook.io/docs/rook/latest/development-flow.html)
documentation before submitting a Pull Request!
Thank you for contributing to Rook! -->


**Which issue is resolved by this Pull Request:**
Resolves #

**Checklist:**

- [ ] **Commit Message Formatting**: Commit titles and messages follow guidelines in the [developer guide](https://rook.io/docs/rook/latest/development-flow.html#commit-structure).
- [ ] **Skip Tests for Docs**: Add the flag for skipping the build if this is only a documentation change. See [here](https://github.com/rook/rook/blob/master/INSTALL.md#skip-ci) for the flag.
- [ ] **Skip Unrelated Tests**: Add a flag to run tests for a specific storage provider. See [test options](https://github.com/rook/rook/blob/master/INSTALL.md#test-storage-provider).
- [ ] Reviewed the developer guide on [Submitting a Pull Request](https://rook.io/docs/rook/latest/development-flow.html#submitting-a-pull-request)
- [ ] Documentation has been updated, if necessary.
- [ ] Unit tests have been added, if necessary.
- [ ] Integration tests have been added, if necessary.
- [ ] Pending release notes updated with breaking and/or notable changes, if necessary.
- [ ] Upgrade from previous release is tested and upgrade user guide is updated, if necessary.
- [ ] Code generation (`make codegen`) has been run to update object specifications, if necessary.


Madhu-1: @leseb dependency needs to be updated https://github.com/rook/rook/blob/master/design/ceph/resource-dependencies.md? if there are any PV/volumesnapshots in the subvolumegroup we should block/allow it for deletion?
leseb: > @leseb dependency needs to be updated https://github.com/rook/rook/blob/master/design/ceph/resource-dependencies.md? if there are any PV/volumesnapshots in the subvolumegroup we should block/allow it for deletion?

Right now, the deletion is blocked already if Ceph detects that there are subvolumes in the subvolumegroup, so it's sufficient.
Madhu-1: > > @leseb dependency needs to be updated https://github.com/rook/rook/blob/master/design/ceph/resource-dependencies.md? if there are any PV/volumesnapshots in the subvolumegroup we should block/allow it for deletion?
> 
> Right now, the deletion is blocked already if Ceph detects that there are subvolumes in the subvolumegroup, so it's sufficient.

@leseb  I don't see a code for this one if you don't mind can you please point me to that one?
leseb: > > > @leseb dependency needs to be updated https://github.com/rook/rook/blob/master/design/ceph/resource-dependencies.md? if there are any PV/volumesnapshots in the subvolumegroup we should block/allow it for deletion?
> > 
> > 
> > Right now, the deletion is blocked already if Ceph detects that there are subvolumes in the subvolumegroup, so it's sufficient.
> 
> @leseb I don't see a code for this one if you don't mind can you please point me to that one?

https://github.com/rook/rook/blob/376ca62f8ad07540d9ddffe9dc0ee53f4ac35e29/pkg/operator/ceph/file/subvolumegroup/controller.go#L244-L253

Also, I just realized there is a small issue with this code which I'll correct in this PR.
leseb: @Madhu-1 if we have the hash for everything, we will have leftover entries for the same cluster, see:

```
  csi-cluster-config-json: '[{"clusterID":"rook-ceph","monitors":["10.107.84.70:6789","10.97.224.243:6789","10.111.155.211:6789"]},{"clusterID":"4df0bb6e1b7ddc0c1ea080cbe043dd1a","monitors":["10.97.224.243:6789","10.111.155.211:6789","10.107.84.70:6789"],"cephFS":{"subvolumeGroup":"group-a"}}]'
```

For this, should we only apply the hash once a certain length is reached? Thanks!
Madhu-1: > @Madhu-1 if we have the hash for everything, we will have leftover entries for the same cluster, see:
> 
> ```
>   csi-cluster-config-json: '[{"clusterID":"rook-ceph","monitors":["10.107.84.70:6789","10.97.224.243:6789","10.111.155.211:6789"]},{"clusterID":"4df0bb6e1b7ddc0c1ea080cbe043dd1a","monitors":["10.97.224.243:6789","10.111.155.211:6789","10.107.84.70:6789"],"cephFS":{"subvolumeGroup":"group-a"}}]'
> ```
> 
> For this, should we only apply the hash once a certain length is reached? Thanks!

@leseb sorry i didn't get the problem with leftover. can you please explain it?
leseb: > > @Madhu-1 if we have the hash for everything, we will have leftover entries for the same cluster, see:
> > ```
> >   csi-cluster-config-json: '[{"clusterID":"rook-ceph","monitors":["10.107.84.70:6789","10.97.224.243:6789","10.111.155.211:6789"]},{"clusterID":"4df0bb6e1b7ddc0c1ea080cbe043dd1a","monitors":["10.97.224.243:6789","10.111.155.211:6789","10.107.84.70:6789"],"cephFS":{"subvolumeGroup":"group-a"}}]'
> > ```
> > 
> > 
> >     
> >       
> >     
> > 
> >       
> >     
> > 
> >     
> >   
> > For this, should we only apply the hash once a certain length is reached? Thanks!
> 
> @leseb sorry i didn't get the problem with leftover. can you please explain it?

Essentially we have duplicate entries, the old once with clusterID: rook-ceph and the new clusterID with the hash as a value.
Madhu-1: > > > @Madhu-1 if we have the hash for everything, we will have leftover entries for the same cluster, see:
> > > ```
> > >   csi-cluster-config-json: '[{"clusterID":"rook-ceph","monitors":["10.107.84.70:6789","10.97.224.243:6789","10.111.155.211:6789"]},{"clusterID":"4df0bb6e1b7ddc0c1ea080cbe043dd1a","monitors":["10.97.224.243:6789","10.111.155.211:6789","10.107.84.70:6789"],"cephFS":{"subvolumeGroup":"group-a"}}]'
> > > ```
> > > 
> > > 
> > >     
> > >       
> > >     
> > > 
> > >       
> > >     
> > > 
> > >     
> > >   
> > > For this, should we only apply the hash once a certain length is reached? Thanks!
> > 
> > 
> > @leseb sorry i didn't get the problem with leftover. can you please explain it?
> 
> Essentially we have duplicate entries, the old one with clusterID: rook-ceph and the new clusterID with the hash as a value.

Sorry, I missed one thing in review. the configmap entry should be deleted when the subvolumegroup CR is deleted. is this already handled?
leseb: > > > > @Madhu-1 if we have the hash for everything, we will have leftover entries for the same cluster, see:
> > > > ```
> > > >   csi-cluster-config-json: '[{"clusterID":"rook-ceph","monitors":["10.107.84.70:6789","10.97.224.243:6789","10.111.155.211:6789"]},{"clusterID":"4df0bb6e1b7ddc0c1ea080cbe043dd1a","monitors":["10.97.224.243:6789","10.111.155.211:6789","10.107.84.70:6789"],"cephFS":{"subvolumeGroup":"group-a"}}]'
> > > > ```
> > > > 
> > > > 
> > > >     
> > > >       
> > > >     
> > > > 
> > > >       
> > > >     
> > > > 
> > > >     
> > > >   
> > > > For this, should we only apply the hash once a certain length is reached? Thanks!
> > > 
> > > 
> > > @leseb sorry i didn't get the problem with leftover. can you please explain it?
> > 
> > 
> > Essentially we have duplicate entries, the old one with clusterID: rook-ceph and the new clusterID with the hash as a value.
> 
> Sorry, I missed one thing in review. the configmap entry should be deleted when the subvolumegroup CR is deleted. is this already handled?

No, let me fix this.

Patch:
diff --git a/deploy/charts/rook-ceph/templates/configmap.yaml b/deploy/charts/rook-ceph/templates/configmap.yaml
deleted file mode 100644
index ccc0a3a48..000000000
--- a/deploy/charts/rook-ceph/templates/configmap.yaml
+++ /dev/null
@@ -1,163 +0,0 @@
-# Operator settings that can be updated without an operator restart
-# Operator settings that require an operator restart are found in the operator env vars
-kind: ConfigMap
-apiVersion: v1
-metadata:
-  name: rook-ceph-operator-config
-data:
-  ROOK_LOG_LEVEL: {{ .Values.logLevel | quote }}
-  ROOK_CEPH_COMMANDS_TIMEOUT_SECONDS: {{ .Values.cephCommandsTimeoutSeconds | quote }}
-  ROOK_OBC_WATCH_OPERATOR_NAMESPACE: {{ .Values.enableOBCWatchOperatorNamespace | quote }}
-{{- if .Values.csi }}
-  ROOK_CSI_ENABLE_RBD: {{ .Values.csi.enableRbdDriver | quote }}
-  ROOK_CSI_ENABLE_CEPHFS: {{ .Values.csi.enableCephfsDriver | quote }}
-  CSI_ENABLE_CEPHFS_SNAPSHOTTER: {{ .Values.csi.enableCephfsSnapshotter | quote }}
-  CSI_ENABLE_RBD_SNAPSHOTTER: {{ .Values.csi.enableRBDSnapshotter | quote }}
-  CSI_PLUGIN_ENABLE_SELINUX_HOST_MOUNT: {{ .Values.csi.enablePluginSelinuxHostMount | quote }}
-  CSI_ENABLE_OMAP_GENERATOR: {{ .Values.csi.enableOMAPGenerator | quote }}
-{{- if .Values.csi.pluginPriorityClassName }}
-  CSI_PLUGIN_PRIORITY_CLASSNAME: {{ .Values.csi.pluginPriorityClassName | quote }}
-{{- end }}
-{{- if .Values.csi.provisionerPriorityClassName }}
-  CSI_PROVISIONER_PRIORITY_CLASSNAME: {{ .Values.csi.provisionerPriorityClassName | quote }}
-{{- end }}
-{{- if .Values.csi.enableCSIHostNetwork }}
-  CSI_ENABLE_HOST_NETWORK: {{ .Values.csi.enableCSIHostNetwork | quote }}
-{{- end }}
-{{- if .Values.csi.cephFSPluginUpdateStrategy }}
-  CSI_CEPHFS_PLUGIN_UPDATE_STRATEGY: {{ .Values.csi.cephFSPluginUpdateStrategy | quote }}
-{{- end }}
-{{- if .Values.csi.rbdFSGroupPolicy }}
-  CSI_RBD_FSGROUPPOLICY: {{ .Values.csi.rbdFSGroupPolicy | quote }}
-{{- end }}
-{{- if .Values.csi.cephFSFSGroupPolicy }}
-  CSI_CEPHFS_FSGROUPPOLICY: {{ .Values.csi.cephFSFSGroupPolicy | quote }}
-{{- end }}
-{{- if .Values.csi.rbdPluginUpdateStrategy }}
-  CSI_RBD_PLUGIN_UPDATE_STRATEGY: {{ .Values.csi.rbdPluginUpdateStrategy | quote }}
-{{- end }}
-{{- if .Values.csi.kubeletDirPath }}
-  ROOK_CSI_KUBELET_DIR_PATH: {{ .Values.csi.kubeletDirPath | quote }}
-{{- end }}
-  ROOK_CSI_ENABLE_GRPC_METRICS: {{ .Values.csi.enableGrpcMetrics | quote }}
-{{- if .Values.csi.cephcsi }}
-{{- if .Values.csi.cephcsi.image }}
-  ROOK_CSI_CEPH_IMAGE: {{ .Values.csi.cephcsi.image | quote }}
-{{- end }}
-{{- end }}
-{{- if .Values.csi.registrar }}
-{{- if .Values.csi.registrar.image }}
-  ROOK_CSI_REGISTRAR_IMAGE: {{ .Values.csi.registrar.image | quote }}
-{{- end }}
-{{- end }}
-{{- if .Values.csi.provisioner }}
-{{- if .Values.csi.provisioner.image }}
-  ROOK_CSI_PROVISIONER_IMAGE: {{ .Values.csi.provisioner.image | quote }}
-{{- end }}
-{{- end }}
-{{- if .Values.csi.snapshotter }}
-{{- if .Values.csi.snapshotter.image }}
-  ROOK_CSI_SNAPSHOTTER_IMAGE: {{ .Values.csi.snapshotter.image | quote }}
-{{- end }}
-{{- end }}
-{{- if .Values.csi.attacher }}
-{{- if .Values.csi.attacher.image }}
-  ROOK_CSI_ATTACHER_IMAGE: {{ .Values.csi.attacher.image | quote }}
-{{- end }}
-{{- end }}
-{{- if .Values.csi.resizer }}
-{{- if .Values.csi.resizer.image }}
-  ROOK_CSI_RESIZER_IMAGE: {{ .Values.csi.resizer.image | quote }}
-{{- end }}
-{{- end }}
-{{- if .Values.csi.volumeReplication }}
-  CSI_ENABLE_VOLUME_REPLICATION: {{ .Values.csi.volumeReplication.enabled | quote }}
-{{- if .Values.csi.volumeReplication.image }}
-  CSI_VOLUME_REPLICATION_IMAGE: {{ .Values.csi.volumeReplication.image | quote }}
-{{- end }}
-{{- end }}
-{{- if .Values.csi.csiAddons }}
-  CSI_ENABLE_CSIADDONS: {{ .Values.csi.csiAddons.enabled | quote }}
-{{- if .Values.csi.csiAddons.image }}
-  ROOK_CSIADDONS_IMAGE: {{ .Values.csi.csiAddons.image | quote }}
-{{- end }}
-{{- end }}
-{{- if .Values.csi.cephfsPodLabels }}
-  ROOK_CSI_CEPHFS_POD_LABELS: {{ .Values.csi.cephfsPodLabels | quote }}
-{{- end }}
-{{- if .Values.csi.rbdPodLabels }}
-  ROOK_CSI_RBD_POD_LABELS: {{ .Values.csi.rbdPodLabels | quote }}
-{{- end }}
-{{- if .Values.csi.provisionerTolerations }}
-  CSI_PROVISIONER_TOLERATIONS: {{ toYaml .Values.csi.provisionerTolerations | quote }}
-{{- end }}
-{{- if .Values.csi.provisionerNodeAffinity }}
-  CSI_PROVISIONER_NODE_AFFINITY: {{ .Values.csi.provisionerNodeAffinity }}
-{{- end }}
-{{- if .Values.csi.rbdProvisionerTolerations }}
-  CSI_RBD_PROVISIONER_TOLERATIONS: {{ toYaml .Values.csi.rbdProvisionerTolerations | quote }}
-{{- end }}
-{{- if .Values.csi.rbdProvisionerNodeAffinity }}
-  CSI_RBD_PROVISIONER_NODE_AFFINITY: {{ .Values.csi.rbdProvisionerNodeAffinity }}
-{{- end }}
-{{- if .Values.csi.cephFSProvisionerTolerations }}
-  CSI_CEPHFS_PROVISIONER_TOLERATIONS: {{ toYaml .Values.csi.cephFSProvisionerTolerations | quote }}
-{{- end }}
-{{- if .Values.csi.cephFSProvisionerNodeAffinity }}
-  CSI_CEPHFS_PROVISIONER_NODE_AFFINITY: {{ .Values.csi.cephFSProvisionerNodeAffinity }}
-{{- end }}
-{{- if .Values.csi.allowUnsupportedVersion }}
-  ROOK_CSI_ALLOW_UNSUPPORTED_VERSION: {{ .Values.csi.allowUnsupportedVersion | quote }}
-{{- end }}
-{{- if .Values.csi.pluginTolerations }}
-  CSI_PLUGIN_TOLERATIONS: {{ toYaml .Values.csi.pluginTolerations | quote }}
-{{- end }}
-{{- if .Values.csi.pluginNodeAffinity }}
-  CSI_PLUGIN_NODE_AFFINITY: {{ .Values.csi.pluginNodeAffinity }}
-{{- end }}
-{{- if .Values.csi.rbdPluginTolerations }}
-  CSI_RBD_PLUGIN_TOLERATIONS: {{ toYaml .Values.csi.rbdPluginTolerations | quote }}
-{{- end }}
-{{- if .Values.csi.rbdPluginNodeAffinity }}
-  CSI_RBD_PLUGIN_NODE_AFFINITY: {{ .Values.csi.rbdPluginNodeAffinity }}
-{{- end }}
-{{- if .Values.csi.cephFSPluginTolerations }}
-  CSI_CEPHFS_PLUGIN_TOLERATIONS: {{ toYaml .Values.csi.cephFSPluginTolerations | quote }}
-{{- end }}
-{{- if .Values.csi.cephFSPluginNodeAffinity }}
-  CSI_CEPHFS_PLUGIN_NODE_AFFINITY: {{ .Values.csi.cephFSPluginNodeAffinity }}
-{{- end }}
-{{- if .Values.csi.cephfsGrpcMetricsPort }}
-  CSI_CEPHFS_GRPC_METRICS_PORT: {{ .Values.csi.cephfsGrpcMetricsPort | quote }}
-{{- end }}
-{{- if .Values.csi.cephfsLivenessMetricsPort }}
-  CSI_CEPHFS_LIVENESS_METRICS_PORT: {{ .Values.csi.cephfsLivenessMetricsPort | quote }}
-{{- end }}
-{{- if .Values.csi.rbdGrpcMetricsPort }}
-  CSI_RBD_GRPC_METRICS_PORT: {{ .Values.csi.rbdGrpcMetricsPort | quote }}
-{{- end }}
-{{- if .Values.csi.rbdLivenessMetricsPort }}
-  CSI_RBD_LIVENESS_METRICS_PORT: {{ .Values.csi.rbdLivenessMetricsPort | quote }}
-{{- end }}
-{{- if .Values.csi.forceCephFSKernelClient }}
-  CSI_FORCE_CEPHFS_KERNEL_CLIENT: {{ .Values.csi.forceCephFSKernelClient | quote }}
-{{- end }}
-{{- if .Values.csi.logLevel }}
-  CSI_LOG_LEVEL: {{ .Values.csi.logLevel | quote }}
-{{- end }}
-{{- if .Values.csi.provisionerReplicas }}
-  CSI_PROVISIONER_REPLICAS: {{ .Values.csi.provisionerReplicas | quote }}
-{{- end }}
-{{- if .Values.csi.csiRBDProvisionerResource }}
-  CSI_RBD_PROVISIONER_RESOURCE: {{ .Values.csi.csiRBDProvisionerResource | quote }}
-{{- end }}
-{{- if .Values.csi.csiRBDPluginResource }}
-  CSI_RBD_PLUGIN_RESOURCE: {{ .Values.csi.csiRBDPluginResource | quote }}
-{{- end }}
-{{- if .Values.csi.csiCephFSProvisionerResource }}
-  CSI_CEPHFS_PROVISIONER_RESOURCE: {{ .Values.csi.csiCephFSProvisionerResource | quote }}
-{{- end }}
-{{- if .Values.csi.csiCephFSPluginResource }}
-  CSI_CEPHFS_PLUGIN_RESOURCE: {{ .Values.csi.csiCephFSPluginResource | quote }}
-{{- end }}
-{{- end }}
diff --git a/deploy/charts/rook-ceph/templates/deployment.yaml b/deploy/charts/rook-ceph/templates/deployment.yaml
index 9e98f86f7..446e59b24 100644
--- a/deploy/charts/rook-ceph/templates/deployment.yaml
+++ b/deploy/charts/rook-ceph/templates/deployment.yaml
@@ -68,12 +68,222 @@ spec:
 {{- end }}
         - name: ROOK_HOSTPATH_REQUIRES_PRIVILEGED
           value: "{{ .Values.hostpathRequiresPrivileged }}"
+        - name: ROOK_LOG_LEVEL
+          value: {{ .Values.logLevel }}
         - name: ROOK_ENABLE_SELINUX_RELABELING
           value: "{{ .Values.enableSelinuxRelabeling }}"
         - name: ROOK_DISABLE_DEVICE_HOTPLUG
           value: "{{ .Values.disableDeviceHotplug }}"
+{{- if .Values.csi }}
+        - name: ROOK_CSI_ENABLE_RBD
+          value: {{ .Values.csi.enableRbdDriver | quote }}
+        - name: ROOK_CSI_ENABLE_CEPHFS
+          value: {{ .Values.csi.enableCephfsDriver | quote }}
+        - name: CSI_ENABLE_CEPHFS_SNAPSHOTTER
+          value: {{ .Values.csi.enableCephfsSnapshotter | quote }}
+        - name: CSI_ENABLE_RBD_SNAPSHOTTER
+          value: {{ .Values.csi.enableRBDSnapshotter | quote }}
+        - name: CSI_PLUGIN_PRIORITY_CLASSNAME
+          value: {{ .Values.csi.pluginPriorityClassName | quote }}
+        - name: CSI_PROVISIONER_PRIORITY_CLASSNAME
+          value: {{ .Values.csi.provisionerPriorityClassName | quote }}
+        - name: CSI_ENABLE_OMAP_GENERATOR
+          value: {{ .Values.csi.enableOMAPGenerator | quote }}
+        - name: CSI_ENABLE_VOLUME_REPLICATION
+          value: {{ .Values.csi.volumeReplication.enabled | quote }}
+        - name: CSI_ENABLE_CSIADDONS
+          value: {{ .Values.csi.csiAddons.enabled | quote }}
+        - name: CSI_PLUGIN_ENABLE_SELINUX_HOST_MOUNT
+          value: {{ .Values.csi.enablePluginSelinuxHostMount | quote }}
+{{- if .Values.csi.enableCSIHostNetwork }}
+        - name: CSI_ENABLE_HOST_NETWORK
+          value: {{ .Values.csi.enableCSIHostNetwork | quote }}
+{{- end }}
+{{- if .Values.csi.cephFSPluginUpdateStrategy }}
+        - name: CSI_CEPHFS_PLUGIN_UPDATE_STRATEGY
+          value: {{ .Values.csi.cephFSPluginUpdateStrategy | quote }}
+{{- end }}
+{{- if .Values.csi.rbdFSGroupPolicy }}
+        - name: CSI_RBD_FSGROUPPOLICY
+          value: {{ .Values.csi.rbdFSGroupPolicy | quote }}
+{{- end }}
+{{- if .Values.csi.cephFSFSGroupPolicy }}
+        - name: CSI_CEPHFS_FSGROUPPOLICY
+          value: {{ .Values.csi.cephFSFSGroupPolicy | quote }}
+{{- end }}
+{{- if .Values.csi.rbdPluginUpdateStrategy }}
+        - name: CSI_RBD_PLUGIN_UPDATE_STRATEGY
+          value: {{ .Values.csi.rbdPluginUpdateStrategy | quote }}
+{{- end }}
+{{- if .Values.csi.kubeletDirPath }}
+        - name: ROOK_CSI_KUBELET_DIR_PATH
+          value: {{ .Values.csi.kubeletDirPath | quote }}
+{{- end }}
+        - name: ROOK_CSI_ENABLE_GRPC_METRICS
+          value: {{ .Values.csi.enableGrpcMetrics | quote }}
+{{- if .Values.csi.cephcsi }}
+{{- if .Values.csi.cephcsi.image }}
+        - name: ROOK_CSI_CEPH_IMAGE
+          value: {{ .Values.csi.cephcsi.image | quote }}
+{{- end }}
+{{- end }}
+{{- if .Values.csi.registrar }}
+{{- if .Values.csi.registrar.image }}
+        - name: ROOK_CSI_REGISTRAR_IMAGE
+          value: {{ .Values.csi.registrar.image | quote }}
+{{- end }}
+{{- end }}
+{{- if .Values.csi.provisioner }}
+{{- if .Values.csi.provisioner.image }}
+        - name: ROOK_CSI_PROVISIONER_IMAGE
+          value: {{ .Values.csi.provisioner.image | quote }}
+{{- end }}
+{{- end }}
+{{- if .Values.csi.snapshotter }}
+{{- if .Values.csi.snapshotter.image }}
+        - name: ROOK_CSI_SNAPSHOTTER_IMAGE
+          value: {{ .Values.csi.snapshotter.image | quote }}
+{{- end }}
+{{- end }}
+{{- if .Values.csi.attacher }}
+{{- if .Values.csi.attacher.image }}
+        - name: ROOK_CSI_ATTACHER_IMAGE
+          value: {{ .Values.csi.attacher.image | quote }}
+{{- end }}
+{{- end }}
+{{- if .Values.csi.resizer }}
+{{- if .Values.csi.resizer.image }}
+        - name: ROOK_CSI_RESIZER_IMAGE
+          value: {{ .Values.csi.resizer.image | quote }}
+{{- end }}
+{{- end }}
+{{- if .Values.csi.volumeReplication }}
+{{- if .Values.csi.volumeReplication.image }}
+        - name: CSI_VOLUME_REPLICATION_IMAGE
+          value: {{ .Values.csi.volumeReplication.image | quote }}
+{{- end }}
+{{- end }}
+{{- if .Values.csi.csiAddons }}
+{{- if .Values.csi.csiAddons.image }}
+        - name: ROOK_CSIADDONS_IMAGE
+          value: {{ .Values.csi.csiAddons.image | quote }}
+{{- end }}
+{{- if .Values.csi.csiAddonsPort }}
+        - name: CSIADDONS_PORT
+          value: {{ .Values.csi.csiAddonsPort | quote }}
+{{- end }}
+{{- end }}
+{{- if .Values.csi.cephfsPodLabels }}
+        - name: ROOK_CSI_CEPHFS_POD_LABELS
+          value: {{ .Values.csi.cephfsPodLabels | quote }}
+{{- end }}
+{{- if .Values.csi.rbdPodLabels }}
+        - name: ROOK_CSI_RBD_POD_LABELS
+          value: {{ .Values.csi.rbdPodLabels | quote }}
+{{- end }}
+{{- if .Values.csi.provisionerTolerations }}
+        - name: CSI_PROVISIONER_TOLERATIONS
+          value: {{ toYaml .Values.csi.provisionerTolerations | quote }}
+{{- end }}
+{{- if .Values.csi.provisionerNodeAffinity }}
+        - name: CSI_PROVISIONER_NODE_AFFINITY
+          value: {{ .Values.csi.provisionerNodeAffinity }}
+{{- end }}
+{{- if .Values.csi.rbdProvisionerTolerations }}
+        - name: CSI_RBD_PROVISIONER_TOLERATIONS
+          value: {{ toYaml .Values.csi.rbdProvisionerTolerations | quote }}
+{{- end }}
+{{- if .Values.csi.rbdProvisionerNodeAffinity }}
+        - name: CSI_RBD_PROVISIONER_NODE_AFFINITY
+          value: {{ .Values.csi.rbdProvisionerNodeAffinity }}
+{{- end }}
+{{- if .Values.csi.cephFSProvisionerTolerations }}
+        - name: CSI_CEPHFS_PROVISIONER_TOLERATIONS
+          value: {{ toYaml .Values.csi.cephFSProvisionerTolerations | quote }}
+{{- end }}
+{{- if .Values.csi.cephFSProvisionerNodeAffinity }}
+        - name: CSI_CEPHFS_PROVISIONER_NODE_AFFINITY
+          value: {{ .Values.csi.cephFSProvisionerNodeAffinity }}
+{{- end }}
+{{- if .Values.csi.allowUnsupportedVersion }}
+        - name: ROOK_CSI_ALLOW_UNSUPPORTED_VERSION
+          value: {{ .Values.csi.allowUnsupportedVersion | quote }}
+{{- end }}
+{{- if .Values.csi.pluginTolerations }}
+        - name: CSI_PLUGIN_TOLERATIONS
+          value: {{ toYaml .Values.csi.pluginTolerations | quote }}
+{{- end }}
+{{- if .Values.csi.pluginNodeAffinity }}
+        - name: CSI_PLUGIN_NODE_AFFINITY
+          value: {{ .Values.csi.pluginNodeAffinity }}
+{{- end }}
+{{- if .Values.csi.rbdPluginTolerations }}
+        - name: CSI_RBD_PLUGIN_TOLERATIONS
+          value: {{ toYaml .Values.csi.rbdPluginTolerations | quote }}
+{{- end }}
+{{- if .Values.csi.rbdPluginNodeAffinity }}
+        - name: CSI_RBD_PLUGIN_NODE_AFFINITY
+          value: {{ .Values.csi.rbdPluginNodeAffinity }}
+{{- end }}
+{{- if .Values.csi.cephFSPluginTolerations }}
+        - name: CSI_CEPHFS_PLUGIN_TOLERATIONS
+          value: {{ toYaml .Values.csi.cephFSPluginTolerations | quote }}
+{{- end }}
+{{- if .Values.csi.cephFSPluginNodeAffinity }}
+        - name: CSI_CEPHFS_PLUGIN_NODE_AFFINITY
+          value: {{ .Values.csi.cephFSPluginNodeAffinity }}
+{{- end }}
+{{- if .Values.csi.cephfsGrpcMetricsPort }}
+        - name: CSI_CEPHFS_GRPC_METRICS_PORT
+          value: {{ .Values.csi.cephfsGrpcMetricsPort | quote }}
+{{- end }}
+{{- if .Values.csi.cephfsLivenessMetricsPort }}
+        - name: CSI_CEPHFS_LIVENESS_METRICS_PORT
+          value: {{ .Values.csi.cephfsLivenessMetricsPort | quote }}
+{{- end }}
+{{- if .Values.csi.rbdGrpcMetricsPort }}
+        - name: CSI_RBD_GRPC_METRICS_PORT
+          value: {{ .Values.csi.rbdGrpcMetricsPort | quote }}
+{{- end }}
+{{- if .Values.csi.rbdLivenessMetricsPort }}
+        - name: CSI_RBD_LIVENESS_METRICS_PORT
+          value: {{ .Values.csi.rbdLivenessMetricsPort | quote }}
+{{- end }}
+{{- if .Values.csi.forceCephFSKernelClient }}
+        - name: CSI_FORCE_CEPHFS_KERNEL_CLIENT
+          value: {{ .Values.csi.forceCephFSKernelClient | quote }}
+{{- end }}
+{{- if .Values.csi.logLevel }}
+        - name: CSI_LOG_LEVEL
+          value: {{ .Values.csi.logLevel | quote }}
+{{- end }}
+{{- if .Values.csi.provisionerReplicas }}
+        - name: CSI_PROVISIONER_REPLICAS
+          value: {{ .Values.csi.provisionerReplicas | quote }}
+{{- end }}
+{{- if .Values.csi.csiRBDProvisionerResource }}
+        - name: CSI_RBD_PROVISIONER_RESOURCE
+          value: {{ .Values.csi.csiRBDProvisionerResource | quote }}
+{{- end }}
+{{- if .Values.csi.csiRBDPluginResource }}
+        - name: CSI_RBD_PLUGIN_RESOURCE
+          value: {{ .Values.csi.csiRBDPluginResource | quote }}
+{{- end }}
+{{- if .Values.csi.csiCephFSProvisionerResource }}
+        - name: CSI_CEPHFS_PROVISIONER_RESOURCE
+          value: {{ .Values.csi.csiCephFSProvisionerResource | quote }}
+{{- end }}
+{{- if .Values.csi.csiCephFSPluginResource }}
+        - name: CSI_CEPHFS_PLUGIN_RESOURCE
+          value: {{ .Values.csi.csiCephFSPluginResource | quote }}
+{{- end }}
+{{- end }}
         - name: ROOK_ENABLE_DISCOVERY_DAEMON
           value: "{{ .Values.enableDiscoveryDaemon }}"
+        - name: ROOK_CEPH_COMMANDS_TIMEOUT_SECONDS
+          value: "{{ .Values.cephCommandsTimeoutSeconds }}"
+        - name: ROOK_OBC_WATCH_OPERATOR_NAMESPACE
+          value: "{{ .Values.enableOBCWatchOperatorNamespace }}"
 
         - name: NODE_NAME
           valueFrom:
diff --git a/pkg/operator/ceph/cluster/cluster.go b/pkg/operator/ceph/cluster/cluster.go
index 3a6ffdb6b..c9cf926f4 100755
--- a/pkg/operator/ceph/cluster/cluster.go
+++ b/pkg/operator/ceph/cluster/cluster.go
@@ -22,7 +22,6 @@ import (
 	"fmt"
 	"os/exec"
 	"path"
-	"sync"
 	"syscall"
 
 	"github.com/pkg/errors"
@@ -67,7 +66,7 @@ type clusterHealth struct {
 	internalCancel context.CancelFunc
 }
 
-func newCluster(c *cephv1.CephCluster, context *clusterd.Context, csiMutex *sync.Mutex, ownerInfo *k8sutil.OwnerInfo) *cluster {
+func newCluster(c *cephv1.CephCluster, context *clusterd.Context, ownerInfo *k8sutil.OwnerInfo) *cluster {
 	return &cluster{
 		// at this phase of the cluster creation process, the identity components of the cluster are
 		// not yet established. we reserve this struct which is filled in as soon as the cluster's
@@ -79,7 +78,7 @@ func newCluster(c *cephv1.CephCluster, context *clusterd.Context, csiMutex *sync
 		namespacedName:     types.NamespacedName{Namespace: c.Namespace, Name: c.Name},
 		monitoringRoutines: make(map[string]*clusterHealth),
 		ownerInfo:          ownerInfo,
-		mons:               mon.New(context, c.Namespace, c.Spec, ownerInfo, csiMutex),
+		mons:               mon.New(context, c.Namespace, c.Spec, ownerInfo),
 	}
 }
 
diff --git a/pkg/operator/ceph/cluster/cluster_external.go b/pkg/operator/ceph/cluster/cluster_external.go
index 284d34a9a..fb97a9adf 100644
--- a/pkg/operator/ceph/cluster/cluster_external.go
+++ b/pkg/operator/ceph/cluster/cluster_external.go
@@ -111,7 +111,7 @@ func (c *ClusterController) configureExternalCephCluster(cluster *cluster) error
 	}
 
 	// Save CSI configmap
-	err = csi.SaveClusterConfig(c.context.Clientset, c.namespacedName.Namespace, cluster.ClusterInfo, c.csiConfigMutex)
+	err = csi.SaveClusterConfig(c.context.Clientset, c.namespacedName.Namespace, cluster.ClusterInfo, &csi.CsiClusterConfigEntry{Monitors: csi.MonEndpoints(cluster.ClusterInfo.Monitors)})
 	if err != nil {
 		return errors.Wrap(err, "failed to update csi cluster config")
 	}
diff --git a/pkg/operator/ceph/cluster/controller.go b/pkg/operator/ceph/cluster/controller.go
index d232a821f..f1fa42874 100644
--- a/pkg/operator/ceph/cluster/controller.go
+++ b/pkg/operator/ceph/cluster/controller.go
@@ -21,7 +21,6 @@ import (
 	"context"
 	"fmt"
 	"os"
-	"sync"
 
 	"github.com/coreos/pkg/capnslog"
 	"github.com/pkg/errors"
@@ -85,7 +84,6 @@ type ClusterController struct {
 	context        *clusterd.Context
 	rookImage      string
 	clusterMap     map[string]*cluster
-	csiConfigMutex *sync.Mutex
 	osdChecker     *osd.OSDHealthMonitor
 	client         client.Client
 	namespacedName types.NamespacedName
@@ -327,10 +325,9 @@ func (r *ReconcileCephCluster) reconcileDelete(cephCluster *cephv1.CephCluster)
 // NewClusterController create controller for watching cluster custom resources created
 func NewClusterController(context *clusterd.Context, rookImage string) *ClusterController {
 	return &ClusterController{
-		context:        context,
-		rookImage:      rookImage,
-		clusterMap:     make(map[string]*cluster),
-		csiConfigMutex: &sync.Mutex{},
+		context:    context,
+		rookImage:  rookImage,
+		clusterMap: make(map[string]*cluster),
 	}
 }
 
@@ -343,7 +340,7 @@ func (c *ClusterController) reconcileCephCluster(clusterObj *cephv1.CephCluster,
 	cluster, ok := c.clusterMap[clusterObj.Namespace]
 	if !ok {
 		// It's a new cluster so let's populate the struct
-		cluster = newCluster(clusterObj, c.context, c.csiConfigMutex, ownerInfo)
+		cluster = newCluster(clusterObj, c.context, ownerInfo)
 	}
 	cluster.namespacedName = c.namespacedName
 
diff --git a/pkg/operator/ceph/cluster/mon/mon.go b/pkg/operator/ceph/cluster/mon/mon.go
index 070ce2edc..bf7988ba3 100644
--- a/pkg/operator/ceph/cluster/mon/mon.go
+++ b/pkg/operator/ceph/cluster/mon/mon.go
@@ -120,7 +120,6 @@ type Cluster struct {
 	monTimeoutList     map[string]time.Time
 	mapping            *Mapping
 	ownerInfo          *k8sutil.OwnerInfo
-	csiConfigMutex     *sync.Mutex
 	isUpgrade          bool
 	arbiterMon         string
 }
@@ -164,7 +163,7 @@ type SchedulingResult struct {
 }
 
 // New creates an instance of a mon cluster
-func New(context *clusterd.Context, namespace string, spec cephv1.ClusterSpec, ownerInfo *k8sutil.OwnerInfo, csiConfigMutex *sync.Mutex) *Cluster {
+func New(context *clusterd.Context, namespace string, spec cephv1.ClusterSpec, ownerInfo *k8sutil.OwnerInfo) *Cluster {
 	return &Cluster{
 		context:        context,
 		spec:           spec,
@@ -175,8 +174,7 @@ func New(context *clusterd.Context, namespace string, spec cephv1.ClusterSpec, o
 		mapping: &Mapping{
 			Schedule: map[string]*MonScheduleInfo{},
 		},
-		ownerInfo:      ownerInfo,
-		csiConfigMutex: csiConfigMutex,
+		ownerInfo: ownerInfo,
 	}
 }
 
@@ -1042,7 +1040,7 @@ func (c *Cluster) saveMonConfig() error {
 		return errors.Wrap(err, "failed to write connection config for new mons")
 	}
 
-	if err := csi.SaveClusterConfig(c.context.Clientset, c.Namespace, c.ClusterInfo, c.csiConfigMutex); err != nil {
+	if err := csi.SaveClusterConfig(c.context.Clientset, c.Namespace, c.ClusterInfo, &csi.CsiClusterConfigEntry{Monitors: csi.MonEndpoints(c.ClusterInfo.Monitors)}); err != nil {
 		return errors.Wrap(err, "failed to update csi cluster config")
 	}
 
diff --git a/pkg/operator/ceph/csi/cluster_config.go b/pkg/operator/ceph/csi/cluster_config.go
index 03e76934f..3f4ad3b8c 100644
--- a/pkg/operator/ceph/csi/cluster_config.go
+++ b/pkg/operator/ceph/csi/cluster_config.go
@@ -33,15 +33,22 @@ import (
 )
 
 var (
-	logger = capnslog.NewPackageLogger("github.com/rook/rook", "ceph-csi")
+	logger      = capnslog.NewPackageLogger("github.com/rook/rook", "ceph-csi")
+	configMutex sync.Mutex
 )
 
-type csiClusterConfigEntry struct {
-	ClusterID string   `json:"clusterID"`
-	Monitors  []string `json:"monitors"`
+type CsiClusterConfigEntry struct {
+	ClusterID      string         `json:"clusterID"`
+	Monitors       []string       `json:"monitors"`
+	CephFS         *CsiCephFSSpec `json:"cephFS,omitempty"`
+	RadosNamespace string         `json:"radosNamespace,omitempty"`
 }
 
-type csiClusterConfig []csiClusterConfigEntry
+type CsiCephFSSpec struct {
+	SubvolumeGroup string `json:"subvolumeGroup,omitempty"`
+}
+
+type csiClusterConfig []CsiClusterConfigEntry
 
 // FormatCsiClusterConfig returns a json-formatted string containing
 // the cluster-to-mon mapping required to configure ceph csi.
@@ -79,7 +86,7 @@ func formatCsiClusterConfig(cc csiClusterConfig) (string, error) {
 	return string(ccJson), nil
 }
 
-func monEndpoints(mons map[string]*cephclient.MonInfo) []string {
+func MonEndpoints(mons map[string]*cephclient.MonInfo) []string {
 	endpoints := make([]string, 0)
 	for _, m := range mons {
 		endpoints = append(endpoints, m.Endpoint)
@@ -89,32 +96,59 @@ func monEndpoints(mons map[string]*cephclient.MonInfo) []string {
 
 // updateCsiClusterConfig returns a json-formatted string containing
 // the cluster-to-mon mapping required to configure ceph csi.
-func updateCsiClusterConfig(
-	curr, clusterKey string, mons map[string]*cephclient.MonInfo) (string, error) {
-
+func updateCsiClusterConfig(curr, clusterKey string, newCsiClusterConfigEntry *CsiClusterConfigEntry) (string, error) {
 	var (
 		cc     csiClusterConfig
-		centry csiClusterConfigEntry
+		centry CsiClusterConfigEntry
 		found  bool
 	)
+
 	cc, err := parseCsiClusterConfig(curr)
 	if err != nil {
 		return "", errors.Wrap(err, "failed to parse current csi cluster config")
 	}
 
+	// Regardless of which controllers call updateCsiClusterConfig(), the values will be preserved since
+	// a lock is acquired for the update operation. So concurrent updates (rare event) will block and
+	// wait for the other update to complete. Monitors and Subvolumegroup will be updated
+	// independently and won't collide.
 	for i, centry := range cc {
 		if centry.ClusterID == clusterKey {
-			centry.Monitors = monEndpoints(mons)
+			// If the new entry is nil, this means the entry is being deleted so remove it from the list
+			if newCsiClusterConfigEntry == nil {
+				cc = append(cc[:i], cc[i+1:]...)
+				found = true
+				break
+			}
+			centry.Monitors = newCsiClusterConfigEntry.Monitors
+			if newCsiClusterConfigEntry.CephFS != nil && newCsiClusterConfigEntry.CephFS.SubvolumeGroup != "" {
+				centry.CephFS = newCsiClusterConfigEntry.CephFS
+			}
+			if newCsiClusterConfigEntry.RadosNamespace != "" {
+				centry.RadosNamespace = newCsiClusterConfigEntry.RadosNamespace
+			}
 			found = true
 			cc[i] = centry
 			break
 		}
 	}
 	if !found {
-		centry.ClusterID = clusterKey
-		centry.Monitors = monEndpoints(mons)
-		cc = append(cc, centry)
+		// If it's the first time we create the cluster, the entry does not exist, so the removal
+		// will fail with a dangling pointer
+		if newCsiClusterConfigEntry != nil {
+			centry.ClusterID = clusterKey
+			centry.Monitors = newCsiClusterConfigEntry.Monitors
+			// Add a condition not to fill with empty values
+			if newCsiClusterConfigEntry.CephFS != nil && newCsiClusterConfigEntry.CephFS.SubvolumeGroup != "" {
+				centry.CephFS = newCsiClusterConfigEntry.CephFS
+			}
+			if newCsiClusterConfigEntry.RadosNamespace != "" {
+				centry.RadosNamespace = newCsiClusterConfigEntry.RadosNamespace
+			}
+			cc = append(cc, centry)
+		}
 	}
+
 	return formatCsiClusterConfig(cc)
 }
 
@@ -155,24 +189,24 @@ func CreateCsiConfigMap(namespace string, clientset kubernetes.Interface, ownerI
 // value that is provided to ceph-csi uses in the storage class.
 // The locker l is typically a mutex and is used to prevent the config
 // map from being updated for multiple clusters simultaneously.
-func SaveClusterConfig(
-	clientset kubernetes.Interface, clusterNamespace string,
-	clusterInfo *cephclient.ClusterInfo, l sync.Locker) error {
+func SaveClusterConfig(clientset kubernetes.Interface, clusterNamespace string, clusterInfo *cephclient.ClusterInfo, newCsiClusterConfigEntry *CsiClusterConfigEntry) error {
 	if !CSIEnabled() {
+		logger.Debug("skipping csi config update because csi is not enabled")
 		return nil
 	}
-	l.Lock()
-	defer l.Unlock()
+
+	configMutex.Lock()
+	defer configMutex.Unlock()
+
 	// csi is deployed into the same namespace as the operator
 	csiNamespace := os.Getenv(k8sutil.PodNamespaceEnvVar)
 	if csiNamespace == "" {
-		return errors.Errorf("namespace value missing for %s", k8sutil.PodNamespaceEnvVar)
+		return errors.Errorf("namespace value missing for %q", k8sutil.PodNamespaceEnvVar)
 	}
-	logger.Debugf("Using %+v for CSI ConfigMap Namespace", csiNamespace)
+	logger.Debugf("using %q for csi configmap namespace", csiNamespace)
 
 	// fetch current ConfigMap contents
-	configMap, err := clientset.CoreV1().ConfigMaps(csiNamespace).Get(clusterInfo.Context,
-		ConfigName, metav1.GetOptions{})
+	configMap, err := clientset.CoreV1().ConfigMaps(csiNamespace).Get(clusterInfo.Context, ConfigName, metav1.GetOptions{})
 	if err != nil {
 		return errors.Wrap(err, "failed to fetch current csi config map")
 	}
@@ -182,8 +216,8 @@ func SaveClusterConfig(
 	if currData == "" {
 		currData = "[]"
 	}
-	newData, err := updateCsiClusterConfig(
-		currData, clusterNamespace, clusterInfo.Monitors)
+
+	newData, err := updateCsiClusterConfig(currData, clusterNamespace, newCsiClusterConfigEntry)
 	if err != nil {
 		return errors.Wrap(err, "failed to update csi config map data")
 	}
@@ -191,7 +225,7 @@ func SaveClusterConfig(
 
 	// update ConfigMap with new contents
 	if _, err := clientset.CoreV1().ConfigMaps(csiNamespace).Update(clusterInfo.Context, configMap, metav1.UpdateOptions{}); err != nil {
-		return errors.Wrapf(err, "failed to update csi config map")
+		return errors.Wrap(err, "failed to update csi config map")
 	}
 
 	return nil
diff --git a/pkg/operator/ceph/csi/predicate.go b/pkg/operator/ceph/csi/predicate.go
index 2742475e9..bfe4055e8 100644
--- a/pkg/operator/ceph/csi/predicate.go
+++ b/pkg/operator/ceph/csi/predicate.go
@@ -22,7 +22,6 @@ import (
 
 	"github.com/google/go-cmp/cmp"
 	cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
-	"github.com/rook/rook/pkg/operator/ceph/controller"
 	opcontroller "github.com/rook/rook/pkg/operator/ceph/controller"
 	v1 "k8s.io/api/core/v1"
 	kerrors "k8s.io/apimachinery/pkg/api/errors"
@@ -47,7 +46,7 @@ func predicateController(ctx context.Context, c client.Client, opNamespace strin
 			// If a Ceph Cluster is created we want to reconcile the csi driver
 			if cephCluster, ok := e.Object.(*cephv1.CephCluster); ok {
 				// If there are more than one ceph cluster in the same namespace do not reconcile
-				if controller.DuplicateCephClusters(ctx, c, e.Object, false) {
+				if opcontroller.DuplicateCephClusters(ctx, c, e.Object, false) {
 					return false
 				}
 
diff --git a/pkg/operator/ceph/file/subvolumegroup/controller.go b/pkg/operator/ceph/file/subvolumegroup/controller.go
index c65434723..9c9af8fa6 100644
--- a/pkg/operator/ceph/file/subvolumegroup/controller.go
+++ b/pkg/operator/ceph/file/subvolumegroup/controller.go
@@ -40,7 +40,9 @@ import (
 	"github.com/rook/rook/pkg/clusterd"
 	"github.com/rook/rook/pkg/operator/ceph/cluster/mon"
 	opcontroller "github.com/rook/rook/pkg/operator/ceph/controller"
+	"github.com/rook/rook/pkg/operator/ceph/csi"
 	"github.com/rook/rook/pkg/operator/ceph/reporting"
+	"github.com/rook/rook/pkg/operator/k8sutil"
 	kerrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
@@ -175,9 +177,18 @@ func (r *ReconcileCephFilesystemSubVolumeGroup) reconcile(request reconcile.Requ
 		logger.Debugf("deleting subvolume group %q", cephFilesystemSubVolumeGroup.Name)
 		err := r.deleteSubVolumeGroup(cephFilesystemSubVolumeGroup)
 		if err != nil {
+			if strings.Contains(err.Error(), opcontroller.UninitializedCephConfigError) {
+				logger.Info(opcontroller.OperatorNotInitializedMessage)
+				return opcontroller.WaitForRequeueIfOperatorNotInitialized, nil
+			}
 			return reconcile.Result{}, errors.Wrapf(err, "failed to delete ceph ceph filesystem subvolume group %q", cephFilesystemSubVolumeGroup.Name)
 		}
 
+		err = csi.SaveClusterConfig(r.context.Clientset, buildClusterID(cephFilesystemSubVolumeGroup), r.clusterInfo, nil)
+		if err != nil {
+			return reconcile.Result{}, errors.Wrap(err, "failed to save cluster config")
+		}
+
 		// Remove finalizer
 		err = opcontroller.RemoveFinalizer(r.opManagerContext, r.client, cephFilesystemSubVolumeGroup)
 		if err != nil {
@@ -218,6 +229,20 @@ func (r *ReconcileCephFilesystemSubVolumeGroup) reconcile(request reconcile.Requ
 		return reconcile.Result{}, errors.Wrapf(err, "failed to create or update ceph filesystem subvolume group %q", cephFilesystemSubVolumeGroup.Name)
 	}
 
+	// Update CSI config map
+	// If the mon endpoints change, the mon health check go routine will take care of updating the
+	// config map, so no special care is needed in this controller
+	csiClusterConfigEntry := csi.CsiClusterConfigEntry{
+		Monitors: csi.MonEndpoints(r.clusterInfo.Monitors),
+		CephFS: &csi.CsiCephFSSpec{
+			SubvolumeGroup: cephFilesystemSubVolumeGroup.Name,
+		},
+	}
+	err = csi.SaveClusterConfig(r.context.Clientset, buildClusterID(cephFilesystemSubVolumeGroup), r.clusterInfo, &csiClusterConfigEntry)
+	if err != nil {
+		return reconcile.Result{}, errors.Wrap(err, "failed to save cluster config")
+	}
+
 	// Success! Let's update the status
 	r.updateStatus(r.client, request.NamespacedName, cephv1.ConditionReady)
 
@@ -241,12 +266,17 @@ func (r *ReconcileCephFilesystemSubVolumeGroup) createOrUpdateSubVolumeGroup(cep
 // Delete the ceph filesystem subvolume group
 func (r *ReconcileCephFilesystemSubVolumeGroup) deleteSubVolumeGroup(cephFilesystemSubVolumeGroup *cephv1.CephFilesystemSubVolumeGroup) error {
 	logger.Infof("deleting ceph filesystem subvolume group object %q", cephFilesystemSubVolumeGroup.Name)
-	if err := cephclient.DeleteCephFSSubVolumeGroup(r.context, r.clusterInfo, cephFilesystemSubVolumeGroup.Name, cephFilesystemSubVolumeGroup.Spec.FilesystemName); err != nil {
+	if err := cephclient.DeleteCephFSSubVolumeGroup(r.context, r.clusterInfo, cephFilesystemSubVolumeGroup.Spec.FilesystemName, cephFilesystemSubVolumeGroup.Name); err != nil {
 		code, ok := exec.ExitStatus(err)
+		// If the subvolumegroup does not exit, we should not return an error
+		if ok && code == int(syscall.ENOENT) {
+			logger.Debugf("ceph filesystem subvolume group %q do not exist", cephFilesystemSubVolumeGroup.Name)
+			return nil
+		}
 		// If the subvolumegroup has subvolumes the command will fail with:
 		// Error ENOTEMPTY: error in rmdir /volumes/csi
-		if ok && code != int(syscall.ENOTEMPTY) {
-			return errors.Wrapf(err, "failed to delete ceph filesystem subvolume group %q, remove the subvolumes first.", cephFilesystemSubVolumeGroup.Name)
+		if ok && (code == int(syscall.ENOTEMPTY)) {
+			return errors.Wrapf(err, "failed to delete ceph filesystem subvolume group %q, remove the subvolumes first", cephFilesystemSubVolumeGroup.Name)
 		}
 
 		return errors.Wrapf(err, "failed to delete ceph filesystem subvolume group %q", cephFilesystemSubVolumeGroup.Name)
@@ -272,9 +302,15 @@ func (r *ReconcileCephFilesystemSubVolumeGroup) updateStatus(client client.Clien
 	}
 
 	cephFilesystemSubVolumeGroup.Status.Phase = status
+	cephFilesystemSubVolumeGroup.Status.Info = map[string]string{"clusterID": buildClusterID(cephFilesystemSubVolumeGroup)}
 	if err := reporting.UpdateStatus(client, cephFilesystemSubVolumeGroup); err != nil {
 		logger.Errorf("failed to set ceph ceph filesystem subvolume group %q status to %q. %v", name, status, err)
 		return
 	}
 	logger.Debugf("ceph ceph filesystem subvolume group %q status updated to %q", name, status)
 }
+
+func buildClusterID(cephFilesystemSubVolumeGroup *cephv1.CephFilesystemSubVolumeGroup) string {
+	clusterID := fmt.Sprintf("%s-%s-file-%s", cephFilesystemSubVolumeGroup.Namespace, cephFilesystemSubVolumeGroup.Spec.FilesystemName, cephFilesystemSubVolumeGroup.Name)
+	return k8sutil.Hash(clusterID)
+}
diff --git a/pkg/operator/ceph/nfs/nfs.go b/pkg/operator/ceph/nfs/nfs.go
index 236140003..2da06a4b8 100644
--- a/pkg/operator/ceph/nfs/nfs.go
+++ b/pkg/operator/ceph/nfs/nfs.go
@@ -18,7 +18,6 @@ limitations under the License.
 package nfs
 
 import (
-	"crypto/sha256"
 	"fmt"
 
 	"github.com/banzaicloud/k8s-objectmatcher/patch"
@@ -215,13 +214,7 @@ func (r *ReconcileCephNFS) createConfigMap(n *cephv1.CephNFS, name string) (stri
 		}
 	}
 
-	h := sha256.New()
-	if _, err := h.Write([]byte(fmt.Sprintf("%v", configMap.Data))); err != nil {
-		return "", "", errors.Wrapf(err, "failed to get hash of ganesha config map")
-	}
-	configHash := fmt.Sprintf("%x", h.Sum(nil))
-
-	return configMap.Name, configHash, nil
+	return configMap.Name, k8sutil.Hash(fmt.Sprintf("%v", configMap.Data)), nil
 }
 
 // Down scale the ganesha server


Test patch:
diff --git a/pkg/operator/ceph/cluster/mon/drain_test.go b/pkg/operator/ceph/cluster/mon/drain_test.go
index f64e1f6e7..2ff565d21 100644
--- a/pkg/operator/ceph/cluster/mon/drain_test.go
+++ b/pkg/operator/ceph/cluster/mon/drain_test.go
@@ -18,7 +18,6 @@ package mon
 
 import (
 	"context"
-	"sync"
 	"testing"
 
 	cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
@@ -48,7 +47,7 @@ func createFakeCluster(t *testing.T, cephClusterObj *cephv1.CephCluster, k8sVers
 
 	cl := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects().Build()
 	clientset := test.New(t, 3)
-	c := New(&clusterd.Context{Client: cl, Clientset: clientset}, mockNamespace, cephClusterObj.Spec, ownerInfo, &sync.Mutex{})
+	c := New(&clusterd.Context{Client: cl, Clientset: clientset}, mockNamespace, cephClusterObj.Spec, ownerInfo)
 	test.SetFakeKubernetesVersion(clientset, k8sVersion)
 	return c
 }
diff --git a/pkg/operator/ceph/cluster/mon/health_test.go b/pkg/operator/ceph/cluster/mon/health_test.go
index f27a1a75b..7ba8724a8 100644
--- a/pkg/operator/ceph/cluster/mon/health_test.go
+++ b/pkg/operator/ceph/cluster/mon/health_test.go
@@ -22,7 +22,6 @@ import (
 	"io/ioutil"
 	"os"
 	"reflect"
-	"sync"
 	"testing"
 	"time"
 
@@ -66,7 +65,7 @@ func TestCheckHealth(t *testing.T) {
 		Executor:  executor,
 	}
 	ownerInfo := cephclient.NewMinimumOwnerInfoWithOwnerRef()
-	c := New(context, "ns", cephv1.ClusterSpec{}, ownerInfo, &sync.Mutex{})
+	c := New(context, "ns", cephv1.ClusterSpec{}, ownerInfo)
 	// clusterInfo is nil so we return err
 	err := c.checkHealth(ctx)
 	assert.NotNil(t, err)
@@ -155,7 +154,7 @@ func TestCheckHealth(t *testing.T) {
 }
 
 func TestSkipMonFailover(t *testing.T) {
-	c := New(&clusterd.Context{}, "ns", cephv1.ClusterSpec{}, nil, nil)
+	c := New(&clusterd.Context{}, "ns", cephv1.ClusterSpec{}, nil)
 	c.ClusterInfo = clienttest.CreateTestClusterInfo(1)
 	monName := "arb"
 
@@ -201,7 +200,7 @@ func TestEvictMonOnSameNode(t *testing.T) {
 	}
 	context := &clusterd.Context{Clientset: clientset, ConfigDir: configDir, Executor: executor}
 	ownerInfo := cephclient.NewMinimumOwnerInfoWithOwnerRef()
-	c := New(context, "ns", cephv1.ClusterSpec{}, ownerInfo, &sync.Mutex{})
+	c := New(context, "ns", cephv1.ClusterSpec{}, ownerInfo)
 	setCommonMonProperties(c, 1, cephv1.MonSpec{Count: 0}, "myversion")
 	c.maxMonID = 2
 	c.waitForStart = false
@@ -256,7 +255,7 @@ func TestScaleMonDeployment(t *testing.T) {
 	clientset := test.New(t, 1)
 	context := &clusterd.Context{Clientset: clientset}
 	ownerInfo := cephclient.NewMinimumOwnerInfoWithOwnerRef()
-	c := New(context, "ns", cephv1.ClusterSpec{}, ownerInfo, &sync.Mutex{})
+	c := New(context, "ns", cephv1.ClusterSpec{}, ownerInfo)
 	setCommonMonProperties(c, 1, cephv1.MonSpec{Count: 0, AllowMultiplePerNode: true}, "myversion")
 
 	name := "a"
@@ -307,7 +306,7 @@ func TestCheckHealthNotFound(t *testing.T) {
 		Executor:  executor,
 	}
 	ownerInfo := cephclient.NewMinimumOwnerInfoWithOwnerRef()
-	c := New(context, "ns", cephv1.ClusterSpec{}, ownerInfo, &sync.Mutex{})
+	c := New(context, "ns", cephv1.ClusterSpec{}, ownerInfo)
 	setCommonMonProperties(c, 2, cephv1.MonSpec{Count: 3, AllowMultiplePerNode: true}, "myversion")
 	c.waitForStart = false
 	defer os.RemoveAll(c.context.ConfigDir)
@@ -370,7 +369,7 @@ func TestAddRemoveMons(t *testing.T) {
 		Executor:  executor,
 	}
 	ownerInfo := cephclient.NewMinimumOwnerInfoWithOwnerRef()
-	c := New(context, "ns", cephv1.ClusterSpec{}, ownerInfo, &sync.Mutex{})
+	c := New(context, "ns", cephv1.ClusterSpec{}, ownerInfo)
 	setCommonMonProperties(c, 0, cephv1.MonSpec{Count: 5, AllowMultiplePerNode: true}, "myversion")
 	c.maxMonID = 0 // "a" is max mon id
 	c.waitForStart = false
diff --git a/pkg/operator/ceph/cluster/mon/mon_test.go b/pkg/operator/ceph/cluster/mon/mon_test.go
index 14e6f37db..67fb0b4af 100644
--- a/pkg/operator/ceph/cluster/mon/mon_test.go
+++ b/pkg/operator/ceph/cluster/mon/mon_test.go
@@ -25,7 +25,6 @@ import (
 	"reflect"
 	"strconv"
 	"strings"
-	"sync"
 	"testing"
 	"time"
 
@@ -273,7 +272,7 @@ func validateStart(t *testing.T, c *Cluster) {
 func TestPersistMons(t *testing.T) {
 	clientset := test.New(t, 1)
 	ownerInfo := cephclient.NewMinimumOwnerInfoWithOwnerRef()
-	c := New(&clusterd.Context{Clientset: clientset}, "ns", cephv1.ClusterSpec{Annotations: cephv1.AnnotationsSpec{cephv1.KeyClusterMetadata: cephv1.Annotations{"key": "value"}}}, ownerInfo, &sync.Mutex{})
+	c := New(&clusterd.Context{Clientset: clientset}, "ns", cephv1.ClusterSpec{Annotations: cephv1.AnnotationsSpec{cephv1.KeyClusterMetadata: cephv1.Annotations{"key": "value"}}}, ownerInfo)
 	setCommonMonProperties(c, 1, cephv1.MonSpec{Count: 3, AllowMultiplePerNode: true}, "myversion")
 
 	// Persist mon a
@@ -302,7 +301,7 @@ func TestSaveMonEndpoints(t *testing.T) {
 	configDir, _ := ioutil.TempDir("", "")
 	defer os.RemoveAll(configDir)
 	ownerInfo := cephclient.NewMinimumOwnerInfoWithOwnerRef()
-	c := New(&clusterd.Context{Clientset: clientset, ConfigDir: configDir}, "ns", cephv1.ClusterSpec{}, ownerInfo, &sync.Mutex{})
+	c := New(&clusterd.Context{Clientset: clientset, ConfigDir: configDir}, "ns", cephv1.ClusterSpec{}, ownerInfo)
 	setCommonMonProperties(c, 1, cephv1.MonSpec{Count: 3, AllowMultiplePerNode: true}, "myversion")
 
 	// create the initial config map
@@ -350,7 +349,7 @@ func TestMaxMonID(t *testing.T) {
 	configDir, _ := ioutil.TempDir("", "")
 	defer os.RemoveAll(configDir)
 	ownerInfo := cephclient.NewMinimumOwnerInfoWithOwnerRef()
-	c := New(&clusterd.Context{Clientset: clientset, ConfigDir: configDir}, "ns", cephv1.ClusterSpec{}, ownerInfo, &sync.Mutex{})
+	c := New(&clusterd.Context{Clientset: clientset, ConfigDir: configDir}, "ns", cephv1.ClusterSpec{}, ownerInfo)
 	c.ClusterInfo = clienttest.CreateTestClusterInfo(1)
 
 	// when the configmap is not found, the maxMonID is -1
diff --git a/pkg/operator/ceph/cluster/mon/node_test.go b/pkg/operator/ceph/cluster/mon/node_test.go
index 8fa24cf59..98bf7fbe8 100644
--- a/pkg/operator/ceph/cluster/mon/node_test.go
+++ b/pkg/operator/ceph/cluster/mon/node_test.go
@@ -19,7 +19,6 @@ package mon
 import (
 	"context"
 	"strings"
-	"sync"
 	"testing"
 
 	cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
@@ -37,7 +36,7 @@ import (
 func TestNodeAffinity(t *testing.T) {
 	ctx := context.TODO()
 	clientset := test.New(t, 4)
-	c := New(&clusterd.Context{Clientset: clientset}, "ns", cephv1.ClusterSpec{}, &k8sutil.OwnerInfo{}, &sync.Mutex{})
+	c := New(&clusterd.Context{Clientset: clientset}, "ns", cephv1.ClusterSpec{}, &k8sutil.OwnerInfo{})
 	setCommonMonProperties(c, 0, cephv1.MonSpec{Count: 3, AllowMultiplePerNode: true}, "myversion")
 
 	c.spec.Placement = map[cephv1.KeyType]cephv1.Placement{}
@@ -167,7 +166,7 @@ func TestPodMemory(t *testing.T) {
 
 func TestHostNetwork(t *testing.T) {
 	clientset := test.New(t, 3)
-	c := New(&clusterd.Context{Clientset: clientset}, "ns", cephv1.ClusterSpec{}, &k8sutil.OwnerInfo{}, &sync.Mutex{})
+	c := New(&clusterd.Context{Clientset: clientset}, "ns", cephv1.ClusterSpec{}, &k8sutil.OwnerInfo{})
 	setCommonMonProperties(c, 0, cephv1.MonSpec{Count: 3, AllowMultiplePerNode: true}, "myversion")
 
 	c.spec.Network.HostNetwork = true
diff --git a/pkg/operator/ceph/cluster/mon/service_test.go b/pkg/operator/ceph/cluster/mon/service_test.go
index 62b5dc574..c8cf31e36 100644
--- a/pkg/operator/ceph/cluster/mon/service_test.go
+++ b/pkg/operator/ceph/cluster/mon/service_test.go
@@ -18,7 +18,6 @@ package mon
 
 import (
 	"context"
-	"sync"
 	"testing"
 
 	cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
@@ -33,7 +32,7 @@ import (
 func TestCreateService(t *testing.T) {
 	ctx := context.TODO()
 	clientset := test.New(t, 1)
-	c := New(&clusterd.Context{Clientset: clientset}, "ns", cephv1.ClusterSpec{}, &k8sutil.OwnerInfo{}, &sync.Mutex{})
+	c := New(&clusterd.Context{Clientset: clientset}, "ns", cephv1.ClusterSpec{}, &k8sutil.OwnerInfo{})
 	c.ClusterInfo = client.AdminTestClusterInfo("rook-ceph")
 	m := &monConfig{ResourceName: "rook-ceph-mon-b", DaemonName: "b"}
 	clusterIP, err := c.createService(m)
diff --git a/pkg/operator/ceph/cluster/mon/spec_test.go b/pkg/operator/ceph/cluster/mon/spec_test.go
index 2b213e2be..c1f58ad9f 100644
--- a/pkg/operator/ceph/cluster/mon/spec_test.go
+++ b/pkg/operator/ceph/cluster/mon/spec_test.go
@@ -17,7 +17,6 @@ limitations under the License.
 package mon
 
 import (
-	"sync"
 	"testing"
 
 	cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
@@ -48,7 +47,6 @@ func testPodSpec(t *testing.T, monID string, pvc bool) {
 		"ns",
 		cephv1.ClusterSpec{},
 		ownerInfo,
-		&sync.Mutex{},
 	)
 	setCommonMonProperties(c, 0, cephv1.MonSpec{Count: 3, AllowMultiplePerNode: true}, "rook/rook:myversion")
 	c.spec.CephVersion = cephv1.CephVersionSpec{Image: "quay.io/ceph/ceph:myceph"}
@@ -98,7 +96,6 @@ func TestDeploymentPVCSpec(t *testing.T) {
 		"ns",
 		cephv1.ClusterSpec{},
 		ownerInfo,
-		&sync.Mutex{},
 	)
 	setCommonMonProperties(c, 0, cephv1.MonSpec{Count: 3, AllowMultiplePerNode: true}, "rook/rook:myversion")
 	c.spec.CephVersion = cephv1.CephVersionSpec{Image: "quay.io/ceph/ceph:myceph"}
@@ -158,7 +155,6 @@ func testRequiredDuringScheduling(t *testing.T, hostNetwork, allowMultiplePerNod
 		"ns",
 		cephv1.ClusterSpec{},
 		&k8sutil.OwnerInfo{},
-		&sync.Mutex{},
 	)
 
 	c.spec.Network.HostNetwork = hostNetwork
diff --git a/pkg/operator/ceph/csi/cluster_config_test.go b/pkg/operator/ceph/csi/cluster_config_test.go
index afa53e074..08c9e1cad 100644
--- a/pkg/operator/ceph/csi/cluster_config_test.go
+++ b/pkg/operator/ceph/csi/cluster_config_test.go
@@ -19,71 +19,142 @@ package csi
 import (
 	"testing"
 
-	cephclient "github.com/rook/rook/pkg/daemon/ceph/client"
 	"github.com/stretchr/testify/assert"
 )
 
 func TestUpdateCsiClusterConfig(t *testing.T) {
-	// initialize an empty list & add a simple mons list
-	mons := map[string]*cephclient.MonInfo{
-		"foo": {Name: "foo", Endpoint: "1.2.3.4:5000"},
+	csiClusterConfigEntry := CsiClusterConfigEntry{
+		Monitors: []string{"1.2.3.4:5000"},
 	}
-	s, err := updateCsiClusterConfig("[]", "alpha", mons)
-	assert.NoError(t, err)
-	assert.Equal(t, s,
-		`[{"clusterID":"alpha","monitors":["1.2.3.4:5000"]}]`)
-
-	// add a 2nd mon to the current cluster
-	mons["bar"] = &cephclient.MonInfo{
-		Name: "bar", Endpoint: "10.11.12.13:5000"}
-	s, err = updateCsiClusterConfig(s, "alpha", mons)
-	assert.NoError(t, err)
-	cc, err := parseCsiClusterConfig(s)
-	assert.NoError(t, err)
-	assert.Equal(t, len(cc), 1)
-	assert.Equal(t, cc[0].ClusterID, "alpha")
-	assert.Contains(t, cc[0].Monitors, "1.2.3.4:5000")
-	assert.Contains(t, cc[0].Monitors, "10.11.12.13:5000")
-	assert.Equal(t, len(cc[0].Monitors), 2)
-
-	// add a 2nd cluster with 3 mons
-	mons2 := map[string]*cephclient.MonInfo{
-		"flim": {Name: "flim", Endpoint: "20.1.1.1:5000"},
-		"flam": {Name: "flam", Endpoint: "20.1.1.2:5000"},
-		"blam": {Name: "blam", Endpoint: "20.1.1.3:5000"},
+	csiClusterConfigEntry2 := CsiClusterConfigEntry{
+		Monitors: []string{"20.1.1.1:5000", "20.1.1.2:5000", "20.1.1.3:5000"},
 	}
-	s, err = updateCsiClusterConfig(s, "beta", mons2)
-	assert.NoError(t, err)
-	cc, err = parseCsiClusterConfig(s)
-	assert.NoError(t, err)
-	assert.Equal(t, len(cc), 2)
-	assert.Equal(t, cc[0].ClusterID, "alpha")
-	assert.Contains(t, cc[0].Monitors, "1.2.3.4:5000")
-	assert.Contains(t, cc[0].Monitors, "10.11.12.13:5000")
-	assert.Equal(t, len(cc[0].Monitors), 2)
-	assert.Equal(t, cc[1].ClusterID, "beta")
-	assert.Contains(t, cc[1].Monitors, "20.1.1.1:5000")
-	assert.Contains(t, cc[1].Monitors, "20.1.1.2:5000")
-	assert.Contains(t, cc[1].Monitors, "20.1.1.3:5000")
-	assert.Equal(t, len(cc[1].Monitors), 3)
-
-	// remove a mon from the 2nd cluster
-	delete(mons2, "blam")
-	s, err = updateCsiClusterConfig(s, "beta", mons2)
-	assert.NoError(t, err)
-	cc, err = parseCsiClusterConfig(s)
-	assert.NoError(t, err)
-	assert.Equal(t, len(cc), 2)
-	assert.Equal(t, cc[0].ClusterID, "alpha")
-	assert.Contains(t, cc[0].Monitors, "1.2.3.4:5000")
-	assert.Contains(t, cc[0].Monitors, "10.11.12.13:5000")
-	assert.Equal(t, len(cc[0].Monitors), 2)
-	assert.Equal(t, cc[1].ClusterID, "beta")
-	assert.Contains(t, cc[1].Monitors, "20.1.1.1:5000")
-	assert.Contains(t, cc[1].Monitors, "20.1.1.2:5000")
-	assert.Equal(t, len(cc[1].Monitors), 2)
-
-	// does it return error on garbage input?
-	_, err = updateCsiClusterConfig("qqq", "beta", mons2)
-	assert.Error(t, err)
+	csiClusterConfigEntry3 := CsiClusterConfigEntry{
+		Monitors: []string{"10.1.1.1:5000", "10.1.1.2:5000", "10.1.1.3:5000"},
+		CephFS: &CsiCephFSSpec{
+			SubvolumeGroup: "mygroup",
+		},
+	}
+	var s string
+	var err error
+
+	t.Run("add a simple mons list", func(t *testing.T) {
+		s, err = updateCsiClusterConfig("[]", "alpha", &csiClusterConfigEntry)
+		assert.NoError(t, err)
+		assert.Equal(t, s,
+			`[{"clusterID":"alpha","monitors":["1.2.3.4:5000"]}]`)
+	})
+
+	t.Run("add a 2nd mon to the current cluster", func(t *testing.T) {
+		csiClusterConfigEntry.Monitors = append(csiClusterConfigEntry.Monitors, "10.11.12.13:5000")
+		s, err = updateCsiClusterConfig(s, "alpha", &csiClusterConfigEntry)
+		assert.NoError(t, err)
+		cc, err := parseCsiClusterConfig(s)
+		assert.NoError(t, err)
+		assert.Equal(t, 1, len(cc))
+		assert.Equal(t, "alpha", cc[0].ClusterID)
+		assert.Contains(t, cc[0].Monitors, "1.2.3.4:5000")
+		assert.Contains(t, cc[0].Monitors, "10.11.12.13:5000")
+		assert.Equal(t, 2, len(cc[0].Monitors))
+	})
+
+	t.Run("add a 2nd cluster with 3 mons", func(t *testing.T) {
+		s, err = updateCsiClusterConfig(s, "beta", &csiClusterConfigEntry2)
+		assert.NoError(t, err)
+		cc, err := parseCsiClusterConfig(s)
+		assert.NoError(t, err)
+		assert.Equal(t, 2, len(cc))
+		assert.Equal(t, "alpha", cc[0].ClusterID)
+		assert.Contains(t, cc[0].Monitors, "1.2.3.4:5000")
+		assert.Contains(t, cc[0].Monitors, "10.11.12.13:5000")
+		assert.Equal(t, 2, len(cc[0].Monitors))
+		assert.Equal(t, "beta", cc[1].ClusterID)
+		assert.Contains(t, cc[1].Monitors, "20.1.1.1:5000")
+		assert.Contains(t, cc[1].Monitors, "20.1.1.2:5000")
+		assert.Contains(t, cc[1].Monitors, "20.1.1.3:5000")
+		assert.Equal(t, 3, len(cc[1].Monitors))
+	})
+
+	t.Run("remove a mon from the 2nd cluster", func(t *testing.T) {
+		i := 2
+		// Remove last element of the slice
+		csiClusterConfigEntry2.Monitors = append(csiClusterConfigEntry2.Monitors[:i], csiClusterConfigEntry2.Monitors[i+1:]...)
+		s, err = updateCsiClusterConfig(s, "beta", &csiClusterConfigEntry2)
+		assert.NoError(t, err)
+		cc, err := parseCsiClusterConfig(s)
+		assert.NoError(t, err)
+		assert.Equal(t, 2, len(cc))
+		assert.Equal(t, "alpha", cc[0].ClusterID)
+		assert.Contains(t, cc[0].Monitors, "1.2.3.4:5000")
+		assert.Contains(t, cc[0].Monitors, "10.11.12.13:5000")
+		assert.Equal(t, 2, len(cc[0].Monitors))
+		assert.Equal(t, "beta", cc[1].ClusterID)
+		assert.Contains(t, cc[1].Monitors, "20.1.1.1:5000")
+		assert.Contains(t, cc[1].Monitors, "20.1.1.2:5000")
+		assert.Equal(t, 2, len(cc[1].Monitors))
+	})
+
+	t.Run("add a 3rd cluster with subvolumegroup", func(t *testing.T) {
+		s, err = updateCsiClusterConfig(s, "baba", &csiClusterConfigEntry3)
+		assert.NoError(t, err)
+		cc, err := parseCsiClusterConfig(s)
+		assert.NoError(t, err)
+		assert.Equal(t, 3, len(cc))
+		assert.Equal(t, "alpha", cc[0].ClusterID)
+		assert.Equal(t, 2, len(cc[0].Monitors))
+		assert.Equal(t, "beta", cc[1].ClusterID)
+		assert.Equal(t, "baba", cc[2].ClusterID)
+		assert.Equal(t, "10.1.1.1:5000", cc[2].Monitors[0])
+		assert.Equal(t, 3, len(cc[2].Monitors))
+		assert.Equal(t, "mygroup", cc[2].CephFS.SubvolumeGroup)
+
+	})
+
+	t.Run("add a 4th mon to the 3rd cluster and subvolumegroup is preserved", func(t *testing.T) {
+		csiClusterConfigEntry3.Monitors = append(csiClusterConfigEntry3.Monitors, "10.11.12.13:5000")
+		s, err = updateCsiClusterConfig(s, "baba", &csiClusterConfigEntry3)
+		assert.NoError(t, err)
+		cc, err := parseCsiClusterConfig(s)
+		assert.NoError(t, err)
+		assert.Equal(t, 3, len(cc))
+		assert.Equal(t, 4, len(cc[2].Monitors))
+		assert.Equal(t, "mygroup", cc[2].CephFS.SubvolumeGroup)
+	})
+
+	t.Run("remove subvolumegroup", func(t *testing.T) {
+		csiClusterConfigEntry3.CephFS.SubvolumeGroup = ""
+		s, err = updateCsiClusterConfig(s, "baba", nil)
+		assert.NoError(t, err)
+		cc, err := parseCsiClusterConfig(s)
+		assert.NoError(t, err)
+		assert.Equal(t, 2, len(cc)) // we only have 2 clusters now
+	})
+
+	t.Run("add subvolumegroup and mons after", func(t *testing.T) {
+		csiClusterConfigEntry4 := CsiClusterConfigEntry{
+			CephFS: &CsiCephFSSpec{
+				SubvolumeGroup: "mygroup2",
+			},
+		}
+		s, err = updateCsiClusterConfig(s, "quatre", &csiClusterConfigEntry4)
+		assert.NoError(t, err)
+		cc, err := parseCsiClusterConfig(s)
+		assert.NoError(t, err)
+		assert.Equal(t, 3, len(cc), cc)
+		assert.Equal(t, 0, len(cc[2].Monitors))
+		assert.Equal(t, "mygroup2", cc[2].CephFS.SubvolumeGroup, cc)
+
+		csiClusterConfigEntry4.Monitors = []string{"10.1.1.1:5000", "10.1.1.2:5000", "10.1.1.3:5000"}
+		s, err = updateCsiClusterConfig(s, "quatre", &csiClusterConfigEntry4)
+		assert.NoError(t, err)
+		cc, err = parseCsiClusterConfig(s)
+		assert.NoError(t, err)
+		assert.Equal(t, "mygroup2", cc[2].CephFS.SubvolumeGroup)
+		assert.Equal(t, 3, len(cc[2].Monitors))
+	})
+
+	t.Run("does it return error on garbage input?", func(t *testing.T) {
+		_, err = updateCsiClusterConfig("qqq", "beta", &csiClusterConfigEntry2)
+		assert.Error(t, err)
+	})
 }
diff --git a/pkg/operator/ceph/file/subvolumegroup/controller_test.go b/pkg/operator/ceph/file/subvolumegroup/controller_test.go
index 6fffbdea5..1d0e5a555 100644
--- a/pkg/operator/ceph/file/subvolumegroup/controller_test.go
+++ b/pkg/operator/ceph/file/subvolumegroup/controller_test.go
@@ -26,6 +26,7 @@ import (
 	rookclient "github.com/rook/rook/pkg/client/clientset/versioned/fake"
 	"github.com/rook/rook/pkg/client/clientset/versioned/scheme"
 	"github.com/rook/rook/pkg/clusterd"
+	"github.com/rook/rook/pkg/operator/ceph/csi"
 	"github.com/rook/rook/pkg/operator/k8sutil"
 	testop "github.com/rook/rook/pkg/operator/test"
 	exectest "github.com/rook/rook/pkg/util/exec/test"
@@ -206,15 +207,39 @@ func TestCephClientController(t *testing.T) {
 			client:           cl,
 			scheme:           s,
 			context:          c,
-			opManagerContext: context.TODO(),
+			opManagerContext: ctx,
 		}
 
+		// Enable CSI
+		csi.EnableRBD = true
+		os.Setenv("POD_NAMESPACE", namespace)
+		// Create CSI config map
+		ownerRef := &metav1.OwnerReference{}
+		ownerInfo := k8sutil.NewOwnerInfoWithOwnerRef(ownerRef, "")
+		err = csi.CreateCsiConfigMap(namespace, c.Clientset, ownerInfo)
+		assert.NoError(t, err)
+
 		res, err := r.Reconcile(ctx, req)
 		assert.NoError(t, err)
 		assert.False(t, res.Requeue)
 
-		err = r.client.Get(context.TODO(), req.NamespacedName, cephFilesystemSubVolumeGroup)
+		err = r.client.Get(ctx, req.NamespacedName, cephFilesystemSubVolumeGroup)
 		assert.NoError(t, err)
 		assert.Equal(t, cephv1.ConditionReady, cephFilesystemSubVolumeGroup.Status.Phase)
+		assert.NotEmpty(t, cephFilesystemSubVolumeGroup.Status.Info["clusterID"])
+
+		// test that csi configmap is created
+		cm, err := c.Clientset.CoreV1().ConfigMaps(namespace).Get(ctx, csi.ConfigName, metav1.GetOptions{})
+		assert.NoError(t, err)
+		assert.NotEmpty(t, cm.Data[csi.ConfigKey])
+		assert.Contains(t, cm.Data[csi.ConfigKey], "clusterID")
+		assert.Contains(t, cm.Data[csi.ConfigKey], "group-a")
 	})
 }
+
+func Test_buildClusterID(t *testing.T) {
+	longName := "foooooooooooooooooooooooooooooooooooooooooooo"
+	cephFilesystemSubVolumeGroup := &cephv1.CephFilesystemSubVolumeGroup{ObjectMeta: metav1.ObjectMeta{Namespace: "rook-ceph", Name: longName}, Spec: cephv1.CephFilesystemSubVolumeGroupSpec{FilesystemName: "myfs"}}
+	clusterID := buildClusterID(cephFilesystemSubVolumeGroup)
+	assert.Equal(t, "29e92135b7e8c014079b9f9f3566777d", clusterID)
+}
diff --git a/tests/framework/installer/ceph_helm_installer.go b/tests/framework/installer/ceph_helm_installer.go
index fd07352d4..f61a6feed 100644
--- a/tests/framework/installer/ceph_helm_installer.go
+++ b/tests/framework/installer/ceph_helm_installer.go
@@ -62,6 +62,9 @@ func (h *CephInstaller) configureRookOperatorViaHelm(upgrade bool) error {
 	if err := h.k8shelper.CreateNamespace(h.settings.OperatorNamespace); err != nil {
 		return errors.Errorf("failed to create namespace %s. %v", h.settings.Namespace, err)
 	}
+	if err := h.k8shelper.CreateOpConfigMap(h.settings.OperatorNamespace); err != nil {
+		return errors.Errorf("failed to create operator config map. %v", err)
+	}
 	if err := h.startAdmissionController(); err != nil {
 		return errors.Errorf("failed to start admission controllers. %v", err)
 	}
diff --git a/tests/framework/utils/k8s_helper.go b/tests/framework/utils/k8s_helper.go
index f57440c11..8d4d71f57 100644
--- a/tests/framework/utils/k8s_helper.go
+++ b/tests/framework/utils/k8s_helper.go
@@ -37,6 +37,7 @@ import (
 	rookclient "github.com/rook/rook/pkg/client/clientset/versioned"
 	"github.com/rook/rook/pkg/clusterd"
 	"github.com/rook/rook/pkg/operator/ceph/cluster/crash"
+	"github.com/rook/rook/pkg/operator/ceph/controller"
 	"github.com/rook/rook/pkg/operator/k8sutil"
 	"github.com/rook/rook/pkg/util/exec"
 	"github.com/stretchr/testify/require"
@@ -374,6 +375,22 @@ func (k8sh *K8sHelper) CreateNamespace(namespace string) error {
 	return nil
 }
 
+func (k8sh *K8sHelper) CreateOpConfigMap(namespace string) error {
+	ctx := context.TODO()
+	cm := &v1.ConfigMap{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      controller.OperatorSettingConfigMapName,
+			Namespace: namespace,
+		},
+	}
+	_, err := k8sh.Clientset.CoreV1().ConfigMaps(namespace).Create(ctx, cm, metav1.CreateOptions{})
+	if err != nil && !kerrors.IsAlreadyExists(err) {
+		return fmt.Errorf("failed to create operator config map %s. %+v", controller.OperatorSettingConfigMapName, err)
+	}
+
+	return nil
+}
+
 func (k8sh *K8sHelper) CountPodsWithLabel(label string, namespace string) (int, error) {
 	ctx := context.TODO()
 	options := metav1.ListOptions{LabelSelector: label}