fix bug 802 decrypt vrops_password at vim_account for schema_version>=1.1
[osm/LCM.git] / osm_lcm / ns.py
index ea70a84..a8c6a8c 100644 (file)
@@ -29,7 +29,7 @@ from lcm_utils import LcmException, LcmExceptionNoMgmtIP, LcmBase
 
 from osm_common.dbbase import DbException
 from osm_common.fsbase import FsException
-from n2vc.vnf import N2VC, N2VCPrimitiveExecutionFailed, NetworkServiceDoesNotExist
+from n2vc.vnf import N2VC, N2VCPrimitiveExecutionFailed, NetworkServiceDoesNotExist, PrimitiveDoesNotExist
 
 from copy import copy, deepcopy
 from http import HTTPStatus
@@ -628,6 +628,12 @@ class NsLcm(LcmBase):
                 raise LcmException("ns_update_vnfr: Not found member_vnf_index={} at RO info".format(vnf_index))
 
     async def instantiate(self, nsr_id, nslcmop_id):
+
+        # Try to lock HA task here
+        task_is_locked_by_me = self.lcm_tasks.lock_HA('ns', 'nslcmops', nslcmop_id)
+        if not task_is_locked_by_me:
+            return
+
         logging_text = "Task ns={} instantiate={} ".format(nsr_id, nslcmop_id)
         self.logger.debug(logging_text + "Enter")
         # get all needed from database
@@ -644,6 +650,9 @@ class NsLcm(LcmBase):
         n2vc_key_list = []  # list of public keys to be injected as authorized to VMs
         exc = None
         try:
+            # wait for any previous tasks in process
+            await self.lcm_tasks.waitfor_related_HA('ns', 'nslcmops', nslcmop_id)
+
             step = "Getting nslcmop={} from db".format(nslcmop_id)
             db_nslcmop = self.db.get_one("nslcmops", {"_id": nslcmop_id})
             step = "Getting nsr={} from db".format(nsr_id)
@@ -652,17 +661,6 @@ class NsLcm(LcmBase):
             nsd = db_nsr["nsd"]
             nsr_name = db_nsr["name"]   # TODO short-name??
 
-            # look if previous tasks in process
-            task_name, task_dependency = self.lcm_tasks.lookfor_related("ns", nsr_id, nslcmop_id)
-            if task_dependency:
-                step = db_nslcmop_update["detailed-status"] = \
-                    "Waiting for related tasks to be completed: {}".format(task_name)
-                self.logger.debug(logging_text + step)
-                self.update_db_2("nslcmops", nslcmop_id, db_nslcmop_update)
-                _, pending = await asyncio.wait(task_dependency, timeout=3600)
-                if pending:
-                    raise LcmException("Timeout waiting related tasks to be completed")
-
             step = "Getting vnfrs from db"
             db_vnfrs_list = self.db.get_list("vnfrs", {"nsr-id-ref": nsr_id})
             db_vnfds_ref = {}
@@ -935,17 +933,28 @@ class NsLcm(LcmBase):
                                        .format(vca_deployed["member-vnf-index"],
                                                vca_deployed["vdu_id"])
                                 self.logger.debug(logging_text + step)
