Add n2vc relations after install configuration sw
[osm/LCM.git] / osm_lcm / ns.py
index 2e0ca39..c9b7767 100644 (file)
@@ -25,7 +25,7 @@ import json
 from jinja2 import Environment, Template, meta, TemplateError, TemplateNotFound, TemplateSyntaxError
 
 from osm_lcm import ROclient
-from osm_lcm.lcm_utils import LcmException, LcmExceptionNoMgmtIP, LcmBase, deep_get
+from osm_lcm.lcm_utils import LcmException, LcmExceptionNoMgmtIP, LcmBase, deep_get, get_iterable, populate_dict
 from n2vc.k8s_helm_conn import K8sHelmConnector
 from n2vc.k8s_juju_conn import K8sJujuConnector
 
@@ -43,37 +43,9 @@ from uuid import uuid4
 __author__ = "Alfonso Tierno"
 
 
-def get_iterable(in_dict, in_key):
-    """
-    Similar to <dict>.get(), but if value is None, False, ..., An empty tuple is returned instead
-    :param in_dict: a dictionary
-    :param in_key: the key to look for at in_dict
-    :return: in_dict[in_var] or () if it is None or not present
-    """
-    if not in_dict.get(in_key):
-        return ()
-    return in_dict[in_key]
-
-
-def populate_dict(target_dict, key_list, value):
-    """
-    Update target_dict creating nested dictionaries with the key_list. Last key_list item is asigned the value.
-    Example target_dict={K: J}; key_list=[a,b,c];  target_dict will be {K: J, a: {b: {c: value}}}
-    :param target_dict: dictionary to be changed
-    :param key_list: list of keys to insert at target_dict
-    :param value:
-    :return: None
-    """
-    for key in key_list[0:-1]:
-        if key not in target_dict:
-            target_dict[key] = {}
-        target_dict = target_dict[key]
-    target_dict[key_list[-1]] = value
-
-
 class NsLcm(LcmBase):
     timeout_vca_on_error = 5 * 60   # Time for charm from first time at blocked,error status to mark as failed
-    total_deploy_timeout = 2 * 3600   # global timeout for deployment
+    timeout_ns_deploy = 2 * 3600   # default global timeout for deployment a ns
     timeout_charm_delete = 10 * 60
     timeout_primitive = 10 * 60  # timeout for primitive execution
 
@@ -81,7 +53,7 @@ class NsLcm(LcmBase):
     SUBOPERATION_STATUS_NEW = -2
     SUBOPERATION_STATUS_SKIP = -3
 
