Implement NSD Support part 2

This patchset adds mistral workflow, tests, sample templates
and user guide for NSD

Change-Id: If53081bc76a5436287b307538a1255c65fc71cb2
Co-Authored-By: Bharath Thiruveedula<bharath_ves@hotmail.com>
Partially-implements: blueprint nsd-support
This commit is contained in:
dharmendra 2016-12-26 16:56:48 +05:30 committed by Bharath Thiruveedula
parent d311cfb77a
commit 15c4d7ec5d
31 changed files with 1936 additions and 36 deletions

View File

@ -0,0 +1,244 @@
..
Licensed under the Apache License, Version 2.0 (the "License"); you may
not use this file except in compliance with the License. You may obtain
a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
.. _ref-nsd:
==========================================================
Orchestrating VNFs using Network Services Descriptor (NSD)
==========================================================
To enable dynamic composition of network services, NFV introduces Network
Service Descriptors (NSDs) that specify the network service to be created.
This usage guide describes lifecycle of Network service descriptors and
services.
NSD in Ocata can be used for creating multiple (related) VNFs in one shot
using a single TOSCA template. This is a first (big) step into NSD, few
follow-on enhancements like:
1) Creating VLs / neutron networks using NSD (to support inter-VNF private VL)
2) VNFFGD support in NSD.
Creating the NSD
~~~~~~~~~~~~~~~~
Once OpenStack along with Tacker has been successfully installed,
deploy a sample VNFD templates using vnf1.yaml and vnf2.yaml as mentioned in
reference section.
::
tacker vnfd-create --vnfd-file vnfd1.yaml VNFD1
tacker vnfd-create --vnfd-file vnfd2.yaml VNFD2
The following code represents sample NSD which instantiates the above VNFs
::
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
imports:
- VNFD1
- VNFD2
topology_template:
node_templates:
VNF1:
type: tosca.nodes.nfv.VNF1
requirements:
- virtualLink1: VL1
- virtualLink2: VL2
VNF2:
type: tosca.nodes.nfv.VNF2
VL1:
type: tosca.nodes.nfv.VL
properties:
network_name: net0
vendor: tacker
VL2:
type: tosca.nodes.nfv.VL
properties:
network_name: net_mgmt
vendor: tacker
In above NSD template VL1 and VL2 are substituting the virtuallinks of VNF1.
To onboard the above NSD:
**tacker nsd-create --nsd-file <nsd file> <nsd name>**
Creating the NS
~~~~~~~~~~~~~~~~
To create a NS, you must have onboarded corresponding NSD and
VNFDS(which NS is substituting)
Tacker provides the following CLI to create NS:
**tacker ns-create --nsd-id <nsd-id> <ns-name>**
Reference
~~~~~~~~~
VNF1 sample template for nsd named vnfd1.yaml:
::
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
description: Demo example
node_types:
tosca.nodes.nfv.VNF1:
requirements:
- virtualLink1:
type: tosca.nodes.nfv.VL
required: true
- virtualLink2:
type: tosca.nodes.nfv.VL
required: true
capabilities:
forwader1:
type: tosca.capabilities.nfv.Forwarder
forwader2:
type: tosca.capabilities.nfv.Forwarder
topology_template:
substitution_mappings:
node_type: tosca.nodes.nfv.VNF1
requirements:
virtualLink1: [CP11, virtualLink]
virtualLink2: [CP14, virtualLink]
capabilities:
forwarder1: [CP11, forwarder]
forwarder2: [CP14, forwarder]
node_templates:
VDU1:
type: tosca.nodes.nfv.VDU.Tacker
properties:
image: cirros-0.3.4-x86_64-uec
flavor: m1.tiny
availability_zone: nova
mgmt_driver: noop
config: |
param0: key1
param1: key2
CP11:
type: tosca.nodes.nfv.CP.Tacker
properties:
management: true
anti_spoofing_protection: false
requirements:
- virtualBinding:
node: VDU1
VDU2:
type: tosca.nodes.nfv.VDU.Tacker
properties:
image: cirros-0.3.4-x86_64-uec
flavor: m1.medium
availability_zone: nova
mgmt_driver: noop
config: |
param0: key1
param1: key2
CP13:
type: tosca.nodes.nfv.CP.Tacker
properties:
management: true
anti_spoofing_protection: false
requirements:
- virtualLink:
node: VL1
- virtualBinding:
node: VDU2
CP14:
type: tosca.nodes.nfv.CP.Tacker
properties:
management: true
anti_spoofing_protection: false
requirements:
- virtualBinding:
node: VDU2
VL1:
type: tosca.nodes.nfv.VL
properties:
network_name: net_mgmt
vendor: Tacker
VL2:
type: tosca.nodes.nfv.VL
properties:
network_name: net0
vendor: Tacker
VNF2 sample template for nsd named vnfd2.yaml:
::
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
description: Demo example
node_types:
tosca.nodes.nfv.VNF2:
capabilities:
forwarder1:
type: tosca.capabilities.nfv.Forwarder
topology_template:
substitution_mappings:
node_type: tosca.nodes.nfv.VNF2
capabilities:
forwarder1: [CP21, forwarder]
node_templates:
VDU1:
type: tosca.nodes.nfv.VDU.Tacker
properties:
image: cirros-0.3.4-x86_64-uec
flavor: m1.tiny
availability_zone: nova
mgmt_driver: noop
config: |
param0: key1
param1: key2
CP21:
type: tosca.nodes.nfv.CP.Tacker
properties:
management: true
anti_spoofing_protection: false
requirements:
- virtualLink:
node: VL1
- virtualBinding:
node: VDU1
VDU2:
type: tosca.nodes.nfv.VDU.Tacker
properties:
image: cirros-0.3.4-x86_64-uec
flavor: m1.medium
availability_zone: nova
mgmt_driver: noop
CP22:
type: tosca.nodes.nfv.CP.Tacker
properties:
management: true
anti_spoofing_protection: false
requirements:
- virtualLink:
node: VL2
- virtualBinding:
node: VDU2
VL1:
type: tosca.nodes.nfv.VL
properties:
network_name: net_mgmt
vendor: Tacker
VL2:
type: tosca.nodes.nfv.VL
properties:
network_name: net0
vendor: Tacker

View File

@ -60,6 +60,7 @@ Feature Documentation
devref/event_logging.rst
devref/vnffgd_template_description.rst
devref/vnffg_usage_guide.rst
devref/nsd_usage_guide.rst
API Documentation
=================

View File

@ -20,6 +20,7 @@ class OpenstackClients(object):
super(OpenstackClients, self).__init__()
self.keystone_plugin = keystone.Keystone()
self.heat_client = None
self.mistral_client = None
self.keystone_client = None
self.region_name = region_name
self.auth_attr = auth_attr

View File

