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:
parent
d311cfb77a
commit
15c4d7ec5d
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
=================
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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': {
|
||||
|
|
|
@ -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': {
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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"""
|
||||
|
|
|
@ -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)
|
|
@ -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
|
|
@ -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))
|
|
@ -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']
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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')
|
|
@ -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'}}}}}
|
||||
|
|
|
@ -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'])
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue