Fix cluster deregistration to use two workflows: disconnect and purge 17/15717/4
authorgarciadeblas <gerardo.garciadeblas@telefonica.com>
Wed, 4 Feb 2026 16:40:30 +0000 (17:40 +0100)
committergarciadeblas <gerardo.garciadeblas@telefonica.com>
Fri, 6 Feb 2026 10:55:24 +0000 (11:55 +0100)
Change-Id: I1c3ef019363384e7a5b68c4e7929506a92efd6fe
Signed-off-by: garciadeblas <gerardo.garciadeblas@telefonica.com>
osm_lcm/k8s.py
osm_lcm/odu_libs/cluster_mgmt.py
osm_lcm/odu_libs/templates/launcher-purge-delete-cluster.yaml.j2 [new file with mode: 0644]
osm_lcm/odu_workflows.py

index 6b02ec5..f3f7ad4 100644 (file)
@@ -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):
index 9c0cbaf..b4a24d6 100644 (file)
@@ -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 (file)
index 0000000..8fd52eb
--- /dev/null
@@ -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
index 0c51b2d..93ba14b 100644 (file)
@@ -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