X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FN2VC.git;a=blobdiff_plain;f=n2vc%2Fvnf.py;h=d6c87dc34fcfa89356fbffc7d9717c0b9567308e;hp=7244b2245153cd55b0fa849f390e8cec785482ac;hb=refs%2Fchanges%2F35%2F6335%2F1;hpb=88a49630895a6045586e6f547c0b6ad118110f25 diff --git a/n2vc/vnf.py b/n2vc/vnf.py index 7244b22..d6c87dc 100644 --- a/n2vc/vnf.py +++ b/n2vc/vnf.py @@ -36,6 +36,7 @@ class JujuCharmNotFound(Exception): class JujuApplicationExists(Exception): """The Application already exists.""" + class N2VCPrimitiveExecutionFailed(Exception): """Something failed while attempting to execute a primitive.""" @@ -46,42 +47,69 @@ logging.getLogger('juju.client.connection').setLevel(logging.WARN) logging.getLogger('juju.model').setLevel(logging.WARN) logging.getLogger('juju.machine').setLevel(logging.WARN) + class VCAMonitor(ModelObserver): """Monitor state changes within the Juju Model.""" - callback = None - callback_args = None log = None ns_name = None - application_name = None + applications = {} - def __init__(self, ns_name, application_name, callback, *args): + def __init__(self, ns_name): self.log = logging.getLogger(__name__) self.ns_name = ns_name - self.application_name = application_name - self.callback = callback - self.callback_args = args + + def AddApplication(self, application_name, callback, *callback_args): + if application_name not in self.applications: + self.applications[application_name] = { + 'callback': callback, + 'callback_args': callback_args + } + + def RemoveApplication(self, application_name): + if application_name in self.applications: + del self.applications[application_name] async def on_change(self, delta, old, new, model): """React to changes in the Juju model.""" if delta.entity == "unit": + # Ignore change events from other applications + if delta.data['application'] not in self.applications.keys(): + return + try: + + application_name = delta.data['application'] + + callback = self.applications[application_name]['callback'] + callback_args = self.applications[application_name]['callback_args'] + if old and new: old_status = old.workload_status new_status = new.workload_status if old_status == new_status: - """The workload status may fluctuate around certain events, so wait until the status has stabilized before triggering the callback.""" - if self.callback: - self.callback( + if callback: + callback( self.ns_name, - self.application_name, + delta.data['application'], new_status, - *self.callback_args) + new.workload_status_message, + *callback_args) + + if old and not new: + # This is a charm being removed + if callback: + callback( + self.ns_name, + delta.data['application'], + "removed", + "", + *callback_args) except Exception as e: self.log.debug("[1] notify_callback exception {}".format(e)) elif delta.entity == "action": @@ -108,6 +136,7 @@ class VCAMonitor(ModelObserver): # Create unique models per network service # Document all public functions + class N2VC: # Juju API @@ -185,10 +214,10 @@ class N2VC: """Close any open connections.""" yield self.logout() - def notify_callback(self, model_name, application_name, status, callback=None, *callback_args): + def notify_callback(self, model_name, application_name, status, message, callback=None, *callback_args): try: if callback: - callback(model_name, application_name, status, *callback_args) + callback(model_name, application_name, status, message, *callback_args) except Exception as e: self.log.error("[0] notify_callback exception {}".format(e)) raise e @@ -219,10 +248,7 @@ class N2VC: Deploy the charm(s) referenced in a VNF Descriptor. - You can pass either the nsd record or the id of the network - service, but this method will fail without one of them. - - :param str ns_name: The name of the network service + :param str model_name: The name of the network service. :param str application_name: The name of the application :param dict vnfd: The name of the application :param str charm_path: The path to the Juju charm @@ -271,15 +297,17 @@ class N2VC: ######################################## app = await self.get_application(model, application_name) if app: - raise JujuApplicationExists("Can't deploy application \"{}\" to model \"{}\" because it already exists.".format(application_name, model)) + raise JujuApplicationExists("Can't deploy application \"{}\" to model \"{}\" because it already exists.".format(application_name, model_name)) - ############################################################ - # Create a monitor to watch for application status changes # - ############################################################ + ################################################################ + # Register this application with the model-level event monitor # + ################################################################ if callback: - self.log.debug("Setting monitor<->callback") - self.monitors[application_name] = VCAMonitor(model_name, application_name, callback, *callback_args) - model.add_observer(self.monitors[application_name]) + self.monitors[model_name].AddApplication( + application_name, + callback, + *callback_args + ) ######################################################## # Check for specific machine placement (native charms) # @@ -345,14 +373,16 @@ class N2VC: # This is applied when the Application is deployed pass else: - # TODO: We need to sort by seq, and queue the actions in order. - seq = primitive['seq'] + params = {} + if 'parameter' in primitive: + params = primitive['parameter'] + primitives[seq] = { 'name': primitive['name'], 'parameters': self._map_primitive_parameters( - primitive['parameter'], + params, {'': rw_mgmt_ip} ), } @@ -373,6 +403,24 @@ class N2VC: raise async def ExecutePrimitive(self, model_name, application_name, primitive, callback, *callback_args, **params): + """Execute a primitive of a charm for Day 1 or Day 2 configuration. + + Execute a primitive defined in the VNF descriptor. + + :param str model_name: The name of the network service. + :param str application_name: The name of the application + :param str primitive: The name of the primitive to execute. + :param obj callback: A callback function to receive status changes. + :param tuple callback_args: A list of arguments to be passed to the callback function. + :param dict params: A dictionary of key=value pairs representing the primitive's parameters + Examples:: + { + 'rw_mgmt_ip': '1.2.3.4', + # Pass the initial-config-primitives section of the vnf or vdu + 'initial-config-primitives': {...} + } + """ + uuid = None try: if not self.authenticated: await self.login() @@ -380,11 +428,14 @@ class N2VC: # FIXME: This is hard-coded until model-per-ns is added model_name = 'default' + model = await self.controller.get_model(model_name) + if primitive == 'config': # config is special, and expecting params to be a dictionary - await self.set_config(application_name, params['params']) + self.log.debug("Setting charm configuration for {}".format(application_name)) + self.log.debug(params['params']) + await self.set_config(model, application_name, params['params']) else: - model = await self.controller.get_model(model_name) app = await self.get_application(model, application_name) if app: # Run against the first (and probably only) unit in the app @@ -392,13 +443,23 @@ class N2VC: if unit: self.log.debug("Executing primitive {}".format(primitive)) action = await unit.run_action(primitive, **params) - # action = await action.wait() + uuid = action.id await model.disconnect() except Exception as e: self.log.debug("Caught exception while executing primitive: {}".format(e)) raise e + return uuid async def RemoveCharms(self, model_name, application_name, callback=None, *callback_args): + """Remove a charm from the VCA. + + Remove a charm referenced in a VNF Descriptor. + + :param str model_name: The name of the network service. + :param str application_name: The name of the application + :param obj callback: A callback function to receive status changes. + :param tuple callback_args: A list of arguments to be passed to the callback function. + """ try: if not self.authenticated: await self.login() @@ -406,9 +467,16 @@ class N2VC: model = await self.get_model(model_name) app = await self.get_application(model, application_name) if app: - self.notify_callback(model_name, application_name, "removing", callback, *callback_args) + # Remove this application from event monitoring + self.monitors[model_name].RemoveApplication(application_name) + + # self.notify_callback(model_name, application_name, "removing", callback, *callback_args) + self.log.debug("Removing the application {}".format(application_name)) await app.remove() + + # Notify the callback that this charm has been removed. self.notify_callback(model_name, application_name, "removed", callback, *callback_args) + except Exception as e: print("Caught exception: {}".format(e)) self.log.debug(e) @@ -417,9 +485,19 @@ class N2VC: async def DestroyNetworkService(self, nsd): raise NotImplementedError() - async def GetMetrics(self, nsd, vnfd): - """Get the metrics collected by the VCA.""" - raise NotImplementedError() + async def GetMetrics(self, model_name, application_name): + """Get the metrics collected by the VCA. + + :param model_name The name of the model + :param application_name The name of the application + """ + metrics = {} + model = await self.get_model(model_name) + app = await self.get_application(model, application_name) + if app: + metrics = await app.get_metrics() + + return metrics # Non-public methods async def add_relation(self, a, b, via=None): @@ -440,12 +518,12 @@ class N2VC: finally: await m.disconnect() - async def apply_config(self, config, application): - """Apply a configuration to the application.""" - print("JujuApi: Applying configuration to {}.".format( - application - )) - return await self.set_config(application=application, config=config) + # async def apply_config(self, config, application): + # """Apply a configuration to the application.""" + # print("JujuApi: Applying configuration to {}.".format( + # application + # )) + # return await self.set_config(application=application, config=config) def _get_config_from_dict(self, config_primitive, values): """Transform the yang config primitive to dict. @@ -569,6 +647,10 @@ class N2VC: print("connecting to model {}".format(model_name)) self.models[model_name] = await self.controller.get_model(model_name) + # Create an observer for this model + self.monitors[model_name] = VCAMonitor(model_name) + self.models[model_name].add_observer(self.monitors[model_name]) + return self.models[model_name] async def login(self): @@ -709,12 +791,12 @@ class N2VC: return result - async def set_config(self, application, config): + async def set_config(self, model_name, application, config): """Apply a configuration to the application.""" if not self.authenticated: await self.login() - app = await self.get_application(self.default_model, application) + app = await self.get_application(model_name, application) if app: self.log.debug("JujuApi: Setting config for Application {}".format( application, @@ -727,20 +809,20 @@ class N2VC: if config[key] != newconf[key]['value']: self.log.debug("JujuApi: Config not set! Key {} Value {} doesn't match {}".format(key, config[key], newconf[key])) - async def set_parameter(self, parameter, value, application=None): - """Set a config parameter for a service.""" - if not self.authenticated: - await self.login() - - self.log.debug("JujuApi: Setting {}={} for Application {}".format( - parameter, - value, - application, - )) - return await self.apply_config( - {parameter: value}, - application=application, - ) + # async def set_parameter(self, parameter, value, application=None): + # """Set a config parameter for a service.""" + # if not self.authenticated: + # await self.login() + # + # self.log.debug("JujuApi: Setting {}={} for Application {}".format( + # parameter, + # value, + # application, + # )) + # return await self.apply_config( + # {parameter: value}, + # application=application, + # ) async def wait_for_application(self, name, timeout=300): """Wait for an application to become active."""