1418 lines
63 KiB
Python
1418 lines
63 KiB
Python
# 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.
|
|
|
|
from oslo_utils import uuidutils
|
|
import random
|
|
import sqlalchemy as sa
|
|
|
|
from oslo_log import log as logging
|
|
from six import iteritems
|
|
from sqlalchemy import orm
|
|
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
|
|
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.PENDING_DELETE, constants.ERROR, constants.DEAD)
|
|
_VALID_VNFFG_UPDATE_ATTRIBUTES = ('vnf_mapping',)
|
|
_VALID_SFC_UPDATE_ATTRIBUTES = ('chain', 'symmetrical')
|
|
_VALID_NFP_UPDATE_ATTRIBUTES = ('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)
|
|
|
|
# Vnffgd template source - onboarded
|
|
template_source = sa.Column(sa.String(255), server_default='onboarded')
|
|
|
|
|
|
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)
|
|
|
|
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"""
|
|
|
|
name = sa.Column(sa.String(255), nullable=False)
|
|
vnffg_id = sa.Column(types.Uuid, sa.ForeignKey('vnffgs.id'),
|
|
nullable=False)
|
|
|
|
# List of associated classifiers
|
|
classifiers = orm.relationship('VnffgClassifier', backref='nfp')
|
|
chain = orm.relationship('VnffgChain', backref='nfp',
|
|
uselist=False)
|
|
|
|
status = sa.Column(sa.String(255), nullable=False)
|
|
path_id = sa.Column(sa.String(255), nullable=True)
|
|
|
|
# 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=True)
|
|
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"""
|
|
|
|
name = sa.Column(sa.String(255), nullable=True)
|
|
|
|
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 = uuidutils.generate_uuid()
|
|
fc_instance = uuidutils.generate_uuid()
|
|
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)
|
|
template_source = template.get('template_source')
|
|
|
|
with context.session.begin(subtransactions=True):
|
|
template_id = uuidutils.generate_uuid()
|
|
template_db = VnffgTemplate(
|
|
id=template_id,
|
|
tenant_id=tenant_id,
|
|
name=template.get('name'),
|
|
description=template.get('description'),
|
|
template=template.get('template'),
|
|
template_source=template_source)
|
|
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):
|
|
if ('template_source' in filters) and \
|
|
(filters['template_source'][0] == 'all'):
|
|
filters.pop('template_source')
|
|
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 create_classifiers_map(self, classifier_ids, instance_ids):
|
|
return {classifier_id: instance_ids[i]
|
|
for i, classifier_id in enumerate(classifier_ids)}
|
|
|
|
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)
|
|
|
|
def _update_template_params(self, original, paramvalues, param_matched):
|
|
if 'get_input' not in str(original):
|
|
return
|
|
if isinstance(original, dict):
|
|
for key_, value in iteritems(original):
|
|
if isinstance(value, dict) and 'get_input' in value:
|
|
if value['get_input'] in paramvalues:
|
|
original[key_] = paramvalues[value['get_input']]
|
|
param_matched.setdefault(value['get_input'], 0)
|
|
param_matched[value['get_input']] += 1
|
|
else:
|
|
raise nfvo.VnffgTemplateParamParsingException(
|
|
get_input=value['get_input'])
|
|
else:
|
|
self._update_template_params(value,
|
|
paramvalues, param_matched)
|
|
elif isinstance(original, list):
|
|
for element in original:
|
|
self._update_template_params(element,
|
|
paramvalues, param_matched)
|
|
|
|
def _process_parameterized_template(self, dev_attrs, vnffgd_template):
|
|
param_vattrs_dict = dev_attrs.pop('param_values', None)
|
|
param_matched = {}
|
|
if isinstance(param_vattrs_dict, dict):
|
|
self._update_template_params(vnffgd_template,
|
|
param_vattrs_dict, param_matched)
|
|
else:
|
|
raise nfvo.VnffgParamValueFormatError(
|
|
param_value=param_vattrs_dict)
|
|
for param_key in param_vattrs_dict.keys():
|
|
if param_matched.get(param_key) is None:
|
|
LOG.warning("Param input %s not used.", param_key)
|
|
|
|
def _parametrize_topology_template(self, vnffg, template_db):
|
|
if vnffg.get('attributes') and \
|
|
vnffg['attributes'].get('param_values'):
|
|
vnffg_param = vnffg['attributes']
|
|
vnffgd_topology_template = \
|
|
template_db.template['vnffgd']['topology_template']
|
|
self._process_parameterized_template(vnffg_param,
|
|
vnffgd_topology_template)
|
|
template_db.template['vnffgd']['topology_template'] = \
|
|
vnffgd_topology_template
|
|
|
|
# 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 uuidutils.generate_uuid()
|
|
template_id = vnffg['vnffgd_id']
|
|
ns_id = vnffg.get('ns_id', None)
|
|
symmetrical_in_temp = self._get_symmetrical_template(context, vnffg)
|
|
symmetrical = symmetrical_in_temp if symmetrical_in_temp is not None \
|
|
else vnffg.get('symmetrical')
|
|
|
|
with context.session.begin(subtransactions=True):
|
|
template_db = self._get_resource(context, VnffgTemplate,
|
|
template_id)
|
|
LOG.debug('vnffg template %s', template_db)
|
|
|
|
self._parametrize_topology_template(vnffg, template_db)
|
|
|
|
vnf_members = self._get_vnffg_property(template_db.template,
|
|
'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)
|
|
LOG.debug('NFP: %s', nfp_dict)
|
|
path_id = nfp_dict['path_id']
|
|
try:
|
|
if path_id:
|
|
vnffgNfp_db = (self._model_query(context, VnffgNfp).
|
|
filter(VnffgNfp.path_id == path_id).one())
|
|
raise nfvo.NfpDuplicatePathID(path_id=path_id,
|
|
nfp_name=vnffgNfp_db.name,
|
|
vnffg_name=name)
|
|
except orm_exc.NoResultFound:
|
|
pass
|
|
|
|
vnffg_db = Vnffg(id=vnffg_id,
|
|
tenant_id=tenant_id,
|
|
name=name,
|
|
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)
|
|
|
|
nfp_id = uuidutils.generate_uuid()
|
|
sfc_id = uuidutils.generate_uuid()
|
|
|
|
classifiers = self._policy_to_acl_criteria(context, template_db,
|
|
nfp_dict['name'],
|
|
vnf_mapping)
|
|
LOG.debug('classifiers %s', classifiers)
|
|
|
|
classifier_ids = [uuidutils.generate_uuid() for i in classifiers]
|
|
|
|
nfp_db = VnffgNfp(id=nfp_id, vnffg_id=vnffg_id,
|
|
tenant_id=tenant_id,
|
|
name=nfp_dict['name'],
|
|
status=constants.PENDING_CREATE,
|
|
path_id=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=path_id)
|
|
|
|
context.session.add(sfc_db)
|
|
|
|
for i, classifier_id in enumerate(classifier_ids):
|
|
|
|
sfcc_db = VnffgClassifier(id=classifier_id,
|
|
name=classifiers[i]['name'],
|
|
tenant_id=tenant_id,
|
|
status=constants.PENDING_CREATE,
|
|
nfp_id=nfp_id,
|
|
chain_id=sfc_id)
|
|
context.session.add(sfcc_db)
|
|
|
|
match_db_table = ACLMatchCriteria(
|
|
id=uuidutils.generate_uuid(),
|
|
vnffgc_id=classifier_id,
|
|
tenant_id=tenant_id,
|
|
**classifiers[i]['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 = list(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'].get('id', None)
|
|
# 'path_id' will be updated when creating port chain is done
|
|
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],
|
|
'sfc_encap': element.get('sfc_encap', True)})
|
|
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][CP].append(vnf_cp)
|
|
return chain_list
|
|
|
|
@staticmethod
|
|
def _get_vnffg_property(template, vnffg_property):
|
|
template = template['vnffgd']['topology_template']
|
|
vnffg_name = list(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:
|
|
LOG.debug('NFP %(nfp)s, attr %(attr)s',
|
|
{'nfp': template['node_templates'][nfp],
|
|
'attr': attribute})
|
|
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.items():
|
|
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 _validate_vnfd_in_vnf_mapping(self, vnf_mapping, vnf_members):
|
|
"""Validate whether or not the vnf_mapping is valid for update.
|
|
|
|
In the update_vnnfg procedure we need to know whether or not the
|
|
the vnf_mapping is valid so we can use it to update the chain.
|
|
"""
|
|
if not vnf_mapping:
|
|
raise nfvo.VnfMappingNotFoundException()
|
|
else:
|
|
for vnfd, vnf in vnf_mapping.items():
|
|
if vnfd not in vnf_members:
|
|
raise nfvo.VnfMappingNotValidException(vnfd=vnfd)
|
|
|
|
def _combine_current_and_new_vnf_mapping(self, context,
|
|
new_mapping, old_mapping):
|
|
"""Create an updated vnf mapping.
|
|
|
|
In this function we create an updated vnf mapping which is
|
|
a mix of the vnf_mapping which already exists in database
|
|
and the new mapping that the user passes.
|
|
"""
|
|
updated_vnf_mapping = old_mapping.copy()
|
|
updated_vnf_mapping.update(new_mapping)
|
|
return updated_vnf_mapping
|
|
|
|
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 len(vnf_list) == 0:
|
|
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 _validate_criteria(self, criteria):
|
|
"""Validate whether or not the classifiers are unique.
|
|
|
|
We define a classifier as unique when at least one
|
|
key-value pair is different from another classifier.
|
|
"""
|
|
if not criteria:
|
|
raise nfvo.NfpPolicyCriteriaIndexError()
|
|
elif len(criteria) == 1:
|
|
pass
|
|
else:
|
|
for index, dict_one in enumerate(criteria):
|
|
if index != (len(criteria) - 1):
|
|
for dict_two in criteria[(index + 1):]:
|
|
if dict_one == dict_two:
|
|
raise nfvo. \
|
|
NfpDuplicatePolicyCriteria(first_dict=dict_one,
|
|
sec_dict=dict_two)
|
|
|
|
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]
|
|
|
|
if 'policy' in nfp['properties']:
|
|
policy = nfp['properties']['policy']
|
|
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")
|
|
validation_list = []
|
|
for item in policy['criteria']:
|
|
if item.get('name') is None:
|
|
LOG.warning('The unnamed classifier approach'
|
|
' will be deprecated in subsequent'
|
|
' releases')
|
|
validation_list.append(item)
|
|
else:
|
|
validation_list.append(item['classifier'])
|
|
|
|
self._validate_criteria(validation_list)
|
|
|
|
classifiers = []
|
|
for criteria in policy['criteria']:
|
|
match = dict()
|
|
if criteria.get('name') is None:
|
|
criteria_dict = criteria.copy()
|
|
else:
|
|
criteria_dict = criteria['classifier'].copy()
|
|
for key, val in criteria_dict.items():
|
|
if key in MATCH_CRITERIA:
|
|
match.update(self._convert_criteria(context, key, val,
|
|
vnf_mapping))
|
|
else:
|
|
raise nfvo.NfpPolicyCriteriaError(error="Unsupported "
|
|
"criteria: "
|
|
"{}".format(key))
|
|
classifiers.append({'name': criteria.get('name'),
|
|
'match': match})
|
|
return classifiers
|
|
else:
|
|
return []
|
|
|
|
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 = list(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 uuidutils.generate_uuid()
|
|
|
|
# called internally, not by REST API
|
|
# instance_id = None means error on creation
|
|
def _create_vnffg_post(self, context, sfc_instance_id, path_id,
|
|
classifiers_map, vnffg_dict):
|
|
LOG.debug('SFC created instance is %s', sfc_instance_id)
|
|
LOG.debug('Flow Classifiers created instances are %s',
|
|
[classifiers_map[item] for item in classifiers_map])
|
|
nfp_dict = self.get_nfp(context, vnffg_dict['forwarding_paths'])
|
|
sfc_id = nfp_dict['chain_id']
|
|
with context.session.begin(subtransactions=True):
|
|
nfp_query = (self._model_query(context, VnffgNfp).
|
|
filter(VnffgNfp.id == nfp_dict['id']).
|
|
filter(VnffgNfp.status == constants.PENDING_CREATE).
|
|
one())
|
|
nfp_query.update({'path_id': path_id})
|
|
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, 'path_id': path_id})
|
|
if sfc_instance_id is None:
|
|
query.update({'status': constants.ERROR})
|
|
else:
|
|
query.update({'status': constants.ACTIVE})
|
|
for classifier_id, fc_instance_id in classifiers_map.items():
|
|
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'])
|
|
|
|
if chain['status'] == constants.ERROR:
|
|
self._update_all_status(context, vnffg['id'], nfp['id'],
|
|
constants.ERROR)
|
|
|
|
elif chain['status'] == constants.ACTIVE:
|
|
classifiers_active_state = True
|
|
for classifier in [self.get_classifier(context, classifier_id)
|
|
for classifier_id in nfp['classifier_ids']]:
|
|
|
|
if classifier['status'] == constants.ACTIVE:
|
|
continue
|
|
elif classifier['status'] == constants.ERROR:
|
|
classifiers_active_state = False
|
|
break
|
|
|
|
if classifiers_active_state:
|
|
self._update_all_status(context, vnffg['id'], nfp['id'],
|
|
constants.ACTIVE)
|
|
else:
|
|
self._update_all_status(context, vnffg['id'], nfp['id'],
|
|
constants.ERROR)
|
|
|
|
def _update_all_status(self, context, vnffg_id, nfp_id, status):
|
|
nfp_dict = self.get_nfp(context, nfp_id)
|
|
sfc_id = nfp_dict['chain_id']
|
|
|
|
with context.session.begin(subtransactions=True):
|
|
for classifier_id in nfp_dict['classifier_ids']:
|
|
query_cls = (self._model_query(context, VnffgClassifier).
|
|
filter(VnffgClassifier.id == classifier_id))
|
|
query_cls.update({'status': status})
|
|
query_chain = (self._model_query(context, VnffgChain).
|
|
filter(VnffgChain.id == sfc_id))
|
|
query_chain.update({'status': status})
|
|
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', 'ns_id',
|
|
'vnf_mapping', 'status', 'vnffgd_id', 'attributes')
|
|
res.update((key, vnffg_db[key]) for key in key_list)
|
|
return self._fields(res, fields)
|
|
|
|
def _update_vnffg_status_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'])
|
|
classifiers = [self.get_classifier(context, classifier_id) for
|
|
classifier_id in nfp['classifier_ids']]
|
|
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)
|
|
for classifier in classifiers:
|
|
self._get_classifier_db(context, classifier['id'],
|
|
_ACTIVE_UPDATE,
|
|
constants.PENDING_UPDATE)
|
|
return self._make_vnffg_dict(vnffg_db)
|
|
|
|
def _update_vnffg_pre(self, context, vnffg, vnffg_id, vnffg_old):
|
|
vnffg = vnffg['vnffg']
|
|
del vnffg['symmetrical']
|
|
if vnffg.get('vnffgd_template') is None:
|
|
try:
|
|
return self._update_vnffg_without_template(context, vnffg_old,
|
|
vnffg, vnffg_id)
|
|
except (nfvo.VnfMappingNotFoundException,
|
|
nfvo.VnfMappingNotValidException) as e:
|
|
raise e
|
|
|
|
with context.session.begin(subtransactions=True):
|
|
# Templates
|
|
template_db_new = self._get_resource(context, VnffgTemplate,
|
|
vnffg['vnffgd_id'])
|
|
|
|
LOG.debug('vnffg new template %s', template_db_new)
|
|
|
|
template_db_old = self._get_resource(context, VnffgTemplate,
|
|
vnffg_old['vnffgd_id'])
|
|
|
|
LOG.debug('vnffg old template %s', template_db_old)
|
|
|
|
self._parametrize_topology_template(vnffg, template_db_new)
|
|
|
|
# VNF-Members
|
|
vnf_members_new = self._get_vnffg_property(
|
|
template_db_new.template, 'constituent_vnfs')
|
|
|
|
LOG.debug('New Constituent VNFs: %s', vnf_members_new)
|
|
|
|
vnf_members_old = self._get_vnffg_property(
|
|
template_db_old.template, 'constituent_vnfs')
|
|
|
|
LOG.debug('Old Constituent VNFs: %s', vnf_members_old)
|
|
|
|
if set(vnf_members_new) == set(vnf_members_old):
|
|
if vnffg.get('vnf_mapping') is None:
|
|
final_vnf_mapping = vnffg_old['vnf_mapping']
|
|
else:
|
|
try:
|
|
self._validate_vnfd_in_vnf_mapping(
|
|
vnffg['vnf_mapping'], vnf_members_new)
|
|
except (nfvo.VnfMappingNotFoundException,
|
|
nfvo.VnfMappingNotValidException) as e:
|
|
raise e
|
|
updated_vnf_mapping = \
|
|
self._combine_current_and_new_vnf_mapping(
|
|
context, vnffg['vnf_mapping'],
|
|
vnffg_old['vnf_mapping'])
|
|
|
|
final_vnf_mapping = self._get_vnf_mapping(
|
|
context, updated_vnf_mapping, vnf_members_new)
|
|
else:
|
|
final_vnf_mapping = self._get_vnf_mapping(context, vnffg.get(
|
|
'vnf_mapping'),
|
|
vnf_members_new)
|
|
|
|
LOG.debug('VNF Mapping: %s', final_vnf_mapping)
|
|
# Update the vnffg with the new template.
|
|
query_vnffg = (self._model_query(context, Vnffg).
|
|
filter(Vnffg.id == vnffg_old['id']).
|
|
filter(Vnffg.status == constants.PENDING_UPDATE))
|
|
query_vnffg.update({'vnf_mapping': final_vnf_mapping,
|
|
'vnffgd_id': vnffg['vnffgd_id'],
|
|
'description': template_db_new.description,
|
|
'attributes': template_db_new.get('template')})
|
|
|
|
# Delete the old_vnffgd_template if template_source is 'inline'
|
|
if template_db_old.template_source == 'inline':
|
|
self.delete_vnffgd(context, vnffg_old['vnffgd_id'])
|
|
|
|
# update NFP
|
|
nfp_dict_old = self.get_nfp(context, vnffg_old['forwarding_paths'])
|
|
|
|
LOG.debug('Current NFP: %s', nfp_dict_old)
|
|
|
|
nfp_dict_new = self._update_nfp_pre(template_db_new, nfp_dict_old)
|
|
|
|
LOG.debug('New NFP: %s', nfp_dict_new)
|
|
|
|
query_nfp = (self._model_query(context, VnffgNfp).
|
|
filter(VnffgNfp.id == nfp_dict_old['id']).
|
|
filter(VnffgNfp.status == constants.PENDING_UPDATE))
|
|
query_nfp.update(nfp_dict_new)
|
|
|
|
# update chain
|
|
chain_old = self.get_sfc(context, nfp_dict_old['chain_id'])
|
|
LOG.debug('Current chain: %s', chain_old)
|
|
chain_new = self._create_port_chain(context, final_vnf_mapping,
|
|
template_db_new,
|
|
nfp_dict_new['name'])
|
|
LOG.debug('New chain: %s', chain_new)
|
|
# to check if it is updated
|
|
update_chain = self._set_updated_chain(chain_old['chain'],
|
|
chain_new)
|
|
|
|
if update_chain:
|
|
query_chain = (self._model_query(context, VnffgChain).
|
|
filter(VnffgChain.id == chain_old['id']).
|
|
filter(VnffgChain.status == constants.
|
|
PENDING_UPDATE))
|
|
query_chain.update({'chain': chain_new,
|
|
'path_id': nfp_dict_new['path_id']})
|
|
|
|
# update classifiers
|
|
classifiers_old = []
|
|
for classifier_id in nfp_dict_old['classifier_ids']:
|
|
classifiers_old.append(self.
|
|
get_classifier(context,
|
|
classifier_id,
|
|
fields=['name', 'match', 'id']))
|
|
classifiers_new = self._policy_to_acl_criteria(context,
|
|
template_db_new,
|
|
nfp_dict_new['name'],
|
|
final_vnf_mapping)
|
|
try:
|
|
classifiers_update, classifiers_delete = \
|
|
self._find_classifiers_to_update(classifiers_old,
|
|
classifiers_new)
|
|
except nfvo.UpdateVnffgException as e:
|
|
raise e
|
|
for clsfr in classifiers_update:
|
|
if clsfr.get('id'):
|
|
for item in MATCH_DB_KEY_LIST:
|
|
if clsfr['match'].get(item) is None:
|
|
clsfr['match'][item] = None
|
|
query_match = (self._model_query(context,
|
|
ACLMatchCriteria).
|
|
filter(ACLMatchCriteria.vnffgc_id == clsfr['id']))
|
|
query_match.update(clsfr['match'])
|
|
else:
|
|
classifier_id = uuidutils.generate_uuid()
|
|
sfcc_db = VnffgClassifier(id=classifier_id,
|
|
name=clsfr['name'],
|
|
tenant_id=vnffg_old['tenant_id'],
|
|
status=constants.PENDING_CREATE,
|
|
nfp_id=nfp_dict_old['id'],
|
|
chain_id=chain_old['id'])
|
|
context.session.add(sfcc_db)
|
|
|
|
match_db = ACLMatchCriteria(
|
|
id=uuidutils.generate_uuid(),
|
|
vnffgc_id=classifier_id,
|
|
**clsfr['match'])
|
|
context.session.add(match_db)
|
|
for clsfr in classifiers_delete:
|
|
query_clsfr = (self._model_query(context, VnffgClassifier).
|
|
filter(VnffgClassifier.id == clsfr['id']).
|
|
filter(VnffgClassifier.status == constants.
|
|
PENDING_UPDATE))
|
|
query_clsfr.update({'status': constants.PENDING_DELETE})
|
|
|
|
return self.get_vnffg(context, vnffg_id)
|
|
|
|
def _find_classifiers_to_update(self, current_classifiers,
|
|
new_classifiers):
|
|
update_classifiers = []
|
|
delete_classifiers = []
|
|
names_list = []
|
|
for new_clsfr in new_classifiers:
|
|
found_name = False
|
|
if new_clsfr['name'] is None:
|
|
LOG.error('VNFFG update requires named classifiers')
|
|
raise nfvo.UpdateVnffgException(
|
|
message="Failed to update VNFFG")
|
|
for cur_clsfr in current_classifiers:
|
|
if cur_clsfr['name'] == new_clsfr['name']:
|
|
new_clsfr['id'] = cur_clsfr['id']
|
|
names_list.append(new_clsfr['name'])
|
|
update_classifiers.append(new_clsfr)
|
|
found_name = True
|
|
break
|
|
if not found_name:
|
|
names_list.append(new_clsfr['name'])
|
|
update_classifiers.append(new_clsfr)
|
|
for cur_clsfr in current_classifiers:
|
|
if cur_clsfr['name'] not in names_list:
|
|
delete_classifiers.append(cur_clsfr)
|
|
return update_classifiers, delete_classifiers
|
|
|
|
def _set_updated_chain(self, current_chain, new_chain):
|
|
if len(current_chain) != len(new_chain):
|
|
return True
|
|
else:
|
|
for i, item in enumerate(current_chain):
|
|
cp_vnf = new_chain[i]
|
|
if (cp_vnf['name'] == item['name'] and
|
|
cp_vnf['connection_points'] == item[
|
|
'connection_points']):
|
|
continue
|
|
else:
|
|
return True
|
|
return False
|
|
|
|
def _update_vnffg_without_template(self, context, old_vnffg, new_vnffg,
|
|
vnffg_id):
|
|
|
|
template_db = self._get_resource(context, VnffgTemplate,
|
|
old_vnffg['vnffgd_id'])
|
|
vnfd_members = self._get_vnffg_property(template_db.template,
|
|
'constituent_vnfs')
|
|
nfp = self.get_nfp(context, old_vnffg['forwarding_paths'])
|
|
|
|
chain_dict = self.get_sfc(context, nfp['chain_id'])
|
|
try:
|
|
self._validate_vnfd_in_vnf_mapping(new_vnffg.get('vnf_mapping'),
|
|
vnfd_members)
|
|
except (nfvo.VnfMappingNotFoundException,
|
|
nfvo.VnfMappingNotValidException) as e:
|
|
raise e
|
|
|
|
combined_vnf_mapping = self._combine_current_and_new_vnf_mapping(
|
|
context, new_vnffg['vnf_mapping'], old_vnffg['vnf_mapping'])
|
|
|
|
new_vnffg['vnf_mapping'] = self._get_vnf_mapping(context,
|
|
combined_vnf_mapping,
|
|
vnfd_members)
|
|
new_chain = self._create_port_chain(context,
|
|
new_vnffg['vnf_mapping'],
|
|
template_db,
|
|
nfp['name'])
|
|
|
|
LOG.debug('chain update: %s', new_chain)
|
|
|
|
query_vnffg = (self._model_query(context, Vnffg).
|
|
filter(Vnffg.id == old_vnffg['id']).
|
|
filter(Vnffg.status == constants.PENDING_UPDATE))
|
|
query_vnffg.update({'vnf_mapping': new_vnffg['vnf_mapping']})
|
|
|
|
query_chain = (self._model_query(context, VnffgChain).
|
|
filter(VnffgChain.id == chain_dict['id']).
|
|
filter(VnffgChain.status == constants.
|
|
PENDING_UPDATE))
|
|
query_chain.update({'chain': new_chain})
|
|
|
|
return self.get_vnffg(context, vnffg_id)
|
|
|
|
def _update_nfp_pre(self, template_db, nfp_dict_old):
|
|
template_new = template_db.template['vnffgd']['topology_template']
|
|
nfp_dict_new = dict()
|
|
vnffg_name = list(template_new['groups'].keys())[0]
|
|
nfp_dict_new['name'] = template_new['groups'][vnffg_name]['members'][0]
|
|
nfp_dict_new['path_id'] = template_new['node_templates'][nfp_dict_new[
|
|
'name']]['properties'].get('id')
|
|
|
|
if not nfp_dict_new['path_id']:
|
|
nfp_dict_new['path_id'] = nfp_dict_old['path_id']
|
|
return nfp_dict_new
|
|
|
|
def _update_vnffg_post(self, context, n_sfc_chain_id,
|
|
classifiers_map, vnffg_dict):
|
|
"""Updates the status and the n-sfc instance_ids in the db
|
|
|
|
:param context: SQL Session Context
|
|
:param n_sfc_chain_id: Id of port-chain in n-sfc side
|
|
:param classifiers_map: classifier and instance Ids map
|
|
:param vnffg_dict: vnffg dictionary
|
|
:return: None
|
|
"""
|
|
nfp_dict = self.get_nfp(context, vnffg_dict['forwarding_paths'])
|
|
sfc_id = nfp_dict['chain_id']
|
|
with context.session.begin(subtransactions=True):
|
|
query_chain = (self._model_query(context, VnffgChain).
|
|
filter(VnffgChain.id == sfc_id).
|
|
filter(VnffgChain.status == constants.PENDING_UPDATE).one())
|
|
if n_sfc_chain_id is None:
|
|
query_chain.update({'status': constants.ERROR})
|
|
else:
|
|
query_chain.update({'status': constants.ACTIVE})
|
|
for clsfr_id in nfp_dict['classifier_ids']:
|
|
query_clsfr = (self._model_query(context, VnffgClassifier).
|
|
filter(VnffgClassifier.id == clsfr_id))
|
|
if classifiers_map.get(clsfr_id):
|
|
query_clsfr.update({
|
|
'instance_id': classifiers_map[clsfr_id]})
|
|
if classifiers_map[clsfr_id]:
|
|
query_clsfr.update({'status': constants.ACTIVE})
|
|
else:
|
|
query_clsfr.update({'status': constants.ERROR})
|
|
else:
|
|
# Deletion of unused Match criterias which are
|
|
# not longer required due to the update classifier
|
|
# procedure.
|
|
query_match = (
|
|
self._model_query(context, ACLMatchCriteria).
|
|
filter(ACLMatchCriteria.vnffgc_id == clsfr_id))
|
|
query_match.delete()
|
|
query_clsfr.delete()
|
|
|
|
def _update_vnffg_status_post(self, context, vnffg, error=False,
|
|
db_state=constants.ERROR):
|
|
|
|
nfp = self.get_nfp(context, vnffg['forwarding_paths'])
|
|
chain = self.get_sfc(context, nfp['chain_id'])
|
|
|
|
if error:
|
|
if db_state == constants.ACTIVE:
|
|
self._update_all_status(context, vnffg['id'], nfp['id'],
|
|
constants.ACTIVE)
|
|
else:
|
|
self._update_all_status(context, vnffg['id'], nfp['id'],
|
|
constants.ERROR)
|
|
else:
|
|
if chain['status'] == constants.ERROR:
|
|
self._update_all_status(context, vnffg['id'], nfp['id'],
|
|
constants.ERROR)
|
|
elif chain['status'] == constants.ACTIVE:
|
|
classifiers_active_state = True
|
|
for classifier in [self.get_classifier(context, classifier_id)
|
|
for classifier_id in nfp['classifier_ids']]:
|
|
if classifier['status'] == constants.ACTIVE:
|
|
continue
|
|
elif classifier['status'] == constants.ERROR:
|
|
classifiers_active_state = False
|
|
break
|
|
if classifiers_active_state:
|
|
self._update_all_status(context, vnffg['id'], nfp['id'],
|
|
constants.ACTIVE)
|
|
else:
|
|
self._update_all_status(context, vnffg['id'], nfp['id'],
|
|
constants.ERROR)
|
|
|
|
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)
|
|
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)
|
|
for classifier_id in nfp['classifier_ids']]
|
|
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)
|
|
for classifier in classifiers:
|
|
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'])
|
|
classifiers = [self.get_classifier(context, classifier_id)
|
|
for classifier_id in nfp['classifier_ids']]
|
|
fc_queries = []
|
|
match_queries = []
|
|
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))
|
|
for classifier in classifiers:
|
|
fc_queries.append((
|
|
self._model_query(context, VnffgClassifier).
|
|
filter(VnffgClassifier.id == classifier['id']).
|
|
filter(VnffgClassifier.status ==
|
|
constants.PENDING_DELETE)))
|
|
match_queries.append((
|
|
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})
|
|
for fc_query in fc_queries:
|
|
fc_query.update({'status': constants.ERROR})
|
|
else:
|
|
for match_query in match_queries:
|
|
match_query.delete()
|
|
for fc_query in fc_queries:
|
|
fc_query.delete()
|
|
sfc_query.delete()
|
|
nfp_query.delete()
|
|
vnffg_query.delete()
|
|
|
|
vnffgd_id = vnffg.get('vnffgd_id')
|
|
template_db = self._get_resource(context, VnffgTemplate,
|
|
vnffgd_id)
|
|
|
|
if template_db.get('template_source') == 'inline':
|
|
self.delete_vnffgd(context, vnffgd_id)
|
|
|
|
def _get_symmetrical_template(self, context, vnffg):
|
|
fp_prop = self._get_fp_properties(context, vnffg)
|
|
return fp_prop.get('symmetrical', False)
|
|
|
|
def _get_correlation_template(self, context, vnffg):
|
|
fp_prop = self._get_fp_properties(context, vnffg)
|
|
return fp_prop.get('correlation', 'mpls')
|
|
|
|
def _get_fp_properties(self, context, vnffg):
|
|
vnffgd_topo = None
|
|
if vnffg.get('vnffgd_template'):
|
|
vnffgd_topo = vnffg['vnffgd_template']['topology_template']
|
|
elif vnffg.get('vnffgd_id'):
|
|
vnffgd_template = self.get_vnffgd(context, vnffg.get('vnffgd_id'))
|
|
vnffgd_topo = vnffgd_template['template']['vnffgd'][
|
|
'topology_template']
|
|
vnffg_name = list(vnffgd_topo['groups'].keys())[0]
|
|
nfp_name = vnffgd_topo['groups'][vnffg_name]['members'][0]
|
|
fp_prop = vnffgd_topo['node_templates'][nfp_name]['properties']
|
|
return fp_prop
|
|
|
|
def _make_template_dict(self, template, fields=None):
|
|
res = {}
|
|
key_list = ('id', 'tenant_id', 'name', 'description', 'template',
|
|
'template_source')
|
|
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', 'name', '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_ids': [classifier['id'] for classifier in
|
|
nfp_db.classifiers]}
|
|
key_list = ('name', 'id', 'tenant_id', 'symmetrical', 'status',
|
|
'path_id', 'vnffg_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
|