@ -10,19 +10,23 @@
# License for the specific language governing permissions and limitations
# under the License.
import ast
import uuid
from oslo_log import log as logging
from oslo_utils import timeutils
from six import iteritems
import sqlalchemy as sa
from sqlalchemy import orm
from sqlalchemy.orm import exc as orm_exc
from tacker.db.common_services import common_services_db
from tacker.db import db_base
from tacker.db import model_base
from tacker.db import models_v1
from tacker.db import types
from tacker.extensions import nfvo
from tacker.extensions.nfvo_plugins import network_service
from tacker.plugins.common import constants
@ -94,6 +98,29 @@ class NSPluginDb(network_service.NSPluginBase, db_base.CommonDbMixin):
super(NSPluginDb, self).__init__()
self._cos_db_plg = common_services_db.CommonServicesPluginDb()
def _get_resource(self, context, model, id):
try:
return self._get_by_id(context, model, id)
except orm_exc.NoResultFound:
if issubclass(model, NSD):
raise network_service.NSDNotFound(nsd_id=id)
if issubclass(model, NS):
raise network_service.NSNotFound(ns_id=id)
else:
raise
def _get_ns_db(self, context, ns_id, current_statuses, new_status):
try:
ns_db = (
self._model_query(context, NS).
filter(NS.id == ns_id).
filter(NS.status.in_(current_statuses)).
with_lockmode('update').one())
except orm_exc.NoResultFound:
raise network_service.NSNotFound(ns_id=ns_id)
ns_db.update({'status': new_status})
return ns_db
def _make_attributes_dict(self, attributes_db):
return dict((attr.key, attr.value) for attr in attributes_db)
@ -106,6 +133,18 @@ class NSPluginDb(network_service.NSPluginBase, db_base.CommonDbMixin):
res.update((key, nsd[key]) for key in key_list)
return self._fields(res, fields)
def _make_dev_attrs_dict(self, dev_attrs_db):
return dict((arg.key, arg.value) for arg in dev_attrs_db)
def _make_ns_dict(self, ns_db, fields=None):
LOG.debug(_('ns_db %s'), ns_db)
res = {}
key_list = ('id', 'tenant_id', 'nsd_id', 'name', 'description',
'vnf_ids', 'status', 'mgmt_urls', 'error_reason',
'vim_id', 'created_at', 'updated_at')
res.update((key, ns_db[key]) for key in key_list)
return self._fields(res, fields)
def create_nsd(self, context, nsd):
vnfds = nsd['vnfds']
nsd = nsd['nsd']
@ -150,8 +189,7 @@ class NSPluginDb(network_service.NSPluginBase, db_base.CommonDbMixin):
nss_db = context.session.query(NS).filter_by(
nsd_id=nsd_id).first()
if nss_db is not None and nss_db.deleted_at is None:
raise network_service.NSDInUse(
nsd_id=nsd_id)
raise nfvo.NSDInUse(nsd_id=nsd_id)
nsd_db = self._get_resource(context, NSD,
nsd_id)
@ -179,14 +217,125 @@ class NSPluginDb(network_service.NSPluginBase, db_base.CommonDbMixin):
# reference implementation. needs to be overrided by subclass
def create_ns(self, context, ns):
return {'nsd': {}}
LOG.debug(_('ns %s'), ns)
ns = ns['ns']
tenant_id = self._get_tenant_id_for_create(context, ns)
nsd_id = ns['nsd_id']
vim_id = ns['vim_id']
name = ns.get('name')
ns_id = str(uuid.uuid4())
with context.session.begin(subtransactions=True):
nsd_db = self._get_resource(context, NSD,
nsd_id)
ns_db = NS(id=ns_id,
tenant_id=tenant_id,
name=name,
description=nsd_db.description,
vnf_ids=None,
status=constants.PENDING_CREATE,
mgmt_urls=None,
nsd_id=nsd_id,
vim_id=vim_id,
error_reason=None)
context.session.add(ns_db)
evt_details = "NS UUID assigned."
self._cos_db_plg.create_event(
context, res_id=ns_id,
res_type=constants.RES_TYPE_NS,
res_state=constants.PENDING_CREATE,
evt_type=constants.RES_EVT_CREATE,
tstamp=ns_db[constants.RES_EVT_CREATED_FLD],
details=evt_details)
return self._make_ns_dict(ns_db)
def create_ns_post(self, context, ns_id, mistral_obj,
vnfd_dict, error_reason):
LOG.debug(_('ns ID %s'), ns_id)
output = ast.literal_eval(mistral_obj.output)
mgmt_urls = dict()
vnf_ids = dict()
if len(output) > 0:
for vnfd_name, vnfd_val in iteritems(vnfd_dict):
for instance in vnfd_val['instances']:
mgmt_urls[instance] = ast.literal_eval(
output['mgmt_url_' + instance].strip())
vnf_ids[instance] = output['vnf_id_' + instance]
vnf_ids = str(vnf_ids)
mgmt_urls = str(mgmt_urls)
if not vnf_ids:
vnf_ids = None
if not mgmt_urls:
mgmt_urls = None
status = constants.ACTIVE if mistral_obj.state == 'SUCCESS' \
else constants.ERROR
with context.session.begin(subtransactions=True):
ns_db = self._get_resource(context, NS,
ns_id)
ns_db.update({'vnf_ids': vnf_ids})
ns_db.update({'mgmt_urls': mgmt_urls})
ns_db.update({'status': status})
ns_db.update({'error_reason': error_reason})
ns_db.update({'updated_at': timeutils.utcnow()})
ns_dict = self._make_ns_dict(ns_db)
self._cos_db_plg.create_event(
context, res_id=ns_dict['id'],
res_type=constants.RES_TYPE_NS,
res_state=constants.RES_EVT_NA_STATE,
evt_type=constants.RES_EVT_UPDATE,
tstamp=ns_dict[constants.RES_EVT_UPDATED_FLD])
return ns_dict
# reference implementation. needs to be overrided by subclass
def delete_ns(self, context, ns_id, soft_delete=True):
pass
def delete_ns(self, context, ns_id):
with context.session.begin(subtransactions=True):
ns_db = self._get_ns_db(
context, ns_id, _ACTIVE_UPDATE_ERROR_DEAD,
constants.PENDING_DELETE)
deleted_ns_db = self._make_ns_dict(ns_db)
self._cos_db_plg.create_event(
context, res_id=ns_id,
res_type=constants.RES_TYPE_NS,
res_state=deleted_ns_db['status'],
evt_type=constants.RES_EVT_DELETE,
tstamp=timeutils.utcnow(), details="NS delete initiated")
return deleted_ns_db
def delete_ns_post(self, context, ns_id, mistral_obj,
error_reason, soft_delete=True):
with context.session.begin(subtransactions=True):
query = (
self._model_query(context, NS).
filter(NS.id == ns_id).
filter(NS.status == constants.PENDING_DELETE))
if mistral_obj.state == 'ERROR':
query.update({'status': constants.ERROR})
self._cos_db_plg.create_event(
context, res_id=ns_id,
res_type=constants.RES_TYPE_NS,
res_state=constants.ERROR,
evt_type=constants.RES_EVT_DELETE,
tstamp=timeutils.utcnow(),
details="NS Delete ERROR")
else:
if soft_delete:
deleted_time_stamp = timeutils.utcnow()
query.update({'deleted_at': deleted_time_stamp})
self._cos_db_plg.create_event(
context, res_id=ns_id,
res_type=constants.RES_TYPE_NS,
res_state=constants.PENDING_DELETE,
evt_type=constants.RES_EVT_DELETE,
tstamp=deleted_time_stamp,
details="ns Delete Complete")
else:
query.delete()
def get_ns(self, context, ns_id, fields=None):
pass
ns_db = self._get_resource(context, NS, ns_id)
return self._make_ns_dict(ns_db)
def get_nss(self, context, filters=None, fields=None):
pass
return self._get_collection(context, NS,
self._make_ns_dict,
filters=filters, fields=fields)

View File

