Implements VNFFG into NFVO

implements blueprint: tacker-vnffg

Change-Id: I9e2fd8b14fd6eaf05aa7813b5f0fab6daa4abd43
Signed-off-by: Tim Rozet <trozet@redhat.com>
This commit is contained in:
Tim Rozet 2016-07-01 16:13:59 -04:00
parent d3f791591a
commit 8b479f4268
18 changed files with 2508 additions and 7 deletions

View File

@ -37,6 +37,7 @@ oslo.serialization>=1.10.0 # Apache-2.0
oslo.service>=1.10.0 # Apache-2.0 oslo.service>=1.10.0 # Apache-2.0
oslo.utils>=3.16.0 # Apache-2.0 oslo.utils>=3.16.0 # Apache-2.0
oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
python-neutronclient>=5.1.0 # Apache-2.0
python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0
tosca-parser>=0.5.0 # Apache-2.0 tosca-parser>=0.5.0 # Apache-2.0
heat-translator>=0.4.0 # Apache-2.0 heat-translator>=0.4.0 # Apache-2.0

View File

@ -0,0 +1,145 @@
# Copyright 2016 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
"""adds_VNFFG
Revision ID: 507122918800
Revises: 4ee19c8a6d0a
Create Date: 2016-07-29 21:48:18.816277
"""
# revision identifiers, used by Alembic.
revision = '507122918800'
down_revision = '4ee19c8a6d0a'
import sqlalchemy as sa
from alembic import op
from tacker.db.types import Json
def upgrade(active_plugins=None, options=None):
op.create_table(
'vnffgtemplates',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('tenant_id', sa.String(length=64), nullable=False),
sa.Column('name', sa.String(length=255), nullable=False),
sa.Column('description', sa.String(length=255), nullable=True),
sa.Column('template', Json),
sa.PrimaryKeyConstraint('id'),
mysql_engine='InnoDB'
)
op.create_table(
'vnffgs',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('tenant_id', sa.String(length=64), nullable=False),
sa.Column('name', sa.String(length=255), nullable=False),
sa.Column('description', sa.String(length=255), nullable=True),
sa.Column('vnffgd_id', sa.String(length=36), nullable=False),
sa.Column('status', sa.String(length=255), nullable=False),
sa.Column('vnf_mapping', Json),
sa.ForeignKeyConstraint(['vnffgd_id'], ['vnffgtemplates.id'], ),
sa.PrimaryKeyConstraint('id'),
mysql_engine='InnoDB'
)
op.create_table(
'vnffgnfps',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('tenant_id', sa.String(length=64), nullable=False),
sa.Column('vnffg_id', sa.String(length=36), nullable=False),
sa.Column('name', sa.String(length=255), nullable=False),
sa.Column('status', sa.String(length=255), nullable=False),
sa.Column('path_id', sa.String(length=255), nullable=False),
sa.Column('symmetrical', sa.Boolean, default=False),
sa.ForeignKeyConstraint(['vnffg_id'], ['vnffgs.id'], ),
sa.PrimaryKeyConstraint('id'),
mysql_engine='InnoDB'
)
op.create_table(
'vnffgchains',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('tenant_id', sa.String(length=64), nullable=False),
sa.Column('instance_id', sa.String(length=255), nullable=True),
sa.Column('nfp_id', sa.String(length=36), nullable=False),
sa.Column('status', sa.String(length=255), nullable=False),
sa.Column('path_id', sa.String(length=255), nullable=False),
sa.Column('symmetrical', sa.Boolean, default=False),
sa.Column('chain', Json),
sa.ForeignKeyConstraint(['nfp_id'], ['vnffgnfps.id'], ),
sa.PrimaryKeyConstraint('id'),
mysql_engine='InnoDB'
)
op.create_table(
'vnffgclassifiers',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('tenant_id', sa.String(length=64), nullable=False),
sa.Column('nfp_id', sa.String(length=36), nullable=False),
sa.Column('instance_id', sa.String(length=255), nullable=True),
sa.Column('chain_id', sa.String(length=36), nullable=False),
sa.Column('status', sa.String(length=255), nullable=False),
sa.ForeignKeyConstraint(['nfp_id'], ['vnffgnfps.id'], ),
sa.ForeignKeyConstraint(['chain_id'], ['vnffgchains.id'], ),
sa.PrimaryKeyConstraint('id'),
mysql_engine='InnoDB'
)
op.create_table(
'aclmatchcriterias',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('vnffgc_id', sa.String(length=36), nullable=False),
sa.Column('eth_src', sa.String(length=36), nullable=True),
sa.Column('eth_dst', sa.String(length=36), nullable=True),
sa.Column('eth_type', sa.String(length=36), nullable=True),
sa.Column('vlan_id', sa.Integer, nullable=True),
sa.Column('vlan_pcp', sa.Integer, nullable=True),
sa.Column('mpls_label', sa.Integer, nullable=True),
sa.Column('mpls_tc', sa.Integer, nullable=True),
sa.Column('ip_dscp', sa.Integer, nullable=True),
sa.Column('ip_ecn', sa.Integer, nullable=True),
sa.Column('ip_src_prefix', sa.String(length=36), nullable=True),
sa.Column('ip_dst_prefix', sa.String(length=36), nullable=True),
sa.Column('source_port_min', sa.Integer, nullable=True),
sa.Column('source_port_max', sa.Integer, nullable=True),
sa.Column('destination_port_min', sa.Integer, nullable=True),
sa.Column('destination_port_max', sa.Integer, nullable=True),
sa.Column('ip_proto', sa.Integer, nullable=True),
sa.Column('network_id', sa.String(length=36), nullable=True),
sa.Column('network_src_port_id', sa.String(length=36), nullable=True),
sa.Column('network_dst_port_id', sa.String(length=36), nullable=True),
sa.Column('tenant_id', sa.String(length=64), nullable=True),
sa.Column('icmpv4_type', sa.Integer, nullable=True),
sa.Column('icmpv4_code', sa.Integer, nullable=True),
sa.Column('arp_op', sa.Integer, nullable=True),
sa.Column('arp_spa', sa.Integer, nullable=True),
sa.Column('arp_tpa', sa.Integer, nullable=True),
sa.Column('arp_sha', sa.Integer, nullable=True),
sa.Column('arp_tha', sa.Integer, nullable=True),
sa.Column('ipv6_src', sa.String(36), nullable=True),
sa.Column('ipv6_dst', sa.String(36), nullable=True),
sa.Column('ipv6_flabel', sa.Integer, nullable=True),
sa.Column('icmpv6_type', sa.Integer, nullable=True),
sa.Column('icmpv6_code', sa.Integer, nullable=True),
sa.Column('ipv6_nd_target', sa.String(36), nullable=True),
sa.Column('ipv6_nd_sll', sa.String(36), nullable=True),
sa.Column('ipv6_nd_tll', sa.String(36), nullable=True),
sa.ForeignKeyConstraint(['vnffgc_id'], ['vnffgclassifiers.id'], ),
sa.PrimaryKeyConstraint('id'),
)

View File

@ -1 +1 @@
4ee19c8a6d0a 507122918800

View File

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

908
tacker/db/nfvo/vnffg_db.py Normal file
View File