-                                primitive_id = await self.n2vc.ExecutePrimitive(
-                                    vca_deployed["model"],
-                                    vca_deployed["application"],
-                                    "get-ssh-public-key",
-                                    None,
-                                )
-                                vca_deployed["step"] = db_nsr_update[database_entry + "step"] = "get-ssh-public-key"
-                                vca_deployed["primitive_id"] = db_nsr_update[database_entry + "primitive_id"] =\
-                                    primitive_id
-                                db_nsr_update[database_entry + "operational-status"] =\
-                                    vca_deployed["operational-status"]
+                                try:
+                                    primitive_id = await self.n2vc.ExecutePrimitive(
+                                        vca_deployed["model"],
+                                        vca_deployed["application"],
+                                        "get-ssh-public-key",
+                                        None,
+                                    )
+                                    vca_deployed["step"] = db_nsr_update[database_entry + "step"] = "get-ssh-public-key"
+                                    vca_deployed["primitive_id"] = db_nsr_update[database_entry + "primitive_id"] =\
+                                        primitive_id
+                                    db_nsr_update[database_entry + "operational-status"] =\
+                                        vca_deployed["operational-status"]
+                                except PrimitiveDoesNotExist:
+                                    ssh_public_key = None
+                                    vca_deployed["step"] = db_nsr_update[database_entry + "step"] =\
+                                        "ssh-public-key-obtained"
+                                    vca_deployed["ssh-public-key"] = db_nsr_update[database_entry + "ssh-public-key"] =\
+                                        ssh_public_key
+                                    step = "charm ssh-public-key for  member_vnf_index={} vdu_id={} not needed".format(
+                                        vca_deployed["member-vnf-index"], vca_deployed["vdu_id"])
+                                    self.logger.debug(logging_text + step)
+
                         elif vca_deployed["step"] in ("get-ssh-public-key", "retry-get-ssh-public-key"):
                             primitive_id = vca_deployed["primitive_id"]
                             primitive_status = await self.n2vc.GetPrimitiveStatus(vca_deployed["model"],
@@ -1208,8 +1217,8 @@ class NsLcm(LcmBase):
                         for vdu_index, vdu in enumerate(get_iterable(vnfd, 'vdu')):
                             if vdu["id"] == vdu_id:
                                 initial_config_primitive_list = vdu['vdu-configuration'].get(
-                                    'initial-config-primitive', ())
-                            break
+                                    'initial-config-primitive', [])
+                                break
                         else:
                             raise LcmException("Not found vdu_id={} at vnfd:vdu".format(vdu_id))
                         vdur = db_vnfrs[vnf_index]["vdur"][vdu_index]
@@ -1220,7 +1229,7 @@ class NsLcm(LcmBase):
                         add_params["rw_mgmt_ip"] = vdur["ip-address"]
                     else:
                         add_params["rw_mgmt_ip"] = db_vnfrs[vnf_index]["ip-address"]
-                        initial_config_primitive_list = vnfd["vnf-configuration"].get('initial-config-primitive', ())
+                        initial_config_primitive_list = vnfd["vnf-configuration"].get('initial-config-primitive', [])
                 else:
                     if db_nsr.get("additionalParamsForNs"):
                         add_params = db_nsr["additionalParamsForNs"].copy()
@@ -1228,19 +1237,25 @@ class NsLcm(LcmBase):
                         if isinstance(v, str) and v.startswith("!!yaml "):
                             add_params[k] = yaml.safe_load(v[7:])
                     add_params["rw_mgmt_ip"] = None
+                    initial_config_primitive_list = nsd["ns-configuration"].get('initial-config-primitive', [])
 
                 # add primitive verify-ssh-credentials to the list after config only when is a vnf or vdu charm
                 initial_config_primitive_list = initial_config_primitive_list.copy()
-                if initial_config_primitive_list and vnf_index:
-                    initial_config_primitive_list.insert(1, {"name": "verify-ssh-credentials", "paramter": []})
+                if initial_config_primitive_list and vnf_index and vca_deployed.get("ssh-public-key"):
+                    initial_config_primitive_list.insert(1, {"name": "verify-ssh-credentials", "parameter": []})
 
                 for initial_config_primitive in initial_config_primitive_list:
+                    primitive_params_ = self._map_primitive_params(initial_config_primitive, {}, add_params)
+                    self.logger.debug(logging_text + step + " primitive '{}' params '{}'"
+                                      .format(initial_config_primitive["name"], primitive_params_))
                     primitive_result, primitive_detail = await self._ns_execute_primitive(
                         db_nsr["_admin"]["deployed"], vnf_index, vdu_id, vdu_name, vdu_count_index,
                         initial_config_primitive["name"],
-                        self._map_primitive_params(initial_config_primitive, {}, add_params))
+                        primitive_params_,
+                        retries=10 if initial_config_primitive["name"] == "verify-ssh-credentials" else 0,
+                        retries_interval=30)
                     if primitive_result != "COMPLETED":
-                        raise LcmException("charm error executing primitive {} for  member_vnf_index={} vdu_id={}: '{}'"
+                        raise LcmException("charm error executing primitive {} for member_vnf_index={} vdu_id={}: '{}'"
                                            .format(initial_config_primitive["name"], vca_deployed["member-vnf-index"],
                                                    vca_deployed["vdu_id"], primitive_detail))
 
@@ -1391,6 +1406,7 @@ class NsLcm(LcmBase):
             # waiting all charms are ok
             configuration_failed = False
             if number_to_configure:
+                step = "Waiting all charms are active"
                 old_status = "configuring: init: {}".format(number_to_configure)
                 db_nsr_update["config-status"] = old_status
                 db_nsr_update["detailed-status"] = old_status
@@ -1544,8 +1560,9 @@ class NsLcm(LcmBase):
         else:
             return False
 
-    # Get a numerically sorted list of the sequences for this VNFD's terminate action
-    def _get_terminate_config_primitive_seq_list(self, vnfd):
+    @staticmethod
+    def _get_terminate_config_primitive_seq_list(vnfd):
+        """ Get a numerically sorted list of the sequences for this VNFD's terminate action """
         # No need to check for existing primitive twice, already done before
         vnf_config = vnfd.get("vnf-configuration")
         seq_list = vnf_config.get("terminate-config-primitive")
@@ -1587,13 +1604,49 @@ class NsLcm(LcmBase):
         }
         return nslcmop
 
-    # Create a primitive with params from VNFD
-    # - Called from terminate() before deleting instance
-    # - Calls action() to execute the primitive
+    def _get_terminate_primitive_params(self, seq, vnf_index):
+        primitive = seq.get('name')
+        primitive_params = {}
+        params = {
+            "member_vnf_index": vnf_index,
+            "primitive": primitive,
+            "primitive_params": primitive_params,
+        }
+        desc_params = {}
+        return self._map_primitive_params(seq, params, desc_params)
+
+    def _add_suboperation(self, db_nslcmop, nslcmop_id, vnf_index, vdu_id, vdu_count_index,
+                          vdu_name, primitive, mapped_primitive_params):
+        # Create the "_admin.operations" array, or append operation if array already exists
+        key_admin = '_admin'
+        key_operations = 'operations'
+        db_nslcmop_update = {}
+        db_nslcmop_admin = db_nslcmop.get(key_admin, {})
+        op_list = db_nslcmop_admin.get(key_operations)
+        new_op = {'member_vnf_index': vnf_index,
+                  'vdu_id': vdu_id,
+                  'vdu_count_index': vdu_count_index,
+                  'primitive': primitive,
+                  'primitive_params': mapped_primitive_params}
+        if db_nslcmop_admin:
+            if not op_list:
+                # First operation, create key 'operations' with current operation as first list element
+                db_nslcmop_admin.update({key_operations: [new_op]})
+                op_list = db_nslcmop_admin.get(key_operations)
+            else:
+                # Not first operation, append operation to existing list
+                op_list.append(new_op)
+
+        db_nslcmop_update['_admin.operations'] = op_list
+        self.update_db_2("nslcmops", nslcmop_id, db_nslcmop_update)
+
     async def _terminate_action(self, db_nslcmop, nslcmop_id, nsr_id):
+        """ Create a primitive with params from VNFD
+            Called from terminate() before deleting instance
+            Calls action() to execute the primitive """
         logging_text = "Task ns={} _terminate_action={} ".format(nsr_id, nslcmop_id)
-        db_vnfds = {}
         db_vnfrs_list = self.db.get_list("vnfrs", {"nsr-id-ref": nsr_id})
