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
244
doc/source/devref/nsd_usage_guide.rst
Normal file
244
doc/source/devref/nsd_usage_guide.rst
Normal file
@ -0,0 +1,244 @@
|
||||
..
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
not use this file except in compliance with the License. You may obtain
|
||||
a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
.. _ref-nsd:
|
||||
|
||||
==========================================================
|
||||
Orchestrating VNFs using Network Services Descriptor (NSD)
|
||||
==========================================================
|
||||
|
||||
To enable dynamic composition of network services, NFV introduces Network
|
||||
Service Descriptors (NSDs) that specify the network service to be created.
|
||||
This usage guide describes lifecycle of Network service descriptors and
|
||||
services.
|
||||
|
||||
NSD in Ocata can be used for creating multiple (related) VNFs in one shot
|
||||
using a single TOSCA template. This is a first (big) step into NSD, few
|
||||
follow-on enhancements like:
|
||||
1) Creating VLs / neutron networks using NSD (to support inter-VNF private VL)
|
||||
2) VNFFGD support in NSD.
|
||||
|
||||
Creating the NSD
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Once OpenStack along with Tacker has been successfully installed,
|
||||
deploy a sample VNFD templates using vnf1.yaml and vnf2.yaml as mentioned in
|
||||
reference section.
|
||||
|
||||
::
|
||||
tacker vnfd-create --vnfd-file vnfd1.yaml VNFD1
|
||||
|
||||
tacker vnfd-create --vnfd-file vnfd2.yaml VNFD2
|
||||
|
||||
The following code represents sample NSD which instantiates the above VNFs
|
||||
|
||||
::
|
||||
|
||||
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
|
||||
imports:
|
||||
- VNFD1
|
||||
- VNFD2
|
||||
topology_template:
|
||||
node_templates:
|
||||
VNF1:
|
||||
type: tosca.nodes.nfv.VNF1
|
||||
requirements:
|
||||
- virtualLink1: VL1
|
||||
- virtualLink2: VL2
|
||||
VNF2:
|
||||
type: tosca.nodes.nfv.VNF2
|
||||
VL1:
|
||||
type: tosca.nodes.nfv.VL
|
||||
properties:
|
||||
network_name: net0
|
||||
vendor: tacker
|
||||
VL2:
|
||||
type: tosca.nodes.nfv.VL
|
||||
properties:
|
||||
network_name: net_mgmt
|
||||
vendor: tacker
|
||||
|
||||
In above NSD template VL1 and VL2 are substituting the virtuallinks of VNF1.
|
||||
To onboard the above NSD:
|
||||
|
||||
**tacker nsd-create --nsd-file <nsd file> <nsd name>**
|
||||
|
||||
Creating the NS
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
To create a NS, you must have onboarded corresponding NSD and
|
||||
VNFDS(which NS is substituting)
|
||||
|
||||
Tacker provides the following CLI to create NS:
|
||||
|
||||
**tacker ns-create --nsd-id <nsd-id> <ns-name>**
|
||||
|
||||
Reference
|
||||
~~~~~~~~~
|
||||
|
||||
VNF1 sample template for nsd named vnfd1.yaml:
|
||||
|
||||
::
|
||||
|
||||
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
|
||||
description: Demo example
|
||||
node_types:
|
||||
tosca.nodes.nfv.VNF1:
|
||||
requirements:
|
||||
- virtualLink1:
|
||||
type: tosca.nodes.nfv.VL
|
||||
required: true
|
||||
- virtualLink2:
|
||||
type: tosca.nodes.nfv.VL
|
||||
required: true
|
||||
capabilities:
|
||||
forwader1:
|
||||
type: tosca.capabilities.nfv.Forwarder
|
||||
forwader2:
|
||||
type: tosca.capabilities.nfv.Forwarder
|
||||
|
||||
topology_template:
|
||||
substitution_mappings:
|
||||
node_type: tosca.nodes.nfv.VNF1
|
||||
requirements:
|
||||
virtualLink1: [CP11, virtualLink]
|
||||
virtualLink2: [CP14, virtualLink]
|
||||
capabilities:
|
||||
forwarder1: [CP11, forwarder]
|
||||
forwarder2: [CP14, forwarder]
|
||||
node_templates:
|
||||
VDU1:
|
||||
type: tosca.nodes.nfv.VDU.Tacker
|
||||
properties:
|
||||
image: cirros-0.3.4-x86_64-uec
|
||||
flavor: m1.tiny
|
||||
availability_zone: nova
|
||||
mgmt_driver: noop
|
||||
config: |
|
||||
param0: key1
|
||||
param1: key2
|
||||
CP11:
|
||||
type: tosca.nodes.nfv.CP.Tacker
|
||||
properties:
|
||||
management: true
|
||||
anti_spoofing_protection: false
|
||||
requirements:
|
||||
- virtualBinding:
|
||||
node: VDU1
|
||||
|
||||
VDU2:
|
||||
type: tosca.nodes.nfv.VDU.Tacker
|
||||
properties:
|
||||
image: cirros-0.3.4-x86_64-uec
|
||||
flavor: m1.medium
|
||||
availability_zone: nova
|
||||
mgmt_driver: noop
|
||||
config: |
|
||||
param0: key1
|
||||
param1: key2
|
||||
CP13:
|
||||
type: tosca.nodes.nfv.CP.Tacker
|
||||
properties:
|
||||
management: true
|
||||
anti_spoofing_protection: false
|
||||
requirements:
|
||||
- virtualLink:
|
||||
node: VL1
|
||||
- virtualBinding:
|
||||
node: VDU2
|
||||
CP14:
|
||||
type: tosca.nodes.nfv.CP.Tacker
|
||||
properties:
|
||||
management: true
|
||||
anti_spoofing_protection: false
|
||||
requirements:
|
||||
- virtualBinding:
|
||||
node: VDU2
|
||||
VL1:
|
||||
type: tosca.nodes.nfv.VL
|
||||
properties:
|
||||
network_name: net_mgmt
|
||||
vendor: Tacker
|
||||
VL2:
|
||||
type: tosca.nodes.nfv.VL
|
||||
properties:
|
||||
network_name: net0
|
||||
vendor: Tacker
|
||||
|
||||
VNF2 sample template for nsd named vnfd2.yaml:
|
||||
|
||||
::
|
||||
|
||||
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
|
||||
description: Demo example
|
||||
|
||||
node_types:
|
||||
tosca.nodes.nfv.VNF2:
|
||||
capabilities:
|
||||
forwarder1:
|
||||
type: tosca.capabilities.nfv.Forwarder
|
||||
topology_template:
|
||||
substitution_mappings:
|
||||
node_type: tosca.nodes.nfv.VNF2
|
||||
capabilities:
|
||||
forwarder1: [CP21, forwarder]
|
||||
node_templates:
|
||||
VDU1:
|
||||
type: tosca.nodes.nfv.VDU.Tacker
|
||||
properties:
|
||||
image: cirros-0.3.4-x86_64-uec
|
||||
flavor: m1.tiny
|
||||
availability_zone: nova
|
||||
mgmt_driver: noop
|
||||
config: |
|
||||
param0: key1
|
||||
param1: key2
|
||||
CP21:
|
||||
type: tosca.nodes.nfv.CP.Tacker
|
||||
properties:
|
||||
management: true
|
||||
anti_spoofing_protection: false
|
||||
requirements:
|
||||
- virtualLink:
|
||||
node: VL1
|
||||
- virtualBinding:
|
||||
node: VDU1
|
||||
VDU2:
|
||||
type: tosca.nodes.nfv.VDU.Tacker
|
||||
properties:
|
||||
image: cirros-0.3.4-x86_64-uec
|
||||
flavor: m1.medium
|
||||
availability_zone: nova
|
||||
mgmt_driver: noop
|
||||
CP22:
|
||||
type: tosca.nodes.nfv.CP.Tacker
|
||||
properties:
|
||||
management: true
|
||||
anti_spoofing_protection: false
|
||||
requirements:
|
||||
- virtualLink:
|
||||
node: VL2
|
||||
- virtualBinding:
|
||||
node: VDU2
|
||||
VL1:
|
||||
type: tosca.nodes.nfv.VL
|
||||
properties:
|
||||
network_name: net_mgmt
|
||||
vendor: Tacker
|
||||
VL2:
|
||||
type: tosca.nodes.nfv.VL
|
||||
properties:
|
||||
network_name: net0
|
||||
vendor: Tacker
|
||||
|
||||
|
@ -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
tacker/nfvo/drivers/workflow/__init__.py
Normal file
0
tacker/nfvo/drivers/workflow/__init__.py
Normal file
53
tacker/nfvo/drivers/workflow/mistral.py
Normal file
53
tacker/nfvo/drivers/workflow/mistral.py
Normal file
@ -0,0 +1,53 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import yaml
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from tacker.nfvo.drivers.workflow import workflow_generator
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
FREQUENCY = 10
|
||||
SLEEP = 5
|
||||
|
||||
|
||||
class MistralClient(object):
|
||||
|
||||
def __init__(self, context, client, resource, action):
|
||||
self.context = context
|
||||
self.client = client
|
||||
self.wg = workflow_generator.WorkflowGenerator(resource, action)
|
||||
|
||||
def prepare_workflow(self, **kwargs):
|
||||
self.wg.task(**kwargs)
|
||||
|
||||
def create_workflow(self):
|
||||
definition_yaml = yaml.dump(self.wg.definition)
|
||||
wf = self.client.workflows.create(definition_yaml)
|
||||
wf_id = wf[0].id
|
||||
return wf_id
|
||||
|
||||
def delete_workflow(self, wf_id):
|
||||
self.client.workflows.delete(wf_id)
|
||||
|
||||
def execute_workflow(self, wf_id):
|
||||
wf_ex = self.client.executions.create(
|
||||
workflow_identifier=wf_id,
|
||||
workflow_input=self.wg.input_dict,
|
||||
wf_params={})
|
||||
return wf_ex
|
||||
|
||||
def get_execution_state(self, ex_id):
|
||||
return self.client.executions.get(ex_id).state
|
||||
|
||||
def delete_execution(self, ex_id):
|
||||
self.client.executions.delete(ex_id)
|
27
tacker/nfvo/drivers/workflow/workflow.py
Normal file
27
tacker/nfvo/drivers/workflow/workflow.py
Normal file
@ -0,0 +1,27 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
class Workflow(object):
|
||||
def __init__(self, wf_name, wf_type, version='2.0'):
|
||||
self._wf_name = wf_name
|
||||
self._wf_type = wf_type
|
||||
self._version = '2.0'
|
||||
|
||||
def get_name(self):
|
||||
return self.wf_name
|
||||
|
||||
def get_type(self):
|
||||
return self.wf_type
|
||||
|
||||
def get_version(self):
|
||||
self._version
|
190
tacker/nfvo/drivers/workflow/workflow_generator.py
Normal file
190
tacker/nfvo/drivers/workflow/workflow_generator.py
Normal file
@ -0,0 +1,190 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import ast
|
||||
import uuid
|
||||
|
||||
from six import iteritems
|
||||
|
||||
|
||||
OUTPUT = {
|
||||
'create_vnf': ['vnf_id', 'vim_id', 'mgmt_url', 'status']
|
||||
}
|
||||
|
||||
|
||||
class WorkflowGenerator(object):
|
||||
def __init__(self, resource, action):
|
||||
self.resource = resource
|
||||
self.action = action
|
||||
self.wf_name = self.action + '_' + self.resource
|
||||
self.wf_identifier = 'std.' + self.wf_name + str(uuid.uuid4())
|
||||
self.task = getattr(self, self.wf_name)
|
||||
self.input_dict = dict()
|
||||
self._build_basic_workflow()
|
||||
|
||||
def _build_basic_workflow(self):
|
||||
self.definition = {
|
||||
'version': '2.0',
|
||||
self.wf_identifier: {
|
||||
'type': 'direct',
|
||||
'input': [self.resource]
|
||||
}
|
||||
}
|
||||
|
||||
def _get_vim_id(self):
|
||||
pass
|
||||
|
||||
def _get_vnfd_id(self):
|
||||
pass
|
||||
|
||||
def _get_vnf_name(self):
|
||||
pass
|
||||
|
||||
def _get_attr(self):
|
||||
pass
|
||||
|
||||
def _get_description(self):
|
||||
pass
|
||||
|
||||
def _add_create_vnf_tasks(self, ns):
|
||||
vnfds = ns['vnfd_details']
|
||||
task_dict = dict()
|
||||
for vnfd_name, vnfd_info in iteritems(vnfds):
|
||||
nodes = vnfd_info['instances']
|
||||
for node in nodes:
|
||||
task = self.wf_name + '_' + node
|
||||
task_dict[task] = {
|
||||
'action': 'tacker.create_vnf body=<% $.vnf.{0} '
|
||||
'%>'.format(node),
|
||||
'input': {'body': '<% $.vnf.{0} %>'.format(node)},
|
||||
'publish': {
|
||||
'vnf_id_' + node: '<% task({0}).result.vnf.id '
|
||||
'%>'.format(task),
|
||||
'vim_id_' + node: '<% task({0}).result.vnf.vim_id'
|
||||
' %>'.format(task),
|
||||
'mgmt_url_' + node: '<% task({0}).result.vnf.mgmt_url'
|
||||
' %>'.format(task),
|
||||
'status_' + node: '<% task({0}).result.vnf.status'
|
||||
' %>'.format(task),
|
||||
},
|
||||
'on-success': ['wait_vnf_active_%s' % node]
|
||||
}
|
||||
return task_dict
|
||||
|
||||
def _add_wait_vnf_tasks(self, ns):
|
||||
vnfds = ns['vnfd_details']
|
||||
task_dict = dict()
|
||||
for vnfd_name, vnfd_info in iteritems(vnfds):
|
||||
nodes = vnfd_info['instances']
|
||||
for node in nodes:
|
||||
task = 'wait_vnf_active_%s' % node
|
||||
task_dict[task] = {
|
||||
'action': 'tacker.show_vnf vnf=<% $.vnf_id_{0} '
|
||||
'%>'.format(node),
|
||||
'retry': {
|
||||
'count': 10,
|
||||
'delay': 10,
|
||||
'break-on': '<% $.status_{0} = "ACTIVE" '
|
||||
'%>'.format(node),
|
||||
'break-on': '<% $.status_{0} = "ERROR"'
|
||||
' %>'.format(node),
|
||||
'continue-on': '<% $.status_{0} = "PENDING_CREATE" '
|
||||
'%>'.format(node),
|
||||
},
|
||||
'publish': {
|
||||
'mgmt_url_' + node: ' <% task({0}).result.vnf.'
|
||||
'mgmt_url %>'.format(task),
|
||||
'status_' + node: '<% task({0}).result.vnf.status'
|
||||
' %>'.format(task),
|
||||
},
|
||||
'on-success': [
|
||||
{'delete_vnf_' + node: '<% $.status_{0}='
|
||||
'"ERROR" %>'.format(node)}
|
||||
]
|
||||
}
|
||||
return task_dict
|
||||
|
||||
def _add_delete_vnf_tasks(self, ns):
|
||||
vnfds = ns['vnfd_details']
|
||||
task_dict = dict()
|
||||
for vnfd_name, vnfd_info in iteritems(vnfds):
|
||||
nodes = vnfd_info['instances']
|
||||
for node in nodes:
|
||||
task = 'delete_vnf_%s' % node
|
||||
task_dict[task] = {
|
||||
'action': 'tacker.delete_vnf vnf=<% $.vnf_id_{0}'
|
||||
'%>'.format(node),
|
||||
}
|
||||
return task_dict
|
||||
|
||||
def _build_output_dict(self, ns):
|
||||
vnfds = ns['vnfd_details']
|
||||
task_dict = dict()
|
||||
for vnfd_name, vnfd_info in iteritems(vnfds):
|
||||
nodes = vnfd_info['instances']
|
||||
for node in nodes:
|
||||
for op_name in OUTPUT[self.wf_name]:
|
||||
task_dict[op_name + '_' + node] = \
|
||||
'<% $.{0}_{1} %>'.format(op_name, node)
|
||||
return task_dict
|
||||
|
||||
def get_input_dict(self):
|
||||
return self.input_dict
|
||||
|
||||
def build_input(self, ns, params):
|
||||
vnfds = ns['vnfd_details']
|
||||
id = str(uuid.uuid4())
|
||||
self.input_dict = {'vnf': {}}
|
||||
for vnfd_name, vnfd_info in iteritems(vnfds):
|
||||
nodes = vnfd_info['instances']
|
||||
for node in nodes:
|
||||
self.input_dict['vnf'][node] = dict()
|
||||
self.input_dict['vnf'][node]['vnf'] = {
|
||||
'attributes': {},
|
||||
'vim_id': ns['ns'].get('vim_id', ''),
|
||||
'vnfd_id': vnfd_info['id'],
|
||||
'name': 'create_vnf_%s_%s' % (vnfd_info['id'],
|
||||
id)
|
||||
}
|
||||
if params.get(vnfd_name):
|
||||
self.input_dict['vnf'][node]['vnf']['attributes'] = {
|
||||
'param_values': params.get(vnfd_name)
|
||||
}
|
||||
|
||||
def create_vnf(self, **kwargs):
|
||||
ns = kwargs.get('ns')
|
||||
params = kwargs.get('params')
|
||||
# TODO(anyone): Keep this statements in a loop and
|
||||
# remove in all the methods.
|
||||
self.definition[self.wf_identifier]['tasks'] = dict()
|
||||
self.definition[self.wf_identifier]['tasks'].update(
|
||||
self._add_create_vnf_tasks(ns))
|
||||
self.definition[self.wf_identifier]['tasks'].update(
|
||||
self._add_wait_vnf_tasks(ns))
|
||||
self.definition[self.wf_identifier]['tasks'].update(
|
||||
self._add_delete_vnf_tasks(ns))
|
||||
self.definition[self.wf_identifier]['output'] = \
|
||||
self._build_output_dict(ns)
|
||||
self.build_input(ns, params)
|
||||
|
||||
def delete_vnf(self, ns):
|
||||
ns_dict = {'vnfd_details': {}}
|
||||
vnf_ids = ast.literal_eval(ns['vnf_ids'])
|
||||
self.definition[self.wf_identifier]['input'] = []
|
||||
for vnf in vnf_ids.keys():
|
||||
vnf_key = 'vnf_id_' + vnf
|
||||
self.definition[self.wf_identifier]['input'].append(vnf_key)
|
||||
self.input_dict[vnf_key] = vnf_ids[vnf]
|
||||
ns_dict['vnfd_details'][vnf] = {'instances': [vnf]}
|
||||
self.definition[self.wf_identifier]['tasks'] = dict()
|
||||
self.definition[self.wf_identifier]['tasks'].update(
|
||||
self._add_delete_vnf_tasks(ns_dict))
|
@ -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
|
||||
|
37
tacker/tests/etc/samples/test-ns-nsd.yaml
Normal file
37
tacker/tests/etc/samples/test-ns-nsd.yaml
Normal file
@ -0,0 +1,37 @@
|
||||
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
|
||||
imports:
|
||||
- test-ns-vnfd1
|
||||
- test-ns-vnfd2
|
||||
|
||||
topology_template:
|
||||
inputs:
|
||||
vl1_name:
|
||||
type: string
|
||||
description: name of VL1 virtuallink
|
||||
default: net_mgmt
|
||||
vl2_name:
|
||||
type: string
|
||||
description: name of VL2 virtuallink
|
||||
default: net0
|
||||
node_templates:
|
||||
VNF1:
|
||||
type: tosca.nodes.nfv.VNF1
|
||||
requirements:
|
||||
- virtualLink1: VL1
|
||||
- virtualLink2: VL2
|
||||
|
||||
VNF2:
|
||||
type: tosca.nodes.nfv.VNF2
|
||||
|
||||
VL1:
|
||||
type: tosca.nodes.nfv.VL
|
||||
properties:
|
||||
network_name: {get_input: vl1_name}
|
||||
vendor: tacker
|
||||
|
||||
VL2:
|
||||
type: tosca.nodes.nfv.VL
|
||||
properties:
|
||||
network_name: {get_input: vl2_name}
|
||||
vendor: tacker
|
||||
|
98
tacker/tests/etc/samples/test-ns-vnfd1.yaml
Normal file
98
tacker/tests/etc/samples/test-ns-vnfd1.yaml
Normal file
@ -0,0 +1,98 @@
|
||||
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
|
||||
|
||||
description: Demo example
|
||||
node_types:
|
||||
tosca.nodes.nfv.VNF1:
|
||||
requirements:
|
||||
- virtualLink1:
|
||||
type: tosca.nodes.nfv.VL
|
||||
required: true
|
||||
- virtualLink2:
|
||||
type: tosca.nodes.nfv.VL
|
||||
required: true
|
||||
capabilities:
|
||||
forwader1:
|
||||
type: tosca.capabilities.nfv.Forwarder
|
||||
forwader2:
|
||||
type: tosca.capabilities.nfv.Forwarder
|
||||
|
||||
topology_template:
|
||||
substitution_mappings:
|
||||
node_type: tosca.nodes.nfv.VNF1
|
||||
requirements:
|
||||
virtualLink1: [CP11, virtualLink]
|
||||
virtualLink2: [CP14, virtualLink]
|
||||
capabilities:
|
||||
forwarder1: [CP11, forwarder]
|
||||
forwarder2: [CP14, forwarder]
|
||||
|
||||
node_templates:
|
||||
VDU1:
|
||||
type: tosca.nodes.nfv.VDU.Tacker
|
||||
properties:
|
||||
image: cirros-0.3.4-x86_64-uec
|
||||
flavor: m1.tiny
|
||||
availability_zone: nova
|
||||
mgmt_driver: noop
|
||||
config: |
|
||||
param0: key1
|
||||
param1: key2
|
||||
|
||||
CP11:
|
||||
type: tosca.nodes.nfv.CP.Tacker
|
||||
properties:
|
||||
management: true
|
||||
anti_spoofing_protection: false
|
||||
requirements:
|
||||
- virtualBinding:
|
||||
node: VDU1
|
||||
|
||||
CP12:
|
||||
type: tosca.nodes.nfv.CP.Tacker
|
||||
properties:
|
||||
anti_spoofing_protection: false
|
||||
requirements:
|
||||
- virtualLink:
|
||||
node: VL2
|
||||
- virtualBinding:
|
||||
node: VDU1
|
||||
|
||||
VDU2:
|
||||
type: tosca.nodes.nfv.VDU.Tacker
|
||||
properties:
|
||||
image: cirros-0.3.4-x86_64-uec
|
||||
flavor: m1.medium
|
||||
availability_zone: nova
|
||||
mgmt_driver: noop
|
||||
config: |
|
||||
param0: key1
|
||||
param1: key2
|
||||
|
||||
CP13:
|
||||
type: tosca.nodes.nfv.CP.Tacker
|
||||
properties:
|
||||
management: true
|
||||
requirements:
|
||||
- virtualLink:
|
||||
node: VL1
|
||||
- virtualBinding:
|
||||
node: VDU2
|
||||
|
||||
CP14:
|
||||
type: tosca.nodes.nfv.CP.Tacker
|
||||
requirements:
|
||||
- virtualBinding:
|
||||
node: VDU2
|
||||
|
||||
VL1:
|
||||
type: tosca.nodes.nfv.VL
|
||||
properties:
|
||||
network_name: net_mgmt
|
||||
vendor: Tacker
|
||||
|
||||
VL2:
|
||||
type: tosca.nodes.nfv.VL
|
||||
properties:
|
||||
network_name: net0
|
||||
vendor: Tacker
|
||||
|
68
tacker/tests/etc/samples/test-ns-vnfd2.yaml
Normal file
68
tacker/tests/etc/samples/test-ns-vnfd2.yaml
Normal file
@ -0,0 +1,68 @@
|
||||
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
|
||||
|
||||
description: Demo example
|
||||
|
||||
node_types:
|
||||
tosca.nodes.nfv.VNF2:
|
||||
capabilities:
|
||||
forwarder1:
|
||||
type: tosca.capabilities.nfv.Forwarder
|
||||
topology_template:
|
||||
substitution_mappings:
|
||||
node_type: tosca.nodes.nfv.VNF2
|
||||
capabilities:
|
||||
forwarder1: [CP21, forwarder]
|
||||
node_templates:
|
||||
VDU1:
|
||||