Feature 10956: Add namespace and force arguments to helm upgrade 78/12578/11
authorGabriel Cuba <gcuba@whitestack.com>
Mon, 10 Oct 2022 17:13:55 +0000 (12:13 -0500)
committerGabriel Cuba <gcuba@whitestack.com>
Wed, 23 Nov 2022 16:35:52 +0000 (11:35 -0500)
Change-Id: I8e37e43b72c5f7f63c4b9f49542905727610fa5a
Signed-off-by: Gabriel Cuba <gcuba@whitestack.com>
n2vc/k8s_conn.py
n2vc/k8s_helm3_conn.py
n2vc/k8s_helm_base_conn.py
n2vc/k8s_helm_conn.py
n2vc/tests/unit/test_k8s_helm3_conn.py
n2vc/tests/unit/test_k8s_helm_conn.py

index ada7075..1c88653 100644 (file)
@@ -194,6 +194,7 @@ class K8sConnector(abc.ABC, Loggable):
         timeout: float = 300,
         params: dict = None,
         db_dict: dict = None,
+        force: bool = False,
     ):
         """
         Upgrades an existing KDU instance. It would implicitly use the `upgrade` call
@@ -213,6 +214,7 @@ class K8sConnector(abc.ABC, Loggable):
                         path: <str>},
                             e.g. {collection: "nsrs", filter:
                             {_id: <nsd-id>, path: "_admin.deployed.K8S.3"}
+        :param force: force recreation of resources if necessary
         :return: reference to the new revision number of the KDU instance
         """
 
index d828da2..3d7e3b2 100644 (file)
@@ -544,6 +544,7 @@ class K8sHelm3Connector(K8sHelmBaseConnector):
         atomic: bool,
         timeout: float,
         kubeconfig: str,
+        force: bool = False,
     ) -> str:
         """Generates the command to upgrade a Helm Chart release
 
@@ -557,7 +558,7 @@ class K8sHelm3Connector(K8sHelmBaseConnector):
                 The --wait flag will be set automatically if --atomic is used
             timeout (float): The time, in seconds, to wait
             kubeconfig (str): Kubeconfig file path
-
+            force (bool): If set, helm forces resource updates through a replacement strategy. This may recreate pods.
         Returns:
             str: command to upgrade a Helm Chart release
         """
@@ -571,6 +572,11 @@ class K8sHelm3Connector(K8sHelmBaseConnector):
         if atomic:
             atomic_str = "--atomic"
 
+        # force
+        force_str = ""
+        if force:
+            force_str = "--force "
+
         # version
         version_str = ""
         if version:
@@ -582,7 +588,7 @@ class K8sHelm3Connector(K8sHelmBaseConnector):
             namespace_str = "--namespace {}".format(namespace)
 
         command = (
-            "env KUBECONFIG={kubeconfig} {helm} upgrade {name} {model} {namespace} {atomic} "
+            "env KUBECONFIG={kubeconfig} {helm} upgrade {name} {model} {namespace} {atomic} {force}"
             "--output yaml {params} {timeout} --reuse-values {ver}"
         ).format(
             kubeconfig=kubeconfig,
@@ -590,6 +596,7 @@ class K8sHelm3Connector(K8sHelmBaseConnector):
             name=kdu_instance,
             namespace=namespace_str,
             atomic=atomic_str,
+            force=force_str,
             params=params_str,
             timeout=timeout_str,
             model=kdu_model,
index e89d6fa..a6cb11a 100644 (file)
@@ -481,6 +481,8 @@ class K8sHelmBaseConnector(K8sConnector):
         timeout: float = 300,
         params: dict = None,
         db_dict: dict = None,
+        namespace: str = None,
+        force: bool = False,
     ):
         self.log.debug("upgrading {} in cluster {}".format(kdu_model, cluster_uuid))
 
@@ -488,9 +490,13 @@ class K8sHelmBaseConnector(K8sConnector):
         self.fs.sync(from_path=cluster_uuid)
 
         # look for instance to obtain namespace