@ -0,0 +1,908 @@
# Copyright 2016 Red Hat Inc
# 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.
import random
import sqlalchemy as sa
import uuid
from oslo_log import log as logging
from sqlalchemy import orm
from sqlalchemy.orm import exc as orm_exc
from tacker._i18n import _
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 vnffg
from tacker import manager
from tacker.plugins.common import constants
LOG = logging.getLogger(__name__)
_ACTIVE_UPDATE = (constants.ACTIVE, constants.PENDING_UPDATE)
_ACTIVE_UPDATE_ERROR_DEAD = (
constants.PENDING_CREATE, constants.ACTIVE, constants.PENDING_UPDATE,
constants.ERROR, constants.DEAD)
_VALID_VNFFG_UPDATE_ATTRIBUTES = ('name', 'description', 'vnf_mapping')
_VALID_SFC_UPDATE_ATTRIBUTES = ('chain', 'symmetrical')
_VALID_FC_UPDATE_ATTRIBUTES = ()
MATCH_CRITERIA = (
'eth_type', 'eth_src', 'eth_dst', 'vlan_id', 'vlan_pcp', 'mpls_label',
'mpls_tc', 'ip_dscp', 'ip_ecn', 'ip_src_prefix', 'ip_dst_prefix',
'ip_proto', 'destination_port_range', 'source_port_range',
'network_src_port_id', 'network_dst_port_id', 'network_id', 'network_name',
'tenant_id', 'icmpv4_type', 'icmpv4_code', 'arp_op', 'arp_spa',
'arp_tpa', 'arp_sha', 'arp_tha', 'ipv6_src', 'ipv6_dst', 'ipv6_flabel',
'icmpv6_type', 'icmpv6_code', 'ipv6_nd_target', 'ipv6_nd_sll',
'ipv6_nd_tll')
MATCH_DB_KEY_LIST = (
'eth_type', 'eth_src', 'eth_dst', 'vlan_id', 'vlan_pcp', 'mpls_label',
'mpls_tc', 'ip_dscp', 'ip_ecn', 'ip_src_prefix', 'ip_dst_prefix',
'ip_proto', 'destination_port_min', 'destination_port_max',
'source_port_min', 'source_port_max', 'network_src_port_id',
'network_dst_port_id', 'network_id', 'tenant_id', 'icmpv4_type',
'icmpv4_code', 'arp_op', 'arp_spa', 'arp_tpa', 'arp_sha', 'arp_tha',
'ipv6_src', 'ipv6_dst', 'ipv6_flabel', 'icmpv6_type', 'icmpv6_code',
'ipv6_nd_target', 'ipv6_nd_sll', 'ipv6_nd_tll'
)
CP = 'connection_points'
class VnffgTemplate(model_base.BASE, models_v1.HasId, models_v1.HasTenant):
"""Represents template to create a VNF Forwarding Graph."""
# Descriptive name
name = sa.Column(sa.String(255), nullable=False)
description = sa.Column(sa.Text)
# Vnffg template
template = sa.Column(types.Json)
class Vnffg(model_base.BASE, models_v1.HasTenant, models_v1.HasId):
"""VNF Forwarding Graph Data Model"""
name = sa.Column(sa.String(255), nullable=False)
description = sa.Column(sa.String(255), nullable=True)
# List of associated NFPs
forwarding_paths = orm.relationship("VnffgNfp", backref="vnffg")
vnffgd_id = sa.Column(types.Uuid, sa.ForeignKey('vnffgtemplates.id'))
vnffgd = orm.relationship('VnffgTemplate')
status = sa.Column(sa.String(255), nullable=False)
# Mapping of VNFD to VNF instance names
vnf_mapping = sa.Column(types.Json)
class VnffgNfp(model_base.BASE, models_v1.HasTenant, models_v1.HasId):
"""Network Forwarding Path Data Model"""
name = sa.Column(sa.String(255), nullable=False)
vnffg_id = sa.Column(types.Uuid, sa.ForeignKey('vnffgs.id'),
nullable=False)
classifier = orm.relationship('VnffgClassifier', backref='nfp',
uselist=False)
chain = orm.relationship('VnffgChain', backref='nfp',
uselist=False)
status = sa.Column(sa.String(255), nullable=False)
path_id = sa.Column(sa.String(255), nullable=False)
# symmetry of forwarding path
symmetrical = sa.Column(sa.Boolean(), default=False)
class VnffgChain(model_base.BASE, models_v1.HasTenant, models_v1.HasId):
"""Service Function Chain Data Model"""
status = sa.Column(sa.String(255), nullable=False)
instance_id = sa.Column(sa.String(255), nullable=True)
# symmetry of forwarding path
symmetrical = sa.Column(sa.Boolean(), default=False)
# chain
chain = sa.Column(types.Json)
path_id = sa.Column(sa.String(255), nullable=False)
nfp_id = sa.Column(types.Uuid, sa.ForeignKey('vnffgnfps.id'))
class VnffgClassifier(model_base.BASE, models_v1.HasTenant, models_v1.HasId):
"""VNFFG NFP Classifier Data Model"""
status = sa.Column(sa.String(255), nullable=False)
instance_id = sa.Column(sa.String(255), nullable=True)
chain_id = sa.Column(types.Uuid, sa.ForeignKey('vnffgchains.id'))
chain = orm.relationship('VnffgChain', backref='classifier',
uselist=False, foreign_keys=[chain_id])
nfp_id = sa.Column(types.Uuid, sa.ForeignKey('vnffgnfps.id'))
# match criteria
match = orm.relationship('ACLMatchCriteria')
class ACLMatchCriteria(model_base.BASE, models_v1.HasId):
"""Represents ACL match criteria of a classifier."""
vnffgc_id = sa.Column(types.Uuid, sa.ForeignKey('vnffgclassifiers.id'))
eth_src = sa.Column(sa.String(36), nullable=True)
eth_dst = sa.Column(sa.String(36), nullable=True)
eth_type = sa.Column(sa.String(36), nullable=True)
vlan_id = sa.Column(sa.Integer, nullable=True)
vlan_pcp = sa.Column(sa.Integer, nullable=True)
mpls_label = sa.Column(sa.Integer, nullable=True)
mpls_tc = sa.Column(sa.Integer, nullable=True)
ip_dscp = sa.Column(sa.Integer, nullable=True)
ip_ecn = sa.Column(sa.Integer, nullable=True)
ip_src_prefix = sa.Column(sa.String(36), nullable=True)
ip_dst_prefix = sa.Column(sa.String(36), nullable=True)
source_port_min = sa.Column(sa.Integer, nullable=True)
source_port_max = sa.Column(sa.Integer, nullable=True)
destination_port_min = sa.Column(sa.Integer, nullable=True)
destination_port_max = sa.Column(sa.Integer, nullable=True)
ip_proto = sa.Column(sa.Integer, nullable=True)
network_id = sa.Column(types.Uuid, nullable=True)
network_src_port_id = sa.Column(types.Uuid, nullable=True)
network_dst_port_id = sa.Column(types.Uuid, nullable=True)
tenant_id = sa.Column(sa.String(64), nullable=True)
icmpv4_type = sa.Column(sa.Integer, nullable=True)
icmpv4_code = sa.Column(sa.Integer, nullable=True)
arp_op = sa.Column(sa.Integer, nullable=True)
arp_spa = sa.Column(sa.String(36), nullable=True)
arp_tpa = sa.Column(sa.String(36), nullable=True)
arp_sha = sa.Column(sa.String(36), nullable=True)
arp_tha = sa.Column(sa.String(36), nullable=True)
ipv6_src = sa.Column(sa.String(36), nullable=True)
ipv6_dst = sa.Column(sa.String(36), nullable=True)
ipv6_flabel = sa.Column(sa.Integer, nullable=True)
icmpv6_type = sa.Column(sa.Integer, nullable=True)
icmpv6_code = sa.Column(sa.Integer, nullable=True)
ipv6_nd_target = sa.Column(sa.String(36), nullable=True)
ipv6_nd_sll = sa.Column(sa.String(36), nullable=True)
ipv6_nd_tll = sa.Column(sa.String(36), nullable=True)
class VnffgPluginDbMixin(vnffg.VNFFGPluginBase, db_base.CommonDbMixin):
def __init__(self):
super(VnffgPluginDbMixin, self).__init__()
def create_vnffg(self, context, vnffg):
vnffg_dict = self._create_vnffg_pre(context, vnffg)
sfc_instance = str(uuid.uuid4())
fc_instance = str(uuid.uuid4())
self._create_vnffg_post(context, sfc_instance,
fc_instance, vnffg_dict)
self._create_vnffg_status(context, vnffg_dict)
return vnffg_dict
def get_vnffg(self, context, vnffg_id, fields=None):
vnffg_db = self._get_resource(context, Vnffg, vnffg_id)
return self._make_vnffg_dict(vnffg_db, fields)
def get_vnffgs(self, context, filters=None, fields=None):
return self._get_collection(context, Vnffg, self._make_vnffg_dict,
filters=filters, fields=fields)
def update_vnffg(self, context, vnffg_id, vnffg):
vnffg_dict = self._update_vnffg_pre(context, vnffg_id)
self._update_vnffg_post(context, vnffg_id, constants.ACTIVE, vnffg)
return vnffg_dict
def delete_vnffg(self, context, vnffg_id):
self._delete_vnffg_pre(context, vnffg_id)
self._delete_vnffg_post(context, vnffg_id, False)
def create_vnffgd(self, context, vnffgd):
template = vnffgd['vnffgd']
LOG.debug(_('template %s'), template)
tenant_id = self._get_tenant_id_for_create(context, template)
with context.session.begin(subtransactions=True):
template_id = str(uuid.uuid4())
template_db = VnffgTemplate(
id=template_id,
tenant_id=tenant_id,
name=template.get('name'),
description=template.get('description'),
template=template.get('template'))
context.session.add(template_db)
LOG.debug(_('template_db %(template_db)s'),
{'template_db': template_db})
return self._make_template_dict(template_db)
def get_vnffgd(self, context, vnffgd_id, fields=None):
template_db = self._get_resource(context, VnffgTemplate,
vnffgd_id)
return self._make_template_dict(template_db, fields)
def get_vnffgds(self, context, filters=None, fields=None):
return self._get_collection(context, VnffgTemplate,
self._make_template_dict,
filters=filters, fields=fields)
def delete_vnffgd(self, context, vnffgd_id):
with context.session.begin(subtransactions=True):
vnffg_db = context.session.query(Vnffg).filter_by(
vnffgd_id=vnffgd_id).first()
if vnffg_db is not None:
raise nfvo.VnffgdInUse(vnffgd_id=vnffgd_id)
template_db = self._get_resource(context, VnffgTemplate,
vnffgd_id)
context.session.delete(template_db)
def get_classifier(self, context, classifier_id, fields=None):
classifier_db = self._get_resource(context, VnffgClassifier,
classifier_id)
return self._make_classifier_dict(classifier_db, fields)
def get_classifiers(self, context, filters=None, fields=None):
return self._get_collection(context, VnffgClassifier,
self._make_classifier_dict,
filters=filters, fields=fields)
def get_nfp(self, context, nfp_id, fields=None):
nfp_db = self._get_resource(context, VnffgNfp, nfp_id)
return self._make_nfp_dict(nfp_db, fields)
def get_nfps(self, context, filters=None, fields=None):
return self._get_collection(context, VnffgNfp,
self._make_nfp_dict,
filters=filters, fields=fields)
def get_sfc(self, context, sfc_id, fields=None):
chain_db = self._get_resource(context, VnffgChain, sfc_id)
return self._make_chain_dict(chain_db, fields)
def get_sfcs(self, context, filters=None, fields=None):
return self._get_collection(context, VnffgChain,
self._make_chain_dict,
filters=filters, fields=fields)
# called internally, not by REST API
def _create_vnffg_pre(self, context, vnffg):
vnffg = vnffg['vnffg']
LOG.debug(_('vnffg %s'), vnffg)
tenant_id = self._get_tenant_id_for_create(context, vnffg)
name = vnffg.get('name')
vnffg_id = vnffg.get('id') or str(uuid.uuid4())
template_id = vnffg['vnffgd_id']
symmetrical = vnffg['symmetrical']
with context.session.begin(subtransactions=True):
template_db = self._get_resource(context, VnffgTemplate,
template_id)
LOG.debug(_('vnffg template %s'), template_db)
vnf_members = self._get_vnffg_property(template_db,
'constituent_vnfs')
LOG.debug(_('Constituent VNFs: %s'), vnf_members)
vnf_mapping = self._get_vnf_mapping(context, vnffg.get(
'vnf_mapping'), vnf_members)
LOG.debug(_('VNF Mapping: %s'), vnf_mapping)
# create NFP dict
nfp_dict = self._create_nfp_pre(template_db)
vnffg_db = Vnffg(id=vnffg_id,
tenant_id=tenant_id,
name=name,
description=template_db.description,
vnf_mapping=vnf_mapping,
vnffgd_id=template_id,
status=constants.PENDING_CREATE)
context.session.add(vnffg_db)
nfp_id = str(uuid.uuid4())
sfc_id = str(uuid.uuid4())
classifier_id = str(uuid.uuid4())
nfp_db = VnffgNfp(id=nfp_id, vnffg_id=vnffg_id,
tenant_id=tenant_id,
name=nfp_dict['name'],
status=constants.PENDING_CREATE,
path_id=nfp_dict['path_id'],
symmetrical=symmetrical)
context.session.add(nfp_db)
chain = self._create_port_chain(context, vnf_mapping, template_db,
nfp_dict['name'])
LOG.debug(_('chain: %s'), chain)
sfc_db = VnffgChain(id=sfc_id,
tenant_id=tenant_id,
status=constants.PENDING_CREATE,
symmetrical=symmetrical,
chain=chain,
nfp_id=nfp_id,
path_id=nfp_dict['path_id'])
context.session.add(sfc_db)
sfcc_db = VnffgClassifier(id=classifier_id,
tenant_id=tenant_id,
status=constants.PENDING_CREATE,
nfp_id=nfp_id,
chain_id=sfc_id)
context.session.add(sfcc_db)
match = self._policy_to_acl_criteria(context, template_db,
nfp_dict['name'],
vnf_mapping)
LOG.debug(_('acl_match %s'), match)
match_db_table = ACLMatchCriteria(
id=str(uuid.uuid4()),
vnffgc_id=classifier_id,
**match)
context.session.add(match_db_table)
return self._make_vnffg_dict(vnffg_db)
@staticmethod
def _create_nfp_pre(template_db):
template = template_db.template['vnffgd']['topology_template']
nfp_dict = dict()
vnffg_name = template['groups'].keys()[0]
# we assume only one NFP for initial implementation
nfp_dict['name'] = template['groups'][vnffg_name]['members'][0]
nfp_dict['path_id'] = template['node_templates'][nfp_dict['name']][
'properties']['id']
if not nfp_dict['path_id']:
# TODO(trozet): do we need to check if this path ID is already
# taken by another VNFFG
nfp_dict['path_id'] = random.randint(1, 16777216)
return nfp_dict
def _create_port_chain(self, context, vnf_mapping, template_db, nfp_name):
"""Creates a list of physical port ids to represent an ordered chain
:param context: SQL session context
:param vnf_mapping: dict of VNFD to VNF instance mappings
:param template_db: VNFFG Descriptor
:param nfp_name: name of the forwarding path with chain requirements
:return: list of port chain including vnf name and list of CPs
"""
chain_list = []
prev_forwarder = None
vnfm_plugin = manager.TackerManager.get_service_plugins()['VNFM']
# Build the list of logical chain representation
logical_chain = self._get_nfp_attribute(template_db.template,
nfp_name, 'path')
# Build physical port chain
for element in logical_chain:
if element['forwarder'] not in vnf_mapping.keys():
raise nfvo.NfpForwarderNotFoundException(vnfd=element[
'forwarder'],
mapping=vnf_mapping)
# TODO(trozet): validate CP in VNFD has forwarding capability
# Find VNF resources
vnf = vnfm_plugin.get_vnf_resources(context,
vnf_mapping[element[
'forwarder']]
)
vnf_info = vnfm_plugin.get_vnf(context,
vnf_mapping[element['forwarder']])
vnf_cp = None
for resource in vnf:
if resource['name'] == element['capability']:
vnf_cp = resource['id']
break
if vnf_cp is None:
raise nfvo.VnffgCpNotFoundException(cp_id=element[
'capability'], vnf_id=vnf_mapping[element['forwarder']])
# Check if this is a new VNF entry in the chain
if element['forwarder'] != prev_forwarder:
chain_list.append({'name': vnf_info['name'],
CP: [vnf_cp]})
prev_forwarder = element['forwarder']
# Must be an egress CP
else:
if len(chain_list[-1][CP]) > 1:
raise nfvo.NfpRequirementsException(vnfd=element[
'forwarder'])
else:
chain_list[-1]['connection_points'].append(vnf_cp)
return chain_list
@staticmethod
def _get_vnffg_property(template_db, vnffg_property):
template = template_db.template['vnffgd']['topology_template']
vnffg_name = template['groups'].keys()[0]
try:
return template['groups'][vnffg_name]['properties'][vnffg_property]
except KeyError:
raise nfvo.VnffgPropertyNotFoundException(
vnffg_property=vnffg_property)
@staticmethod
def _get_nfp_attribute(template, nfp, attribute):
"""Finds any attribute of an NFP described in a template
:param template: VNFFGD template
:param nfp: name of NFP
:param attribute: attribute to find
:return: value of attribute from template
"""
template = template['vnffgd']['topology_template']
try:
attr_val = VnffgPluginDbMixin._search_value(
template['node_templates'][nfp], attribute)
if attr_val is None:
print(template['node_templates'][nfp])
raise nfvo.NfpAttributeNotFoundException(attribute=attribute)
else:
return attr_val
except KeyError:
raise nfvo.NfpAttributeNotFoundException(attribute=attribute)
@staticmethod
def _search_value(search_dict, search_key):
for k, v in search_dict.iteritems():
if k == search_key:
return v
elif isinstance(v, dict):
val = VnffgPluginDbMixin._search_value(v, search_key)
if val is not None:
return val
def _get_vnf_mapping(self, context, vnf_mapping, vnf_members):
"""Creates/validates a mapping of VNFD names to VNF IDs for NFP.
:param context: SQL session context
:param vnf_mapping: dict of requested VNFD:VNF_ID mappings
:param vnf_members: list of constituent VNFs from a VNFFG
:return: dict of VNFD:VNF_ID mappings
"""
vnfm_plugin = manager.TackerManager.get_service_plugins()['VNFM']
new_mapping = dict()
for vnfd in vnf_members:
# there should only be one ID returned for a unique name
try:
vnfd_id = vnfm_plugin.get_vnfds(context, {'name': [vnfd]},
fields=['id']).pop()['id']
except Exception:
raise nfvo.VnffgdVnfdNotFoundException(vnfd_name=vnfd)
if vnfd_id is None:
raise nfvo.VnffgdVnfdNotFoundException(vnfd_name=vnfd)
else:
# if no VNF mapping, we need to abstractly look for instances
# that match VNFD
if vnf_mapping is None or vnfd not in vnf_mapping.keys():
# find suitable VNFs from vnfd_id
LOG.debug(_('Searching VNFS with id %s'), vnfd_id)
vnf_list = vnfm_plugin.get_vnfs(context,
{'vnfd_id': [vnfd_id]},
fields=['id'])
if vnf_list is None:
raise nfvo.VnffgInvalidMappingException(vnfd_name=vnfd)
else:
LOG.debug(_('Matching VNFs found %s'), vnf_list)
vnf_list = [vnf['id'] for vnf in vnf_list]
if len(vnf_list) > 1:
new_mapping[vnfd] = random.choice(vnf_list)
else:
new_mapping[vnfd] = vnf_list[0]
# if VNF mapping, validate instances exist and match the VNFD
else:
vnf_vnfd = vnfm_plugin.get_vnf(context, vnf_mapping[vnfd],
fields=['vnfd_id'])
if vnf_vnfd is not None:
vnf_vnfd_id = vnf_vnfd['vnfd_id']
else:
raise nfvo.VnffgInvalidMappingException(vnfd_name=vnfd)
if vnfd_id != vnf_vnfd_id:
raise nfvo.VnffgInvalidMappingException(vnfd_name=vnfd)
else:
new_mapping[vnfd] = vnf_mapping.pop(vnfd)
self._validate_vim(context, new_mapping.values())
return new_mapping
def _validate_vim(self, context, vnfs):
"""Validates all VNFs are in the same VIM
:param context: SQL Session Context
:param vnfs: List of VNF instance IDs
:return: None
"""
LOG.debug(_('validating vim for vnfs %s'), vnfs)
vnfm_plugin = manager.TackerManager.get_service_plugins()['VNFM']
vim_id = None
for vnf in vnfs:
vnf_dict = vnfm_plugin.get_vnf(context, vnf)
if vim_id is None:
vim_id = vnf_dict['vim_id']
elif vnf_dict['vim_id'] != vim_id:
raise nfvo.VnffgVimMappingException(vnf_id=vnf, vim_id=vim_id)
def _policy_to_acl_criteria(self, context, template_db, nfp_name,
vnf_mapping):
template = template_db.template['vnffgd']['topology_template']
nfp = template['node_templates'][nfp_name]
try:
policy = nfp['properties']['policy']
except KeyError:
raise nfvo.NfpPolicyNotFoundException(policy=nfp)
if 'type' in policy:
if policy['type'] != 'ACL':
raise nfvo.NfpPolicyTypeError(type=policy['type'])
if 'criteria' not in policy:
raise nfvo.NfpPolicyCriteriaError(error="Missing criteria in "
"policy")
match = dict()
for criteria in policy['criteria']:
for key, val in criteria.iteritems():
if key in MATCH_CRITERIA:
match.update(self._convert_criteria(context, key, val,
vnf_mapping))
else:
raise nfvo.NfpPolicyCriteriaError(error="Unsupported "
"criteria: "
"{}".format(key))
return match
def _convert_criteria(self, context, criteria, value, vnf_mapping):
"""Method is used to convert criteria to proper db value from template
:param context: SQL session context
:param criteria: input criteria name
:param value: input value
:param vnf_mapping: mapping of VNFD to VNF instances
:return: converted dictionary
"""
if criteria.endswith('_range'):
prefix = criteria[:-6]
criteria_min = prefix + "_min"
criteria_max = prefix + "_max"
try:
min_val, max_val = value.split('-')
except ValueError:
raise nfvo.NfpPolicyCriteriaError(error="Range missing or "
"incorrect for "
"%s".format(criteria))
return {criteria_min: int(min_val), criteria_max: int(max_val)}
elif criteria.endswith('_name'):
prefix = criteria[:-5]
vnf_id = vnf_mapping.values()[0]
new_value = self._vim_resource_name_to_id(context, prefix, value,
vnf_id)
new_name = prefix + "_id"
return {new_name: new_value}
else:
return {criteria: value}
def _vim_resource_name_to_id(self, context, resource, name, vnf_id):
"""Converts a VIM resource name to its ID
:param context: SQL session context
:param resource: resource type to find (network, subnet, etc)
:param name: name of the resource to find its ID
:param vnf_id: A VNF instance ID that is part of the chain to which
the classifier will apply to
:return: ID of the resource name
"""
# this should be overridden with driver call to find ID given name
# for resource
return str(uuid.uuid4())
# called internally, not by REST API
# instance_id = None means error on creation
def _create_vnffg_post(self, context, sfc_instance_id,
fc_instance_id, vnffg_dict):
LOG.debug(_('SFC created instance is %s'), sfc_instance_id)
LOG.debug(_('Flow Classifier created instance is %s'),
fc_instance_id)
nfp_dict = self.get_nfp(context, vnffg_dict['forwarding_paths'])
sfc_id = nfp_dict['chain_id']
classifier_id = nfp_dict['classifier_id']
with context.session.begin(subtransactions=True):
query = (self._model_query(context, VnffgChain).
filter(VnffgChain.id == sfc_id).
filter(VnffgChain.status == constants.PENDING_CREATE).
one())
query.update({'instance_id': sfc_instance_id})
if sfc_instance_id is None:
query.update({'status': constants.ERROR})
else:
query.update({'status': constants.ACTIVE})
query = (self._model_query(context, VnffgClassifier).
filter(VnffgClassifier.id == classifier_id).
filter(VnffgClassifier.status ==
constants.PENDING_CREATE).
one())
query.update({'instance_id': fc_instance_id})
if fc_instance_id is None:
query.update({'status': constants.ERROR})
else:
query.update({'status': constants.ACTIVE})
def _create_vnffg_status(self, context, vnffg):
nfp = self.get_nfp(context, vnffg['forwarding_paths'])
chain = self.get_sfc(context, nfp['chain_id'])
classifier = self.get_classifier(context, nfp['classifier_id'])
if classifier['status'] == constants.ERROR or chain['status'] ==\
constants.ERROR:
self._update_all_status(context, vnffg['id'], nfp['id'],
constants.ERROR)
elif classifier['status'] == constants.ACTIVE and \
chain['status'] == constants.ACTIVE:
self._update_all_status(context, vnffg['id'], nfp['id'],
constants.ACTIVE)
def _update_all_status(self, context, vnffg_id, nfp_id, status):
with context.session.begin(subtransactions=True):
query = (self._model_query(context, Vnffg).
filter(Vnffg.id == vnffg_id))
query.update({'status': status})
nfp_query = (self._model_query(context, VnffgNfp).
filter(VnffgNfp.id == nfp_id))
nfp_query.update({'status': status})
def _make_vnffg_dict(self, vnffg_db, fields=None):
LOG.debug(_('vnffg_db %s'), vnffg_db)
LOG.debug(_('vnffg_db nfp %s'), vnffg_db.forwarding_paths)
res = {
'forwarding_paths': vnffg_db.forwarding_paths[0]['id']
}
key_list = ('id', 'tenant_id', 'name', 'description',
'vnf_mapping', 'status', 'vnffgd_id')
res.update((key, vnffg_db[key]) for key in key_list)
return self._fields(res, fields)
def _update_vnffg_pre(self, context, vnffg_id):
vnffg = self.get_vnffg(context, vnffg_id)
nfp = self.get_nfp(context, vnffg['forwarding_paths'])
sfc = self.get_sfc(context, nfp['chain_id'])
fc = self.get_classifier(context, nfp['classifier_id'])
with context.session.begin(subtransactions=True):
vnffg_db = self._get_vnffg_db(context, vnffg['id'], _ACTIVE_UPDATE,
constants.PENDING_UPDATE)
self._get_nfp_db(context, nfp['id'], _ACTIVE_UPDATE,
constants.PENDING_UPDATE)
self._get_sfc_db(context, sfc['id'], _ACTIVE_UPDATE,
constants.PENDING_UPDATE)
self._get_classifier_db(context, fc['id'], _ACTIVE_UPDATE,
constants.PENDING_UPDATE)
return self._make_vnffg_dict(vnffg_db)
def _update_vnffg_post(self, context, vnffg_id, new_status,
new_vnffg=None):
vnffg = self.get_vnffg(context, vnffg_id)
nfp = self.get_nfp(context, vnffg['forwarding_paths'])
with context.session.begin(subtransactions=True):
query = (self._model_query(context, Vnffg).
filter(Vnffg.id == vnffg['id']).
filter(Vnffg.status == constants.PENDING_UPDATE))
query.update({'status': new_status})
nfp_query = (self._model_query(context, VnffgNfp).
filter(VnffgNfp.id == nfp['id']).
filter(VnffgNfp.status == constants.PENDING_UPDATE))
nfp_query.update({'status': new_status})
if new_vnffg is not None:
for key in _VALID_VNFFG_UPDATE_ATTRIBUTES:
query.update({key: new_vnffg[key]})
nfp_query.update({'symmetrical': new_vnffg['symmetrical']})
def _update_sfc_post(self, context, sfc_id, new_status, new_sfc=None):
with context.session.begin(subtransactions=True):
sfc_query = (self._model_query(context, VnffgChain).
filter(VnffgChain.id == sfc_id).
filter(VnffgChain.status == constants.PENDING_UPDATE))
sfc_query.update({'status': new_status})
if new_sfc is not None:
for key in _VALID_SFC_UPDATE_ATTRIBUTES:
sfc_query.update({key: new_sfc[key]})
def _update_classifier_post(self, context, sfc_id, new_status,
new_fc=None):
with context.session.begin(subtransactions=True):
fc_query = (self._model_query(context, VnffgClassifier).
filter(VnffgClassifier.id == sfc_id).
filter(VnffgClassifier.status ==
constants.PENDING_UPDATE))
fc_query.update({'status': new_status})
if new_fc is not None:
for key in _VALID_FC_UPDATE_ATTRIBUTES:
fc_query.update({key: new_fc[key]})
def _get_vnffg_db(self, context, vnffg_id, current_statuses, new_status):
try:
vnffg_db = (
self._model_query(context, Vnffg).
filter(Vnffg.id == vnffg_id).
filter(Vnffg.status.in_(current_statuses)).
with_lockmode('update').one())
except orm_exc.NoResultFound:
raise nfvo.VnffgNotFoundException(vnffg_id=vnffg_id)
if vnffg_db.status == constants.PENDING_UPDATE:
raise nfvo.VnffgInUse(vnffg_id=vnffg_id)
vnffg_db.update({'status': new_status})
return vnffg_db
def _get_nfp_db(self, context, nfp_id, current_statuses, new_status):
try:
nfp_db = (
self._model_query(context, VnffgNfp).
filter(VnffgNfp.id == nfp_id).
filter(VnffgNfp.status.in_(current_statuses)).
with_lockmode('update').one())
except orm_exc.NoResultFound:
raise nfvo.NfpNotFoundException(nfp_id=nfp_id)
if nfp_db.status == constants.PENDING_UPDATE:
raise nfvo.NfpInUse(nfp_id=nfp_id)
nfp_db.update({'status': new_status})
return nfp_db
def _get_sfc_db(self, context, sfc_id, current_statuses, new_status):
try:
sfc_db = (
self._model_query(context, VnffgChain).
filter(VnffgChain.id == sfc_id).
filter(VnffgChain.status.in_(current_statuses)).
with_lockmode('update').one())
except orm_exc.NoResultFound:
raise nfvo.SfcNotFoundException(sfc_id=sfc_id)
if sfc_db.status == constants.PENDING_UPDATE:
raise nfvo.SfcInUse(sfc_id=sfc_id)
sfc_db.update({'status': new_status})
return sfc_db
def _get_classifier_db(self, context, fc_id, current_statuses, new_status):
try:
fc_db = (
self._model_query(context, VnffgClassifier).
filter(VnffgClassifier.id == fc_id).
filter(VnffgClassifier.status.in_(current_statuses)).
with_lockmode('update').one())
except orm_exc.NoResultFound:
raise nfvo.ClassifierNotFoundException(fc_id=fc_id)
if fc_db.status == constants.PENDING_UPDATE:
raise nfvo.ClassifierInUse(fc_id=fc_id)
fc_db.update({'status': new_status})
return fc_db
def _delete_vnffg_pre(self, context, vnffg_id):
vnffg = self.get_vnffg(context, vnffg_id)
nfp = self.get_nfp(context, vnffg['forwarding_paths'])
chain = self.get_sfc(context, nfp['chain_id'])
classifier = self.get_classifier(context, nfp['classifier_id'])
with context.session.begin(subtransactions=True):
vnffg_db = self._get_vnffg_db(
context, vnffg['id'], _ACTIVE_UPDATE_ERROR_DEAD,
constants.PENDING_DELETE)
self._get_nfp_db(context, nfp['id'], _ACTIVE_UPDATE_ERROR_DEAD,
constants.PENDING_DELETE)
self._get_sfc_db(context, chain['id'], _ACTIVE_UPDATE_ERROR_DEAD,
constants.PENDING_DELETE)
self._get_classifier_db(context, classifier['id'],
_ACTIVE_UPDATE_ERROR_DEAD,
constants.PENDING_DELETE)
return self._make_vnffg_dict(vnffg_db)
def _delete_vnffg_post(self, context, vnffg_id, error):
vnffg = self.get_vnffg(context, vnffg_id)
nfp = self.get_nfp(context, vnffg['forwarding_paths'])
chain = self.get_sfc(context, nfp['chain_id'])
classifier = self.get_classifier(context, nfp['classifier_id'])
with context.session.begin(subtransactions=True):
vnffg_query = (
self._model_query(context, Vnffg).
filter(Vnffg.id == vnffg['id']).
filter(Vnffg.status == constants.PENDING_DELETE))
nfp_query = (
self._model_query(context, VnffgNfp).
filter(VnffgNfp.id == nfp['id']).
filter(VnffgNfp.status == constants.PENDING_DELETE))
sfc_query = (
self._model_query(context, VnffgChain).
filter(VnffgChain.id == chain['id']).
filter(VnffgChain.status == constants.PENDING_DELETE))
fc_query = (
self._model_query(context, VnffgClassifier).
filter(VnffgClassifier.id == classifier['id']).
filter(VnffgClassifier.status == constants.PENDING_DELETE))
match_query = (
self._model_query(context, ACLMatchCriteria).
filter(ACLMatchCriteria.vnffgc_id == classifier['id']))
if error:
vnffg_query.update({'status': constants.ERROR})
nfp_query.update({'status': constants.ERROR})
sfc_query.update({'status': constants.ERROR})
fc_query.update({'status': constants.ERROR})
else:
match_query.delete()
fc_query.delete()
sfc_query.delete()
nfp_query.delete()
vnffg_query.delete()
def _make_template_dict(self, template, fields=None):
res = {}
key_list = ('id', 'tenant_id', 'name', 'description', 'template')
res.update((key, template[key]) for key in key_list)
return self._fields(res, fields)
def _make_acl_match_dict(self, acl_match_db):
key_list = MATCH_DB_KEY_LIST
return {key: entry[key] for key in key_list for entry in acl_match_db
if entry[key]}
def _make_classifier_dict(self, classifier_db, fields=None):
LOG.debug(_('classifier_db %s'), classifier_db)
LOG.debug(_('classifier_db match %s'), classifier_db.match)
res = {
'match': self._make_acl_match_dict(classifier_db.match)
}
key_list = ('id', 'tenant_id', 'instance_id', 'status', 'chain_id',
'nfp_id')
res.update((key, classifier_db[key]) for key in key_list)
return self._fields(res, fields)
def _make_nfp_dict(self, nfp_db, fields=None):
LOG.debug(_('nfp_db %s'), nfp_db)
res = {'chain_id': nfp_db.chain['id'],
'classifier_id': nfp_db.classifier['id']}
key_list = ('name', 'id', 'tenant_id', 'symmetrical', 'status',
'path_id')
res.update((key, nfp_db[key]) for key in key_list)
return self._fields(res, fields)
def _make_chain_dict(self, chain_db, fields=None):
LOG.debug(_('chain_db %s'), chain_db)
res = {}
key_list = ('id', 'tenant_id', 'symmetrical', 'status', 'chain',
'path_id', 'nfp_id', 'instance_id')
res.update((key, chain_db[key]) for key in key_list)
return self._fields(res, fields)
def _get_resource(self, context, model, res_id):
try:
return self._get_by_id(context, model, res_id)
except orm_exc.NoResultFound:
if issubclass(model, Vnffg):
raise nfvo.VnffgNotFoundException(vnffg_id=res_id)
elif issubclass(model, VnffgClassifier):
raise nfvo.ClassifierNotFoundException(classifier_id=res_id)
if issubclass(model, VnffgTemplate):
raise nfvo.VnffgdNotFoundException(vnffgd_id=res_id)
if issubclass(model, VnffgChain):
raise nfvo.SfcNotFoundException(sfc_id=res_id)
else:
raise

