Check dependency between NBI operations 83/14883/13
authoryshah <shahithya.y@tataelxsi.co.in>
Fri, 17 Jan 2025 04:46:03 +0000 (04:46 +0000)
committergarciadeblas <gerardo.garciadeblas@telefonica.com>
Tue, 22 Jul 2025 14:26:48 +0000 (16:26 +0200)
Change-Id: I77e1e5c78cba1deb3f8fe8ba5b4c2fed4ff36cff
Signed-off-by: yshah <shahithya.y@tataelxsi.co.in>
osm_nbi/acm_topic.py
osm_nbi/k8s_topics.py

index 8d529aa..eb335da 100644 (file)
@@ -26,7 +26,8 @@ from osm_common.fsbase import FsException
 from osm_nbi.base_topic import BaseTopic, EngineException
 from osm_nbi.validation import ValidationError
 
-# import logging
+import logging
+
 # import random
 # import string
 # from yaml import safe_load, YAMLError
@@ -35,6 +36,11 @@ from osm_nbi.validation import ValidationError
 class ACMOperationTopic:
     def __init__(self, db, fs, msg, auth):
         self.multiproject = None  # Declare the attribute here
+        self.db = db
+        self.fs = fs
+        self.msg = msg
+        self.logger = logging.getLogger("nbi.base")
+        self.auth = auth
 
     @staticmethod
     def format_on_operation(content, operation_type, operation_params=None):
@@ -57,6 +63,48 @@ class ACMOperationTopic:
         content["operationHistory"].append(operation)
         return op_id
 
+    def check_dependency(self, check, operation_type=None):
+        topic_to_db_mapping = {
+            "cluster": "clusters",
+            "ksu": "ksus",
+            "infra_controller_profiles": "k8sinfra_controller",
+            "infra_config_profiles": "k8sinfra_config",
+            "resource_profiles": "k8sresource",
+            "app_profiles": "k8sapp",
+            "oka": "okas",
+        }
+        for topic, _id in check.items():
+            filter_q = {
+                "_id": _id,
+            }
+            if topic == "okas":
+                for oka_element in check[topic]:
+                    self.check_dependency({"oka": oka_element})
+            if topic not in ("okas"):
+                element_content = self.db.get_one(topic_to_db_mapping[topic], filter_q)
+                element_name = element_content.get("name")
+                state = element_content["state"]
+                if (
+                    operation_type == "delete"
+                    and state == "FAILED_CREATION"
+                    and element_content["operatingState"] == "IDLE"
+                ):
+                    self.logger.info(f"Delete operation is allowed in {state} state")
+                    return
+                elif element_content["state"] != "CREATED":
+                    raise EngineException(
+                        f"State of the {element_name} {topic} is {state}",
+                        HTTPStatus.UNPROCESSABLE_ENTITY,
+                    )
+                elif (
+                    state == "CREATED"
+                    and element_content["operatingState"] == "PROCESSING"
+                ):
+                    raise EngineException(
+                        f"operatingState of the {element_name} {topic} is not IDLE",
+                        HTTPStatus.UNPROCESSABLE_ENTITY,
+                    )
+
 
 class ACMTopic(BaseTopic, ACMOperationTopic):
     def __init__(self, db, fs, msg, auth):
index 496b737..dc6016c 100644 (file)
@@ -73,6 +73,8 @@ class InfraContTopic(ProfileTopic):
         return self.default_profile(rollback, session, indata, kwargs, headers)
 
     def delete(self, session, _id, dry_run=False, not_send_msg=None):
+        check = {"infra_controller_profiles": _id}
+        self.check_dependency(check, operation_type="delete")
         self.delete_profile(session, _id, dry_run, not_send_msg)
         return _id
 
@@ -95,6 +97,8 @@ class InfraConfTopic(ProfileTopic):
         return self.default_profile(rollback, session, indata, kwargs, headers)
 
     def delete(self, session, _id, dry_run=False, not_send_msg=None):
+        check = {"infra_config_profiles": _id}
+        self.check_dependency(check, operation_type="delete")
         self.delete_profile(session, _id, dry_run, not_send_msg)
         return _id
 
@@ -117,6 +121,8 @@ class AppTopic(ProfileTopic):
         return self.default_profile(rollback, session, indata, kwargs, headers)
 
     def delete(self, session, _id, dry_run=False, not_send_msg=None):
+        check = {"app_profiles": _id}
+        self.check_dependency(check, operation_type="delete")
         self.delete_profile(session, _id, dry_run, not_send_msg)
         return _id
 
@@ -139,6 +145,8 @@ class ResourceTopic(ProfileTopic):
         return self.default_profile(rollback, session, indata, kwargs, headers)
 
     def delete(self, session, _id, dry_run=False, not_send_msg=None):
+        check = {"resource_profiles": _id}
+        self.check_dependency(check, operation_type="delete")
         self.delete_profile(session, _id, dry_run, not_send_msg)
         return _id
 
@@ -447,6 +455,8 @@ class ClusterTopic(ACMTopic):
         return True
 
     def add_profile(self, session, _id, item, indata=None):
+        check = {"cluster": _id, item: indata["add_profile"][0]["id"]}
+        self.check_dependency(check)
         indata = self._remove_envelop(indata)
         operation_params = indata
         profile_id = indata["add_profile"][0]["id"]