-        instance_info = await self.get_instance_info(cluster_uuid, kdu_instance)
-        if not instance_info:
-            raise K8sException("kdu_instance {} not found".format(kdu_instance))
+
+        # set namespace
+        if not namespace:
+            instance_info = await self.get_instance_info(cluster_uuid, kdu_instance)
+            if not instance_info:
+                raise K8sException("kdu_instance {} not found".format(kdu_instance))
+            namespace = instance_info["namespace"]
 
         # init env, paths
         paths, env = self._init_paths_env(
@@ -515,12 +521,13 @@ class K8sHelmBaseConnector(K8sConnector):
         command = self._get_upgrade_command(
             kdu_model,
             kdu_instance,
-            instance_info["namespace"],
+            namespace,
             params_str,
             version,
             atomic,
             timeout,
             paths["kube_config"],
+            force,
         )
 
         self.log.debug("upgrading: {}".format(command))
@@ -538,7 +545,7 @@ class K8sHelmBaseConnector(K8sConnector):
                 coro_or_future=self._store_status(
                     cluster_id=cluster_uuid,
                     kdu_instance=kdu_instance,
-                    namespace=instance_info["namespace"],
+                    namespace=namespace,
                     db_dict=db_dict,
                     operation="upgrade",
                 )
@@ -565,7 +572,7 @@ class K8sHelmBaseConnector(K8sConnector):
         await self._store_status(
             cluster_id=cluster_uuid,
             kdu_instance=kdu_instance,
-            namespace=instance_info["namespace"],
+            namespace=namespace,
             db_dict=db_dict,
             operation="upgrade",
         )
@@ -1355,6 +1362,7 @@ class K8sHelmBaseConnector(K8sConnector):
         atomic,
         timeout,
         kubeconfig,
+        force,
     ) -> str:
         """Generates the command to upgrade a Helm Chart release
 
@@ -1368,7 +1376,7 @@ class K8sHelmBaseConnector(K8sConnector):
                 The --wait flag will be set automatically if --atomic is used
             timeout (float): The time, in seconds, to wait
             kubeconfig (str): Kubeconfig file path
-
+            force (bool): If set, helm forces resource updates through a replacement strategy. This may recreate pods.
         Returns:
             str: command to upgrade a Helm Chart release
         """
index cd440a9..0ea8920 100644 (file)
@@ -702,6 +702,7 @@ class K8sHelmConnector(K8sHelmBaseConnector):
         atomic,
         timeout,
         kubeconfig,
+        force: bool = False,
     ) -> str:
         """Generates the command to upgrade a Helm Chart release
 
@@ -715,7 +716,7 @@ class K8sHelmConnector(K8sHelmBaseConnector):
                 The --wait flag will be set automatically if --atomic is used
             timeout (float): The time, in seconds, to wait
             kubeconfig (str): Kubeconfig file path
-
+            force (bool): If set, helm forces resource updates through a replacement strategy. This may recreate pods.
         Returns:
             str: command to upgrade a Helm Chart release
         """
@@ -729,18 +730,30 @@ class K8sHelmConnector(K8sHelmBaseConnector):
         if atomic:
             atomic_str = "--atomic"
 
+        # force
+        force_str = ""
+        if force:
+            force_str = "--force "
+
         # version
         version_str = ""
         if version:
             version_str = "--version {}".format(version)
 
