From: Adam Israel Date: Tue, 10 Apr 2018 19:04:57 +0000 (-0600) Subject: Improve N2VC performance/functionality X-Git-Tag: v4.0.0~8 X-Git-Url: https://osm.etsi.org/gitweb/?a=commitdiff_plain;h=refs%2Fchanges%2F60%2F5960%2F2;p=osm%2FN2VC.git Improve N2VC performance/functionality - Refactor test to support multi-vdu charms. - Add support for deploying multi-vdu charms. - Execute the initial-config-primitives This is the first commit to add support for executing the initial-config-primitive(s). There are some decisions that still need to be made: how do we report primitive execution back to the caller/callback in a meaningful way. Signed-off-by: Adam Israel Change-Id: Icc0ce41d256930b337c9097af9edcae2694207e8 --- diff --git a/n2vc/vnf.py b/n2vc/vnf.py index c606dda..7244b22 100644 --- a/n2vc/vnf.py +++ b/n2vc/vnf.py @@ -36,6 +36,9 @@ class JujuCharmNotFound(Exception): class JujuApplicationExists(Exception): """The Application already exists.""" +class N2VCPrimitiveExecutionFailed(Exception): + """Something failed while attempting to execute a primitive.""" + # Quiet the debug logging logging.getLogger('websockets.protocol').setLevel(logging.INFO) @@ -67,7 +70,9 @@ class VCAMonitor(ModelObserver): 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.""" @@ -79,7 +84,23 @@ class VCAMonitor(ModelObserver): *self.callback_args) except Exception as e: self.log.debug("[1] notify_callback exception {}".format(e)) + elif delta.entity == "action": + # TODO: Decide how we want to notify the user of actions + + # uuid = delta.data['id'] # The Action's unique id + # msg = delta.data['message'] # The output of the action + # + # if delta.data['status'] == "pending": + # # The action is queued + # pass + # elif delta.data['status'] == "completed"" + # # The action was successful + # pass + # elif delta.data['status'] == "failed": + # # The action failed. + # pass + pass ######## # TODO @@ -170,6 +191,7 @@ class N2VC: callback(model_name, application_name, status, *callback_args) except Exception as e: self.log.error("[0] notify_callback exception {}".format(e)) + raise e return True # Public methods @@ -207,7 +229,9 @@ class N2VC: :param dict params: A dictionary of runtime parameters Examples:: { - 'rw_mgmt_ip': '1.2.3.4' + 'rw_mgmt_ip': '1.2.3.4', + # Pass the initial-config-primitives section of the vnf or vdu + 'initial-config-primitives': {...} } :param dict machine_spec: A dictionary describing the machine to install to Examples:: @@ -241,18 +265,6 @@ class N2VC: # service. In the meantime, we will always use the 'default' model. model_name = 'default' model = await self.get_model(model_name) - # if model_name not in self.models: - # self.log.debug("Getting model {}".format(model_name)) - # self.models[model_name] = await self.controller.get_model(model_name) - # model = await self.CreateNetworkService(ns_name) - - ################################################### - # Get the name of the charm and its configuration # - ################################################### - config_dict = vnfd['vnf-configuration'] - juju = config_dict['juju'] - charm = juju['charm'] - self.log.debug("Charm: {}".format(charm)) ######################################## # Verify the application doesn't exist # @@ -269,7 +281,6 @@ class N2VC: self.monitors[application_name] = VCAMonitor(model_name, application_name, callback, *callback_args) model.add_observer(self.monitors[application_name]) - ######################################################## # Check for specific machine placement (native charms) # ######################################################## @@ -294,12 +305,11 @@ class N2VC: rw_mgmt_ip = params['rw_mgmt_ip'] initial_config = self._get_config_from_dict( - config_dict['initial-config-primitive'], + params['initial-config-primitive'], {'': rw_mgmt_ip} ) - self.log.debug("JujuApi: Deploying charm {} ({}) from {}".format( - charm, + self.log.debug("JujuApi: Deploying charm ({}) from {}".format( application_name, charm_path, to=to, @@ -309,13 +319,59 @@ class N2VC: # Deploy the charm and apply the initial configuration # ######################################################## app = await model.deploy( + # We expect charm_path to be either the path to the charm on disk + # or in the format of cs:series/name charm_path, + # This is the formatted, unique name for this charm application_name=application_name, + # Proxy charms should use the current LTS. This will need to be + # changed for native charms. series='xenial', + # Apply the initial 'config' primitive during deployment config=initial_config, + # TBD: Where to deploy the charm to. to=None, ) + # ####################################### + # # Execute initial config primitive(s) # + # ####################################### + primitives = {} + + # Build a sequential list of the primitives to execute + for primitive in params['initial-config-primitive']: + try: + if primitive['name'] == 'config': + # 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'] + + primitives[seq] = { + 'name': primitive['name'], + 'parameters': self._map_primitive_parameters( + primitive['parameter'], + {'': rw_mgmt_ip} + ), + } + + for primitive in sorted(primitives): + await self.ExecutePrimitive( + model_name, + application_name, + primitives[primitive]['name'], + callback, + callback_args, + **primitives[primitive]['parameters'], + ) + except N2VCPrimitiveExecutionFailed as e: + self.debug.log( + "[N2VC] Exception executing primitive: {}".format(e) + ) + raise + async def ExecutePrimitive(self, model_name, application_name, primitive, callback, *callback_args, **params): try: if not self.authenticated: @@ -336,7 +392,7 @@ class N2VC: if unit: self.log.debug("Executing primitive {}".format(primitive)) action = await unit.run_action(primitive, **params) - action = await action.wait() + # action = await action.wait() await model.disconnect() except Exception as e: self.log.debug("Caught exception while executing primitive: {}".format(e)) @@ -355,6 +411,7 @@ class N2VC: self.notify_callback(model_name, application_name, "removed", callback, *callback_args) except Exception as e: print("Caught exception: {}".format(e)) + self.log.debug(e) raise e async def DestroyNetworkService(self, nsd): @@ -391,10 +448,18 @@ class N2VC: 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.""" + """Transform the yang config primitive to dict. + + Expected result: + + config = { + 'config': + } + """ config = {} for primitive in config_primitive: if primitive['name'] == 'config': + # config = self._map_primitive_parameters() for parameter in primitive['parameter']: param = str(parameter['name']) if parameter['value'] == "": @@ -404,6 +469,16 @@ class N2VC: return config + def _map_primitive_parameters(self, parameters, values): + params = {} + for parameter in parameters: + param = str(parameter['name']) + if parameter['value'] == "": + params[param] = str(values[parameter['value']]) + else: + params[param] = str(parameter['value']) + return params + def _get_config_from_yang(self, config_primitive, values): """Transform the yang config primitive to dict.""" config = {} @@ -521,6 +596,11 @@ class N2VC: # current_controller no longer exists # self.log.debug("Connecting to current controller...") # await self.controller.connect_current() + # await self.controller.connect( + # endpoint=self.endpoint, + # username=self.user, + # cacert=cacert, + # ) self.log.fatal("VCA credentials not configured.") self.authenticated = True diff --git a/tests/test_python.py b/tests/test_python.py index b770a01..5bb4325 100755 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -4,216 +4,184 @@ import functools import os import sys import logging -import osm_im.vnfd as vnfd_catalog -import osm_im.nsd as nsd_catalog -from pyangbind.lib.serialise import pybindJSONDecoder import unittest import yaml from n2vc.vnf import N2VC NSD_YAML = """ -nsd-catalog: +nsd:nsd-catalog: nsd: - - id: rift_ping_pong_ns - logo: rift_logo.png - name: ping_pong_ns - short-name: ping_pong_ns - vendor: RIFT.io - version: '1.1' - description: RIFT.io sample ping pong network service + - id: multicharmvdu-ns + name: multicharmvdu-ns + short-name: multicharmvdu-ns + description: NS with 2 VNFs multicharmvdu-vnf connected by datanet and mgmtnet VLs + version: '1.0' + logo: osm.png constituent-vnfd: - - member-vnf-index: '1' - vnfd-id-ref: rift_ping_vnf - - member-vnf-index: '2' - vnfd-id-ref: rift_pong_vnf - initial-service-primitive: - - name: start traffic - parameter: - - name: port - value: 5555 - - name: ssh-username - value: fedora - - name: ssh-password - value: fedora - seq: '1' - user-defined-script: start_traffic.py - input-parameter-xpath: - - xpath: /nsd:nsd-catalog/nsd:nsd/nsd:vendor - ip-profiles: - - description: Inter VNF Link - ip-profile-params: - gateway-address: 31.31.31.210 - ip-version: ipv4 - subnet-address: 31.31.31.0/24 - dhcp-params: - count: 200 - start-address: 31.31.31.2 - name: InterVNFLink - placement-groups: - - member-vnfd: - - member-vnf-index-ref: '1' - vnfd-id-ref: rift_ping_vnf - - member-vnf-index-ref: '2' - vnfd-id-ref: rift_pong_vnf - name: Orcus - requirement: Place this VM on the Kuiper belt object Orcus - strategy: COLOCATION - - member-vnfd: - - member-vnf-index-ref: '1' - vnfd-id-ref: rift_ping_vnf - - member-vnf-index-ref: '2' - vnfd-id-ref: rift_pong_vnf - name: Quaoar - requirement: Place this VM on the Kuiper belt object Quaoar - strategy: COLOCATION + - vnfd-id-ref: multicharmvdu-vnf + member-vnf-index: '1' + - vnfd-id-ref: multicharmvdu-vnf + member-vnf-index: '2' vld: - - id: mgmt_vl - description: Management VL - name: mgmt_vl - short-name: mgmt_vl - vim-network-name: mgmt + - id: mgmtnet + name: mgmtnet + short-name: mgmtnet type: ELAN - vendor: RIFT.io - version: '1.0' mgmt-network: 'true' + vim-network-name: mgmt vnfd-connection-point-ref: - - member-vnf-index-ref: '1' - vnfd-connection-point-ref: ping_vnfd/cp0 - vnfd-id-ref: rift_ping_vnf - - member-vnf-index-ref: '2' - vnfd-connection-point-ref: pong_vnfd/cp0 - vnfd-id-ref: rift_pong_vnf - - id: ping_pong_vl1 - description: Data VL - ip-profile-ref: InterVNFLink - name: data_vl - short-name: data_vl + - vnfd-id-ref: multicharmvdu-vnf + member-vnf-index-ref: '1' + vnfd-connection-point-ref: vnf-mgmt + - vnfd-id-ref: multicharmvdu-vnf + member-vnf-index-ref: '2' + vnfd-connection-point-ref: vnf-mgmt + - id: datanet + name: datanet + short-name: datanet type: ELAN - vendor: RIFT.io - version: '1.0' vnfd-connection-point-ref: - - member-vnf-index-ref: '1' - vnfd-connection-point-ref: ping_vnfd/cp1 - vnfd-id-ref: rift_ping_vnf - - member-vnf-index-ref: '2' - vnfd-connection-point-ref: pong_vnfd/cp1 - vnfd-id-ref: rift_pong_vnf + - vnfd-id-ref: multicharmvdu-vnf + member-vnf-index-ref: '1' + vnfd-connection-point-ref: vnf-data + - vnfd-id-ref: multicharmvdu-vnf + member-vnf-index-ref: '2' + vnfd-connection-point-ref: vnf-data """ -VNFD_VCA_YAML = """ -vnfd-catalog: +VNFD_YAML = """ +vnfd:vnfd-catalog: vnfd: - - id: rift_ping_vnf - name: ping_vnf - short-name: ping_vnf - logo: rift_logo.png - vendor: RIFT.io - version: '1.1' - description: This is an example RIFT.ware VNF + - id: multicharmvdu-vnf + name: multicharmvdu-vnf + short-name: multicharmvdu-vnf + version: '1.0' + description: A VNF consisting of 2 VDUs w/charms connected to an internal VL, and one VDU with cloud-init + logo: osm.png connection-point: - - name: ping_vnfd/cp0 + - id: vnf-mgmt + name: vnf-mgmt + short-name: vnf-mgmt type: VPORT - - name: ping_vnfd/cp1 + - id: vnf-data + name: vnf-data + short-name: vnf-data type: VPORT - http-endpoint: - - path: api/v1/ping/stats - port: '18888' mgmt-interface: - dashboard-params: - path: api/v1/ping/stats - port: '18888' - port: '18888' - cp: ping_vnfd/cp0 - placement-groups: - - member-vdus: - - member-vdu-ref: iovdu_0 - name: Eris - requirement: Place this VM on the Kuiper belt object Eris - strategy: COLOCATION + cp: vnf-mgmt + internal-vld: + - id: internal + name: internal + short-name: internal + type: ELAN + internal-connection-point: + - id-ref: mgmtVM-internal + - id-ref: dataVM-internal vdu: - - cloud-init-file: ping_cloud_init.cfg + - id: mgmtVM + name: mgmtVM + image: xenial count: '1' + vm-flavor: + vcpu-count: '1' + memory-mb: '1024' + storage-gb: '10' interface: - - name: eth0 - position: 0 + - name: mgmtVM-eth0 + position: '1' type: EXTERNAL virtual-interface: type: VIRTIO - external-connection-point-ref: ping_vnfd/cp0 - - name: eth1 - position: 1 - type: EXTERNAL + external-connection-point-ref: vnf-mgmt + - name: mgmtVM-eth1 + position: '2' + type: INTERNAL virtual-interface: type: VIRTIO - external-connection-point-ref: ping_vnfd/cp1 - id: iovdu_0 - image: Fedora-x86_64-20-20131211.1-sda-ping.qcow2 - name: iovdu_0 + internal-connection-point-ref: mgmtVM-internal + internal-connection-point: + - id: mgmtVM-internal + name: mgmtVM-internal + short-name: mgmtVM-internal + type: VPORT + cloud-init-file: cloud-config.txt + vdu-configuration: + juju: + charm: simple + initial-config-primitive: + - seq: '1' + name: config + parameter: + - name: ssh-hostname + value: + - name: ssh-username + value: ubuntu + - name: ssh-password + value: osm4u + - seq: '2' + name: touch + parameter: + - name: filename + value: '/home/ubuntu/first-touch-mgmtVM' + config-primitive: + - name: touch + parameter: + - name: filename + data-type: STRING + default-value: '/home/ubuntu/touched' + + - id: dataVM + name: dataVM + image: xenial + count: '1' vm-flavor: - memory-mb: '512' - storage-gb: '4' vcpu-count: '1' - vnf-configuration: - config-primitive: - - name: start - - name: stop - - name: restart - - name: config - parameter: - - data-type: STRING - default-value: - name: ssh-hostname - - data-type: STRING - default-value: fedora - name: ssh-username - - data-type: STRING - default-value: fedora - name: ssh-password - - data-type: STRING - name: ssh-private-key - - data-type: STRING - default-value: ping - name: mode - read-only: 'true' - - name: set-server - parameter: - - data-type: STRING - name: server-ip - - data-type: INTEGER - name: server-port - - name: set-rate - parameter: - - data-type: INTEGER - default-value: '5' - name: rate - - name: start-traffic - - name: stop-traffic - initial-config-primitive: - - name: config - parameter: - - name: ssh-hostname - value: - - name: ssh-username - value: fedora - - name: ssh-password - value: fedora - - name: mode - value: ping - seq: '1' - - name: start - seq: '2' - juju: - charm: pingpong + memory-mb: '1024' + storage-gb: '10' + interface: + - name: dataVM-eth0 + position: '1' + type: INTERNAL + virtual-interface: + type: VIRTIO + internal-connection-point-ref: dataVM-internal + - name: dataVM-xe0 + position: '2' + type: EXTERNAL + virtual-interface: + type: VIRTIO + external-connection-point-ref: vnf-data + internal-connection-point: + - id: dataVM-internal + name: dataVM-internal + short-name: dataVM-internal + type: VPORT + vdu-configuration: + juju: + charm: simple + initial-config-primitive: + - seq: '1' + name: config + parameter: + - name: ssh-hostname + value: + - name: ssh-username + value: ubuntu + - name: ssh-password + value: osm4u + - seq: '2' + name: touch + parameter: + - name: filename + value: '/home/ubuntu/first-touch-dataVM' + config-primitive: + - name: touch + parameter: + - name: filename + data-type: STRING + default-value: '/home/ubuntu/touched' """ -NSD_DICT = {'name': 'ping_pong_ns', 'short-name': 'ping_pong_ns', 'ip-profiles': [{'ip-profile-params': {'ip-version': 'ipv4', 'dhcp-params': {'count': 200, 'start-address': '31.31.31.2'}, 'subnet-address': '31.31.31.0/24', 'gateway-address': '31.31.31.210'}, 'description': 'Inter VNF Link', 'name': 'InterVNFLink'}], 'logo': 'rift_logo.png', 'description': 'RIFT.io sample ping pong network service', '_admin': {'storage': {'folder': 'd9bc2e64-dfec-4c36-a8bb-c4667e8d7a53', 'tarfile': 'pkg', 'file': 'ping_pong_ns', 'path': '/app/storage/', 'fs': 'local'}, 'created': 1521127984.6414561, 'modified': 1521127984.6414561, 'projects_write': ['admin'], 'projects_read': ['admin']}, 'placement-groups': [{'member-vnfd': [{'member-vnf-index-ref': '1', 'vnfd-id-ref': 'rift_ping_vnf'}, {'member-vnf-index-ref': '2', 'vnfd-id-ref': 'rift_pong_vnf'}], 'name': 'Orcus', 'strategy': 'COLOCATION', 'requirement': 'Place this VM on the Kuiper belt object Orcus'}, {'member-vnfd': [{'member-vnf-index-ref': '1', 'vnfd-id-ref': 'rift_ping_vnf'}, {'member-vnf-index-ref': '2', 'vnfd-id-ref': 'rift_pong_vnf'}], 'name': 'Quaoar', 'strategy': 'COLOCATION', 'requirement': 'Place this VM on the Kuiper belt object Quaoar'}], 'input-parameter-xpath': [{'xpath': '/nsd:nsd-catalog/nsd:nsd/nsd:vendor'}], 'version': '1.1', 'vld': [{'name': 'mgmt', 'short-name': 'mgmt', 'mgmt-network': 'true', 'vim-network-name': 'mgmt', 'version': '1.0', 'vnfd-connection-point-ref': [{'vnfd-connection-point-ref': 'ping_vnfd/cp0', 'member-vnf-index-ref': '1', 'vnfd-id-ref': 'rift_ping_vnf'}, {'vnfd-connection-point-ref': 'pong_vnfd/cp0', 'member-vnf-index-ref': '2', 'vnfd-id-ref': 'rift_pong_vnf'}], 'description': 'Management VL', 'vendor': 'RIFT.io', 'type': 'ELAN', 'id': 'mgmt'}, {'ip-profile-ref': 'InterVNFLink', 'name': 'data_vl', 'short-name': 'data_vl', 'version': '1.0', 'vnfd-connection-point-ref': [{'vnfd-connection-point-ref': 'ping_vnfd/cp1', 'member-vnf-index-ref': '1', 'vnfd-id-ref': 'rift_ping_vnf'}, {'vnfd-connection-point-ref': 'pong_vnfd/cp1', 'member-vnf-index-ref': '2', 'vnfd-id-ref': 'rift_pong_vnf'}], 'description': 'Data VL', 'vendor': 'RIFT.io', 'type': 'ELAN', 'id': 'ping_pong_vl1'}], 'constituent-vnfd': [{'member-vnf-index': '1', 'vnfd-id-ref': 'rift_ping_vnf'}, {'member-vnf-index': '2', 'vnfd-id-ref': 'rift_pong_vnf'}], '_id': 'd9bc2e64-dfec-4c36-a8bb-c4667e8d7a53', 'vendor': 'RIFT.io', 'id': 'rift_ping_pong_ns', 'initial-service-primitive': [{'parameter': [{'name': 'port', 'value': 5555}, {'name': 'ssh-username', 'value': 'fedora'}, {'name': 'ssh-password', 'value': 'fedora'}], 'name': 'start traffic', 'seq': '1', 'user-defined-script': 'start_traffic.py'}]} - - -VNFD_PING_DICT = {'name': 'ping_vnf', 'short-name': 'ping_vnf', 'mgmt-interface': {'port': '18888', 'cp': 'ping_vnfd/cp0', 'dashboard-params': {'port': '18888', 'path': 'api/v1/ping/stats'}}, 'description': 'This is an example RIFT.ware VNF', 'connection-point': [{'type': 'VPORT', 'name': 'ping_vnfd/cp0'}, {'type': 'VPORT', 'name': 'ping_vnfd/cp1'}], '_admin': {'storage': {'folder': '9ad8de93-cfcc-4da9-9795-d7dc5fec184e', 'fs': 'local', 'file': 'ping_vnf', 'path': '/app/storage/', 'tarfile': 'pkg'}, 'created': 1521127972.1878572, 'modified': 1521127972.1878572, 'projects_write': ['admin'], 'projects_read': ['admin']}, 'vnf-configuration': {'initial-config-primitive': [{'parameter': [{'name': 'ssh-hostname', 'value': ''}, {'name': 'ssh-username', 'value': 'ubuntu'}, {'name': 'ssh-password', 'value': 'ubuntu'}, {'name': 'mode', 'value': 'ping'}], 'name': 'config', 'seq': '1'}, {'name': 'start', 'seq': '2'}], 'juju': {'charm': 'pingpong'}, 'config-primitive': [{'name': 'start'}, {'name': 'stop'}, {'name': 'restart'}, {'parameter': [{'data-type': 'STRING', 'name': 'ssh-hostname', 'default-value': ''}, {'data-type': 'STRING', 'name': 'ssh-username', 'default-value': 'ubuntu'}, {'data-type': 'STRING', 'name': 'ssh-password', 'default-value': 'ubuntu'}, {'data-type': 'STRING', 'name': 'ssh-private-key'}, {'data-type': 'STRING', 'name': 'mode', 'read-only': 'true', 'default-value': 'ping'}], 'name': 'config'}, {'parameter': [{'data-type': 'STRING', 'name': 'server-ip'}, {'data-type': 'INTEGER', 'name': 'server-port'}], 'name': 'set-server'}, {'parameter': [{'data-type': 'INTEGER', 'name': 'rate', 'default-value': '5'}], 'name': 'set-rate'}, {'name': 'start-traffic'}, {'name': 'stop-traffic'}]}, 'placement-groups': [{'member-vdus': [{'member-vdu-ref': 'iovdu_0'}], 'name': 'Eris', 'strategy': 'COLOCATION', 'requirement': 'Place this VM on the Kuiper belt object Eris'}], 'http-endpoint': [{'port': '18888', 'path': 'api/v1/ping/stats'}], 'version': '1.1', 'logo': 'rift_logo.png', 'vdu': [{'vm-flavor': {'vcpu-count': '1', 'storage-gb': '20', 'memory-mb': '2048'}, 'count': '1', 'interface': [{'type': 'EXTERNAL', 'name': 'eth0', 'position': 0, 'virtual-interface': {'type': 'VIRTIO'}, 'external-connection-point-ref': 'ping_vnfd/cp0'}, {'type': 'EXTERNAL', 'name': 'eth1', 'position': 1, 'virtual-interface': {'type': 'VIRTIO'}, 'external-connection-point-ref': 'ping_vnfd/cp1'}], 'name': 'iovdu_0', 'image': 'xenial', 'id': 'iovdu_0', 'cloud-init-file': 'ping_cloud_init.cfg'}], '_id': '9ad8de93-cfcc-4da9-9795-d7dc5fec184e', 'vendor': 'RIFT.io', 'id': 'rift_ping_vnf'} - -VNFD_PONG_DICT = {'name': 'pong_vnf', 'short-name': 'pong_vnf', 'mgmt-interface': {'port': '18888', 'cp': 'pong_vnfd/cp0', 'dashboard-params': {'port': '18888', 'path': 'api/v1/pong/stats'}}, 'description': 'This is an example RIFT.ware VNF', 'connection-point': [{'type': 'VPORT', 'name': 'pong_vnfd/cp0'}, {'type': 'VPORT', 'name': 'pong_vnfd/cp1'}], '_admin': {'storage': {'folder': '9ad8de93-cfcc-4da9-9795-d7dc5fec184e', 'fs': 'local', 'file': 'pong_vnf', 'path': '/app/storage/', 'tarfile': 'pkg'}, 'created': 1521127972.1878572, 'modified': 1521127972.1878572, 'projects_write': ['admin'], 'projects_read': ['admin']}, 'vnf-configuration': {'initial-config-primitive': [{'parameter': [{'name': 'ssh-hostname', 'value': ''}, {'name': 'ssh-username', 'value': 'ubuntu'}, {'name': 'ssh-password', 'value': 'ubuntu'}, {'name': 'mode', 'value': 'pong'}], 'name': 'config', 'seq': '1'}, {'name': 'start', 'seq': '2'}], 'juju': {'charm': 'pingpong'}, 'config-primitive': [{'name': 'start'}, {'name': 'stop'}, {'name': 'restart'}, {'parameter': [{'data-type': 'STRING', 'name': 'ssh-hostname', 'default-value': ''}, {'data-type': 'STRING', 'name': 'ssh-username', 'default-value': 'ubuntu'}, {'data-type': 'STRING', 'name': 'ssh-password', 'default-value': 'ubuntu'}, {'data-type': 'STRING', 'name': 'ssh-private-key'}, {'data-type': 'STRING', 'name': 'mode', 'read-only': 'true', 'default-value': 'pong'}], 'name': 'config'}, {'parameter': [{'data-type': 'STRING', 'name': 'server-ip'}, {'data-type': 'INTEGER', 'name': 'server-port'}], 'name': 'set-server'}, {'parameter': [{'data-type': 'INTEGER', 'name': 'rate', 'default-value': '5'}], 'name': 'set-rate'}, {'name': 'start-traffic'}, {'name': 'stop-traffic'}]}, 'placement-groups': [{'member-vdus': [{'member-vdu-ref': 'iovdu_0'}], 'name': 'Eris', 'strategy': 'COLOCATION', 'requirement': 'Place this VM on the Kuiper belt object Eris'}], 'http-endpoint': [{'port': '18888', 'path': 'api/v1/pong/stats'}], 'version': '1.1', 'logo': 'rift_logo.png', 'vdu': [{'vm-flavor': {'vcpu-count': '1', 'storage-gb': '20', 'memory-mb': '2048'}, 'count': '1', 'interface': [{'type': 'EXTERNAL', 'name': 'eth0', 'position': 0, 'virtual-interface': {'type': 'VIRTIO'}, 'external-connection-point-ref': 'ping_vnfd/cp0'}, {'type': 'EXTERNAL', 'name': 'eth1', 'position': 1, 'virtual-interface': {'type': 'VIRTIO'}, 'external-connection-point-ref': 'ping_vnfd/cp1'}], 'name': 'iovdu_0', 'image': 'xenial', 'id': 'iovdu_0', 'cloud-init-file': 'pong_cloud_init.cfg'}], '_id': '9ad8de93-cfcc-4da9-9795-d7dc5fec184e', 'vendor': 'RIFT.io', 'id': 'rift_pong_vnf'} - - class PythonTest(unittest.TestCase): n2vc = None @@ -222,8 +190,10 @@ class PythonTest(unittest.TestCase): self.log = logging.getLogger() self.log.level = logging.DEBUG - self.loop = asyncio.new_event_loop() - asyncio.set_event_loop(None) + self.loop = asyncio.get_event_loop() + + # self.loop = asyncio.new_event_loop() + # asyncio.set_event_loop(None) # Extract parameters from the environment in order to run our test vca_host = os.getenv('VCA_HOST', '127.0.0.1') @@ -243,38 +213,20 @@ class PythonTest(unittest.TestCase): def tearDown(self): self.loop.run_until_complete(self.n2vc.logout()) - def get_vnf_descriptor(self, descriptor): - vnfd = vnfd_catalog.vnfd() - try: - data = yaml.load(descriptor) - pybindJSONDecoder.load_ietf_json(data, None, None, obj=vnfd) - except ValueError: - assert False - return vnfd - - def get_ns_descriptor(self, descriptor): - nsd = nsd_catalog.nsd() + def get_descriptor(self, descriptor): + desc = None try: - data = yaml.load(descriptor) - pybindJSONDecoder.load_ietf_json(data, None, None, obj=nsd) + tmp = yaml.load(descriptor) + + # Remove the envelope + root = list(tmp.keys())[0] + if root == "nsd:nsd-catalog": + desc = tmp['nsd:nsd-catalog']['nsd'][0] + elif root == "vnfd:vnfd-catalog": + desc = tmp['vnfd:vnfd-catalog']['vnfd'][0] except ValueError: assert False - return nsd - - # def test_descriptor(self): - # """Test loading and parsing a descriptor.""" - # nsd = self.get_ns_descriptor(NSD_YAML) - # vnfd = self.get_vnf_descriptor(VNFD_VCA_YAML) - # if vnfd and nsd: - # pass - - # def test_yang_to_dict(self): - # # Test the conversion of the native object returned by pybind to a dict - # # Extract parameters from the environment in order to run our test - # - # nsd = self.get_ns_descriptor(NSD_YAML) - # new = yang_to_dict(nsd) - # self.assertEqual(NSD_DICT, new) + return desc def n2vc_callback(self, model_name, application_name, workload_status, task=None): """We pass the vnfd when setting up the callback, so expect it to be @@ -301,56 +253,90 @@ class PythonTest(unittest.TestCase): elif workload_status in ["active"]: self.log.debug("Removing charm") task = asyncio.ensure_future( - self.n2vc.RemoveCharms(model_name, application_name, self.n2vc_callback, model_name, application_name) + self.n2vc.RemoveCharms(model_name, application_name, self.n2vc_callback) ) task.add_done_callback(functools.partial(self.n2vc_callback, None, None, None)) - elif task: - if task.done(): - self.loop.stop() def test_deploy_application(self): stream_handler = logging.StreamHandler(sys.stdout) self.log.addHandler(stream_handler) try: self.log.info("Log handler installed") - nsd = NSD_DICT - vnfd_ping = VNFD_PING_DICT - vnfd_pong = VNFD_PONG_DICT - if nsd and vnfd_ping and vnfd_pong: + nsd = self.get_descriptor(NSD_YAML) + vnfd = self.get_descriptor(VNFD_YAML) + + if nsd and vnfd: + vca_charms = os.getenv('VCA_CHARMS', None) - config = vnfd_ping['vnf-configuration'] - self.assertIsNotNone(config) + params = {} + vnf_index = 0 + + def deploy(): + """An inner function to do the deployment of a charm from + either a vdu or vnf. + """ + charm_dir = "{}/{}".format(vca_charms, charm) + + # Setting this to an IP that will fail the initial config. + # This will be detected in the callback, which will execute + # the "config" primitive with the right IP address. + params['rw_mgmt_ip'] = '10.195.8.78' - juju = config['juju'] - self.assertIsNotNone(juju) + # self.loop.run_until_complete(n.CreateNetworkService(nsd)) + ns_name = "default" - charm = juju['charm'] - self.assertIsNotNone(charm) + vnf_name = self.n2vc.FormatApplicationName( + ns_name, + vnfd['name'], + str(vnf_index), + ) + + self.loop.run_until_complete( + self.n2vc.DeployCharms( + ns_name, + vnf_name, + vnfd, + charm_dir, + params, + {}, + self.n2vc_callback + ) + ) + + # Check if the VDUs in this VNF have a charm + for vdu in vnfd['vdu']: + vdu_config = vdu.get('vdu-configuration') + if vdu_config: + juju = vdu_config['juju'] + self.assertIsNotNone(juju) + + charm = juju['charm'] + self.assertIsNotNone(charm) - charm_dir = "{}/{}".format(vca_charms, charm) - # n.callback_status = self.callback_status + params['initial-config-primitive'] = vdu_config['initial-config-primitive'] - # Setting this to an IP that will fail the initial config. - # This will be detected in the callback, which will execute - # the "config" primitive with the right IP address. - params = { - 'rw_mgmt_ip': '127.0.0.1' - } + deploy() + vnf_index += 1 - # self.loop.run_until_complete(n.CreateNetworkService(nsd)) - # task = asyncio.ensure_future(n.DeployCharms(vnfd, nsd=nsd, artifacts=charm_dir)) - # task = asyncio.ensure_future(n.DeployCharms(vnfd, nsd=nsd, artifacts=charm_dir)) - ns_name = "default" + # Check if this VNF has a charm + vnf_config = vnfd.get("vnf-configuration") + if vnf_config: + juju = vnf_config['juju'] + self.assertIsNotNone(juju) - ping_vnf_name = self.n2vc.FormatApplicationName(ns_name, vnfd_ping['name']) - pong_vnf_name = self.n2vc.FormatApplicationName(ns_name, vnfd_pong['name']) + charm = juju['charm'] + self.assertIsNotNone(charm) + + params['initial-config-primitive'] = vnf_config['initial-config-primitive'] + + deploy() + vnf_index += 1 - self.loop.run_until_complete(self.n2vc.DeployCharms(ns_name, ping_vnf_name, vnfd_ping, charm_dir, params, {}, self.n2vc_callback)) - self.loop.run_until_complete(self.n2vc.DeployCharms(ns_name, pong_vnf_name, vnfd_pong, charm_dir, params, {}, self.n2vc_callback)) self.loop.run_forever() # self.loop.run_until_complete(n.GetMetrics(vnfd, nsd=nsd)) + # Test actions # ExecutePrimitive(self, nsd, vnfd, vnf_member_index, primitive, callback, *callback_args, **params): @@ -359,76 +345,3 @@ class PythonTest(unittest.TestCase): # self.loop.run_until_complete(self.n2vc.logout()) finally: self.log.removeHandler(stream_handler) - - # def test_deploy_application(self): - # - # nsd = self.get_ns_descriptor(NSD_YAML) - # vnfd = self.get_vnf_descriptor(VNFD_VCA_YAML) - # if nsd and vnfd: - # # 1) Test that we're parsing the data correctly - # for vnfd_yang in vnfd.vnfd_catalog.vnfd.itervalues(): - # vnfd_rec = vnfd_yang.get() - # # Each vnfd may have a charm - # # print(vnfd) - # config = vnfd_rec.get('vnf-configuration') - # self.assertIsNotNone(config) - # - # juju = config.get('juju') - # self.assertIsNotNone(juju) - # - # charm = juju.get('charm') - # self.assertIsNotNone(charm) - # - # # 2) Exercise the data by deploying the charms - # - # # Extract parameters from the environment in order to run our test - # vca_host = os.getenv('VCA_HOST', '127.0.0.1') - # vca_port = os.getenv('VCA_PORT', 17070) - # vca_user = os.getenv('VCA_USER', 'admin') - # vca_charms = os.getenv('VCA_CHARMS', None) - # vca_secret = os.getenv('VCA_SECRET', None) - # # n = N2VC( - # # server='10.195.8.254', - # # port=17070, - # # user='admin', - # # secret='74e7aa0cc9cb294de3af294bd76b4604' - # # ) - # n = N2VC( - # server=vca_host, - # port=vca_port, - # user=vca_user, - # secret=vca_secret, - # artifacts=vca_charms, - # ) - # - # n.callback_status = self.callback_status - # - # self.loop.run_until_complete(n.CreateNetworkService(nsd)) - # self.loop.run_until_complete(n.DeployCharms(vnfd, nsd=nsd)) - # # self.loop.run_until_complete(n.GetMetrics(vnfd, nsd=nsd)) - # - # # self.loop.run_until_complete(n.RemoveCharms(nsd, vnfd)) - # self.loop.run_until_complete(n.DestroyNetworkService(nsd)) - # - # self.loop.run_until_complete(n.logout()) - # n = None - - def callback_status(self, nsd, vnfd, workload_status): - """An example callback. - - This is an example of how a client using N2VC can receive periodic - updates on the status of a vnfd - """ - # print(nsd) - # print(vnfd) - print("Workload status: {}".format(workload_status)) - - def test_deploy_multivdu_application(self): - """Deploy a multi-vdu vnf that uses multiple charms.""" - pass - - def test_remove_application(self): - pass - - def test_get_metrics(self): - pass