X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;f=lcm%2Fosm_lcm%2Fvca.py;fp=lcm%2Fosm_lcm%2Fvca.py;h=b2b266337f1ecdddae58a22d0627924c4b53fbb3;hb=c887cc2fdd02c898ecd1f08d6927043027fd07eb;hp=0000000000000000000000000000000000000000;hpb=0cb7df3190343531e2ccd4b5d404780af678936e;p=osm%2FRO.git diff --git a/lcm/osm_lcm/vca.py b/lcm/osm_lcm/vca.py new file mode 100644 index 00000000..b2b26633 --- /dev/null +++ b/lcm/osm_lcm/vca.py @@ -0,0 +1,233 @@ +#!/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'] == "": + 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()