VNFFG: neutron networking-sfc driver and plugin fixes

Implements: blueprint tacker-vnffg

This patch implements the networking-sfc driver for VNFFG SFC driver
and necessary plugin side changes.

Change-Id: I531db5c65d7ed3b1adeeb30606e067aa600a957c
Signed-off-by: Tim Rozet <trozet@redhat.com>
Co-Authored-By: Sridhar Ramaswamy <srics.r@gmail.com>
This commit is contained in:
Stephen Wong 2016-07-26 15:16:48 -07:00
parent d3a2fbc861
commit 6f71a86167
14 changed files with 775 additions and 36 deletions

View File

@ -51,6 +51,7 @@ FIXED_RANGE=${FIXED_RANGE:-15.0.0.0/24}
enable_plugin tacker https://git.openstack.org/openstack/tacker master
enable_plugin networking-sfc git://git.openstack.org/openstack/networking-sfc master
enable_service n-novnc
enable_service n-cauth

View File

@ -0,0 +1,76 @@
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
description: Demo example
metadata:
template_name: sample-tosca-vnfd1
topology_template:
node_templates:
VDU1:
type: tosca.nodes.nfv.VDU.Tacker
capabilities:
nfv_compute:
properties:
num_cpus: 1
mem_size: 512 MB
disk_size: 1 GB
properties:
image: cirros-0.3.4-x86_64-uec
availability_zone: nova
mgmt_driver: noop
config: |
param0: key1
param1: key2
CP11:
type: tosca.nodes.nfv.CP.Tacker
properties:
management: true
order: 0
anti_spoofing_protection: false
requirements:
- virtualLink:
node: VL11
- virtualBinding:
node: VDU1
CP12:
type: tosca.nodes.nfv.CP.Tacker
properties:
order: 1
anti_spoofing_protection: false
requirements:
- virtualLink:
node: VL12
- virtualBinding:
node: VDU1
CP13:
type: tosca.nodes.nfv.CP.Tacker
properties:
order: 2
anti_spoofing_protection: false
requirements:
- virtualLink:
node: VL13
- virtualBinding:
node: VDU1
VL11:
type: tosca.nodes.nfv.VL
properties:
network_name: net_mgmt
vendor: Tacker
VL12:
type: tosca.nodes.nfv.VL
properties:
network_name: net0
vendor: Tacker
VL13:
type: tosca.nodes.nfv.VL
properties:
network_name: net1
vendor: Tacker

View File

@ -0,0 +1,76 @@
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
description: Demo example
metadata:
template_name: sample-tosca-vnfd1
topology_template:
node_templates:
VDU1:
type: tosca.nodes.nfv.VDU.Tacker
capabilities:
nfv_compute:
properties:
num_cpus: 1
mem_size: 512 MB
disk_size: 1 GB
properties:
image: cirros-0.3.4-x86_64-uec
availability_zone: nova
mgmt_driver: noop
config: |
param0: key1
param1: key2
CP21:
type: tosca.nodes.nfv.CP.Tacker
properties:
management: true
order: 0
anti_spoofing_protection: false
requirements:
- virtualLink:
node: VL21
- virtualBinding:
node: VDU1
CP22:
type: tosca.nodes.nfv.CP.Tacker
properties:
order: 1
anti_spoofing_protection: false
requirements:
- virtualLink:
node: VL22
- virtualBinding:
node: VDU1
CP23:
type: tosca.nodes.nfv.CP.Tacker
properties:
order: 2
anti_spoofing_protection: false
requirements:
- virtualLink:
node: VL23
- virtualBinding:
node: VDU1
VL21:
type: tosca.nodes.nfv.VL
properties:
network_name: net_mgmt
vendor: Tacker
VL22:
type: tosca.nodes.nfv.VL
properties:
network_name: net0
vendor: Tacker
VL23:
type: tosca.nodes.nfv.VL
properties:
network_name: net1
vendor: Tacker

View File

