From 085fa8d4658a9b621354d5a08853086e2696abdc Mon Sep 17 00:00:00 2001 From: Gabriel Cuba Date: Mon, 10 Oct 2022 12:13:55 -0500 Subject: [PATCH] Feature 10956: Add namespace and force arguments to helm upgrade Change-Id: I8e37e43b72c5f7f63c4b9f49542905727610fa5a Signed-off-by: Gabriel Cuba --- n2vc/k8s_conn.py | 2 + n2vc/k8s_helm3_conn.py | 11 ++- n2vc/k8s_helm_base_conn.py | 22 ++++-- n2vc/k8s_helm_conn.py | 17 ++++- n2vc/tests/unit/test_k8s_helm3_conn.py | 89 +++++++++++++++++++++++- n2vc/tests/unit/test_k8s_helm_conn.py | 96 ++++++++++++++++++++++++-- 6 files changed, 219 insertions(+), 18 deletions(-) diff --git a/n2vc/k8s_conn.py b/n2vc/k8s_conn.py index ada7075..1c88653 100644 --- a/n2vc/k8s_conn.py +++ b/n2vc/k8s_conn.py @@ -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: }, e.g. {collection: "nsrs", filter: {_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 """ diff --git a/n2vc/k8s_helm3_conn.py b/n2vc/k8s_helm3_conn.py index d828da2..3d7e3b2 100644 --- a/n2vc/k8s_helm3_conn.py +++ b/n2vc/k8s_helm3_conn.py @@ -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, diff --git a/n2vc/k8s_helm_base_conn.py b/n2vc/k8s_helm_base_conn.py index e89d6fa..a6cb11a 100644 --- a/n2vc/k8s_helm_base_conn.py +++ b/n2vc/k8s_helm_base_conn.py @@ -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 """ diff --git a/n2vc/k8s_helm_conn.py b/n2vc/k8s_helm_conn.py index cd440a9..0ea8920 100644 --- a/n2vc/k8s_helm_conn.py +++ b/n2vc/k8s_helm_conn.py @@ -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, diff --git a/n2vc/tests/unit/test_k8s_helm3_conn.py b/n2vc/tests/unit/test_k8s_helm3_conn.py index 86b2790..33add05 100644 --- a/n2vc/tests/unit/test_k8s_helm3_conn.py +++ b/n2vc/tests/unit/test_k8s_helm3_conn.py @@ -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" diff --git a/n2vc/tests/unit/test_k8s_helm_conn.py b/n2vc/tests/unit/test_k8s_helm_conn.py index f43b24b..161471a 100644 --- a/n2vc/tests/unit/test_k8s_helm_conn.py +++ b/n2vc/tests/unit/test_k8s_helm_conn.py @@ -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" ) -- 2.17.1