Implement NSD Support - Add CRUD operations for NSD

Co-Authored-By: Dharmendra Kushwaha<dharmendra.kushwaha@nectechnologies.in>
Partially-implements: blueprint nsd-support
Change-Id: Iff51926ce9f3b96f83a51ef06b4b464e4696d777
This commit is contained in:
Bharath Thiruveedula 2016-12-18 12:41:11 +05:30
parent 53c1c0464b
commit 5f46c2b030
19 changed files with 669 additions and 15 deletions

View File

@ -41,3 +41,4 @@ tosca-parser>=0.7.0 # Apache-2.0
heat-translator>=0.4.0 # Apache-2.0
cryptography!=1.3.0,>=1.0 # BSD/Apache-2.0
paramiko>=2.0 # LGPLv2.1+
python-mistralclient>=2.0.0 # Apache-2.0

View File

@ -0,0 +1,73 @@
# Copyright 2016 OpenStack Foundation
#
# 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.
#
"""create of Network service tables
Revision ID: 0ad3bbce1c18
Revises: 0ae5b1ce3024
Create Date: 2016-12-17 19:41:01.906138
"""
# revision identifiers, used by Alembic.
revision = '0ad3bbce1c18'
down_revision = '8f7145914cb0'
from alembic import op
import sqlalchemy as sa
from tacker.db import types
def upgrade(active_plugins=None, options=None):
op.create_table('nsd',
sa.Column('tenant_id', sa.String(length=64), nullable=False),
sa.Column('id', types.Uuid(length=36), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.Column('deleted_at', sa.DateTime(), nullable=True),
sa.Column('name', sa.String(length=255), nullable=False),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('vnfds', types.Json, nullable=True),
sa.PrimaryKeyConstraint('id'),
mysql_engine='InnoDB'
)
op.create_table('ns',
sa.Column('tenant_id', sa.String(length=64), nullable=False),
sa.Column('id', types.Uuid(length=36), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.Column('deleted_at', sa.DateTime(), nullable=True),
sa.Column('nsd_id', types.Uuid(length=36), nullable=True),
sa.Column('vim_id', sa.String(length=64), nullable=False),
sa.Column('name', sa.String(length=255), nullable=False),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('vnf_ids', sa.TEXT(length=65535), nullable=True),
sa.Column('mgmt_urls', sa.TEXT(length=65535), nullable=True),
sa.Column('status', sa.String(length=64), nullable=False),
sa.Column('error_reason', sa.Text(), nullable=True),
sa.ForeignKeyConstraint(['nsd_id'], ['nsd.id'], ),
sa.PrimaryKeyConstraint('id'),
mysql_engine='InnoDB'
)
op.create_table('nsd_attribute',
sa.Column('id', types.Uuid(length=36), nullable=False),
sa.Column('nsd_id', types.Uuid(length=36), nullable=False),
sa.Column('key', sa.String(length=255), nullable=False),
sa.Column('value', sa.TEXT(length=65535), nullable=True),
sa.ForeignKeyConstraint(['nsd_id'], ['nsd.id'], ),
sa.PrimaryKeyConstraint('id'),
mysql_engine='InnoDB'
)

View File

@ -1 +1 @@
8f7145914cb0
0ad3bbce1c18

View File

@ -23,6 +23,7 @@ Based on this comparison database can be healed with healing migration.
from tacker.db import model_base
from tacker.db.nfvo import nfvo_db # noqa
from tacker.db.nfvo import ns_db # noqa
from tacker.db.nfvo import vnffg_db # noqa
from tacker.db.vnfm import vnfm_db # noqa

192
tacker/db/nfvo/ns_db.py Normal file
View File

@ -0,0 +1,192 @@
# 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 uuid
from oslo_log import log as logging
from oslo_utils import timeutils
import sqlalchemy as sa
from sqlalchemy import orm
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.nfvo_plugins import network_service
from tacker.plugins.common import constants
LOG = logging.getLogger(__name__)
_ACTIVE_UPDATE = (constants.ACTIVE, constants.PENDING_UPDATE)
_ACTIVE_UPDATE_ERROR_DEAD = (
constants.PENDING_CREATE, constants.ACTIVE, constants.PENDING_UPDATE,
constants.ERROR, constants.DEAD)
CREATE_STATES = (constants.PENDING_CREATE, constants.DEAD)
###########################################################################
# db tables
class NSD(model_base.BASE, models_v1.HasId, models_v1.HasTenant,
models_v1.Audit):
"""Represents NSD to create NS."""
__tablename__ = 'nsd'
# Descriptive name
name = sa.Column(sa.String(255), nullable=False)
description = sa.Column(sa.Text)
vnfds = sa.Column(types.Json, nullable=True)
# (key, value) pair to spin up
attributes = orm.relationship('NSDAttribute',
backref='nsd')
class NSDAttribute(model_base.BASE, models_v1.HasId):
"""Represents attributes necessary for creation of ns in (key, value) pair
"""
__tablename__ = 'nsd_attribute'
nsd_id = sa.Column(types.Uuid, sa.ForeignKey('nsd.id'),
nullable=False)
key = sa.Column(sa.String(255), nullable=False)
value = sa.Column(sa.TEXT(65535), nullable=True)
class NS(model_base.BASE, models_v1.HasId, models_v1.HasTenant,
models_v1.Audit):
"""Represents network services that deploys services.
"""
__tablename__ = 'ns'
nsd_id = sa.Column(types.Uuid, sa.ForeignKey('nsd.id'))
nsd = orm.relationship('NSD')
name = sa.Column(sa.String(255), nullable=False)
description = sa.Column(sa.Text, nullable=True)
# Dict of VNF details that network service launches
vnf_ids = sa.Column(sa.TEXT(65535), nullable=True)
# Dict of mgmt urls that network servic launches
mgmt_urls = sa.Column(sa.TEXT(65535), nullable=True)
status = sa.Column(sa.String(64), nullable=False)
vim_id = sa.Column(types.Uuid, sa.ForeignKey('vims.id'), nullable=False)
error_reason = sa.Column(sa.Text, nullable=True)
class NSPluginDb(network_service.NSPluginBase, db_base.CommonDbMixin):
def __init__(self):
super(NSPluginDb, self).__init__()
self._cos_db_plg = common_services_db.CommonServicesPluginDb()
def _make_attributes_dict(self, attributes_db):
return dict((attr.key, attr.value) for attr in attributes_db)
def _make_nsd_dict(self, nsd, fields=None):
res = {
'attributes': self._make_attributes_dict(nsd['attributes']),
}
key_list = ('id', 'tenant_id', 'name', 'description',
'created_at', 'updated_at', 'vnfds')
res.update((key, nsd[key]) for key in key_list)
return self._fields(res, fields)
def create_nsd(self, context, nsd):
vnfds = nsd['vnfds']
nsd = nsd['nsd']
LOG.debug(_('nsd %s'), nsd)
tenant_id = self._get_tenant_id_for_create(context, nsd)
with context.session.begin(subtransactions=True):
nsd_id = str(uuid.uuid4())
nsd_db = NSD(
id=nsd_id,
tenant_id=tenant_id,
name=nsd.get('name'),
vnfds=vnfds,
description=nsd.get('description'))
context.session.add(nsd_db)
for (key, value) in nsd.get('attributes', {}).items():
attribute_db = NSDAttribute(
id=str(uuid.uuid4()),
nsd_id=nsd_id,
key=key,
value=value)
context.session.add(attribute_db)
LOG.debug(_('nsd_db %(nsd_db)s %(attributes)s '),
{'nsd_db': nsd_db,
'attributes': nsd_db.attributes})
nsd_dict = self._make_nsd_dict(nsd_db)
LOG.debug(_('nsd_dict %s'), nsd_dict)
self._cos_db_plg.create_event(
context, res_id=nsd_dict['id'],
res_type=constants.RES_TYPE_NSD,
res_state=constants.RES_EVT_ONBOARDED,
evt_type=constants.RES_EVT_CREATE,
tstamp=nsd_dict[constants.RES_EVT_CREATED_FLD])
return nsd_dict
def delete_nsd(self,
context,
nsd_id,
soft_delete=True):
with context.session.begin(subtransactions=True):
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)
nsd_db = self._get_resource(context, NSD,
nsd_id)
if soft_delete:
nsd_db.update({'deleted_at': timeutils.utcnow()})
self._cos_db_plg.create_event(
context, res_id=nsd_db['id'],
res_type=constants.RES_TYPE_NSD,
res_state=constants.RES_EVT_NA_STATE,
evt_type=constants.RES_EVT_DELETE,
tstamp=nsd_db[constants.RES_EVT_DELETED_FLD])
else:
context.session.query(NSDAttribute).filter_by(
nsd_id=nsd_id).delete()
context.session.delete(nsd_db)
def get_nsd(self, context, nsd_id, fields=None):
nsd_db = self._get_resource(context, NSD, nsd_id)
return self._make_nsd_dict(nsd_db)
def get_nsds(self, context, filters, fields=None):
return self._get_collection(context, NSD,
self._make_nsd_dict,
filters=filters, fields=fields)
# reference implementation. needs to be overrided by subclass
def create_ns(self, context, ns):
return {'nsd': {}}
# reference implementation. needs to be overrided by subclass
def delete_ns(self, context, ns_id, soft_delete=True):
pass
def get_ns(self, context, ns_id, fields=None):
pass
def get_nss(self, context, filters=None, fields=None):
pass

View File

@ -18,6 +18,7 @@ import uuid
from oslo_log import log as logging
from oslo_utils import timeutils
from oslo_utils import uuidutils
import sqlalchemy as sa
from sqlalchemy import orm
@ -156,7 +157,9 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin):
def _get_resource(self, context, model, id):
try:
return self._get_by_id(context, model, id)
if uuidutils.is_uuid_like(id):
return self._get_by_id(context, model, id)
return self._get_by_name(context, model, id)
except orm_exc.NoResultFound:
if issubclass(model, VNFD):
raise vnfm.VNFDNotFound(vnfd_id=id)
@ -254,7 +257,7 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin):
self._cos_db_plg.create_event(
context, res_id=vnfd_dict['id'],
res_type=constants.RES_TYPE_VNFD,
res_state=constants.RES_EVT_VNFD_ONBOARDED,
res_state=constants.RES_EVT_ONBOARDED,
evt_type=constants.RES_EVT_CREATE,
tstamp=vnfd_dict[constants.RES_EVT_CREATED_FLD])
return vnfd_dict
@ -270,7 +273,7 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin):
self._cos_db_plg.create_event(
context, res_id=vnfd_dict['id'],
res_type=constants.RES_TYPE_VNFD,
res_state=constants.RES_EVT_VNFD_NA_STATE,
res_state=constants.RES_EVT_NA_STATE,
evt_type=constants.RES_EVT_UPDATE,
tstamp=vnfd_dict[constants.RES_EVT_UPDATED_FLD])
return vnfd_dict
@ -295,7 +298,7 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin):
self._cos_db_plg.create_event(
context, res_id=vnfd_db['id'],
res_type=constants.RES_TYPE_VNFD,
res_state=constants.RES_EVT_VNFD_NA_STATE,
res_state=constants.RES_EVT_NA_STATE,
evt_type=constants.RES_EVT_DELETE,
tstamp=vnfd_db[constants.RES_EVT_DELETED_FLD])
else:

