Implement VNFFG support for NS

This patch will add VNFFG support for NS. In NSD, users can
describe VNFDs and nested VNFFGDs inside. When NS is created,
VNFs and VNFFGs are also created too.

Work items of this patch:
1. Add sample nsd templates
2. Update NFVO plugin to adapt VNFFGD template extraction
    from NSD and create VNFFGs using that template
3. Modify Mistral workflow to create/delete VNFFGs using NSD

User guide, defref will be added in following patches.

Co-Authored-By: Cong Phuoc Hoang <hoangphuocbk2.07@gmail.com>

Change-Id: Id5827e66aec0231bf27e87fc96d8fc6bc5cb9c26
Partially-implements: blueprint vnffg-ns
changes/66/558166/50
Trinh Nguyen 5 years ago committed by dharmendra
parent 22c33bda5a
commit 422055b9ba
  1. 6
      releasenotes/notes/implement-vnffg-support-for-ns-3acd9759e87c9d0c.yaml
  2. 5
      samples/tosca-templates/vnffg-nsd/ns_param.yaml
  3. 109
      samples/tosca-templates/vnffg-nsd/tosca-multiple-vnffg-nsd.yaml
  4. 78
      samples/tosca-templates/vnffg-nsd/tosca-single-vnffg-nsd.yaml
  5. 67
      samples/tosca-templates/vnffg-nsd/tosca-vnfd1-sample.yaml
  6. 61
      samples/tosca-templates/vnffg-nsd/tosca-vnfd2-sample.yaml
  7. 4
      samples/tosca-templates/vnffgd/tosca-vnffgd-sample.yaml
  8. 41
      tacker/db/migration/alembic_migrations/versions/4747cc26b9c6_add_support_vnffg_to_ns_database.py
  9. 2
      tacker/db/migration/alembic_migrations/versions/HEAD
  10. 46
      tacker/db/nfvo/ns_db.py
  11. 18
      tacker/db/nfvo/vnffg_db.py
  12. 22
      tacker/extensions/nfvo.py
  13. 146
      tacker/nfvo/drivers/workflow/workflow_generator.py
  14. 58
      tacker/nfvo/nfvo_plugin.py
  15. 263
      tacker/tests/unit/nfvo/drivers/workflow/test_workflow_generator.py
  16. 11
      tacker/tests/unit/nfvo/test_nfvo_plugin.py

@ -0,0 +1,6 @@
---
features:
- |
This patch will add VNFFG support for NS. In NSD, users can describe VNFDs
and nested VNFFGD inside. When NS is created, VNFs and VNFFG are also
created.

@ -0,0 +1,5 @@
nsd:
vl1_name: net_mgmt
vl2_name: net0
net_src_port_id: c0a40f9c-d229-40b7-871f-55131cf17783
ip_dest_prefix: 10.10.0.11/24

@ -0,0 +1,109 @@
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
description: Import VNFDs(already on-boarded) with input parameters
imports:
- sample-vnfd1
- sample-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
net_src_port_id:
type: string
description: neutron port id of source port
ip_dest_prefix:
type: string
description: IP prefix of destination port
node_templates:
VNF1:
type: tosca.nodes.nfv.VNF1
requirements:
- virtualLink1: VL1
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
Forwarding_path1:
type: tosca.nodes.nfv.FP.TackerV2
description: creates path inside ns (src_port->CP12->CP22->dst_port)
properties:
id: 51
policy:
type: ACL
criteria:
- name: block_tcp
classifier:
network_src_port_id: {get_input: net_src_port_id}
destination_port_range: 80-1024
ip_proto: 6
ip_dst_prefix: {get_input: ip_dest_prefix}
path:
- forwarder: sample-vnfd1
capability: CP12
- forwarder: sample-vnfd2
capability: CP22
Forwarding_path2:
type: tosca.nodes.nfv.FP.TackerV2
description: creates path inside ns (src_port->CP12->dst_port)
properties:
id: 52
policy:
type: ACL
criteria:
- name: block_tcp
classifier:
network_src_port_id: {get_input: net_src_port_id}
destination_port_range: 8080-8080
ip_proto: 6
ip_dst_prefix: {get_input: ip_dest_prefix}
path:
- forwarder: sample-vnfd1
capability: CP12
groups:
VNFFG1:
type: tosca.groups.nfv.VNFFG
description: HTTP to Corporate Net
properties:
vendor: tacker
version: 1.0
number_of_endpoints: 2
dependent_virtual_link: [VL1, VL2]
connection_point: [CP12, CP22]
constituent_vnfs: [sample-vnfd1, sample-vnfd2]
members: [Forwarding_path1]
VNFFG2:
type: tosca.groups.nfv.VNFFG
description: HTTP to Corporate Net
properties:
vendor: tacker
version: 1.0
number_of_endpoints: 1
dependent_virtual_link: [VL1]
connection_point: [CP12]
constituent_vnfs: [sample-vnfd1]
members: [Forwarding_path2]

