X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FN2VC.git;a=blobdiff_plain;f=n2vc%2Fvnf.py;h=6e4aaf37e8bd013663eb4d03c6dbe766408cdb3f;hp=1c1208f430a3fd8ca6d4a6c93b32d5017dda3219;hb=6d84dbd8746388114361e09300697da471de20ca;hpb=85a4b210d2353ae209e3269498220febf0172270 diff --git a/n2vc/vnf.py b/n2vc/vnf.py index 1c1208f..6e4aaf3 100644 --- a/n2vc/vnf.py +++ b/n2vc/vnf.py @@ -19,7 +19,7 @@ if path not in sys.path: from juju.controller import Controller from juju.model import ModelObserver -from juju.errors import JujuAPIError +from juju.errors import JujuAPIError, JujuError # We might need this to connect to the websocket securely, but test and verify. try: @@ -43,6 +43,10 @@ class N2VCPrimitiveExecutionFailed(Exception): """Something failed while attempting to execute a primitive.""" +class NetworkServiceDoesNotExist(Exception): + """The Network Service being acted against does not exist.""" + + # Quiet the debug logging logging.getLogger('websockets.protocol').setLevel(logging.INFO) logging.getLogger('juju.client.connection').setLevel(logging.WARN) @@ -345,6 +349,8 @@ class N2VC: 'rw_mgmt_ip': '1.2.3.4', # Pass the initial-config-primitives section of the vnf or vdu 'initial-config-primitives': {...} + 'user_values': dictionary with the day-1 parameters provided at instantiation time. It will replace values + inside < >. rw_mgmt_ip will be included here also } :param dict machine_spec: A dictionary describing the machine to install to @@ -647,15 +653,20 @@ class N2VC: else: seq = primitive['seq'] - params = {} + params_ = {} if 'parameter' in primitive: - params = primitive['parameter'] + params_ = primitive['parameter'] + + user_values = params.get("user_values", {}) + if 'rw_mgmt_ip' not in user_values: + user_values['rw_mgmt_ip'] = None + # just for backward compatibility, because it will be provided always by modern version of LCM primitives[seq] = { 'name': primitive['name'], 'parameters': self._map_primitive_parameters( - params, - {'': None} + params_, + user_values ), } @@ -698,7 +709,7 @@ class N2VC: 'initial-config-primitives': {...} } """ - self.log.debug("Executing {}".format(primitive)) + self.log.debug("Executing primitive={} params={}".format(primitive, params)) uuid = None try: if not self.authenticated: @@ -772,8 +783,61 @@ class N2VC: self.log.debug(e) raise e - async def DestroyNetworkService(self, nsd): - raise NotImplementedError() + async def CreateNetworkService(self, ns_uuid): + """Create a new Juju model for the Network Service. + + Creates a new Model in the Juju Controller. + + :param str ns_uuid: A unique id representing an instaance of a + Network Service. + + :returns: True if the model was created. Raises JujuError on failure. + """ + if not self.authenticated: + await self.login() + + models = await self.controller.list_models() + if ns_uuid not in models: + try: + self.models[ns_uuid] = await self.controller.add_model( + ns_uuid + ) + except JujuError as e: + if "already exists" not in e.message: + raise e + return True + + async def DestroyNetworkService(self, ns_uuid): + """Destroy a Network Service. + + Destroy the Network Service and any deployed charms. + + :param ns_uuid The unique id of the Network Service + + :returns: True if the model was created. Raises JujuError on failure. + """ + + # Do not delete the default model. The default model was used by all + # Network Services, prior to the implementation of a model per NS. + if ns_uuid.lower() is "default": + return False + + if not self.authenticated: + self.log.debug("Authenticating with Juju") + await self.login() + + # Disconnect from the Model + if ns_uuid in self.models: + await self.disconnect_model(self.models[ns_uuid]) + + try: + await self.controller.destroy_models(ns_uuid) + except JujuError as e: + raise NetworkServiceDoesNotExist( + "The Network Service '{}' does not exist".format(ns_uuid) + ) + + return True async def GetMetrics(self, model_name, application_name): """Get the metrics collected by the VCA. @@ -852,34 +916,45 @@ class N2VC: return config - def _map_primitive_parameters(self, parameters, values): + def _map_primitive_parameters(self, parameters, user_values): params = {} for parameter in parameters: param = str(parameter['name']) - value = None + value = parameter.get('value') + + # map parameters inside a < >; e.g. . with the provided user_values. + # Must exist at user_values except if there is a default value + if isinstance(value, str) and value.startswith("<") and value.endswith(">"): + if parameter['value'][1:-1] in user_values: + value = user_values[parameter['value'][1:-1]] + elif 'default-value' in parameter: + value = parameter['default-value'] + else: + raise KeyError("parameter {}='{}' not supplied ".format(param, value)) # If there's no value, use the default-value (if set) - if parameter['value'] is None and 'default-value' in parameter: + if value is None and 'default-value' in parameter: value = parameter['default-value'] # Typecast parameter value, if present - if 'data-type' in parameter: - paramtype = str(parameter['data-type']).lower() + paramtype = "string" + try: + if 'data-type' in parameter: + paramtype = str(parameter['data-type']).lower() - if paramtype == "integer": - value = int(parameter['value']) - elif paramtype == "boolean": - value = bool(parameter['value']) + if paramtype == "integer": + value = int(value) + elif paramtype == "boolean": + value = bool(value) + else: + value = str(value) else: - value = str(parameter['value']) - else: - # If there's no data-type, assume the value is a string - value = str(parameter['value']) + # If there's no data-type, assume the value is a string + value = str(value) + except ValueError: + raise ValueError("parameter {}='{}' cannot be converted to type {}".format(param, value, paramtype)) - if parameter['value'] == "": - params[param] = str(values[parameter['value']]) - else: - params[param] = value + params[param] = value return params def _get_config_from_yang(self, config_primitive, values): @@ -918,7 +993,7 @@ class N2VC: elif not c.isalpha(): c = "-" appname += c - return re.sub('\-+', '-', appname.lower()) + return re.sub('-+', '-', appname.lower()) # def format_application_name(self, nsd_name, vnfr_name, member_vnf_index=0): # """Format the name of the application @@ -971,9 +1046,13 @@ class N2VC: models = await self.controller.list_models() if model_name not in models: - self.models[model_name] = await self.controller.add_model( - model_name - ) + try: + self.models[model_name] = await self.controller.add_model( + model_name + ) + except JujuError as e: + if "already exists" not in e.message: + raise e else: self.models[model_name] = await self.controller.get_model( model_name @@ -1033,7 +1112,7 @@ class N2VC: async def logout(self): """Logout of the Juju controller.""" if not self.authenticated: - return + return False try: for model in self.models: @@ -1056,15 +1135,11 @@ class N2VC: "Fatal error logging out of Juju Controller: {}".format(e) ) raise e + return True async def disconnect_model(self, model): self.log.debug("Disconnecting model {}".format(model)) if model in self.models: - print(self.models[model].applications) - if len(self.models[model].applications) == 0: - print("Destroying empty model") - await self.controller.destroy_models(model) - print("Disconnecting model") await self.models[model].disconnect() self.refcount['model'] -= 1