@ -38,6 +38,14 @@ class InvalidModelException(exceptions.TackerException):
message = _("Specified model is invalid, only Event model supported")
class InputValuesMissing(exceptions.InvalidInput):
message = _("Parameter input values missing for the key '%(key)s'")
class ParamYAMLInputMissing(exceptions.InvalidInput):
message = _("Parameter YAML input missing")
RESOURCE_ATTRIBUTE_MAP = {
'events': {

View File

@ -212,6 +212,10 @@ class ClassifierNotFoundException(exceptions.NotFound):
class NSDInUse(exceptions.InUse):
message = _('NSD %(nsd_id)s is still in use')
class NSInUse(exceptions.InUse):
message = _('NS %(ns_id)s is still in use')
RESOURCE_ATTRIBUTE_MAP = {
'vims': {

View File

@ -13,6 +13,7 @@
import abc
import six
from tacker.common import exceptions
from tacker.services import service_base
@ -50,3 +51,11 @@ class NSPluginBase(service_base.NFVPluginBase):
@abc.abstractmethod
def delete_ns(self, context, ns_id):
pass
class NSDNotFound(exceptions.NotFound):
message = _('NSD %(nsd_id)s could not be found')
class NSNotFound(exceptions.NotFound):
message = _('NS %(ns_id)s could not be found')

View File

@ -95,14 +95,6 @@ class HeatTranslatorFailed(exceptions.InvalidInput):
message = _("heat-translator failed: - %(error_msg_details)s")
class InputValuesMissing(exceptions.InvalidInput):
message = _("Parameter input values missing for the key '%(key)s'")
class ParamYAMLInputMissing(exceptions.InvalidInput):
message = _("Parameter YAML input missing")
class HeatClientException(exceptions.TackerException):
message = _("%(msg)s")

View File

@ -16,12 +16,14 @@
import os
import six
import yaml
from keystoneauth1 import exceptions
from keystoneauth1 import identity
from keystoneauth1.identity import v2
from keystoneauth1.identity import v3
from keystoneauth1 import session
from mistralclient.api import client as mistral_client
from neutronclient.common import exceptions as nc_exceptions
from neutronclient.v2_0 import client as neutron_client
from oslo_config import cfg
@ -33,6 +35,7 @@ from tacker.common import log
from tacker.extensions import nfvo
from tacker.nfvo.drivers.vim import abstract_vim_driver
from tacker.nfvo.drivers.vnffg import abstract_vnffg_driver
from tacker.nfvo.drivers.workflow import workflow_generator
from tacker.vnfm import keystone
@ -453,6 +456,77 @@ class OpenStack_Driver(abstract_vim_driver.VimAbstractDriver,
neutronclient_ = NeutronClient(auth_attr)
neutronclient_.flow_classifier_delete(fc_id)
def prepare_and_create_workflow(self, resource, action, vim_auth,
kwargs, auth_token=None):
if not auth_token:
LOG.warning(_("auth token required to create mistral workflows"))
raise EnvironmentError('auth token required for'
' mistral workflow driver')
mistral_client = MistralClient(
self.keystone.initialize_client('2', **vim_auth),
auth_token).get_client()
wg = workflow_generator.WorkflowGenerator(resource, action)
wg.task(**kwargs)
definition_yaml = yaml.dump(wg.definition)
workflow = mistral_client.workflows.create(definition_yaml)
return {'id': workflow[0].id, 'input': wg.get_input_dict()}
def execute_workflow(self, workflow, vim_auth, auth_token=None):
if not auth_token:
LOG.warning(_("auth token required to create mistral workflows"))
raise EnvironmentError('auth token required for'
' mistral workflow driver')
mistral_client = MistralClient(
self.keystone.initialize_client('2', **vim_auth),
auth_token).get_client()
return mistral_client.executions.create(
workflow_identifier=workflow['id'],
workflow_input=workflow['input'],
wf_params={})
def get_execution(self, execution_id, vim_auth, auth_token=None):
if not auth_token:
LOG.warning(_("auth token required to create mistral workflows"))
raise EnvironmentError('auth token required for'
' mistral workflow driver')
mistral_client = MistralClient(
self.keystone.initialize_client('2', **vim_auth),
auth_token).get_client()
return mistral_client.executions.get(execution_id)
def delete_execution(self, execution_id, vim_auth, auth_token=None):
if not auth_token:
LOG.warning(_("auth token required to create mistral workflows"))
raise EnvironmentError('auth token required for'
' mistral workflow driver')
mistral_client = MistralClient(
self.keystone.initialize_client('2', **vim_auth),
auth_token).get_client()
return mistral_client.executions.delete(execution_id)
def delete_workflow(self, workflow_id, vim_auth, auth_token=None):
if not auth_token:
LOG.warning(_("auth token required to create mistral workflows"))
raise EnvironmentError('auth token required for'
' mistral workflow driver')
mistral_client = MistralClient(
self.keystone.initialize_client('2', **vim_auth),
auth_token).get_client()
return mistral_client.workflows.delete(workflow_id)
class MistralClient(object):
"""Mistral Client class for NSD"""
def __init__(self, keystone, auth_token):
endpoint = keystone.session.get_endpoint(
service_type='workflowv2', region_name=None)
self.client = mistral_client.client(auth_token=auth_token,
mistral_url=endpoint)
def get_client(self):
return self.client
class NeutronClient(object):
"""Neutron Client class for networking-sfc driver"""

View File

View File

@ -0,0 +1,53 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import yaml
from oslo_log import log as logging
from tacker.nfvo.drivers.workflow import workflow_generator
LOG = logging.getLogger(__name__)
FREQUENCY = 10
SLEEP = 5
class MistralClient(object):
def __init__(self, context, client, resource, action):
self.context = context
self.client = client
self.wg = workflow_generator.WorkflowGenerator(resource, action)
def prepare_workflow(self, **kwargs):
self.wg.task(**kwargs)
def create_workflow(self):
definition_yaml = yaml.dump(self.wg.definition)
wf = self.client.workflows.create(definition_yaml)
wf_id = wf[0].id
return wf_id
def delete_workflow(self, wf_id):
self.client.workflows.delete(wf_id)
def execute_workflow(self, wf_id):
wf_ex = self.client.executions.create(
workflow_identifier=wf_id,
workflow_input=self.wg.input_dict,
wf_params={})
return wf_ex
def get_execution_state(self, ex_id):
return self.client.executions.get(ex_id).state
def delete_execution(self, ex_id):
self.client.executions.delete(ex_id)

View File

@ -0,0 +1,27 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
class Workflow(object):
def __init__(self, wf_name, wf_type, version='2.0'):
self._wf_name = wf_name
self._wf_type = wf_type
self._version = '2.0'
def get_name(self):
return self.wf_name
def get_type(self):
return self.wf_type
def get_version(self):
self._version

View File

@ -0,0 +1,190 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import ast
import uuid
from six import iteritems
OUTPUT = {
'create_vnf': ['vnf_id', 'vim_id', 'mgmt_url', 'status']
}
class WorkflowGenerator(object):
def __init__(self, resource, action):
self.resource = resource
self.action = action
self.wf_name = self.action + '_' + self.resource
self.wf_identifier = 'std.' + self.wf_name + str(uuid.uuid4())
self.task = getattr(self, self.wf_name)
self.input_dict = dict()
self._build_basic_workflow()
def _build_basic_workflow(self):
self.definition = {
'version': '2.0',
self.wf_identifier: {
'type': 'direct',
'input': [self.resource]
}
}
def _get_vim_id(self):
pass
def _get_vnfd_id(self):
pass
def _get_vnf_name(self):
pass
def _get_attr(self):
pass
def _get_description(self):
pass
def _add_create_vnf_tasks(self, ns):
vnfds = ns['vnfd_details']
task_dict = dict()
for vnfd_name, vnfd_info in iteritems(vnfds):
nodes = vnfd_info['instances']
for node in nodes:
task = self.wf_name + '_' + node
task_dict[task] = {
'action': 'tacker.create_vnf body=<% $.vnf.{0} '
'%>'.format(node),
'input': {'body': '<% $.vnf.{0} %>'.format(node)},
'publish': {
'vnf_id_' + node: '<% task({0}).result.vnf.id '
'%>'.format(task),
'vim_id_' + node: '<% task({0}).result.vnf.vim_id'
' %>'.format(task),
'mgmt_url_' + node: '<% task({0}).result.vnf.mgmt_url'
' %>'.format(task),
'status_' + node: '<% task({0}).result.vnf.status'
' %>'.format(task),
},
'on-success': ['wait_vnf_active_%s' % node]
}
return task_dict
def _add_wait_vnf_tasks(self, ns):
vnfds = ns['vnfd_details']
task_dict = dict()
for vnfd_name, vnfd_info in iteritems(vnfds):
nodes = vnfd_info['instances']
for node in nodes:
task = 'wait_vnf_active_%s' % node
task_dict[task] = {
'action': 'tacker.show_vnf vnf=<% $.vnf_id_{0} '
'%>'.format(node),
'retry': {
'count': 10,
'delay': 10,
'break-on': '<% $.status_{0} = "ACTIVE" '
'%>'.format(node),
'break-on': '<% $.status_{0} = "ERROR"'
' %>'.format(node),
'continue-on': '<% $.status_{0} = "PENDING_CREATE" '
'%>'.format(node),
},
'publish': {
'mgmt_url_' + node: ' <% task({0}).result.vnf.'
'mgmt_url %>'.format(task),
'status_' + node: '<% task({0}).result.vnf.status'
' %>'.format(task),
},
'on-success': [
{'delete_vnf_' + node: '<% $.status_{0}='
'"ERROR" %>'.format(node)}
]
}
return task_dict
def _add_delete_vnf_tasks(self, ns):
vnfds = ns['vnfd_details']
task_dict = dict()
for vnfd_name, vnfd_info in iteritems(vnfds):
nodes = vnfd_info['instances']
for node in nodes:
task = 'delete_vnf_%s' % node
task_dict[task] = {
'action': 'tacker.delete_vnf vnf=<% $.vnf_id_{0}'
'%>'.format(node),
}
return task_dict
def _build_output_dict(self, ns):
vnfds = ns['vnfd_details']
task_dict = dict()
for vnfd_name, vnfd_info in iteritems(vnfds):
nodes = vnfd_info['instances']
for node in nodes:
for op_name in OUTPUT[self.wf_name]:
task_dict[op_name + '_' + node] = \
'<% $.{0}_{1} %>'.format(op_name, node)
return task_dict
def get_input_dict(self):
return self.input_dict
def build_input(self, ns, params):
vnfds = ns['vnfd_details']
id = str(uuid.uuid4())
self.input_dict = {'vnf': {}}
for vnfd_name, vnfd_info in iteritems(vnfds):
nodes = vnfd_info['instances']
for node in nodes:
self.input_dict['vnf'][node] = dict()
self.input_dict['vnf'][node]['vnf'] = {
'attributes': {},
'vim_id': ns['ns'].get('vim_id', ''),
'vnfd_id': vnfd_info['id'],
'name': 'create_vnf_%s_%s' % (vnfd_info['id'],
id)
}
if params.get(vnfd_name):
self.input_dict['vnf'][node]['vnf']['attributes'] = {
'param_values': params.get(vnfd_name)
}
def create_vnf(self, **kwargs):
ns = kwargs.get('ns')
params = kwargs.get('params')
# TODO(anyone): Keep this statements in a loop and
# remove in all the methods.
self.definition[self.wf_identifier]['tasks'] = dict()
self.definition[self.wf_identifier]['tasks'].update(
self._add_create_vnf_tasks(ns))
self.definition[self.wf_identifier]['tasks'].update(
self._add_wait_vnf_tasks(ns))
self.definition[self.wf_identifier]['tasks'].update(
self._add_delete_vnf_tasks(ns))
self.definition[self.wf_identifier]['output'] = \
self._build_output_dict(ns)
self.build_input(ns, params)
def delete_vnf(self, ns):
ns_dict = {'vnfd_details': {}}
vnf_ids = ast.literal_eval(ns['vnf_ids'])
self.definition[self.wf_identifier]['input'] = []
for vnf in vnf_ids.keys():
vnf_key = 'vnf_id_' + vnf
self.definition[self.wf_identifier]['input'].append(vnf_key)
self.input_dict[vnf_key] = vnf_ids[vnf]
ns_dict['vnfd_details'][vnf] = {'instances': [vnf]}
self.definition[self.wf_identifier]['tasks'] = dict()
self.definition[self.wf_identifier]['tasks'].update(
self._add_delete_vnf_tasks(ns_dict))

View File

@ -21,10 +21,12 @@ import uuid
import yaml
from cryptography import fernet
import eventlet
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import excutils
from oslo_utils import strutils
from six import iteritems
from tempfile import mkstemp
from toscaparser.tosca_template import ToscaTemplate
@ -37,15 +39,19 @@ from tacker import context as t_context
from tacker.db.nfvo import nfvo_db
from tacker.db.nfvo import ns_db
from tacker.db.nfvo import vnffg_db
from tacker.extensions import common_services as cs
from tacker.extensions import nfvo
from tacker import manager
from tacker.plugins.common import constants
from tacker.vnfm.tosca import utils as toscautils
from tacker.vnfm import vim_client
from toscaparser import tosca_template
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
MISTRAL_RETRIES = 30
MISTRAL_RETRY_WAIT = 6
def config_opts():
@ -76,10 +82,12 @@ class NfvoPlugin(nfvo_db.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin,
def __init__(self):
super(NfvoPlugin, self).__init__()
self._pool = eventlet.GreenPool()
self._vim_drivers = driver_manager.DriverManager(
'tacker.nfvo.vim.drivers',
cfg.CONF.nfvo_vim.vim_drivers)
self._created_vims = dict()
self.vim_client = vim_client.VimClient()
context = t_context.get_admin_context()
vims = self.get_vims(context)
for vim in vims:
@ -93,6 +101,9 @@ class NfvoPlugin(nfvo_db.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin,
for created_vim in self._created_vims.values():
self.monitor_vim(created_vim)
def spawn_n(self, function, *args, **kwargs):
self._pool.spawn_n(function, *args, **kwargs)
@log.log
def create_vim(self, context, vim):
LOG.debug(_('Create vim called with parameters %s'),
@ -480,3 +491,231 @@ class NfvoPlugin(nfvo_db.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin,
'template_name', '')
LOG.debug(_('nsd %s'), nsd)
def _get_vnfd_id(self, vnfd_name, onboarded_vnfds):
for vnfd in onboarded_vnfds:
if vnfd_name == vnfd['name']:
return vnfd['id']
@log.log
def create_ns(self, context, ns):
"""Create NS and corresponding VNFs.
:param ns ns dict which contains nsd_id and attributes
This method has 3 steps:
step-1: substitute all get_input params to its corresponding values
step-2: Build params dict for substitution mappings case through which
VNFs will actually substitute their requirements.
step-3: Create mistral workflow and execute the workflow
"""
nsd = self.get_nsd(context, ns['ns']['nsd_id'])
nsd_dict = yaml.load(nsd['attributes']['nsd'])
vnfm_plugin = manager.TackerManager.get_service_plugins()['VNFM']
onboarded_vnfds = vnfm_plugin.get_vnfds(context, [])
region_name = ns.setdefault('placement_attr', {}).get(
'region_name', None)
vim_res = self.vim_client.get_vim(context, ns['ns']['vim_id'],
region_name)
driver_type = vim_res['vim_type']
if not ns['ns']['vim_id']:
ns['ns']['vim_id'] = vim_res['vim_id']
if self._get_by_name(context, ns_db.NS, ns['ns']['name']):
raise exceptions.DuplicateResourceName(resource='NS',
name=ns['ns']['name'])
# Step-1
param_values = ns['ns']['attributes'].get('param_values', {})
if 'get_input' in str(nsd_dict):
self._process_parameterized_input(ns['ns']['attributes'],
nsd_dict)
# Step-2
vnfds = nsd['vnfds']
# vnfd_dict is used while generating workflow
vnfd_dict = dict()
for node_name, node_val in \
iteritems(nsd_dict['topology_template']['node_templates']):
if node_val.get('type') not in vnfds.keys():
continue
vnfd_name = vnfds[node_val.get('type')]
if not vnfd_dict.get(vnfd_name):
vnfd_dict[vnfd_name] = {
'id': self._get_vnfd_id(vnfd_name, onboarded_vnfds),
'instances': [node_name]
}
else:
vnfd_dict[vnfd_name]['instances'].append(node_name)
if not node_val.get('requirements'):
continue
if not param_values.get(vnfd_name):
param_values[vnfd_name] = {}
param_values[vnfd_name]['substitution_mappings'] = dict()
req_dict = dict()
requirements = node_val.get('requirements')
for requirement in requirements:
req_name = list(requirement.keys())[0]
req_val = list(requirement.values())[0]
res_name = req_val + ns['ns']['nsd_id'][:11]
req_dict[req_name] = res_name
if req_val in nsd_dict['topology_template']['node_templates']:
param_values[vnfd_name]['substitution_mappings'][
res_name] = nsd_dict['topology_template'][
'node_templates'][req_val]
param_values[vnfd_name]['substitution_mappings'][
'requirements'] = req_dict
ns['vnfd_details'] = vnfd_dict
# Step-3
kwargs = {'ns': ns, 'params': param_values}
workflow = self._vim_drivers.invoke(driver_type,
'prepare_and_create_workflow',
resource='vnf',
action='create',
vim_auth=vim_res['vim_auth'],
auth_token=context.auth_token,
kwargs=kwargs)
try:
mistral_execution = self._vim_drivers.invoke(
driver_type,
'execute_workflow',
workflow=workflow,
vim_auth=vim_res['vim_auth'],
auth_token=context.auth_token)
except Exception as ex:
raise ex
ns_dict = super(NfvoPlugin, self).create_ns(context, ns)
def _create_ns_wait(self_obj, ns_id, execution_id):
exec_state = "RUNNING"
mistral_retries = MISTRAL_RETRIES
while exec_state == "RUNNING" and mistral_retries > 0:
time.sleep(MISTRAL_RETRY_WAIT)
exec_state = self._vim_drivers.invoke(
driver_type,
'get_execution',
execution_id=execution_id,
vim_auth=vim_res['vim_auth'],
auth_token=context.auth_token).state
LOG.debug(_('status: %s'), exec_state)
if exec_state == 'SUCCESS' or exec_state == 'ERROR':
break
mistral_retries = mistral_retries - 1
error_reason = None
if mistral_retries == 0 and exec_state == 'RUNNING':
error_reason = _("NS creation is not completed within"
" {wait} seconds as creation of mistral"
" exection {mistral} is not completed").format(
wait=MISTRAL_RETRIES * MISTRAL_RETRY_WAIT,
mistral=execution_id)
exec_obj = self._vim_drivers.invoke(driver_type,
'get_execution',
execution_id=execution_id,
vim_auth=vim_res['vim_auth'],
auth_token=context.auth_token)
self._vim_drivers.invoke(driver_type,
'delete_execution',
execution_id=execution_id,
vim_auth=vim_res['vim_auth'],
auth_token=context.auth_token)
self._vim_drivers.invoke(driver_type,
'delete_workflow',
workflow_id=workflow['id'],
vim_auth=vim_res['vim_auth'],
auth_token=context.auth_token)
super(NfvoPlugin, self).create_ns_post(context, ns_id, exec_obj,
vnfd_dict, error_reason)
self.spawn_n(_create_ns_wait, self, ns_dict['id'],
mistral_execution.id)
return ns_dict
@log.log
def _update_params(self, original, paramvalues):
for key, value in iteritems(original):
if not isinstance(value, dict) or 'get_input' not in str(value):
pass
elif isinstance(value, dict):
if 'get_input' in value:
if value['get_input'] in paramvalues:
original[key] = paramvalues[value['get_input']]
else:
LOG.debug('Key missing Value: %s', key)
raise cs.InputValuesMissing(key=key)
else:
self._update_params(value, paramvalues)
@log.log
def _process_parameterized_input(self, attrs, nsd_dict):
param_vattrs_dict = attrs.pop('param_values', None)
if param_vattrs_dict:
for node in \
nsd_dict['topology_template']['node_templates'].values():
if 'get_input' in str(node):
self._update_params(node, param_vattrs_dict['nsd'])
else:
raise cs.ParamYAMLInputMissing()
@log.log
def delete_ns(self, context, ns_id):
ns = super(NfvoPlugin, self).get_ns(context, ns_id)
vim_res = self.vim_client.get_vim(context, ns['vim_id'])
driver_type = vim_res['vim_type']
workflow = self._vim_drivers.invoke(driver_type,
'prepare_and_create_workflow',
resource='vnf',
action='delete',
vim_auth=vim_res['vim_auth'],
auth_token=context.auth_token,
kwargs={'ns': ns})
try:
mistral_execution = self._vim_drivers.invoke(
driver_type,
'execute_workflow',
workflow=workflow,
vim_auth=vim_res['vim_auth'],
auth_token=context.auth_token)
except Exception as ex:
raise ex
super(NfvoPlugin, self).delete_ns(context, ns_id)
def _delete_ns_wait(ns_id, execution_id):
exec_state = "RUNNING"
mistral_retries = MISTRAL_RETRIES
while exec_state == "RUNNING" and mistral_retries > 0:
time.sleep(MISTRAL_RETRY_WAIT)
exec_state = self._vim_drivers.invoke(
driver_type,
'get_execution',
execution_id=execution_id,
vim_auth=vim_res['vim_auth'],
auth_token=context.auth_token).state
LOG.debug(_('status: %s'), exec_state)
if exec_state == 'SUCCESS' or exec_state == 'ERROR':
break
mistral_retries -= 1
error_reason = None
if mistral_retries == 0 and exec_state == 'RUNNING':
error_reason = _("NS deletion is not completed within"
" {wait} seconds as deletion of mistral"
" exection {mistral} is not completed").format(
wait=MISTRAL_RETRIES * MISTRAL_RETRY_WAIT,
mistral=execution_id)
exec_obj = self._vim_drivers.invoke(driver_type,
'get_execution',
execution_id=execution_id,
vim_auth=vim_res['vim_auth'],
auth_token=context.auth_token)
self._vim_drivers.invoke(driver_type,
'delete_execution',
execution_id=execution_id,
vim_auth=vim_res['vim_auth'],
auth_token=context.auth_token)
self._vim_drivers.invoke(driver_type,
'delete_workflow',
workflow_id=workflow['id'],
vim_auth=vim_res['vim_auth'],
auth_token=context.auth_token)
super(NfvoPlugin, self).delete_ns_post(context, ns_id, exec_obj,
error_reason)
self.spawn_n(_delete_ns_wait, ns['id'], mistral_execution.id)
return ns['id']

View File

@ -57,6 +57,7 @@ DEFAULT_ALARM_ACTIONS = ['respawn', 'log', 'log_and_kill', 'notify']
RES_TYPE_VNFD = "vnfd"
RES_TYPE_NSD = "nsd"
RES_TYPE_NS = "ns"
RES_TYPE_VNF = "vnf"
RES_TYPE_VIM = "vim"

View File

@ -18,3 +18,5 @@ VNF_CIRROS_DEAD_TIMEOUT = 250
ACTIVE_SLEEP_TIME = 3
DEAD_SLEEP_TIME = 1
SCALE_WINDOW_SLEEP_TIME = 120
NS_CREATE_TIMEOUT = 400
NS_DELETE_TIMEOUT = 300

View File

@ -0,0 +1,37 @@
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
imports:
- test-ns-vnfd1
- test-ns-vnfd2
topology_template:
inputs:
vl1_name:
type: string
description: name of VL1 virtuallink
default: net_mgmt
vl2_name:
type: string
description: name of VL2 virtuallink
default: net0
node_templates:
VNF1:
type: tosca.nodes.nfv.VNF1
requirements:
- virtualLink1: VL1
- virtualLink2: VL2
VNF2:
type: tosca.nodes.nfv.VNF2
VL1:
type: tosca.nodes.nfv.VL
properties:
network_name: {get_input: vl1_name}
vendor: tacker
VL2:
type: tosca.nodes.nfv.VL
properties:
network_name: {get_input: vl2_name}
vendor: tacker

View File

@ -0,0 +1,98 @@
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
description: Demo example
node_types:
tosca.nodes.nfv.VNF1:
requirements:
- virtualLink1:
type: tosca.nodes.nfv.VL
required: true
- virtualLink2:
type: tosca.nodes.nfv.VL
required: true
capabilities:
forwader1:
type: tosca.capabilities.nfv.Forwarder
forwader2:
type: tosca.capabilities.nfv.Forwarder
topology_template:
substitution_mappings:
node_type: tosca.nodes.nfv.VNF1
requirements:
virtualLink1: [CP11, virtualLink]
virtualLink2: [CP14, virtualLink]
capabilities:
forwarder1: [CP11, forwarder]
forwarder2: [CP14, forwarder]
node_templates:
VDU1:
type: tosca.nodes.nfv.VDU.Tacker
properties:
image: cirros-0.3.4-x86_64-uec
flavor: m1.tiny
availability_zone: nova
mgmt_driver: noop
config: |
param0: key1
param1: key2
CP11:
type: tosca.nodes.nfv.CP.Tacker
properties:
management: true
anti_spoofing_protection: false
requirements:
- virtualBinding:
node: VDU1
CP12:
type: tosca.nodes.nfv.CP.Tacker
properties:
anti_spoofing_protection: false
requirements:
- virtualLink:
node: VL2
- virtualBinding:
node: VDU1
VDU2:
type: tosca.nodes.nfv.VDU.Tacker
properties:
image: cirros-0.3.4-x86_64-uec
flavor: m1.medium
availability_zone: nova
mgmt_driver: noop
config: |
param0: key1
param1: key2
CP13:
type: tosca.nodes.nfv.CP.Tacker
properties:
management: true
requirements:
- virtualLink:
node: VL1
- virtualBinding:
node: VDU2
CP14:
type: tosca.nodes.nfv.CP.Tacker
requirements:
- virtualBinding:
node: VDU2
VL1:
type: tosca.nodes.nfv.VL
properties:
network_name: net_mgmt
vendor: Tacker
VL2:
type: tosca.nodes.nfv.VL
properties:
network_name: net0
vendor: Tacker

View File

@ -0,0 +1,68 @@
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
description: Demo example
node_types:
tosca.nodes.nfv.VNF2:
capabilities:
forwarder1:
type: tosca.capabilities.nfv.Forwarder
topology_template:
substitution_mappings:
node_type: tosca.nodes.nfv.VNF2
capabilities:
forwarder1: [CP21, forwarder]
node_templates:
VDU1:
type: tosca.nodes.nfv.VDU.Tacker
properties:
image: cirros-0.3.4-x86_64-uec
flavor: m1.tiny
availability_zone: nova
mgmt_driver: noop
config: |
param0: key1
param1: key2
CP21:
type: tosca.nodes.nfv.CP.Tacker
properties:
management: true
anti_spoofing_protection: false
requirements:
- virtualLink:
node: VL1
- virtualBinding:
node: VDU1
VDU2:
type: tosca.nodes.nfv.VDU.Tacker
properties:
image: cirros-0.3.4-x86_64-uec
flavor: m1.medium
availability_zone: nova
mgmt_driver: noop
config: |
param0: key1
param1: key2
CP22:
type: tosca.nodes.nfv.CP.Tacker
requirements:
- virtualLink:
node: VL2
- virtualBinding:
node: VDU2
VL1:
type: tosca.nodes.nfv.VL
properties:
network_name: net_mgmt
vendor: Tacker
VL2:
type: tosca.nodes.nfv.VL
properties:
network_name: net0
vendor: Tacker

View File

@ -0,0 +1,98 @@
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
description: Demo example
node_types:
tosca.nodes.nfv.VNF1:
requirements:
- virtualLink1:
type: tosca.nodes.nfv.VL
required: true
- virtualLink2:
type: tosca.nodes.nfv.VL
required: true
capabilities:
forwader1:
type: tosca.capabilities.nfv.Forwarder
forwader2:
type: tosca.capabilities.nfv.Forwarder
topology_template:
substitution_mappings:
node_type: tosca.nodes.nfv.VNF1
requirements:
virtualLink1: [CP11, virtualLink]
virtualLink2: [CP14, virtualLink]
capabilities:
forwarder1: [CP11, forwarder]
forwarder2: [CP14, forwarder]
node_templates:
VDU1:
type: tosca.nodes.nfv.VDU.Tacker
properties:
image: cirros-0.3.4-x86_64-uec
flavor: m1.tiny
availability_zone: nova
mgmt_driver: noop
config: |
param0: key1
param1: key2
CP11:
type: tosca.nodes.nfv.CP.Tacker
properties:
management: true
anti_spoofing_protection: false
requirements:
- virtualBinding:
node: VDU1
CP12:
type: tosca.nodes.nfv.CP.Tacker
properties:
anti_spoofing_protection: false
requirements:
- virtualLink:
node: VL2
- virtualBinding:
node: VDU1
VDU2:
type: tosca.nodes.nfv.VDU.Tacker
properties:
image: cirros-0.3.4-x86_64-uec
flavor: m1.medium
availability_zone: nova
mgmt_driver: noop
config: |
param0: key1
param1: key2
CP13:
type: tosca.nodes.nfv.CP.Tacker
properties:
management: true
requirements:
- virtualLink:
node: VL1
- virtualBinding:
node: VDU2
CP14:
type: tosca.nodes.nfv.CP.Tacker
requirements:
- virtualBinding:
node: VDU2
VL1:
type: tosca.nodes.nfv.VL
properties:
network_name: net_mgmt
vendor: Tacker
VL2:
type: tosca.nodes.nfv.VL
properties:
network_name: net0
vendor: Tacker

View File

@ -0,0 +1,68 @@
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
description: Demo example
node_types:
tosca.nodes.nfv.VNF2:
capabilities:
forwarder1:
type: tosca.capabilities.nfv.Forwarder
topology_template:
substitution_mappings:
node_type: tosca.nodes.nfv.VNF2
capabilities:
forwarder1: [CP21, forwarder]
node_templates:
VDU1:
type: tosca.nodes.nfv.VDU.Tacker
properties:
image: cirros-0.3.4-x86_64-uec
flavor: m1.tiny
availability_zone: nova
mgmt_driver: noop
config: |
param0: key1
param1: key2
CP21:
type: tosca.nodes.nfv.CP.Tacker
properties:
management: true
anti_spoofing_protection: false
requirements:
- virtualLink:
node: VL1
- virtualBinding:
node: VDU1
VDU2:
type: tosca.nodes.nfv.VDU.Tacker
properties:
image: cirros-0.3.4-x86_64-uec
flavor: m1.medium
availability_zone: nova
mgmt_driver: noop
config: |
param0: key1
param1: key2
CP22:
type: tosca.nodes.nfv.CP.Tacker
requirements:
- virtualLink:
node: VL2
- virtualBinding:
node: VDU2
VL1:
type: tosca.nodes.nfv.VL
properties:
network_name: net_mgmt
vendor: Tacker
VL2:
type: tosca.nodes.nfv.VL
properties:
network_name: net0
vendor: Tacker

View File

@ -0,0 +1,37 @@
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
imports:
- test-nsd-vnfd1
- test-nsd-vnfd2
topology_template:
inputs:
vl1_name:
type: string
description: name of VL1 virtuallink
default: net_mgmt
vl2_name:
type: string
description: name of VL2 virtuallink
default: net0
node_templates:
VNF1:
type: tosca.nodes.nfv.VNF1
requirements:
- virtualLink1: VL1
- virtualLink2: VL2
VNF2:
type: tosca.nodes.nfv.VNF2
VL1:
type: tosca.nodes.nfv.VL
properties:
network_name: {get_input: vl1_name}
vendor: tacker
VL2:
type: tosca.nodes.nfv.VL
properties:
network_name: {get_input: vl2_name}
vendor: tacker

View File

@ -0,0 +1,150 @@
# Copyright 2016 Brocade Communications System, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import yaml
from oslo_config import cfg
from tackerclient.common import exceptions
from tacker.plugins.common import constants as evt_constants
from tacker.tests import constants
from tacker.tests.functional import base
from tacker.tests.utils import read_file
import time
CONF = cfg.CONF
class NsdTestCreate(base.BaseTackerTest):
def _test_create_tosca_vnfd(self, tosca_vnfd_file, vnfd_name):
input_yaml = read_file(tosca_vnfd_file)
tosca_dict = yaml.safe_load(input_yaml)
tosca_arg = {'vnfd': {'name': vnfd_name,
'attributes': {'vnfd': tosca_dict}}}
vnfd_instance = self.client.create_vnfd(body=tosca_arg)
self.assertEqual(vnfd_instance['vnfd']['name'], vnfd_name)
self.assertIsNotNone(vnfd_instance)
vnfds = self.client.list_vnfds().get('vnfds')
self.assertIsNotNone(vnfds, "List of vnfds are Empty after Creation")
return vnfd_instance['vnfd']['id']
def _test_create_nsd(self, tosca_nsd_file, nsd_name):
input_yaml = read_file(tosca_nsd_file)
tosca_dict = yaml.safe_load(input_yaml)
tosca_arg = {'nsd': {'name': nsd_name,
'attributes': {'nsd': tosca_dict}}}
nsd_instance = self.client.create_nsd(body=tosca_arg)
self.assertIsNotNone(nsd_instance)
return nsd_instance['nsd']['id']
def _test_delete_nsd(self, nsd_id):
try:
self.client.delete_nsd(nsd_id)
except Exception:
assert False, "nsd Delete failed"
def _test_delete_vnfd(self, vnfd_id, timeout=constants.NS_DELETE_TIMEOUT):
start_time = int(time.time())
while True:
try:
self.client.delete_vnfd(vnfd_id)
except exceptions.Conflict:
time.sleep(2)
except Exception:
assert False, "vnfd Delete failed"
else:
break
if (int(time.time()) - start_time) > timeout:
assert False, "vnfd still in use"
self.verify_vnfd_events(vnfd_id, evt_constants.RES_EVT_DELETE,
evt_constants.RES_EVT_NA_STATE)
def _wait_until_ns_status(self, ns_id, target_status, timeout,
sleep_interval):
start_time = int(time.time())
while True:
ns_result = self.client.show_ns(ns_id)
status = ns_result['ns']['status']
if (status == target_status) or (
(int(time.time()) - start_time) > timeout):
break
time.sleep(sleep_interval)
self.assertEqual(status, target_status,
"ns %(ns_id)s with status %(status)s is"
" expected to be %(target)s" %
{"ns_id": ns_id, "status": status,
"target": target_status})
def _wait_until_ns_delete(self, ns_id, timeout):
start_time = int(time.time())
while True:
try:
ns_result = self.client.show_ns(ns_id)
time.sleep(2)
except Exception:
return
status = ns_result['ns']['status']
if (status != 'PENDING_DELETE') or ((
int(time.time()) - start_time) > timeout):
raise Exception("Failed with status: %s" % status)
def _test_create_delete_ns(self, nsd_file, ns_name):
vnfd1_id = self._test_create_tosca_vnfd(
'test-ns-vnfd1.yaml',
'test-ns-vnfd1')
vnfd2_id = self._test_create_tosca_vnfd(
'test-ns-vnfd2.yaml',
'test-ns-vnfd2')
nsd_id = self._test_create_nsd(
nsd_file,
'test-ns-nsd')
ns_arg = {'ns': {'nsd_id': nsd_id, 'name': ns_name,
'attributes': {"param_values": {"nsd":
{"vl2_name": "net0",
"vl1_name": "net_mgmt"}}}}}
ns_instance = self.client.create_ns(body=ns_arg)
ns_id = ns_instance['ns']['id']
self._wait_until_ns_status(ns_id, 'ACTIVE',
constants.NS_CREATE_TIMEOUT,
constants.ACTIVE_SLEEP_TIME)
ns_show_out = self.client.show_ns(ns_id)['ns']
self.assertIsNotNone(ns_show_out['mgmt_urls'])
try:
self.client.delete_ns(ns_id)
except Exception:
assert False, "ns Delete failed"
self._wait_until_ns_delete(ns_id, constants.NS_DELETE_TIMEOUT)
self._test_delete_nsd(nsd_id)
self._test_delete_vnfd(vnfd1_id)
self._test_delete_vnfd(vnfd2_id)
def test_create_delete_nsd(self):
vnfd1_id = self._test_create_tosca_vnfd(
'test-nsd-vnfd1.yaml',
'test-nsd-vnfd1')
vnfd2_id = self._test_create_tosca_vnfd(
'test-nsd-vnfd2.yaml',
'test-nsd-vnfd2')
nsd_id = self._test_create_nsd(
'test-nsd.yaml',
'test-nsd')
self._test_delete_nsd(nsd_id)
self._test_delete_vnfd(vnfd1_id)
self._test_delete_vnfd(vnfd2_id)
def test_create_delete_network_service(self):
self._test_create_delete_ns('test-ns-nsd.yaml',
'test-ns')

View File

@ -186,3 +186,15 @@ def get_dummy_nsd_obj():
'tenant_id': u'8819a1542a5948b68f94d4be0fd50496',
'template': {},
'attributes': {u'nsd': nsd_tosca_template}}}
def get_dummy_ns_obj():
return {'ns': {'description': 'dummy_ns_description',
'id': u'ba6bf017-f6f7-45f1-a280-57b073bf78ea',
'nsd_id': u'eb094833-995e-49f0-a047-dfb56aaf7c4e',
'vim_id': u'6261579e-d6f3-49ad-8bc3-a9cb974778ff',
'tenant_id': u'ad7ebc56538745a08ef7c5e97f8bd437',
'name': 'dummy_ns',
'attributes': {
'param_values': {'nsd': {'vl1_name': 'net_mgmt',
'vl2_name': 'net0'}}}}}

View File

@ -0,0 +1,159 @@
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from tacker import context
from tacker.nfvo.drivers.workflow import mistral
from tacker.tests.unit import base
def get_dummy_ns():
return {u'ns': {'description': '',
'tenant_id': u'a81900a92bda40588c52699e1873a92f',
'vim_id': u'96025dd5-ca16-49f3-9823-958eb04260c4',
'vnf_ids': '', u'attributes': {},
u'nsd_id': u'b8587afb-6099-4f56-abce-572c62e3d61d',
u'name': u'test_create_ns'},
'vnfd_details': {u'vnf1': {'instances': ['VNF1'],
'id': u'dec09ed4-f355-4ec8-a00b-8548f6575a80'},
u'vnf2': {'instances': ['VNF2'],
'id': u'9f8f2af7-6407-4f79-a6fe-302c56172231'}},
'placement_attr': {}}
def get_dummy_param():
return {u'vnf1': {'substitution_mappings': {u'VL1b8587afb-60': {
'type': 'tosca.nodes.nfv.VL', 'properties': {
'network_name': u'net_mgmt',
'vendor': 'tacker'}}, 'requirements': {
'virtualLink2': u'VL2b8587afb-60',
'virtualLink1': u'VL1b8587afb-60'}, u'VL2b8587afb-60': {
'type': 'tosca.nodes.nfv.VL',
'properties': {'network_name': u'net0',
'vendor': 'tacker'}}}},
u'nsd': {u'vl2_name': u'net0', u'vl1_name': u'net_mgmt'}}
def get_dummy_create_workflow():
return {'std.create_vnf_dummy': {'input': ['vnf'],
'tasks': {
'wait_vnf_active_VNF2': {
'action': 'tacker.show_vnf vnf=<% $.vnf_id_VNF2 %>',
'retry': {'count': 10, 'delay': 10,
'continue-on': '<% $.status_VNF2 = '
'"PENDING_CREATE" %>',
'break-on': '<% $.status_VNF2 = "ERROR" %>'},
'publish': {
'status_VNF2': '<% task(wait_vnf_active_VNF2).'
'result.vnf.status %>',
'mgmt_url_VNF2': ' <% task(wait_vnf_active_VNF2).'
'result.vnf.mgmt_url %>'},
'on-success': [{
'delete_vnf_VNF2': '<% $.status_VNF2='
'"ERROR" %>'}]},
'create_vnf_VNF2': {
'action': 'tacker.create_vnf body=<% $.vnf.VNF2 %>',
'input': {'body': '<% $.vnf.VNF2 %>'},
'publish': {
'status_VNF2': '<% task(create_vnf_VNF2).'
'result.vnf.status %>',
'vim_id_VNF2': '<% task(create_vnf_VNF2).'
'result.vnf.vim_id %>',
'mgmt_url_VNF2': '<% task(create_vnf_VNF2).'
'result.vnf.mgmt_url %>',
'vnf_id_VNF2': '<% task(create_vnf_VNF2)'
'.result.vnf.id %>'},
'on-success': ['wait_vnf_active_VNF2']},
'create_vnf_VNF1': {
'action': 'tacker.create_vnf body=<% $.vnf.VNF1 %>',
'input': {'body': '<% $.vnf.VNF1 %>'},
'publish': {
'status_VNF1': '<% task(create_vnf_VNF1).'
'result.vnf.status %>',
'vnf_id_VNF1': '<% task(create_vnf_VNF1).'
'result.vnf.id %>',
'mgmt_url_VNF1': '<% task(create_vnf_VNF1).'
'result.vnf.mgmt_url %>',
'vim_id_VNF1': '<% task(create_vnf_VNF1).'
'result.vnf.vim_id %>'},
'on-success': ['wait_vnf_active_VNF1']},
'wait_vnf_active_VNF1': {
'action': 'tacker.show_vnf vnf=<% $.vnf_id_VNF1 %>',
'retry': {'count': 10, 'delay': 10,
'continue-on': '<% $.status_VNF1 = "PENDING_'
'CREATE" %>',
'break-on': '<% $.status_VNF1 = "ERROR" %>'},
'publish': {
'status_VNF1': '<% task(wait_vnf_active_VNF1).'
'result.vnf.status %>',
'mgmt_url_VNF1': ' <% task(wait_vnf_active_VNF1).'
'result.vnf.mgmt_url %>'},
'on-success': [{'delete_vnf_VNF1': '<% $.status_VNF1='
'"ERROR" %>'}]},
'delete_vnf_VNF1': {'action': 'tacker.delete_vnf vnf=<% '
'$.vnf_id_VNF1%>'},
'delete_vnf_VNF2': {'action': 'tacker.delete_vnf vnf=<% '
'$.vnf_id_VNF2%>'}},
'type': 'direct', 'output': {
'status_VNF1': '<% $.status_VNF1 %>',
'status_VNF2': '<% $.status_VNF2 %>',
'mgmt_url_VNF2': '<% $.mgmt_url_VNF2 %>',
'mgmt_url_VNF1': '<% $.mgmt_url_VNF1 %>',
'vim_id_VNF2': '<% $.vim_id_VNF2 %>',
'vnf_id_VNF1': '<% $.vnf_id_VNF1 %>',
'vnf_id_VNF2': '<% $.vnf_id_VNF2 %>',
'vim_id_VNF1': '<% $.vim_id_VNF1 %>'}},
'version': '2.0'}
def dummy_delete_ns_obj():
return {'vnf_ids': u"{'VNF1': '5de5eca6-3e21-4bbd-a9d7-86458de75f0c'}"}
def get_dummy_delete_workflow():
return {'version': '2.0',
'std.delete_vnf_dummy': {'input': ['vnf_id_VNF1'],
'tasks': {'delete_vnf_VNF1': {
'action': 'tacker.delete_vnf vnf=<% $.vnf_id_VNF1%>'}},
'type': 'direct'}}
class FakeMistral(object):
def __init__(self):
pass
class TestMistralClient(base.TestCase):
def setUp(self):
super(TestMistralClient, self).setUp()
self.mistral_client = FakeMistral()
def test_prepare_workflow_create(self):
mc = mistral.MistralClient(context, self.mistral_client,
resource='vnf', action='create')
mc.prepare_workflow(ns=get_dummy_ns(), params=get_dummy_param())
wf_def_values = [mc.wg.definition[k] for k in mc.wg.definition]
self.assertIn(get_dummy_create_workflow()['std.create_vnf_dummy'],
wf_def_values)
self.assertEqual(get_dummy_create_workflow()['version'],
mc.wg.definition['version'])
def test_prepare_workflow_delete(self):
mc = mistral.MistralClient(context, self.mistral_client,
resource='vnf', action='delete')
mc.prepare_workflow(ns=dummy_delete_ns_obj())
wf_def_values = [mc.wg.definition[k] for k in mc.wg.definition]
self.assertIn(get_dummy_delete_workflow()['std.delete_vnf_dummy'],
wf_def_values)
self.assertEqual(get_dummy_delete_workflow()['version'],
mc.wg.definition['version'])

View File

@ -13,7 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
import codecs
import datetime
import mock
import os
import uuid
@ -23,6 +26,7 @@ from tacker.common import exceptions
from tacker import context
from tacker.db.common_services import common_services_db
from tacker.db.nfvo import nfvo_db
from tacker.db.nfvo import ns_db
from tacker.db.nfvo import vnffg_db
from tacker.extensions import nfvo
from tacker.manager import TackerManager
@ -30,6 +34,7 @@ from tacker.nfvo import nfvo_plugin
from tacker.plugins.common import constants
from tacker.tests.unit.db import base as db_base
from tacker.tests.unit.db import utils
from tacker.vnfm import vim_client
SECRET_PASSWORD = '***'
@ -41,11 +46,66 @@ def dummy_get_vim(*args, **kwargs):
return vim_obj
def _get_template(name):
filename = os.path.abspath(os.path.join(os.path.dirname(__file__),
'../../../etc/samples/' + str(name)))
f = codecs.open(filename, encoding='utf-8', errors='strict')
return f.read()
class FakeDriverManager(mock.Mock):
def invoke(self, *args, **kwargs):
if any(x in ['create', 'create_chain', 'create_flow_classifier'] for
x in args):
return str(uuid.uuid4())
elif 'execute_workflow' in args:
mock_execution = mock.Mock()
mock_execution.id.return_value = \
"ba6bf017-f6f7-45f1-a280-57b073bf78ea"
return mock_execution
def get_fake_nsd():
create_time = datetime.datetime(2017, 1, 19, 9, 2, 11)
return {'description': u'',
'tenant_id': u'a81900a92bda40588c52699e1873a92f',
'created_at': create_time, 'updated_at': None,
'vnfds': {u'tosca.nodes.nfv.VNF1': u'vnf1',
u'tosca.nodes.nfv.VNF2': u'vnf2'},
'attributes': {u'nsd': u'imports: [tinku1, tinku2]\ntopology_'
'template:\n inputs:\n vl1_name: {default: net_mgmt, '
'description: name of VL1 virtuallink, type: string}\n '
'vl2_name: {default: net0, description: name of VL2 virtuallink, '
'type: string}\n node_templates:\n VL1:\n properties:\n'
' network_name: {get_input: vl1_name}\n vendor: '
'tacker\n type: tosca.nodes.nfv.VL\n VL2:\n '
'properties:\n network_name: {get_input: vl2_name}\n '
' vendor: tacker\n type: tosca.nodes.nfv.VL\n VNF1:\n'
' requirements:\n - {virtualLink1: VL1}\n - {'
'virtualLink2: VL2}\n type: tosca.nodes.nfv.VNF1\n VNF2: '
'{type: tosca.nodes.nfv.VNF2}\ntosca_definitions_version: tosca_'
'simple_profile_for_nfv_1_0_0\n'},
'id': u'eb094833-995e-49f0-a047-dfb56aaf7c4e', 'name': u'nsd'}
def get_by_name():
return False
def dummy_get_vim_auth(*args, **kwargs):
return {'vim_auth': {u'username': u'admin', 'password': 'devstack',
u'project_name': u'nfv', u'user_id': u'',
u'user_domain_name': u'Default',
u'auth_url': u'http://10.0.4.207/identity/v3',
u'project_id': u'',
u'project_domain_name': u'Default'},
'vim_id': u'96025dd5-ca16-49f3-9823-958eb04260c4',
'vim_type': u'openstack', 'vim_name': u'VIM0'}
class FakeClient(mock.Mock):
def __init__(self, auth):
pass
class FakeVNFMPlugin(mock.Mock):
@ -55,6 +115,7 @@ class FakeVNFMPlugin(mock.Mock):
self.vnf1_vnfd_id = 'eb094833-995e-49f0-a047-dfb56aaf7c4e'
self.vnf1_vnf_id = '91e32c20-6d1f-47a4-9ba7-08f5e5effe07'
self.vnf3_vnfd_id = 'e4015e9f-1ef2-49fb-adb6-070791ad3c45'
self.vnf2_vnfd_id = 'e4015e9f-1ef2-49fb-adb6-070791ad3c45'
self.vnf3_vnf_id = '7168062e-9fa1-4203-8cb7-f5c99ff3ee1b'
self.vnf3_update_vnf_id = '10f66bc5-b2f1-45b7-a7cd-6dd6ad0017f5'
@ -63,6 +124,18 @@ class FakeVNFMPlugin(mock.Mock):
self.cp32_id = '3d1bd2a2-bf0e-44d1-87af-a2c6b2cad3ed'
self.cp32_update_id = '064c0d99-5a61-4711-9597-2a44dc5da14b'
def get_vnfd(self, *args, **kwargs):
if 'VNF1' in args:
return {'id': self.vnf1_vnfd_id,
'name': 'VNF1',
'attributes': {'vnfd': _get_template(
'test-nsd-vnfd1.yaml')}}
elif 'VNF2' in args:
return {'id': self.vnf3_vnfd_id,
'name': 'VNF2',
'attributes': {'vnfd': _get_template(
'test-nsd-vnfd2.yaml')}}
def get_vnfds(self, *args, **kwargs):
if {'name': ['VNF1']} in args:
return [{'id': self.vnf1_vnfd_id}]
@ -433,12 +506,99 @@ class TestNfvoPlugin(db_base.SqlTestCase):
fc_id=mock.ANY,
auth_attr=mock.ANY)
# def test_create_nsd(self):
# nsd_obj = utils.get_dummy_nsd_obj()
# with patch.object(TackerManager, 'get_service_plugins') as \
# mock_plugins:
# mock_plugins.return_value = {'VNFM': FakeVNFMPlugin()}
# mock.patch('tacker.common.driver_manager.DriverManager',
# side_effect=FakeDriverManager()).start()
# result = self.nfvo_plugin.create_nsd(self.context, nsd_obj)
# self.assertIsNotNone(result)
def _insert_dummy_ns_template(self):
session = self.context.session
attributes = {
u'nsd': 'imports: [VNF1, VNF2]\ntopology_template:\n inputs:\n '
' vl1_name: {default: net_mgmt, description: name of VL1'
' virtuallink, type: string}\n vl2_name: {default: '
'net0, description: name of VL2 virtuallink, type: string'
'}\n node_templates:\n VL1:\n properties:\n '
' network_name: {get_input: vl1_name}\n vendor: '
'tacker\n type: tosca.nodes.nfv.VL\n VL2:\n '
'properties:\n network_name: {get_input: vl2_name}'
'\n vendor: tacker\n type: tosca.nodes.nfv.VL'
'\n VNF1:\n requirements:\n - {virtualLink1: '
'VL1}\n - {virtualLink2: VL2}\n type: tosca.node'
's.nfv.VNF1\n VNF2: {type: tosca.nodes.nfv.VNF2}\ntosca'
'_definitions_version: tosca_simple_profile_for_nfv_1_0_0'
'\n'}
nsd_template = ns_db.NSD(
id='eb094833-995e-49f0-a047-dfb56aaf7c4e',
tenant_id='ad7ebc56538745a08ef7c5e97f8bd437',
name='fake_template',
vnfds={'tosca.nodes.nfv.VNF1': 'vnf1',
'tosca.nodes.nfv.VNF2': 'vnf2'},
description='fake_nsd_template_description')
session.add(nsd_template)
for (key, value) in attributes.items():
attribute_db = ns_db.NSDAttribute(
id=str(uuid.uuid4()),
nsd_id='eb094833-995e-49f0-a047-dfb56aaf7c4e',
key=key,
value=value)
session.add(attribute_db)
session.flush()
return nsd_template
def _insert_dummy_ns(self):
session = self.context.session
ns = ns_db.NS(
id='ba6bf017-f6f7-45f1-a280-57b073bf78ea',
name='fake_ns',
tenant_id='ad7ebc56538745a08ef7c5e97f8bd437',
status='ACTIVE',
nsd_id='eb094833-995e-49f0-a047-dfb56aaf7c4e',
vim_id='6261579e-d6f3-49ad-8bc3-a9cb974778ff',
description='fake_ns_description')
session.add(ns)
session.flush()
return ns
def test_create_nsd(self):
nsd_obj = utils.get_dummy_nsd_obj()
with patch.object(TackerManager, 'get_service_plugins') as \
mock_plugins:
mock_plugins.return_value = {'VNFM': FakeVNFMPlugin()}
mock.patch('tacker.common.driver_manager.DriverManager',
side_effect=FakeDriverManager()).start()
result = self.nfvo_plugin.create_nsd(self.context, nsd_obj)
self.assertIsNotNone(result)
self.assertEqual(result['name'], 'dummy_NSD')
@mock.patch.object(vim_client.VimClient, 'get_vim')
@mock.patch.object(nfvo_plugin.NfvoPlugin, '_get_by_name')
def test_create_ns(self, mock_get_by_name, mock_get_vim):
self._insert_dummy_ns_template()
self._insert_dummy_vim()
with patch.object(TackerManager, 'get_service_plugins') as \
mock_plugins:
mock_plugins.return_value = {'VNFM': FakeVNFMPlugin()}
mock.patch('tacker.common.driver_manager.DriverManager',
side_effect=FakeDriverManager()).start()
mock_get_by_name.return_value = get_by_name()
ns_obj = utils.get_dummy_ns_obj()
result = self.nfvo_plugin.create_ns(self.context, ns_obj)
self.assertIsNotNone(result)
self.assertIn('id', result)
self.assertEqual(ns_obj['ns']['nsd_id'], result['nsd_id'])
self.assertEqual(ns_obj['ns']['name'], result['name'])
self.assertIn('status', result)
self.assertIn('tenant_id', result)
@mock.patch.object(vim_client.VimClient, 'get_vim')
@mock.patch.object(nfvo_plugin.NfvoPlugin, '_get_by_name')
def test_delete_ns(self, mock_get_by_name, mock_get_vim):
self._insert_dummy_vim()
self._insert_dummy_ns_template()
self._insert_dummy_ns()
with patch.object(TackerManager, 'get_service_plugins') as \
mock_plugins:
mock_plugins.return_value = {'VNFM': FakeVNFMPlugin()}
mock.patch('tacker.common.driver_manager.DriverManager',
side_effect=FakeDriverManager()).start()
mock_get_by_name.return_value = get_by_name()
result = self.nfvo_plugin.delete_ns(self.context,
'ba6bf017-f6f7-45f1-a280-57b073bf78ea')
self.assertIsNotNone(result)

View File

@ -229,3 +229,18 @@ class TestToscaUtils(testtools.TestCase):
toscautils.convert_unsupported_res_prop(dummy_heat_dict,
unsupported_res_prop_dict)
self.assertEqual(expected_heat_dict, dummy_heat_dict)
def test_check_for_substitution_mappings(self):
tosca_sb_map = _get_template('../../../../../etc/samples/test-nsd-'
'vnfd1.yaml')
param = {'substitution_mappings': {
'VL2': {'type': 'tosca.nodes.nfv.VL', 'properties': {
'network_name': 'net0', 'vendor': 'tacker'}},
'VL1': {'type': 'tosca.nodes.nfv.VL', 'properties': {
'network_name': 'net_mgmt', 'vendor': 'tacker'}},
'requirements': {'virtualLink2': 'VL2',
'virtualLink1': 'VL1'}}}
template = yaml.load(tosca_sb_map)
toscautils.updateimports(template)
toscautils.check_for_substitution_mappings(template, param)
self.assertNotIn('substitution_mappings', param)

View File

@ -280,7 +280,7 @@ class OpenStack(abstract_driver.DeviceAbstractDriver,
stack_status=status)
LOG.warning(error_reason)
raise vnfm.VNFCreateWaitFailed(vnf_id=vnf_id,
eason=error_reason)
reason=error_reason)
@classmethod
def _find_mgmt_ips_from_groups(cls, heat_client, instance_id, group_names):

View File

@ -21,6 +21,7 @@ from translator.hot import tosca_translator
import yaml
from tacker.common import log
from tacker.extensions import common_services as cs
from tacker.extensions import vnfm
from tacker.vnfm.tosca import utils as toscautils
@ -165,13 +166,13 @@ class TOSCAToHOT(object):
self._update_params(value, paramvalues[key], False)
else:
LOG.debug('Key missing Value: %s', key)
raise vnfm.InputValuesMissing(key=key)
raise cs.InputValuesMissing(key=key)
elif 'get_input' in value:
if value['get_input'] in paramvalues:
original[key] = paramvalues[value['get_input']]
else:
LOG.debug('Key missing Value: %s', key)
raise vnfm.InputValuesMissing(key=key)
raise cs.InputValuesMissing(key=key)
else:
self._update_params(value, paramvalues, True)
@ -189,7 +190,7 @@ class TOSCAToHOT(object):
else:
self._update_params(vnfd_dict, param_vattrs_dict)
else:
raise vnfm.ParamYAMLInputMissing()
raise cs.ParamYAMLInputMissing()
@log.log
def _process_vdu_network_interfaces(self, vdu_id, vdu_dict, properties,

View File

@ -101,16 +101,19 @@ def updateimports(template):
@log.log
def check_for_substitution_mappings(template, params):
sm_dict = params.get('substitution_mappings')
if not sm_dict:
raise vnfm.InvalidParamsForSM()
del params['substitution_mappings']
sm_dict = params.get('substitution_mappings', {})
requirements = sm_dict.get('requirements')
if not requirements:
pass
node_tpl = template['topology_template']['node_templates']
req_dict_tpl = template['topology_template']['substitution_mappings'][
'requirements']
req_dict_tpl = template['topology_template']['substitution_mappings'].get(
'requirements')
# Check if substitution_mappings and requirements are empty in params but
# not in template. If True raise exception
if (not sm_dict or not requirements) and req_dict_tpl:
raise vnfm.InvalidParamsForSM()
# Check if requirements are present for SM in template, if True then return
elif (not sm_dict or not requirements) and not req_dict_tpl:
return
del params['substitution_mappings']
for req_name, req_val in iteritems(req_dict_tpl):
if req_name not in requirements:
raise vnfm.SMRequirementMissing(requirement=req_name)