View File

@ -17,6 +17,7 @@ import abc
import six import six
from tacker._i18n import _
from tacker.api import extensions from tacker.api import extensions
from tacker.api.v1 import attributes as attr from tacker.api.v1 import attributes as attr
from tacker.api.v1 import resource_helper from tacker.api.v1 import resource_helper
@ -73,6 +74,151 @@ class VimDuplicateUrlException(exceptions.TackerException):
"duplicate VIM") "duplicate VIM")
class VimUnsupportedResourceTypeException(exceptions.TackerException):
message = _("Resource type %(type) is unsupported by VIM")
class VimGetResourceException(exceptions.TackerException):
message = _("Error while trying to issue %(cmd)s to find resource type "
"%(type)s")
class VimFromVnfNotFoundException(exceptions.NotFound):
message = _('VIM from VNF %(vnf_id)s could not be found')
class ToscaParserFailed(exceptions.InvalidInput):
message = _("tosca-parser failed: - %(error_msg_details)s")
class VnffgdInvalidTemplate(exceptions.InvalidInput):
message = _("Invalid VNFFG template input: %(template)s")
class VnffgdDuplicateForwarderException(exceptions.InvalidInput):
message = _("Invalid Forwarding Path contains duplicate forwarder not in "
"order: %(forwarder)s")
class VnffgdDuplicateCPException(exceptions.InvalidInput):
message = _("Invalid Forwarding Path contains duplicate connection point "
": %(cp)s")
class VnffgdVnfdNotFoundException(exceptions.NotFound):
message = _("Specified VNFD %(vnfd_name)s in VNFFGD does not exist. "
"Please create VNFDs before creating VNFFG")
class VnffgdCpNotFoundException(exceptions.NotFound):
message = _("Specified CP %(cp_id)s could not be found in VNFD "
"%(vnfd_name)s. Please check VNFD for correct Connection "
"Point.")
class VnffgdCpNoForwardingException(exceptions.TackerException):
message = _("Specified CP %(cp_id)s in VNFD %(vnfd_name)s "
"does not have forwarding capability, which is required to be "
"included in forwarding path")
class VnffgdInUse(exceptions.InUse):
message = _('VNFFGD %(vnffgd_id)s is still in use')
class VnffgdNotFoundException(exceptions.NotFound):
message = _('VNFFG Template %(vnffgd_id)s could not be found')
class VnffgCreateFailed(exceptions.TackerException):
message = _('Creating VNFFG based on %(vnffgd_id)s failed')
class VnffgInvalidMappingException(exceptions.TackerException):
message = _("Matching VNF Instance for VNFD %(vnfd_name)s could not be "
"found. Please create an instance of this VNFD before "
"creating/updating VNFFG.")
class VnffgVimMappingException(exceptions.TackerException):
message = _("VNF Instance VNF %(vnf_id)s does not match VIM ID %(vim_id).")
class VnffgPropertyNotFoundException(exceptions.NotFound):
message = _('VNFFG Property %(vnffg_property)s could not be found')
class VnffgCpNotFoundException(exceptions.NotFound):
message = _("Specified CP %(cp_id)s could not be found in VNF "
"%(vnf_id)s.")
class VnffgNotFoundException(exceptions.NotFound):
message = _('VNFFG %(vnffg_id)s could not be found')
class VnffgInUse(exceptions.InUse):
message = _('VNFFG %(vnffg_id)s is still in use')
class VnffgVnfNotFoundException(exceptions.NotFound):
message = _("Specified VNF instance %(vnf_name)s in VNF Mapping could not "
"be found")
class VnffgDeleteFailed(exceptions.TackerException):
message = _('Deleting VNFFG %(vnffg_id)s failed')
class NfpAttributeNotFoundException(exceptions.NotFound):
message = _('NFP attribute %(attribute)s could not be found')
class NfpNotFoundException(exceptions.NotFound):
message = _('NFP %(nfp_id)s could not be found')
class NfpInUse(exceptions.InUse):
message = _('NFP %(nfp_id)s is still in use')
class NfpPolicyCriteriaError(exceptions.PolicyCheckError):
message = _('%(error)s in policy')
class NfpPolicyNotFoundException(exceptions.NotFound):
message = _('Policy not found in NFP %(nfp)s')
class NfpPolicyTypeError(exceptions.PolicyCheckError):
message = _('Unsupported Policy Type: %(type)s')
class NfpForwarderNotFoundException(exceptions.NotFound):
message = _('VNFD Forwarder %(vnfd)s not found in VNF Mapping %(mapping)s')
class NfpRequirementsException(exceptions.TackerException):
message = _('VNFD Forwarder %(vnfd) specified more than twice in '
'requirements path')
class SfcInUse(exceptions.InUse):
message = _('SFC %(sfc_id)s is still in use')
class SfcNotFoundException(exceptions.NotFound):
message = _('Service Function Chain %(sfc_id)s could not be found')
class ClassifierInUse(exceptions.InUse):
message = _('Classifier %(classifier_id)s is still in use')
class ClassifierNotFoundException(exceptions.NotFound):
message = _('Classifier %(classifier_id)s could not be found')
RESOURCE_ATTRIBUTE_MAP = { RESOURCE_ATTRIBUTE_MAP = {
'vims': { 'vims': {
@ -162,6 +308,257 @@ RESOURCE_ATTRIBUTE_MAP = {
'allow_put': False, 'allow_put': False,
'is_visible': True, 'is_visible': True,
}, },
},
'vnffgds': {
'id': {
'allow_post': False,
'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
'primary_key': True,
},
'tenant_id': {
'allow_post': True,
'allow_put': False,
'validate': {'type:string': None},
'required_by_policy': True,
'is_visible': True,
},
'name': {
'allow_post': True,
'allow_put': True,
'validate': {'type:string': None},
'is_visible': True,
},
'description': {
'allow_post': True,
'allow_put': True,
'validate': {'type:string': None},
'is_visible': True,
'default': '',
},
'template': {
'allow_post': True,
'allow_put': False,
'convert_to': attr.convert_none_to_empty_dict,
'validate': {'type:dict_or_nodata': None},
'is_visible': True,
'default': None,
},
},
'vnffgs': {
'id': {
'allow_post': False,
'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
'primary_key': True
},
'tenant_id': {
'allow_post': True,
'allow_put': False,
'validate': {'type:string': None},
'required_by_policy': True,
'is_visible': True
},
'vnffgd_id': {
'allow_post': True,
'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
},
'name': {
'allow_post': True,
'allow_put': True,
'validate': {'type:string': None},
'is_visible': True,
},
'description': {
'allow_post': True,
'allow_put': True,
'validate': {'type:string': None},
'is_visible': True,
'default': '',
},
'vnf_mapping': {
'allow_post': True,
'allow_put': True,
'convert_to': attr.convert_none_to_empty_dict,
'validate': {'type:dict_or_nodata': None},
'is_visible': True,
'default': None,
},
'symmetrical': {
'allow_post': True,
'allow_put': True,
'is_visible': True,
'validate': {'type:boolean': None},
'default': False,
},
'forwarding_paths': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
},
'status': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
},
},
'nfps': {
'id': {
'allow_post': False,
'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
'primary_key': True
},
'tenant_id': {
'allow_post': False,
'allow_put': False,
'validate': {'type:string': None},
'required_by_policy': True,
'is_visible': True
},
'vnffg_id': {
'allow_post': False,
'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
},
'name': {
'allow_post': False,
'allow_put': False,
'validate': {'type:string': None},
'is_visible': True,
},
'classifier_id': {
'allow_post': False,
'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
},
'chain_id': {
'allow_post': False,
'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
},
'path_id': {
'allow_post': False,
'allow_put': False,
'validate': {'type:string': None},
'is_visible': True,
},
'symmetrical': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
'validate': {'type:boolean': None},
'default': False,
},
'status': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
},
},
'sfcs': {
'id': {
'allow_post': False,
'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
'primary_key': True
},
'tenant_id': {
'allow_post': False,
'allow_put': False,
'validate': {'type:string': None},
'required_by_policy': True,
'is_visible': True
},
'nfp_id': {
'allow_post': False,
'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
},
'instance_id': {
'allow_post': False,
'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
},
'chain': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
},
'path_id': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
},
'symmetrical': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
'validate': {'type:boolean': None},
'default': False,
},
'status': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
},
},
'classifiers': {
'id': {
'allow_post': False,
'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
'primary_key': True
},
'tenant_id': {
'allow_post': False,
'allow_put': False,
'validate': {'type:string': None},
'required_by_policy': True,
'is_visible': True
},
'nfp_id': {
'allow_post': False,
'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
},
'instance_id': {
'allow_post': False,
'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
},
'match': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
},
'chain_id': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
},
'status': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
},
} }
} }

