+ async def deploy_kdus(self, logging_text, nsr_id, nsd, db_nsr, db_nslcmop, db_vnfrs, db_vnfds_ref, db_k8scluster):
+ # Launch kdus if present in the descriptor
+ logging_text = "Deploy kdus: "
+ db_nsr_update = {}
+ db_nsr_update["_admin.deployed.K8s"] = []
+ try:
+ # Look for all vnfds
+ # db_nsr_update["_admin.deployed.K8s"] = []
+ vnf_update = []
+ task_list = []
+ for c_vnf in nsd.get("constituent-vnfd", ()):
+ vnfr = db_vnfrs[c_vnf["member-vnf-index"]]
+ member_vnf_index = c_vnf["member-vnf-index"]
+ vnfd = db_vnfds_ref[c_vnf['vnfd-id-ref']]
+ vnfd_ref = vnfd["id"]
+ desc_params = {}
+
+ step = "Checking kdu from vnf: {} - member-vnf-index: {}".format(vnfd_ref, member_vnf_index)
+ self.logger.debug(logging_text + step)
+ if vnfd.get("kdu"):
+ step = "vnf: {} has kdus".format(vnfd_ref)
+ self.logger.debug(logging_text + step)
+ for vnfr_name, vnfr_data in db_vnfrs.items():
+ if vnfr_data["vnfd-ref"] == vnfd["id"]:
+ if vnfr_data.get("additionalParamsForVnf"):
+ desc_params = self._format_additional_params(vnfr_data["additionalParamsForVnf"])
+ break
+ else:
+ raise LcmException("VNF descriptor not found with id: {}".format(vnfr_data["vnfd-ref"]))
+ self.logger.debug(logging_text + step)
+
+ for kdur in vnfr.get("kdur"):
+ index = 0
+ for k8scluster in db_k8scluster:
+ if kdur["k8s-cluster"]["id"] == k8scluster["_id"]:
+ cluster_uuid = k8scluster["cluster-uuid"]
+ break
+ else:
+ raise LcmException("K8scluster not found with id: {}".format(kdur["k8s-cluster"]["id"]))
+ self.logger.debug(logging_text + step)
+
+ step = "Instantiate KDU {} in k8s cluster {}".format(kdur["kdu-name"], cluster_uuid)
+ self.logger.debug(logging_text + step)
+ for kdu in vnfd.get("kdu"):
+ if kdu.get("name") == kdur["kdu-name"]:
+ break
+ else:
+ raise LcmException("KDU not found with name: {} in VNFD {}".format(kdur["kdu-name"],
+ vnfd["name"]))
+ self.logger.debug(logging_text + step)
+ kdumodel = None
+ k8sclustertype = None
+ if kdu.get("helm-chart"):
+ kdumodel = kdu["helm-chart"]
+ k8sclustertype = "chart"
+ elif kdu.get("juju-bundle"):
+ kdumodel = kdu["juju-bundle"]
+ k8sclustertype = "juju"
+ k8s_instace_info = {"kdu-instance": None, "k8scluster-uuid": cluster_uuid,
+ "vnfr-id": vnfr["id"], "k8scluster-type": k8sclustertype,
+ "kdu-name": kdur["kdu-name"], "kdu-model": kdumodel}
+ db_nsr_update["_admin.deployed.K8s"].append(k8s_instace_info)
+ db_dict = {"collection": "nsrs", "filter": {"_id": nsr_id}, "path": "_admin.deployed.K8s."
+ "{}".format(index)}
+ if k8sclustertype == "chart":
+ task = self.k8sclusterhelm.install(cluster_uuid=cluster_uuid, kdu_model=kdumodel,
+ atomic=True, params=desc_params,
+ db_dict=db_dict, timeout=300)
+ else:
+ # TODO I need the juju connector in place
+ pass
+ task_list.append(task)
+ index += 1
+ self.update_db_2("nsrs", nsr_id, db_nsr_update)
+ done = None
+ pending = None
+ if len(task_list) > 0:
+ self.logger.debug('Waiting for terminate pending tasks...')
+ done, pending = await asyncio.wait(task_list, timeout=3600)
+ if not pending:
+ for fut in done:
+ k8s_instance = fut.result()
+ k8s_instace_info = {"kdu-instance": k8s_instance, "k8scluster-uuid": cluster_uuid,
+ "vnfr-id": vnfr["id"], "k8scluster-type": k8sclustertype,
+ "kdu-name": kdur["kdu-name"], "kdu-model": kdumodel}
+ vnf_update.append(k8s_instace_info)
+ self.logger.debug('All tasks finished...')
+ else:
+ self.logger.info('There are pending tasks: {}'.format(pending))
+
+ db_nsr_update["_admin.deployed.K8s"] = vnf_update
+ except Exception as e:
+ self.logger.critical(logging_text + "Exit Exception {} while '{}': {}".format(type(e).__name__, step, e))
+ raise LcmException("{} Exit Exception {} while '{}': {}".format(logging_text, type(e).__name__, step, e))
+ finally:
+ # TODO Write in data base
+ if db_nsr_update:
+ self.update_db_2("nsrs", nsr_id, db_nsr_update)
+
+ def _deploy_n2vc(self, logging_text, db_nsr, db_vnfr, nslcmop_id, nsr_id, nsi_id, vnfd_id, vdu_id,
+ kdu_name, member_vnf_index, vdu_index, vdu_name, deploy_params, descriptor_config,
+ base_folder, task_instantiation_list):
+ # launch instantiate_N2VC in a asyncio task and register task object
+ # Look where information of this charm is at database <nsrs>._admin.deployed.VCA
+ # if not found, create one entry and update database
+
+ # fill db_nsr._admin.deployed.VCA.<index>
+ vca_index = -1
+ for vca_index, vca_deployed in enumerate(db_nsr["_admin"]["deployed"]["VCA"]):
+ if not vca_deployed:
+ continue
+ if vca_deployed.get("member-vnf-index") == member_vnf_index and \
+ vca_deployed.get("vdu_id") == vdu_id and \
+ vca_deployed.get("kdu_name") == kdu_name and \
+ vca_deployed.get("vdu_count_index", 0) == vdu_index:
+ break
+ else:
+ # not found, create one.
+ vca_deployed = {
+ "member-vnf-index": member_vnf_index,
+ "vdu_id": vdu_id,
+ "kdu_name": kdu_name,
+ "vdu_count_index": vdu_index,
+ "operational-status": "init", # TODO revise
+ "detailed-status": "", # TODO revise
+ "step": "initial-deploy", # TODO revise
+ "vnfd_id": vnfd_id,
+ "vdu_name": vdu_name,
+ }
+ vca_index += 1
+ self.update_db_2("nsrs", nsr_id, {"_admin.deployed.VCA.{}".format(vca_index): vca_deployed})
+ db_nsr["_admin"]["deployed"]["VCA"].append(vca_deployed)
+
+ # Launch task
+ task_n2vc = asyncio.ensure_future(
+ self.instantiate_N2VC(
+ logging_text=logging_text,
+ vca_index=vca_index,
+ nsi_id=nsi_id,
+ db_nsr=db_nsr,
+ db_vnfr=db_vnfr,
+ vdu_id=vdu_id,
+ kdu_name=kdu_name,
+ vdu_index=vdu_index,
+ deploy_params=deploy_params,
+ config_descriptor=descriptor_config,
+ base_folder=base_folder,
+ )
+ )
+ self.lcm_tasks.register("ns", nsr_id, nslcmop_id, "instantiate_N2VC-{}".format(vca_index), task_n2vc)
+ task_instantiation_list.append(task_n2vc)
+
+ # Check if this VNFD has a configured terminate action
+ def _has_terminate_config_primitive(self, vnfd):
+ vnf_config = vnfd.get("vnf-configuration")
+ if vnf_config and vnf_config.get("terminate-config-primitive"):
+ return True
+ else:
+ return False
+
+ @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")
+ # Get all 'seq' tags in seq_list, order sequences numerically, ascending.
+ seq_list_sorted = sorted(seq_list, key=lambda x: int(x['seq']))
+ return seq_list_sorted
+
+ @staticmethod
+ def _create_nslcmop(nsr_id, operation, params):
+ """
+ Creates a ns-lcm-opp content to be stored at database.
+ :param nsr_id: internal id of the instance
+ :param operation: instantiate, terminate, scale, action, ...
+ :param params: user parameters for the operation
+ :return: dictionary following SOL005 format
+ """
+ # Raise exception if invalid arguments
+ if not (nsr_id and operation and params):
+ raise LcmException(
+ "Parameters 'nsr_id', 'operation' and 'params' needed to create primitive not provided")
+ now = time()
+ _id = str(uuid4())
+ nslcmop = {
+ "id": _id,
+ "_id": _id,
+ # COMPLETED,PARTIALLY_COMPLETED,FAILED_TEMP,FAILED,ROLLING_BACK,ROLLED_BACK
+ "operationState": "PROCESSING",
+ "statusEnteredTime": now,
+ "nsInstanceId": nsr_id,
+ "lcmOperationType": operation,
+ "startTime": now,
+ "isAutomaticInvocation": False,
+ "operationParams": params,
+ "isCancelPending": False,
+ "links": {
+ "self": "/osm/nslcm/v1/ns_lcm_op_occs/" + _id,
+ "nsInstance": "/osm/nslcm/v1/ns_instances/" + nsr_id,
+ }
+ }
+ return nslcmop
+
+ def _format_additional_params(self, params):
+
+ for key, value in params.items():
+ if str(value).startswith("!!yaml "):
+ params[key] = yaml.safe_load(value[7:])
+
+ return params
+
+ 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)
+
+ # sub-operations
+
+ def _reintent_or_skip_suboperation(self, db_nslcmop, op_index):
+ op = db_nslcmop.get('_admin', {}).get('operations', [])[op_index]
+ if (op.get('operationState') == 'COMPLETED'):
+ # b. Skip sub-operation
+ # _ns_execute_primitive() or RO.create_action() will NOT be executed
+ return self.SUBOPERATION_STATUS_SKIP
+ else:
+ # c. Reintent executing sub-operation
+ # The sub-operation exists, and operationState != 'COMPLETED'
+ # Update operationState = 'PROCESSING' to indicate a reintent.
+ operationState = 'PROCESSING'
+ detailed_status = 'In progress'
+ self._update_suboperation_status(
+ db_nslcmop, op_index, operationState, detailed_status)
+ # Return the sub-operation index
+ # _ns_execute_primitive() or RO.create_action() will be called from scale()
+ # with arguments extracted from the sub-operation
+ return op_index
+
+ # Find a sub-operation where all keys in a matching dictionary must match
+ # Returns the index of the matching sub-operation, or SUBOPERATION_STATUS_NOT_FOUND if no match
+ def _find_suboperation(self, db_nslcmop, match):
+ if (db_nslcmop and match):
+ op_list = db_nslcmop.get('_admin', {}).get('operations', [])
+ for i, op in enumerate(op_list):
+ if all(op.get(k) == match[k] for k in match):
+ return i
+ return self.SUBOPERATION_STATUS_NOT_FOUND
+
+ # Update status for a sub-operation given its index
+ def _update_suboperation_status(self, db_nslcmop, op_index, operationState, detailed_status):
+ # Update DB for HA tasks
+ q_filter = {'_id': db_nslcmop['_id']}
+ update_dict = {'_admin.operations.{}.operationState'.format(op_index): operationState,
+ '_admin.operations.{}.detailed-status'.format(op_index): detailed_status}
+ self.db.set_one("nslcmops",
+ q_filter=q_filter,
+ update_dict=update_dict,
+ fail_on_empty=False)
+
+ # Add sub-operation, return the index of the added sub-operation
+ # Optionally, set operationState, detailed-status, and operationType
+ # Status and type are currently set for 'scale' sub-operations:
+ # 'operationState' : 'PROCESSING' | 'COMPLETED' | 'FAILED'
+ # 'detailed-status' : status message
+ # 'operationType': may be any type, in the case of scaling: 'PRE-SCALE' | 'POST-SCALE'
+ # Status and operation type are currently only used for 'scale', but NOT for 'terminate' sub-operations.
+ def _add_suboperation(self, db_nslcmop, vnf_index, vdu_id, vdu_count_index, vdu_name, primitive,
+ mapped_primitive_params, operationState=None, detailed_status=None, operationType=None,
+ RO_nsr_id=None, RO_scaling_info=None):
+ if not (db_nslcmop):
+ return self.SUBOPERATION_STATUS_NOT_FOUND
+ # Get the "_admin.operations" list, if it exists
+ db_nslcmop_admin = db_nslcmop.get('_admin', {})
+ op_list = db_nslcmop_admin.get('operations')
+ # Create or append to the "_admin.operations" list
+ 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 operationState:
+ new_op['operationState'] = operationState
+ if detailed_status:
+ new_op['detailed-status'] = detailed_status
+ if operationType:
+ new_op['lcmOperationType'] = operationType
+ if RO_nsr_id:
+ new_op['RO_nsr_id'] = RO_nsr_id
+ if RO_scaling_info:
+ new_op['RO_scaling_info'] = RO_scaling_info
+ if not op_list:
+ # No existing operations, create key 'operations' with current operation as first list element
+ db_nslcmop_admin.update({'operations': [new_op]})
+ op_list = db_nslcmop_admin.get('operations')
+ else:
+ # Existing operations, append operation to list
+ op_list.append(new_op)
+
+ db_nslcmop_update = {'_admin.operations': op_list}
+ self.update_db_2("nslcmops", db_nslcmop['_id'], db_nslcmop_update)
+ op_index = len(op_list) - 1
+ return op_index
+
+ # Helper methods for scale() sub-operations
+
+ # pre-scale/post-scale:
+ # Check for 3 different cases:
+ # a. New: First time execution, return SUBOPERATION_STATUS_NEW
+ # b. Skip: Existing sub-operation exists, operationState == 'COMPLETED', return SUBOPERATION_STATUS_SKIP
+ # c. Reintent: Existing sub-operation exists, operationState != 'COMPLETED', return op_index to re-execute
+ def _check_or_add_scale_suboperation(self, db_nslcmop, vnf_index, vnf_config_primitive, primitive_params,
+ operationType, RO_nsr_id=None, RO_scaling_info=None):
+ # Find this sub-operation
+ if (RO_nsr_id and RO_scaling_info):
+ operationType = 'SCALE-RO'
+ match = {
+ 'member_vnf_index': vnf_index,
+ 'RO_nsr_id': RO_nsr_id,
+ 'RO_scaling_info': RO_scaling_info,
+ }
+ else:
+ match = {
+ 'member_vnf_index': vnf_index,
+ 'primitive': vnf_config_primitive,
+ 'primitive_params': primitive_params,
+ 'lcmOperationType': operationType
+ }
+ op_index = self._find_suboperation(db_nslcmop, match)
+ if (op_index == self.SUBOPERATION_STATUS_NOT_FOUND):
+ # a. New sub-operation
+ # The sub-operation does not exist, add it.
+ # _ns_execute_primitive() will be called from scale() as usual, with non-modified arguments
+ # The following parameters are set to None for all kind of scaling:
+ vdu_id = None
+ vdu_count_index = None
+ vdu_name = None
+ if (RO_nsr_id and RO_scaling_info):
+ vnf_config_primitive = None
+ primitive_params = None
+ else:
+ RO_nsr_id = None
+ RO_scaling_info = None
+ # Initial status for sub-operation
+ operationState = 'PROCESSING'
+ detailed_status = 'In progress'
+ # Add sub-operation for pre/post-scaling (zero or more operations)
+ self._add_suboperation(db_nslcmop,
+ vnf_index,
+ vdu_id,
+ vdu_count_index,
+ vdu_name,
+ vnf_config_primitive,
+ primitive_params,
+ operationState,
+ detailed_status,
+ operationType,
+ RO_nsr_id,
+ RO_scaling_info)
+ return self.SUBOPERATION_STATUS_NEW
+ else:
+ # Return either SUBOPERATION_STATUS_SKIP (operationState == 'COMPLETED'),
+ # or op_index (operationState != 'COMPLETED')
+ return self._reintent_or_skip_suboperation(db_nslcmop, op_index)
+
+ # Helper methods for terminate()
+
+ 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_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"]
+ vnf_index = vnfr["member-vnf-index-ref"]
+ if vnfd_id not in db_vnfds:
+ step = "Getting vnfd={} id='{}' from db".format(vnfd_id, vnfd_id)
+ vnfd = self.db.get_one("vnfds", {"_id": vnfd_id})
+ db_vnfds[vnfd_id] = vnfd
+ vnfd = db_vnfds[vnfd_id]
+ if not self._has_terminate_config_primitive(vnfd):
+ continue
+ # 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, 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, i.e. "primitive": "touch"
+ primitive = seq.get('name')
+ 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 sub-operation
+ self._add_suboperation(db_nslcmop,
+ nslcmop_id,
+ vnf_index,
+ vdu_id,
+ vdu_count_index,
+ vdu_name,
+ primitive,
+ mapped_primitive_params)
+ # Sub-operations: Call _ns_execute_primitive() instead of action()
+ # db_nsr = self.db.get_one("nsrs", {"_id": nsr_id})
+ # nsr_deployed = db_nsr["_admin"]["deployed"]
+
+ # 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']
+ # 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"), result_detail))
+
+ # TODO: find ee_id
+ ee_id = None
+ try:
+ await self.n2vc.exec_primitive(
+ ee_id=ee_id,
+ primitive_name=primitive,
+ params_dict=mapped_primitive_params
+ )
+ except Exception as e:
+ self.logger.error('Error executing primitive {}: {}'.format(primitive, e))
+ raise LcmException(
+ "terminate_primitive_action for vnf_member_index={}, primitive={} fails with error {}"
+ .format(vnf_index, seq.get("name"), e),
+ )
+