+#!/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_id = self.context['vnf_id']
+
+ 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_id]['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_id in nsr_lcm["VCA"]:
+ if nsr_lcm["VCA"][vnf_id]['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.
+ logging.basicConfig(level=logging.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,
+ vnfd_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 = nsr_lcm["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(),
+ vnfd['id'],
+ vnfd_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_id] = {}
+ nsr_lcm["VCA"][vnf_id]['operational-status'] = 'INIT'
+ nsr_lcm["VCA"][vnf_id]['application'] = application
+
+ db.replace("nsrs", nsr_id, db_nsr)
+
+ model = await api.get_model()
+ context = {
+ 'application': application,
+ 'vnf_id': vnf_id,
+ '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'][vnfd_index]
+ )
+
+ await api.apply_config(cfg, application)
+
+ await api.logout()
+
+
+async def RemoveApplication(vcaconfig, db, db_nsr, vnfd, vnfd_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_lcm = db_nsr["_admin"]["deploy"]
+ vnf_id = vnfd['id']
+ application = nsr_lcm["VCA"][vnf_id]['application']
+
+ api = GetJujuApi(vcaconfig)
+
+ await api.login()
+ if api.authenticated:
+ model = await api.get_model()
+ context = {
+ 'application': application,
+ 'vnf_id': vnf_id,
+ '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)
+ await api.logout()