WIP - Fix N2VC integration issues 37/5937/3
authorAdam Israel <adam.israel@canonical.com>
Thu, 5 Apr 2018 00:01:47 +0000 (20:01 -0400)
committerAdam Israel <adam.israel@canonical.com>
Fri, 6 Apr 2018 17:04:09 +0000 (13:04 -0400)
This fixes a couple issues:

- Bump up the version of N2VC used, which fixes the updating of deployment status
- Add a new 'params' parameter to DeployCharms to address rw_mgmt_ip.
- RemoveCharms now succeeds
- Explicitly generate and store application name, per conversation

I've verified this executes the initial-config-primitive, and now
correctly sets the rw_mgmt_ip.

Still TODO:
- There still seems to be an occasional block when deploying a network
service with multiple charms, where one charm hangs in DeployCharms.
Race condition TBD.

Signed-off-by: Adam Israel <adam.israel@canonical.com>
Change-Id: Idf630d7ace375bd508d45a7140efac2cda609ae0

lcm/Dockerfile
lcm/osm_lcm/lcm.py
lcm/osm_lcm/vca.py [deleted file]

index 68f6247..698ec9c 100644 (file)
@@ -15,8 +15,6 @@ RUN apt-get update && apt-get install -y git python3 \
 
 RUN git clone https://osm.etsi.org/gerrit/osm/N2VC.git \
     && cd N2VC \
-    && git fetch https://osm.etsi.org/gerrit/osm/N2VC refs/changes/70/5870/46 \
-    && git checkout FETCH_HEAD \
     && cd modules/libjuju && python3 setup.py develop && cd ../.. \
     && pip3 install -U -r requirements.txt \
     && python3 setup.py develop \
index b39efe8..ceabda5 100644 (file)
@@ -133,7 +133,7 @@ class Lcm:
         except DbException as e:
             self.logger.error("Updating nsr_id={}: {}".format(nsr_id, e))
 
-    def n2vc_callback(self, nsd, vnfd, vnf_member_index, workload_status, *args):
+    def n2vc_callback(self, model_name, application_name, workload_status, db_nsr, vnf_member_index, task=None):
         """Update the lcm database with the status of the charm.
 
         Updates the VNF's operational status with the state of the charm:
@@ -147,55 +147,59 @@ class Lcm:
         Updates the network service's config-status to reflect the state of all
         charms.
         """
-        if workload_status and len(args) == 3:
-            # self.logger.debug("[n2vc_callback] Workload status \"{}\"".format(workload_status))
-            try:
-                (db_nsr, vnf_index, task) = args
-
-                nsr_id = db_nsr["_id"]
-                nsr_lcm = db_nsr["_admin"]["deploy"]
-                nsr_lcm["VCA"][vnf_index]['operational-status'] = workload_status
-
-                if task:
-                    if task.cancelled():
-                        return
-
-                    if task.done():
-                        exc = task.exception()
-                        if exc:
-                            nsr_lcm = db_nsr["_admin"]["deploy"]
-                            nsr_lcm["VCA"][vnf_index]['operational-status'] = "failed"
-                            db_nsr["detailed-status"] = "fail configuring vnf_index={} {}".format(vnf_index, exc)
-                            db_nsr["config-status"] = "failed"
-                            self.update_nsr_db(nsr_id, db_nsr)
-                else:
-                    units = len(nsr_lcm["VCA"])
-                    active = 0
-                    statusmap = {}
-                    for vnf_index in nsr_lcm["VCA"]:
-                        if 'operational-status' in nsr_lcm["VCA"][vnf_index]:
-
-                            if nsr_lcm["VCA"][vnf_index]['operational-status'] not in statusmap:
-                                # Initialize it
-                                statusmap[nsr_lcm["VCA"][vnf_index]['operational-status']] = 0
-
-                            statusmap[nsr_lcm["VCA"][vnf_index]['operational-status']] += 1
-
-                            if nsr_lcm["VCA"][vnf_index]['operational-status'] == "active":
-                                active += 1
-                        else:
-                            self.logger.debug("No operational-status")
-
-                    cs = ""
-                    for status in statusmap:
-                        cs += "{} ({}) ".format(status, statusmap[status])
-                    db_nsr["config-status"] = cs
-                    self.update_nsr_db(nsr_id, db_nsr)
 
