Merge "VNFFG: neutron networking-sfc driver and plugin fixes"
This commit is contained in:
commit
3baa7b34ee
@ -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
|
||||
|
76
samples/tosca-templates/vnffgd/tosca-vnffg-vnfd1.yaml
Normal file
76
samples/tosca-templates/vnffgd/tosca-vnffg-vnfd1.yaml
Normal 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
|
76
samples/tosca-templates/vnffgd/tosca-vnffg-vnfd2.yaml
Normal file
76
samples/tosca-templates/vnffgd/tosca-vnffg-vnfd2.yaml
Normal 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
|
39
samples/tosca-templates/vnffgd/tosca-vnffgd-sample.yaml
Normal file
39
samples/tosca-templates/vnffgd/tosca-vnffgd-sample.yaml
Normal 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]
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
0
tacker/nfvo/drivers/vnffg/sfc_drivers/__init__.py
Normal file
0
tacker/nfvo/drivers/vnffg/sfc_drivers/__init__.py
Normal 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
|
||||
|
@ -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)
|
||||
|
0
tacker/tests/unit/vm/nfvo/drivers/vnffg/__init__.py
Normal file
0
tacker/tests/unit/vm/nfvo/drivers/vnffg/__init__.py
Normal file
234
tacker/tests/unit/vm/nfvo/drivers/vnffg/sfc_drivers/networking-sfc/test_n_sfc.py
Executable file
234
tacker/tests/unit/vm/nfvo/drivers/vnffg/sfc_drivers/networking-sfc/test_n_sfc.py
Executable 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)
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user