From 82f8be1d1cfed2da38f10509c4f14b539e445b45 Mon Sep 17 00:00:00 2001 From: Nilushan Costa Date: Mon, 22 Apr 2024 15:58:41 +0530 Subject: [PATCH] Add support for adding CSI volumes as Additional Volumes (#784) ### Description This PR adds the capability to add CSI volumes as AdditionalVolumes in the OpenSearch cluster ### Issues Resolved Closes https://github.com/opensearch-project/opensearch-k8s-operator/issues/275 ### Check List - [x] Commits are signed per the DCO using --signoff - [x] Unittest added for the new/changed functionality and all unit tests are successful - [x] Customer-visible features documented - [x] No linter warnings (`make lint`) If CRDs are changed: - [x] CRD YAMLs updated (`make manifests`) and also copied into the helm chart - [x] Changes to CRDs documented Please refer to the [PR guidelines](https://github.com/opensearch-project/opensearch-k8s-operator/blob/main/docs/developing.md#submitting-a-pr) before submitting this pull request. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. For more information on following Developer Certificate of Origin and signing off your commits, please check [here](https://github.com/opensearch-project/OpenSearch/blob/main/CONTRIBUTING.md#developer-certificate-of-origin). --------- Signed-off-by: Nilushan Costa --- ...ensearch.opster.io_opensearchclusters.yaml | 90 +++++++++++++++++++ docs/designs/crd.md | 56 ++++++++---- docs/userguide/main.md | 10 ++- .../api/v1/opensearch_types.go | 2 + .../api/v1/zz_generated.deepcopy.go | 5 ++ ...ensearch.opster.io_opensearchclusters.yaml | 90 +++++++++++++++++++ .../pkg/reconcilers/util/util.go | 12 ++- .../pkg/reconcilers/util/util_test.go | 43 +++++++++ 8 files changed, 289 insertions(+), 19 deletions(-) diff --git a/charts/opensearch-operator/files/opensearch.opster.io_opensearchclusters.yaml b/charts/opensearch-operator/files/opensearch.opster.io_opensearchclusters.yaml index 40b1d274..c52e6c84 100644 --- a/charts/opensearch-operator/files/opensearch.opster.io_opensearchclusters.yaml +++ b/charts/opensearch-operator/files/opensearch.opster.io_opensearchclusters.yaml @@ -1022,6 +1022,51 @@ spec: type: boolean type: object x-kubernetes-map-type: atomic + csi: + description: CSI object to use to populate the volume + properties: + driver: + description: |- + driver is the name of the CSI driver that handles this volume. + Consult with your admin for the correct name as registered in the cluster. + type: string + fsType: + description: |- + fsType to mount. Ex. "ext4", "xfs", "ntfs". + If not provided, the empty value is passed to the associated CSI driver + which will determine the default filesystem to apply. + type: string + nodePublishSecretRef: + description: |- + nodePublishSecretRef is a reference to the secret object containing + sensitive information to pass to the CSI driver to complete the CSI + NodePublishVolume and NodeUnpublishVolume calls. + This field is optional, and may be empty if no secret is required. If the + secret object contains more than one secret, all secret references are passed. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + description: |- + readOnly specifies a read-only configuration for the volume. + Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: |- + volumeAttributes stores driver-specific properties that are passed to the CSI + driver. Consult your driver's documentation for supported values. + type: object + required: + - driver + type: object emptyDir: description: EmptyDir to use to populate the volume properties: @@ -2645,6 +2690,51 @@ spec: type: boolean type: object x-kubernetes-map-type: atomic + csi: + description: CSI object to use to populate the volume + properties: + driver: + description: |- + driver is the name of the CSI driver that handles this volume. + Consult with your admin for the correct name as registered in the cluster. + type: string + fsType: + description: |- + fsType to mount. Ex. "ext4", "xfs", "ntfs". + If not provided, the empty value is passed to the associated CSI driver + which will determine the default filesystem to apply. + type: string + nodePublishSecretRef: + description: |- + nodePublishSecretRef is a reference to the secret object containing + sensitive information to pass to the CSI driver to complete the CSI + NodePublishVolume and NodeUnpublishVolume calls. + This field is optional, and may be empty if no secret is required. If the + secret object contains more than one secret, all secret references are passed. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + description: |- + readOnly specifies a read-only configuration for the volume. + Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: |- + volumeAttributes stores driver-specific properties that are passed to the CSI + driver. Consult your driver's documentation for supported values. + type: object + required: + - driver + type: object emptyDir: description: EmptyDir to use to populate the volume properties: diff --git a/docs/designs/crd.md b/docs/designs/crd.md index 80401608..6e5f189b 100644 --- a/docs/designs/crd.md +++ b/docs/designs/crd.md @@ -672,22 +672,31 @@ Monitoring TLS configuration options Every Keystore Value defines a secret to pull secrets from. - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + +
secretcorev1.LocalObjectReferenceDefine secret that contains key value pairstrue-
keyMappingsmapDefine key mappings from secret to keystore entry. Example: "old: new" creates a keystore entry "new" with the value from the secret entry "old". When a map is provided, only the specified keys are loaded from the secret, so use "key: key" to load a key that should not be renamed.false-
NameTypeDescriptionRequireddefault
secretcorev1.LocalObjectReferenceDefine secret that contains key value pairstrue-
keyMappingsmapDefine key mappings from secret to keystore entry. Example: "old: new" creates a keystore entry "new" with the value from the secret entry "old". When a map is provided, only the specified keys are loaded from the secret, so use "key: key" to load a key that should not be renamed.false-

@@ -696,6 +705,15 @@ Every Keystore Value defines a secret to pull secrets from. AdditionalVolume object define additional volume and volumeMount + + + + + + + + + @@ -739,6 +757,12 @@ AdditionalVolume object define additional volume and volumeMount + + + + + +
NameTypeDescriptionRequireddefault
nameDefines Secret object to be mounted false -
csicorev1.CSIVolumeSourceDefines the CSI object to be mountedfalse-
diff --git a/docs/userguide/main.md b/docs/userguide/main.md index 3c033694..eea812bd 100644 --- a/docs/userguide/main.md +++ b/docs/userguide/main.md @@ -698,7 +698,7 @@ spec: ### Additional Volumes -Sometimes it is neccessary to mount ConfigMaps, Secrets or emptyDir into the Opensearch pods as volumes to provide additional configuration (e.g. plugin config files). This can be achieved by providing an array of additional volumes to mount to the custom resource. This option is located in either `spec.general.additionalVolumes` or `spec.dashboards.additionalVolumes`. The format is as follows: +Sometimes it is neccessary to mount ConfigMaps, Secrets, emptyDir or CSI volumes into the Opensearch pods as volumes to provide additional configuration (e.g. plugin config files). This can be achieved by providing an array of additional volumes to mount to the custom resource. This option is located in either `spec.general.additionalVolumes` or `spec.dashboards.additionalVolumes`. The format is as follows: ```yaml spec: @@ -713,6 +713,14 @@ spec: - name: temp path: /tmp emptyDir: {} + - name: example-csi-volume + path: /path/to/mount/volume + #subPath: "subpath" # Add this to mount the CSI volume at a specific subpath + csi: + driver: csi-driver-name + readOnly: true + volumeAttributes: + secretProviderClass: example-secret-provider-class dashboards: additionalVolumes: - name: example-secret diff --git a/opensearch-operator/api/v1/opensearch_types.go b/opensearch-operator/api/v1/opensearch_types.go index 9f1f91d9..8deee4ca 100644 --- a/opensearch-operator/api/v1/opensearch_types.go +++ b/opensearch-operator/api/v1/opensearch_types.go @@ -293,6 +293,8 @@ type AdditionalVolume struct { ConfigMap *corev1.ConfigMapVolumeSource `json:"configMap,omitempty"` // EmptyDir to use to populate the volume EmptyDir *corev1.EmptyDirVolumeSource `json:"emptyDir,omitempty"` + // CSI object to use to populate the volume + CSI *corev1.CSIVolumeSource `json:"csi,omitempty"` // Whether to restart the pods on content change RestartPods bool `json:"restartPods,omitempty"` } diff --git a/opensearch-operator/api/v1/zz_generated.deepcopy.go b/opensearch-operator/api/v1/zz_generated.deepcopy.go index 92155bc1..9fe9637a 100644 --- a/opensearch-operator/api/v1/zz_generated.deepcopy.go +++ b/opensearch-operator/api/v1/zz_generated.deepcopy.go @@ -147,6 +147,11 @@ func (in *AdditionalVolume) DeepCopyInto(out *AdditionalVolume) { *out = new(corev1.EmptyDirVolumeSource) (*in).DeepCopyInto(*out) } + if in.CSI != nil { + in, out := &in.CSI, &out.CSI + *out = new(corev1.CSIVolumeSource) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalVolume. diff --git a/opensearch-operator/config/crd/bases/opensearch.opster.io_opensearchclusters.yaml b/opensearch-operator/config/crd/bases/opensearch.opster.io_opensearchclusters.yaml index 40b1d274..c52e6c84 100644 --- a/opensearch-operator/config/crd/bases/opensearch.opster.io_opensearchclusters.yaml +++ b/opensearch-operator/config/crd/bases/opensearch.opster.io_opensearchclusters.yaml @@ -1022,6 +1022,51 @@ spec: type: boolean type: object x-kubernetes-map-type: atomic + csi: + description: CSI object to use to populate the volume + properties: + driver: + description: |- + driver is the name of the CSI driver that handles this volume. + Consult with your admin for the correct name as registered in the cluster. + type: string + fsType: + description: |- + fsType to mount. Ex. "ext4", "xfs", "ntfs". + If not provided, the empty value is passed to the associated CSI driver + which will determine the default filesystem to apply. + type: string + nodePublishSecretRef: + description: |- + nodePublishSecretRef is a reference to the secret object containing + sensitive information to pass to the CSI driver to complete the CSI + NodePublishVolume and NodeUnpublishVolume calls. + This field is optional, and may be empty if no secret is required. If the + secret object contains more than one secret, all secret references are passed. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + description: |- + readOnly specifies a read-only configuration for the volume. + Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: |- + volumeAttributes stores driver-specific properties that are passed to the CSI + driver. Consult your driver's documentation for supported values. + type: object + required: + - driver + type: object emptyDir: description: EmptyDir to use to populate the volume properties: @@ -2645,6 +2690,51 @@ spec: type: boolean type: object x-kubernetes-map-type: atomic + csi: + description: CSI object to use to populate the volume + properties: + driver: + description: |- + driver is the name of the CSI driver that handles this volume. + Consult with your admin for the correct name as registered in the cluster. + type: string + fsType: + description: |- + fsType to mount. Ex. "ext4", "xfs", "ntfs". + If not provided, the empty value is passed to the associated CSI driver + which will determine the default filesystem to apply. + type: string + nodePublishSecretRef: + description: |- + nodePublishSecretRef is a reference to the secret object containing + sensitive information to pass to the CSI driver to complete the CSI + NodePublishVolume and NodeUnpublishVolume calls. + This field is optional, and may be empty if no secret is required. If the + secret object contains more than one secret, all secret references are passed. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + description: |- + readOnly specifies a read-only configuration for the volume. + Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: |- + volumeAttributes stores driver-specific properties that are passed to the CSI + driver. Consult your driver's documentation for supported values. + type: object + required: + - driver + type: object emptyDir: description: EmptyDir to use to populate the volume properties: diff --git a/opensearch-operator/pkg/reconcilers/util/util.go b/opensearch-operator/pkg/reconcilers/util/util.go index f812d400..cde53a22 100644 --- a/opensearch-operator/pkg/reconcilers/util/util.go +++ b/opensearch-operator/pkg/reconcilers/util/util.go @@ -127,14 +127,22 @@ func CreateAdditionalVolumes( }, }) } + if volumeConfig.CSI != nil { + retVolumes = append(retVolumes, corev1.Volume{ + Name: volumeConfig.Name, + VolumeSource: corev1.VolumeSource{ + CSI: volumeConfig.CSI, + }, + }) + } if volumeConfig.RestartPods { namesIndex[volumeConfig.Name] = i names = append(names, volumeConfig.Name) } subPath := "" - // SubPaths are only supported for ConfigMaps and Secrets - if volumeConfig.ConfigMap != nil || volumeConfig.Secret != nil { + // SubPaths are only supported for ConfigMaps, Secrets and CSI volumes + if volumeConfig.ConfigMap != nil || volumeConfig.Secret != nil || volumeConfig.CSI != nil { subPath = strings.TrimSpace(volumeConfig.SubPath) } diff --git a/opensearch-operator/pkg/reconcilers/util/util_test.go b/opensearch-operator/pkg/reconcilers/util/util_test.go index 30620d8f..fb5f7756 100644 --- a/opensearch-operator/pkg/reconcilers/util/util_test.go +++ b/opensearch-operator/pkg/reconcilers/util/util_test.go @@ -88,4 +88,47 @@ var _ = Describe("Additional volumes", func() { Expect(volumeMount[0].SubPath).To(BeEmpty()) }) }) + + When("CSI volume is added", func() { + It("Should have CSIVolumeSource fields", func() { + readOnly := true + volumeConfigs[0].CSI = &v1.CSIVolumeSource{ + Driver: "testDriver", + ReadOnly: &readOnly, + VolumeAttributes: map[string]string{ + "secretProviderClass": "testSecretProviderClass", + }, + NodePublishSecretRef: &v1.LocalObjectReference{ + Name: "testSecret", + }, + } + + volume, _, _, _ := CreateAdditionalVolumes(mockClient, namespace, volumeConfigs) + Expect(volume[0].CSI.Driver).To(Equal("testDriver")) + Expect(*volume[0].CSI.ReadOnly).Should(BeTrue()) + Expect(volume[0].CSI.VolumeAttributes["secretProviderClass"]).To(Equal("testSecretProviderClass")) + Expect(volume[0].CSI.NodePublishSecretRef.Name).To(Equal("testSecret")) + }) + }) + + When("CSI volume is added with subPath", func() { + It("Should have the subPath", func() { + volumeConfigs[0].CSI = &v1.CSIVolumeSource{} + volumeConfigs[0].SubPath = "c" + + _, volumeMount, _, _ := CreateAdditionalVolumes(mockClient, namespace, volumeConfigs) + Expect(volumeMount[0].MountPath).To(Equal("myPath/a/b")) + Expect(volumeMount[0].SubPath).To(Equal("c")) + }) + }) + + When("CSI volume is added without subPath", func() { + It("Should not have the subPath", func() { + volumeConfigs[0].CSI = &v1.CSIVolumeSource{} + + _, volumeMount, _, _ := CreateAdditionalVolumes(mockClient, namespace, volumeConfigs) + Expect(volumeMount[0].MountPath).To(Equal("myPath/a/b")) + Expect(volumeMount[0].SubPath).To(BeEmpty()) + }) + }) })