-            except Exception as e:
-                # self.logger.critical("Task create_ns={} n2vc_callback Exception {}".format(nsr_id, e), exc_info=True)
-                self.logger.critical("Task create_ns n2vc_callback Exception {}".format(e), exc_info=True)
-            pass
+        if not workload_status and not task:
+            self.logger.error("Task create_ns={} n2vc_callback Enter with bad parameters")
+            return
+
+        if not workload_status:
+            self.logger.error("Task create_ns={} n2vc_callback Enter with bad parameters, no workload_status")
+            return
+
+        try:
+            self.logger.debug("[n2vc_callback] Workload status \"{}\"".format(workload_status))
+            nsr_id = db_nsr["_id"]
+            nsr_lcm = db_nsr["_admin"]["deploy"]
+            nsr_lcm["VCA"][vnf_member_index]['operational-status'] = workload_status
+
+            if task:
+                if task.cancelled():
+                    return
+
+                if task.done():
+                    exc = task.exception()
+                    if exc:
+                        nsr_lcm = db_nsr["_admin"]["deploy"]
+                        nsr_lcm["VCA"][vnf_member_index]['operational-status'] = "failed"
+                        db_nsr["detailed-status"] = "fail configuring vnf_index={} {}".format(vnf_member_index, exc)
+                        db_nsr["config-status"] = "failed"
+                        self.update_nsr_db(nsr_id, db_nsr)
+            else:
+                vca_status = nsr_lcm["VCA"][vnf_member_index]['operational-status']
+
+                units = len(nsr_lcm["VCA"])
+                active = 0
+                statusmap = {}
+                for vnf_index in nsr_lcm["VCA"]:
+                    if vca_status not in statusmap:
+                        # Initialize it
+                        statusmap[vca_status] = 0
+
+                    statusmap[vca_status] += 1
+
+                    if vca_status == "active":
+                        active += 1
+
+                cs = ""
+                for status in statusmap:
+                    cs += "{} ({}) ".format(status, statusmap[status])
+                db_nsr["config-status"] = cs
+                self.update_nsr_db(nsr_id, db_nsr)
+
+        except Exception as e:
+            # self.logger.critical("Task create_ns={} n2vc_callback Exception {}".format(nsr_id, e), exc_info=True)
+            self.logger.critical("Task create_ns n2vc_callback Exception {}".format(e), exc_info=True)
+        pass
 
     def vca_deploy_callback(self, db_nsr, vnf_index, status, task):
         # TODO study using this callback when VCA.DeployApplication success from VCAMonitor
@@ -362,7 +366,6 @@ class Lcm:
                 vnf_index = str(c_vnf["member-vnf-index"])
                 vnfd = needed_vnfd[vnfd_id]
                 if vnfd.get("vnf-configuration") and vnfd["vnf-configuration"].get("juju"):
-                    nsr_lcm["VCA"][vnf_index] = {}
                     vnfd_to_config += 1
                     proxy_charm = vnfd["vnf-configuration"]["juju"]["charm"]
 
@@ -376,13 +379,44 @@ class Lcm:
                         proxy_charm
                     )
 
+                    # Setup the runtime parameters for this VNF
+                    params = {
+                        'rw_mgmt_ip': nsr_lcm['nsr_ip'][vnf_index],
+                    }
+
+                    # ns_name will be ignored in the current version of N2VC
+                    # but will be implemented for the next point release.
+                    ns_name = 'default'
+                    application_name = self.n2vc.FormatApplicationName(
+                        'default',
+                        vnfd['name'],
+                        vnf_index,
+                    )
+
+                    nsr_lcm["VCA"][vnf_index] = {
+                        "model": ns_name,
+                        "application": application_name,
+                        "operational-status": "init",
+                        "vnfd_id": vnfd_id,
+                    }
+
                     self.logger.debug("Passing artifacts path '{}' for {}".format(charm_path, proxy_charm))
                     task = asyncio.ensure_future(
-                        self.n2vc.DeployCharms(nsd, vnfd, vnf_index, charm_path, self.n2vc_callback, db_nsr, vnf_index, None)
+                        self.n2vc.DeployCharms(
+                            ns_name,             # The network service name
+                            application_name,    # The application name
+                            vnfd,                # The vnf descriptor
+                            charm_path,          # Path to charm
+                            params,              # Runtime params, like mgmt ip
+                            {},                  # for native charms only
+                            self.n2vc_callback,  # Callback for status changes
+                            db_nsr,              # Callback parameter
+                            vnf_index,           # Callback parameter
+                            None,                # Callback parameter (task)
+                        )
                     )
-                    task.add_done_callback(functools.partial(self.n2vc_callback, None, None, None, None, None, db_nsr, vnf_index))
+                    task.add_done_callback(functools.partial(self.n2vc_callback, None, None, None, None, db_nsr))
 
