From: Adam Israel Date: Fri, 14 Sep 2018 16:01:12 +0000 (-0400) Subject: Relation support for multi-charm VNFs X-Git-Tag: v5.0.0~6^2 X-Git-Url: https://osm.etsi.org/gitweb/?a=commitdiff_plain;h=136186e189027698662452ee00a5e50ebe82384f;p=osm%2FN2VC.git Relation support for multi-charm VNFs Adds support for establishing relations between charms Signed-off-by: Adam Israel Change-Id: I9a0b7725013e06635875cd824e219ab6a023efb3 --- diff --git a/n2vc/vnf.py b/n2vc/vnf.py index a1fcfe3..31e4877 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 # We might need this to connect to the websocket securely, but test and verify. try: @@ -254,6 +254,100 @@ class N2VC: return self.default_model + async def Relate(self, ns_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. + + vdu: + ... + relation: + - provides: dataVM:db + requires: mgmtVM:app + + This tells N2VC that the charm referred to by the dataVM vdu offers a relation named 'db', and the mgmtVM vdu has an 'app' endpoint that should be connected to a database. + + :param str ns_name: The name of the network service. + :param dict vnfd: The parsed yaml VNF descriptor. + """ + + # Currently, the call to Relate() is made automatically after the + # deployment of each charm; if the relation depends on a charm that + # hasn't been deployed yet, the call will fail silently. This will + # prevent an API breakage, with the intent of making this an explicitly + # required call in a more object-oriented refactor of the N2VC API. + + configs = [] + vnf_config = vnfd.get("vnf-configuration") + if vnf_config: + juju = vnf_config['juju'] + if juju: + configs.append(vnf_config) + + for vdu in vnfd['vdu']: + vdu_config = vdu.get('vdu-configuration') + if vdu_config: + juju = vdu_config['juju'] + if juju: + configs.append(vdu_config) + + def _get_application_name(name): + """Get the application name that's mapped to a vnf/vdu.""" + vnf_member_index = 0 + vnf_name = vnfd['name'] + + for vdu in vnfd.get('vdu'): + # Compare the named portion of the relation to the vdu's id + if vdu['id'] == name: + application_name = self.FormatApplicationName( + ns_name, + vnf_name, + str(vnf_member_index), + ) + return application_name + else: + vnf_member_index += 1 + + return None + + # Loop through relations + for cfg in configs: + if 'juju' in cfg: + if 'relation' in juju: + for rel in juju['relation']: + try: + + # get the application name for the provides + (name, endpoint) = rel['provides'].split(':') + application_name = _get_application_name(name) + + provides = "{}:{}".format( + application_name, + endpoint + ) + + # get the application name for thr requires + (name, endpoint) = rel['requires'].split(':') + application_name = _get_application_name(name) + + requires = "{}:{}".format( + application_name, + endpoint + ) + self.log.debug("Relation: {} <-> {}".format( + provides, + requires + )) + await self.add_relation( + ns_name, + provides, + requires, + ) + except Exception as e: + self.log.debug("Exception: {}".format(e)) + + return + async def DeployCharms(self, model_name, application_name, vnfd, charm_path, params={}, machine_spec={}, callback=None, *callback_args): @@ -384,6 +478,10 @@ class N2VC: to=to, ) + # Map the vdu id<->app name, + # + await self.Relate(model_name, vnfd) + # ####################################### # # Execute initial config primitive(s) # # ####################################### @@ -727,23 +825,31 @@ class N2VC: return False # Non-public methods - async def add_relation(self, a, b, via=None): + async def add_relation(self, model_name, relation1, relation2): """ Add a relation between two application endpoints. - :param a An application endpoint - :param b An application endpoint - :param via The egress subnet(s) for outbound traffic, e.g., - (192.168.0.0/16,10.0.0.0/8) + :param str model_name Name of the network service. + :param str relation1 '[:]' + :param str relation12 '[:]' """ + if not self.authenticated: await self.login() - m = await self.get_model() + m = await self.get_model(model_name) try: - m.add_relation(a, b, via) - finally: - await m.disconnect() + await m.add_relation(relation1, relation2) + except JujuAPIError as e: + # If one of the applications in the relationship doesn't exist, + # or the relation has already been added, let the operation fail + # silently. + if 'not found' in e.message: + return + if 'already exists' in e.message: + return + + raise e # async def apply_config(self, config, application): # """Apply a configuration to the application.""" diff --git a/tests/charms/layers/native-ci/layer.yaml b/tests/charms/layers/native-ci/layer.yaml index edc8839..138d9d3 100644 --- a/tests/charms/layers/native-ci/layer.yaml +++ b/tests/charms/layers/native-ci/layer.yaml @@ -1,4 +1,7 @@ -includes: ['layer:basic'] +includes: + - 'layer:basic' + - 'interface:mysql' + options: basic: use_venv: false diff --git a/tests/charms/layers/native-ci/metadata.yaml b/tests/charms/layers/native-ci/metadata.yaml index 6acf296..0460e48 100644 --- a/tests/charms/layers/native-ci/metadata.yaml +++ b/tests/charms/layers/native-ci/metadata.yaml @@ -4,3 +4,9 @@ description: A native VNF charm maintainer: Adam Israel subordinate: false series: ['xenial'] +provides: + db: + interface: mysql +requires: + app: + interface: mysql diff --git a/tests/charms/layers/native-ci/reactive/native-ci.py b/tests/charms/layers/native-ci/reactive/native-ci.py index 17bf5f4..9e5fe67 100644 --- a/tests/charms/layers/native-ci/reactive/native-ci.py +++ b/tests/charms/layers/native-ci/reactive/native-ci.py @@ -42,3 +42,21 @@ def testint(): action_set({'output': intval}) finally: clear_flag('actions.testint') + + +@when('db.joined') +def provides_db(db): + """Simulate providing database credentials.""" + db.configure( + database="mydb", + user="myuser", + password="mypassword", + host="myhost", + slave="myslave", + ) + + +@when('db.available') +def requires_db(db): + """Simulate receiving database credentials.""" + pass diff --git a/tests/charms/layers/proxy-ci/metadata.yaml b/tests/charms/layers/proxy-ci/metadata.yaml index b96abe4..bb00a03 100644 --- a/tests/charms/layers/proxy-ci/metadata.yaml +++ b/tests/charms/layers/proxy-ci/metadata.yaml @@ -10,3 +10,9 @@ tags: subordinate: false series: - xenial +provides: + db: + interface: mysql +requires: + app: + interface: mysql diff --git a/tests/charms/layers/proxy-ci/reactive/proxy_ci.py b/tests/charms/layers/proxy-ci/reactive/proxy_ci.py index 98b7f96..9c0136e 100644 --- a/tests/charms/layers/proxy-ci/reactive/proxy_ci.py +++ b/tests/charms/layers/proxy-ci/reactive/proxy_ci.py @@ -32,3 +32,21 @@ def test(): action_set({'output': result}) finally: clear_flag('actions.test') + + +@when('db.joined') +def provides_db(db): + """Simulate providing database credentials.""" + db.configure( + database="mydb", + user="myuser", + password="mypassword", + host="myhost", + slave="myslave", + ) + + +@when('db.available') +def requires_db(db): + """Simulate receiving database credentials.""" + pass diff --git a/tests/integration/test_multivdu_multicharm.py b/tests/integration/test_multivdu_multicharm.py index e0fb9c7..b879373 100644 --- a/tests/integration/test_multivdu_multicharm.py +++ b/tests/integration/test_multivdu_multicharm.py @@ -144,6 +144,11 @@ class TestCharm(base.TestN2VC): juju: charm: proxy-ci proxy: true + # Relation needs to map to the vdu providing or + # requiring, so that we can map to the deployed app. + relation: + - provides: dataVM:db + requires: mgmtVM:app initial-config-primitive: - seq: '1' name: test