View File

@ -0,0 +1,83 @@
# Copyright 2016 Red Hat Inc
# 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.
import abc
import six
from tacker.services import service_base
@six.add_metaclass(abc.ABCMeta)
class VNFFGPluginBase(service_base.NFVPluginBase):
@abc.abstractmethod
def create_vnffgd(self, context, vnffgd):
pass
@abc.abstractmethod
def delete_vnffgd(self, context, vnffgd_id):
pass
@abc.abstractmethod
def get_vnffgd(self, context, vnffgd_id, fields=None):
pass
@abc.abstractmethod
def get_vnffgds(self, context, filters=None, fields=None):
pass
@abc.abstractmethod
def create_vnffg(self, context, vnffg):
pass
@abc.abstractmethod
def get_vnffgs(self, context, filters=None, fields=None):
pass
@abc.abstractmethod
def get_vnffg(self, context, vnffg_id, fields=None):
pass
@abc.abstractmethod
def update_vnffg(self, context, vnffg_id, vnffg):
pass
@abc.abstractmethod
def delete_vnffg(self, context, vnffg_id):
pass
@abc.abstractmethod
def get_nfp(self, context, nfp_id, fields=None):
pass
@abc.abstractmethod
def get_nfps(self, context, filters=None, fields=None):
pass
@abc.abstractmethod
def get_sfcs(self, context, filters=None, fields=None):
pass
@abc.abstractmethod
def get_sfc(self, context, sfc_id, fields=None):
pass
@abc.abstractmethod
def get_classifiers(self, context, filters=None, fields=None):
pass
@abc.abstractmethod
def get_classifier(self, context, classifier_id, fields=None):
pass

