From 85a4b210d2353ae209e3269498220febf0172270 Mon Sep 17 00:00:00 2001 From: Adam Israel Date: Thu, 29 Nov 2018 20:30:24 -0500 Subject: [PATCH] Add per-network service models In part to address, Bug 585, this patch drops the use of the "default" Juju model and instead creates a model per network service (which is required to be passed to N2VC methods). Change-Id: I31cfd56d71697066ff9c11df9c7607c791470cfa Signed-off-by: Adam Israel --- n2vc/vnf.py | 114 ++++++++++++++++++++++---------------------------- tests/base.py | 25 +++++------ 2 files changed, 60 insertions(+), 79 deletions(-) diff --git a/n2vc/vnf.py b/n2vc/vnf.py index 1c79aed..1c1208f 100644 --- a/n2vc/vnf.py +++ b/n2vc/vnf.py @@ -176,7 +176,6 @@ class N2VC: } self.models = {} - self.default_model = None # Model Observers self.monitors = {} @@ -235,26 +234,7 @@ class N2VC: return True # Public methods - async def CreateNetworkService(self, nsd): - """Create a new model to encapsulate this network service. - - Create a new model in the Juju controller to encapsulate the - charms associated with a network service. - - You can pass either the nsd record or the id of the network - service, but this method will fail without one of them. - """ - if not self.authenticated: - await self.login() - - # Ideally, we will create a unique model per network service. - # This change will require all components, i.e., LCM and SO, to use - # N2VC for 100% compatibility. If we adopt unique models for the LCM, - # services deployed via LCM would't be manageable via SO and vice versa - - return self.default_model - - async def Relate(self, ns_name, vnfd): + async def Relate(self, model_name, vnfd): """Create a relation between the charm-enabled VDUs in a VNF. The Relation mapping has two parts: the id of the vdu owning the endpoint, and the name of the endpoint. @@ -300,7 +280,7 @@ class N2VC: # Compare the named portion of the relation to the vdu's id if vdu['id'] == name: application_name = self.FormatApplicationName( - ns_name, + model_name, vnf_name, str(vnf_member_index), ) @@ -339,7 +319,7 @@ class N2VC: requires )) await self.add_relation( - ns_name, + model_name, provides, requires, ) @@ -355,7 +335,7 @@ class N2VC: Deploy the charm(s) referenced in a VNF Descriptor. - :param str model_name: The name of the network service. + :param str model_name: The name or unique id 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 @@ -402,9 +382,6 @@ class N2VC: ########################################## # Get the model for this network service # ########################################## - # TODO: In a point release, we will use a model per deployed network - # service. In the meantime, we will always use the 'default' model. - model_name = 'default' model = await self.get_model(model_name) ######################################## @@ -454,7 +431,8 @@ class N2VC: {'': rw_mgmt_ip} ) - self.log.debug("JujuApi: Deploying charm ({}) from {}".format( + self.log.debug("JujuApi: Deploying charm ({}/{}) from {}".format( + model_name, application_name, charm_path, to=to, @@ -543,9 +521,6 @@ class N2VC: if not self.authenticated: await self.login() - # FIXME: This is hard-coded until model-per-ns is added - model_name = 'default' - model = await self.get_model(model_name) results = await model.get_action_status(uuid) @@ -571,9 +546,6 @@ class N2VC: if not self.authenticated: await self.login() - # FIXME: This is hard-coded until model-per-ns is added - model_name = 'default' - model = await self.get_model(model_name) results = await model.get_action_output(uuid, 60) except Exception as e: @@ -711,7 +683,7 @@ class N2VC: Execute a primitive defined in the VNF descriptor. - :param str model_name: The name of the network service. + :param str model_name: The name or unique id 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. @@ -732,9 +704,6 @@ class N2VC: if not self.authenticated: await self.login() - # FIXME: This is hard-coded until model-per-ns is added - model_name = 'default' - model = await self.get_model(model_name) if primitive == 'config': @@ -787,6 +756,8 @@ class N2VC: ) await app.remove() + await self.disconnect_model(self.monitors[model_name]) + # Notify the callback that this charm has been removed. self.notify_callback( model_name, @@ -807,7 +778,7 @@ class N2VC: async def GetMetrics(self, model_name, application_name): """Get the metrics collected by the VCA. - :param model_name The name of the model + :param model_name The name or unique id of the network service :param application_name The name of the application """ metrics = {} @@ -830,9 +801,9 @@ class N2VC: """ Add a relation between two application endpoints. - :param str model_name Name of the network service. - :param str relation1 '[:]' - :param str relation12 '[:]' + :param str model_name: The name or unique id of the network service + :param str relation1: '[:]' + :param str relation2: '[:]' """ if not self.authenticated: @@ -987,7 +958,7 @@ class N2VC: return app - async def get_model(self, model_name='default'): + async def get_model(self, model_name): """Get a model from the Juju Controller. Note: Model objects returned must call disconnected() before it goes @@ -996,9 +967,18 @@ class N2VC: await self.login() if model_name not in self.models: - self.models[model_name] = await self.controller.get_model( - model_name, - ) + # Get the models in the controller + models = await self.controller.list_models() + + if model_name not in models: + self.models[model_name] = await self.controller.add_model( + model_name + ) + else: + self.models[model_name] = await self.controller.get_model( + model_name + ) + self.refcount['model'] += 1 # Create an observer for this model @@ -1056,18 +1036,8 @@ class N2VC: return try: - if self.default_model: - self.log.debug("Disconnecting model {}".format( - self.default_model - )) - await self.default_model.disconnect() - self.refcount['model'] -= 1 - self.default_model = None - for model in self.models: - await self.models[model].disconnect() - self.refcount['model'] -= 1 - self.models[model] = None + await self.disconnect_model(model) if self.controller: self.log.debug("Disconnecting controller {}".format( @@ -1087,6 +1057,19 @@ class N2VC: ) raise e + 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 + self.models[model] = None + # async def remove_application(self, name): # """Remove the application.""" # if not self.authenticated: @@ -1116,12 +1099,14 @@ class N2VC: finally: await m.disconnect() - async def resolve_error(self, application=None): + async def resolve_error(self, model_name, application=None): """Resolve units in error state.""" if not self.authenticated: await self.login() - app = await self.get_application(self.default_model, application) + model = await self.get_model(model_name) + + app = await self.get_application(model, application) if app: self.log.debug( "JujuApi: Resolving errors for application {}".format( @@ -1132,7 +1117,7 @@ class N2VC: for unit in app.units: app.resolved(retry=True) - async def run_action(self, application, action_name, **params): + async def run_action(self, model_name, application, action_name, **params): """Execute an action and return an Action object.""" if not self.authenticated: await self.login() @@ -1143,7 +1128,10 @@ class N2VC: 'results': None, } } - app = await self.get_application(self.default_model, application) + + model = await self.get_model(model_name) + + app = await self.get_application(model, application) if app: # We currently only have one unit per application # so use the first unit available. @@ -1206,14 +1194,10 @@ class N2VC: if not self.authenticated: await self.login() - # TODO: In a point release, we will use a model per deployed network - # service. In the meantime, we will always use the 'default' model. - model_name = 'default' model = await self.get_model(model_name) app = await self.get_application(model, application_name) self.log.debug("Application: {}".format(app)) - # app = await self.get_application(model_name, application_name) if app: self.log.debug( "JujuApi: Waiting {} seconds for Application {}".format( diff --git a/tests/base.py b/tests/base.py index 0959059..8b61461 100644 --- a/tests/base.py +++ b/tests/base.py @@ -65,9 +65,9 @@ def debug(msg): logging.debug( "[{}] {}".format(now.strftime('%Y-%m-%dT%H:%M:%S'), msg) ) - # print( - # "[{}] {}".format(now.strftime('%Y-%m-%dT%H:%M:%S'), msg) - # ) + print( + "[{}] {}".format(now.strftime('%Y-%m-%dT%H:%M:%S'), msg) + ) def get_charm_path(): @@ -432,9 +432,6 @@ class TestN2VC(object): self.ns_name = self.nsd['name'] self.vnf_name = self.vnfd['name'] - # Hard-coded to default for now, but this may change in the future. - self.model = "default" - self.charms = {} self.parse_vnf_descriptor() assert self.charms is not {} @@ -553,7 +550,7 @@ class TestN2VC(object): # Make sure the charm snap is installed try: subprocess.check_call(['which', 'charm']) - except subprocess.CalledProcessError as e: + except subprocess.CalledProcessError: raise Exception("charm snap not installed.") if charm not in self.artifacts: @@ -565,7 +562,7 @@ class TestN2VC(object): builds = get_charm_path() if not os.path.exists("{}/builds/{}".format(builds, charm)): - cmd = "charm build {}/{} -o {}/".format( + cmd = "charm build --no-local-layers {}/{} -o {}/".format( get_layer_path(), charm, builds, @@ -891,13 +888,13 @@ class TestN2VC(object): for application in self.charms: try: - await self.n2vc.RemoveCharms(self.model, application) + await self.n2vc.RemoveCharms(self.ns_name, application) while True: # Wait for the application to be removed await asyncio.sleep(10) if not await self.n2vc.HasApplication( - self.model, + self.ns_name, application, ): break @@ -962,7 +959,7 @@ class TestN2VC(object): ) await self.n2vc.ExecutePrimitive( - self.model, + self.ns_name, application, "config", None, @@ -987,7 +984,7 @@ class TestN2VC(object): Re-run those actions so we can inspect the status. """ uuids = await self.n2vc.ExecuteInitialPrimitives( - self.model, + self.ns_name, application, init_config, ) @@ -1019,7 +1016,7 @@ class TestN2VC(object): debug("Collecting metrics for {}".format(application)) metrics = await self.n2vc.GetMetrics( - self.model, + self.ns_name, application, ) @@ -1069,7 +1066,7 @@ class TestN2VC(object): debug("Getting status of {} ({})...".format(uid, status)) status = await self.n2vc.GetPrimitiveStatus( - self.model, + self.ns_name, uid, ) debug("...state of {} is {}".format(uid, status)) -- 2.17.1