-                    # task.add_done_callback(functools.partial(self.vca_deploy_callback, db_nsr, vnf_index, None))
                     self.lcm_tasks[nsr_id][order_id]["create_charm:" + vnf_index] = task
             db_nsr["config-status"] = "configuring" if vnfd_to_config else "configured"
             db_nsr["detailed-status"] = "Configuring 1/{}".format(vnfd_to_config) if vnfd_to_config else "done"
@@ -424,11 +458,20 @@ class Lcm:
                 step = db_nsr["detailed-status"] = "Deleting charms"
                 self.logger.debug(logging_text + step)
                 for vnf_index, deploy_info in nsr_lcm["VCA"].items():
-                    if deploy_info and deploy_info.get("appliation"):
+                    if deploy_info and deploy_info.get("application"):
+                        # n2vc_callback(self, model_name, application_name, workload_status, db_nsr, vnf_member_index, task=None):
 
+                        # self.n2vc.RemoveCharms(model_name, application_name, self.n2vc_callback, model_name, application_name)
                         task = asyncio.ensure_future(
-                            self.n2vc.RemoveCharms(nsd, vnfd, vnf_index, self.n2vc_callback, db_nsr, vnf_index, None)
+                            self.n2vc.RemoveCharms(
+                                deploy_info['model'],
+                                deploy_info['application'],
+                                self.n2vc_callback,
+                                db_nsr,
+                                vnf_index,
+                            )
                         )
+
                         self.lcm_tasks[nsr_id][order_id]["delete_charm:" + vnf_index] = task
             except Exception as e:
                 self.logger.debug(logging_text + "Failed while deleting charms: {}".format(e))