View File

@ -90,3 +90,14 @@ class VimAbstractDriver(extensions.PluginInterface):
Checks the health status of VIM and return a boolean value Checks the health status of VIM and return a boolean value
""" """
pass pass
@abc.abstractmethod
def get_vim_resource_id(self, vim_obj, resource_type, resource_name):
"""Parses a VIM resource ID from a given type and name
:param vim_obj: VIM information
:param resource_type: type of resource, such as network, compute
:param resource_name: name of resource, such at "test-network"
:return: ID of of resource
"""
pass

View File

@ -16,11 +16,15 @@
import os import os
from keystoneclient.auth.identity import v2
from keystoneclient.auth.identity import v3
from keystoneclient import exceptions from keystoneclient import exceptions
from keystoneclient import session
from neutronclient.v2_0 import client as neutron_client
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
from tacker._i18n import _LW from tacker._i18n import _LW, _
from tacker.agent.linux import utils as linux_utils from tacker.agent.linux import utils as linux_utils
from tacker.common import log from tacker.common import log
from tacker.extensions import nfvo from tacker.extensions import nfvo
@ -46,6 +50,11 @@ OPENSTACK_OPTS = [
cfg.CONF.register_opts(OPTS, 'vim_keys') cfg.CONF.register_opts(OPTS, 'vim_keys')
cfg.CONF.register_opts(OPENSTACK_OPTS, 'vim_monitor') cfg.CONF.register_opts(OPENSTACK_OPTS, 'vim_monitor')
_VALID_RESOURCE_TYPES = {'network': {'client': neutron_client.Client,
'cmd': 'list_'
}
}
def config_opts(): def config_opts():
return [('vim_keys', OPTS), ('vim_monitor', OPENSTACK_OPTS)] return [('vim_keys', OPTS), ('vim_monitor', OPENSTACK_OPTS)]
@ -78,10 +87,15 @@ class OpenStack_Driver(abstract_vim_driver.VimAbstractDriver):
Initialize keystoneclient with provided authentication attributes. Initialize keystoneclient with provided authentication attributes.
""" """
auth_url = vim_obj['auth_url']
keystone_version = self._validate_auth_url(auth_url)
auth_cred = self._get_auth_creds(keystone_version, vim_obj)
return self._initialize_keystone(keystone_version, auth_cred)
def _get_auth_creds(self, keystone_version, vim_obj):
auth_url = vim_obj['auth_url'] auth_url = vim_obj['auth_url']
auth_cred = vim_obj['auth_cred'] auth_cred = vim_obj['auth_cred']
vim_project = vim_obj['vim_project'] vim_project = vim_obj['vim_project']
keystone_version = self._validate_auth_url(auth_url)
if keystone_version not in auth_url: if keystone_version not in auth_url:
vim_obj['auth_url'] = auth_url + '/' + keystone_version vim_obj['auth_url'] = auth_url + '/' + keystone_version
@ -97,7 +111,15 @@ class OpenStack_Driver(abstract_vim_driver.VimAbstractDriver):
auth_cred.pop('user_domain_name', None) auth_cred.pop('user_domain_name', None)
auth_cred.pop('user_id', None) auth_cred.pop('user_id', None)
auth_cred['auth_url'] = vim_obj['auth_url'] auth_cred['auth_url'] = vim_obj['auth_url']
return self._initialize_keystone(keystone_version, auth_cred) return auth_cred
def _get_auth_plugin(self, version, **kwargs):
if version == 'v2.0':
auth_plugin = v2.Password(**kwargs)
else:
auth_plugin = v3.Password(**kwargs)
return auth_plugin
def _validate_auth_url(self, auth_url): def _validate_auth_url(self, auth_url):
try: try:
@ -207,3 +229,44 @@ class OpenStack_Driver(abstract_vim_driver.VimAbstractDriver):
except RuntimeError: except RuntimeError:
LOG.warning(_LW("Cannot ping ip address: %s"), vim_ip) LOG.warning(_LW("Cannot ping ip address: %s"), vim_ip)
return False return False
@log.log
def get_vim_resource_id(self, vim_obj, resource_type, resource_name):
"""Locates openstack resource by type/name and returns ID
:param vim_obj: VIM info used to access openstack instance
:param resource_type: type of resource to find
:param resource_name: name of resource to locate
:return: ID of resource
"""
if resource_type in _VALID_RESOURCE_TYPES.keys():
client_type = _VALID_RESOURCE_TYPES[resource_type]['client']
cmd_prefix = _VALID_RESOURCE_TYPES[resource_type]['cmd']
else:
raise nfvo.VimUnsupportedResourceTypeException(type=resource_type)
client = self._get_client(vim_obj, client_type)
cmd = str(cmd_prefix) + str(resource_name)
try:
resources = getattr(client, "%s" % cmd)()
LOG.debug(_('resources output %s'), resources)
for resource in resources[resource_type]:
if resource['name'] == resource_name:
return resource['id']
except Exception:
raise nfvo.VimGetResourceException(cmd=cmd, type=resource_type)
@log.log
def _get_client(self, vim_obj, client_type):
"""Initializes and returns an openstack client
:param vim_obj: VIM Information
:param client_type: openstack client to initialize
:return: initialized client
"""
auth_url = vim_obj['auth_url']
keystone_version = self._validate_auth_url(auth_url)
auth_cred = self._get_auth_creds(keystone_version, vim_obj)
auth_plugin = self._get_auth_plugin(keystone_version, **auth_cred)
sess = session.Session(auth=auth_plugin)
return client_type(session=sess)

View File

@ -0,0 +1,72 @@
# Copyright 2016 Red Hat Inc
# 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.
import uuid
from oslo_log import log as logging
from tacker.common import log
from tacker.nfvo.drivers.vnffg.sfc_drivers import abstract_driver
LOG = logging.getLogger(__name__)
class VNFFGNoop(abstract_driver.SfcAbstractDriver):
"""Noop driver for VNFFG tests"""
def __init__(self):
super(VNFFGNoop, self).__init__()
self._instances = set()
def get_type(self):
return 'noop'
def get_name(self):
return 'noop'
def get_description(self):
return 'VNFFG Noop driver'
@log.log
def create_chain(self, fc_id, vnfs, auth_attr=None):
instance_id = str(uuid.uuid4())
self._instances.add(instance_id)
return instance_id
@log.log
def update_chain(self, chain_id, fc_ids, vnfs, auth_attr=None):
if chain_id not in self._instances:
LOG.debug(_('Chain not found'))
raise ValueError('No chain instance %s' % chain_id)
@log.log
def delete_chain(self, chain_id, auth_attr=None):
self._instances.remove(chain_id)
@log.log
def create_flow_classifier(self, fc, auth_attr=None):
instance_id = str(uuid.uuid4())
self._instances.add(instance_id)
return instance_id
@log.log
def update_flow_classifier(self, fc_id, fc, auth_attr=None):
if fc_id not in self._instances:
LOG.debug(_('FC not found'))
raise ValueError('No FC instance %s' % fc_id)
@log.log
def delete_flow_classifier(self, fc_id, auth_attr=None):
self._instances.remove(fc_id)

View File

@ -23,11 +23,18 @@ from oslo_log import log as logging
from oslo_utils import excutils from oslo_utils import excutils
from oslo_utils import strutils from oslo_utils import strutils
from tacker._i18n import _
from tacker.common import driver_manager from tacker.common import driver_manager
from tacker.common import log from tacker.common import log
from tacker.common import utils from tacker.common import utils
from tacker import context as t_context from tacker import context as t_context
from tacker.db.nfvo import nfvo_db from tacker.db.nfvo import nfvo_db
from tacker.db.nfvo import vnffg_db
from tacker.extensions import nfvo
from tacker import manager
from tacker.plugins.common import constants
from tacker.vnfm.tosca import utils as toscautils
from toscaparser import tosca_template
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -36,7 +43,7 @@ def config_opts():
return [('nfvo', NfvoPlugin.OPTS)] return [('nfvo', NfvoPlugin.OPTS)]
class NfvoPlugin(nfvo_db.NfvoPluginDb): class NfvoPlugin(nfvo_db.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin):
"""NFVO reference plugin for NFVO extension """NFVO reference plugin for NFVO extension
Implements the NFVO extension and defines public facing APIs for VIM Implements the NFVO extension and defines public facing APIs for VIM
@ -138,3 +145,218 @@ class NfvoPlugin(nfvo_db.NfvoPluginDb):
t_context.get_admin_context(), t_context.get_admin_context(),
vim_id, status) vim_id, status)
self._created_vims[vim_id]["status"] = status self._created_vims[vim_id]["status"] = status
@log.log
def validate_tosca(self, template):
if "tosca_definitions_version" not in template:
raise nfvo.ToscaParserFailed(
error_msg_details='tosca_definitions_version missing in '
'template'
)
LOG.debug(_('template yaml: %s'), template)
toscautils.updateimports(template)
try:
tosca_template.ToscaTemplate(
a_file=False, yaml_dict_tpl=template)
except Exception as e:
LOG.exception(_("tosca-parser error: %s"), str(e))
raise nfvo.ToscaParserFailed(error_msg_details=str(e))
@log.log
def create_vnffgd(self, context, vnffgd):
template = vnffgd['vnffgd']
if 'vnffgd' not in template.get('template'):
raise nfvo.VnffgdInvalidTemplate(template=template.get('template'))
else:
self.validate_tosca(template['template']['vnffgd'])
temp = template['template']['vnffgd']['topology_template']
vnffg_name = temp['groups'].keys()[0]
nfp_name = temp['groups'][vnffg_name]['members'][0]
path = self._get_nfp_attribute(template['template'], nfp_name,
'path')
prev_element = None
known_forwarders = set()
for element in path:
if element.get('forwarder') in known_forwarders:
if prev_element is not None and element.get('forwarder')\
!= prev_element['forwarder']:
raise nfvo.VnffgdDuplicateForwarderException(
forwarder=element.get('forwarder')
)
elif prev_element is not None and element.get(
'capability') == prev_element['capability']:
raise nfvo.VnffgdDuplicateCPException(
cp=element.get('capability')
)
else:
known_forwarders.add(element.get('forwarder'))
prev_element = element
return super(NfvoPlugin, self).create_vnffgd(context, vnffgd)
@log.log
def create_vnffg(self, context, vnffg):
vnffg_dict = super(NfvoPlugin, self)._create_vnffg_pre(context, vnffg)
nfp = super(NfvoPlugin, self).get_nfp(context,
vnffg_dict['forwarding_paths'])
sfc = super(NfvoPlugin, self).get_sfc(context, nfp['chain_id'])
match = super(NfvoPlugin, self).get_classifier(context,
nfp['classifier_id'],
fields='match')
# grab the first VNF to check it's VIM type
# we have already checked that all VNFs are in the same VIM
vim_auth = self._get_vim_from_vnf(context,
vnffg_dict['vnf_mapping'].values()[0]
)
# TODO(trozet): figure out what auth info we actually need to pass
# to the driver. Is it a session, or is full vim obj good enough?
driver_type = vim_auth['type']
try:
fc_id = self._vim_drivers.invoke(driver_type,
'create_flow_classifier',
fc=match, auth_attr=vim_auth,
symmetrical=sfc['symmetrical'])
sfc_id = self._vim_drivers.invoke(driver_type, 'create_chain',
vnfs=sfc['chain'], fc_id=fc_id,
symmetrical=sfc['symmetrical'],
auth_attr=vim_auth)
except Exception:
with excutils.save_and_reraise_exception():
self.delete_vnffg(context, vnffg_id=vnffg_dict['id'])
super(NfvoPlugin, self)._create_vnffg_post(context, sfc_id, fc_id,
vnffg_dict)
super(NfvoPlugin, self)._create_vnffg_status(context, vnffg_dict)
return vnffg_dict
@log.log
def update_vnffg(self, context, vnffg_id, vnffg):
vnffg_dict = super(NfvoPlugin, self)._update_vnffg_pre(context,
vnffg_id)
new_vnffg = vnffg['vnffg']
LOG.debug(_('vnffg update: %s'), vnffg)
nfp = super(NfvoPlugin, self).get_nfp(context,
vnffg_dict['forwarding_paths'])
sfc = super(NfvoPlugin, self).get_sfc(context, nfp['chain_id'])
fc = super(NfvoPlugin, self).get_classifier(context,
nfp['classifier_id'])
template_db = self._get_resource(context, vnffg_db.VnffgTemplate,
vnffg_dict['vnffgd_id'])
vnf_members = self._get_vnffg_property(template_db,
'constituent_vnfs')
new_vnffg['vnf_mapping'] = super(NfvoPlugin, self)._get_vnf_mapping(
context, new_vnffg.get('vnf_mapping'), vnf_members)
template_id = vnffg_dict['vnffgd_id']
template_db = self._get_resource(context, vnffg_db.VnffgTemplate,
template_id)
# functional attributes that allow update are vnf_mapping,
# and symmetrical. Therefore we need to figure out the new chain if
# it was updated by new vnf_mapping. Symmetrical is handled by driver.
chain = super(NfvoPlugin, self)._create_port_chain(context,
new_vnffg[
'vnf_mapping'],
template_db,
nfp['name'])
LOG.debug(_('chain update: %s'), chain)
sfc['chain'] = chain
sfc['symmetrical'] = new_vnffg['symmetrical']
vim_auth = self._get_vim_from_vnf(context,
vnffg_dict['vnf_mapping'].values()[0]
)
driver_type = vim_auth['type']
try:
# we don't support updating the match criteria in first iteration
# so this is essentially a noop. Good to keep for future use
# though.
self._vim_drivers.invoke(driver_type, 'update_flow_classifier',
fc_id=fc['instance_id'], fc=fc['match'],
auth_attr=vim_auth,
symmetrical=new_vnffg['symmetrical'])
self._vim_drivers.invoke(driver_type, 'update_chain',
vnfs=sfc['chain'],
fc_ids=[fc['instance_id']],
chain_id=sfc['instance_id'],
auth_attr=vim_auth,
symmetrical=new_vnffg['symmetrical'])
except Exception:
with excutils.save_and_reraise_exception():
vnffg_dict['status'] = constants.ERROR
super(NfvoPlugin, self)._update_vnffg_post(context, vnffg_id,
constants.ERROR)
super(NfvoPlugin, self)._update_vnffg_post(context, vnffg_id,
constants.ACTIVE, new_vnffg)
# update chain
super(NfvoPlugin, self)._update_sfc_post(context, sfc['id'],
constants.ACTIVE, sfc)
# update classifier - this is just updating status until functional
# updates are supported to classifier
super(NfvoPlugin, self)._update_classifier_post(context, fc['id'],
constants.ACTIVE)
return vnffg_dict
@log.log
def delete_vnffg(self, context, vnffg_id):
vnffg_dict = super(NfvoPlugin, self)._delete_vnffg_pre(context,
vnffg_id)
nfp = super(NfvoPlugin, self).get_nfp(context,
vnffg_dict['forwarding_paths'])
sfc = super(NfvoPlugin, self).get_sfc(context, nfp['chain_id'])
fc = super(NfvoPlugin, self).get_classifier(context,
nfp['classifier_id'])
vim_auth = self._get_vim_from_vnf(context,
vnffg_dict['vnf_mapping'].values()[0]
)
driver_type = vim_auth['type']
try:
if sfc['instance_id'] is not None:
self._vim_drivers.invoke(driver_type, 'delete_chain',
chain_id=sfc['instance_id'],
auth_attr=vim_auth)
if fc['instance_id'] is not None:
self._vim_drivers.invoke(driver_type,
'delete_flow_classifier',
fc_id=fc['instance_id'],
auth_attr=vim_auth)
except Exception:
with excutils.save_and_reraise_exception():
vnffg_dict['status'] = constants.ERROR
super(NfvoPlugin, self)._delete_vnffg_post(context, vnffg_id,
True)
super(NfvoPlugin, self)._delete_vnffg_post(context, vnffg_id, False)
return vnffg_dict
def _get_vim_from_vnf(self, context, vnf_id):
"""Figures out VIM based on a VNF
:param context: SQL Session Context
:param vnf_id: VNF ID
:return: VIM or VIM properties if fields are provided
"""
vnfm_plugin = manager.TackerManager.get_service_plugins()['VNFM']
vim_id = vnfm_plugin.get_vnf(context, vnf_id, fields=['vim_id'])
vim_obj = self.get_vim(context, vim_id['vim_id'])
if vim_obj is None:
raise nfvo.VimFromVnfNotFoundException(vnf_id=vnf_id)
return vim_obj
def _vim_resource_name_to_id(self, context, resource, name, vnf_id):
"""Converts a VIM resource name to its ID
:param resource: resource type to find (network, subnet, etc)
:param name: name of the resource to find its ID
:param vnf_id: A VNF instance ID that is part of the chain to which
the classifier will apply to
:return: ID of the resource name
"""
vim_auth = self._get_vim_from_vnf(context, vnf_id)
driver_type = vim_auth['type']
return self._vim_drivers.invoke(driver_type,
'get_vim_resource_id',
vim_auth=vim_auth,
resource_type=resource,
resource_name=name)