+        # namespace
+        namespace_str = ""
+        if namespace:
+            namespace_str = "--namespace {}".format(namespace)
+
         command = (
-            "env KUBECONFIG={kubeconfig} {helm} upgrade {atomic} --output yaml {params} {timeout} "
+            "env KUBECONFIG={kubeconfig} {helm} upgrade {namespace} {atomic} --output yaml {params} {timeout} {force}"
             "--reuse-values {name} {model} {ver}"
         ).format(
             kubeconfig=kubeconfig,
             helm=self._helm_command,
+            namespace=namespace_str,
             atomic=atomic_str,
+            force=force_str,
             params=params_str,
             timeout=timeout_str,
             name=kdu_instance,
index 86b2790..33add05 100644 (file)
@@ -271,9 +271,46 @@ class TestK8sHelm3Conn(asynctest.TestCase):
         self.helm_conn.get_instance_info = asynctest.CoroutineMock(
             return_value=instance_info
         )
+        # TEST-1 (--force true)
+        await self.helm_conn.upgrade(
+            self.cluster_uuid,
+            kdu_instance,
+            kdu_model,
+            atomic=True,
+            db_dict=db_dict,
+            force=True,
+        )
+        self.helm_conn.fs.sync.assert_called_with(from_path=self.cluster_id)
+        self.helm_conn.fs.reverse_sync.assert_has_calls(
+            [
+                asynctest.call(from_path=self.cluster_id),
+                asynctest.call(from_path=self.cluster_id),
+            ]
+        )
+        self.helm_conn._store_status.assert_called_with(
+            cluster_id=self.cluster_id,
+            kdu_instance=kdu_instance,
+            namespace=self.namespace,
+            db_dict=db_dict,
+            operation="upgrade",
+        )
+        command = (
+            "env KUBECONFIG=./tmp/helm3_cluster_id/.kube/config "
+            "/usr/bin/helm3 upgrade stable-openldap-0005399828 stable/openldap "
+            "--namespace testk8s --atomic --force --output yaml  --timeout 300s "
+            "--reuse-values --version 1.2.3"
+        )
+        self.helm_conn._local_async_exec.assert_called_with(
+            command=command, env=self.env, raise_exception_on_error=False
+        )
 
+        # TEST-2 (--force false)
         await self.helm_conn.upgrade(
-            self.cluster_uuid, kdu_instance, kdu_model, atomic=True, db_dict=db_dict
+            self.cluster_uuid,
+            kdu_instance,
+            kdu_model,
+            atomic=True,
+            db_dict=db_dict,
         )
         self.helm_conn.fs.sync.assert_called_with(from_path=self.cluster_id)
         self.helm_conn.fs.reverse_sync.assert_has_calls(
@@ -299,6 +336,56 @@ class TestK8sHelm3Conn(asynctest.TestCase):
             command=command, env=self.env, raise_exception_on_error=False
         )
 
+    @asynctest.fail_on(active_handles=True)
+    async def test_upgrade_namespace(self):
+        kdu_model = "stable/openldap:1.2.3"
+        kdu_instance = "stable-openldap-0005399828"
+        db_dict = {}
+        instance_info = {
+            "chart": "openldap-1.2.2",
+            "name": kdu_instance,
+            "namespace": self.namespace,
+            "revision": 1,
+            "status": "DEPLOYED",
+        }
+        self.helm_conn._local_async_exec = asynctest.CoroutineMock(return_value=("", 0))
+        self.helm_conn._store_status = asynctest.CoroutineMock()
+        self.helm_conn.get_instance_info = asynctest.CoroutineMock(
+            return_value=instance_info
+        )
+
+        await self.helm_conn.upgrade(
+            self.cluster_uuid,
+            kdu_instance,
+            kdu_model,
+            atomic=True,
+            db_dict=db_dict,
+            namespace="default",
+        )
+        self.helm_conn.fs.sync.assert_called_with(from_path=self.cluster_id)
+        self.helm_conn.fs.reverse_sync.assert_has_calls(
+            [
+                asynctest.call(from_path=self.cluster_id),
+                asynctest.call(from_path=self.cluster_id),
+            ]
+        )
+        self.helm_conn._store_status.assert_called_with(
+            cluster_id=self.cluster_id,
+            kdu_instance=kdu_instance,
+            namespace="default",
+            db_dict=db_dict,
+            operation="upgrade",
+        )
+        command = (
+            "env KUBECONFIG=./tmp/helm3_cluster_id/.kube/config "
+            "/usr/bin/helm3 upgrade stable-openldap-0005399828 stable/openldap "
+            "--namespace default --atomic --output yaml  --timeout 300s "
+            "--reuse-values --version 1.2.3"
+        )
+        self.helm_conn._local_async_exec.assert_called_with(
+            command=command, env=self.env, raise_exception_on_error=False
+        )
+
     @asynctest.fail_on(active_handles=True)
     async def test_scale(self):
         kdu_model = "stable/openldap:1.2.3"
index f43b24b..161471a 100644 (file)
@@ -190,7 +190,7 @@ class TestK8sHelmConn(asynctest.TestCase):
         )
 
     @asynctest.fail_on(active_handles=True)
-    async def test_upgrade(self):
+    async def test_upgrade_force_true(self):
         kdu_model = "stable/openldap:1.2.3"
         kdu_instance = "stable-openldap-0005399828"
         db_dict = {}
@@ -206,9 +206,14 @@ class TestK8sHelmConn(asynctest.TestCase):
         self.helm_conn.get_instance_info = asynctest.CoroutineMock(
             return_value=instance_info
         )