+        db_vnfds = {}
         # Loop over VNFRs
         for vnfr in db_vnfrs_list:
             vnfd_id = vnfr["vnfd-id"]
@@ -1608,41 +1661,51 @@ class NsLcm(LcmBase):
             # Get the primitive's sorted sequence list
             seq_list = self._get_terminate_config_primitive_seq_list(vnfd)
             for seq in seq_list:
-                # For each sequence in list, call terminate action
+                # For each sequence in list, get primitive and call _ns_execute_primitive()
                 step = "Calling terminate action for vnf_member_index={} primitive={}".format(
                     vnf_index, seq.get("name"))
                 self.logger.debug(logging_text + step)
-                # Create the primitive for each sequence
-                operation = "action"
-                # primitive, i.e. "primitive": "touch"
+                # Create the primitive for each sequence, i.e. "primitive": "touch"
                 primitive = seq.get('name')
-                primitive_params = {}
-                params = {
-                    "member_vnf_index": vnf_index,
-                    "primitive": primitive,
-                    "primitive_params": primitive_params,
-                }
-                nslcmop_primitive = self._create_nslcmop(nsr_id, operation, params)
-                # Get a copy of db_nslcmop 'admin' part
-                db_nslcmop_action = {"_admin": deepcopy(db_nslcmop["_admin"])}
-                # Update db_nslcmop with the primitive data
-                db_nslcmop_action.update(nslcmop_primitive)
-                # Create a new db entry for the created primitive, returns the new ID.
-                # (The ID is normally obtained from Kafka.)
-                nslcmop_terminate_action_id = self.db.create(
-                    "nslcmops", db_nslcmop_action)
-                # Execute the primitive
-                nslcmop_operation_state, nslcmop_operation_state_detail = await self.action(
-                    nsr_id, nslcmop_terminate_action_id)
+                mapped_primitive_params = self._get_terminate_primitive_params(seq, vnf_index)
+                # The following 3 parameters are currently set to None for 'terminate':
+                # vdu_id, vdu_count_index, vdu_name
+                vdu_id = db_nslcmop["operationParams"].get("vdu_id")
+                vdu_count_index = db_nslcmop["operationParams"].get("vdu_count_index")
+                vdu_name = db_nslcmop["operationParams"].get("vdu_name")
+                # Add suboperation
+                self._add_suboperation(db_nslcmop,
+                                       nslcmop_id,
+                                       vnf_index,
+                                       vdu_id,
+                                       vdu_count_index,
+                                       vdu_name,
+                                       primitive,
+                                       mapped_primitive_params)
+                # Suboperations: Call _ns_execute_primitive() instead of action()
+                db_nsr = self.db.get_one("nsrs", {"_id": nsr_id})
+                nsr_deployed = db_nsr["_admin"]["deployed"]
+                result, result_detail = await self._ns_execute_primitive(
+                    nsr_deployed, vnf_index, vdu_id, vdu_name, vdu_count_index, primitive,
+                    mapped_primitive_params)
+
+                # nslcmop_operation_state, nslcmop_operation_state_detail = await self.action(
+                #    nsr_id, nslcmop_terminate_action_id)
                 # Launch Exception if action() returns other than ['COMPLETED', 'PARTIALLY_COMPLETED']
-                nslcmop_operation_states_ok = ['COMPLETED', 'PARTIALLY_COMPLETED']
-                if nslcmop_operation_state not in nslcmop_operation_states_ok:
+                result_ok = ['COMPLETED', 'PARTIALLY_COMPLETED']
+                if result not in result_ok:
                     raise LcmException(
                         "terminate_primitive_action for vnf_member_index={}",
                         " primitive={} fails with error {}".format(
-                            vnf_index, seq.get("name"), nslcmop_operation_state_detail))
+                            vnf_index, seq.get("name"), result_detail))
 
     async def terminate(self, nsr_id, nslcmop_id):