-    def __init__(self, db, msg, fs, lcm_tasks, ro_config, vca_config, loop):
+    def __init__(self, db, msg, fs, lcm_tasks, config, loop):
         """
         Init, Connect to database, filesystem storage, and messaging
         :param config: two level dictionary with configuration. Top level should contain 'database', 'storage',
@@ -96,21 +68,9 @@ class NsLcm(LcmBase):
 
         self.loop = loop
         self.lcm_tasks = lcm_tasks
-        self.ro_config = ro_config
-        self.vca_config = vca_config
-        if 'pubkey' in self.vca_config:
-            self.vca_config['public_key'] = self.vca_config['pubkey']
-        if 'cacert' in self.vca_config:
-            self.vca_config['ca_cert'] = self.vca_config['cacert']
-        if 'apiproxy' in self.vca_config:
-            self.vca_config['api_proxy'] = self.vca_config['apiproxy']
-        if 'enableosupgrade' in self.vca_config:
-            if self.vca_config['enableosupgrade'].lower() == 'false':
-                self.vca_config['enable_os_upgrade'] = False
-            elif self.vca_config['enableosupgrade'].lower() == 'true':
-                self.vca_config['enable_os_upgrade'] = True
-        if 'aptmirror' in self.vca_config:
-            self.vca_config['apt_mirror'] = self.vca_config['aptmirror']
+        self.timeout = config["timeout"]
+        self.ro_config = config["ro_config"]
+        self.vca_config = config["VCA"].copy()
 
         # create N2VC connector
         self.n2vc = N2VCJujuConnector(
@@ -773,6 +733,10 @@ class NsLcm(LcmBase):
         start_deploy = time()
         vdu_flag = False  # If any of the VNFDs has VDUs
         ns_params = db_nslcmop.get("operationParams")
+        if ns_params and ns_params.get("timeout_ns_deploy"):
+            timeout_ns_deploy = ns_params["timeout_ns_deploy"]
+        else:
+            timeout_ns_deploy = self.timeout.get("ns_deploy", self.timeout_ns_deploy)
 
         # deploy RO
 
@@ -909,7 +873,7 @@ class NsLcm(LcmBase):
         self.logger.debug(logging_text + step)
 
         old_desc = None
-        while time() <= start_deploy + self.total_deploy_timeout:
+        while time() <= start_deploy + timeout_ns_deploy:
             desc = await self.RO.show("ns", RO_nsr_id)
 
             # deploymentStatus
@@ -938,7 +902,7 @@ class NsLcm(LcmBase):
                     detailed_status_old = db_nsr_update["_admin.deployed.RO.detailed-status"] = detailed_status
                     self.update_db_2("nsrs", nsr_id, db_nsr_update)
                 await asyncio.sleep(5, loop=self.loop)
-        else:  # total_deploy_timeout
+        else:  # timeout_ns_deploy
             raise ROclient.ROClientException("Timeout waiting ns to be ready")
 
         step = "Updating NSR"
@@ -1119,6 +1083,7 @@ class NsLcm(LcmBase):
                     element_under_configuration = "{}-{}".format(vdu_id, vdu_index or 0)
 
             # Get artifact path
+            self.fs.sync()  # Sync from FSMongo
             artifact_path = "{}/{}/charms/{}".format(
                 base_folder["folder"],
                 base_folder["pkg-dir"],
@@ -1137,7 +1102,7 @@ class NsLcm(LcmBase):
             # create or register execution environment in VCA
             if is_proxy_charm:
 
-                await self._write_configuration_status(
+                self._write_configuration_status(
                     nsr_id=nsr_id,
                     vca_index=vca_index,
                     status='CREATING',
@@ -1173,7 +1138,7 @@ class NsLcm(LcmBase):
                 credentials["username"] = username
                 # n2vc_redesign STEP 3.2
 
-                await self._write_configuration_status(
+                self._write_configuration_status(
                     nsr_id=nsr_id,
                     vca_index=vca_index,
                     status='REGISTERING',
@@ -1199,7 +1164,7 @@ class NsLcm(LcmBase):
 
             step = "Install configuration Software"
 
-            await self._write_configuration_status(
+            self._write_configuration_status(
                 nsr_id=nsr_id,
                 vca_index=vca_index,
                 status='INSTALLING SW',
@@ -1211,6 +1176,12 @@ class NsLcm(LcmBase):
             self.logger.debug(logging_text + step)
             await self.n2vc.install_configuration_sw(ee_id=ee_id, artifact_path=artifact_path, db_dict=db_dict)
 
+            # write in db flag of configuration_sw already installed
+            self.update_db_2("nsrs", nsr_id, {db_update_entry + "config_sw_installed": True})
+
+            # add relations for this VCA (wait for other peers related with this VCA)
+            await self._add_vca_relations(logging_text=logging_text, nsr_id=nsr_id, vca_index=vca_index)
+
             # if SSH access is required, then get execution environment SSH public
             if is_proxy_charm:  # if native charm we have waited already to VM be UP
                 pub_key = None
@@ -1244,10 +1215,13 @@ class NsLcm(LcmBase):
             initial_config_primitive_list = config_descriptor.get('initial-config-primitive')
 
             # sort initial config primitives by 'seq'
-            try:
-                initial_config_primitive_list.sort(key=lambda val: int(val['seq']))
-            except Exception as e:
-                self.logger.error(logging_text + step + ": " + str(e))
+            if initial_config_primitive_list:
+                try:
+                    initial_config_primitive_list.sort(key=lambda val: int(val['seq']))
+                except Exception as e:
+                    self.logger.error(logging_text + step + ": " + str(e))
+            else:
+                self.logger.debug(logging_text + step + ": No initial-config-primitive")
 
             # add config if not present for NS charm
             initial_config_primitive_list = self._get_initial_config_primitive_list(initial_config_primitive_list,
@@ -1269,7 +1243,7 @@ class NsLcm(LcmBase):
                 # NS
                 stage = 'Stage 5/5: running Day-1 primitives for NS'
 
-            await self._write_configuration_status(
+            self._write_configuration_status(
                 nsr_id=nsr_id,
                 vca_index=vca_index,
                 status='EXECUTING PRIMITIVE'
@@ -1301,7 +1275,7 @@ class NsLcm(LcmBase):
             step = "instantiated at VCA"
             self.logger.debug(logging_text + step)
 
-            await self._write_configuration_status(
+            self._write_configuration_status(
                 nsr_id=nsr_id,
                 vca_index=vca_index,
                 status='READY'
@@ -1309,7 +1283,7 @@ class NsLcm(LcmBase):
 
         except Exception as e:  # TODO not use Exception but N2VC exception
             # self.update_db_2("nsrs", nsr_id, {db_update_entry + "instantiation": "FAILED"})
-            await self._write_configuration_status(
+            self._write_configuration_status(
                 nsr_id=nsr_id,
                 vca_index=vca_index,
                 status='BROKEN'
@@ -1359,8 +1333,8 @@ class NsLcm(LcmBase):
         except Exception as e:
             self.logger.warn('Error writing all configuration status, ns={}: {}'.format(nsr_id, e))
 
-    async def _write_configuration_status(self, nsr_id: str, vca_index: int, status: str,
-                                          element_under_configuration: str = None, element_type: str = None):
+    def _write_configuration_status(self, nsr_id: str, vca_index: int, status: str = None,
+                                    element_under_configuration: str = None, element_type: str = None):
 
         # self.logger.debug('_write_configuration_status(): vca_index={}, status={}'
         #                   .format(vca_index, status))
@@ -1368,7 +1342,8 @@ class NsLcm(LcmBase):
         try:
             db_path = 'configurationStatus.{}.'.format(vca_index)
             db_dict = dict()
-            db_dict[db_path + 'status'] = status
+            if status:
+                db_dict[db_path + 'status'] = status
             if element_under_configuration:
                 db_dict[db_path + 'elementUnderConfiguration'] = element_under_configuration
             if element_type:
@@ -1436,6 +1411,11 @@ class NsLcm(LcmBase):
             # read from db: operation
             step = "Getting nslcmop={} from db".format(nslcmop_id)
             db_nslcmop = self.db.get_one("nslcmops", {"_id": nslcmop_id})
+            ns_params = db_nslcmop.get("operationParams")
+            if ns_params and ns_params.get("timeout_ns_deploy"):
+                timeout_ns_deploy = ns_params["timeout_ns_deploy"]
+            else:
+                timeout_ns_deploy = self.timeout.get("ns_deploy", self.timeout_ns_deploy)
 
             # read from db: ns
             step = "Getting nsr={} from db".format(nsr_id)
@@ -1467,7 +1447,7 @@ class NsLcm(LcmBase):
                 vnfd_ref = vnfr["vnfd-ref"]                     # vnfd name for this vnf
                 # if we haven't this vnfd, read it from db
                 if vnfd_id not in db_vnfds:
-                    # read from cb
+                    # read from db
                     step = "Getting vnfd={} id='{}' from db".format(vnfd_id, vnfd_ref)
                     self.logger.debug(logging_text + step)
                     vnfd = self.db.get_one("vnfds", {"_id": vnfd_id})
@@ -1549,7 +1529,7 @@ class NsLcm(LcmBase):
             task_instantiation_list.append(task_ro)
 
             # n2vc_redesign STEP 3 to 6 Deploy N2VC
-            step = "Looking for needed vnfd to configure with proxy charm"
+            step = "Deploying proxy and native charms"
             self.logger.debug(logging_text + step)
 
             nsi_id = None  # TODO put nsi_id when this nsr belongs to a NSI
@@ -1709,9 +1689,7 @@ class NsLcm(LcmBase):
 
             # Wait until all tasks of "task_instantiation_list" have been finished
 
-            # while time() <= start_deploy + self.total_deploy_timeout:
             error_text_list = []
-            timeout = 3600
 
             # let's begin with all OK
             instantiated_ok = True
@@ -1720,17 +1698,15 @@ class NsLcm(LcmBase):
             # let's begin with VCA 'configured' status (later we can change it)
             db_nsr_update["config-status"] = "configured"
 
+            step = "Waiting for tasks to be finished"
             if task_instantiation_list:
                 # wait for all tasks completion
-                done, pending = await asyncio.wait(task_instantiation_list, timeout=timeout)
+                done, pending = await asyncio.wait(task_instantiation_list, timeout=timeout_ns_deploy)
 
                 for task in pending:
                     instantiated_ok = False
-                    if task == task_ro:
-                        # RO task is pending
-                        db_nsr_update["operational-status"] = "failed"
-                    elif task == task_kdu:
-                        # KDU task is pending
+                    if task in (task_ro, task_kdu):
+                        # RO or KDU task is pending
                         db_nsr_update["operational-status"] = "failed"
                     else:
                         # A N2VC task is pending
@@ -1740,11 +1716,8 @@ class NsLcm(LcmBase):
                 for task in done:
                     if task.cancelled():
                         instantiated_ok = False
-                        if task == task_ro:
-                            # RO task was cancelled
-                            db_nsr_update["operational-status"] = "failed"
-                        elif task == task_kdu:
-                            # KDU task was cancelled
+                        if task in (task_ro, task_kdu):
+                            # RO or KDU task was cancelled
                             db_nsr_update["operational-status"] = "failed"
                         else:
                             # A N2VC was cancelled
@@ -1755,11 +1728,8 @@ class NsLcm(LcmBase):
                         exc = task.exception()
                         if exc:
                             instantiated_ok = False
-                            if task == task_ro:
-                                # RO task raised an exception
-                                db_nsr_update["operational-status"] = "failed"
-                            elif task == task_kdu:
-                                # KDU task raised an exception
+                            if task in (task_ro, task_kdu):
+                                # RO or KDU task raised an exception
                                 db_nsr_update["operational-status"] = "failed"
                             else:
                                 # A N2VC task raised an exception
@@ -1778,15 +1748,15 @@ class NsLcm(LcmBase):
             if error_text_list:
                 error_text = "\n".join(error_text_list)
                 db_nsr_update["detailed-status"] = error_text
-                db_nslcmop_update["operationState"] = nslcmop_operation_state = "FAILED_TEMP"
                 db_nslcmop_update["detailed-status"] = error_text
+                db_nslcmop_update["operationState"] = nslcmop_operation_state = "FAILED"
                 db_nslcmop_update["statusEnteredTime"] = time()
             else:
                 # all is done
+                db_nsr_update["detailed-status"] = "done"
+                db_nslcmop_update["detailed-status"] = "done"
                 db_nslcmop_update["operationState"] = nslcmop_operation_state = "COMPLETED"
                 db_nslcmop_update["statusEnteredTime"] = time()
-                db_nslcmop_update["detailed-status"] = "done"
-                db_nsr_update["detailed-status"] = "done"
 
         except (ROclient.ROClientException, DbException, LcmException) as e:
             self.logger.error(logging_text + "Exit Exception while '{}': {}".format(step, e))
@@ -1856,6 +1826,166 @@ class NsLcm(LcmBase):
             self.logger.debug(logging_text + "Exit")
             self.lcm_tasks.remove("ns", nsr_id, nslcmop_id, "ns_instantiate")
 
+    async def _add_vca_relations(self, logging_text, nsr_id, vca_index: int, timeout: int = 3600) -> bool:
+
+        # steps:
+        # 1. find all relations for this VCA
+        # 2. wait for other peers related
+        # 3. add relations
+
+        try:
+
+            # STEP 1: find all relations for this VCA
+
+            # read nsr record
+            db_nsr = self.db.get_one("nsrs", {"_id": nsr_id})
+
+            # this VCA data
+            my_vca = deep_get(db_nsr, ('_admin', 'deployed', 'VCA'))[vca_index]
+
+            # read all ns-configuration relations
+            ns_relations = list()
+            db_ns_relations = deep_get(db_nsr, ('nsd', 'ns-configuration', 'relation'))
+            if db_ns_relations:
+                for r in db_ns_relations:
+                    # check if this VCA is in the relation
+                    if my_vca.get('member-vnf-index') in\
+                            (r.get('entities')[0].get('id'), r.get('entities')[1].get('id')):
+                        ns_relations.append(r)
+
+            # read all vnf-configuration relations
+            vnf_relations = list()
+            db_vnfd_list = db_nsr.get('vnfd-id')
+            if db_vnfd_list:
+                for vnfd in db_vnfd_list:
+                    db_vnfd = self.db.get_one("vnfds", {"_id": vnfd})
+                    db_vnf_relations = deep_get(db_vnfd, ('vnf-configuration', 'relation'))
+                    if db_vnf_relations:
+                        for r in db_vnf_relations:
+                            # check if this VCA is in the relation
+                            if my_vca.get('vdu_id') in (r.get('entities')[0].get('id'), r.get('entities')[1].get('id')):
+                                vnf_relations.append(r)
+
+            # if no relations, terminate
+            if not ns_relations and not vnf_relations:
+                self.logger.debug(logging_text + ' No relations')
+                return True
+
+            self.logger.debug(logging_text + ' adding relations\n    {}\n    {}'.format(ns_relations, vnf_relations))
+
+            # add all relations
+            start = time()
+            while True:
+                # check timeout
+                now = time()
+                if now - start >= timeout:
+                    self.logger.error(logging_text + ' : timeout adding relations')
+                    return False
+
+                # reload nsr from database (we need to update record: _admin.deloyed.VCA)
+                db_nsr = self.db.get_one("nsrs", {"_id": nsr_id})
+
+                # for each defined NS relation, find the VCA's related
+                for r in ns_relations:
+                    from_vca_ee_id = None
+                    to_vca_ee_id = None
+                    from_vca_endpoint = None
+                    to_vca_endpoint = None
+                    vca_list = deep_get(db_nsr, ('_admin', 'deployed', 'VCA'))
+                    for vca in vca_list:
+                        if vca.get('member-vnf-index') == r.get('entities')[0].get('id') \
+                                and vca.get('config_sw_installed'):
+                            from_vca_ee_id = vca.get('ee_id')
+                            from_vca_endpoint = r.get('entities')[0].get('endpoint')
+                        if vca.get('member-vnf-index') == r.get('entities')[1].get('id') \
+                                and vca.get('config_sw_installed'):
+                            to_vca_ee_id = vca.get('ee_id')
+                            to_vca_endpoint = r.get('entities')[1].get('endpoint')
+                    if from_vca_ee_id and to_vca_ee_id:
+                        # add relation
+                        await self.n2vc.add_relation(
+                            ee_id_1=from_vca_ee_id,
+                            ee_id_2=to_vca_ee_id,
+                            endpoint_1=from_vca_endpoint,
+                            endpoint_2=to_vca_endpoint)
+                        # remove entry from relations list
+                        ns_relations.remove(r)
+                    else:
+                        # check failed peers
+                        try:
+                            vca_status_list = db_nsr.get('configurationStatus')
+                            if vca_status_list:
+                                for i in range(len(vca_list)):
+                                    vca = vca_list[i]
+                                    vca_status = vca_status_list[i]
+                                    if vca.get('member-vnf-index') == r.get('entities')[0].get('id'):
+                                        if vca_status.get('status') == 'BROKEN':
+                                            # peer broken: remove relation from list
+                                            ns_relations.remove(r)
+                                    if vca.get('member-vnf-index') == r.get('entities')[1].get('id'):
+                                        if vca_status.get('status') == 'BROKEN':
+                                            # peer broken: remove relation from list
+                                            ns_relations.remove(r)
+                        except Exception:
+                            # ignore
+                            pass
+
+                # for each defined VNF relation, find the VCA's related
+                for r in vnf_relations:
+                    from_vca_ee_id = None
+                    to_vca_ee_id = None
+                    from_vca_endpoint = None
+                    to_vca_endpoint = None
+                    vca_list = deep_get(db_nsr, ('_admin', 'deployed', 'VCA'))
+                    for vca in vca_list:
+                        if vca.get('vdu_id') == r.get('entities')[0].get('id') and vca.get('config_sw_installed'):
+                            from_vca_ee_id = vca.get('ee_id')
+                            from_vca_endpoint = r.get('entities')[0].get('endpoint')
+                        if vca.get('vdu_id') == r.get('entities')[1].get('id') and vca.get('config_sw_installed'):
+                            to_vca_ee_id = vca.get('ee_id')
+                            to_vca_endpoint = r.get('entities')[1].get('endpoint')
+                    if from_vca_ee_id and to_vca_ee_id:
+                        # add relation
+                        await self.n2vc.add_relation(
+                            ee_id_1=from_vca_ee_id,
+                            ee_id_2=to_vca_ee_id,
+                            endpoint_1=from_vca_endpoint,
+                            endpoint_2=to_vca_endpoint)
+                        # remove entry from relations list
+                        vnf_relations.remove(r)
+                    else:
+                        # check failed peers
+                        try:
+                            vca_status_list = db_nsr.get('configurationStatus')
+                            if vca_status_list:
+                                for i in range(len(vca_list)):
+                                    vca = vca_list[i]
+                                    vca_status = vca_status_list[i]
+                                    if vca.get('vdu_id') == r.get('entities')[0].get('id'):
+                                        if vca_status.get('status') == 'BROKEN':
+                                            # peer broken: remove relation from list
+                                            ns_relations.remove(r)
+                                    if vca.get('vdu_id') == r.get('entities')[1].get('id'):
+                                        if vca_status.get('status') == 'BROKEN':
+                                            # peer broken: remove relation from list
+                                            ns_relations.remove(r)
+                        except Exception:
+                            # ignore
+                            pass
+
+                # wait for next try
+                await asyncio.sleep(5.0)
+
+                if not ns_relations and not vnf_relations:
+                    self.logger.debug('Relations added')
+                    break
+
+            return True
+
+        except Exception as e:
+            self.logger.warn(logging_text + ' ERROR adding relations: {}'.format(e))
+            return False
+
     async def deploy_kdus(self, logging_text, nsr_id, db_nsr, db_vnfrs):
         # Launch kdus if present in the descriptor