From: garciadeblas Date: Wed, 4 Feb 2026 16:40:30 +0000 (+0100) Subject: Fix cluster deregistration to use two workflows: disconnect and purge X-Git-Url: https://osm.etsi.org/gitweb/?a=commitdiff_plain;h=f6dc6044398e58dd22552f2223eb404a0ea6685b;p=osm%2FLCM.git Fix cluster deregistration to use two workflows: disconnect and purge Change-Id: I1c3ef019363384e7a5b68c4e7929506a92efd6fe Signed-off-by: garciadeblas --- diff --git a/osm_lcm/k8s.py b/osm_lcm/k8s.py index 6b02ec5f..f3f7ad43 100644 --- a/osm_lcm/k8s.py +++ b/osm_lcm/k8s.py @@ -362,6 +362,12 @@ class ClusterLcm(GitOpsLcm): "delete_cluster": { "check_resource_function": self.check_delete_cluster, }, + "deregister_cluster": { + "check_resource_function": self.check_deregister_cluster, + }, + "purge_cluster": { + "check_resource_function": self.check_purge_cluster, + }, } self.regist = vim_sdn.K8sClusterLcm(msg, self.lcm_tasks, config) @@ -1162,6 +1168,50 @@ class ClusterLcm(GitOpsLcm): op_id, checkings_list, "clusters", db_cluster ) + async def check_deregister_cluster(self, op_id, op_params, content): + self.logger.info( + f"check_deregister_cluster Operation {op_id}. Params: {op_params}." + ) + # self.logger.debug(f"Content: {content}") + db_cluster = content["cluster"] + cluster_name = db_cluster["git_name"].lower() + cluster_kustomization_name = cluster_name + checkings_list = [ + { + "item": "kustomization", + "name": f"{cluster_kustomization_name}-bstrp-fluxctrl", + "namespace": "managed-resources", + "deleted": True, + "timeout": self._checkloop_kustomization_timeout, + "enable": True, + "resourceState": "IN_PROGRESS.CLUSTER_DISCONNECTED", + }, + ] + return await self.common_check_list( + op_id, checkings_list, "clusters", db_cluster + ) + + async def check_purge_cluster(self, op_id, op_params, content): + self.logger.info(f"check_purge_cluster Operation {op_id}. Params: {op_params}.") + # self.logger.debug(f"Content: {content}") + db_cluster = content["cluster"] + cluster_name = db_cluster["git_name"].lower() + cluster_kustomization_name = cluster_name + checkings_list = [ + { + "item": "kustomization", + "name": cluster_kustomization_name, + "namespace": "managed-resources", + "deleted": True, + "timeout": self._checkloop_kustomization_timeout, + "enable": True, + "resourceState": "IN_PROGRESS.CLUSTER_DEREGISTERED", + }, + ] + return await self.common_check_list( + op_id, checkings_list, "clusters", db_cluster + ) + async def deregister(self, params, order_id): self.logger.info("cluster deregister enter") @@ -1235,14 +1285,104 @@ class ClusterLcm(GitOpsLcm): ) self.db.set_one("clusters", {"_id": db_cluster["_id"]}, db_cluster) - await self.delete(params, order_id) - # Clean items used in the workflow or in the cluster, no matter if the workflow succeeded + # Clean items used in the workflow, no matter if the workflow succeeded clean_status, clean_msg = await self.odu.clean_items_workflow( "deregister_cluster", op_id, op_params, workflow_content ) self.logger.info( f"clean_status is :{clean_status} and clean_msg is :{clean_msg}" ) + if not workflow_status or not resource_status: + return + + # Now, we launch the workflow to purge the cluster + # Initialize the operation again + self.initialize_operation(cluster_id, op_id) + workflow_res, workflow_name, _ = await self.odu.launch_workflow( + "purge_cluster", op_id, op_params, workflow_content + ) + if not workflow_res: + self.logger.error(f"Failed to launch workflow: {workflow_name}") + db_cluster["state"] = "FAILED_DELETION" + db_cluster["resourceState"] = "ERROR" + db_cluster = self.update_operation_history( + db_cluster, op_id, workflow_status=False, resource_status=None + ) + self.db.set_one("clusters", {"_id": db_cluster["_id"]}, db_cluster) + # Clean items used in the workflow, no matter if the workflow succeeded + clean_status, clean_msg = await self.odu.clean_items_workflow( + "purge_cluster", op_id, op_params, workflow_content + ) + self.logger.info( + f"clean_status is :{clean_status} and clean_msg is :{clean_msg}" + ) + return + + self.logger.info("workflow_name is: {}".format(workflow_name)) + workflow_status, workflow_msg = await self.odu.check_workflow_status( + op_id, workflow_name + ) + self.logger.info( + "workflow_status is: {} and workflow_msg is: {}".format( + workflow_status, workflow_msg + ) + ) + if workflow_status: + db_cluster["state"] = "DELETED" + db_cluster["resourceState"] = "IN_PROGRESS.GIT_SYNCED" + else: + db_cluster["state"] = "FAILED_DELETION" + db_cluster["resourceState"] = "ERROR" + # has to call update_operation_history return content + db_cluster = self.update_operation_history( + db_cluster, op_id, workflow_status, None + ) + self.db.set_one("clusters", {"_id": db_cluster["_id"]}, db_cluster) + + # Clean items used in the workflow or in the cluster, no matter if the workflow succeeded + clean_status, clean_msg = await self.odu.clean_items_workflow( + "purge_cluster", op_id, op_params, workflow_content + ) + self.logger.info( + f"clean_status is :{clean_status} and clean_msg is :{clean_msg}" + ) + + if workflow_status: + resource_status, resource_msg = await self.check_resource_status( + "purge_cluster", op_id, op_params, workflow_content + ) + self.logger.info( + "resource_status is :{} and resource_msg is :{}".format( + resource_status, resource_msg + ) + ) + if resource_status: + db_cluster["resourceState"] = "READY" + else: + db_cluster["resourceState"] = "ERROR" + + db_cluster["operatingState"] = "IDLE" + db_cluster = self.update_operation_history( + db_cluster, op_id, workflow_status, resource_status + ) + db_cluster["current_operation"] = None + self.db.set_one("clusters", {"_id": db_cluster["_id"]}, db_cluster) + + force = params.get("force", False) + if force: + force_delete_status = self.check_force_delete_and_delete_from_db( + cluster_id, workflow_status, resource_status, force + ) + if force_delete_status: + return + + # To delete it from DB + if db_cluster["state"] == "DELETED": + self.delete_cluster(db_cluster) + + # To delete it from k8scluster collection + self.db.del_one("k8sclusters", {"name": db_cluster["name"]}) + return async def get_creds(self, params, order_id): diff --git a/osm_lcm/odu_libs/cluster_mgmt.py b/osm_lcm/odu_libs/cluster_mgmt.py index 9c0cbafb..b4a24d62 100644 --- a/osm_lcm/odu_libs/cluster_mgmt.py +++ b/osm_lcm/odu_libs/cluster_mgmt.py @@ -159,7 +159,7 @@ async def create_cluster(self, op_id, op_params, content): workflow_debug=self._workflow_debug, workflow_dry_run=self._workflow_dry_run, ) - self.logger.debug(f"Workflow manifest: {manifest}") + # self.logger.debug(f"Workflow manifest: {manifest}") # Submit workflow self.logger.debug(f"Testing kubectl: {self._kubectl}") @@ -259,7 +259,7 @@ async def update_cluster(self, op_id, op_params, content): workflow_debug=self._workflow_debug, workflow_dry_run=self._workflow_dry_run, ) - self.logger.info(manifest) + # self.logger.info(manifest) # Submit workflow self.logger.debug(f"Testing kubectl: {self._kubectl}") @@ -305,7 +305,7 @@ async def delete_cluster(self, op_id, op_params, content): workflow_debug=self._workflow_debug, workflow_dry_run=self._workflow_dry_run, ) - self.logger.info(manifest) + # self.logger.info(manifest) # Submit workflow self.logger.debug(f"Testing kubectl: {self._kubectl}") @@ -428,7 +428,7 @@ async def register_cluster(self, op_id, op_params, content): workflow_debug=self._workflow_debug, workflow_dry_run=self._workflow_dry_run, ) - self.logger.debug(f"Workflow manifest: {manifest}") + # self.logger.debug(f"Workflow manifest: {manifest}") # Submit workflow self.logger.debug(f"Testing kubectl: {self._kubectl}") @@ -458,6 +458,37 @@ async def deregister_cluster(self, op_id, op_params, content): workflow_template = "launcher-disconnect-flux-remote-cluster.j2" workflow_name = f"deregister-cluster-{db_cluster['_id']}" + # Create secret with kubeconfig + secret_name = f"kubeconfig-{cluster_name}" + secret_namespace = "osm-workflows" + secret_key = "kubeconfig" + secret_value = yaml.safe_dump( + db_cluster["credentials"], indent=2, default_flow_style=False, sort_keys=False + ) + try: + self.logger.debug(f"Testing kubectl: {self._kubectl}") + self.logger.debug( + f"Testing kubectl configuration: {self._kubectl.configuration}" + ) + self.logger.debug( + f"Testing kubectl configuration Host: {self._kubectl.configuration.host}" + ) + await self.create_secret( + secret_name, + secret_namespace, + secret_key, + secret_value, + ) + except Exception as e: + self.logger.info( + f"Cannot create secret {secret_name} in namespace {secret_namespace}: {e}" + ) + return ( + False, + f"Cannot create secret {secret_name} in namespace {secret_namespace}: {e}", + None, + ) + # Additional params for the workflow cluster_kustomization_name = cluster_name osm_project_name = "osm_admin" # TODO: get project name from DB @@ -473,7 +504,54 @@ async def deregister_cluster(self, op_id, op_params, content): workflow_debug=self._workflow_debug, workflow_dry_run=self._workflow_dry_run, ) - self.logger.info(manifest) + # self.logger.info(manifest) + + # Submit workflow + self.logger.debug(f"Testing kubectl: {self._kubectl}") + self.logger.debug(f"Testing kubectl configuration: {self._kubectl.configuration}") + self.logger.debug( + f"Testing kubectl configuration Host: {self._kubectl.configuration.host}" + ) + self._kubectl.create_generic_object( + namespace="osm-workflows", + manifest_dict=yaml.safe_load(manifest), + api_group="argoproj.io", + api_plural="workflows", + api_version="v1alpha1", + ) + return True, workflow_name, None + + +async def purge_cluster(self, op_id, op_params, content): + self.logger.info(f"purge_cluster Enter. Operation {op_id}. Params: {op_params}") + # self.logger.debug(f"Content: {content}") + + db_cluster = content["cluster"] + cluster_name = db_cluster["git_name"].lower() + + workflow_template = "launcher-purge-delete-cluster.yaml.j2" + workflow_name = f"purge-cluster-{db_cluster['_id']}" + + # Create secret with kubeconfig + temp_kubeconfig_secret_name = f"kubeconfig-{cluster_name}" + + # Additional params for the workflow + cluster_kustomization_name = cluster_name + osm_project_name = "osm_admin" # TODO: get project name from DB + + # Render workflow + manifest = self.render_jinja_template( + workflow_template, + output_file=None, + workflow_name=workflow_name, + git_fleet_url=self._repo_fleet_url, + cluster_kustomization_name=cluster_kustomization_name, + osm_project_name=osm_project_name, + temp_kubeconfig_secret_name=temp_kubeconfig_secret_name, + workflow_debug=self._workflow_debug, + workflow_dry_run=self._workflow_dry_run, + ) + # self.logger.info(manifest) # Submit workflow self.logger.debug(f"Testing kubectl: {self._kubectl}") @@ -575,9 +653,9 @@ async def clean_items_cluster_register(self, op_id, op_params, content): return False, f"Error while cleaning items: {e}" -async def clean_items_cluster_deregister(self, op_id, op_params, content): +async def clean_items_cluster_purge(self, op_id, op_params, content): self.logger.info( - f"clean_items_cluster_deregister Enter. Operation {op_id}. Params: {op_params}" + f"clean_items_cluster_purge Enter. Operation {op_id}. Params: {op_params}" ) # self.logger.debug(f"Content: {content}") # Clean secrets @@ -585,6 +663,10 @@ async def clean_items_cluster_deregister(self, op_id, op_params, content): cluster_name = content["cluster"]["git_name"].lower() items = { "secrets": [ + { + "name": f"kubeconfig-{cluster_name}", + "namespace": "osm-workflows", + }, { "name": f"kubeconfig-{cluster_name}", "namespace": "managed-resources", diff --git a/osm_lcm/odu_libs/templates/launcher-purge-delete-cluster.yaml.j2 b/osm_lcm/odu_libs/templates/launcher-purge-delete-cluster.yaml.j2 new file mode 100644 index 00000000..8fd52eb3 --- /dev/null +++ b/osm_lcm/odu_libs/templates/launcher-purge-delete-cluster.yaml.j2 @@ -0,0 +1,57 @@ +####################################################################################### +# Copyright ETSI Contributors and Others. +# +# 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. +####################################################################################### + +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + name: {{ workflow_name }} +spec: + arguments: + parameters: + + # Fleet repo + - name: git_fleet_url + value: "{{ git_fleet_url }}" + - name: fleet_destination_folder + value: "/fleet/fleet-osm" + - name: git_fleet_cred_secret + value: fleet-repo + + # Specific parameters + - name: cluster_kustomization_name + value: {{ cluster_kustomization_name }} + - name: project_name + value: "{{ osm_project_name }}" + - name: temp_kubeconfig_secret_name + value: "{{ temp_kubeconfig_secret_name }}" + - name: purge + value: "true" + + # Debug/dry run? + - name: debug + value: "{{ workflow_debug }}" + - name: dry_run + value: "{{ workflow_dry_run }}" + + # Cleanup policy + ttlStrategy: + secondsAfterCompletion: 1000 # Time to live after workflow is completed, replaces ttlSecondsAfterFinished + secondsAfterSuccess: 500 # Time to live after workflow is successful + secondsAfterFailure: 500 # Time to live after workflow fails + + workflowTemplateRef: + name: full-purge-delete-cluster-wft diff --git a/osm_lcm/odu_workflows.py b/osm_lcm/odu_workflows.py index 0c51b2d8..93ba14b6 100644 --- a/osm_lcm/odu_workflows.py +++ b/osm_lcm/odu_workflows.py @@ -85,7 +85,10 @@ class OduWorkflow(LcmBase): }, "deregister_cluster": { "workflow_function": self.deregister_cluster, - "clean_function": self.clean_items_cluster_deregister, + }, + "purge_cluster": { + "workflow_function": self.purge_cluster, + "clean_function": self.clean_items_cluster_purge, }, "create_profile": { "workflow_function": self.create_profile, @@ -188,10 +191,11 @@ class OduWorkflow(LcmBase): delete_cluster = odu_cluster_mgmt.delete_cluster register_cluster = odu_cluster_mgmt.register_cluster deregister_cluster = odu_cluster_mgmt.deregister_cluster + purge_cluster = odu_cluster_mgmt.purge_cluster clean_items_cluster_create = odu_cluster_mgmt.clean_items_cluster_create clean_items_cluster_update = odu_cluster_mgmt.clean_items_cluster_update clean_items_cluster_register = odu_cluster_mgmt.clean_items_cluster_register - clean_items_cluster_deregister = odu_cluster_mgmt.clean_items_cluster_deregister + clean_items_cluster_purge = odu_cluster_mgmt.clean_items_cluster_purge get_cluster_credentials = odu_cluster_mgmt.get_cluster_credentials add_nodegroup = odu_nodegroup.add_nodegroup scale_nodegroup = odu_nodegroup.scale_nodegroup