+
+        # Try to lock HA task here
+        task_is_locked_by_me = self.lcm_tasks.lock_HA('ns', 'nslcmops', nslcmop_id)
+        if not task_is_locked_by_me:
+            return
+
         logging_text = "Task ns={} terminate={} ".format(nsr_id, nslcmop_id)
         self.logger.debug(logging_text + "Enter")
         db_nsr = None
@@ -1655,6 +1718,9 @@ class NsLcm(LcmBase):
         nslcmop_operation_state = None
         autoremove = False  # autoremove after terminated
         try:
+            # wait for any previous tasks in process
+            await self.lcm_tasks.waitfor_related_HA("ns", 'nslcmops', nslcmop_id)
+
             step = "Getting nslcmop={} from db".format(nslcmop_id)
             db_nslcmop = self.db.get_one("nslcmops", {"_id": nslcmop_id})
             step = "Getting nsr={} from db".format(nsr_id)
@@ -1926,7 +1992,7 @@ class NsLcm(LcmBase):
         return calculated_params
 
     async def _ns_execute_primitive(self, db_deployed, member_vnf_index, vdu_id, vdu_name, vdu_count_index,
-                                    primitive, primitive_params):
+                                    primitive, primitive_params, retries=0, retries_interval=30):
         start_primitive_time = time()
         try:
             for vca_deployed in db_deployed["VCA"]:
@@ -1956,34 +2022,47 @@ class NsLcm(LcmBase):
             await self.n2vc.login()
             if primitive == "config":
                 primitive_params = {"params": primitive_params}
-            primitive_id = await self.n2vc.ExecutePrimitive(
-                model_name,
-                application_name,
-                primitive,
-                callback,
-                *callback_args,
-                **primitive_params
-            )
-            while time() - start_primitive_time < self.timeout_primitive:
-                primitive_result_ = await self.n2vc.GetPrimitiveStatus(model_name, primitive_id)
-                if primitive_result_ in ("completed", "failed"):
-                    primitive_result = "COMPLETED" if primitive_result_ == "completed" else "FAILED"
-                    detailed_result = await self.n2vc.GetPrimitiveOutput(model_name, primitive_id)
-                    break
-                elif primitive_result_ is None and primitive == "config":
-                    primitive_result = "COMPLETED"
-                    detailed_result = None
+            while retries >= 0:
+                primitive_id = await self.n2vc.ExecutePrimitive(
+                    model_name,
+                    application_name,
+                    primitive,
+                    callback,
+                    *callback_args,
+                    **primitive_params
+                )
+                while time() - start_primitive_time < self.timeout_primitive:
+                    primitive_result_ = await self.n2vc.GetPrimitiveStatus(model_name, primitive_id)
+                    if primitive_result_ in ("completed", "failed"):
+                        primitive_result = "COMPLETED" if primitive_result_ == "completed" else "FAILED"
+                        detailed_result = await self.n2vc.GetPrimitiveOutput(model_name, primitive_id)
+                        break
+                    elif primitive_result_ is None and primitive == "config":
+                        primitive_result = "COMPLETED"
+                        detailed_result = None
+                        break
+                    else:  # ("running", "pending", None):
+                        pass
+                    await asyncio.sleep(5)
+                else:
+                    raise LcmException("timeout after {} seconds".format(self.timeout_primitive))
+                if primitive_result == "COMPLETED":
                     break
-                else:  # ("running", "pending", None):
-                    pass
-                await asyncio.sleep(5)
-            else:
-                raise LcmException("timeout after {} seconds".format(self.timeout_primitive))
+                retries -= 1
+                if retries >= 0:
+                    await asyncio.sleep(retries_interval)
+
             return primitive_result, detailed_result
         except (N2VCPrimitiveExecutionFailed, LcmException) as e:
             return "FAILED", str(e)
 
     async def action(self, nsr_id, nslcmop_id):