View File

@ -209,6 +209,9 @@ class ClassifierNotFoundException(exceptions.NotFound):
message = _('Classifier %(classifier_id)s could not be found')
class NSDInUse(exceptions.InUse):
message = _('NSD %(nsd_id)s is still in use')
RESOURCE_ATTRIBUTE_MAP = {
'vims': {
@ -549,7 +552,152 @@ RESOURCE_ATTRIBUTE_MAP = {
'allow_put': False,
'is_visible': True,
},
}
},
'nsds': {
'id': {
'allow_post': False,
'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
'primary_key': True,
},
'tenant_id': {
'allow_post': True,
'allow_put': False,
'validate': {'type:string': None},
'required_by_policy': True,
'is_visible': True,
},
'name': {
'allow_post': True,
'allow_put': True,
'validate': {'type:string': None},
'is_visible': True,
},
'description': {
'allow_post': True,
'allow_put': True,
'validate': {'type:string': None},
'is_visible': True,
'default': '',
},
'created_at': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
},
'updated_at': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
},
'attributes': {
'allow_post': True,
'allow_put': False,
'convert_to': attr.convert_none_to_empty_dict,
'validate': {'type:dict_or_nodata': None},
'is_visible': True,
'default': None,
},
},
'nss': {
'id': {
'allow_post': False,
'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
'primary_key': True,
},
'tenant_id': {
'allow_post': True,
'allow_put': False,
'validate': {'type:string': None},
'required_by_policy': True,
'is_visible': True,
},
'name': {
'allow_post': True,
'allow_put': True,
'validate': {'type:string': None},
'is_visible': True,
},
'description': {
'allow_post': True,
'allow_put': True,
'validate': {'type:string': None},
'is_visible': True,
'default': '',
},
'created_at': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
},
'updated_at': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
},
'vnf_ids': {
'allow_post': True,
'allow_put': False,
'validate': {'type:string': None},
'is_visible': True,
'default': '',
},
'nsd_id': {
'allow_post': True,
'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
},
'vim_id': {
'allow_post': True,
'allow_put': False,
'validate': {'type:string': None},
'is_visible': True,
'default': '',
},
'status': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
},
'error_reason': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
},
'created_at': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
},
'updated_at': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
},
'attributes': {
'allow_post': True,
'allow_put': False,
'convert_to': attr.convert_none_to_empty_dict,
'validate': {'type:dict_or_nodata': None},
'is_visible': True,
'default': None,
},
'mgmt_urls': {
'allow_post': False,
'allow_put': False,
'convert_to': attr.convert_none_to_empty_dict,
'validate': {'type:dict_or_nodata': None},
'is_visible': True,
},
},
}