@@ -490,6 +500,8 @@ class ClusterTopic(ACMTopic):
         return default_profiles
 
     def remove_profile(self, session, _id, item, indata):
+        check = {"cluster": _id, item: indata["remove_profile"][0]["id"]}
+        self.check_dependency(check)
         indata = self._remove_envelop(indata)
         operation_params = indata
         profile_id = indata["remove_profile"][0]["id"]
@@ -613,6 +625,8 @@ class ClusterTopic(ACMTopic):
         return {"cluster_id": _id, "operation_id": op_id, "force": session["force"]}
 
     def delete(self, session, _id, dry_run=False, not_send_msg=None):
+        check = {"cluster": _id}
+        self.check_dependency(check, operation_type="delete")
         filter_q = self._get_project_filter(session)
         filter_q[self.id_field(self.topic, _id)] = _id
         check = self.db.get_one(self.topic, filter_q)
@@ -1046,17 +1060,20 @@ class KsusTopic(ACMTopic):
 
     def new(self, rollback, session, indata=None, kwargs=None, headers=None):
         _id_list = []
-        for ksus in indata["ksus"]:
-            content = ksus
+        for content in indata["ksus"]:
+            check = {content["profile"]["profile_type"]: content["profile"]["_id"]}
             oka = content["oka"][0]
             oka_flag = ""
             if oka["_id"]:
+                check["okas"] = []
                 oka_flag = "_id"
                 oka["sw_catalog_path"] = ""
             elif oka["sw_catalog_path"]:
                 oka_flag = "sw_catalog_path"
 
             for okas in content["oka"]:
+                if okas.get("_id") is not None:
+                    check["okas"].append(okas["_id"])
                 if okas["_id"] and okas["sw_catalog_path"]:
                     raise EngineException(
                         "Cannot create ksu with both OKA and SW catalog path",
@@ -1071,6 +1088,7 @@ class KsusTopic(ACMTopic):
                         "Cannot create ksu. Give either OKA or SW catalog path for all oka in a KSU",
                         HTTPStatus.UNPROCESSABLE_ENTITY,
                     )
+            self.check_dependency(check)
 
             # Override descriptor with query string kwargs
             content = self._remove_envelop(content)
@@ -1123,6 +1141,11 @@ class KsusTopic(ACMTopic):
         return _id_list, op_id
 
     def clone(self, rollback, session, _id, indata, kwargs, headers):
+        check = {
+            "ksu": _id,
+            indata["profile"]["profile_type"]: indata["profile"]["_id"],
+        }
+        self.check_dependency(check)
         filter_db = self._get_project_filter(session)
         filter_db[BaseTopic.id_field(self.topic, _id)] = _id
         data = self.db.get_one(self.topic, filter_db)
@@ -1151,6 +1174,11 @@ class KsusTopic(ACMTopic):
             )
 
     def move_ksu(self, session, _id, indata=None, kwargs=None, content=None):
+        check = {
+            "ksu": _id,
+            indata["profile"]["profile_type"]: indata["profile"]["_id"],
+        }
+        self.check_dependency(check)
         indata = self._remove_envelop(indata)
 
         # Override descriptor with query string kwargs
@@ -1274,6 +1302,18 @@ class KsusTopic(ACMTopic):
         return outdata
 
     def edit_ksu(self, session, _id, indata, kwargs):
+        check = {
+            "ksu": _id,
+        }
+        if indata.get("profile"):
+            check[indata["profile"]["profile_type"]] = indata["profile"]["_id"]
+        if indata.get("oka"):
+            check["okas"] = []
+            for oka in indata["oka"]:
+                if oka.get("_id") is not None:
+                    check["okas"].append(oka["_id"])
+
+        self.check_dependency(check)
         content = None
         indata = self._remove_envelop(indata)
 
@@ -1339,6 +1379,13 @@ class KsusTopic(ACMTopic):
             filter_q = self._get_project_filter(session)
         filter_q[self.id_field(self.topic, _id)] = _id
         item_content = self.db.get_one(self.topic, filter_q)
+
+        check = {
+            "ksu": _id,
+            item_content["profile"]["profile_type"]: item_content["profile"]["_id"],
+        }
+        self.check_dependency(check, operation_type="delete")
+
         item_content["state"] = "IN_DELETION"
         item_content["operatingState"] = "PROCESSING"
         item_content["resourceState"] = "IN_PROGRESS"
@@ -1449,6 +1496,8 @@ class OkaTopic(DescriptorTopic, ACMOperationTopic):
             raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
 
     def delete(self, session, _id, dry_run=False, not_send_msg=None):
+        check = {"oka": _id}
+        self.check_dependency(check, operation_type="delete")
         if not self.multiproject:
             filter_q = {}
         else:
@@ -1502,6 +1551,9 @@ class OkaTopic(DescriptorTopic, ACMOperationTopic):
         return _id, op_id
 
     def upload_content(self, session, _id, indata, kwargs, headers):
+        if headers["Method"] in ("PUT", "PATCH"):
+            check = {"oka": _id}
+            self.check_dependency(check)
         current_desc = self.show(session, _id)
 
         compressed = None