View File

@ -31,6 +31,10 @@ ipparams = _get_template('vnf_cirros_param_values_ipaddr.yaml')
vnfd_userdata_template = _get_template('vnf_cirros_template_user_data.yaml') vnfd_userdata_template = _get_template('vnf_cirros_template_user_data.yaml')
userdata_params = _get_template('vnf_cirros_param_values_user_data.yaml') userdata_params = _get_template('vnf_cirros_param_values_user_data.yaml')
config_data = _get_template('config_data.yaml') config_data = _get_template('config_data.yaml')
vnffgd_template = yaml.load(_get_template('vnffgd_template.yaml'))
vnffgd_tosca_template = yaml.load(_get_template('tosca_vnffgd_template.yaml'))
vnffgd_invalid_tosca_template = yaml.load(_get_template(
'tosca_invalid_vnffgd_template.yaml'))
def get_dummy_vnfd_obj(): def get_dummy_vnfd_obj():
@ -165,3 +169,31 @@ def get_vim_auth_obj():
'auth_url': 'http://localhost:5000/v3', 'auth_url': 'http://localhost:5000/v3',
'user_domain_name': 'default', 'user_domain_name': 'default',
'project_domain_name': 'default'} 'project_domain_name': 'default'}
def get_dummy_vnffgd_obj():
return {u'vnffgd': {'name': 'dummy_vnfd',
'tenant_id': u'ad7ebc56538745a08ef7c5e97f8bd437',
u'template': {u'vnffgd': vnffgd_tosca_template},
'description': 'dummy_vnfd_description'}}
def get_dummy_vnffg_obj():
return {'vnffg': {'description': 'dummy_vnf_description',
'vnffgd_id': u'eb094833-995e-49f0-a047-dfb56aaf7c4e',
'tenant_id': u'ad7ebc56538745a08ef7c5e97f8bd437',
'name': 'dummy_vnffg',
'vnf_mapping': {},
'symmetrical': False}}
def get_dummy_vnffg_obj_vnf_mapping():
return {'vnffg': {'description': 'dummy_vnf_description',
'vnffgd_id': u'eb094833-995e-49f0-a047-dfb56aaf7c4e',
'tenant_id': u'ad7ebc56538745a08ef7c5e97f8bd437',
'name': 'dummy_vnffg',
'vnf_mapping': {
'VNF1': '91e32c20-6d1f-47a4-9ba7-08f5e5effe07',
'VNF3': '7168062e-9fa1-4203-8cb7-f5c99ff3ee1b'
},
'symmetrical': False}}

