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 heat-translator>=0.4.0 # Apache-2.0
cryptography!=1.3.0,>=1.0 # BSD/Apache-2.0 cryptography!=1.3.0,>=1.0 # BSD/Apache-2.0
paramiko>=2.0 # LGPLv2.1+ 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 import model_base
from tacker.db.nfvo import nfvo_db # noqa 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.nfvo import vnffg_db # noqa
from tacker.db.vnfm import vnfm_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_log import log as logging
from oslo_utils import timeutils from oslo_utils import timeutils
from oslo_utils import uuidutils
import sqlalchemy as sa import sqlalchemy as sa
from sqlalchemy import orm from sqlalchemy import orm
@ -156,7 +157,9 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin):
def _get_resource(self, context, model, id): def _get_resource(self, context, model, id):
try: 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: except orm_exc.NoResultFound:
if issubclass(model, VNFD): if issubclass(model, VNFD):
raise vnfm.VNFDNotFound(vnfd_id=id) raise vnfm.VNFDNotFound(vnfd_id=id)
@ -254,7 +257,7 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin):
self._cos_db_plg.create_event( self._cos_db_plg.create_event(
context, res_id=vnfd_dict['id'], context, res_id=vnfd_dict['id'],
res_type=constants.RES_TYPE_VNFD, 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, evt_type=constants.RES_EVT_CREATE,
tstamp=vnfd_dict[constants.RES_EVT_CREATED_FLD]) tstamp=vnfd_dict[constants.RES_EVT_CREATED_FLD])
return vnfd_dict return vnfd_dict
@ -270,7 +273,7 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin):
self._cos_db_plg.create_event( self._cos_db_plg.create_event(
context, res_id=vnfd_dict['id'], context, res_id=vnfd_dict['id'],
res_type=constants.RES_TYPE_VNFD, 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, evt_type=constants.RES_EVT_UPDATE,
tstamp=vnfd_dict[constants.RES_EVT_UPDATED_FLD]) tstamp=vnfd_dict[constants.RES_EVT_UPDATED_FLD])
return vnfd_dict return vnfd_dict
@ -295,7 +298,7 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin):
self._cos_db_plg.create_event( self._cos_db_plg.create_event(
context, res_id=vnfd_db['id'], context, res_id=vnfd_db['id'],
res_type=constants.RES_TYPE_VNFD, 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, evt_type=constants.RES_EVT_DELETE,
tstamp=vnfd_db[constants.RES_EVT_DELETED_FLD]) tstamp=vnfd_db[constants.RES_EVT_DELETED_FLD])
else: else:

View File