diff --git a/lcm/osm_lcm/vca.py b/lcm/osm_lcm/vca.py
deleted file mode 100644 (file)
index d582096..0000000
+++ /dev/null
@@ -1,234 +0,0 @@
-#!/usr/bin/python3
-# -*- coding: utf-8 -*-
-
-from juju_api import JujuApi
-from juju.model import ModelObserver
-import logging
-import os
-import os.path
-import re
-
-
-class VCAMonitor(ModelObserver):
-    """Monitor state changes within the Juju Model."""
-    context = None
-
-    async def on_change(self, delta, old, new, model):
-        """React to changes in the Juju model."""
-        status = None
-        db_nsr = self.context['db_nsr']
-        vnf_index = self.context['vnf_index']
-
-        nsr_lcm = db_nsr["_admin"]["deploy"]
-        nsr_id = nsr_lcm["id"]
-        application = self.context['application']
-
-        if delta.entity == "unit":
-            # We only care about changes to a unit
-            if delta.type == "add" and old is None:
-                if new and new.application == application:
-                    status = "BUILD"
-            elif delta.type == "change":
-                if new and new.application == application:
-                    if new.agent_status == "idle":
-                        if new.workload_status in ("active", "blocked"):
-                            status = "ACTIVE"
-
-            elif delta.type == "remove" and new is None:
-                if new and new.application == application:
-                    status = "DELETING"
-
-        if status:
-            nsr_lcm["VCA"][vnf_index]['operational-status'] = status
-
-            # TODO: Clean this up, and make it work with deletes (if we need
-            # TODO: to update the database post-delete)
-            # Figure out if we're finished configuring
-            count = len(nsr_lcm["VCA"])
-            active = 0
-            for vnf_index in nsr_lcm["VCA"]:
-                if nsr_lcm["VCA"][vnf_index]['operational-status'] == "ACTIVE":
-                    active += 1
-            if active == count:
-                db_nsr["config-status"] = "done"
-            else:
-                db_nsr["config-status"] = "configuring {}/{}".format(active, count)
-
-            try:
-                self.context['db'].replace(
-                    "nsrs",
-                    nsr_id,
-                    db_nsr
-                )
-
-                # self.context['db'].replace(
-                #     "nsr_lcm",
-                #     {"id": self.context['nsr_lcm']['id']},
-                #     self.context['nsr_lcm']
-                # )
-            except Exception as e:
-                # I've seen this happen when we handle a delete, because the
-                # db record is gone by the time we've finished deleting
-                # the charms.
-                print("Error updating database: ", e)
-
-    pass
-
-
-def GetJujuApi(config):
-    # Quiet logging from the websocket library. If you want to see
-    # everything sent over the wire, set this to DEBUG.
-
-    ws_logger = logging.getLogger('websockets.protocol')
-    ws_logger.setLevel(logging.INFO)
-
-    api = JujuApi(server=config['host'],
-                  port=config['port'],
-                  user=config['user'],
-                  secret=config['secret'],
-                  log=ws_logger,
-                  model_name='default'
-                  )
-    return api
-
-
-def get_vnf_unique_name(nsr_name, vnfr_name, member_vnf_index):
-    """Get the unique VNF name.
-    Charm names accepts only a to z and non-consecutive - characters."""
-    name = "{}-{}-{}".format(nsr_name, vnfr_name, member_vnf_index)
-    new_name = ''
-    for c in name:
-        if c.isdigit():
-            c = chr(97 + int(c))
-        elif not c.isalpha():
-            c = "-"
-        new_name += c
-    return re.sub('\-+', '-', new_name.lower())
-
-
-def get_initial_config(initial_config_primitive, mgmt_ip):
-    config = {}
-    for primitive in initial_config_primitive:
-        if primitive['name'] == 'config':
-            for parameter in primitive['parameter']:
-                param = parameter['name']
-                if parameter['value'] == "<rw_mgmt_ip>":
-                    config[param] = mgmt_ip
-                else:
-                    config[param] = parameter['value']
-    return config
-
-
-async def DeployApplication(vcaconfig, db, db_nsr, vnfd,
-                            vnf_index, charm_path):
-    """
-    Deploy a charm.
-
-    Deploy a VNF configuration charm from a local directory.
-    :param dict vcaconfig: The VCA portion of the LCM Configuration
-    :param object vnfd: The VNF descriptor
-    ...
-    :param int vnfd_index: The index of the vnf.
-
-    :Example:
-
-    DeployApplication(...)
-    """
-    nsr_lcm = db_nsr["_admin"]["deploy"]
-    nsr_id = db_nsr["_id"]
-    vnf_id = vnfd['id']
-
-    if "proxy" in vnfd["vnf-configuration"]["juju"]:
-        use_proxy = vnfd["vnf-configuration"]["juju"]["proxy"]
-    else:
-        # TBD: We need this to handle a full charm
-        use_proxy = True
-
-    application = get_vnf_unique_name(
-        db_nsr["name"].lower().strip(),
-        vnf_id,
-        vnf_index,
-    )
-
-    api = GetJujuApi(vcaconfig)
-
-    await api.login()
-    if api.authenticated:
-        charm = os.path.basename(charm_path)
-
-        # Set the INIT state; further operational status updates
-        # will be made by the VCAMonitor
-        nsr_lcm["VCA"][vnf_index] = {}
-        nsr_lcm["VCA"][vnf_index]['operational-status'] = 'INIT'
-        nsr_lcm["VCA"][vnf_index]['application'] = application
-
-        db.replace("nsrs", nsr_id, db_nsr)
-
-        model = await api.get_model()
-        context = {
-            'application': application,
-            'vnf_index': vnf_index,
-            'db_nsr': db_nsr,
-            'db': db,
-        }
-        mon = VCAMonitor()
-        mon.context = context
-        model.add_observer(mon)
-
-        await api.deploy_application(charm,
-                                     name=application,
-                                     path=charm_path,
-                                     )
-
-        # Get and apply the initial config primitive
-        cfg = get_initial_config(
-            vnfd["vnf-configuration"].get(
-                "initial-config-primitive"
-            ),
-            nsr_lcm['nsr_ip'][vnf_index]
-        )
-
-        await api.apply_config(cfg, application)
-
-    await api.logout()
-
-
-async def RemoveApplication(vcaconfig, db, db_nsr, vnf_index):
-    """
-    Remove an application from the Juju Controller
-
-    Removed the named application and it's charm from the Juju controller.
-
-    :param object loop: The event loop.
-    :param str application_name: The unique name of the application.
-
-    :Example:
-
-    RemoveApplication(loop, "ping_vnf")
-    RemoveApplication(loop, "pong_vnf")
-    """
-    nsr_id = db_nsr["_id"]
-    nsr_lcm = db_nsr["_admin"]["deploy"]
-    application = nsr_lcm["VCA"][vnf_index]['application']
-
-    api = GetJujuApi(vcaconfig)
-
-    await api.login()
-    if api.authenticated:
-        model = await api.get_model()
-        context = {
-            'application': application,
-            'vnf_index': vnf_index,
-            'db_nsr': db_nsr,
-            'db': db,
-        }
-
-        mon = VCAMonitor()
-        mon.context = context
-        model.add_observer(mon)
-
-        print("VCA: Removing application {}".format(application))
-        await api.remove_application(application)
-        nsr_lcm["VCA"][vnf_index]['application'] = None
-        db.replace("nsrs", nsr_id, db_nsr)
-    await api.logout()