View File

@ -0,0 +1,41 @@
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
description: example template
topology_template:
description: Example VNFFG template
node_templates:
Forwarding_path1:
type: tosca.nodes.nfv.FP.Tacker
description: creates path (CP11->CP12->CP32)
properties:
id: 51
policy:
type: ACL
criteria:
- blah: tenant1_net
- destination_port_range: 80-1024
- ip_proto: 6
- ip_dst_prefix: 192.168.1.2/24
path:
- forwarder: VNF1
capability: CP11
- forwarder: VNF1
capability: CP12
- forwarder: VNF3
capability: CP32
groups:
VNFFG1:
type: tosca.groups.nfv.VNFFG
description: HTTP to Corporate Net
properties:
vendor: tacker
version: 1.0
number_of_endpoints: 5
dependent_virtual_link: [VL1,VL2,VL3]
connection_point: [CP11,CP12,CP32]
constituent_vnfs: [VNF1,VNF3]
members: [Forwarding_path1]

View File

@ -0,0 +1,41 @@
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
description: example template
topology_template:
description: Example VNFFG template
node_templates:
Forwarding_path1:
type: tosca.nodes.nfv.FP.Tacker
description: creates path (CP11->CP12->CP32)
properties:
id: 51
policy:
type: ACL
criteria:
- network_name: tenant1_net
- destination_port_range: 80-1024
- ip_proto: 6
- ip_dst_prefix: 192.168.1.2/24
path:
- forwarder: VNF1
capability: CP11
- forwarder: VNF1
capability: CP12
- forwarder: VNF3
capability: CP32
groups:
VNFFG1:
type: tosca.groups.nfv.VNFFG
description: HTTP to Corporate Net
properties:
vendor: tacker
version: 1.0
number_of_endpoints: 5
dependent_virtual_link: [VL1,VL2,VL3]
connection_point: [CP11,CP12,CP32]
constituent_vnfs: [VNF1,VNF3]
members: [Forwarding_path1]

View File

@ -0,0 +1,32 @@
Forwarding_path1:
type: tosca.nodes.nfv.FP
id: 51
description: creates path (CP11->CP12->CP32)
properties:
policy:
type: ACL
criteria:
- network_name: tenant1_net
- destination_port_range: 80-1024
- ip_proto: 6
- ip_dst_prefix: 192.168.1.2/24
requirements:
- forwarder: VNF1
capability: CP11
- forwarder: VNF1
capability: CP12
- forwarder: VNF3
capability: CP32
groups:
VNFFG1:
type: tosca.groups.nfv.VNFFG
description: HTTP to Corporate Net
properties:
vendor: tacker
version: 1.0
number_of_endpoints: 5
dependent_virtual_link: [VL1,VL2,VL3]
connection_point: [CP11,CP12,CP32]
constituent_vnfs: [VNF1,VNF3]
members: [Forwarding_path1]

View File