@ -0,0 +1,78 @@
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
description: Import VNFDs(already on-boarded) with input parameters
imports:
- sample-vnfd1
- sample-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
net_src_port_id:
type: string
description: neutron port id of source port
ip_dest_prefix:
type: string
description: IP prefix of destination port
node_templates:
VNF1:
type: tosca.nodes.nfv.VNF1
requirements:
- virtualLink1: VL1
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
Forwarding_path1:
type: tosca.nodes.nfv.FP.TackerV2
description: creates path inside ns (src_port->CP12->CP22->dst_port)
properties:
id: 51
policy:
type: ACL
criteria:
- name: block_tcp
classifier:
network_src_port_id: {get_input: net_src_port_id}
destination_port_range: 80-1024
ip_proto: 6
ip_dst_prefix: {get_input: ip_dest_prefix}
path:
- forwarder: sample-vnfd1
capability: CP12
- forwarder: sample-vnfd2
capability: CP22
groups:
VNFFG1:
type: tosca.groups.nfv.VNFFG
description: HTTP to Corporate Net
properties:
vendor: tacker
version: 1.0
number_of_endpoints: 2
dependent_virtual_link: [VL1, VL2]
connection_point: [CP12, CP22]
constituent_vnfs: [sample-vnfd1, sample-vnfd2]
members: [Forwarding_path1]

@ -0,0 +1,67 @@
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
description: VirtualLinks of CP11 and CP22 will be provided by NS descriptor
node_types:
tosca.nodes.nfv.VNF1:
requirements:
- virtualLink1:
type: tosca.nodes.nfv.VL
required: true
capabilities:
forwader1:
type: tosca.capabilities.nfv.Forwarder
topology_template:
substitution_mappings:
node_type: tosca.nodes.nfv.VNF1
requirements:
virtualLink1: [CP11, virtualLink]
capabilities:
forwarder1: [CP11, forwarder]
node_templates:
VDU1:
type: tosca.nodes.nfv.VDU.Tacker
properties:
image: cirros-0.3.5-x86_64-disk
flavor: m1.tiny
availability_zone: nova
mgmt_driver: noop
config: |
param0: key1
param1: key2
user_data_format: RAW
user_data: |
#!/bin/sh
echo 1 > /proc/sys/net/ipv4/ip_forward
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
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,61 @@
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.5-x86_64-disk
flavor: m1.tiny
availability_zone: nova
mgmt_driver: noop
config: |
param0: key1
param1: key2
user_data_format: RAW
user_data: |
#!/bin/sh
echo 1 > /proc/sys/net/ipv4/ip_forward
CP21:
type: tosca.nodes.nfv.CP.Tacker
properties:
management: true
anti_spoofing_protection: false
requirements:
- virtualLink:
node: VL1
- virtualBinding:
node: VDU1
CP22:
type: tosca.nodes.nfv.CP.Tacker
requirements:
- virtualLink:
node: VL2
- virtualBinding:
node: VDU1
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

@ -16,10 +16,10 @@ topology_template:
criteria:
- name: block_tcp
classifier:
network_src_port_id: 640dfd77-c92b-45a3-b8fc-22712de480e1
network_src_port_id: 14ad4f29-629f-4b97-8bc8-86e96cb49974
destination_port_range: 80-1024
ip_proto: 6
ip_dst_prefix: 192.168.1.2/24
ip_dst_prefix: 10.10.0.5/24
path:
- forwarder: VNFD1
capability: CP12

@ -0,0 +1,41 @@
# Copyright 2018 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
"""add support vnffg to ns database
Revision ID: 4747cc26b9c6
Revises: 5d490546290c
Create Date: 2018-06-27 03:18:12.227673
"""
# revision identifiers, used by Alembic.
revision = '4747cc26b9c6'
down_revision = '5d490546290c'
from alembic import op
import sqlalchemy as sa
from tacker.db import types
def upgrade(active_plugins=None, options=None):
op.add_column('ns', sa.Column(
'vnffg_ids', sa.TEXT(length=65535), nullable=True))
op.add_column('vnffgs', sa.Column(
'ns_id', types.Uuid(length=36), nullable=True))
op.create_foreign_key('vnffg_foreign_key',
'vnffgs', 'ns',
['ns_id'], ['id'])