-
+        # TEST-1 (--force true)
         await self.helm_conn.upgrade(
-            self.cluster_uuid, kdu_instance, kdu_model, atomic=True, db_dict=db_dict
+            self.cluster_uuid,
+            kdu_instance,
+            kdu_model,
+            atomic=True,
+            db_dict=db_dict,
+            force=True,
         )
         self.helm_conn.fs.sync.assert_called_with(from_path=self.cluster_id)
         self.helm_conn.fs.reverse_sync.assert_has_calls(
@@ -225,7 +230,86 @@ class TestK8sHelmConn(asynctest.TestCase):
             operation="upgrade",
         )
         command = (
-            "env KUBECONFIG=./tmp/helm_cluster_id/.kube/config /usr/bin/helm upgrade "
+            "env KUBECONFIG=./tmp/helm_cluster_id/.kube/config /usr/bin/helm upgrade --namespace testk8s "
+            "--atomic --output yaml  --timeout 300 --force --reuse-values stable-openldap-0005399828 stable/openldap "
+            "--version 1.2.3"
+        )
+        self.helm_conn._local_async_exec.assert_called_with(
+            command=command, env=self.env, raise_exception_on_error=False
+        )
+        # TEST-2 (--force false)
+        await self.helm_conn.upgrade(
+            self.cluster_uuid,
+            kdu_instance,
+            kdu_model,
+            atomic=True,
+            db_dict=db_dict,
+        )
+        self.helm_conn.fs.sync.assert_called_with(from_path=self.cluster_id)
+        self.helm_conn.fs.reverse_sync.assert_has_calls(
+            [
+                asynctest.call(from_path=self.cluster_id),
+                asynctest.call(from_path=self.cluster_id),
+            ]
+        )
+        self.helm_conn._store_status.assert_called_with(
+            cluster_id=self.cluster_id,
+            kdu_instance=kdu_instance,
+            namespace=self.namespace,
+            db_dict=db_dict,
+            operation="upgrade",
+        )
+        command = (
+            "env KUBECONFIG=./tmp/helm_cluster_id/.kube/config /usr/bin/helm upgrade --namespace testk8s "
+            "--atomic --output yaml  --timeout 300 --reuse-values stable-openldap-0005399828 stable/openldap "
+            "--version 1.2.3"
+        )
+        self.helm_conn._local_async_exec.assert_called_with(
+            command=command, env=self.env, raise_exception_on_error=False
+        )
+
+    @asynctest.fail_on(active_handles=True)
+    async def test_upgrade_namespace(self):
+        kdu_model = "stable/openldap:1.2.3"
+        kdu_instance = "stable-openldap-0005399828"
+        db_dict = {}
+        instance_info = {
+            "chart": "openldap-1.2.2",
+            "name": kdu_instance,
+            "namespace": self.namespace,
+            "revision": 1,
+            "status": "DEPLOYED",
+        }
+        self.helm_conn._local_async_exec = asynctest.CoroutineMock(return_value=("", 0))
+        self.helm_conn._store_status = asynctest.CoroutineMock()
+        self.helm_conn.get_instance_info = asynctest.CoroutineMock(
+            return_value=instance_info
+        )
+
+        await self.helm_conn.upgrade(
+            self.cluster_uuid,
+            kdu_instance,
+            kdu_model,
+            atomic=True,
+            db_dict=db_dict,
+            namespace="default",
+        )
+        self.helm_conn.fs.sync.assert_called_with(from_path=self.cluster_id)
+        self.helm_conn.fs.reverse_sync.assert_has_calls(
+            [
+                asynctest.call(from_path=self.cluster_id),
+                asynctest.call(from_path=self.cluster_id),
+            ]
+        )
+        self.helm_conn._store_status.assert_called_with(
+            cluster_id=self.cluster_id,
+            kdu_instance=kdu_instance,
+            namespace="default",
+            db_dict=db_dict,
+            operation="upgrade",
+        )
+        command = (
+            "env KUBECONFIG=./tmp/helm_cluster_id/.kube/config /usr/bin/helm upgrade --namespace default "
             "--atomic --output yaml  --timeout 300 --reuse-values stable-openldap-0005399828 stable/openldap "
             "--version 1.2.3"
         )
@@ -281,7 +365,7 @@ class TestK8sHelmConn(asynctest.TestCase):
         )
         command = (
             "env KUBECONFIG=./tmp/helm_cluster_id/.kube/config "
-            "/usr/bin/helm upgrade --atomic --output yaml --set replicaCount=2 "
+            "/usr/bin/helm upgrade --namespace testk8s --atomic --output yaml --set replicaCount=2 "
             "--timeout 1800 --reuse-values stable-openldap-0005399828 stable/openldap "
             "--version 1.2.3"
         )
@@ -301,7 +385,7 @@ class TestK8sHelmConn(asynctest.TestCase):
         )
         command = (
             "env KUBECONFIG=./tmp/helm_cluster_id/.kube/config "
-            "/usr/bin/helm upgrade --atomic --output yaml --set dummy-app.replicas=3 "
+            "/usr/bin/helm upgrade --namespace testk8s --atomic --output yaml --set dummy-app.replicas=3 "
             "--timeout 1800 --reuse-values stable-openldap-0005399828 stable/openldap "
             "--version 1.2.3"
         )