| #!/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() |