@ -209,6 +209,9 @@ class ClassifierNotFoundException(exceptions.NotFound):
message = _('Classifier %(classifier_id)s could not be found') 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 = { RESOURCE_ATTRIBUTE_MAP = {
'vims': { 'vims': {
@ -549,7 +552,152 @@ RESOURCE_ATTRIBUTE_MAP = {
'allow_put': False, 'allow_put': False,
'is_visible': True, '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") 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): def _validate_service_type_list(data, valid_values=None):
if not isinstance(data, list): if not isinstance(data, list):
msg = _("invalid data format for service list: '%s'") % data msg = _("invalid data format for service list: '%s'") % data

View File

@ -18,12 +18,15 @@ import os
import threading import threading
import time import time
import uuid import uuid
import yaml
from cryptography import fernet from cryptography import fernet
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
from oslo_utils import excutils from oslo_utils import excutils
from oslo_utils import strutils from oslo_utils import strutils
from tempfile import mkstemp
from toscaparser.tosca_template import ToscaTemplate
from tacker._i18n import _ from tacker._i18n import _
from tacker.common import driver_manager from tacker.common import driver_manager
@ -32,6 +35,7 @@ from tacker.common import log
from tacker.common import utils from tacker.common import utils
from tacker import context as t_context from tacker import context as t_context
from tacker.db.nfvo import nfvo_db from tacker.db.nfvo import nfvo_db
from tacker.db.nfvo import ns_db
from tacker.db.nfvo import vnffg_db from tacker.db.nfvo import vnffg_db
from tacker.extensions import nfvo from tacker.extensions import nfvo
from tacker import manager from tacker import manager
@ -48,7 +52,8 @@ def config_opts():
return [('nfvo_vim', NfvoPlugin.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 """NFVO reference plugin for NFVO extension
Implements the NFVO extension and defines public facing APIs for VIM 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'], vim_auth=vim_obj['auth_cred'],
resource_type=resource, resource_type=resource,
resource_name=name) 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'] DEFAULT_ALARM_ACTIONS = ['respawn', 'log', 'log_and_kill', 'notify']
RES_TYPE_VNFD = "vnfd" RES_TYPE_VNFD = "vnfd"
RES_TYPE_NSD = "nsd"
RES_TYPE_VNF = "vnf" RES_TYPE_VNF = "vnf"
RES_TYPE_VIM = "vim" RES_TYPE_VIM = "vim"
@ -64,8 +65,8 @@ RES_EVT_DELETE = "DELETE"
RES_EVT_UPDATE = "UPDATE" RES_EVT_UPDATE = "UPDATE"
RES_EVT_MONITOR = "MONITOR" RES_EVT_MONITOR = "MONITOR"
RES_EVT_SCALE = "SCALE" RES_EVT_SCALE = "SCALE"
RES_EVT_VNFD_NA_STATE = "Not Applicable" RES_EVT_NA_STATE = "Not Applicable"
RES_EVT_VNFD_ONBOARDED = "OnBoarded" RES_EVT_ONBOARDED = "OnBoarded"
RES_EVT_CREATED_FLD = "created_at" RES_EVT_CREATED_FLD = "created_at"
RES_EVT_DELETED_FLD = "deleted_at" RES_EVT_DELETED_FLD = "deleted_at"

View File

@ -37,14 +37,14 @@ class VnfdTestCreate(base.BaseTackerTest):
vnfd_id = vnfd_instance['vnfd']['id'] vnfd_id = vnfd_instance['vnfd']['id']
self.verify_vnfd_events( self.verify_vnfd_events(
vnfd_id, evt_constants.RES_EVT_CREATE, vnfd_id, evt_constants.RES_EVT_CREATE,
evt_constants.RES_EVT_VNFD_ONBOARDED) evt_constants.RES_EVT_ONBOARDED)
try: try:
self.client.delete_vnfd(vnfd_id) self.client.delete_vnfd(vnfd_id)
except Exception: except Exception:
assert False, "vnfd Delete failed" assert False, "vnfd Delete failed"
self.verify_vnfd_events(vnfd_id, evt_constants.RES_EVT_DELETE, 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): def test_tosca_vnfd(self):
self._test_create_list_delete_tosca_vnfd('sample-tosca-vnfd.yaml', 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.assertIsNotNone(vnfd_id)
self.verify_vnfd_events( self.verify_vnfd_events(
vnfd_id, evt_constants.RES_EVT_CREATE, vnfd_id, evt_constants.RES_EVT_CREATE,
evt_constants.RES_EVT_VNFD_ONBOARDED) evt_constants.RES_EVT_ONBOARDED)
return vnfd_instance return vnfd_instance
def _test_vnfd_delete(self, vnfd_instance): def _test_vnfd_delete(self, vnfd_instance):
@ -45,7 +45,7 @@ class VnfmTestParam(base.BaseTackerTest):
except Exception: except Exception:
assert False, "vnfd Delete failed" assert False, "vnfd Delete failed"
self.verify_vnfd_events(vnfd_id, evt_constants.RES_EVT_DELETE, 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: try:
vnfd_d = self.client.show_vnfd(vnfd_id) vnfd_d = self.client.show_vnfd(vnfd_id)
except Exception: except Exception:

View File

@ -41,6 +41,7 @@ vnfd_alarm_respawn_tosca_template = _get_template(
'test_tosca_vnfd_alarm_respawn.yaml') 'test_tosca_vnfd_alarm_respawn.yaml')
vnfd_alarm_scale_tosca_template = _get_template( vnfd_alarm_scale_tosca_template = _get_template(
'test_tosca_vnfd_alarm_scale.yaml') 'test_tosca_vnfd_alarm_scale.yaml')
nsd_tosca_template = yaml.load(_get_template('tosca_nsd_template.yaml'))
def get_dummy_vnfd_obj(): def get_dummy_vnfd_obj():
@ -177,3 +178,11 @@ def get_dummy_vnffg_obj_vnf_mapping():
'VNF3': '7168062e-9fa1-4203-8cb7-f5c99ff3ee1b' 'VNF3': '7168062e-9fa1-4203-8cb7-f5c99ff3ee1b'
}, },
'symmetrical': False}} '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: elif {'name': ['VNF3']} in args:
return [{'id': self.vnf3_vnfd_id}] return [{'id': self.vnf3_vnfd_id}]
else: else:
return None return []
def get_vnfs(self, *args, **kwargs): def get_vnfs(self, *args, **kwargs):
if {'vnfd_id': [self.vnf1_vnfd_id]} in args: 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, self._driver_manager.invoke.assert_called_with(mock.ANY, mock.ANY,
fc_id=mock.ANY, fc_id=mock.ANY,
auth_attr=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) mock_update_imports.assert_called_once_with(yaml_dict)
self._cos_db_plugin.create_event.assert_called_once_with( self._cos_db_plugin.create_event.assert_called_once_with(
self.context, evt_type=constants.RES_EVT_CREATE, res_id=mock.ANY, 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) res_type=constants.RES_TYPE_VNFD, tstamp=mock.ANY)
def test_create_vnfd_no_service_types(self): 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)) raise vnfm.ParamYAMLNotWellFormed(error_msg_details=str(e))
toscautils.updateimports(vnfd_dict) toscautils.updateimports(vnfd_dict)
if 'substitution_mappings' in str(vnfd_dict):
toscautils.check_for_substitution_mappings(vnfd_dict,
parsed_params)
try: try:
tosca = tosca_template.ToscaTemplate(parsed_params=parsed_params, tosca = tosca_template.ToscaTemplate(parsed_params=parsed_params,

View File

@ -99,6 +99,38 @@ def updateimports(template):
LOG.debug(_("%s"), path) 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 @log.log
def get_vdu_monitoring(template): def get_vdu_monitoring(template):
monitoring_dict = {} monitoring_dict = {}