View File

@ -0,0 +1,52 @@
# 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 abc
import six
from tacker.services import service_base
@six.add_metaclass(abc.ABCMeta)
class NSPluginBase(service_base.NFVPluginBase):
@abc.abstractmethod
def create_nsd(self, context, nsd):
pass
@abc.abstractmethod
def delete_nsd(self, context, nsd_id):
pass
@abc.abstractmethod
def get_nsd(self, context, nsd_id, fields=None):
pass
@abc.abstractmethod
def get_nsds(self, context, filters=None, fields=None):
pass
@abc.abstractmethod
def create_ns(self, context, ns):
pass
@abc.abstractmethod
def get_nss(self, context, filters=None, fields=None):
pass
@abc.abstractmethod
def get_ns(self, context, ns_id, fields=None):
pass
@abc.abstractmethod
def delete_ns(self, context, ns_id):
pass

View File

@ -149,6 +149,21 @@ class MetadataNotMatched(exceptions.InvalidInput):
message = _("Metadata for alarm policy is not matched")
class InvalidSubstitutionMapping(exceptions.InvalidInput):
message = _("Input for substitution mapping requirements are not"
" valid for %(requirement)s. They must be in the form"
" of list with two entries")
class SMRequirementMissing(exceptions.InvalidInput):
message = _("All the requirements for substitution_mappings are not"
" provided. Missing requirement for %(requirement)s")
class InvalidParamsForSM(exceptions.InvalidInput):
message = _("Please provide parameters for substitution mappings")
def _validate_service_type_list(data, valid_values=None):
if not isinstance(data, list):
msg = _("invalid data format for service list: '%s'") % data