@ -98,6 +98,9 @@ class NS(model_base.BASE, models_v1.HasId, models_v1.HasTenant,
# Dict of VNF details that network service launches
vnf_ids = sa.Column(sa.TEXT(65535), nullable=True)
# VNFFG ids
vnffg_ids = sa.Column(sa.TEXT(65535), nullable=True)
# Dict of mgmt urls that network servic launches
mgmt_urls = sa.Column(sa.TEXT(65535), nullable=True)
@ -161,8 +164,8 @@ class NSPluginDb(network_service.NSPluginBase, db_base.CommonDbMixin):
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')
'vnf_ids', 'vnffg_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)
@ -209,18 +212,14 @@ class NSPluginDb(network_service.NSPluginBase, db_base.CommonDbMixin):
tstamp=nsd_dict[constants.RES_EVT_CREATED_FLD])
return nsd_dict
def delete_nsd(self,
context,
nsd_id,
soft_delete=True):
def delete_nsd(self, context, nsd_id, soft_delete=True):
with context.session.begin(subtransactions=True):
nss_db = context.session.query(NS).filter_by(
nsd_id=nsd_id).first()
if nss_db is not None and nss_db.deleted_at is None:
raise nfvo.NSDInUse(nsd_id=nsd_id)
nsd_db = self._get_resource(context, NSD,
nsd_id)
nsd_db = self._get_resource(context, NSD, nsd_id)
if soft_delete:
nsd_db.update({'deleted_at': timeutils.utcnow()})
self._cos_db_plg.create_event(
@ -254,7 +253,7 @@ class NSPluginDb(network_service.NSPluginBase, db_base.CommonDbMixin):
nsd_id = ns['nsd_id']
vim_id = ns['vim_id']
name = ns.get('name')
ns_id = uuidutils.generate_uuid()
ns_id = ns['ns_id']
description = None
if 'description' in ns:
description = ns.get('description')
@ -269,6 +268,7 @@ class NSPluginDb(network_service.NSPluginBase, db_base.CommonDbMixin):
name=name,
description=description,
vnf_ids=None,
vnffg_ids=None,
status=constants.PENDING_CREATE,
mgmt_urls=None,
nsd_id=nsd_id,
@ -291,11 +291,12 @@ class NSPluginDb(network_service.NSPluginBase, db_base.CommonDbMixin):
return self._make_ns_dict(ns_db)
def create_ns_post(self, context, ns_id, mistral_obj,
vnfd_dict, error_reason):
vnfd_dict, vnffgd_templates, error_reason):
LOG.debug('ns ID %s', ns_id)
output = ast.literal_eval(mistral_obj.output)
mgmt_urls = dict()
vnf_ids = dict()
vnffg_ids = dict()
if len(output) > 0:
for vnfd_name, vnfd_val in iteritems(vnfd_dict):
for instance in vnfd_val['instances']:
@ -305,28 +306,37 @@ class NSPluginDb(network_service.NSPluginBase, db_base.CommonDbMixin):
vnf_ids[instance] = output['vnf_id_' + instance]
vnf_ids = str(vnf_ids)
mgmt_urls = str(mgmt_urls)
if vnffgd_templates:
for vnffg_name in vnffgd_templates:
vnffg_output = 'vnffg_id_%s' % vnffg_name
vnffg_ids[vnffg_name] = output[vnffg_output]
vnffg_ids = str(vnffg_ids)
if not vnf_ids:
vnf_ids = None
if not mgmt_urls:
mgmt_urls = None
if not vnffg_ids:
vnffg_ids = 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 = self._get_resource(context, NS, ns_id)
ns_db.update({'vnf_ids': vnf_ids})
ns_db.update({'vnffg_ids': vnffg_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])
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