+
+        # Try to lock HA task here
+        task_is_locked_by_me = self.lcm_tasks.lock_HA('ns', 'nslcmops', nslcmop_id)
+        if not task_is_locked_by_me:
+            return
+
         logging_text = "Task ns={} action={} ".format(nsr_id, nslcmop_id)
         self.logger.debug(logging_text + "Enter")
         # get all needed from database
@@ -1995,6 +2074,9 @@ class NsLcm(LcmBase):
         nslcmop_operation_state_detail = None
         exc = None
         try:
+            # wait for any previous tasks in process
+            await self.lcm_tasks.waitfor_related_HA('ns', 'nslcmops', nslcmop_id)
+
             step = "Getting information from database"
             db_nslcmop = self.db.get_one("nslcmops", {"_id": nslcmop_id})
             db_nsr = self.db.get_one("nsrs", {"_id": nsr_id})
@@ -2017,17 +2099,6 @@ class NsLcm(LcmBase):
                     step = "Getting nsd from database"
                     db_nsd = self.db.get_one("nsds", {"_id": db_nsr["nsd-id"]})
 
-            # look if previous tasks in process
-            task_name, task_dependency = self.lcm_tasks.lookfor_related("ns", nsr_id, nslcmop_id)
-            if task_dependency:
-                step = db_nslcmop_update["detailed-status"] = \
-                    "Waiting for related tasks to be completed: {}".format(task_name)
-                self.logger.debug(logging_text + step)
-                self.update_db_2("nslcmops", nslcmop_id, db_nslcmop_update)
-                _, pending = await asyncio.wait(task_dependency, timeout=3600)
-                if pending:
-                    raise LcmException("Timeout waiting related tasks to be completed")
-
             # for backward compatibility
             if nsr_deployed and isinstance(nsr_deployed.get("VCA"), dict):
                 nsr_deployed["VCA"] = list(nsr_deployed["VCA"].values())
@@ -2115,6 +2186,12 @@ class NsLcm(LcmBase):
             return nslcmop_operation_state, nslcmop_operation_state_detail
 
     async def scale(self, nsr_id, nslcmop_id):
+
+        # Try to lock HA task here
+        task_is_locked_by_me = self.lcm_tasks.lock_HA('ns', 'nslcmops', nslcmop_id)
+        if not task_is_locked_by_me:
+            return
+
         logging_text = "Task ns={} scale={} ".format(nsr_id, nslcmop_id)
         self.logger.debug(logging_text + "Enter")
         # get all needed from database
@@ -2130,16 +2207,8 @@ class NsLcm(LcmBase):
         old_config_status = ""
         vnfr_scaled = False
         try:
-            # look if previous tasks in process
-            task_name, task_dependency = self.lcm_tasks.lookfor_related("ns", nsr_id, nslcmop_id)
-            if task_dependency:
-                step = db_nslcmop_update["detailed-status"] = \
-                    "Waiting for related tasks to be completed: {}".format(task_name)
-                self.logger.debug(logging_text + step)
-                self.update_db_2("nslcmops", nslcmop_id, db_nslcmop_update)
-                _, pending = await asyncio.wait(task_dependency, timeout=3600)
-                if pending:
-                    raise LcmException("Timeout waiting related tasks to be completed")
+            # wait for any previous tasks in process
+            await self.lcm_tasks.waitfor_related_HA('ns', 'nslcmops', nslcmop_id)
 
             step = "Getting nslcmop from database"
             self.logger.debug(step + " after having waited for previous tasks to be completed")
@@ -2211,7 +2280,7 @@ class NsLcm(LcmBase):
                         raise LcmException("reached the limit of {} (max-instance-count) "
                                            "scaling-out operations for the "
                                            "scaling-group-descriptor '{}'".format(nb_scale_op, scaling_group))
-                        
+
                 nb_scale_op += 1
                 vdu_scaling_info["scaling_direction"] = "OUT"
                 vdu_scaling_info["vdu-create"] = {}