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)
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."""
*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
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
: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::
# 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 #
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) #
########################################################
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>': 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,
# 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>': 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:
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))
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):
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'] == "<rw_mgmt_ip>":
return config
+ def _map_primitive_parameters(self, parameters, values):
+ params = {}
+ for parameter in parameters:
+ param = str(parameter['name'])
+ if parameter['value'] == "<rw_mgmt_ip>":
+ 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 = {}
# 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
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: <rw_mgmt_ip>
+ - 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: <rw_mgmt_ip>
- 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: <rw_mgmt_ip>
- - 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: <rw_mgmt_ip>
+ - 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': '<rw_mgmt_ip>'}, {'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': '<rw_mgmt_ip>'}, {'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': '<rw_mgmt_ip>'}, {'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': '<rw_mgmt_ip>'}, {'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
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')
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
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):
# 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