@ -24,6 +24,7 @@ from sqlalchemy.orm import exc as orm_exc
from tacker.db import db_base
from tacker.db import model_base
from tacker.db import models_v1
from tacker.db.nfvo.ns_db import NS
from tacker.db import types
from tacker.extensions import nfvo
from tacker.extensions.nfvo_plugins import vnffg
@ -97,6 +98,9 @@ class Vnffg(model_base.BASE, models_v1.HasTenant, models_v1.HasId):
attributes = sa.Column(types.Json)
# Associated Network Service
ns_id = sa.Column(types.Uuid, sa.ForeignKey('ns.id'), nullable=True)
class VnffgNfp(model_base.BASE, models_v1.HasTenant, models_v1.HasId):
"""Network Forwarding Path Data Model"""
@ -332,7 +336,7 @@ class VnffgPluginDbMixin(vnffg.VNFFGPluginBase, db_base.CommonDbMixin):
param_value=param_vattrs_dict)
for param_key in param_vattrs_dict.keys():
if param_matched.get(param_key) is None:
raise nfvo.VnffgParamValueNotUsed(param_key=param_key)
LOG.warning("Param input %s not used.", param_key)
def _parametrize_topology_template(self, vnffg, template_db):
if vnffg.get('attributes') and \
@ -353,6 +357,7 @@ class VnffgPluginDbMixin(vnffg.VNFFGPluginBase, db_base.CommonDbMixin):
name = vnffg.get('name')
vnffg_id = vnffg.get('id') or uuidutils.generate_uuid()
template_id = vnffg['vnffgd_id']
ns_id = vnffg.get('ns_id', None)
symmetrical = vnffg['symmetrical']
with context.session.begin(subtransactions=True):
@ -377,6 +382,7 @@ class VnffgPluginDbMixin(vnffg.VNFFGPluginBase, db_base.CommonDbMixin):
description=template_db.description,
vnf_mapping=vnf_mapping,
vnffgd_id=template_id,
ns_id=ns_id,
attributes=template_db.get('template'),
status=constants.PENDING_CREATE)
context.session.add(vnffg_db)
@ -835,7 +841,7 @@ class VnffgPluginDbMixin(vnffg.VNFFGPluginBase, db_base.CommonDbMixin):
res = {
'forwarding_paths': vnffg_db.forwarding_paths[0]['id']
}
key_list = ('id', 'tenant_id', 'name', 'description',
key_list = ('id', 'tenant_id', 'name', 'description', 'ns_id',
'vnf_mapping', 'status', 'vnffgd_id', 'attributes')
res.update((key, vnffg_db[key]) for key in key_list)
return self._fields(res, fields)
@ -1240,6 +1246,14 @@ class VnffgPluginDbMixin(vnffg.VNFFGPluginBase, db_base.CommonDbMixin):
def _delete_vnffg_pre(self, context, vnffg_id):
vnffg = self.get_vnffg(context, vnffg_id)
ns_id = vnffg.get('ns_id')
if ns_id:
ns_db = self._get_resource(context, NS, ns_id)
# If network service is not in pending_delete status,
# raise error when delete vnffg.
if ns_db['status'] != constants.PENDING_DELETE:
raise nfvo.VnffgInUseNS(vnffg_id=vnffg_id,
ns_id=vnffg.get('ns_id'))
nfp = self.get_nfp(context, vnffg['forwarding_paths'])
chain = self.get_sfc(context, nfp['chain_id'])
classifiers = [self.get_classifier(context, classifier_id)

@ -153,10 +153,6 @@ class VnffgTemplateParamParsingException(exceptions.TackerException):
"missing input param %(get_input)s.")
class VnffgParamValueNotUsed(exceptions.TackerException):
message = _("Param input %(param_key)s not used.")
class VnffgPropertyNotFoundException(exceptions.NotFound):
message = _('VNFFG Property %(vnffg_property)s could not be found')
@ -183,6 +179,11 @@ class VnffgDeleteFailed(exceptions.TackerException):
message = _('Deleting VNFFG %(vnffg_id)s failed')
class VnffgInUseNS(exceptions.TackerException):
message = _('VNFFG %(vnffg_id)s belongs to active network service '
'%(ns_id)s')
class NfpAttributeNotFoundException(exceptions.NotFound):
message = _('NFP attribute %(attribute)s could not be found')
@ -485,6 +486,12 @@ RESOURCE_ATTRIBUTE_MAP = {
'is_visible': True,
'default': None,
},
'ns_id': {
'allow_post': True,
'allow_put': False,
'is_visible': True,
'default': None,
},
},
'nfps': {
@ -745,6 +752,13 @@ RESOURCE_ATTRIBUTE_MAP = {
'is_visible': True,
'default': '',
},
'vnffg_ids': {
'allow_post': True,
'allow_put': False,
'validate': {'type:string': None},
'is_visible': True,
'default': '',
},
'nsd_id': {
'allow_post': True,
'allow_put': False,

@ -11,13 +11,13 @@
# under the License.
import ast
from oslo_utils import uuidutils
from tacker.mistral import workflow_generator
OUTPUT = {
'create_vnf': ['vnf_id', 'vim_id', 'mgmt_url', 'status']
'create_vnf': ['vnf_id', 'vim_id', 'mgmt_url', 'status'],
'create_vnffg': ['vnffg_id'],
}
@ -31,9 +31,9 @@ class WorkflowGenerator(workflow_generator.WorkflowGeneratorBase):
for node in nodes:
task = self.wf_name + '_' + node
task_dict[task] = {
'action': 'tacker.create_vnf body=<% $.vnf.{0} '
'action': 'tacker.create_vnf body=<% $.ns.{0} '
'%>'.format(node),
'input': {'body': '<% $.vnf.{0} %>'.format(node)},
'input': {'body': '<% $.ns.{0} %>'.format(node)},
'publish': {
'vnf_id_' + node: '<% task({0}).result.vnf.id '
'%>'.format(task),
@ -79,9 +79,20 @@ class WorkflowGenerator(workflow_generator.WorkflowGeneratorBase):
'"ERROR" %>'.format(node)}
]
}
vnffgd_templates = ns.get('vnffgd_templates')
if vnffgd_templates:
for vnffg_name in vnffgd_templates:
vnffg_group = vnffgd_templates[vnffg_name][
'topology_template']['groups'][vnffg_name]
constituent_vnfs = vnffg_group[
'properties']['constituent_vnfs']
if vnfd_name in constituent_vnfs:
task_dict[task]['on-success'].append(
'create_vnffg_%s' % vnffg_name)
return task_dict
def _add_delete_vnf_tasks(self, ns):
def _add_delete_vnf_tasks(self, ns, vnffg_ids=None):
vnfds = ns['vnfd_details']
task_dict = dict()
for vnfd_name, vnfd_info in (vnfds).items():
@ -92,6 +103,55 @@ class WorkflowGenerator(workflow_generator.WorkflowGeneratorBase):
'action': 'tacker.delete_vnf vnf=<% $.vnf_id_{0}'
'%>'.format(node),
}
if vnffg_ids and len(vnffg_ids):
task_dict[task].update({'join': 'all'})
return task_dict
def _add_create_vnffg_task(self, vnffgd_templates):
task_dict = dict()
previous_task = None
for vnffg_name in vnffgd_templates:
task = 'create_vnffg_%s' % vnffg_name
vnffg_output = 'vnffg_id_%s' % vnffg_name
task_dict[task] = {
'join': 'all',
'action': 'tacker.create_vnffg body=<% $.ns.{0} '
'%>'.format(vnffg_name),
'input': {'body': '<% $.ns.{0} %>'.format(vnffg_name)},
'publish': {
vnffg_output: '<% task({0}).result.'
'vnffg.id %>'.format(task)}
}
if previous_task:
task_dict[previous_task].update({'on-success': [task]})
previous_task = task
return task_dict
def _add_delete_vnffg_task(self, ns):
task_dict = dict()
vnfds = ns['vnfd_details']
vnffg_ids = ns['vnffg_details']
delayed_tasks = list()
for vnfd_name, vnfd_info in (vnfds).items():
nodes = vnfd_info['instances']
for node in nodes:
wait_task = 'delete_vnf_%s' % node
delayed_tasks.append(wait_task)
previous_task = None
for vnffg_name in vnffg_ids.keys():
task = 'delete_vnffg_%s' % vnffg_name
if previous_task:
wait_tasks = delayed_tasks + [previous_task]
else:
wait_tasks = delayed_tasks
previous_task = task
task_dict[task] = {
'action': 'tacker.delete_vnffg vnffg=<% $.{0} '
'%>'.format(vnffg_name),
'on-success': wait_tasks
}
return task_dict
def _build_output_dict(self, ns):
@ -100,9 +160,16 @@ class WorkflowGenerator(workflow_generator.WorkflowGeneratorBase):
for vnfd_name, vnfd_info in (vnfds).items():
nodes = vnfd_info['instances']
for node in nodes:
for op_name in OUTPUT[self.wf_name]:
for op_name in OUTPUT['create_vnf']:
task_dict[op_name + '_' + node] = \
'<% $.{0}_{1} %>'.format(op_name, node)
vnffgd_templates = ns.get('vnffgd_templates')
if vnffgd_templates:
for vnffg_name in vnffgd_templates:
for op_name in OUTPUT['create_vnffg']:
vnffg_output = '%s_%s' % (op_name, vnffg_name)
task_dict[vnffg_output] = \
'<% $.{0}_{1} %>'.format(op_name, vnffg_name)
return task_dict
def get_input_dict(self):
@ -110,24 +177,60 @@ class WorkflowGenerator(workflow_generator.WorkflowGeneratorBase):
def build_input(self, ns, params):
vnfds = ns['vnfd_details']
id = uuidutils.generate_uuid()
self.input_dict = {'vnf': {}}
ns_id = ns['ns'].get('ns_id')
ns_name = ns['ns'].get('name')
self.input_dict = {'ns': {}}
for vnfd_name, vnfd_info in (vnfds).items():
nodes = vnfd_info['instances']
for node in nodes:
self.input_dict['vnf'][node] = dict()
self.input_dict['vnf'][node]['vnf'] = {
vnf_name = '%s_VNF_%s' % (ns_name, vnfd_info['id'])
self.input_dict['ns'][node] = dict()
self.input_dict['ns'][node]['vnf'] = {
'attributes': {},
'vim_id': ns['ns'].get('vim_id', ''),
'vnfd_id': vnfd_info['id'],
'name': 'create_vnf_%s_%s' % (vnfd_info['id'], id)
'name': vnf_name
}
if params.get(vnfd_name):
self.input_dict['vnf'][node]['vnf']['attributes'] = {
self.input_dict['ns'][node]['vnf']['attributes'] = {
'param_values': params.get(vnfd_name)
}
if ns.get('vnffgd_templates'):
vnffg_input = self.build_vnffg_input(ns, params, ns_id)
self.input_dict['ns'].update(vnffg_input)
def build_vnffg_input(self, ns, params, ns_id):
vnffgd_templates = ns.get('vnffgd_templates')
vnffg_input = dict()
for vnffg_name in vnffgd_templates:
vnffg_group = vnffgd_templates[vnffg_name][
'topology_template']['groups'][vnffg_name]
constituent_vnfs = vnffg_group[
'properties']['constituent_vnfs']
vnf_mapping = self.get_vnf_mapping(ns, constituent_vnfs)
vnffgd_body = dict()
vnffgd_body['vnffg'] = {
'name': '%s_%s_%s' % (ns['ns'].get('name'), vnffg_name, ns_id),
'vnffgd_template': vnffgd_templates[vnffg_name],
'vnf_mapping': vnf_mapping,
'attributes': {
'param_values': params.get('nsd')},
'ns_id': ns_id
}
vnffg_input[vnffg_name] = vnffgd_body
return vnffg_input
def create_vnf(self, **kwargs):
def get_vnf_mapping(self, ns, constituent_vnfs):
vnfds = ns['vnfd_details']
vnf_mapping = dict()
for vnfd_name, vnfd_info in (vnfds).items():
if vnfd_name in constituent_vnfs:
vnf_name = '%s_VNF_%s' % (ns['ns'].get('name'),
vnfd_info['id'])
vnf_mapping[vnfd_name] = vnf_name
return vnf_mapping
def create_ns(self, **kwargs):
ns = kwargs.get('ns')
params = kwargs.get('params')
# TODO(anyone): Keep this statements in a loop and
@ -142,8 +245,12 @@ class WorkflowGenerator(workflow_generator.WorkflowGeneratorBase):
self.definition[self.wf_identifier]['output'] = \
self._build_output_dict(ns)
self.build_input(ns, params)
vnffgd_templates = ns.get('vnffgd_templates')
if vnffgd_templates:
self.definition[self.wf_identifier]['tasks'].update(
self._add_create_vnffg_task(vnffgd_templates))
def delete_vnf(self, ns):
def delete_ns(self, ns):
ns_dict = {'vnfd_details': {}}
vnf_ids = ast.literal_eval(ns['vnf_ids'])
self.definition[self.wf_identifier]['input'] = []
@ -153,5 +260,14 @@ class WorkflowGenerator(workflow_generator.WorkflowGeneratorBase):
self.input_dict[vnf_key] = vnf_ids[vnf]
ns_dict['vnfd_details'][vnf] = {'instances': [vnf]}
self.definition[self.wf_identifier]['tasks'] = dict()
vnffg_ids = ast.literal_eval(ns.get('vnffg_ids'))
if len(vnffg_ids):
for vnffg_name in vnffg_ids.keys():
self.definition[self.wf_identifier]['input'].append(vnffg_name)
self.input_dict[vnffg_name] = vnffg_ids[vnffg_name]
ns_dict['vnffg_details'] = vnffg_ids
self.definition[self.wf_identifier]['tasks'].update(
self._add_delete_vnffg_task(ns_dict))
self.definition[self.wf_identifier]['tasks'].update(
self._add_delete_vnf_tasks(ns_dict))
self._add_delete_vnf_tasks(ns_dict, vnffg_ids))

@ -663,16 +663,41 @@ class NfvoPlugin(nfvo_db_plugin.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin,
if vnfd_name == vnfd['name']:
return vnfd['id']
@log.log
def _get_vnffgds_from_nsd(self, nsd_dict):
ns_topo = nsd_dict.get('topology_template')
vnffgd_templates = dict()
if ns_topo and ns_topo.get('groups'):
for vnffg_name in ns_topo.get('groups'):
vnffgd_template = dict()
# TODO(phuoc): add checking in case vnffg_name exists
# more than one time.
# Constructing vnffgd from nsd, remove imports section
vnffgd_template['tosca_definitions_version'] = \
nsd_dict.get('tosca_definitions_version')
vnffgd_template['description'] = nsd_dict.get('description')
vnffgd_template['topology_template'] = dict()
vnffgd_template['topology_template']['groups'] = dict()
vnffgd_template['topology_template']['groups'][vnffg_name] = \
ns_topo['groups'].get(vnffg_name)
vnffgd_template['topology_template']['node_templates'] = dict()
for fp_name in ns_topo['groups'][vnffg_name]['members']:
vnffgd_template['topology_template']['node_templates'][
fp_name] = ns_topo['node_templates'].get(fp_name)
vnffgd_templates[vnffg_name] = vnffgd_template
return vnffgd_templates
@log.log
def create_ns(self, context, ns):
"""Create NS and corresponding VNFs.
"""Create NS, corresponding VNFs, VNFFGs.
: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
step-3: Create mistral workflow to create VNFs, VNFFG and execute the
workflow
"""
ns_info = ns['ns']
name = ns_info['name']
@ -699,6 +724,13 @@ class NfvoPlugin(nfvo_db_plugin.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin,
if not ns['ns']['vim_id']:
ns['ns']['vim_id'] = vim_res['vim_id']
# TODO(phuoc): currently, create_ns function does not have
# create_ns_pre function, that pre-defines information of a network
# service. Creating ns_uuid keeps ns_id for consistency, it should be
# provided as return value of create_ns_pre function in ns db.
# Generate ns_uuid
ns['ns']['ns_id'] = uuidutils.generate_uuid()
# Step-1
param_values = ns['ns']['attributes'].get('param_values', {})
if 'get_input' in str(nsd_dict):
@ -740,6 +772,11 @@ class NfvoPlugin(nfvo_db_plugin.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin,
param_values[vnfd_name]['substitution_mappings'][
'requirements'] = req_dict
ns['vnfd_details'] = vnfd_dict
vnffgd_templates = self._get_vnffgds_from_nsd(nsd_dict)
LOG.debug('vnffgd_templates: %s', vnffgd_templates)
ns['vnffgd_templates'] = vnffgd_templates
# Step-3
kwargs = {'ns': ns, 'params': param_values}
@ -747,7 +784,7 @@ class NfvoPlugin(nfvo_db_plugin.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin,
workflow = self._vim_drivers.invoke(
driver_type,
'prepare_and_create_workflow',
resource='vnf',
resource='ns',
action='create',
auth_dict=self.get_auth_dict(context),
kwargs=kwargs)
@ -780,6 +817,8 @@ class NfvoPlugin(nfvo_db_plugin.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin,
if exec_state == 'SUCCESS' or exec_state == 'ERROR':
break
mistral_retries = mistral_retries - 1
# TODO(phuoc): add more information about error reason in case
# of exec_state is 'ERROR'
error_reason = None
if mistral_retries == 0 and exec_state == 'RUNNING':
error_reason = _(
@ -801,8 +840,9 @@ class NfvoPlugin(nfvo_db_plugin.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin,
'delete_workflow',
workflow_id=workflow['id'],
auth_dict=self.get_auth_dict(context))
super(NfvoPlugin, self).create_ns_post(context, ns_id, exec_obj,
vnfd_dict, error_reason)
super(NfvoPlugin, self).create_ns_post(
context, ns_id, exec_obj, vnfd_dict,
vnffgd_templates, error_reason)
self.spawn_n(_create_ns_wait, self, ns_dict['id'],
mistral_execution.id)
@ -837,6 +877,7 @@ class NfvoPlugin(nfvo_db_plugin.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin,
@log.log
def delete_ns(self, context, ns_id):
ns = super(NfvoPlugin, self).get_ns(context, ns_id)
LOG.debug("Deleting ns: %s", ns)
vim_res = self.vim_client.get_vim(context, ns['vim_id'])
super(NfvoPlugin, self).delete_ns_pre(context, ns_id)
driver_type = vim_res['vim_type']
@ -846,11 +887,10 @@ class NfvoPlugin(nfvo_db_plugin.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin,
workflow = self._vim_drivers.invoke(
driver_type,
'prepare_and_create_workflow',
resource='vnf',
resource='ns',
action='delete',
auth_dict=self.get_auth_dict(context),
kwargs={
'ns': ns})
kwargs={'ns': ns})
except nfvo.NoTasksException:
LOG.warning("No VNF deletion task(s).")
if workflow:
@ -884,6 +924,8 @@ class NfvoPlugin(nfvo_db_plugin.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin,
if exec_state == 'SUCCESS' or exec_state == 'ERROR':
break
mistral_retries -= 1
# TODO(phuoc): add more information about error reason in case
# of exec_state is 'ERROR'
error_reason = None
if mistral_retries == 0 and exec_state == 'RUNNING':
error_reason = _(

@ -31,6 +31,71 @@ def get_dummy_ns():
'placement_attr': {}}
def get_dummy_vnffg_ns():
return {
u'ns': {
'description': '',
'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': {},
'vnffgd_templates': {
'VNFFG1': {
'tosca_definitions_version':
'tosca_simple_profile_for_nfv_1_0_0',
'description': 'VNFFG1 descriptor',
'topology_template': {
'node_templates': {
'Forwarding_path1': {
'type': 'tosca.nodes.nfv.FP.TackerV2',
'description': 'creates path inside ns - test',
'properties': {
'policy': {
'type': 'ACL',
'criteria': [{
'classifier': {
'ip_proto': 6,
'network_src_port_id': {
'get_input': 'net_src_port_id'
},
'ip_dst_prefix': {
'get_input': 'ip_dest_prefix'
},
'destination_port_range': '80-1024'
},
'name': 'block_tcp'}]},
'path': [
{'capability': 'CP12',
'forwarder': 'vnf1'},
{'capability': 'CP22',
'forwarder': 'vnf2'}],
'id': 51}}},
'groups': {
'VNFFG1': {
'type': 'tosca.groups.nfv.VNFFG',
'description': 'HTTP to Corporate Net',
'members': ['Forwarding_path1'],
'properties': {
'version': 1.0,
'vendor': 'tacker',
'constituent_vnfs': ['vnf1', 'vnf2'],
'connection_point': ['CP12', 'CP22'],
'number_of_endpoints': 2,
'dependent_virtual_link': ['VL1', 'VL2']}
}
}
}
}
}
}
def get_dummy_param():
return {u'vnf1': {'substitution_mappings': {u'VL1b8587afb-60': {
'type': 'tosca.nodes.nfv.VL', 'properties': {
@ -45,7 +110,7 @@ def get_dummy_param():
def get_dummy_create_workflow():
return {'std.create_vnf_dummy': {'input': ['vnf'],
return {'std.create_ns_dummy': {'input': ['ns'],
'tasks': {
'wait_vnf_active_VNF2': {
'action': 'tacker.show_vnf vnf=<% $.vnf_id_VNF2 %>',
@ -61,30 +126,30 @@ def get_dummy_create_workflow():
'on-success': [{
'delete_vnf_VNF2': '<% $.status_VNF2='
'"ERROR" %>'}]},
'create_vnf_VNF2': {
'action': 'tacker.create_vnf body=<% $.vnf.VNF2 %>',
'input': {'body': '<% $.vnf.VNF2 %>'},
'create_ns_VNF2': {
'action': 'tacker.create_vnf body=<% $.ns.VNF2 %>',
'input': {'body': '<% $.ns.VNF2 %>'},
'publish': {
'status_VNF2': '<% task(create_vnf_VNF2).'
'status_VNF2': '<% task(create_ns_VNF2).'
'result.vnf.status %>',
'vim_id_VNF2': '<% task(create_vnf_VNF2).'
'vim_id_VNF2': '<% task(create_ns_VNF2).'
'result.vnf.vim_id %>',
'mgmt_url_VNF2': '<% task(create_vnf_VNF2).'
'mgmt_url_VNF2': '<% task(create_ns_VNF2).'
'result.vnf.mgmt_url %>',
'vnf_id_VNF2': '<% task(create_vnf_VNF2)'
'vnf_id_VNF2': '<% task(create_ns_VNF2)'
'.result.vnf.id %>'},
'on-success': ['wait_vnf_active_VNF2']},
'create_vnf_VNF1': {
'action': 'tacker.create_vnf body=<% $.vnf.VNF1 %>',
'input': {'body': '<% $.vnf.VNF1 %>'},
'create_ns_VNF1': {
'action': 'tacker.create_vnf body=<% $.ns.VNF1 %>',
'input': {'body': '<% $.ns.VNF1 %>'},
'publish': {
'status_VNF1': '<% task(create_vnf_VNF1).'
'status_VNF1': '<% task(create_ns_VNF1).'
'result.vnf.status %>',
'vnf_id_VNF1': '<% task(create_vnf_VNF1).'
'vnf_id_VNF1': '<% task(create_ns_VNF1).'
'result.vnf.id %>',
'mgmt_url_VNF1': '<% task(create_vnf_VNF1).'
'mgmt_url_VNF1': '<% task(create_ns_VNF1).'
'result.vnf.mgmt_url %>',
'vim_id_VNF1': '<% task(create_vnf_VNF1).'
'vim_id_VNF1': '<% task(create_ns_VNF1).'
'result.vnf.vim_id %>'},
'on-success': ['wait_vnf_active_VNF1']},
'wait_vnf_active_VNF1': {
@ -100,10 +165,10 @@ def get_dummy_create_workflow():
'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%>'}},
'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 %>',
@ -116,15 +181,132 @@ def get_dummy_create_workflow():
'version': '2.0'}
def get_dummy_create_vnffg_ns_workflow():
return {
'std.create_ns_dummy': {
'input': ['ns'],
'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_vnffg_VNFFG1']},
'create_vnffg_VNFFG1': {
'action': 'tacker.create_vnffg body=<% $.ns.VNFFG1 %>',
'input': {'body': '<% $.ns.VNFFG1 %>'},
'join': 'all',
'publish': {
'vnffg_id_VNFFG1': '<% task(create_vnffg_VNFFG1).'
'result.vnffg.id %>'}},
'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" %>'},
'create_vnffg_VNFFG1']},
'create_ns_VNF1': {
'action': 'tacker.create_vnf body=<% $.ns.VNF1 %>',
'input': {'body': '<% $.ns.VNF1 %>'},
'publish': {
'status_VNF1':
'<% task(create_ns_VNF1).result.vnf.status %>',
'vnf_id_VNF1':
'<% task(create_ns_VNF1).result.vnf.id %>',
'mgmt_url_VNF1':
'<% task(create_ns_VNF1).result.vnf.mgmt_url %>',
'vim_id_VNF1':
'<% task(create_ns_VNF1).result.vnf.vim_id %>'},
'on-success': ['wait_vnf_active_VNF1']},
'create_ns_VNF2': {
'action': 'tacker.create_vnf body=<% $.ns.VNF2 %>',
'input': {'body': '<% $.ns.VNF2 %>'},
'publish': {
'status_VNF2':
'<% task(create_ns_VNF2).result.vnf.status %>',
'vim_id_VNF2':
'<% task(create_ns_VNF2).result.vnf.vim_id %>',
'mgmt_url_VNF2':
'<% task(create_ns_VNF2).result.vnf.mgmt_url %>',
'vnf_id_VNF2':
'<% task(create_ns_VNF2).result.vnf.id %>'},
'on-success': ['wait_vnf_active_VNF2']},
'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 %>',
'vnffg_id_VNFFG1': '<% $.vnffg_id_VNFFG1 %>',