View File

@ -18,12 +18,15 @@ import os
import threading
import time
import uuid
import yaml
from cryptography import fernet
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import excutils
from oslo_utils import strutils
from tempfile import mkstemp
from toscaparser.tosca_template import ToscaTemplate
from tacker._i18n import _
from tacker.common import driver_manager
@ -32,6 +35,7 @@ from tacker.common import log
from tacker.common import utils
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 nfvo
from tacker import manager
@ -48,7 +52,8 @@ def config_opts():
return [('nfvo_vim', NfvoPlugin.OPTS)]
class NfvoPlugin(nfvo_db.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin):
class NfvoPlugin(nfvo_db.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin,
ns_db.NSPluginDb):
"""NFVO reference plugin for NFVO extension
Implements the NFVO extension and defines public facing APIs for VIM
@ -404,3 +409,74 @@ class NfvoPlugin(nfvo_db.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin):
vim_auth=vim_obj['auth_cred'],
resource_type=resource,
resource_name=name)
@log.log
def create_nsd(self, context, nsd):
nsd_data = nsd['nsd']
template = nsd_data['attributes'].get('nsd')
if isinstance(template, dict):
nsd_data['attributes']['nsd'] = yaml.safe_dump(
template)
LOG.debug(_('nsd %s'), nsd_data)
name = nsd_data['name']
if self._get_by_name(context, ns_db.NSD, name):
raise exceptions.DuplicateResourceName(resource='NSD', name=name)
self._parse_template_input(context, nsd)
return super(NfvoPlugin, self).create_nsd(
context, nsd)
def _parse_template_input(self, context, nsd):
nsd_dict = nsd['nsd']
nsd_yaml = nsd_dict['attributes'].get('nsd')
inner_nsd_dict = yaml.load(nsd_yaml)
nsd['vnfds'] = dict()
LOG.debug(_('nsd_dict: %s'), inner_nsd_dict)
vnfm_plugin = manager.TackerManager.get_service_plugins()['VNFM']
vnfd_imports = inner_nsd_dict['imports']
inner_nsd_dict['imports'] = []
new_files = []
for vnfd_name in vnfd_imports:
vnfd = vnfm_plugin.get_vnfd(context, vnfd_name)
# Copy VNF types and VNF names
sm_dict = yaml.load(vnfd['attributes']['vnfd'])[
'topology_template'][
'substitution_mappings']
nsd['vnfds'][sm_dict['node_type']] = vnfd['name']
# Ugly Hack to validate the child templates
# TODO(tbh): add support in tosca-parser to pass child
# templates as dict
fd, temp_path = mkstemp()
with open(temp_path, 'w') as fp:
fp.write(vnfd['attributes']['vnfd'])
os.close(fd)
new_files.append(temp_path)
inner_nsd_dict['imports'].append(temp_path)
# Prepend the tacker_defs.yaml import file with the full
# path to the file
toscautils.updateimports(inner_nsd_dict)
try:
ToscaTemplate(a_file=False,
yaml_dict_tpl=inner_nsd_dict)
except Exception as e:
LOG.exception(_("tosca-parser error: %s"), str(e))
raise nfvo.ToscaParserFailed(error_msg_details=str(e))
finally:
for file_path in new_files:
os.remove(file_path)
inner_nsd_dict['imports'] = vnfd_imports
if ('description' not in nsd_dict or
nsd_dict['description'] == ''):
nsd_dict['description'] = inner_nsd_dict.get(
'description', '')
if (('name' not in nsd_dict or
not len(nsd_dict['name'])) and
'metadata' in inner_nsd_dict):
nsd_dict['name'] = inner_nsd_dict['metadata'].get(
'template_name', '')
LOG.debug(_('nsd %s'), nsd)

View File

@ -56,6 +56,7 @@ POLICY_ALARMING = 'tosca.policies.tacker.Alarming'
DEFAULT_ALARM_ACTIONS = ['respawn', 'log', 'log_and_kill', 'notify']
RES_TYPE_VNFD = "vnfd"
RES_TYPE_NSD = "nsd"
RES_TYPE_VNF = "vnf"
RES_TYPE_VIM = "vim"
@ -64,8 +65,8 @@ RES_EVT_DELETE = "DELETE"
RES_EVT_UPDATE = "UPDATE"
RES_EVT_MONITOR = "MONITOR"
RES_EVT_SCALE = "SCALE"
RES_EVT_VNFD_NA_STATE = "Not Applicable"
RES_EVT_VNFD_ONBOARDED = "OnBoarded"
RES_EVT_NA_STATE = "Not Applicable"
RES_EVT_ONBOARDED = "OnBoarded"
RES_EVT_CREATED_FLD = "created_at"
RES_EVT_DELETED_FLD = "deleted_at"

View File

@ -37,14 +37,14 @@ class VnfdTestCreate(base.BaseTackerTest):
vnfd_id = vnfd_instance['vnfd']['id']
self.verify_vnfd_events(
vnfd_id, evt_constants.RES_EVT_CREATE,
evt_constants.RES_EVT_VNFD_ONBOARDED)
evt_constants.RES_EVT_ONBOARDED)
try:
self.client.delete_vnfd(vnfd_id)
except Exception:
assert False, "vnfd Delete failed"
self.verify_vnfd_events(vnfd_id, evt_constants.RES_EVT_DELETE,
evt_constants.RES_EVT_VNFD_NA_STATE)
evt_constants.RES_EVT_NA_STATE)
def test_tosca_vnfd(self):
self._test_create_list_delete_tosca_vnfd('sample-tosca-vnfd.yaml',

View File

@ -33,7 +33,7 @@ class VnfmTestParam(base.BaseTackerTest):
self.assertIsNotNone(vnfd_id)
self.verify_vnfd_events(
vnfd_id, evt_constants.RES_EVT_CREATE,
evt_constants.RES_EVT_VNFD_ONBOARDED)
evt_constants.RES_EVT_ONBOARDED)
return vnfd_instance
def _test_vnfd_delete(self, vnfd_instance):
@ -45,7 +45,7 @@ class VnfmTestParam(base.BaseTackerTest):
except Exception:
assert False, "vnfd Delete failed"
self.verify_vnfd_events(vnfd_id, evt_constants.RES_EVT_DELETE,
evt_constants.RES_EVT_VNFD_NA_STATE)
evt_constants.RES_EVT_NA_STATE)
try:
vnfd_d = self.client.show_vnfd(vnfd_id)
except Exception:

View File

@ -41,6 +41,7 @@ vnfd_alarm_respawn_tosca_template = _get_template(
'test_tosca_vnfd_alarm_respawn.yaml')
vnfd_alarm_scale_tosca_template = _get_template(
'test_tosca_vnfd_alarm_scale.yaml')
nsd_tosca_template = yaml.load(_get_template('tosca_nsd_template.yaml'))
def get_dummy_vnfd_obj():
@ -177,3 +178,11 @@ def get_dummy_vnffg_obj_vnf_mapping():
'VNF3': '7168062e-9fa1-4203-8cb7-f5c99ff3ee1b'
},
'symmetrical': False}}
def get_dummy_nsd_obj():
return {'nsd': {'description': 'dummy nsd description',
'name': 'dummy_NSD',
'tenant_id': u'8819a1542a5948b68f94d4be0fd50496',
'template': {},
'attributes': {u'nsd': nsd_tosca_template}}}

View File

@ -0,0 +1,38 @@
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
imports:
- VNF1
- VNF2
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

@ -69,7 +69,7 @@ class FakeVNFMPlugin(mock.Mock):
elif {'name': ['VNF3']} in args:
return [{'id': self.vnf3_vnfd_id}]
else:
return None
return []
def get_vnfs(self, *args, **kwargs):
if {'vnfd_id': [self.vnf1_vnfd_id]} in args:
@ -432,3 +432,13 @@ class TestNfvoPlugin(db_base.SqlTestCase):
self._driver_manager.invoke.assert_called_with(mock.ANY, mock.ANY,
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)

View File

@ -226,7 +226,7 @@ class TestVNFMPlugin(db_base.SqlTestCase):
mock_update_imports.assert_called_once_with(yaml_dict)
self._cos_db_plugin.create_event.assert_called_once_with(
self.context, evt_type=constants.RES_EVT_CREATE, res_id=mock.ANY,
res_state=constants.RES_EVT_VNFD_ONBOARDED,
res_state=constants.RES_EVT_ONBOARDED,
res_type=constants.RES_TYPE_VNFD, tstamp=mock.ANY)
def test_create_vnfd_no_service_types(self):

View File

@ -289,6 +289,9 @@ class TOSCAToHOT(object):
raise vnfm.ParamYAMLNotWellFormed(error_msg_details=str(e))
toscautils.updateimports(vnfd_dict)
if 'substitution_mappings' in str(vnfd_dict):
toscautils.check_for_substitution_mappings(vnfd_dict,
parsed_params)
try:
tosca = tosca_template.ToscaTemplate(parsed_params=parsed_params,

View File

@ -99,6 +99,38 @@ def updateimports(template):
LOG.debug(_("%s"), path)
@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']
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']
for req_name, req_val in iteritems(req_dict_tpl):
if req_name not in requirements:
raise vnfm.SMRequirementMissing(requirement=req_name)
if not isinstance(req_val, list):
raise vnfm.InvalidSubstitutionMapping(requirement=req_name)
try:
node_name = req_val[0]
node_req = req_val[1]
node_tpl[node_name]['requirements'].append({
node_req: {
'node': requirements[req_name]
}
})
node_tpl[requirements[req_name]] = \
sm_dict[requirements[req_name]]
except Exception:
raise vnfm.InvalidSubstitutionMapping(requirement=req_name)
@log.log
def get_vdu_monitoring(template):
monitoring_dict = {}