@ -13,26 +13,120 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import mock
import uuid import uuid
import mock
from mock import patch
from tacker import context from tacker import context
from tacker.db.common_services import common_services_db from tacker.db.common_services import common_services_db
from tacker.db.nfvo import nfvo_db from tacker.db.nfvo import nfvo_db
from tacker.db.nfvo import vnffg_db
from tacker.extensions import nfvo
from tacker.manager import TackerManager
from tacker.nfvo import nfvo_plugin from tacker.nfvo import nfvo_plugin
from tacker.plugins.common import constants from tacker.plugins.common import constants
from tacker.tests.unit.db import base as db_base from tacker.tests.unit.db import base as db_base
from tacker.tests.unit.db import utils
SECRET_PASSWORD = '***' SECRET_PASSWORD = '***'
def dummy_get_vim(*args, **kwargs):
vim_auth = utils.get_vim_auth_obj()
vim_auth['type'] = 'openstack'
return vim_auth
class FakeDriverManager(mock.Mock): class FakeDriverManager(mock.Mock):
def invoke(self, *args, **kwargs): def invoke(self, *args, **kwargs):
if 'create' in args: if any(x in ['create', 'create_chain', 'create_flow_classifier'] for
x in args):
return str(uuid.uuid4()) return str(uuid.uuid4())
class FakeVNFMPlugin(mock.Mock):
def __init__(self):
super(FakeVNFMPlugin, self).__init__()
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.vnf3_vnf_id = '7168062e-9fa1-4203-8cb7-f5c99ff3ee1b'
self.vnf3_update_vnf_id = '10f66bc5-b2f1-45b7-a7cd-6dd6ad0017f5'
self.cp11_id = 'd18c8bae-898a-4932-bff8-d5eac981a9c9'
self.cp12_id = 'c8906342-3e30-4b2a-9401-a251a7a9b5dd'
self.cp32_id = '3d1bd2a2-bf0e-44d1-87af-a2c6b2cad3ed'
self.cp32_update_id = '064c0d99-5a61-4711-9597-2a44dc5da14b'
def get_vnfds(self, *args, **kwargs):
if {'name': ['VNF1']} in args:
return [{'id': self.vnf1_vnfd_id}]
elif {'name': ['VNF3']} in args:
return [{'id': self.vnf3_vnfd_id}]
else:
return None
def get_vnfs(self, *args, **kwargs):
if {'vnfd_id': [self.vnf1_vnfd_id]} in args:
return [{'id': self.vnf1_vnf_id}]
elif {'vnfd_id': [self.vnf3_vnfd_id]} in args:
return [{'id': self.vnf3_vnf_id}]
else:
return None
def get_vnf(self, *args, **kwargs):
if self.vnf1_vnf_id in args:
return self.get_dummy_vnf1()
elif self.vnf3_vnf_id in args:
return self.get_dummy_vnf3()
elif self.vnf3_update_vnf_id in args:
return self.get_dummy_vnf3_update()
def get_vnf_resources(self, *args, **kwargs):
if self.vnf1_vnf_id in args:
return self.get_dummy_vnf1_details()
elif self.vnf3_vnf_id in args:
return self.get_dummy_vnf3_details()
elif self.vnf3_update_vnf_id in args:
return self.get_dummy_vnf3_update_details()
def get_dummy_vnf1_details(self):
return [{'name': 'CP11', 'id': self.cp11_id},
{'name': 'CP12', 'id': self.cp12_id}]
def get_dummy_vnf3_details(self):
return [{'name': 'CP32', 'id': self.cp32_id}]
def get_dummy_vnf3_update_details(self):
return [{'name': 'CP32', 'id': self.cp32_update_id}]
def get_dummy_vnf1(self):
return {'description': 'dummy_vnf_description',
'vnfd_id': self.vnf1_vnfd_id,
'vim_id': u'6261579e-d6f3-49ad-8bc3-a9cb974778ff',
'tenant_id': u'ad7ebc56538745a08ef7c5e97f8bd437',
'name': 'dummy_vnf1',
'attributes': {}}
def get_dummy_vnf3(self):
return {'description': 'dummy_vnf_description',
'vnfd_id': self.vnf3_vnfd_id,
'vim_id': u'6261579e-d6f3-49ad-8bc3-a9cb974778ff',
'tenant_id': u'ad7ebc56538745a08ef7c5e97f8bd437',
'name': 'dummy_vnf2',
'attributes': {}}
def get_dummy_vnf3_update(self):
return {'description': 'dummy_vnf_description',
'vnfd_id': self.vnf3_vnfd_id,
'vim_id': u'6261579e-d6f3-49ad-8bc3-a9cb974778ff',
'tenant_id': u'ad7ebc56538745a08ef7c5e97f8bd437',
'name': 'dummy_vnf_update',
'attributes': {}}
class TestNfvoPlugin(db_base.SqlTestCase): class TestNfvoPlugin(db_base.SqlTestCase):
def setUp(self): def setUp(self):
super(TestNfvoPlugin, self).setUp() super(TestNfvoPlugin, self).setUp()
@ -40,6 +134,8 @@ class TestNfvoPlugin(db_base.SqlTestCase):
self.context = context.get_admin_context() self.context = context.get_admin_context()
self._mock_driver_manager() self._mock_driver_manager()
mock.patch('tacker.nfvo.nfvo_plugin.NfvoPlugin.__run__').start() mock.patch('tacker.nfvo.nfvo_plugin.NfvoPlugin.__run__').start()
mock.patch('tacker.nfvo.nfvo_plugin.NfvoPlugin._get_vim_from_vnf',
side_effect=dummy_get_vim).start()
self.nfvo_plugin = nfvo_plugin.NfvoPlugin() self.nfvo_plugin = nfvo_plugin.NfvoPlugin()
mock.patch('tacker.db.common_services.common_services_db.' mock.patch('tacker.db.common_services.common_services_db.'
'CommonServicesPluginDb.create_event' 'CommonServicesPluginDb.create_event'
@ -139,3 +235,189 @@ class TestNfvoPlugin(db_base.SqlTestCase):
self.context, evt_type=constants.RES_EVT_UPDATE, res_id=mock.ANY, self.context, evt_type=constants.RES_EVT_UPDATE, res_id=mock.ANY,
res_state=mock.ANY, res_type=constants.RES_TYPE_VIM, res_state=mock.ANY, res_type=constants.RES_TYPE_VIM,
tstamp=mock.ANY) tstamp=mock.ANY)
def _insert_dummy_vnffg_template(self):
session = self.context.session
vnffg_template = vnffg_db.VnffgTemplate(
id='eb094833-995e-49f0-a047-dfb56aaf7c4e',
tenant_id='ad7ebc56538745a08ef7c5e97f8bd437',
name='fake_template',
description='fake_template_description',
template={u'vnffgd': utils.vnffgd_tosca_template})
session.add(vnffg_template)
session.flush()
return vnffg_template
def _insert_dummy_vnffg(self):
session = self.context.session
vnffg = vnffg_db.Vnffg(
id='ffc1a59b-65bb-4874-94d3-84f639e63c74',
tenant_id='ad7ebc56538745a08ef7c5e97f8bd437',
name='dummy_vnffg',
description="fake vnffg",
vnffgd_id='eb094833-995e-49f0-a047-dfb56aaf7c4e',
status='ACTIVE',
vnf_mapping={'VNF1': '91e32c20-6d1f-47a4-9ba7-08f5e5effe07',
'VNF3': '7168062e-9fa1-4203-8cb7-f5c99ff3ee1b'})
session.add(vnffg)
nfp = vnffg_db.VnffgNfp(
id='768f76a7-9025-4acd-b51c-0da609759983',
tenant_id='ad7ebc56538745a08ef7c5e97f8bd437',
status="ACTIVE",
name='Forwarding_path1',
vnffg_id='ffc1a59b-65bb-4874-94d3-84f639e63c74',
path_id=51,
symmetrical=False)
session.add(nfp)
sfc = vnffg_db.VnffgChain(
id='f28e33bc-1061-4762-b942-76060bbd59c4',
tenant_id='ad7ebc56538745a08ef7c5e97f8bd437',
symmetrical=False,
chain=[{'connection_points': [
'd18c8bae-898a-4932-bff8-d5eac981a9c9',
'c8906342-3e30-4b2a-9401-a251a7a9b5dd'],
'name': 'dummy_vnf1'},
{'connection_points': ['3d1bd2a2-bf0e-44d1-87af-a2c6b2cad3ed'],
'name': 'dummy_vnf2'}],
path_id=51,
status='ACTIVE',
nfp_id='768f76a7-9025-4acd-b51c-0da609759983',
instance_id='bcfb295e-578e-405b-a349-39f06b25598c')
session.add(sfc)
fc = vnffg_db.VnffgClassifier(
id='a85f21b5-f446-43f0-86f4-d83bdc5590ab',
tenant_id='ad7ebc56538745a08ef7c5e97f8bd437',
status='ACTIVE',
instance_id='3007dc2d-30dc-4651-9184-f1e6273cc0b6',
chain_id='f28e33bc-1061-4762-b942-76060bbd59c4',
nfp_id='768f76a7-9025-4acd-b51c-0da609759983')
session.add(fc)
match = vnffg_db.ACLMatchCriteria(
id='bdb0f2db-d4c2-42a2-a1df-426079ecc443',
vnffgc_id='a85f21b5-f446-43f0-86f4-d83bdc5590ab',
eth_src=None, eth_dst=None, eth_type=None, vlan_id=None,
vlan_pcp=None, mpls_label=None, mpls_tc=None, ip_dscp=None,
ip_ecn=None, ip_src_prefix=None, ip_dst_prefix='192.168.1.2/24',
source_port_min=None, source_port_max=None,
destination_port_min=80, destination_port_max=1024, ip_proto=6,
network_id=None, network_src_port_id=None,
network_dst_port_id=None, tenant_id=None, icmpv4_type=None,
icmpv4_code=None, arp_op=None, arp_spa=None, arp_tpa=None,
arp_sha=None, arp_tha=None, ipv6_src=None, ipv6_dst=None,
ipv6_flabel=None, icmpv6_type=None, icmpv6_code=None,
ipv6_nd_target=None, ipv6_nd_sll=None, ipv6_nd_tll=None)
session.add(match)
session.flush()
return vnffg
def test_validate_tosca(self):
template = utils.vnffgd_tosca_template
self.nfvo_plugin.validate_tosca(template)
def test_validate_tosca_missing_tosca_ver(self):
template = utils.vnffgd_template
self.assertRaises(nfvo.ToscaParserFailed,
self.nfvo_plugin.validate_tosca,
template)
def test_validate_tosca_invalid(self):
template = utils.vnffgd_invalid_tosca_template
self.assertRaises(nfvo.ToscaParserFailed,
self.nfvo_plugin.validate_tosca,
template)
def test_create_vnffgd(self):
vnffgd_obj = utils.get_dummy_vnffgd_obj()
result = self.nfvo_plugin.create_vnffgd(self.context, vnffgd_obj)
self.assertIsNotNone(result)
self.assertIn('id', result)
self.assertIn('template', result)
def test_create_vnffg_abstract_types(self):
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()
self._insert_dummy_vnffg_template()
vnffg_obj = utils.get_dummy_vnffg_obj()
result = self.nfvo_plugin.create_vnffg(self.context, vnffg_obj)
self.assertIsNotNone(result)
self.assertIn('id', result)
self.assertIn('status', result)
self.assertEqual('PENDING_CREATE', result['status'])
self._driver_manager.invoke.assert_called_with(mock.ANY, mock.ANY,
vnfs=mock.ANY,
fc_id=mock.ANY,
auth_attr=mock.ANY,
symmetrical=mock.ANY
)
def test_create_vnffg_vnf_mapping(self):
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()
self._insert_dummy_vnffg_template()
vnffg_obj = utils.get_dummy_vnffg_obj_vnf_mapping()
result = self.nfvo_plugin.create_vnffg(self.context, vnffg_obj)
self.assertIsNotNone(result)
self.assertIn('id', result)
self.assertIn('status', result)
self.assertEqual('PENDING_CREATE', result['status'])
self._driver_manager.invoke.assert_called_with(mock.ANY, mock.ANY,
vnfs=mock.ANY,
fc_id=mock.ANY,
auth_attr=mock.ANY,
symmetrical=mock.ANY
)
def test_update_vnffg_nonexistent_vnf(self):
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()
self._insert_dummy_vnffg_template()
vnffg = self._insert_dummy_vnffg()
updated_vnffg = utils.get_dummy_vnffg_obj_vnf_mapping()
updated_vnffg['vnffg']['symmetrical'] = True
updated_vnf_mapping = \
{'VNF1': '91e32c20-6d1f-47a4-9ba7-08f5e5effe07',
'VNF3': '5c7f5631-9e74-46e8-b3d2-397c0eda9d0b'}
updated_vnffg['vnffg']['vnf_mapping'] = updated_vnf_mapping
self.assertRaises(nfvo.VnffgInvalidMappingException,
self.nfvo_plugin.update_vnffg,
self.context, vnffg['id'], updated_vnffg)
def test_update_vnffg(self):
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()
self._insert_dummy_vnffg_template()
vnffg = self._insert_dummy_vnffg()
updated_vnffg = utils.get_dummy_vnffg_obj_vnf_mapping()
updated_vnffg['vnffg']['symmetrical'] = True
updated_vnf_mapping = \
{'VNF1': '91e32c20-6d1f-47a4-9ba7-08f5e5effe07',
'VNF3': '10f66bc5-b2f1-45b7-a7cd-6dd6ad0017f5'}
updated_vnffg['vnffg']['vnf_mapping'] = updated_vnf_mapping
self.nfvo_plugin.update_vnffg(self.context, vnffg['id'],
updated_vnffg)
self._driver_manager.invoke.assert_called_with(mock.ANY, mock.ANY,
vnfs=mock.ANY,
fc_ids=mock.ANY,
chain_id=mock.ANY,
auth_attr=mock.ANY,
symmetrical=True)
def test_delete_vnffg(self):
self._insert_dummy_vnffg_template()
vnffg = self._insert_dummy_vnffg()
self.nfvo_plugin.delete_vnffg(self.context, vnffg['id'])
self._driver_manager.invoke.assert_called_with(mock.ANY, mock.ANY,
fc_id=mock.ANY,
auth_attr=mock.ANY)

View File

@ -1,3 +1,157 @@
data_types:
tosca.nfv.datatypes.pathType:
properties:
forwarder:
type: string
required: true
capability:
type: string
required: true
tosca.nfv.datatypes.aclType:
properties:
eth_type:
type: string
required: false
eth_src:
type: string
required: false
eth_dst:
type: string
required: false
vlan_id:
type: integer
constraints:
- in_range: [ 1, 4094 ]
required: false
vlan_pcp:
type: integer
constraints:
- in_range: [ 0, 7 ]
required: false
mpls_label:
type: integer
constraints:
- in_range: [ 16, 1048575]
required: false
mpls_tc:
type: integer
constraints:
- in_range: [ 0, 7 ]
required: false
ip_dscp:
type: integer
constraints:
- in_range: [ 0, 63 ]
required: false
ip_ecn:
type: integer
constraints:
- in_range: [ 0, 3 ]
required: false
ip_src_prefix:
type: string
required: false
ip_dst_prefix:
type: string
required: false
ip_proto:
type: integer
constraints:
- in_range: [ 1, 254 ]
required: false
destination_port_range:
type: string
required: false
source_port_range:
type: string
required: false
network_src_port_id:
type: string
required: false
network_dst_port_id:
type: string
required: false
network_id:
type: string
required: false
network_name:
type: string
required: false
tenant_id:
type: string
required: false
icmpv4_type:
type: integer
constraints:
- in_range: [ 0, 254 ]
required: false
icmpv4_code:
type: integer
constraints:
- in_range: [ 0, 15 ]
required: false
arp_op:
type: integer
constraints:
- in_range: [ 1, 25 ]
required: false
arp_spa:
type: string
required: false
arp_tpa:
type: string
required: false
arp_sha:
type: string
required: false
arp_tha:
type: string
required: false
ipv6_src:
type: string
required: false
ipv6_dst:
type: string
required: false
ipv6_flabel:
type: integer
constraints:
- in_range: [ 0, 1048575]
required: false
icmpv6_type:
type: integer
constraints:
- in_range: [ 0, 255]
required: false
icmpv6_code:
type: integer
constraints:
- in_range: [ 0, 7]
required: false
ipv6_nd_target:
type: string
required: false
ipv6_nd_sll:
type: string
required: false
ipv6_nd_tll:
type: string
required: false
tosca.nfv.datatypes.policyType:
properties:
type:
type: string
required: false
constraints:
- valid_values: [ ACL ]
criteria:
type: list
required: true
entry_schema:
type: tosca.nfv.datatypes.aclType
node_types: node_types:
tosca.nodes.nfv.VDU.Tacker: tosca.nodes.nfv.VDU.Tacker:
derived_from: tosca.nodes.nfv.VDU derived_from: tosca.nodes.nfv.VDU
@ -75,3 +229,19 @@ node_types:
required: false required: false
constraints: constraints:
- valid_values: [ sriov, vnic ] - valid_values: [ sriov, vnic ]
tosca.nodes.nfv.FP.Tacker:
derived_from: tosca.nodes.Root
properties:
id:
type: integer
required: false
policy:
type: tosca.nfv.datatypes.policyType
required: true
description: policy to use to match traffic for this FP
path:
type: list
required: true
entry_schema:
type: tosca.nfv.datatypes.pathType