From 1cf93af988c854bc7ac566f6961acbedbb849a96 Mon Sep 17 00:00:00 2001 From: Adam Israel Date: Wed, 4 Apr 2018 20:01:47 -0400 Subject: [PATCH] WIP - Fix N2VC integration issues 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 Change-Id: Idf630d7ace375bd508d45a7140efac2cda609ae0 --- lcm/Dockerfile | 2 - lcm/osm_lcm/lcm.py | 153 ++++++++++++++++++----------- lcm/osm_lcm/vca.py | 234 --------------------------------------------- 3 files changed, 98 insertions(+), 291 deletions(-) delete mode 100644 lcm/osm_lcm/vca.py diff --git a/lcm/Dockerfile b/lcm/Dockerfile index 68f62477..698ec9ca 100644 --- a/lcm/Dockerfile +++ b/lcm/Dockerfile @@ -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 \ diff --git a/lcm/osm_lcm/lcm.py b/lcm/osm_lcm/lcm.py index b39efe80..ceabda5b 100644 --- a/lcm/osm_lcm/lcm.py +++ b/lcm/osm_lcm/lcm.py @@ -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 index d5820969..00000000 --- a/lcm/osm_lcm/vca.py +++ /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'] == "": - 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() -- 2.17.1