@ -0,0 +1,39 @@
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
description: Sample VNFFG template
topology_template:
description: Sample VNFFG template
node_templates:
Forwarding_path1:
type: tosca.nodes.nfv.FP.Tacker
description: creates path (CP12->CP22)
properties:
id: 51
policy:
type: ACL
criteria:
- network_src_port_id: 640dfd77-c92b-45a3-b8fc-22712de480e1
- destination_port_range: 80-1024
- ip_proto: 6
- ip_dst_prefix: 192.168.1.2/24
path:
- forwarder: VNFD1
capability: CP12
- forwarder: VNFD2
capability: CP22
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: [VL12,VL22]
connection_point: [CP12,CP22]
constituent_vnfs: [VNFD1,VNFD2]
members: [Forwarding_path1]

View File

@ -17,10 +17,12 @@
import os
import six
from keystoneauth1 import identity
from keystoneauth1 import session
from keystoneclient.auth.identity import v2
from keystoneclient.auth.identity import v3
from keystoneclient import exceptions
from keystoneclient import session
from neutronclient.common import exceptions as nc_exceptions
from neutronclient.v2_0 import client as neutron_client
from oslo_config import cfg
from oslo_log import log as logging
@ -30,6 +32,7 @@ from tacker.agent.linux import utils as linux_utils
from tacker.common import log
from tacker.extensions import nfvo
from tacker.nfvo.drivers.vim import abstract_vim_driver
from tacker.nfvo.drivers.vnffg import abstract_vnffg_driver
from tacker.vnfm import keystone
@ -56,12 +59,27 @@ _VALID_RESOURCE_TYPES = {'network': {'client': neutron_client.Client,
}
}
FC_MAP = {'name': 'name',
'description': 'description',
'eth_type': 'ethertype',
'ip_src_prefix': 'source_ip_prefix',
'ip_dst_prefix': 'destination_ip_prefix',
'source_port_min': 'source_port_range_min',
'source_port_max': 'source_port_range_max',
'destination_port_min': 'destination_port_range_min',
'destination_port_max': 'destination_port_range_max',
'network_src_port_id': 'logical_source_port',
'network_dst_port_id': 'logical_destination_port'}
CONNECTION_POINT = 'connection_points'
def config_opts():
return [('vim_keys', OPTS), ('vim_monitor', OPENSTACK_OPTS)]
class OpenStack_Driver(abstract_vim_driver.VimAbstractDriver):
class OpenStack_Driver(abstract_vim_driver.VimAbstractDriver,
abstract_vnffg_driver.VnffgAbstractDriver):
"""Driver for OpenStack VIM
OpenStack driver handles interactions with local as well as
@ -274,3 +292,263 @@ class OpenStack_Driver(abstract_vim_driver.VimAbstractDriver):
auth_plugin = self._get_auth_plugin(keystone_version, **auth_cred)
sess = session.Session(auth=auth_plugin)
return client_type(session=sess)
def create_flow_classifier(self, name, fc, symmetrical=False,
auth_attr=None):
def _translate_ip_protocol(ip_proto):
if ip_proto == '1':
return 'icmp'
elif ip_proto == '6':
return 'tcp'
elif ip_proto == '17':
return 'udp'
else:
return None
if not auth_attr:
LOG.warning(_("auth information required for n-sfc driver"))
return None
if symmetrical:
LOG.warning(_("n-sfc driver does not support symmetrical"))
raise NotImplementedError('symmetrical chain not supported')
LOG.debug(_('fc passed is %s'), fc)
sfc_classifier_params = {}
for field in fc:
if field in FC_MAP:
sfc_classifier_params[FC_MAP[field]] = fc[field]
elif field == 'ip_proto':
protocol = _translate_ip_protocol(str(fc[field]))
if not protocol:
raise ValueError('protocol %s not supported' % fc[field])
sfc_classifier_params['protocol'] = protocol
else:
LOG.warning(_("flow classifier %s not supported by "
"networking-sfc driver"), field)
LOG.debug(_('sfc_classifier_params is %s'), sfc_classifier_params)
if len(sfc_classifier_params) > 0:
neutronclient_ = NeutronClient(auth_attr)
fc_id = neutronclient_.flow_classifier_create(
sfc_classifier_params)
return fc_id
raise ValueError('empty match field for input flow classifier')
def create_chain(self, name, fc_id, vnfs, symmetrical=False,
auth_attr=None):
if not auth_attr:
LOG.warning(_("auth information required for n-sfc driver"))
return None
if symmetrical:
LOG.warning(_("n-sfc driver does not support symmetrical"))
raise NotImplementedError('symmetrical chain not supported')
neutronclient_ = NeutronClient(auth_attr)
port_pair_group_list = []
for vnf in vnfs:
# TODO(s3wong): once scaling is in place and VNFFG supports it
# that model needs to be implemented to concatenate all
# port-pairs into the port-pair-group
# port pair group could include port-pairs from different VNFs
port_pair_group = {}
port_pair_group['name'] = vnf['name'] + '-port-pair-group'
port_pair_group['description'] = \
'port pair group for %s' % vnf['name']
port_pair_group['port_pairs'] = []
if CONNECTION_POINT not in vnf:
LOG.warning(_("Chain creation failed due to missing "
"connection point info in VNF "
"%(vnfname)s"), {'vnfname': vnf['name']})
return None
cp_list = vnf[CONNECTION_POINT]
num_cps = len(cp_list)
if num_cps != 1 and num_cps != 2:
LOG.warning(_("Chain creation failed due to wrong number of "
"connection points: expected [1 | 2], got "
"%(cps)d"), {'cps': num_cps})
return None
port_pair = {}
port_pair['name'] = vnf['name'] + '-connection-points'
port_pair['description'] = 'port pair for %s' % vnf['name']
if num_cps == 1:
port_pair['ingress'] = cp_list[0]
port_pair['egress'] = cp_list[0]
else:
port_pair['ingress'] = cp_list[0]
port_pair['egress'] = cp_list[1]
port_pair_id = neutronclient_.port_pair_create(port_pair)
if not port_pair_id:
LOG.warning(_("Chain creation failed due to port pair creation"
" failed for vnf %(vnf)s"), {'vnf': vnf['name']})
return None
port_pair_group['port_pairs'].append(port_pair_id)
port_pair_group_id = \
neutronclient_.port_pair_group_create(port_pair_group)
if not port_pair_group_id:
LOG.warning(_("Chain creation failed due to port pair group "
"creation failed for vnf "
"%(vnf)s"), {'vnf': vnf['name']})
return None
port_pair_group_list.append(port_pair_group_id)
# TODO(s3wong): should the chain name be given as a parameter?
port_chain = {}
port_chain['name'] = name + '-port-chain'
port_chain['description'] = 'port-chain for Tacker VNFFG'
port_chain['port_pair_groups'] = port_pair_group_list
port_chain['flow_classifiers'] = [fc_id]
return neutronclient_.port_chain_create(port_chain)
def update_chain(self, chain_id, fc_ids, vnfs,
symmetrical=False, auth_attr=None):
# TODO(s3wong): chain can be updated either for
# the list of fc and/or list of port-pair-group
# since n-sfc driver does NOT track the ppg id
# it will look it up (or reconstruct) from
# networking-sfc DB --- but the caveat is that
# the VNF name MUST be unique
LOG.warning(_("n-sfc driver does not support sf chain update"))
raise NotImplementedError('sf chain update not supported')
def delete_chain(self, chain_id, auth_attr=None):
if not auth_attr:
LOG.warning(_("auth information required for n-sfc driver"))
return None
neutronclient_ = NeutronClient(auth_attr)
neutronclient_.port_chain_delete(chain_id)
def update_flow_classifier(self, fc_id, fc,
symmetrical=False, auth_attr=None):
if not auth_attr:
LOG.warning(_("auth information required for n-sfc driver"))
return None
if symmetrical:
LOG.warning(_("n-sfc driver does not support symmetrical"))
raise NotImplementedError('symmetrical chain not supported')
# for now, the only parameters allowed for flow-classifier-update
# is 'name' and/or 'description'
sfc_classifier_params = {}
sfc_classifier_params['name'] = fc['name']
sfc_classifier_params['description'] = fc['description']
LOG.debug(_('sfc_classifier_params is %s'), sfc_classifier_params)
neutronclient_ = NeutronClient(auth_attr)
return neutronclient_.flow_classifier_update(fc_id,
sfc_classifier_params)
def delete_flow_classifier(self, fc_id, auth_attr=None):
if not auth_attr:
LOG.warning(_("auth information required for n-sfc driver"))
raise EnvironmentError('auth attribute required for'
' networking-sfc driver')
neutronclient_ = NeutronClient(auth_attr)
neutronclient_.flow_classifier_delete(fc_id)
class NeutronClient(object):
"""Neutron Client class for networking-sfc driver"""
def __init__(self, auth_attr):
auth = identity.Password(**auth_attr)
sess = session.Session(auth=auth)
self.client = neutron_client.Client(session=sess)
def flow_classifier_create(self, fc_dict):
LOG.debug(_("fc_dict passed is {fc_dict}").format(fc_dict=fc_dict))
fc = self.client.create_flow_classifier({'flow_classifier': fc_dict})
if fc:
return fc['flow_classifier']['id']
else:
return None
def flow_classifier_update(self, fc_id, update_fc):
update_fc_dict = {'flow_classifier': update_fc}
return self.client.update_flow_classifier(fc_id, update_fc_dict)
def flow_classifier_delete(self, fc_id):
try:
self.client.delete_flow_classifier(fc_id)
except nc_exceptions.NotFound:
LOG.warning(_("fc %s not found") % fc_id)
raise ValueError('fc %s not found' % fc_id)
def port_pair_create(self, port_pair_dict):
try:
pp = self.client.create_port_pair({'port_pair': port_pair_dict})
except nc_exceptions.BadRequest as e:
LOG.error(_("create port pair returns %s") % e)
raise ValueError(str(e))
if pp and len(pp):
return pp['port_pair']['id']
else:
return None
def port_pair_delete(self, port_pair_id):
try:
self.client.delete_port_pair(port_pair_id)
except nc_exceptions.NotFound:
LOG.warning(_('port pair %s not found') % port_pair_id)
raise ValueError('port pair %s not found' % port_pair_id)
def port_pair_group_create(self, ppg_dict):
try:
ppg = self.client.create_port_pair_group(
{'port_pair_group': ppg_dict})
except nc_exceptions.BadRequest as e:
LOG.warning(_('create port pair group returns %s') % e)
raise ValueError(str(e))
if ppg and len(ppg):
return ppg['port_pair_group']['id']
else:
return None
def port_pair_group_delete(self, ppg_id):
try:
self.client.delete_port_pair_group(ppg_id)
except nc_exceptions.NotFound:
LOG.warning(_('port pair group %s not found') % ppg_id)
raise ValueError('port pair group %s not found' % ppg_id)
def port_chain_create(self, port_chain_dict):
try:
pc = self.client.create_port_chain(
{'port_chain': port_chain_dict})
except nc_exceptions.BadRequest as e:
LOG.warning(_('create port chain returns %s') % e)
raise ValueError(str(e))
if pc and len(pc):
return pc['port_chain']['id']
else:
return None
def port_chain_delete(self, port_chain_id):
try:
port_chain = self.client.show_port_chain(port_chain_id)
if port_chain:
self.client.delete_port_chain(port_chain_id)
ppg_list = port_chain['port_chain'].get('port_pair_groups')
if ppg_list and len(ppg_list):
for i in xrange(0, len(ppg_list)):
ppg = self.client.show_port_pair_group(ppg_list[i])
if ppg:
self.client.delete_port_pair_group(ppg_list[i])
port_pairs = ppg['port_pair_group']['port_pairs']
if port_pairs and len(port_pairs):
for j in xrange(0, len(port_pairs)):
pp_id = port_pairs[j]
self.client.delete_port_pair(pp_id)
except nc_exceptions.NotFound:
LOG.warning(_('port chain %s not found') % port_chain_id)
raise ValueError('port chain %s not found' % port_chain_id)

View File

@ -35,7 +35,8 @@ class VnffgAbstractDriver(extensions.PluginInterface):
pass
@abc.abstractmethod
def create_chain(self, fc_id, vnfs, symmetrical=False, auth_attr=None):
def create_chain(self, name, fc_id, vnfs, symmetrical=False,
auth_attr=None):
"""Create service function chain and returns an ID"""
pass
@ -51,7 +52,8 @@ class VnffgAbstractDriver(extensions.PluginInterface):
pass
@abc.abstractmethod
def create_flow_classifier(self, fc, symmetrical=False, auth_attr=None):
def create_flow_classifier(self, name, fc, symmetrical=False,
auth_attr=None):
"""Create flow classifier and returns an ID"""
pass

View File

@ -40,7 +40,7 @@ class VNFFGNoop(abstract_driver.SfcAbstractDriver):
return 'VNFFG Noop driver'
@log.log
def create_chain(self, fc_id, vnfs, auth_attr=None):
def create_chain(self, name, fc_id, vnfs, auth_attr=None):
instance_id = str(uuid.uuid4())
self._instances.add(instance_id)
return instance_id
@ -56,7 +56,7 @@ class VNFFGNoop(abstract_driver.SfcAbstractDriver):
self._instances.remove(chain_id)
@log.log
def create_flow_classifier(self, fc, auth_attr=None):
def create_flow_classifier(self, name, fc, auth_attr=None):
instance_id = str(uuid.uuid4())
self._instances.add(instance_id)
return instance_id

View File

@ -14,10 +14,12 @@
# License for the specific language governing permissions and limitations
# under the License.
import os
import threading
import time
import uuid
from cryptography import fernet
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import excutils
@ -39,6 +41,7 @@ from toscaparser import tosca_template
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
def config_opts():
@ -216,25 +219,28 @@ class NfvoPlugin(nfvo_db.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin):
sfc = super(NfvoPlugin, self).get_sfc(context, nfp['chain_id'])
match = super(NfvoPlugin, self).get_classifier(context,
nfp['classifier_id'],
fields='match')
fields='match')['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,
list(vnffg_dict[
'vnf_mapping'].values())[0]
)
vim_obj = self._get_vim_from_vnf(context,
list(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']
driver_type = vim_obj['type']
try:
fc_id = self._vim_drivers.invoke(driver_type,
'create_flow_classifier',
fc=match, auth_attr=vim_auth,
name=vnffg_dict['name'],
fc=match,
auth_attr=vim_obj['auth_cred'],
symmetrical=sfc['symmetrical'])
sfc_id = self._vim_drivers.invoke(driver_type, 'create_chain',
sfc_id = self._vim_drivers.invoke(driver_type,
'create_chain',
name=vnffg_dict['name'],
vnfs=sfc['chain'], fc_id=fc_id,
symmetrical=sfc['symmetrical'],
auth_attr=vim_auth)
auth_attr=vim_obj['auth_cred'])
except Exception:
with excutils.save_and_reraise_exception():
self.delete_vnffg(context, vnffg_id=vnffg_dict['id'])
@ -276,24 +282,23 @@ class NfvoPlugin(nfvo_db.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin):
LOG.debug(_('chain update: %s'), chain)
sfc['chain'] = chain
sfc['symmetrical'] = new_vnffg['symmetrical']
vim_auth = self._get_vim_from_vnf(context,
list(vnffg_dict[
'vnf_mapping'].values())[0]
)
driver_type = vim_auth['type']
vim_obj = self._get_vim_from_vnf(context,
list(vnffg_dict[
'vnf_mapping'].values())[0])
driver_type = vim_obj['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,
auth_attr=vim_obj['auth_cred'],
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,
auth_attr=vim_obj['auth_cred'],
symmetrical=new_vnffg['symmetrical'])
except Exception:
with excutils.save_and_reraise_exception():
@ -321,21 +326,20 @@ class NfvoPlugin(nfvo_db.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin):
fc = super(NfvoPlugin, self).get_classifier(context,
nfp['classifier_id'])
vim_auth = self._get_vim_from_vnf(context,
list(vnffg_dict[
'vnf_mapping'].values())[0]
)
driver_type = vim_auth['type']
vim_obj = self._get_vim_from_vnf(context,
list(vnffg_dict[
'vnf_mapping'].values())[0])
driver_type = vim_obj['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)
auth_attr=vim_obj['auth_cred'])
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)
auth_attr=vim_obj['auth_cred'])
except Exception:
with excutils.save_and_reraise_exception():
vnffg_dict['status'] = constants.ERROR
@ -353,11 +357,37 @@ class NfvoPlugin(nfvo_db.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin):
"""
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'])
vim_obj = self.get_vim(context, vim_id['vim_id'], mask_password=False)
vim_auth = vim_obj['auth_cred']
vim_auth['password'] = self._decode_vim_auth(vim_obj['id'],
vim_auth['password'].
encode('utf-8'))
vim_auth['auth_url'] = vim_obj['auth_url']
if vim_obj is None:
raise nfvo.VimFromVnfNotFoundException(vnf_id=vnf_id)
return vim_obj
def _decode_vim_auth(self, vim_id, cred):
"""Decode Vim credentials
Decrypt VIM cred. using Fernet Key
"""
vim_key = self._find_vim_key(vim_id)
f = fernet.Fernet(vim_key)
if not f:
LOG.warning(_('Unable to decode VIM auth'))
raise nfvo.VimNotFoundException('Unable to decode VIM auth key')
return f.decrypt(cred)
@staticmethod
def _find_vim_key(vim_id):
key_file = os.path.join(CONF.vim_keys.openstack, vim_id)
LOG.debug(_('Attempting to open key file for vim id %s'), vim_id)
with open(key_file, 'r') as f:
return f.read()
LOG.warning(_('VIM id invalid or key not found for %s'), vim_id)
def _vim_resource_name_to_id(self, context, resource, name, vnf_id):
"""Converts a VIM resource name to its ID
@ -367,10 +397,10 @@ class NfvoPlugin(nfvo_db.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin):
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']
vim_obj = self._get_vim_from_vnf(context, vnf_id)
driver_type = vim_obj['type']
return self._vim_drivers.invoke(driver_type,
'get_vim_resource_id',
vim_auth=vim_auth,
vim_auth=vim_obj['auth_cred'],
resource_type=resource,
resource_name=name)

View File

@ -0,0 +1,234 @@
#
# 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 mock
import uuid
from tacker import context
from tacker.nfvo.drivers.vim import openstack_driver
from tacker.tests.unit import base
from tacker.tests.unit.db import utils
class FakeNeutronClient(mock.Mock):
def __init__(self):
super(FakeNeutronClient, self).__init__()
self.__fc_dict = {}
self.__pp_dict = {}
self.__ppg_dict = {}
self.__chain_dict = {}
def flow_classifier_create(self, fc_create_dict):
fc_id = uuid.uuid4()
self.__fc_dict[fc_id] = fc_create_dict
return fc_id
def show_flow_classifier(self, fc_dict):
fc_name = fc_dict['name']
for fc_id in self.__fc_dict:
fc = self.__fc_dict[fc_id]
if fc_name == fc['name']:
return {'id': fc_id}
return None
def flow_classifier_update(self, fc_id, fc_update_dict):
if fc_id not in self.__fc_dict:
return None
self.__fc_dict[fc_id] = fc_update_dict
return fc_update_dict
def flow_classifier_delete(self, fc_id):
if fc_id not in self.__fc_dict:
raise ValueError('fc not found')
self.__fc_dict.pop(fc_id)
def port_pair_create(self, port_pair):
pp_id = uuid.uuid4()
self.__pp_dict[pp_id] = port_pair
return pp_id
def show_port_pair(self, port_pair_dict):
input_pp_name = port_pair_dict['name']
for pp_id in self.__pp_dict:
port_pair = self.__pp_dict[pp_id]
if port_pair['name'] == input_pp_name:
return {'id': pp_id}
return None
def port_pair_group_create(self, port_pair_group):
ppg_id = uuid.uuid4()
self.__ppg_dict[ppg_id] = port_pair_group
return ppg_id
def show_port_pair_group(self, port_pair_group_dict):
input_ppg_name = port_pair_group_dict['name']
for ppg_id in self.__ppg_dict:
port_pair_group = self.__ppg_dict[ppg_id]
if port_pair_group['name'] == input_ppg_name:
return {'id': ppg_id}
return None
def port_chain_create(self, port_chain):
chain_id = uuid.uuid4()
self.__chain_dict[chain_id] = port_chain
return chain_id
def show_port_chain(self, port_chain_dict):
input_chain_name = port_chain_dict['name']
for chain_id in self.__chain_dict:
port_chain = self.__chain_dict[chain_id]
if port_chain['name'] == input_chain_name:
return {'id': chain_id}
return None
def port_chain_delete(self, chain_id):
if chain_id not in self.__chain_dict:
raise ValueError('port chain delete failed')
self.__chain_dict.pop(chain_id)
class TestChainSFC(base.TestCase):
def setUp(self):
super(TestChainSFC, self).setUp()
self.context = context.get_admin_context()
self.sfc_driver = openstack_driver.OpenStack_Driver()
self._mock_neutron_client()
self.addCleanup(mock.patch.stopall)
def _mock_neutron_client(self):
self.neutron_client = mock.Mock(wraps=FakeNeutronClient())
fake_neutron_client = mock.Mock()
fake_neutron_client.return_value = self.neutron_client
self._mock(
'tacker.nfvo.drivers.vim.openstack_driver.'
'NeutronClient',
fake_neutron_client)
def _mock(self, target, new=mock.DEFAULT):
patcher = mock.patch(target, new)
return patcher.start()
def test_create_flow_classifier(self):
flow_classifier = {'name': 'fake_fc',
'source_port_range': '2005-2010',
'ip_proto': 6,
'destination_port_range': '80-180'}
result = self.sfc_driver.\
create_flow_classifier(name='fake_ffg', fc=flow_classifier,
auth_attr=utils.get_vim_auth_obj())
self.assertIsNotNone(result)
def test_update_flow_classifier(self):
flow_classifier = {'name': 'next_fake_fc',
'description': 'fake flow-classifier',
'source_port_range': '2005-2010',
'ip_proto': 6,
'destination_port_range': '80-180'}
fc_id = self.sfc_driver.\
create_flow_classifier(name='fake_ffg', fc=flow_classifier,
auth_attr=utils.get_vim_auth_obj())
self.assertIsNotNone(fc_id)
flow_classifier['description'] = 'next fake flow-classifier'
result = self.sfc_driver.\
update_flow_classifier(fc_id=fc_id,
fc=flow_classifier,
auth_attr=utils.get_vim_auth_obj())
self.assertIsNotNone(result)
def test_delete_flow_classifier(self):
flow_classifier = {'name': 'another_fake_fc',
'description': 'another flow-classifier',
'source_port_range': '1999-2005',
'ip_proto': 6,
'destination_port_range': '80-100'}
fc_id = self.sfc_driver.\
create_flow_classifier(name='fake_ffg', fc=flow_classifier,
auth_attr=utils.get_vim_auth_obj())
self.assertIsNotNone(fc_id)
try:
self.sfc_driver.\
delete_flow_classifier(fc_id=fc_id,
auth_attr=utils.get_vim_auth_obj())
except Exception:
self.assertTrue(True)
def test_create_chain(self):
auth_attr = utils.get_vim_auth_obj()
flow_classifier = {'name': 'test_create_chain_fc',
'description': 'fc for testing create chain',
'source_port_range': '1997-2008',
'ip_proto': 6,
'destination_port_range': '80-100'}
fc_id = self.sfc_driver.\
create_flow_classifier(name='fake_ffg', fc=flow_classifier,
auth_attr=auth_attr)
self.assertIsNotNone(fc_id)
vnf_1 = {'name': 'test_create_chain_vnf_1',
'connection_points': [uuid.uuid4(), uuid.uuid4()]}
vnf_2 = {'name': 'test_create_chain_vnf_2',
'connection_points': [uuid.uuid4(), uuid.uuid4()]}
vnf_3 = {'name': 'test_create_chain_vnf_3',
'connection_points': [uuid.uuid4(), uuid.uuid4()]}
vnfs = [vnf_1, vnf_2, vnf_3]
result = self.sfc_driver.create_chain(name='fake_ffg',
fc_id=fc_id,
vnfs=vnfs,
auth_attr=auth_attr)
self.assertIsNotNone(result)
def test_delete_chain(self):
auth_attr = utils.get_vim_auth_obj()
flow_classifier = {'name': 'test_delete_chain_fc',
'description': 'fc for testing delete chain',
'source_port_range': '1000-2000',
'ip_proto': 6,
'destination_port_range': '80-180'}
fc_id = self.sfc_driver.\
create_flow_classifier(name='fake_ffg', fc=flow_classifier,
auth_attr=auth_attr)
self.assertIsNotNone(fc_id)
vnf_1 = {'name': 'test_delete_chain_vnf_1',
'connection_points': [uuid.uuid4(), uuid.uuid4()]}
vnf_2 = {'name': 'test_delete_chain_vnf_2',
'connection_points': [uuid.uuid4(), uuid.uuid4()]}
vnf_3 = {'name': 'test_delete_chain_vnf_3',
'connection_points': [uuid.uuid4(), uuid.uuid4()]}
vnfs = [vnf_1, vnf_2, vnf_3]
chain_id = self.sfc_driver.create_chain(name='fake_ffg',
fc_id=fc_id,
vnfs=vnfs,
auth_attr=auth_attr)
self.assertIsNotNone(chain_id)
try:
self.sfc_driver.delete_chain(chain_id,
auth_attr=auth_attr)
except Exception:
self.assertTrue(True)

View File

@ -35,9 +35,10 @@ SECRET_PASSWORD = '***'
def dummy_get_vim(*args, **kwargs):
vim_auth = utils.get_vim_auth_obj()
vim_auth['type'] = 'openstack'
return vim_auth
vim_obj = dict()
vim_obj['auth_cred'] = utils.get_vim_auth_obj()
vim_obj['type'] = 'openstack'
return vim_obj
class FakeDriverManager(mock.Mock):
@ -355,6 +356,7 @@ class TestNfvoPlugin(db_base.SqlTestCase):
self.assertIn('status', result)
self.assertEqual('PENDING_CREATE', result['status'])
self._driver_manager.invoke.assert_called_with(mock.ANY, mock.ANY,
name=mock.ANY,
vnfs=mock.ANY,
fc_id=mock.ANY,
auth_attr=mock.ANY,
@ -375,6 +377,7 @@ class TestNfvoPlugin(db_base.SqlTestCase):
self.assertIn('status', result)
self.assertEqual('PENDING_CREATE', result['status'])
self._driver_manager.invoke.assert_called_with(mock.ANY, mock.ANY,
name=mock.ANY,
vnfs=mock.ANY,
fc_id=mock.ANY,
auth_attr=mock.ANY,