Add Netapp volume backend support STX-O storage classes
This change allows Kubernetes PVCs StorageClasses to not depend on Ceph, and it will allow for multiple storage classes to coexist. The system will pick the highest priority storage backend available to create the persistent volumes where the data is stored. Test Plan: [PASS] build all packages and STX-O tarball. [PASS] Apply successfully on host ceph only deployments. [PASS] Apply successfully on rook ceph only deployments. [PASS] Apply successfully on host ceph and NetApp-nfs deployments. [PASS] Apply successfully when priority list is updated. [PASS] Verify that persistent volumes are created correctly. This change also updates the openstack lifecycle validation to allow uploading STX-O to be applied without ceph. However, STX-O has an automatic installation feature that installs required dependencies. It auto installs rook-ceph despite it being removed. Hence, this scenario was not tested Story: 2011281 Task: 53121 Change-Id: Idea57a4ee8a362bcaea60dd873715cb57f7e8e77 Signed-off-by: Johnny Chia <johnny.chialung@windriver.com>
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
From cdad5b37e60f36d9b88e0e2ec79c666fffbcfae8 Mon Sep 17 00:00:00 2001
|
||||
From: jchialun <johnny.chialung@windriver.com>
|
||||
Date: Wed, 19 Nov 2025 14:56:26 -0500
|
||||
Subject: [PATCH 1/1] Add volume storage class priorities
|
||||
|
||||
Added volume_storage_class_priority list to values.yaml file to enable
|
||||
external applications to update the value. This list represents the
|
||||
priority order for creation of the Kubernetes PVCs StorageClasses for
|
||||
MariaDB and RabbitMQ. The highest available storage class will be
|
||||
selected as the k8s volume storageClass
|
||||
|
||||
Signed-off-by: Johnny Chia <johnny.chialung@windriver.com>
|
||||
---
|
||||
mariadb/values.yaml | 8 ++++++++
|
||||
rabbitmq/values.yaml | 7 +++++++
|
||||
2 files changed, 15 insertions(+)
|
||||
|
||||
diff --git a/mariadb/values.yaml b/mariadb/values.yaml
|
||||
index 9eb81231..66740a2c 100644
|
||||
--- a/mariadb/values.yaml
|
||||
+++ b/mariadb/values.yaml
|
||||
@@ -740,6 +740,14 @@ network_policy:
|
||||
# Set helm3_hook: false in case helm2 is used.
|
||||
helm3_hook: true
|
||||
|
||||
+storage_conf:
|
||||
+ volume_storage_class_priority:
|
||||
+ - "ceph"
|
||||
+ - "netapp-nfs"
|
||||
+ - "netapp-iscsi"
|
||||
+ - "netapp-fc"
|
||||
+
|
||||
+
|
||||
manifests:
|
||||
certificates: false
|
||||
configmap_bin: true
|
||||
diff --git a/rabbitmq/values.yaml b/rabbitmq/values.yaml
|
||||
index fbb98414..99b2833c 100644
|
||||
--- a/rabbitmq/values.yaml
|
||||
+++ b/rabbitmq/values.yaml
|
||||
@@ -441,6 +441,13 @@ io_thread_pool:
|
||||
enabled: false
|
||||
size: 64
|
||||
|
||||
+storage_conf:
|
||||
+ volume_storage_class_priority:
|
||||
+ - "ceph"
|
||||
+ - "netapp-nfs"
|
||||
+ - "netapp-iscsi"
|
||||
+ - "netapp-fc"
|
||||
+
|
||||
manifests:
|
||||
certificates: false
|
||||
configmap_bin: true
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -22,3 +22,4 @@
|
||||
0022-Update-ipFamilyPolicy-to-support-DualStack.patch
|
||||
0023-Update-libvirt-cgroup-controllers-initiation.patch
|
||||
0024-Add-cluster-host-ip-env-var-to-libvirt.patch
|
||||
0025-Add-volume-storage-class-priorities.patch
|
||||
|
||||
@@ -154,6 +154,9 @@ NETAPP_STORAGECLASS_NAME = "csi.trident.netapp.io"
|
||||
NETAPP_NFS_BACKEND_NAME = 'netapp-nfs'
|
||||
NETAPP_ISCSI_BACKEND_NAME = 'netapp-iscsi'
|
||||
NETAPP_FC_BACKEND_NAME = 'netapp-fc'
|
||||
BACKEND_DEFAULT_STORAGE_CLASS = "general"
|
||||
BACKEND_TYPE_NETAPP_NFS = "ontap-nas"
|
||||
BACKEND_TYPE_NETAPP_ISCSI = "ontap-san"
|
||||
|
||||
# Storage backends overrides
|
||||
OVERRIDE_STORAGE_BACKENDS = "storage_conf.storage_backends"
|
||||
|
||||
@@ -10,6 +10,8 @@ from sysinv.helm import common
|
||||
|
||||
from k8sapp_openstack.common import constants as app_constants
|
||||
from k8sapp_openstack.helm import openstack
|
||||
from k8sapp_openstack.utils import get_available_volume_backends
|
||||
from k8sapp_openstack.utils import get_storage_backends_priority_list
|
||||
from k8sapp_openstack.utils import is_ipv4
|
||||
|
||||
|
||||
@@ -25,6 +27,16 @@ class MariadbHelm(openstack.OpenstackBaseHelm):
|
||||
return self._num_provisioned_controllers()
|
||||
|
||||
def get_overrides(self, namespace=None):
|
||||
|
||||
available_backend = get_available_volume_backends()
|
||||
default_priority_list = get_storage_backends_priority_list(app_constants.HELM_CHART_MARIADB)
|
||||
priority_storage_class = app_constants.BACKEND_DEFAULT_STORAGE_CLASS
|
||||
|
||||
for priority in default_priority_list:
|
||||
if available_backend.get(priority, ""):
|
||||
priority_storage_class = available_backend.get(priority)
|
||||
break
|
||||
|
||||
overrides = {
|
||||
common.HELM_NS_OPENSTACK: {
|
||||
'pod': {
|
||||
@@ -36,6 +48,13 @@ class MariadbHelm(openstack.OpenstackBaseHelm):
|
||||
'endpoints': self._get_endpoints_overrides(),
|
||||
'manifests': {
|
||||
'config_ipv6': not is_ipv4()
|
||||
},
|
||||
'volume': {
|
||||
'class_name': priority_storage_class,
|
||||
'backup': {
|
||||
'class_name': priority_storage_class,
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2019-2020 Wind River Systems, Inc.
|
||||
# Copyright (c) 2019-2025 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
@@ -9,6 +9,8 @@ from sysinv.helm import common
|
||||
|
||||
from k8sapp_openstack.common import constants as app_constants
|
||||
from k8sapp_openstack.helm import openstack
|
||||
from k8sapp_openstack.utils import get_available_volume_backends
|
||||
from k8sapp_openstack.utils import get_storage_backends_priority_list
|
||||
|
||||
|
||||
class RabbitmqHelm(openstack.OpenstackBaseHelm):
|
||||
@@ -27,6 +29,15 @@ class RabbitmqHelm(openstack.OpenstackBaseHelm):
|
||||
elif io_thread_pool_size > 1024:
|
||||
io_thread_pool_size = 1024
|
||||
|
||||
available_backend = get_available_volume_backends()
|
||||
default_priority_list = get_storage_backends_priority_list(app_constants.HELM_CHART_RABBITMQ)
|
||||
priority_storage_class = app_constants.BACKEND_DEFAULT_STORAGE_CLASS
|
||||
|
||||
for priority in default_priority_list:
|
||||
if available_backend.get(priority, ""):
|
||||
priority_storage_class = available_backend.get(priority)
|
||||
break
|
||||
|
||||
overrides = {
|
||||
common.HELM_NS_OPENSTACK: {
|
||||
'pod': {
|
||||
@@ -56,6 +67,9 @@ class RabbitmqHelm(openstack.OpenstackBaseHelm):
|
||||
'endpoints': self._get_endpoints_overrides(),
|
||||
'manifests': {
|
||||
'config_ipv6': self._is_ipv6_cluster_service()
|
||||
},
|
||||
'volume': {
|
||||
'class_name': priority_storage_class,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,8 +431,16 @@ class OpenstackAppLifecycleOperator(base.AppLifecycleOperator):
|
||||
rook_ceph_available, _ = app_utils.is_ceph_backend_available(
|
||||
ceph_type=constants.SB_TYPE_CEPH_ROOK
|
||||
)
|
||||
netapp_backends_available = app_utils.check_netapp_backends()
|
||||
netapp_nfs_available = netapp_backends_available.get("nfs", False)
|
||||
netapp_iscsi_available = netapp_backends_available.get("iscsi", False)
|
||||
netapp_fc_available = netapp_backends_available.get("fc", False)
|
||||
|
||||
status = f"ceph_available={ceph_available}, " \
|
||||
f"rook_ceph_available={rook_ceph_available}"
|
||||
f"rook_ceph_available={rook_ceph_available}, " \
|
||||
f"netapp_nfs_available={netapp_nfs_available}, " \
|
||||
f"netapp_iscsi_available={netapp_iscsi_available}, " \
|
||||
f"netapp_fc_available={netapp_fc_available}"
|
||||
if rook_ceph_available:
|
||||
rook_api_available = app_utils.is_rook_ceph_api_available()
|
||||
fsid_available = app_utils.get_ceph_fsid() is not None
|
||||
@@ -444,6 +452,13 @@ class OpenstackAppLifecycleOperator(base.AppLifecycleOperator):
|
||||
backend_available = fsid_available
|
||||
status += f", fsid_available={fsid_available}"
|
||||
|
||||
if netapp_nfs_available:
|
||||
backend_available = True
|
||||
elif netapp_iscsi_available:
|
||||
backend_available = True
|
||||
elif netapp_fc_available:
|
||||
backend_available = True
|
||||
|
||||
if not backend_available:
|
||||
err_msg = "No storage backends available and ready for openstack " \
|
||||
f"deployment. status: {status}"
|
||||
|
||||
@@ -33,6 +33,8 @@ class OpenstackAppLifecycleOperatorTest(dbbase.BaseHostTestCase):
|
||||
constants.SB_TYPE_CEPH):
|
||||
return ceph_type == constants.SB_TYPE_CEPH, ""
|
||||
|
||||
@mock.patch('k8sapp_openstack.utils.check_netapp_backends',
|
||||
return_value={"nfs": False, "iscsi": False, "fc": False})
|
||||
@mock.patch('k8sapp_openstack.utils.is_rook_ceph_api_available',
|
||||
return_value=True)
|
||||
@mock.patch('k8sapp_openstack.utils.get_ceph_fsid',
|
||||
@@ -42,7 +44,8 @@ class OpenstackAppLifecycleOperatorTest(dbbase.BaseHostTestCase):
|
||||
self,
|
||||
mock_is_ceph_backend_available,
|
||||
mock_get_ceph_fsid,
|
||||
mock_is_rook_ceph_api_available
|
||||
mock_is_rook_ceph_api_available,
|
||||
mock_check_netapp_backends
|
||||
):
|
||||
""" Test _semantic_check_storage_backend_available for rook ceph
|
||||
backend, api and fsid available.
|
||||
@@ -51,16 +54,20 @@ class OpenstackAppLifecycleOperatorTest(dbbase.BaseHostTestCase):
|
||||
self._rook_ceph_backend_available
|
||||
self.lifecycle._semantic_check_storage_backend_available()
|
||||
mock_is_ceph_backend_available.assert_called()
|
||||
mock_check_netapp_backends.assert_called()
|
||||
mock_get_ceph_fsid.assert_called()
|
||||
mock_is_rook_ceph_api_available.assert_called()
|
||||
|
||||
@mock.patch('k8sapp_openstack.utils.check_netapp_backends',
|
||||
return_value={"nfs": False, "iscsi": False, "fc": False})
|
||||
@mock.patch('k8sapp_openstack.utils.get_ceph_fsid',
|
||||
return_value='aa8c8da0-47de-4fad-8b5d-2c06be236fc8')
|
||||
@mock.patch('k8sapp_openstack.utils.is_ceph_backend_available')
|
||||
def test_semantic_check_storage_backend_available_ceph(
|
||||
self,
|
||||
mock_is_ceph_backend_available,
|
||||
mock_get_ceph_fsid
|
||||
mock_get_ceph_fsid,
|
||||
mock_check_netapp_backends
|
||||
):
|
||||
""" Test _semantic_check_storage_backend_available for host ceph
|
||||
backend and fsid available.
|
||||
@@ -69,14 +76,38 @@ class OpenstackAppLifecycleOperatorTest(dbbase.BaseHostTestCase):
|
||||
self._ceph_backend_available
|
||||
self.lifecycle._semantic_check_storage_backend_available()
|
||||
mock_is_ceph_backend_available.assert_called()
|
||||
mock_check_netapp_backends.assert_called()
|
||||
mock_get_ceph_fsid.assert_called()
|
||||
|
||||
@mock.patch('k8sapp_openstack.utils.check_netapp_backends',
|
||||
return_value={"nfs": True, "iscsi": False, "fc": False})
|
||||
@mock.patch('k8sapp_openstack.utils.get_ceph_fsid', return_value=None)
|
||||
@mock.patch('k8sapp_openstack.utils.is_ceph_backend_available')
|
||||
def test_semantic_check_storage_backend_available_netapp_nfs(
|
||||
self,
|
||||
mock_is_ceph_backend_available,
|
||||
mock_get_ceph_fsid,
|
||||
mock_check_netapp_backends,
|
||||
):
|
||||
""" Test _semantic_check_storage_backend_available for netapp backend
|
||||
backend available.
|
||||
"""
|
||||
mock_is_ceph_backend_available.side_effect = \
|
||||
self._ceph_backend_available
|
||||
self.lifecycle._semantic_check_storage_backend_available()
|
||||
mock_is_ceph_backend_available.assert_called()
|
||||
mock_check_netapp_backends.assert_called()
|
||||
mock_get_ceph_fsid.assert_called()
|
||||
|
||||
@mock.patch('k8sapp_openstack.utils.check_netapp_backends',
|
||||
return_value={"nfs": False, "iscsi": False, "fc": False})
|
||||
@mock.patch('k8sapp_openstack.utils.get_ceph_fsid', return_value=None)
|
||||
@mock.patch('k8sapp_openstack.utils.is_ceph_backend_available')
|
||||
def test_semantic_check_storage_backend_available_fsid_unavailable(
|
||||
self,
|
||||
mock_is_ceph_backend_available,
|
||||
mock_get_ceph_fsid
|
||||
mock_get_ceph_fsid,
|
||||
mock_check_netapp_backends,
|
||||
):
|
||||
""" Test _semantic_check_storage_backend_available for host ceph
|
||||
available and fsid unavailable.
|
||||
@@ -90,7 +121,10 @@ class OpenstackAppLifecycleOperatorTest(dbbase.BaseHostTestCase):
|
||||
else:
|
||||
self.fail("LifecycleSemanticCheckException was not raised")
|
||||
mock_get_ceph_fsid.assert_called()
|
||||
mock_check_netapp_backends.assert_called()
|
||||
|
||||
@mock.patch('k8sapp_openstack.utils.check_netapp_backends',
|
||||
return_value={"nfs": False, "iscsi": False, "fc": False})
|
||||
@mock.patch('k8sapp_openstack.utils.get_ceph_fsid', return_value=None)
|
||||
@mock.patch('k8sapp_openstack.utils.is_ceph_backend_available',
|
||||
side_effect=[(False, ""), (False, "")])
|
||||
|
||||
@@ -10,6 +10,7 @@ import subprocess
|
||||
import mock
|
||||
from sysinv.common import constants
|
||||
from sysinv.common import exception
|
||||
from sysinv.common import kubernetes
|
||||
from sysinv.tests.db import base as dbbase
|
||||
|
||||
from k8sapp_openstack import utils as app_utils
|
||||
@@ -1616,3 +1617,26 @@ class UtilsTest(dbbase.ControllerHostTestCase):
|
||||
backends_map = app_utils.check_netapp_backends()
|
||||
|
||||
assert backends_map["nfs"]
|
||||
|
||||
@mock.patch("k8sapp_openstack.utils.subprocess.run")
|
||||
def test_get_netapp_storage_class_name(self, mock_run):
|
||||
""" Tests if get_netapp_storage_class_name returns a valid
|
||||
storage class name for netapp.
|
||||
"""
|
||||
mock_process = mock.MagicMock()
|
||||
mock_process.stdout = "netapp-nas-backend ontap-nas"
|
||||
mock_process.stderr = ""
|
||||
mock_process.check_returncode.return_value = None
|
||||
mock_run.return_value = mock_process
|
||||
|
||||
result = app_utils.get_netapp_storage_class_name(app_constants.BACKEND_TYPE_NETAPP_NFS)
|
||||
|
||||
assert result == "netapp-nas-backend"
|
||||
mock_run.assert_called_once_with(
|
||||
args=["kubectl", "--kubeconfig", kubernetes.KUBERNETES_ADMIN_CONF,
|
||||
"get", "sc", "-o", "custom-columns=NAME:.metadata.name,TYPE:.parameters.backendType"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
shell=False
|
||||
)
|
||||
|
||||
@@ -1671,6 +1671,84 @@ def get_server_list() -> str:
|
||||
return ""
|
||||
|
||||
|
||||
def get_available_volume_backends() -> dict:
|
||||
"""
|
||||
Searches for all available backends volume available.
|
||||
|
||||
Returns:
|
||||
dict[string, string]: A dictionary containing the backend volumes with corresponding
|
||||
storage class name.
|
||||
"""
|
||||
rook_ceph = is_ceph_backend_available(
|
||||
ceph_type=constants.SB_TYPE_CEPH_ROOK
|
||||
)
|
||||
netapp_backend = check_netapp_backends()
|
||||
|
||||
available_volume_backends = {
|
||||
"ceph": app_constants.BACKEND_DEFAULT_STORAGE_CLASS if rook_ceph else "",
|
||||
"netapp-nfs":
|
||||
get_netapp_storage_class_name(
|
||||
app_constants.BACKEND_TYPE_NETAPP_NFS
|
||||
) if netapp_backend.get("nfs", False) else "",
|
||||
"netapp-iscsi":
|
||||
get_netapp_storage_class_name(
|
||||
app_constants.BACKEND_TYPE_NETAPP_ISCSI
|
||||
) if netapp_backend.get("iscsi", False) else "",
|
||||
"netapp-fc": "",
|
||||
}
|
||||
return available_volume_backends
|
||||
|
||||
|
||||
def get_netapp_storage_class_name(backend_type) -> str:
|
||||
"""
|
||||
Check for the storage class name for NetApp backends based on a backend-type.
|
||||
|
||||
Returns:
|
||||
str: A string indicating the backends class_name
|
||||
Example: "netapp-nas-backend"
|
||||
"""
|
||||
class_name = ""
|
||||
|
||||
try:
|
||||
cmd = [
|
||||
"kubectl", "--kubeconfig", kubernetes.KUBERNETES_ADMIN_CONF,
|
||||
"get", "sc", "-o", "custom-columns=NAME:.metadata.name,TYPE:.parameters.backendType"
|
||||
]
|
||||
|
||||
storage_classes_info = subprocess.run(
|
||||
args=cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
shell=False)
|
||||
|
||||
if not storage_classes_info.stdout:
|
||||
return class_name
|
||||
|
||||
# Need to manually search for the backend type and parse the string
|
||||
lines = storage_classes_info.stdout.splitlines()
|
||||
for line in lines:
|
||||
if backend_type in line:
|
||||
class_name = line.split(" ")[0]
|
||||
return class_name
|
||||
|
||||
except KubeApiException as e:
|
||||
LOG.error(f"Failed to get kubectl sc: {e}")
|
||||
return class_name
|
||||
except subprocess.CalledProcessError as e:
|
||||
LOG.error(
|
||||
"kubectl command did not return successful return code: "
|
||||
f"{e.returncode}. Error message was: {e.output}"
|
||||
)
|
||||
return class_name
|
||||
except subprocess.TimeoutExpired as e:
|
||||
LOG.error(f"kubectl command timed out: {e}")
|
||||
return class_name
|
||||
except Exception as e:
|
||||
LOG.error(f"Unexpected error while fetching NetApp backends: {e}")
|
||||
return class_name
|
||||
|
||||
|
||||
def is_dex_enabled() -> bool:
|
||||
"""
|
||||
Determine whether DEX integration is enabled in Keystone overrides.
|
||||
|
||||
@@ -533,6 +533,12 @@ monitoring:
|
||||
enabled: false
|
||||
mysqld_exporter:
|
||||
scrape: true
|
||||
storage_conf:
|
||||
volume_storage_class_priority:
|
||||
- "ceph"
|
||||
- "netapp-nfs"
|
||||
- "netapp-iscsi"
|
||||
- "netapp-fc"
|
||||
secrets:
|
||||
identity:
|
||||
admin: keystone-admin-user
|
||||
|
||||
@@ -425,6 +425,12 @@ helm3_hook: true
|
||||
io_thread_pool:
|
||||
enabled: false
|
||||
size: 64
|
||||
storage_conf:
|
||||
volume_storage_class_priority:
|
||||
- "ceph"
|
||||
- "netapp-nfs"
|
||||
- "netapp-iscsi"
|
||||
- "netapp-fc"
|
||||
manifests:
|
||||
certificates: false
|
||||
configmap_bin: true
|
||||
|
||||
Reference in New Issue
Block a user