[ATMOSPHERE-592][stable/2023.1] Add support for nfs_shares_config (#2144)

This is an automated cherry-pick of #2121
/assign larainema
Depends-On: #2148
diff --git a/charts/cinder/templates/configmap-etc.yaml b/charts/cinder/templates/configmap-etc.yaml
index 1a20ea8..97bfe0f 100644
--- a/charts/cinder/templates/configmap-etc.yaml
+++ b/charts/cinder/templates/configmap-etc.yaml
@@ -165,6 +165,14 @@
 {{- $formatter_fluent := dict "class" "oslo_log.formatters.FluentFormatter" -}}
 {{- $_ := set .Values.conf.logging "formatter_fluent" $formatter_fluent -}}
 {{- end -}}
+
+{{- range $key, $backend := .Values.conf.backends }}
+{{- if and $backend.nfs_shares_config (not $backend.nfs_shares_config_content) }}
+{{- $nfs_shares_config_content := $backend.nfs_shares_config }}
+{{- $_ := set $backend "nfs_shares_config_content" $nfs_shares_config_content -}}
+{{- $_ := set $backend "nfs_shares_config" (printf "/etc/cinder/nfs/%s" $key) -}}
+{{- end -}}
+{{- end -}}
 ---
 apiVersion: v1
 kind: Secret
@@ -198,3 +206,13 @@
   external-ceph.conf: {{ include "helm-toolkit.utils.to_oslo_conf" .Values.ceph_client.external_ceph.conf | b64enc }}
 {{- end }}
 {{- end }}
+{{- range $key, $backend := .Values.conf.backends }}
+{{- if $backend.nfs_shares_config }}
+  nfs-{{ $key }}: |
+    {{- if kindIs "string" $backend.nfs_shares_config_content }}
+    {{ $backend.nfs_shares_config_content | b64enc }}
+    {{- else if kindIs "slice" $backend.nfs_shares_config_content }}
+        {{ $backend.nfs_shares_config_content | join "\n"  | b64enc }}
+    {{- end }}
+{{- end }}
+{{- end }}
diff --git a/charts/cinder/templates/deployment-volume.yaml b/charts/cinder/templates/deployment-volume.yaml
index 01aea23..f029aff 100644
--- a/charts/cinder/templates/deployment-volume.yaml
+++ b/charts/cinder/templates/deployment-volume.yaml
@@ -241,6 +241,14 @@
               readOnly: true
             {{- end }}
             {{- end }}
+            {{- range $key, $backend := .Values.conf.backends }}
+            {{- if $backend.nfs_shares_config }}
+            - name: cinder-etc
+              mountPath: /etc/cinder/nfs/{{ $key }}
+              subPath: nfs-{{ $key }}
+              readOnly: true
+            {{- end }}
+            {{- end }}
             {{- if .Values.conf.enable_iscsi }}
             - name: host-rootfs
               mountPath: /mnt/host-rootfs
diff --git a/charts/patches/cinder/0002-add-nfs-support.patch b/charts/patches/cinder/0002-add-nfs-support.patch
new file mode 100644
index 0000000..afd5074
--- /dev/null
+++ b/charts/patches/cinder/0002-add-nfs-support.patch
@@ -0,0 +1,52 @@
+diff --git a/cinder/templates/configmap-etc.yaml b/cinder/templates/configmap-etc.yaml
+index 1a20ea84..97bfe0f7 100644
+--- a/cinder/templates/configmap-etc.yaml
++++ b/cinder/templates/configmap-etc.yaml
+@@ -165,6 +165,14 @@ limitations under the License.
+ {{- $formatter_fluent := dict "class" "oslo_log.formatters.FluentFormatter" -}}
+ {{- $_ := set .Values.conf.logging "formatter_fluent" $formatter_fluent -}}
+ {{- end -}}
++
++{{- range $key, $backend := .Values.conf.backends }}
++{{- if and $backend.nfs_shares_config (not $backend.nfs_shares_config_content) }}
++{{- $nfs_shares_config_content := $backend.nfs_shares_config }}
++{{- $_ := set $backend "nfs_shares_config_content" $nfs_shares_config_content -}}
++{{- $_ := set $backend "nfs_shares_config" (printf "/etc/cinder/nfs/%s" $key) -}}
++{{- end -}}
++{{- end -}}
+ ---
+ apiVersion: v1
+ kind: Secret
+@@ -198,3 +206,13 @@ data:
+   external-ceph.conf: {{ include "helm-toolkit.utils.to_oslo_conf" .Values.ceph_client.external_ceph.conf | b64enc }}
+ {{- end }}
+ {{- end }}
++{{- range $key, $backend := .Values.conf.backends }}
++{{- if $backend.nfs_shares_config }}
++  nfs-{{ $key }}: |
++    {{- if kindIs "string" $backend.nfs_shares_config_content }}
++    {{ $backend.nfs_shares_config_content | b64enc }}
++    {{- else if kindIs "slice" $backend.nfs_shares_config_content }}
++        {{ $backend.nfs_shares_config_content | join "\n"  | b64enc }}
++    {{- end }}
++{{- end }}
++{{- end }}
+diff --git a/cinder/templates/deployment-volume.yaml b/cinder/templates/deployment-volume.yaml
+index 93625536..a21c13ef 100644
+--- a/cinder/templates/deployment-volume.yaml
++++ b/cinder/templates/deployment-volume.yaml
+@@ -242,6 +242,14 @@ spec:
+               readOnly: true
+             {{- end }}
+             {{- end }}
++            {{- range $key, $backend := .Values.conf.backends }}
++            {{- if $backend.nfs_shares_config }}
++            - name: cinder-etc
++              mountPath: /etc/cinder/nfs/{{ $key }}
++              subPath: nfs-{{ $key }}
++              readOnly: true
++            {{- end }}
++            {{- end }}
+             {{- if .Values.conf.enable_iscsi }}
+             - name: host-rootfs
+               mountPath: /mnt/host-rootfs
diff --git a/hack/helm-unittest.py b/hack/helm-unittest.py
new file mode 100644
index 0000000..82df4f8
--- /dev/null
+++ b/hack/helm-unittest.py
@@ -0,0 +1,50 @@
+# Copyright (c) 2024 VEXXHOST, Inc.
+# SPDX-License-Identifier: Apache-2.0
+
+import glob
+import os
+import subprocess
+
+
+def run_helm_unittest(charts_dir, exclusions):
+    """
+    :param charts_dir: The directory containing Helm charts.
+    :param exclusions: List of subdirectories to exclude.
+    """
+    all_tests_passed = True
+    charts = [
+        os.path.basename(d)
+        for d in glob.glob(os.path.join(charts_dir, "*"))
+        if os.path.isdir(d) and os.path.basename(d) not in exclusions
+    ]
+    for chart in charts:
+        print(f"Running helm unittest for chart: {chart}")
+        chart_tests_path = f"../../roles/{chart}/tests/*.yaml"
+        chart_path = os.path.join(charts_dir, chart)
+        try:
+            # Run helm unittest
+            subprocess.run(
+                ["helm", "unittest", "-f", chart_tests_path, chart_path],
+                check=True,
+            )
+            print(f"Helm unitest  passed for chart: {chart}")
+        except subprocess.CalledProcessError as e:
+            print(f"Helm unittest failed for chart: {chart}")
+            all_tests_passed = False
+            raise e
+    if all_tests_passed:
+        print("\nAll Helm unitests passed successfully!")
+        exit(0)
+    else:
+        print("\nOne or more charts had test failures.")
+        exit(1)
+
+
+def main():
+    charts_dir = os.getenv("CHARTS_DIR", "charts")
+    exclusions = ["patches"]
+    run_helm_unittest(charts_dir, exclusions)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/roles/cinder/tests/nfs_test.yaml b/roles/cinder/tests/nfs_test.yaml
new file mode 100644
index 0000000..a2ec0aa
--- /dev/null
+++ b/roles/cinder/tests/nfs_test.yaml
@@ -0,0 +1,96 @@
+suite: nfs
+tests:
+  - it: should support a single nfs backend with a single share
+    templates:
+      - templates/configmap-etc.yaml
+      - templates/deployment-volume.yaml
+    set:
+      conf:
+        backends:
+          nfs:
+            nfs_shares_config: test_content_1
+    asserts:
+      - template: templates/configmap-etc.yaml
+        equal:
+          path: data.nfs-nfs
+          decodeBase64: true
+          value: test_content_1
+      - template: templates/deployment-volume.yaml
+        documentIndex: 3
+        equal:
+          path: spec.template.spec.containers[?(@.name=='cinder-volume')].volumeMounts[?(@.subPath=='nfs-nfs')]
+          value:
+            name: cinder-etc
+            mountPath: /etc/cinder/nfs/nfs
+            readOnly: true
+            subPath: nfs-nfs
+
+  - it: should support a single nfs backend with multiple shares
+    templates:
+      - templates/configmap-etc.yaml
+      - templates/deployment-volume.yaml
+    set:
+      conf:
+        backends:
+          nfs:
+            nfs_shares_config:
+              - test_content_1
+              - test_content_2
+    asserts:
+      - template: templates/configmap-etc.yaml
+        equal:
+          path: data.nfs-nfs
+          decodeBase64: true
+          value: |-
+            test_content_1
+            test_content_2
+      - template: templates/deployment-volume.yaml
+        documentIndex: 3
+        equal:
+          path: spec.template.spec.containers[?(@.name=='cinder-volume')].volumeMounts[?(@.subPath=='nfs-nfs')]
+          value:
+            name: cinder-etc
+            mountPath: /etc/cinder/nfs/nfs
+            readOnly: true
+            subPath: nfs-nfs
+
+  - it: should support multiple nfs backends
+    templates:
+      - templates/configmap-etc.yaml
+      - templates/deployment-volume.yaml
+    set:
+      conf:
+        backends:
+          nfs1:
+            nfs_shares_config: test_content_1
+          nfs2:
+            nfs_shares_config: test_content_2
+    asserts:
+      - template: templates/configmap-etc.yaml
+        equal:
+          path: data.nfs-nfs1
+          decodeBase64: true
+          value: test_content_1
+      - template: templates/configmap-etc.yaml
+        equal:
+          path: data.nfs-nfs2
+          decodeBase64: true
+          value: test_content_2
+      - template: templates/deployment-volume.yaml
+        documentIndex: 3
+        equal:
+          path: spec.template.spec.containers[?(@.name=='cinder-volume')].volumeMounts[?(@.subPath=='nfs-nfs1')]
+          value:
+            name: cinder-etc
+            mountPath: /etc/cinder/nfs/nfs1
+            readOnly: true
+            subPath: nfs-nfs1
+      - template: templates/deployment-volume.yaml
+        documentIndex: 3
+        equal:
+          path: spec.template.spec.containers[?(@.name=='cinder-volume')].volumeMounts[?(@.subPath=='nfs-nfs2')]
+          value:
+            name: cinder-etc
+            mountPath: /etc/cinder/nfs/nfs2
+            readOnly: true
+            subPath: nfs-nfs2
diff --git a/tox.ini b/tox.ini
index ff3459b..34519eb 100644
--- a/tox.ini
+++ b/tox.ini
@@ -113,3 +113,7 @@
   rjsonnet
 commands =
   python3 {toxinidir}/hack/promtool-test.py
+
+[testenv:helm-unittest]
+commands =
+  python3 {toxinidir}/hack/helm-unittest.py
diff --git a/zuul.d/jobs.yaml b/zuul.d/jobs.yaml
index f138d9d..435181d 100644
--- a/zuul.d/jobs.yaml
+++ b/zuul.d/jobs.yaml
@@ -30,6 +30,13 @@
       tox_envlist: promtool-test
 
 - job:
+    name: atmosphere-tox-helm-unittest
+    parent: tox
+    pre-run: zuul.d/playbooks/helm-unittest/pre.yml
+    vars:
+      tox_envlist: helm-unittest
+
+- job:
     name: atmosphere-tox-py3
     parent: tox
 
diff --git a/zuul.d/playbooks/helm-unittest/pre.yml b/zuul.d/playbooks/helm-unittest/pre.yml
new file mode 100644
index 0000000..ebabd5b
--- /dev/null
+++ b/zuul.d/playbooks/helm-unittest/pre.yml
@@ -0,0 +1,22 @@
+# Copyright (c) 2024 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+- hosts: all
+  roles:
+    - role: ensure-helm
+      helm_version: 3.13.3
+  tasks:
+    - name: Install helm unittest
+      ansible.builtin.shell: |
+        helm plugin install https://github.com/helm-unittest/helm-unittest.git
diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml
index 1dd4c48..4f36ef0 100644
--- a/zuul.d/project.yaml
+++ b/zuul.d/project.yaml
@@ -20,6 +20,7 @@
         - atmosphere-golang-go-test
         - atmosphere-linters
         - atmosphere-tox-promtool-test
+        - atmosphere-tox-helm-unittest
         - atmosphere-tox-py3
         - atmosphere-build-collection:
             dependencies: &molecule_check_dependencies