summaryrefslogtreecommitdiffstats
path: root/sfc/lib
diff options
context:
space:
mode:
Diffstat (limited to 'sfc/lib')
-rw-r--r--sfc/lib/config.py51
-rw-r--r--sfc/lib/odl_utils.py217
-rw-r--r--sfc/lib/openstack_utils.py182
-rw-r--r--sfc/lib/test_utils.py30
4 files changed, 338 insertions, 142 deletions
diff --git a/sfc/lib/config.py b/sfc/lib/config.py
index 7fb42e3e..a4f5d67b 100644
--- a/sfc/lib/config.py
+++ b/sfc/lib/config.py
@@ -8,16 +8,18 @@
# http://www.apache.org/licenses/LICENSE-2.0
#
+import logging
import os
import yaml
-import sfc
import functest
-import sfc.lib.test_utils as test_utils
-from functest.utils.constants import CONST
-import logging
+from functest.utils import config
+from functest.utils import env
import functest.utils.functest_utils as ft_utils
+import sfc
+import sfc.lib.test_utils as test_utils
+
logger = logging.getLogger(__name__)
@@ -28,8 +30,7 @@ class CommonConfig(object):
"""
def __init__(self):
- self.line_length = 30
- self.test_db = os.environ['TEST_DB_URL']
+ self.line_length = 35
self.functest_repo_path = os.path.dirname(functest.__file__)
self.functest_logging_api = os.path.join(self.functest_repo_path,
"ci", "logging.ini")
@@ -41,16 +42,30 @@ class CommonConfig(object):
self.sfc_test_dir, "vnfd-default-params-file")
self.vnffgd_dir = os.path.join(self.sfc_test_dir, "vnffgd-templates")
self.functest_results_dir = os.path.join(
- CONST.dir_results, "odl-sfc")
- self.config_file = os.path.join(self.sfc_test_dir, "config.yaml")
+ getattr(config.CONF, 'dir_results'), "odl-sfc")
+
+ # We need to know the openstack version in order to use one config or
+ # another. For Pike we will use config-pike.yaml. Queens and Rocky
+ # will use config.yaml
+ if 'OPENSTACK_OSA_VERSION' in os.environ:
+ if os.environ['OPENSTACK_OSA_VERSION'] == 'stable/pike':
+ self.config_file = os.path.join(self.sfc_test_dir,
+ "config-pike.yaml")
+ else:
+ self.config_file = os.path.join(self.sfc_test_dir,
+ "config.yaml")
+ else:
+ self.config_file = os.path.join(self.sfc_test_dir,
+ "config-pike.yaml")
+
self.vim_file = os.path.join(self.sfc_test_dir, "register-vim.json")
- self.installer_type = CONST.__getattribute__('INSTALLER_TYPE')
+ self.installer_type = env.get('INSTALLER_TYPE')
self.installer_fields = test_utils.fill_installer_dict(
- self.installer_type)
+ self.installer_type)
- self.installer_ip = CONST.__getattribute__('INSTALLER_IP')
+ self.installer_ip = env.get('INSTALLER_IP')
self.installer_user = ft_utils.get_parameter_from_yaml(
self.installer_fields['user'], self.config_file)
@@ -58,19 +73,19 @@ class CommonConfig(object):
try:
self.installer_password = ft_utils.get_parameter_from_yaml(
self.installer_fields['password'], self.config_file)
- except:
+ except Exception:
self.installer_password = None
try:
self.installer_key_file = ft_utils.get_parameter_from_yaml(
self.installer_fields['pkey_file'], self.config_file)
- except:
+ except Exception:
self.installer_key_file = None
try:
self.installer_cluster = ft_utils.get_parameter_from_yaml(
self.installer_fields['cluster'], self.config_file)
- except:
+ except Exception:
self.installer_cluster = None
self.flavor = ft_utils.get_parameter_from_yaml(
@@ -87,8 +102,7 @@ class CommonConfig(object):
"defaults.image_format", self.config_file)
self.image_url = ft_utils.get_parameter_from_yaml(
"defaults.image_url", self.config_file)
- self.dir_functest_data = ft_utils.get_functest_config(
- "general.dir.functest_data")
+ self.dir_functest_data = getattr(config.CONF, 'dir_functest_data')
class TestcaseConfig(object):
@@ -104,7 +118,8 @@ class TestcaseConfig(object):
testcases_yaml = yaml.safe_load(f)
test_config = testcases_yaml['testcases'].get(testcase, None)
if test_config is None:
- logger.error('Test {0} configuration is not present in {1}'
- .format(testcase, common_config.config_file))
+ logger.error(
+ 'Test %s configuration is not present in %s',
+ testcase, common_config.config_file)
# Update class fields with configuration variables dynamically
self.__dict__.update(**test_config)
diff --git a/sfc/lib/odl_utils.py b/sfc/lib/odl_utils.py
index c8b0ebdd..e1980423 100644
--- a/sfc/lib/odl_utils.py
+++ b/sfc/lib/odl_utils.py
@@ -5,13 +5,22 @@ import time
import json
import re
import logging
-import functest.utils.functest_utils as ft_utils
+import functools
import sfc.lib.openstack_utils as os_sfc_utils
logger = logging.getLogger(__name__)
+ODL_MODULE_EXCEPTIONS = {
+ "service-function-path-state": "service-function-path"
+}
+
+ODL_PLURAL_EXCEPTIONS = {
+ "service-function-path-state": "service-function-paths-state"
+}
+
+
def actual_rsps_in_compute(ovs_logger, compute_ssh):
'''
Example flows that match the regex (line wrapped because of flake8)
@@ -20,31 +29,36 @@ def actual_rsps_in_compute(ovs_logger, compute_ssh):
load:0x27->NXM_NX_NSP[0..23],load:0xff->NXM_NX_NSI[],
load:0xffffff->NXM_NX_NSH_C1[],load:0->NXM_NX_NSH_C2[],resubmit(,17)
'''
- match_rsp = re.compile(
- r'.+tp_dst=([0-9]+).+load:(0x[0-9a-f]+)->NXM_NX_NSP\[0\.\.23\].+')
+ match_rsp = re.compile(r'.+'
+ r'(tp_(?:src|dst)=[0-9]+)'
+ r'.+'
+ r'load:(0x[0-9a-f]+)->NXM_NX_NSP\[0\.\.23\]'
+ r'.+')
# First line is OFPST_FLOW reply (OF1.3) (xid=0x2):
# This is not a flow so ignore
flows = (ovs_logger.ofctl_dump_flows(compute_ssh, 'br-int', '101')
.strip().split('\n')[1:])
matching_flows = [match_rsp.match(f) for f in flows]
- # group(1) = 22 (tp_dst value) | group(2) = 0xff (rsp value)
- rsps_in_compute = ['{0}_{1}'.format(mf.group(2), mf.group(1))
+ # group(1) = tsp_dst=22 | group(2) = 0xff (rsp value)
+ rsps_in_compute = ['{0}|{1}'.format(mf.group(2), mf.group(1))
for mf in matching_flows if mf is not None]
return rsps_in_compute
-def get_active_rsps(odl_ip, odl_port):
+def get_active_rsps_on_ports(odl_ip, odl_port, neutron_ports):
'''
Queries operational datastore and returns the RSPs for which we have
- created a classifier (ACL). These are considered as active RSPs
- for which classification rules should exist in the compute nodes
+ created a classifier (ACL) on the specified neutron ports. These are
+ considered as active RSPs on those ports for which classification rules
+ should exist in the compute node on which such ports are located.
- This function enhances the returned dictionary with the
- destination port of the ACL.
+ This function enhances each returned RSP with the openflow matches on
+ the tcp ports that classify traffic into that RSP.
'''
+ port_ids = [port.id for port in neutron_ports]
acls = get_odl_acl_list(odl_ip, odl_port)
- rsps = []
+ rsps = {}
for acl in acls['access-lists']['acl']:
try:
# We get the first ace. ODL creates a new ACL
@@ -55,66 +69,129 @@ def get_active_rsps(odl_ip, odl_port):
acl['acl-name']))
continue
- if not ('netvirt-sfc-acl:rsp-name' in ace['actions']):
+ matches = ace['matches']
+
+ # We are just interested in the destination-port-range matches
+ # that we use throughout the tests
+ if matches.get('destination-port-range') is None:
continue
+ tcp_port = matches['destination-port-range']['lower-port']
+
+ # A single ace may classify traffic into a forward path
+ # and optionally into a reverse path if destination port is set
+ src_port = matches.get('netvirt-sfc-acl:source-port-uuid')
+ dst_port = matches.get('netvirt-sfc-acl:destination-port-uuid')
+ forward_of_match = None
+ reverse_of_match = None
+ if src_port in port_ids:
+ forward_of_match = 'tp_dst=' + str(tcp_port)
+ if dst_port in port_ids:
+ # For classification to the reverse path
+ # the openflow match inverts
+ reverse_of_match = 'tp_src=' + str(tcp_port)
+
+ # This ACL does not apply to any of the given ports
+ if not forward_of_match and not reverse_of_match:
+ continue
+
+ actions = ace['actions']
+ rsp_names = get_rsps_from_netvirt_acl_actions(odl_ip,
+ odl_port,
+ actions)
+
+ for rsp_name in rsp_names:
+ rsp = rsps.get(rsp_name)
+ if not rsp:
+ rsp = get_rsp(odl_ip, odl_port, rsp_name)
+ of_matches = rsp.get('of-matches', [])
+ if reverse_of_match and rsp.get('reverse-path'):
+ of_matches.append(reverse_of_match)
+ elif forward_of_match and not rsp.get('reverse-path'):
+ of_matches.append(forward_of_match)
+ rsp['of-matches'] = of_matches
+ rsps[rsp_name] = rsp
- rsp_name = ace['actions']['netvirt-sfc-acl:rsp-name']
- rsp = get_odl_resource_elem(odl_ip,
- odl_port,
- 'rendered-service-path',
- rsp_name,
- datastore='operational')
- '''
- Rsps are returned in the format:
- {
- "rendered-service-path": [
- {
- "name": "Path-red-Path-83",
- "path-id": 83,
- ...
- "rendered-service-path-hop": [
- {
- ...
- "service-function-name": "testVNF1",
- "service-index": 255
- ...
- 'rendered-service-path' Is returned as a list with one
- element (we select by name and the names are unique)
- '''
- rsp_port = rsp['rendered-service-path'][0]
- rsp_port['dst-port'] = (ace['matches']
- ['destination-port-range']['lower-port'])
- rsps.append(rsp_port)
- return rsps
-
-
-def promised_rsps_in_computes(odl_ip, odl_port):
+ return rsps.values()
+
+
+def get_rsps_from_netvirt_acl_actions(odl_ip, odl_port, netvirt_acl_actions):
'''
- Return a list of rsp_port which represents the rsp id and the destination
- port configured in ODL
+ Return the list of RSPs referenced from the netvirt sfc redirect action
'''
- rsps = get_active_rsps(odl_ip, odl_port)
- rsps_in_computes = ['{0}_{1}'.format(hex(rsp['path-id']), rsp['dst-port'])
- for rsp in rsps]
+ rsp_names = []
+
+ if 'netvirt-sfc-acl:rsp-name' in netvirt_acl_actions:
+ rsp_names.append(netvirt_acl_actions['netvirt-sfc-acl:rsp-name'])
+
+ if 'netvirt-sfc-acl:sfp-name' in netvirt_acl_actions:
+ # If the acl redirect action is a sfp instead of rsp
+ # we need to get the rsps associated to that sfp
+ sfp_name = netvirt_acl_actions['netvirt-sfc-acl:sfp-name']
+ sfp_state = get_odl_resource_elem(odl_ip,
+ odl_port,
+ 'service-function-path-state',
+ sfp_name,
+ datastore='operational')
+ sfp_rsps = sfp_state.get('sfp-rendered-service-path', [])
+ sfp_rsp_names = [rsp['name'] for rsp in sfp_rsps if 'name' in rsp]
+ rsp_names.extend(sfp_rsp_names)
+
+ return rsp_names
+
+
+def get_rsp(odl_ip, odl_port, rsp_name):
+ rsp = get_odl_resource_elem(odl_ip,
+ odl_port,
+ 'rendered-service-path',
+ rsp_name,
+ datastore='operational')
+ return rsp
+
+
+def promised_rsps_in_compute(odl_ip, odl_port, compute_ports):
+ '''
+ Return a list of rsp|of_match which represents the RSPs and openflow
+ matches on the source/destination port that classify traffic into such
+ RSP as configured in ODL ACLs
+ '''
+ rsps = get_active_rsps_on_ports(odl_ip, odl_port, compute_ports)
+ rsps_in_computes = ['{0}|{1}'.format(hex(rsp['path-id']), of_match)
+ for rsp in rsps
+ for of_match in rsp['of-matches']]
return rsps_in_computes
-@ft_utils.timethis
+def timethis(func):
+ """Measure the time it takes for a function to complete"""
+ @functools.wraps(func)
+ def timed(*args, **kwargs):
+ ts = time.time()
+ result = func(*args, **kwargs)
+ te = time.time()
+ elapsed = '{0}'.format(te - ts)
+ logger.info('{f}(*{a}, **{kw}) took: {t} sec'.format(
+ f=func.__name__, a=args, kw=kwargs, t=elapsed))
+ return result, elapsed
+ return timed
+
+
+@timethis
def wait_for_classification_rules(ovs_logger, compute_nodes, odl_ip, odl_port,
- compute_client_name, timeout=200):
+ compute_name, neutron_ports, timeout=200):
'''
Check if the classification rules configured in ODL are implemented in OVS.
We know by experience that this process might take a while
'''
try:
- compute = find_compute(compute_client_name, compute_nodes)
+ compute = find_compute(compute_name, compute_nodes)
# Find the configured rsps in ODL. Its format is nsp_destPort
promised_rsps = []
timeout2 = 10
while not promised_rsps:
- promised_rsps = promised_rsps_in_computes(odl_ip, odl_port)
+ promised_rsps = promised_rsps_in_compute(odl_ip, odl_port,
+ neutron_ports)
timeout2 -= 1
if timeout2 == 0:
os_sfc_utils.get_tacker_items()
@@ -123,7 +200,8 @@ def wait_for_classification_rules(ovs_logger, compute_nodes, odl_ip, odl_port,
time.sleep(3)
while timeout > 0:
- logger.info("RSPs in ODL Operational DataStore:")
+ logger.info("RSPs in ODL Operational DataStore"
+ "for compute '{}':".format(compute_name))
logger.info("{0}".format(promised_rsps))
# Fetch the rsps implemented in the compute
@@ -167,8 +245,18 @@ def get_odl_ip_port(nodes):
return ip, port
-def pluralize(s):
- return '{0}s'.format(s)
+def pluralize(resource):
+ plural = ODL_PLURAL_EXCEPTIONS.get(resource, None)
+ if not plural:
+ plural = '{0}s'.format(resource)
+ return plural
+
+
+def get_module(resource):
+ module = ODL_MODULE_EXCEPTIONS.get(resource, None)
+ if not module:
+ module = resource
+ return module
def format_odl_resource_list_url(odl_ip, odl_port, resource,
@@ -176,7 +264,8 @@ def format_odl_resource_list_url(odl_ip, odl_port, resource,
odl_pwd='admin'):
return ('http://{usr}:{pwd}@{ip}:{port}/restconf/{ds}/{rsrc}:{rsrcs}'
.format(usr=odl_user, pwd=odl_pwd, ip=odl_ip, port=odl_port,
- ds=datastore, rsrc=resource, rsrcs=pluralize(resource)))
+ ds=datastore, rsrc=get_module(resource),
+ rsrcs=pluralize(resource)))
def format_odl_resource_elem_url(odl_ip, odl_port, resource,
@@ -202,7 +291,12 @@ def get_odl_resource_elem(odl_ip, odl_port, resource,
elem_name, datastore='config'):
url = format_odl_resource_elem_url(
odl_ip, odl_port, resource, elem_name, datastore=datastore)
- return requests.get(url).json()
+ response = requests.get(url).json()
+ # Response is in the format of a dictionary containing
+ # a single value that is an array with the element requested:
+ # {'resource' : [element]}
+ # Return just the element
+ return response.get(resource, [{}])[0]
def delete_odl_resource_elem(odl_ip, odl_port, resource, elem_name,
@@ -288,10 +382,10 @@ def find_compute(compute_client_name, compute_nodes):
return compute
-def check_vnffg_deletion(odl_ip, odl_port, ovs_logger, compute_client_name,
- compute_nodes, retries=20):
+def check_vnffg_deletion(odl_ip, odl_port, ovs_logger, neutron_ports,
+ compute_client_name, compute_nodes, retries=20):
'''
- First, RSPs are checked in the oprational datastore of ODL. Nothing should
+ First, RSPs are checked in the operational datastore of ODL. Nothing
should exist. As it might take a while for ODL to remove that, some
retries are needed.
@@ -302,7 +396,7 @@ def check_vnffg_deletion(odl_ip, odl_port, ovs_logger, compute_client_name,
# Check RSPs
while retries_counter > 0:
- if (get_active_rsps(odl_ip, odl_port)):
+ if get_active_rsps_on_ports(odl_ip, odl_port, neutron_ports):
retries_counter -= 1
time.sleep(3)
else:
@@ -334,6 +428,7 @@ def check_vnffg_deletion(odl_ip, odl_port, ovs_logger, compute_client_name,
return True
+
def create_chain(tacker_client, default_param_file, neutron_port,
COMMON_CONFIG, TESTCASE_CONFIG):
diff --git a/sfc/lib/openstack_utils.py b/sfc/lib/openstack_utils.py
index 7257b126..b7254bf1 100644
--- a/sfc/lib/openstack_utils.py
+++ b/sfc/lib/openstack_utils.py
@@ -4,7 +4,8 @@ import time
import json
import yaml
from tackerclient.tacker import client as tackerclient
-from functest.utils.constants import CONST
+from functest.utils import constants
+from functest.utils import env
from snaps.openstack.tests import openstack_tests
@@ -29,7 +30,7 @@ import snaps.openstack.create_instance as cr_inst
from snaps.config.vm_inst import VmInstanceConfig, FloatingIpConfig
from snaps.openstack.utils import (
- nova_utils, neutron_utils, glance_utils, heat_utils, keystone_utils)
+ nova_utils, neutron_utils, heat_utils, keystone_utils)
logger = logging.getLogger(__name__)
DEFAULT_TACKER_API_VERSION = '1.0'
@@ -39,12 +40,12 @@ class OpenStackSFC:
def __init__(self):
self.os_creds = openstack_tests.get_credentials(
- os_env_file=CONST.__getattribute__('openstack_creds'))
+ os_env_file=constants.ENV_FILE)
self.creators = []
self.nova = nova_utils.nova_client(self.os_creds)
self.neutron = neutron_utils.neutron_client(self.os_creds)
- self.glance = glance_utils.glance_client(self.os_creds)
self.heat = heat_utils.heat_client(self.os_creds)
+ self.keystone = keystone_utils.keystone_client(self.os_creds)
def register_glance_image(self, name, url, img_format, public):
image_settings = ImageConfig(name=name, img_format=img_format, url=url,
@@ -80,7 +81,7 @@ class OpenStackSFC:
self.creators.append(network_creator)
# Router
- ext_network_name = CONST.__getattribute__('EXTERNAL_NETWORK')
+ ext_network_name = env.get('EXTERNAL_NETWORK')
router_settings = RouterConfig(name=router_name,
external_gateway=ext_network_name,
@@ -156,19 +157,30 @@ class OpenStackSFC:
'''
Return the compute where the client sits
'''
+ return self.get_vm_compute('client')
+
+ def get_compute_server(self):
+ '''
+ Return the compute where the server sits
+ '''
+ return self.get_vm_compute('server')
+
+ def get_vm_compute(self, vm_name):
+ '''
+ Return the compute where the vm sits
+ '''
for creator in self.creators:
# We want to filter the vm creators
- if hasattr(creator, 'get_vm_info'):
- vm_info = creator.get_vm_info()
- # We want to fetch only the client
- if vm_info['name'] == 'client':
- return vm_info['OS-EXT-SRV-ATTR:host']
+ if hasattr(creator, 'get_vm_inst'):
+ # We want to fetch by vm_name
+ if creator.get_vm_inst().name == vm_name:
+ return creator.get_vm_inst().compute_host
- raise Exception("There is no client VM!!")
+ raise Exception("There is no VM with name '{}'!!".format(vm_name))
def assign_floating_ip(self, router, vm, vm_creator):
'''
- Assign a floating ips to all the VMs
+ Assign floating ips to all the VMs
'''
name = vm.name + "-float"
port_name = vm.ports[0].name
@@ -180,22 +192,45 @@ class OpenStackSFC:
return ip.ip
# We need this function because tacker VMs cannot be created through SNAPs
- def assign_floating_ip_vnfs(self, router):
+ def assign_floating_ip_vnfs(self, router, ips=None):
'''
- Assign a floating ips to all the SFs
+ Assign floating ips to all the SFs. Optionally specify the
+ subnet IPs that a floating IP should be assigned to, assuming that the
+ SF is connected to a single subnet globally and per port.
'''
stacks = self.heat.stacks.list()
fips = []
+ project_name = 'admin'
for stack in stacks:
servers = heat_utils.get_stack_servers(self.heat,
self.nova,
self.neutron,
- stack)
+ self.keystone,
+ stack,
+ project_name)
sf_creator = cr_inst.generate_creator(self.os_creds,
servers[0],
- self.image_settings)
- port_name = servers[0].ports[0].name
+ self.image_settings,
+ project_name)
+
name = servers[0].name + "-float"
+ if ips is None:
+ port_name = servers[0].ports[0].name
+ else:
+ port_name = None
+ for port in servers[0].ports:
+ if port.ips[0]['ip_address'] in ips:
+ port_name = port.name
+ break
+
+ if port_name is None:
+ err_msg = "The VNF {} does not have any suitable port {} " \
+ "for floating IP assignment".format(
+ name,
+ 'with ip any of ' + str(ips) if ips else '')
+ logger.error(err_msg)
+ raise Exception(err_msg)
+
float_ip = FloatingIpConfig(name=name,
port_name=port_name,
router_name=router.name)
@@ -205,13 +240,19 @@ class OpenStackSFC:
return fips
- def get_client_port_id(self, vm):
+ def get_client_port(self, vm, vm_creator):
'''
Get the neutron port id of the client
'''
- port_id = neutron_utils.get_port(self.neutron,
- port_name=vm.name + "-port")
- return port_id
+ port_name = vm.name + "-port"
+ port = vm_creator.get_port_by_name(port_name)
+ if port is not None:
+ return port
+ else:
+ logger.error("The VM {0} does not have any port"
+ " with name {1}".format(vm.name, port_name))
+ raise Exception("Client VM does not have the desired port")
+
# TACKER SECTION #
@@ -227,7 +268,7 @@ def get_tacker_client_version():
def get_tacker_client(other_creds={}):
creds_override = None
os_creds = openstack_tests.get_credentials(
- os_env_file=CONST.__getattribute__('openstack_creds'),
+ os_env_file=constants.ENV_FILE,
overrides=creds_override)
sess = keystone_utils.keystone_session(os_creds)
return tackerclient.Client(get_tacker_client_version(), session=sess)
@@ -235,12 +276,12 @@ def get_tacker_client(other_creds={}):
def get_id_from_name(tacker_client, resource_type, resource_name):
try:
- req_params = {'fields': 'id', 'name': resource_name}
- endpoint = '/{0}s'.format(resource_type)
- resp = tacker_client.get(endpoint, params=req_params)
- endpoint = endpoint.replace('-', '_')
- return resp[endpoint[1:]][0]['id']
- except Exception, e:
+ params = {'fields': 'id', 'name': resource_name}
+ collection = resource_type + 's'
+ path = '/' + collection
+ resp = tacker_client.list(collection, path, **params)
+ return resp[collection][0]['id']
+ except Exception as e:
logger.error("Error [get_id_from_name(tacker_client, "
"resource_type, resource_name)]: %s" % e)
return None
@@ -288,7 +329,7 @@ def list_vnfds(tacker_client, verbose=False):
if not verbose:
vnfds = [vnfd['id'] for vnfd in vnfds['vnfds']]
return vnfds
- except Exception, e:
+ except Exception as e:
logger.error("Error [list_vnfds(tacker_client)]: %s" % e)
return None
@@ -303,7 +344,7 @@ def create_vnfd(tacker_client, tosca_file=None, vnfd_name=None):
return tacker_client.create_vnfd(
body={"vnfd": {"attributes": {"vnfd": vnfd_body},
"name": vnfd_name}})
- except Exception, e:
+ except Exception as e:
logger.error("Error [create_vnfd(tacker_client, '%s')]: %s"
% (tosca_file, e))
return None
@@ -317,7 +358,7 @@ def delete_vnfd(tacker_client, vnfd_id=None, vnfd_name=None):
raise Exception('You need to provide VNFD id or VNFD name')
vnfd = get_vnfd_id(tacker_client, vnfd_name)
return tacker_client.delete_vnfd(vnfd)
- except Exception, e:
+ except Exception as e:
logger.error("Error [delete_vnfd(tacker_client, '%s', '%s')]: %s"
% (vnfd_id, vnfd_name, e))
return None
@@ -329,7 +370,7 @@ def list_vnfs(tacker_client, verbose=False):
if not verbose:
vnfs = [vnf['id'] for vnf in vnfs['vnfs']]
return vnfs
- except Exception, e:
+ except Exception as e:
logger.error("Error [list_vnfs(tacker_client)]: %s" % e)
return None
@@ -364,7 +405,7 @@ def create_vnf(tacker_client, vnf_name, vnfd_id=None,
vnf_body['vnf']['vim_id'] = get_vim_id(tacker_client, vim_name)
return tacker_client.create_vnf(body=vnf_body)
- except Exception, e:
+ except Exception as e:
logger.error("error [create_vnf(tacker_client,"
" '%s', '%s', '%s')]: %s"
% (vnf_name, vnfd_id, vnfd_name, e))
@@ -384,12 +425,28 @@ def get_vnf(tacker_client, vnf_id=None, vnf_name=None):
else:
raise Exception('Could not retrieve ID from name [%s]' % vnf_name)
- except Exception, e:
+ except Exception as e:
logger.error("Could not retrieve VNF [vnf_id=%s, vnf_name=%s] - %s"
% (vnf_id, vnf_name, e))
return None
+def get_vnf_ip(tacker_client, vnf_id=None, vnf_name=None):
+ """
+ Get the management ip of the first VNF component as obtained from the
+ tacker REST API:
+
+ {
+ "vnf": {
+ ...
+ "mgmt_url": "{\"VDU1\": \"192.168.120.3\"}",
+ ...
+ }
+ """
+ vnf = get_vnf(tacker_client, vnf_id, vnf_name)
+ return json.loads(vnf['mgmt_url']).values()[0]
+
+
def wait_for_vnf(tacker_client, vnf_id=None, vnf_name=None, timeout=100):
try:
vnf = get_vnf(tacker_client, vnf_id, vnf_name)
@@ -409,7 +466,7 @@ def wait_for_vnf(tacker_client, vnf_id=None, vnf_name=None, timeout=100):
raise Exception('Timeout when booting vnf %s' % vnf['id'])
return vnf['id']
- except Exception, e:
+ except Exception as e:
logger.error("error [wait_for_vnf(tacker_client, '%s', '%s')]: %s"
% (vnf_id, vnf_name, e))
return None
@@ -423,7 +480,7 @@ def delete_vnf(tacker_client, vnf_id=None, vnf_name=None):
raise Exception('You need to provide a VNF id or name')
vnf = get_vnf_id(tacker_client, vnf_name)
return tacker_client.delete_vnf(vnf)
- except Exception, e:
+ except Exception as e:
logger.error("Error [delete_vnf(tacker_client, '%s', '%s')]: %s"
% (vnf_id, vnf_name, e))
return None
@@ -437,7 +494,7 @@ def create_vim(tacker_client, vim_file=None):
vim_body = json.load(vim_fd)
logger.info('VIM template:\n{0}'.format(vim_body))
return tacker_client.create_vim(body=vim_body)
- except Exception, e:
+ except Exception as e:
logger.error("Error [create_vim(tacker_client, '%s')]: %s"
% (vim_file, e))
return None
@@ -453,14 +510,14 @@ def create_vnffgd(tacker_client, tosca_file=None, vnffgd_name=None):
return tacker_client.create_vnffgd(
body={'vnffgd': {'name': vnffgd_name,
'template': {'vnffgd': vnffgd_body}}})
- except Exception, e:
+ except Exception as e:
logger.error("Error [create_vnffgd(tacker_client, '%s')]: %s"
% (tosca_file, e))
return None
def create_vnffg(tacker_client, vnffg_name=None, vnffgd_id=None,
- vnffgd_name=None, param_file=None):
+ vnffgd_name=None, param_file=None, symmetrical=False):
'''
Creates the vnffg which will provide the RSP and the classifier
'''
@@ -468,7 +525,8 @@ def create_vnffg(tacker_client, vnffg_name=None, vnffgd_id=None,
vnffg_body = {
'vnffg': {
'attributes': {},
- 'name': vnffg_name
+ 'name': vnffg_name,
+ 'symmetrical': symmetrical
}
}
if param_file is not None:
@@ -485,7 +543,7 @@ def create_vnffg(tacker_client, vnffg_name=None, vnffgd_id=None,
vnffg_body['vnffg']['vnffgd_id'] = get_vnffgd_id(tacker_client,
vnffgd_name)
return tacker_client.create_vnffg(body=vnffg_body)
- except Exception, e:
+ except Exception as e:
logger.error("error [create_vnffg(tacker_client,"
" '%s', '%s', '%s')]: %s"
% (vnffg_name, vnffgd_id, vnffgd_name, e))
@@ -498,7 +556,7 @@ def list_vnffgds(tacker_client, verbose=False):
if not verbose:
vnffgds = [vnffgd['id'] for vnffgd in vnffgds['vnffgds']]
return vnffgds
- except Exception, e:
+ except Exception as e:
logger.error("Error [list_vnffgds(tacker_client)]: %s" % e)
return None
@@ -509,7 +567,7 @@ def list_vnffgs(tacker_client, verbose=False):
if not verbose:
vnffgs = [vnffg['id'] for vnffg in vnffgs['vnffgs']]
return vnffgs
- except Exception, e:
+ except Exception as e:
logger.error("Error [list_vnffgs(tacker_client)]: %s" % e)
return None
@@ -522,7 +580,7 @@ def delete_vnffg(tacker_client, vnffg_id=None, vnffg_name=None):
raise Exception('You need to provide a VNFFG id or name')
vnffg = get_vnffg_id(tacker_client, vnffg_name)
return tacker_client.delete_vnffg(vnffg)
- except Exception, e:
+ except Exception as e:
logger.error("Error [delete_vnffg(tacker_client, '%s', '%s')]: %s"
% (vnffg_id, vnffg_name, e))
return None
@@ -536,7 +594,7 @@ def delete_vnffgd(tacker_client, vnffgd_id=None, vnffgd_name=None):
raise Exception('You need to provide VNFFGD id or VNFFGD name')
vnffgd = get_vnffgd_id(tacker_client, vnffgd_name)
return tacker_client.delete_vnffgd(vnffgd)
- except Exception, e:
+ except Exception as e:
logger.error("Error [delete_vnffgd(tacker_client, '%s', '%s')]: %s"
% (vnffgd_id, vnffgd_name, e))
return None
@@ -548,7 +606,7 @@ def list_vims(tacker_client, verbose=False):
if not verbose:
vims = [vim['id'] for vim in vims['vims']]
return vims
- except Exception, e:
+ except Exception as e:
logger.error("Error [list_vims(tacker_client)]: %s" % e)
return None
@@ -561,7 +619,7 @@ def delete_vim(tacker_client, vim_id=None, vim_name=None):
raise Exception('You need to provide VIM id or VIM name')
vim = get_vim_id(tacker_client, vim_name)
return tacker_client.delete_vim(vim)
- except Exception, e:
+ except Exception as e:
logger.error("Error [delete_vim(tacker_client, '%s', '%s')]: %s"
% (vim_id, vim_name, e))
return None
@@ -582,9 +640,8 @@ def register_vim(tacker_client, vim_file=None):
with open(vim_file) as f:
json_dict = json.load(f)
- json_dict['vim']['auth_url'] = CONST.__getattribute__('OS_AUTH_URL')
- json_dict['vim']['auth_cred']['password'] = CONST.__getattribute__(
- 'OS_PASSWORD')
+ json_dict['vim']['auth_url'] = os.environ['OS_AUTH_URL']
+ json_dict['vim']['auth_cred']['password'] = os.environ['OS_PASSWORD']
json.dump(json_dict, open(tmp_file, 'w'))
@@ -617,19 +674,28 @@ def create_vnf_in_av_zone(
def create_vnffg_with_param_file(tacker_client, vnffgd_name, vnffg_name,
- default_param_file, neutron_port):
+ default_param_file, client_port,
+ server_port=None, server_ip=None):
param_file = default_param_file
-
- if neutron_port is not None:
+ data = {}
+ if client_port:
+ data['net_src_port_id'] = client_port
+ if server_port:
+ data['net_dst_port_id'] = server_port
+ if server_ip:
+ data['ip_dst_prefix'] = server_ip
+
+ if client_port is not None or server_port is not None:
param_file = os.path.join(
'/tmp',
- 'param_{0}.json'.format(neutron_port))
- data = {
- 'net_src_port_id': neutron_port
- }
+ 'param_{0}.json'.format(vnffg_name))
with open(param_file, 'w+') as f:
json.dump(data, f)
+
+ symmetrical = True if client_port and server_port else False
+
create_vnffg(tacker_client,
vnffgd_name=vnffgd_name,
vnffg_name=vnffg_name,
- param_file=param_file)
+ param_file=param_file,
+ symmetrical=symmetrical)
diff --git a/sfc/lib/test_utils.py b/sfc/lib/test_utils.py
index a04f1e24..18c55dc1 100644
--- a/sfc/lib/test_utils.py
+++ b/sfc/lib/test_utils.py
@@ -10,9 +10,10 @@
import os
import subprocess
import time
+import shutil
+import urllib
import logging
-import functest.utils.functest_utils as ft_utils
logger = logging.getLogger(__name__)
@@ -51,13 +52,29 @@ def run_cmd_remote(ip, cmd, username="root", passwd="opnfv"):
return run_cmd(ssh_cmd)
+def download_url(url, dest_path):
+ """
+ Download a file to a destination path given a URL
+ """
+ name = url.rsplit('/')[-1]
+ dest = dest_path + "/" + name
+ try:
+ response = urllib.urlopen(url)
+ except Exception:
+ return False
+
+ with open(dest, 'wb') as lfile:
+ shutil.copyfileobj(response, lfile)
+ return True
+
+
def download_image(url, image_path):
image_filename = os.path.basename(image_path)
image_url = "%s/%s" % (url, image_filename)
image_dir = os.path.dirname(image_path)
if not os.path.isfile(image_path):
logger.info("Downloading image")
- ft_utils.download_url(image_url, image_dir)
+ download_url(image_url, image_dir)
else:
logger.info("Using old image")
@@ -107,17 +124,20 @@ def start_http_server(ip, iterations_check=10):
return False
-def start_vxlan_tool(remote_ip, interface="eth0", block=None):
+def start_vxlan_tool(remote_ip, interface="eth0", output=None, block=None):
"""
Starts vxlan_tool on a remote host.
vxlan_tool.py converts a regular Service Function into a NSH-aware SF
when the "--do forward" option is used, it decrements the NSI appropiately.
- 'block' parameters allows to specify a port where packets will be dropped.
+ 'output' allows to specify an interface through which to forward if
+ different than the input interface.
+ 'block' parameter allows to specify a port where packets will be dropped.
"""
command = "nohup python /root/vxlan_tool.py"
- options = "{do} {interface} {block_option}".format(
+ options = "{do} {interface} {output_option} {block_option}".format(
do="--do forward",
interface="--interface {}".format(interface),
+ output_option="--output {}".format(output) if output else "",
block_option="--block {}".format(block) if block is not None else "")
output_redirection = "> /dev/null 2>&1"