From d4757d239155cbd21ce45b656469c10d67cdf569 Mon Sep 17 00:00:00 2001 From: Manuel Buil Date: Tue, 12 Dec 2017 12:15:00 +0100 Subject: Clean up our utils.py Utils.py was getting messy. This patch divides it into three different files: * test_utils.py * odl_utils.py * openstack_utils.py The tacker library is integrated into openstack_utils.py Change-Id: I310949d1cee49b6aa1c9b3396bf6d6ca458cbaac Signed-off-by: Manuel Buil --- sfc/lib/odl_utils.py | 285 ++++++++ sfc/lib/openstack_tacker.py | 369 ---------- sfc/lib/openstack_utils.py | 616 +++++++++++++++++ sfc/lib/test_utils.py | 222 ++++++ sfc/lib/utils.py | 748 --------------------- .../sfc_one_chain_two_service_functions.py | 77 +-- sfc/tests/functest/sfc_symmetric_chain.py | 2 +- sfc/tests/functest/sfc_two_chains_SSH_and_HTTP.py | 105 +-- 8 files changed, 1216 insertions(+), 1208 deletions(-) create mode 100644 sfc/lib/odl_utils.py delete mode 100644 sfc/lib/openstack_tacker.py create mode 100644 sfc/lib/openstack_utils.py create mode 100644 sfc/lib/test_utils.py delete mode 100644 sfc/lib/utils.py diff --git a/sfc/lib/odl_utils.py b/sfc/lib/odl_utils.py new file mode 100644 index 00000000..45937263 --- /dev/null +++ b/sfc/lib/odl_utils.py @@ -0,0 +1,285 @@ +import ConfigParser +import os +import requests +import time +import json +import re +import logging +import functest.utils.functest_utils as ft_utils +import sfc.lib.openstack_utils as os_sfc_utils + + +logger = logging.getLogger(__name__) + + +def actual_rsps_in_compute(ovs_logger, compute_ssh): + ''' + Example flows that match the regex (line wrapped because of flake8) + table=101, n_packets=7, n_bytes=595, priority=500,tcp,in_port=2,tp_dst=80 + actions=push_nsh,load:0x1->NXM_NX_NSH_MDTYPE[],load:0x3->NXM_NX_NSH_NP[], + 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\].+') + # 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)) + for mf in matching_flows if mf is not None] + return rsps_in_compute + + +def get_active_rsps(odl_ip, odl_port): + ''' + 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 + + This function enhances the returned dictionary with the + destination port of the ACL. + ''' + + acls = get_odl_acl_list(odl_ip, odl_port) + rsps = [] + for acl in acls['access-lists']['acl']: + try: + # We get the first ace. ODL creates a new ACL + # with one ace for each classifier + ace = acl['access-list-entries']['ace'][0] + except: + logger.warn('ACL {0} does not have an ACE'.format( + acl['acl-name'])) + continue + + if not ('netvirt-sfc-acl:rsp-name' in ace['actions']): + continue + + 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 a list of rsp_port which represents the rsp id and the destination + port configured in ODL + ''' + 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] + + return rsps_in_computes + + +@ft_utils.timethis +def wait_for_classification_rules(ovs_logger, compute_nodes, odl_ip, odl_port, + 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: + # Find the compute where the client is + compute_client = os_sfc_utils.get_compute_client() + + for compute_node in compute_nodes: + if compute_node.name in compute_client: + compute = compute_node + try: + compute + except NameError: + logger.debug("No compute where the client is was found") + raise Exception("No compute where the client is was found") + + # 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) + timeout2 -= 1 + if timeout2 == 0: + os_sfc_utils.get_tacker_items() + get_odl_items(odl_ip, odl_port) + raise Exception("RSPs not configured in ODL") + time.sleep(3) + + while timeout > 0: + logger.info("RSPs in ODL Operational DataStore:") + logger.info("{0}".format(promised_rsps)) + + # Fetch the rsps implemented in the compute + compute_rsps = actual_rsps_in_compute(ovs_logger, + compute.ssh_client) + + logger.info("RSPs in compute nodes:") + logger.info("{0}".format(compute_rsps)) + + # We use sets to compare as we will not have the same value twice + if not (set(promised_rsps) ^ set(compute_rsps)): + # OVS state is consistent with ODL + logger.info("Classification rules were updated") + return + + timeout -= 1 + time.sleep(1) + + if timeout <= 0: + logger.error("Timeout but classification rules are not updated") + + except Exception as e: + logger.error('Error when waiting for classification rules: %s' % e) + + +def get_odl_ip_port(nodes): + controller_node = next(n for n in nodes if n.is_controller()) + home_folder = controller_node.run_cmd('pwd') + remote_ml2_conf_etc = '/etc/neutron/plugins/ml2/ml2_conf.ini' + remote_ml2_conf_home = '{0}/ml2_conf.ini'.format(home_folder) + local_ml2_conf_file = os.path.join(os.getcwd(), 'ml2_conf.ini') + controller_node.run_cmd('sudo cp {0} {1}/' + .format(remote_ml2_conf_etc, home_folder)) + controller_node.run_cmd('sudo chmod 777 {0}' + .format(remote_ml2_conf_home)) + controller_node.get_file(remote_ml2_conf_home, local_ml2_conf_file) + con_par = ConfigParser.RawConfigParser() + con_par.read(local_ml2_conf_file) + ip, port = re.search(r'[0-9]+(?:\.[0-9]+){3}\:[0-9]+', + con_par.get('ml2_odl', 'url')).group().split(':') + return ip, port + + +def pluralize(s): + return '{0}s'.format(s) + + +def format_odl_resource_list_url(odl_ip, odl_port, resource, + datastore='config', odl_user='admin', + 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))) + + +def format_odl_resource_elem_url(odl_ip, odl_port, resource, + elem_name, datastore='config'): + list_url = format_odl_resource_list_url( + odl_ip, odl_port, resource, datastore=datastore) + return ('{0}/{1}/{2}'.format(list_url, resource, elem_name)) + + +def odl_resource_list_names(resource, resource_json): + if len(resource_json[pluralize(resource)].items()) == 0: + return [] + return [r['name'] for r in resource_json[pluralize(resource)][resource]] + + +def get_odl_resource_list(odl_ip, odl_port, resource, datastore='config'): + url = format_odl_resource_list_url( + odl_ip, odl_port, resource, datastore=datastore) + return requests.get(url).json() + + +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() + + +def delete_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) + requests.delete(url) + + +def odl_acl_types_names(acl_json): + if len(acl_json['access-lists'].items()) == 0: + return [] + return [(acl['acl-type'], acl['acl-name']) + for acl in acl_json['access-lists']['acl']] + + +def format_odl_acl_list_url(odl_ip, odl_port, + odl_user='admin', odl_pwd='admin'): + acl_list_url = ('http://{usr}:{pwd}@{ip}:{port}/restconf/config/' + 'ietf-access-control-list:access-lists' + .format(usr=odl_user, pwd=odl_pwd, + ip=odl_ip, port=odl_port)) + return acl_list_url + + +def improve_json_layout(json_response): + return json.dumps(json_response, indent=4, separators=(',', ': ')) + + +def get_odl_items(odl_ip, odl_port): + acl_list_url = format_odl_acl_list_url(odl_ip, odl_port) + sf_list_url = format_odl_resource_list_url(odl_ip, odl_port, + "service-function") + sff_list_url = format_odl_resource_list_url(odl_ip, odl_port, + "service-function-forwarder") + sfc_list_url = format_odl_resource_list_url(odl_ip, odl_port, + "service-function-chain") + rsp_list_url = format_odl_resource_list_url(odl_ip, odl_port, + "rendered-service-path", + datastore="operational") + r_acl = requests.get(acl_list_url).json() + r_sf = requests.get(sf_list_url).json() + r_sff = requests.get(sff_list_url).json() + r_sfc = requests.get(sfc_list_url).json() + r_rsp = requests.get(rsp_list_url).json() + logger.debug("Configured ACLs in ODL: %s" % improve_json_layout(r_acl)) + logger.debug("Configured SFs in ODL: %s" % improve_json_layout(r_sf)) + logger.debug("Configured SFFs in ODL: %s" % improve_json_layout(r_sff)) + logger.debug("Configured SFCs in ODL: %s" % improve_json_layout(r_sfc)) + logger.debug("Configured RSPs in ODL: %s" % improve_json_layout(r_rsp)) + + +def get_odl_acl_list(odl_ip, odl_port): + acl_list_url = format_odl_acl_list_url(odl_ip, odl_port) + r = requests.get(acl_list_url) + return r.json() + + +def delete_odl_acl(odl_ip, odl_port, acl_type, acl_name): + acl_list_url = format_odl_acl_list_url(odl_ip, odl_port) + acl_url = '{0}/acl/{1}/{2}'.format(acl_list_url, acl_type, acl_name) + requests.delete(acl_url) + + +def delete_acl(clf_name, odl_ip, odl_port): + # delete_sfc_classifier(tacker_client, sfc_clf_name=clf_name) + delete_odl_acl(odl_ip, + odl_port, + 'ietf-access-control-list:ipv4-acl', + clf_name) diff --git a/sfc/lib/openstack_tacker.py b/sfc/lib/openstack_tacker.py deleted file mode 100644 index 474cabf8..00000000 --- a/sfc/lib/openstack_tacker.py +++ /dev/null @@ -1,369 +0,0 @@ -import logging -import os -import time -import json -import yaml - -from tackerclient.tacker import client as tackerclient -from functest.utils import openstack_utils as os_utils - - -logger = logging.getLogger(__name__) - -DEFAULT_TACKER_API_VERSION = '1.0' - - -def get_tacker_client_version(): - api_version = os.getenv('OS_TACKER_API_VERSION') - if api_version is not None: - logger.info("OS_TACKER_API_VERSION is set in env as '%s'", api_version) - return api_version - return DEFAULT_TACKER_API_VERSION - - -def get_tacker_client(other_creds={}): - sess = os_utils.get_session(other_creds) - return tackerclient.Client(get_tacker_client_version(), session=sess) - - -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: - logger.error("Error [get_id_from_name(tacker_client, " - "resource_type, resource_name)]: %s" % e) - return None - - -def get_vnfd_id(tacker_client, vnfd_name): - return get_id_from_name(tacker_client, 'vnfd', vnfd_name) - - -def get_vim_id(tacker_client, vim_name): - return get_id_from_name(tacker_client, 'vim', vim_name) - - -def get_vnf_id(tacker_client, vnf_name, timeout=5): - vnf_id = None - while vnf_id is None and timeout >= 0: - vnf_id = get_id_from_name(tacker_client, 'vnf', vnf_name) - if vnf_id is None: - logger.info("Could not retrieve ID for vnf with name [%s]." - " Retrying." % vnf_name) - time.sleep(1) - timeout -= 1 - return vnf_id - - -def get_vnffg_id(tacker_client, vnffg_name, timeout=5): - vnffg_id = None - while vnffg_id is None and timeout >= 0: - vnffg_id = get_id_from_name(tacker_client, 'vnffg', vnffg_name) - if vnffg_id is None: - logger.info("Could not retrieve ID for vnffg with name [%s]." - " Retrying." % vnffg_name) - time.sleep(1) - timeout -= 1 - return vnffg_id - - -def get_vnffgd_id(tacker_client, vnffgd_name): - return get_id_from_name(tacker_client, 'vnffgd', vnffgd_name) - - -def list_vnfds(tacker_client, verbose=False): - try: - vnfds = tacker_client.list_vnfds(retrieve_all=True) - if not verbose: - vnfds = [vnfd['id'] for vnfd in vnfds['vnfds']] - return vnfds - except Exception, e: - logger.error("Error [list_vnfds(tacker_client)]: %s" % e) - return None - - -def create_vnfd(tacker_client, tosca_file=None, vnfd_name=None): - try: - vnfd_body = {} - if tosca_file is not None: - with open(tosca_file) as tosca_fd: - vnfd_body = tosca_fd.read() - logger.info('VNFD template:\n{0}'.format(vnfd_body)) - return tacker_client.create_vnfd( - body={"vnfd": {"attributes": {"vnfd": vnfd_body}, - "name": vnfd_name}}) - except Exception, e: - logger.error("Error [create_vnfd(tacker_client, '%s')]: %s" - % (tosca_file, e)) - return None - - -def delete_vnfd(tacker_client, vnfd_id=None, vnfd_name=None): - try: - vnfd = vnfd_id - if vnfd is None: - if vnfd_name is 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: - logger.error("Error [delete_vnfd(tacker_client, '%s', '%s')]: %s" - % (vnfd_id, vnfd_name, e)) - return None - - -def list_vnfs(tacker_client, verbose=False): - try: - vnfs = tacker_client.list_vnfs(retrieve_all=True) - if not verbose: - vnfs = [vnf['id'] for vnf in vnfs['vnfs']] - return vnfs - except Exception, e: - logger.error("Error [list_vnfs(tacker_client)]: %s" % e) - return None - - -def create_vnf(tacker_client, vnf_name, vnfd_id=None, - vnfd_name=None, vim_id=None, vim_name=None, param_file=None): - try: - vnf_body = { - 'vnf': { - 'attributes': {}, - 'name': vnf_name - } - } - if param_file is not None: - params = None - with open(param_file) as f: - params = f.read() - vnf_body['vnf']['attributes']['param_values'] = params - - if vnfd_id is not None: - vnf_body['vnf']['vnfd_id'] = vnfd_id - else: - if vnfd_name is None: - raise Exception('vnfd id or vnfd name is required') - vnf_body['vnf']['vnfd_id'] = get_vnfd_id(tacker_client, vnfd_name) - - if vim_id is not None: - vnf_body['vnf']['vim_id'] = vim_id - else: - if vim_name is None: - raise Exception('vim id or vim name is required') - vnf_body['vnf']['vim_id'] = get_vim_id(tacker_client, vim_name) - return tacker_client.create_vnf(body=vnf_body) - - except Exception, e: - logger.error("error [create_vnf(tacker_client," - " '%s', '%s', '%s')]: %s" - % (vnf_name, vnfd_id, vnfd_name, e)) - return None - - -def get_vnf(tacker_client, vnf_id=None, vnf_name=None): - try: - if vnf_id is None and vnf_name is None: - raise Exception('You must specify vnf_id or vnf_name') - - _id = get_vnf_id(tacker_client, vnf_name) if vnf_id is None else vnf_id - - if _id is not None: - all_vnfs = list_vnfs(tacker_client, verbose=True)['vnfs'] - return next((vnf for vnf in all_vnfs if vnf['id'] == _id), None) - else: - raise Exception('Could not retrieve ID from name [%s]' % vnf_name) - - except Exception, e: - logger.error("Could not retrieve VNF [vnf_id=%s, vnf_name=%s] - %s" - % (vnf_id, vnf_name, e)) - return None - - -def wait_for_vnf(tacker_client, vnf_id=None, vnf_name=None, timeout=100): - try: - vnf = get_vnf(tacker_client, vnf_id, vnf_name) - if vnf is None: - raise Exception("Could not retrieve VNF - id='%s', name='%s'" - % vnf_id, vnf_name) - logger.info('Waiting for vnf {0}'.format(str(vnf))) - while vnf['status'] != 'ACTIVE' and timeout >= 0: - if vnf['status'] == 'ERROR': - raise Exception('Error when booting vnf %s' % vnf['id']) - elif vnf['status'] == 'PENDING_CREATE': - time.sleep(3) - timeout -= 3 - vnf = get_vnf(tacker_client, vnf_id, vnf_name) - - if (timeout < 0): - raise Exception('Timeout when booting vnf %s' % vnf['id']) - - return vnf['id'] - except Exception, e: - logger.error("error [wait_for_vnf(tacker_client, '%s', '%s')]: %s" - % (vnf_id, vnf_name, e)) - return None - - -def delete_vnf(tacker_client, vnf_id=None, vnf_name=None): - try: - vnf = vnf_id - if vnf is None: - if vnf_name is 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: - logger.error("Error [delete_vnf(tacker_client, '%s', '%s')]: %s" - % (vnf_id, vnf_name, e)) - return None - - -def create_vim(tacker_client, vim_file=None): - try: - vim_body = {} - if vim_file is not None: - with open(vim_file) as vim_fd: - 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: - logger.error("Error [create_vim(tacker_client, '%s')]: %s" - % (vim_file, e)) - return None - - -def create_vnffgd(tacker_client, tosca_file=None, vnffgd_name=None): - try: - vnffgd_body = {} - if tosca_file is not None: - with open(tosca_file) as tosca_fd: - vnffgd_body = yaml.safe_load(tosca_fd) - logger.info('VNFFGD template:\n{0}'.format(vnffgd_body)) - return tacker_client.create_vnffgd( - body={'vnffgd': {'name': vnffgd_name, - 'template': {'vnffgd': vnffgd_body}}}) - except Exception, 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): - ''' - Creates the vnffg which will provide the RSP and the classifier - ''' - try: - vnffg_body = { - 'vnffg': { - 'attributes': {}, - 'name': vnffg_name - } - } - if param_file is not None: - params = None - with open(param_file) as f: - params = f.read() - params_dict = yaml.safe_load(params) - vnffg_body['vnffg']['attributes']['param_values'] = params_dict - if vnffgd_id is not None: - vnffg_body['vnffg']['vnffgd_id'] = vnffgd_id - else: - if vnffgd_name is None: - raise Exception('vnffgd id or vnffgd name is required') - vnffg_body['vnffg']['vnffgd_id'] = get_vnffgd_id(tacker_client, - vnffgd_name) - return tacker_client.create_vnffg(body=vnffg_body) - except Exception, e: - logger.error("error [create_vnffg(tacker_client," - " '%s', '%s', '%s')]: %s" - % (vnffg_name, vnffgd_id, vnffgd_name, e)) - return None - - -def list_vnffgds(tacker_client, verbose=False): - try: - vnffgds = tacker_client.list_vnffgds(retrieve_all=True) - if not verbose: - vnffgds = [vnffgd['id'] for vnffgd in vnffgds['vnffgds']] - return vnffgds - except Exception, e: - logger.error("Error [list_vnffgds(tacker_client)]: %s" % e) - return None - - -def list_vnffgs(tacker_client, verbose=False): - try: - vnffgs = tacker_client.list_vnffgs(retrieve_all=True) - if not verbose: - vnffgs = [vnffg['id'] for vnffg in vnffgs['vnffgs']] - return vnffgs - except Exception, e: - logger.error("Error [list_vnffgs(tacker_client)]: %s" % e) - return None - - -def delete_vnffg(tacker_client, vnffg_id=None, vnffg_name=None): - try: - vnffg = vnffg_id - if vnffg is None: - if vnffg_name is 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: - logger.error("Error [delete_vnffg(tacker_client, '%s', '%s')]: %s" - % (vnffg_id, vnffg_name, e)) - return None - - -def delete_vnffgd(tacker_client, vnffgd_id=None, vnffgd_name=None): - try: - vnffgd = vnffgd_id - if vnffgd is None: - if vnffgd_name is 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: - logger.error("Error [delete_vnffgd(tacker_client, '%s', '%s')]: %s" - % (vnffgd_id, vnffgd_name, e)) - return None - - -def list_vims(tacker_client, verbose=False): - try: - vims = tacker_client.list_vims(retrieve_all=True) - if not verbose: - vims = [vim['id'] for vim in vims['vims']] - return vims - except Exception, e: - logger.error("Error [list_vims(tacker_client)]: %s" % e) - return None - - -def delete_vim(tacker_client, vim_id=None, vim_name=None): - try: - vim = vim_id - if vim is None: - if vim_name is 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: - logger.error("Error [delete_vim(tacker_client, '%s', '%s')]: %s" - % (vim_id, vim_name, e)) - return None - - -def get_tacker_items(): - tacker_client = get_tacker_client() - logger.debug("VIMs: %s" % list_vims(tacker_client)) - logger.debug("VNFDs: %s" % list_vnfds(tacker_client)) - logger.debug("VNFs: %s" % list_vnfs(tacker_client)) - logger.debug("VNFFGDs: %s" % list_vnffgds(tacker_client)) - logger.debug("VNFFGs: %s" % list_vnffgs(tacker_client)) diff --git a/sfc/lib/openstack_utils.py b/sfc/lib/openstack_utils.py new file mode 100644 index 00000000..2d4ecff3 --- /dev/null +++ b/sfc/lib/openstack_utils.py @@ -0,0 +1,616 @@ +import logging +import os +import time +import json +import yaml +import functest.utils.openstack_utils as os_utils +from tackerclient.tacker import client as tackerclient +from functest.utils.constants import CONST + + +logger = logging.getLogger(__name__) +DEFAULT_TACKER_API_VERSION = '1.0' + + +def get_av_zones(): + ''' + Return the availability zone each host belongs to + ''' + nova_client = os_utils.get_nova_client() + hosts = os_utils.get_hypervisors(nova_client) + return ['nova::{0}'.format(host) for host in hosts] + + +def get_compute_client(): + ''' + Return the compute where the client sits + ''' + nova_client = os_utils.get_nova_client() + hosts = os_utils.get_hypervisors(nova_client) + for compute in hosts: + vms = nova_client.servers.list(search_opts={'host': compute}) + for vm in vms: + if "client" in vm.name: + return compute + return False + + +def setup_neutron(neutron_client, net, subnet, router, subnet_cidr): + n_dict = os_utils.create_network_full(neutron_client, + net, + subnet, + router, + subnet_cidr) + if not n_dict: + logger.error("failed to create neutron network") + return False + + return n_dict["net_id"] + + +def create_secgroup_rule(neutron_client, sg_id, direction, protocol, + port_range_min=None, port_range_max=None): + # We create a security group in 2 steps + # 1 - we check the format and set the json body accordingly + # 2 - we call neturon client to create the security group + + # Format check + json_body = {'security_group_rule': {'direction': direction, + 'security_group_id': sg_id, + 'protocol': protocol}} + # parameters may be + # - both None => we do nothing + # - both Not None => we add them to the json description + # but one cannot be None is the other is not None + if (port_range_min is not None and port_range_max is not None): + # add port_range in json description + json_body['security_group_rule']['port_range_min'] = port_range_min + json_body['security_group_rule']['port_range_max'] = port_range_max + logger.debug("Security_group format set (port range included)") + else: + # either both port range are set to None => do nothing + # or one is set but not the other => log it and return False + if port_range_min is None and port_range_max is None: + logger.debug("Security_group format set (no port range mentioned)") + else: + logger.error("Bad security group format." + "One of the port range is not properly set:" + "range min: {}," + "range max: {}".format(port_range_min, + port_range_max)) + return False + + # Create security group using neutron client + try: + neutron_client.create_security_group_rule(json_body) + return True + except: + return False + + +def setup_ingress_egress_secgroup(neutron_client, protocol, + min_port=None, max_port=None): + secgroups = os_utils.get_security_groups(neutron_client) + for sg in secgroups: + # TODO: the version of the create_secgroup_rule function in + # functest swallows the exception thrown when a secgroup rule + # already exists and prints a ton of noise in the test output. + # Instead of making changes in functest code this late in the + # release cycle, we keep our own version without the exception + # logging. We must find a way to properly cleanup sec group + # rules using "functest openstack clean" or pretty printing the + # specific exception in the next release + create_secgroup_rule(neutron_client, sg['id'], + 'ingress', protocol, + port_range_min=min_port, + port_range_max=max_port) + create_secgroup_rule(neutron_client, sg['id'], + 'egress', protocol, + port_range_min=min_port, + port_range_max=max_port) + + +def create_security_groups(neutron_client, secgroup_name, secgroup_descr): + sg_id = os_utils.create_security_group_full(neutron_client, + secgroup_name, secgroup_descr) + setup_ingress_egress_secgroup(neutron_client, "icmp") + setup_ingress_egress_secgroup(neutron_client, "tcp", 22, 22) + setup_ingress_egress_secgroup(neutron_client, "tcp", 80, 80) + setup_ingress_egress_secgroup(neutron_client, "udp", 67, 68) + return sg_id + + +def create_instance(nova_client, name, flavor, image_id, network_id, sg_id, + secgroup_name=None, fixed_ip=None, + av_zone='', userdata=None, files=None): + logger.info("Creating instance '%s'..." % name) + logger.debug( + "Configuration:\n name=%s \n flavor=%s \n image=%s \n" + " network=%s\n secgroup=%s \n hypervisor=%s \n" + " fixed_ip=%s\n files=%s\n userdata=\n%s\n" + % (name, flavor, image_id, network_id, sg_id, + av_zone, fixed_ip, files, userdata)) + instance = os_utils.create_instance_and_wait_for_active( + flavor, + image_id, + network_id, + name, + config_drive=True, + userdata=userdata, + av_zone=av_zone, + fixed_ip=fixed_ip, + files=files) + + if instance is None: + logger.error("Error while booting instance.") + return None + + if secgroup_name: + logger.debug("Adding '%s' to security group '%s'..." + % (name, secgroup_name)) + else: + logger.debug("Adding '%s' to security group '%s'..." + % (name, sg_id)) + os_utils.add_secgroup_to_instance(nova_client, instance.id, sg_id) + + return instance + + +def assign_floating_ip(nova_client, neutron_client, instance_id): + instance = nova_client.servers.get(instance_id) + floating_ip = os_utils.create_floating_ip(neutron_client)['fip_addr'] + instance.add_floating_ip(floating_ip) + logger.info("Assigned floating ip [%s] to instance [%s]" + % (floating_ip, instance.name)) + + return floating_ip + + +def get_nova_id(tacker_client, resource, vnf_id=None, vnf_name=None): + vnf = get_vnf(tacker_client, vnf_id, vnf_name) + try: + if vnf is None: + raise Exception("VNF not found") + heat = os_utils.get_heat_client() + resource = heat.resources.get(vnf['instance_id'], resource) + return resource.attributes['id'] + except: + logger.error("Cannot get nova ID for VNF (id='%s', name='%s')" + % (vnf_id, vnf_name)) + return None + + +def get_neutron_interfaces(vm): + ''' + Get the interfaces of an instance + ''' + nova_client = os_utils.get_nova_client() + interfaces = nova_client.servers.interface_list(vm.id) + return interfaces + + +def get_client_port_id(vm): + ''' + Get the neutron port id of the client + ''' + interfaces = get_neutron_interfaces(vm) + if len(interfaces) > 1: + raise Exception("Client has more than one interface. Not expected!") + return interfaces[0].id + +# TACKER SECTION # + + +def get_tacker_client_version(): + api_version = os.getenv('OS_TACKER_API_VERSION') + if api_version is not None: + logger.info("OS_TACKER_API_VERSION is set in env as '%s'", api_version) + return api_version + return DEFAULT_TACKER_API_VERSION + + +def get_tacker_client(other_creds={}): + sess = os_utils.get_session(other_creds) + return tackerclient.Client(get_tacker_client_version(), session=sess) + + +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: + logger.error("Error [get_id_from_name(tacker_client, " + "resource_type, resource_name)]: %s" % e) + return None + + +def get_vnfd_id(tacker_client, vnfd_name): + return get_id_from_name(tacker_client, 'vnfd', vnfd_name) + + +def get_vim_id(tacker_client, vim_name): + return get_id_from_name(tacker_client, 'vim', vim_name) + + +def get_vnf_id(tacker_client, vnf_name, timeout=5): + vnf_id = None + while vnf_id is None and timeout >= 0: + vnf_id = get_id_from_name(tacker_client, 'vnf', vnf_name) + if vnf_id is None: + logger.info("Could not retrieve ID for vnf with name [%s]." + " Retrying." % vnf_name) + time.sleep(1) + timeout -= 1 + return vnf_id + + +def get_vnffg_id(tacker_client, vnffg_name, timeout=5): + vnffg_id = None + while vnffg_id is None and timeout >= 0: + vnffg_id = get_id_from_name(tacker_client, 'vnffg', vnffg_name) + if vnffg_id is None: + logger.info("Could not retrieve ID for vnffg with name [%s]." + " Retrying." % vnffg_name) + time.sleep(1) + timeout -= 1 + return vnffg_id + + +def get_vnffgd_id(tacker_client, vnffgd_name): + return get_id_from_name(tacker_client, 'vnffgd', vnffgd_name) + + +def list_vnfds(tacker_client, verbose=False): + try: + vnfds = tacker_client.list_vnfds(retrieve_all=True) + if not verbose: + vnfds = [vnfd['id'] for vnfd in vnfds['vnfds']] + return vnfds + except Exception, e: + logger.error("Error [list_vnfds(tacker_client)]: %s" % e) + return None + + +def create_vnfd(tacker_client, tosca_file=None, vnfd_name=None): + try: + vnfd_body = {} + if tosca_file is not None: + with open(tosca_file) as tosca_fd: + vnfd_body = tosca_fd.read() + logger.info('VNFD template:\n{0}'.format(vnfd_body)) + return tacker_client.create_vnfd( + body={"vnfd": {"attributes": {"vnfd": vnfd_body}, + "name": vnfd_name}}) + except Exception, e: + logger.error("Error [create_vnfd(tacker_client, '%s')]: %s" + % (tosca_file, e)) + return None + + +def delete_vnfd(tacker_client, vnfd_id=None, vnfd_name=None): + try: + vnfd = vnfd_id + if vnfd is None: + if vnfd_name is 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: + logger.error("Error [delete_vnfd(tacker_client, '%s', '%s')]: %s" + % (vnfd_id, vnfd_name, e)) + return None + + +def list_vnfs(tacker_client, verbose=False): + try: + vnfs = tacker_client.list_vnfs(retrieve_all=True) + if not verbose: + vnfs = [vnf['id'] for vnf in vnfs['vnfs']] + return vnfs + except Exception, e: + logger.error("Error [list_vnfs(tacker_client)]: %s" % e) + return None + + +def create_vnf(tacker_client, vnf_name, vnfd_id=None, + vnfd_name=None, vim_id=None, vim_name=None, param_file=None): + try: + vnf_body = { + 'vnf': { + 'attributes': {}, + 'name': vnf_name + } + } + if param_file is not None: + params = None + with open(param_file) as f: + params = f.read() + vnf_body['vnf']['attributes']['param_values'] = params + + if vnfd_id is not None: + vnf_body['vnf']['vnfd_id'] = vnfd_id + else: + if vnfd_name is None: + raise Exception('vnfd id or vnfd name is required') + vnf_body['vnf']['vnfd_id'] = get_vnfd_id(tacker_client, vnfd_name) + + if vim_id is not None: + vnf_body['vnf']['vim_id'] = vim_id + else: + if vim_name is None: + raise Exception('vim id or vim name is required') + vnf_body['vnf']['vim_id'] = get_vim_id(tacker_client, vim_name) + return tacker_client.create_vnf(body=vnf_body) + + except Exception, e: + logger.error("error [create_vnf(tacker_client," + " '%s', '%s', '%s')]: %s" + % (vnf_name, vnfd_id, vnfd_name, e)) + return None + + +def get_vnf(tacker_client, vnf_id=None, vnf_name=None): + try: + if vnf_id is None and vnf_name is None: + raise Exception('You must specify vnf_id or vnf_name') + + _id = get_vnf_id(tacker_client, vnf_name) if vnf_id is None else vnf_id + + if _id is not None: + all_vnfs = list_vnfs(tacker_client, verbose=True)['vnfs'] + return next((vnf for vnf in all_vnfs if vnf['id'] == _id), None) + else: + raise Exception('Could not retrieve ID from name [%s]' % vnf_name) + + except Exception, e: + logger.error("Could not retrieve VNF [vnf_id=%s, vnf_name=%s] - %s" + % (vnf_id, vnf_name, e)) + return None + + +def wait_for_vnf(tacker_client, vnf_id=None, vnf_name=None, timeout=100): + try: + vnf = get_vnf(tacker_client, vnf_id, vnf_name) + if vnf is None: + raise Exception("Could not retrieve VNF - id='%s', name='%s'" + % vnf_id, vnf_name) + logger.info('Waiting for vnf {0}'.format(str(vnf))) + while vnf['status'] != 'ACTIVE' and timeout >= 0: + if vnf['status'] == 'ERROR': + raise Exception('Error when booting vnf %s' % vnf['id']) + elif vnf['status'] == 'PENDING_CREATE': + time.sleep(3) + timeout -= 3 + vnf = get_vnf(tacker_client, vnf_id, vnf_name) + + if (timeout < 0): + raise Exception('Timeout when booting vnf %s' % vnf['id']) + + return vnf['id'] + except Exception, e: + logger.error("error [wait_for_vnf(tacker_client, '%s', '%s')]: %s" + % (vnf_id, vnf_name, e)) + return None + + +def delete_vnf(tacker_client, vnf_id=None, vnf_name=None): + try: + vnf = vnf_id + if vnf is None: + if vnf_name is 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: + logger.error("Error [delete_vnf(tacker_client, '%s', '%s')]: %s" + % (vnf_id, vnf_name, e)) + return None + + +def create_vim(tacker_client, vim_file=None): + try: + vim_body = {} + if vim_file is not None: + with open(vim_file) as vim_fd: + 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: + logger.error("Error [create_vim(tacker_client, '%s')]: %s" + % (vim_file, e)) + return None + + +def create_vnffgd(tacker_client, tosca_file=None, vnffgd_name=None): + try: + vnffgd_body = {} + if tosca_file is not None: + with open(tosca_file) as tosca_fd: + vnffgd_body = yaml.safe_load(tosca_fd) + logger.info('VNFFGD template:\n{0}'.format(vnffgd_body)) + return tacker_client.create_vnffgd( + body={'vnffgd': {'name': vnffgd_name, + 'template': {'vnffgd': vnffgd_body}}}) + except Exception, 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): + ''' + Creates the vnffg which will provide the RSP and the classifier + ''' + try: + vnffg_body = { + 'vnffg': { + 'attributes': {}, + 'name': vnffg_name + } + } + if param_file is not None: + params = None + with open(param_file) as f: + params = f.read() + params_dict = yaml.safe_load(params) + vnffg_body['vnffg']['attributes']['param_values'] = params_dict + if vnffgd_id is not None: + vnffg_body['vnffg']['vnffgd_id'] = vnffgd_id + else: + if vnffgd_name is None: + raise Exception('vnffgd id or vnffgd name is required') + vnffg_body['vnffg']['vnffgd_id'] = get_vnffgd_id(tacker_client, + vnffgd_name) + return tacker_client.create_vnffg(body=vnffg_body) + except Exception, e: + logger.error("error [create_vnffg(tacker_client," + " '%s', '%s', '%s')]: %s" + % (vnffg_name, vnffgd_id, vnffgd_name, e)) + return None + + +def list_vnffgds(tacker_client, verbose=False): + try: + vnffgds = tacker_client.list_vnffgds(retrieve_all=True) + if not verbose: + vnffgds = [vnffgd['id'] for vnffgd in vnffgds['vnffgds']] + return vnffgds + except Exception, e: + logger.error("Error [list_vnffgds(tacker_client)]: %s" % e) + return None + + +def list_vnffgs(tacker_client, verbose=False): + try: + vnffgs = tacker_client.list_vnffgs(retrieve_all=True) + if not verbose: + vnffgs = [vnffg['id'] for vnffg in vnffgs['vnffgs']] + return vnffgs + except Exception, e: + logger.error("Error [list_vnffgs(tacker_client)]: %s" % e) + return None + + +def delete_vnffg(tacker_client, vnffg_id=None, vnffg_name=None): + try: + vnffg = vnffg_id + if vnffg is None: + if vnffg_name is 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: + logger.error("Error [delete_vnffg(tacker_client, '%s', '%s')]: %s" + % (vnffg_id, vnffg_name, e)) + return None + + +def delete_vnffgd(tacker_client, vnffgd_id=None, vnffgd_name=None): + try: + vnffgd = vnffgd_id + if vnffgd is None: + if vnffgd_name is 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: + logger.error("Error [delete_vnffgd(tacker_client, '%s', '%s')]: %s" + % (vnffgd_id, vnffgd_name, e)) + return None + + +def list_vims(tacker_client, verbose=False): + try: + vims = tacker_client.list_vims(retrieve_all=True) + if not verbose: + vims = [vim['id'] for vim in vims['vims']] + return vims + except Exception, e: + logger.error("Error [list_vims(tacker_client)]: %s" % e) + return None + + +def delete_vim(tacker_client, vim_id=None, vim_name=None): + try: + vim = vim_id + if vim is None: + if vim_name is 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: + logger.error("Error [delete_vim(tacker_client, '%s', '%s')]: %s" + % (vim_id, vim_name, e)) + return None + + +def get_tacker_items(): + tacker_client = get_tacker_client() + logger.debug("VIMs: %s" % list_vims(tacker_client)) + logger.debug("VNFDs: %s" % list_vnfds(tacker_client)) + logger.debug("VNFs: %s" % list_vnfs(tacker_client)) + logger.debug("VNFFGDs: %s" % list_vnffgds(tacker_client)) + logger.debug("VNFFGs: %s" % list_vnffgs(tacker_client)) + + +def register_vim(tacker_client, vim_file=None): + tmp_file = '/tmp/register-vim.json' + if vim_file is not 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.dump(json_dict, open(tmp_file, 'w')) + + create_vim(tacker_client, vim_file=tmp_file) + + +def create_vnf_in_av_zone( + tacker_client, + vnf_name, + vnfd_name, + vim_name, + default_param_file, + av_zone=None): + param_file = default_param_file + + if av_zone is not None or av_zone != 'nova': + param_file = os.path.join( + '/tmp', + 'param_{0}.json'.format(av_zone.replace('::', '_'))) + data = { + 'zone': av_zone + } + with open(param_file, 'w+') as f: + json.dump(data, f) + create_vnf(tacker_client, + vnf_name, + vnfd_name=vnfd_name, + vim_name=vim_name, + param_file=param_file) + + +def create_vnffg_with_param_file(tacker_client, vnffgd_name, vnffg_name, + default_param_file, neutron_port): + param_file = default_param_file + + if neutron_port is not None: + param_file = os.path.join( + '/tmp', + 'param_{0}.json'.format(neutron_port)) + data = { + 'net_src_port_id': neutron_port + } + with open(param_file, 'w+') as f: + json.dump(data, f) + create_vnffg(tacker_client, + vnffgd_name=vnffgd_name, + vnffg_name=vnffg_name, + param_file=param_file) diff --git a/sfc/lib/test_utils.py b/sfc/lib/test_utils.py new file mode 100644 index 00000000..9cdc02b2 --- /dev/null +++ b/sfc/lib/test_utils.py @@ -0,0 +1,222 @@ +#!/usr/bin/python +# +# Copyright (c) 2016 All rights reserved +# This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +import os +import subprocess +import time + +import logging +import functest.utils.functest_utils as ft_utils + + +logger = logging.getLogger(__name__) +SSH_OPTIONS = '-q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' +FUNCTEST_RESULTS_DIR = os.path.join("home", "opnfv", + "functest", "results", "odl-sfc") + + +def run_cmd(cmd): + """ + Run given command locally + Return a tuple with the return code, stdout, and stderr of the command + """ + pipe = subprocess.Popen(cmd, + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + stdout, stderr = [stream.strip() for stream in pipe.communicate()] + output = ' - STDOUT: "%s"' % stdout if len(stdout) > 0 else '' + error = ' - STDERR: "%s"' % stdout if len(stderr) > 0 else '' + logger.debug("Running [{command}] returns: [{rc}]{output}{error}".format( + command=cmd, + rc=pipe.returncode, + output=output, + error=error)) + + return pipe.returncode, stdout, stderr + + +def run_cmd_remote(ip, cmd, username="root", passwd="opnfv"): + """run given command on Remote Machine, Can be VM""" + ssh_opt_append = "%s -o ConnectTimeout=50 " % SSH_OPTIONS + ssh_cmd = "sshpass -p %s ssh %s %s@%s %s" % ( + passwd, ssh_opt_append, username, ip, cmd) + return run_cmd(ssh_cmd) + + +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) + else: + logger.info("Using old image") + + +def ping(remote, retries=100, retry_timeout=1): + cmd = 'ping -c1 -w{timeout} {remote}'.format( + timeout=retry_timeout, + remote=remote) + + while retries > 0: + rc, _, _ = run_cmd(cmd) + if rc == 0: + return True + + retries -= 1 + + return False + + +def start_http_server(ip, iterations_check=10): + """ + Start http server on a given machine. Wait until the process exists + and until the port is up + """ + cmd = "\'python -m SimpleHTTPServer 80" + cmd = cmd + " > /dev/null 2>&1 &\'" + run_cmd_remote(ip, cmd) + + # Wait for the process to start before checking + time.sleep(3) + _, output, _ = run_cmd_remote(ip, "ps aux | grep SimpleHTTPServer") + if not output: + logger.error("Failed to start http server") + return False + logger.info(output) + + while iterations_check > 0: + _, output, _ = run_cmd_remote(ip, "ss -na | grep *:80") + if output: + return True + else: + logger.debug("Port 80 is not up yet") + iterations_check -= 1 + time.sleep(5) + + logger.error("Failed to start http server") + return False + + +def start_vxlan_tool(remote_ip, interface="eth0", 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. + """ + command = "nohup python /root/vxlan_tool.py" + options = "{do} {interface} {block_option}".format( + do="--do forward", + interface="--interface {}".format(interface), + block_option="--block {}".format(block) if block is not None else "") + output_redirection = "> /dev/null 2>&1" + + full_command = "{command} {options} {output_redirection} &".format( + command=command, + options=options, + output_redirection=output_redirection) + + output_execution = run_cmd_remote(remote_ip, full_command) + + # Wait for the process to start before checking + time.sleep(3) + _, output, _ = run_cmd_remote(remote_ip, "ps aux | grep vxlan_tool") + if not output: + logger.error("Failed to start the vxlan tool") + return False + + return output_execution + + +def stop_vxlan_tool(remote_ip): + """ Stops vxlan_tool on a remote host""" + command = "pkill -f vxlan_tool.py" + return run_cmd_remote(remote_ip, command) + + +def netcat(source_ip, destination_ip, destination_port, source_port=None, + timeout=5): + """ + SSH into source_ip, and check the connectivity from there to destination_ip + on the specified port, using the netcat command. + Returns 0 on successful execution, != 0 on failure + """ + source_port_option = '' if source_port is None else '-p %s' % source_port + cmd = "nc -z {option} -w {timeout} {ip} {port}".format( + option=source_port_option, + timeout=timeout, + ip=destination_ip, + port=destination_port) + rc, _, _ = run_cmd_remote(source_ip, cmd) + logger.info("Running [%s] from [%s] returns [%s]" % (cmd, source_ip, rc)) + return rc + + +def is_ssh_blocked(source_ip, destination_ip, source_port=None): + rc = netcat( + source_ip, + destination_ip, + destination_port="22", + source_port=source_port) + return rc != 0 + + +def is_http_blocked(source_ip, destination_ip, source_port=None): + rc = netcat( + source_ip, + destination_ip, + destination_port="80", + source_port=source_port) + return rc != 0 + + +def capture_ovs_logs(ovs_logger, controller_clients, compute_clients, error): + timestamp = time.strftime("%Y%m%d-%H%M%S") + ovs_logger.dump_ovs_logs(controller_clients, + compute_clients, + related_error=error, + timestamp=timestamp) + + +def get_ssh_clients(nodes): + return [n.ssh_client for n in nodes] + + +def check_ssh(ips, retries=100): + """Check SSH connectivity to VNFs""" + check = [False for ip in ips] + logger.info("Checking SSH connectivity to the SFs with ips %s" % str(ips)) + while retries and not all(check): + for index, ip in enumerate(ips): + rc, _, _ = run_cmd_remote(ip, "exit") + check[index] = True if rc == 0 else False + + if all(check): + logger.info("SSH connectivity to the SFs established") + return True + + time.sleep(3) + retries -= 1 + + return False + + +def fill_installer_dict(installer_type): + default_string = "defaults.installer.{}.".format(installer_type) + installer_yaml_fields = { + "user": default_string+"user", + "password": default_string+"password", + "cluster": default_string+"cluster", + "pkey_file": default_string+"pkey_file" + } + return installer_yaml_fields diff --git a/sfc/lib/utils.py b/sfc/lib/utils.py deleted file mode 100644 index 5e344187..00000000 --- a/sfc/lib/utils.py +++ /dev/null @@ -1,748 +0,0 @@ -#!/usr/bin/python -# -# Copyright (c) 2016 All rights reserved -# This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -import ConfigParser -import os -import re -import subprocess -import requests -import time -import json - -import logging -from functest.utils.constants import CONST -import functest.utils.functest_utils as ft_utils -import functest.utils.openstack_utils as os_utils -import sfc.lib.openstack_tacker as os_tacker - - -logger = logging.getLogger(__name__) -SSH_OPTIONS = '-q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' -FUNCTEST_RESULTS_DIR = os.path.join("home", "opnfv", - "functest", "results", "odl-sfc") - - -def run_cmd(cmd): - """ - Run given command locally - Return a tuple with the return code, stdout, and stderr of the command - """ - pipe = subprocess.Popen(cmd, - shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - - stdout, stderr = [stream.strip() for stream in pipe.communicate()] - output = ' - STDOUT: "%s"' % stdout if len(stdout) > 0 else '' - error = ' - STDERR: "%s"' % stdout if len(stderr) > 0 else '' - logger.debug("Running [{command}] returns: [{rc}]{output}{error}".format( - command=cmd, - rc=pipe.returncode, - output=output, - error=error)) - - return pipe.returncode, stdout, stderr - - -def run_cmd_remote(ip, cmd, username="root", passwd="opnfv"): - """run given command on Remote Machine, Can be VM""" - ssh_opt_append = "%s -o ConnectTimeout=50 " % SSH_OPTIONS - ssh_cmd = "sshpass -p %s ssh %s %s@%s %s" % ( - passwd, ssh_opt_append, username, ip, cmd) - return run_cmd(ssh_cmd) - - -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) - else: - logger.info("Using old image") - - -def get_av_zones(): - ''' - Return the availability zone each host belongs to - ''' - nova_client = os_utils.get_nova_client() - hosts = os_utils.get_hypervisors(nova_client) - return ['nova::{0}'.format(host) for host in hosts] - - -def get_compute_client(): - ''' - Return the compute where the client sits - ''' - nova_client = os_utils.get_nova_client() - hosts = os_utils.get_hypervisors(nova_client) - for compute in hosts: - vms = nova_client.servers.list(search_opts={'host': compute}) - for vm in vms: - if "client" in vm.name: - return compute - return False - - -def create_vnf_in_av_zone( - tacker_client, - vnf_name, - vnfd_name, - vim_name, - default_param_file, - av_zone=None): - param_file = default_param_file - - if av_zone is not None or av_zone != 'nova': - param_file = os.path.join( - '/tmp', - 'param_{0}.json'.format(av_zone.replace('::', '_'))) - data = { - 'zone': av_zone - } - with open(param_file, 'w+') as f: - json.dump(data, f) - os_tacker.create_vnf(tacker_client, - vnf_name, - vnfd_name=vnfd_name, - vim_name=vim_name, - param_file=param_file) - - -def create_vnffg_with_param_file(tacker_client, vnffgd_name, vnffg_name, - default_param_file, neutron_port): - param_file = default_param_file - - if neutron_port is not None: - param_file = os.path.join( - '/tmp', - 'param_{0}.json'.format(neutron_port)) - data = { - 'net_src_port_id': neutron_port - } - with open(param_file, 'w+') as f: - json.dump(data, f) - os_tacker.create_vnffg(tacker_client, - vnffgd_name=vnffgd_name, - vnffg_name=vnffg_name, - param_file=param_file) - - -def setup_neutron(neutron_client, net, subnet, router, subnet_cidr): - n_dict = os_utils.create_network_full(neutron_client, - net, - subnet, - router, - subnet_cidr) - if not n_dict: - logger.error("failed to create neutron network") - return False - - return n_dict["net_id"] - - -def create_secgroup_rule(neutron_client, sg_id, direction, protocol, - port_range_min=None, port_range_max=None): - # We create a security group in 2 steps - # 1 - we check the format and set the json body accordingly - # 2 - we call neturon client to create the security group - - # Format check - json_body = {'security_group_rule': {'direction': direction, - 'security_group_id': sg_id, - 'protocol': protocol}} - # parameters may be - # - both None => we do nothing - # - both Not None => we add them to the json description - # but one cannot be None is the other is not None - if (port_range_min is not None and port_range_max is not None): - # add port_range in json description - json_body['security_group_rule']['port_range_min'] = port_range_min - json_body['security_group_rule']['port_range_max'] = port_range_max - logger.debug("Security_group format set (port range included)") - else: - # either both port range are set to None => do nothing - # or one is set but not the other => log it and return False - if port_range_min is None and port_range_max is None: - logger.debug("Security_group format set (no port range mentioned)") - else: - logger.error("Bad security group format." - "One of the port range is not properly set:" - "range min: {}," - "range max: {}".format(port_range_min, - port_range_max)) - return False - - # Create security group using neutron client - try: - neutron_client.create_security_group_rule(json_body) - return True - except: - return False - - -def setup_ingress_egress_secgroup(neutron_client, protocol, - min_port=None, max_port=None): - secgroups = os_utils.get_security_groups(neutron_client) - for sg in secgroups: - # TODO: the version of the create_secgroup_rule function in - # functest swallows the exception thrown when a secgroup rule - # already exists and prints a ton of noise in the test output. - # Instead of making changes in functest code this late in the - # release cycle, we keep our own version without the exception - # logging. We must find a way to properly cleanup sec group - # rules using "functest openstack clean" or pretty printing the - # specific exception in the next release - create_secgroup_rule(neutron_client, sg['id'], - 'ingress', protocol, - port_range_min=min_port, - port_range_max=max_port) - create_secgroup_rule(neutron_client, sg['id'], - 'egress', protocol, - port_range_min=min_port, - port_range_max=max_port) - - -def create_security_groups(neutron_client, secgroup_name, secgroup_descr): - sg_id = os_utils.create_security_group_full(neutron_client, - secgroup_name, secgroup_descr) - setup_ingress_egress_secgroup(neutron_client, "icmp") - setup_ingress_egress_secgroup(neutron_client, "tcp", 22, 22) - setup_ingress_egress_secgroup(neutron_client, "tcp", 80, 80) - setup_ingress_egress_secgroup(neutron_client, "udp", 67, 68) - return sg_id - - -def create_instance(nova_client, name, flavor, image_id, network_id, sg_id, - secgroup_name=None, fixed_ip=None, - av_zone='', userdata=None, files=None): - logger.info("Creating instance '%s'..." % name) - logger.debug( - "Configuration:\n name=%s \n flavor=%s \n image=%s \n" - " network=%s\n secgroup=%s \n hypervisor=%s \n" - " fixed_ip=%s\n files=%s\n userdata=\n%s\n" - % (name, flavor, image_id, network_id, sg_id, - av_zone, fixed_ip, files, userdata)) - instance = os_utils.create_instance_and_wait_for_active( - flavor, - image_id, - network_id, - name, - config_drive=True, - userdata=userdata, - av_zone=av_zone, - fixed_ip=fixed_ip, - files=files) - - if instance is None: - logger.error("Error while booting instance.") - return None - - if secgroup_name: - logger.debug("Adding '%s' to security group '%s'..." - % (name, secgroup_name)) - else: - logger.debug("Adding '%s' to security group '%s'..." - % (name, sg_id)) - os_utils.add_secgroup_to_instance(nova_client, instance.id, sg_id) - - return instance - - -def ping(remote, retries=100, retry_timeout=1): - cmd = 'ping -c1 -w{timeout} {remote}'.format( - timeout=retry_timeout, - remote=remote) - - while retries > 0: - rc, _, _ = run_cmd(cmd) - if rc == 0: - return True - - retries -= 1 - - return False - - -def assign_floating_ip(nova_client, neutron_client, instance_id): - instance = nova_client.servers.get(instance_id) - floating_ip = os_utils.create_floating_ip(neutron_client)['fip_addr'] - instance.add_floating_ip(floating_ip) - logger.info("Assigned floating ip [%s] to instance [%s]" - % (floating_ip, instance.name)) - - return floating_ip - - -def start_http_server(ip, iterations_check=10): - """ - Start http server on a given machine. Wait until the process exists - and until the port is up - """ - cmd = "\'python -m SimpleHTTPServer 80" - cmd = cmd + " > /dev/null 2>&1 &\'" - run_cmd_remote(ip, cmd) - - # Wait for the process to start before checking - time.sleep(3) - _, output, _ = run_cmd_remote(ip, "ps aux | grep SimpleHTTPServer") - if not output: - logger.error("Failed to start http server") - return False - logger.info(output) - - while iterations_check > 0: - _, output, _ = run_cmd_remote(ip, "ss -na | grep *:80") - if output: - return True - else: - logger.debug("Port 80 is not up yet") - iterations_check -= 1 - time.sleep(5) - - logger.error("Failed to start http server") - return False - - -def start_vxlan_tool(remote_ip, interface="eth0", 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. - """ - command = "nohup python /root/vxlan_tool.py" - options = "{do} {interface} {block_option}".format( - do="--do forward", - interface="--interface {}".format(interface), - block_option="--block {}".format(block) if block is not None else "") - output_redirection = "> /dev/null 2>&1" - - full_command = "{command} {options} {output_redirection} &".format( - command=command, - options=options, - output_redirection=output_redirection) - - output_execution = run_cmd_remote(remote_ip, full_command) - - # Wait for the process to start before checking - time.sleep(3) - _, output, _ = run_cmd_remote(remote_ip, "ps aux | grep vxlan_tool") - if not output: - logger.error("Failed to start the vxlan tool") - return False - - return output_execution - - -def stop_vxlan_tool(remote_ip): - """ Stops vxlan_tool on a remote host""" - command = "pkill -f vxlan_tool.py" - return run_cmd_remote(remote_ip, command) - - -def netcat(source_ip, destination_ip, destination_port, source_port=None, - timeout=5): - """ - SSH into source_ip, and check the connectivity from there to destination_ip - on the specified port, using the netcat command. - Returns 0 on successful execution, != 0 on failure - """ - source_port_option = '' if source_port is None else '-p %s' % source_port - cmd = "nc -z {option} -w {timeout} {ip} {port}".format( - option=source_port_option, - timeout=timeout, - ip=destination_ip, - port=destination_port) - rc, _, _ = run_cmd_remote(source_ip, cmd) - logger.info("Running [%s] from [%s] returns [%s]" % (cmd, source_ip, rc)) - return rc - - -def is_ssh_blocked(source_ip, destination_ip, source_port=None): - rc = netcat( - source_ip, - destination_ip, - destination_port="22", - source_port=source_port) - return rc != 0 - - -def is_http_blocked(source_ip, destination_ip, source_port=None): - rc = netcat( - source_ip, - destination_ip, - destination_port="80", - source_port=source_port) - return rc != 0 - - -def capture_ovs_logs(ovs_logger, controller_clients, compute_clients, error): - timestamp = time.strftime("%Y%m%d-%H%M%S") - ovs_logger.dump_ovs_logs(controller_clients, - compute_clients, - related_error=error, - timestamp=timestamp) - - -def get_ssh_clients(nodes): - return [n.ssh_client for n in nodes] - - -def check_ssh(ips, retries=100): - """Check SSH connectivity to VNFs""" - check = [False for ip in ips] - logger.info("Checking SSH connectivity to the SFs with ips %s" % str(ips)) - while retries and not all(check): - for index, ip in enumerate(ips): - rc, _, _ = run_cmd_remote(ip, "exit") - check[index] = True if rc == 0 else False - - if all(check): - logger.info("SSH connectivity to the SFs established") - return True - - time.sleep(3) - retries -= 1 - - return False - - -def actual_rsps_in_compute(ovs_logger, compute_ssh): - ''' - Example flows that match the regex (line wrapped because of flake8) - table=101, n_packets=7, n_bytes=595, priority=500,tcp,in_port=2,tp_dst=80 - actions=push_nsh,load:0x1->NXM_NX_NSH_MDTYPE[],load:0x3->NXM_NX_NSH_NP[], - 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\].+') - # 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)) - for mf in matching_flows if mf is not None] - return rsps_in_compute - - -def get_active_rsps(odl_ip, odl_port): - ''' - 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 - - This function enhances the returned dictionary with the - destination port of the ACL. - ''' - - acls = get_odl_acl_list(odl_ip, odl_port) - rsps = [] - for acl in acls['access-lists']['acl']: - try: - # We get the first ace. ODL creates a new ACL - # with one ace for each classifier - ace = acl['access-list-entries']['ace'][0] - except: - logger.warn('ACL {0} does not have an ACE'.format( - acl['acl-name'])) - continue - - if not ('netvirt-sfc-acl:rsp-name' in ace['actions']): - continue - - 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 a list of rsp_port which represents the rsp id and the destination - port configured in ODL - ''' - 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] - - return rsps_in_computes - - -@ft_utils.timethis -def wait_for_classification_rules(ovs_logger, compute_nodes, odl_ip, odl_port, - 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: - # Find the compute where the client is - compute_client = get_compute_client() - - for compute_node in compute_nodes: - if compute_node.name in compute_client: - compute = compute_node - try: - compute - except NameError: - logger.debug("No compute where the client is was found") - raise Exception("No compute where the client is was found") - - # 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) - timeout2 -= 1 - if timeout2 == 0: - os_tacker.get_tacker_items() - get_odl_items(odl_ip, odl_port) - raise Exception("RSPs not configured in ODL") - time.sleep(3) - - while timeout > 0: - logger.info("RSPs in ODL Operational DataStore:") - logger.info("{0}".format(promised_rsps)) - - # Fetch the rsps implemented in the compute - compute_rsps = actual_rsps_in_compute(ovs_logger, - compute.ssh_client) - - logger.info("RSPs in compute nodes:") - logger.info("{0}".format(compute_rsps)) - - # We use sets to compare as we will not have the same value twice - if not (set(promised_rsps) ^ set(compute_rsps)): - # OVS state is consistent with ODL - logger.info("Classification rules were updated") - return - - timeout -= 1 - time.sleep(1) - - if timeout <= 0: - logger.error("Timeout but classification rules are not updated") - - except Exception as e: - logger.error('Error when waiting for classification rules: %s' % e) - - -def get_nova_id(tacker_client, resource, vnf_id=None, vnf_name=None): - vnf = os_tacker.get_vnf(tacker_client, vnf_id, vnf_name) - try: - if vnf is None: - raise Exception("VNF not found") - heat = os_utils.get_heat_client() - resource = heat.resources.get(vnf['instance_id'], resource) - return resource.attributes['id'] - except: - logger.error("Cannot get nova ID for VNF (id='%s', name='%s')" - % (vnf_id, vnf_name)) - return None - - -def get_odl_ip_port(nodes): - controller_node = next(n for n in nodes if n.is_controller()) - home_folder = controller_node.run_cmd('pwd') - remote_ml2_conf_etc = '/etc/neutron/plugins/ml2/ml2_conf.ini' - remote_ml2_conf_home = '{0}/ml2_conf.ini'.format(home_folder) - local_ml2_conf_file = os.path.join(os.getcwd(), 'ml2_conf.ini') - controller_node.run_cmd('sudo cp {0} {1}/' - .format(remote_ml2_conf_etc, home_folder)) - controller_node.run_cmd('sudo chmod 777 {0}' - .format(remote_ml2_conf_home)) - controller_node.get_file(remote_ml2_conf_home, local_ml2_conf_file) - con_par = ConfigParser.RawConfigParser() - con_par.read(local_ml2_conf_file) - ip, port = re.search(r'[0-9]+(?:\.[0-9]+){3}\:[0-9]+', - con_par.get('ml2_odl', 'url')).group().split(':') - return ip, port - - -def pluralize(s): - return '{0}s'.format(s) - - -def format_odl_resource_list_url(odl_ip, odl_port, resource, - datastore='config', odl_user='admin', - 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))) - - -def format_odl_resource_elem_url(odl_ip, odl_port, resource, - elem_name, datastore='config'): - list_url = format_odl_resource_list_url( - odl_ip, odl_port, resource, datastore=datastore) - return ('{0}/{1}/{2}'.format(list_url, resource, elem_name)) - - -def odl_resource_list_names(resource, resource_json): - if len(resource_json[pluralize(resource)].items()) == 0: - return [] - return [r['name'] for r in resource_json[pluralize(resource)][resource]] - - -def get_odl_resource_list(odl_ip, odl_port, resource, datastore='config'): - url = format_odl_resource_list_url( - odl_ip, odl_port, resource, datastore=datastore) - return requests.get(url).json() - - -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() - - -def delete_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) - requests.delete(url) - - -def odl_acl_types_names(acl_json): - if len(acl_json['access-lists'].items()) == 0: - return [] - return [(acl['acl-type'], acl['acl-name']) - for acl in acl_json['access-lists']['acl']] - - -def format_odl_acl_list_url(odl_ip, odl_port, - odl_user='admin', odl_pwd='admin'): - acl_list_url = ('http://{usr}:{pwd}@{ip}:{port}/restconf/config/' - 'ietf-access-control-list:access-lists' - .format(usr=odl_user, pwd=odl_pwd, - ip=odl_ip, port=odl_port)) - return acl_list_url - - -def improve_json_layout(json_response): - return json.dumps(json_response, indent=4, separators=(',', ': ')) - - -def get_odl_items(odl_ip, odl_port): - acl_list_url = format_odl_acl_list_url(odl_ip, odl_port) - sf_list_url = format_odl_resource_list_url(odl_ip, odl_port, - "service-function") - sff_list_url = format_odl_resource_list_url(odl_ip, odl_port, - "service-function-forwarder") - sfc_list_url = format_odl_resource_list_url(odl_ip, odl_port, - "service-function-chain") - rsp_list_url = format_odl_resource_list_url(odl_ip, odl_port, - "rendered-service-path", - datastore="operational") - r_acl = requests.get(acl_list_url).json() - r_sf = requests.get(sf_list_url).json() - r_sff = requests.get(sff_list_url).json() - r_sfc = requests.get(sfc_list_url).json() - r_rsp = requests.get(rsp_list_url).json() - logger.debug("Configured ACLs in ODL: %s" % improve_json_layout(r_acl)) - logger.debug("Configured SFs in ODL: %s" % improve_json_layout(r_sf)) - logger.debug("Configured SFFs in ODL: %s" % improve_json_layout(r_sff)) - logger.debug("Configured SFCs in ODL: %s" % improve_json_layout(r_sfc)) - logger.debug("Configured RSPs in ODL: %s" % improve_json_layout(r_rsp)) - - -def get_odl_acl_list(odl_ip, odl_port): - acl_list_url = format_odl_acl_list_url(odl_ip, odl_port) - r = requests.get(acl_list_url) - return r.json() - - -def delete_odl_acl(odl_ip, odl_port, acl_type, acl_name): - acl_list_url = format_odl_acl_list_url(odl_ip, odl_port) - acl_url = '{0}/acl/{1}/{2}'.format(acl_list_url, acl_type, acl_name) - requests.delete(acl_url) - - -def delete_classifier_and_acl(tacker_client, clf_name, odl_ip, odl_port): - os_tacker.delete_sfc_classifier(tacker_client, sfc_clf_name=clf_name) - delete_odl_acl(odl_ip, - odl_port, - 'ietf-access-control-list:ipv4-acl', - clf_name) - - -def fill_installer_dict(installer_type): - default_string = "defaults.installer.{}.".format(installer_type) - installer_yaml_fields = { - "user": default_string+"user", - "password": default_string+"password", - "cluster": default_string+"cluster", - "pkey_file": default_string+"pkey_file" - } - return installer_yaml_fields - - -def register_vim(tacker_client, vim_file=None): - tmp_file = '/tmp/register-vim.json' - if vim_file is not 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.dump(json_dict, open(tmp_file, 'w')) - - os_tacker.create_vim(tacker_client, vim_file=tmp_file) - - -def get_neutron_interfaces(vm): - ''' - Get the interfaces of an instance - ''' - nova_client = os_utils.get_nova_client() - interfaces = nova_client.servers.interface_list(vm.id) - return interfaces - - -def get_client_port_id(vm): - ''' - Get the neutron port id of the client - ''' - interfaces = get_neutron_interfaces(vm) - if len(interfaces) > 1: - raise Exception("Client has more than one interface. Not expected!") - return interfaces[0].id diff --git a/sfc/tests/functest/sfc_one_chain_two_service_functions.py b/sfc/tests/functest/sfc_one_chain_two_service_functions.py index d2402a65..86fab534 100644 --- a/sfc/tests/functest/sfc_one_chain_two_service_functions.py +++ b/sfc/tests/functest/sfc_one_chain_two_service_functions.py @@ -12,12 +12,13 @@ import sys import threading import logging -import sfc.lib.openstack_tacker as os_tacker +import sfc.lib.openstack_utils as os_sfc_utils +import sfc.lib.odl_utils as odl_utils import functest.utils.openstack_utils as os_utils import opnfv.utils.ovs_logger as ovs_log import sfc.lib.config as sfc_config -import sfc.lib.utils as test_utils +import sfc.lib.test_utils as test_utils from sfc.lib.results import Results from opnfv.deployment.factory import Factory as DeploymentFactory import sfc.lib.topology_shuffler as topo_shuffler @@ -69,7 +70,7 @@ def main(): compute_nodes = [node for node in openstack_nodes if node.is_compute()] - odl_ip, odl_port = test_utils.get_odl_ip_port(openstack_nodes) + odl_ip, odl_port = odl_utils.get_odl_ip_port(openstack_nodes) for compute in compute_nodes: logger.info("This is a compute: %s" % compute.ip) @@ -94,7 +95,7 @@ def main(): glance_client = os_utils.get_glance_client() neutron_client = os_utils.get_neutron_client() nova_client = os_utils.get_nova_client() - tacker_client = os_tacker.get_tacker_client() + tacker_client = os_sfc_utils.get_tacker_client() controller_clients = test_utils.get_ssh_clients(controller_nodes) compute_clients = test_utils.get_ssh_clients(compute_nodes) @@ -109,15 +110,15 @@ def main(): COMMON_CONFIG.image_format, public='public') - network_id = test_utils.setup_neutron(neutron_client, - TESTCASE_CONFIG.net_name, - TESTCASE_CONFIG.subnet_name, - TESTCASE_CONFIG.router_name, - TESTCASE_CONFIG.subnet_cidr) + network_id = os_sfc_utils.setup_neutron(neutron_client, + TESTCASE_CONFIG.net_name, + TESTCASE_CONFIG.subnet_name, + TESTCASE_CONFIG.router_name, + TESTCASE_CONFIG.subnet_cidr) - sg_id = test_utils.create_security_groups(neutron_client, - TESTCASE_CONFIG.secgroup_name, - TESTCASE_CONFIG.secgroup_descr) + sg_id = os_sfc_utils.create_security_groups(neutron_client, + TESTCASE_CONFIG.secgroup_name, + TESTCASE_CONFIG.secgroup_descr) vnfs = ['testVNF1', 'testVNF2'] @@ -129,11 +130,11 @@ def main(): logger.info('Topology description: {0}' .format(testTopology['description'])) - client_instance = test_utils.create_instance( + client_instance = os_sfc_utils.create_instance( nova_client, CLIENT, COMMON_CONFIG.flavor, image_id, network_id, sg_id, av_zone=testTopology['client']) - server_instance = test_utils.create_instance( + server_instance = os_sfc_utils.create_instance( nova_client, SERVER, COMMON_CONFIG.flavor, image_id, network_id, sg_id, av_zone=testTopology['server']) @@ -142,20 +143,20 @@ def main(): server_ip = server_instance.networks.get(TESTCASE_CONFIG.net_name)[0] logger.info("Server instance received private ip [{}]".format(server_ip)) - test_utils.register_vim(tacker_client, vim_file=COMMON_CONFIG.vim_file) + os_sfc_utils.register_vim(tacker_client, vim_file=COMMON_CONFIG.vim_file) tosca_file = os.path.join(COMMON_CONFIG.sfc_test_dir, COMMON_CONFIG.vnfd_dir, TESTCASE_CONFIG.test_vnfd_red) - os_tacker.create_vnfd( + os_sfc_utils.create_vnfd( tacker_client, tosca_file=tosca_file, vnfd_name='test-vnfd1') tosca_file = os.path.join(COMMON_CONFIG.sfc_test_dir, COMMON_CONFIG.vnfd_dir, TESTCASE_CONFIG.test_vnfd_blue) - os_tacker.create_vnfd( + os_sfc_utils.create_vnfd( tacker_client, tosca_file=tosca_file, vnfd_name='test-vnfd2') @@ -164,39 +165,39 @@ def main(): COMMON_CONFIG.vnfd_dir, COMMON_CONFIG.vnfd_default_params_file) - test_utils.create_vnf_in_av_zone( + os_sfc_utils.create_vnf_in_av_zone( tacker_client, vnfs[0], 'test-vnfd1', 'test-vim', default_param_file, testTopology[vnfs[0]]) - test_utils.create_vnf_in_av_zone( + os_sfc_utils.create_vnf_in_av_zone( tacker_client, vnfs[1], 'test-vnfd2', 'test-vim', default_param_file, testTopology[vnfs[1]]) - vnf1_id = os_tacker.wait_for_vnf(tacker_client, vnf_name=vnfs[0]) - vnf2_id = os_tacker.wait_for_vnf(tacker_client, vnf_name=vnfs[1]) + vnf1_id = os_sfc_utils.wait_for_vnf(tacker_client, vnf_name=vnfs[0]) + vnf2_id = os_sfc_utils.wait_for_vnf(tacker_client, vnf_name=vnfs[1]) if vnf1_id is None or vnf2_id is None: logger.error('ERROR while booting vnfs') sys.exit(1) - vnf1_instance_id = test_utils.get_nova_id(tacker_client, 'VDU1', vnf1_id) + vnf1_instance_id = os_sfc_utils.get_nova_id(tacker_client, 'VDU1', vnf1_id) - vnf2_instance_id = test_utils.get_nova_id(tacker_client, 'VDU1', vnf2_id) + vnf2_instance_id = os_sfc_utils.get_nova_id(tacker_client, 'VDU1', vnf2_id) tosca_file = os.path.join(COMMON_CONFIG.sfc_test_dir, COMMON_CONFIG.vnffgd_dir, TESTCASE_CONFIG.test_vnffgd_red) - os_tacker.create_vnffgd(tacker_client, - tosca_file=tosca_file, - vnffgd_name='red') + os_sfc_utils.create_vnffgd(tacker_client, + tosca_file=tosca_file, + vnffgd_name='red') - neutron_port = test_utils.get_client_port_id(client_instance) - test_utils.create_vnffg_with_param_file(tacker_client, 'red', - 'red_http', - default_param_file, - neutron_port) + neutron_port = os_sfc_utils.get_client_port_id(client_instance) + os_sfc_utils.create_vnffg_with_param_file(tacker_client, 'red', + 'red_http', + default_param_file, + neutron_port) # Start measuring the time it takes to implement the classification rules - t1 = threading.Thread(target=test_utils.wait_for_classification_rules, + t1 = threading.Thread(target=odl_utils.wait_for_classification_rules, args=(ovs_logger, compute_nodes, odl_ip, odl_port,)) try: t1.start() @@ -204,13 +205,13 @@ def main(): logger.error("Unable to start the thread that counts time %s" % e) logger.info("Assigning floating IPs to instances") - server_floating_ip = test_utils.assign_floating_ip( + server_floating_ip = os_sfc_utils.assign_floating_ip( nova_client, neutron_client, server_instance.id) - client_floating_ip = test_utils.assign_floating_ip( + client_floating_ip = os_sfc_utils.assign_floating_ip( nova_client, neutron_client, client_instance.id) - sf1_floating_ip = test_utils.assign_floating_ip( + sf1_floating_ip = os_sfc_utils.assign_floating_ip( nova_client, neutron_client, vnf1_instance_id) - sf2_floating_ip = test_utils.assign_floating_ip( + sf2_floating_ip = os_sfc_utils.assign_floating_ip( nova_client, neutron_client, vnf2_instance_id) for ip in (server_floating_ip, @@ -220,8 +221,8 @@ def main(): logger.info("Checking connectivity towards floating IP [%s]" % ip) if not test_utils.ping(ip, retries=50, retry_timeout=3): logger.error("Cannot ping floating IP [%s]" % ip) - os_tacker.get_tacker_items() - test_utils.get_odl_items(odl_ip, odl_port) + os_sfc_utils.get_tacker_items() + odl_utils.get_odl_items(odl_ip, odl_port) sys.exit(1) logger.info("Successful ping to floating IP [%s]" % ip) diff --git a/sfc/tests/functest/sfc_symmetric_chain.py b/sfc/tests/functest/sfc_symmetric_chain.py index 1f3065e1..75ea43f1 100644 --- a/sfc/tests/functest/sfc_symmetric_chain.py +++ b/sfc/tests/functest/sfc_symmetric_chain.py @@ -14,7 +14,7 @@ import sys import threading import logging -import sfc.lib.openstack_tacker as os_tacker +import sfc.lib.openstack_utils as os_tacker import functest.utils.openstack_utils as os_utils import opnfv.utils.ovs_logger as ovs_log from opnfv.deployment.factory import Factory as DeploymentFactory diff --git a/sfc/tests/functest/sfc_two_chains_SSH_and_HTTP.py b/sfc/tests/functest/sfc_two_chains_SSH_and_HTTP.py index b1fe49a0..5a5645df 100644 --- a/sfc/tests/functest/sfc_two_chains_SSH_and_HTTP.py +++ b/sfc/tests/functest/sfc_two_chains_SSH_and_HTTP.py @@ -13,12 +13,13 @@ import sys import threading import logging -import sfc.lib.openstack_tacker as os_tacker +import sfc.lib.openstack_utils as os_sfc_utils +import sfc.lib.odl_utils as odl_utils import functest.utils.openstack_utils as os_utils import opnfv.utils.ovs_logger as ovs_log import sfc.lib.config as sfc_config -import sfc.lib.utils as test_utils +import sfc.lib.test_utils as test_utils from sfc.lib.results import Results from opnfv.deployment.factory import Factory as DeploymentFactory import sfc.lib.topology_shuffler as topo_shuffler @@ -67,7 +68,7 @@ def main(): compute_nodes = [node for node in openstack_nodes if node.is_compute()] - odl_ip, odl_port = test_utils.get_odl_ip_port(openstack_nodes) + odl_ip, odl_port = odl_utils.get_odl_ip_port(openstack_nodes) for compute in compute_nodes: logger.info("This is a compute: %s" % compute.ip) @@ -91,7 +92,7 @@ def main(): glance_client = os_utils.get_glance_client() neutron_client = os_utils.get_neutron_client() nova_client = os_utils.get_nova_client() - tacker_client = os_tacker.get_tacker_client() + tacker_client = os_sfc_utils.get_tacker_client() controller_clients = test_utils.get_ssh_clients(controller_nodes) compute_clients = test_utils.get_ssh_clients(compute_nodes) @@ -106,15 +107,15 @@ def main(): COMMON_CONFIG.image_format, public='public') - network_id = test_utils.setup_neutron(neutron_client, - TESTCASE_CONFIG.net_name, - TESTCASE_CONFIG.subnet_name, - TESTCASE_CONFIG.router_name, - TESTCASE_CONFIG.subnet_cidr) + network_id = os_sfc_utils.setup_neutron(neutron_client, + TESTCASE_CONFIG.net_name, + TESTCASE_CONFIG.subnet_name, + TESTCASE_CONFIG.router_name, + TESTCASE_CONFIG.subnet_cidr) - sg_id = test_utils.create_security_groups(neutron_client, - TESTCASE_CONFIG.secgroup_name, - TESTCASE_CONFIG.secgroup_descr) + sg_id = os_sfc_utils.create_security_groups(neutron_client, + TESTCASE_CONFIG.secgroup_name, + TESTCASE_CONFIG.secgroup_descr) vnf_names = ['testVNF1', 'testVNF2'] @@ -126,71 +127,71 @@ def main(): logger.info('Topology description: {0}' .format(testTopology['description'])) - client_instance = test_utils.create_instance( + client_instance = os_sfc_utils.create_instance( nova_client, CLIENT, COMMON_CONFIG.flavor, image_id, network_id, sg_id, av_zone=testTopology['client']) - server_instance = test_utils.create_instance( + server_instance = os_sfc_utils.create_instance( nova_client, SERVER, COMMON_CONFIG.flavor, image_id, network_id, sg_id, av_zone=testTopology['server']) server_ip = server_instance.networks.get(TESTCASE_CONFIG.net_name)[0] - test_utils.register_vim(tacker_client, vim_file=COMMON_CONFIG.vim_file) + os_sfc_utils.register_vim(tacker_client, vim_file=COMMON_CONFIG.vim_file) tosca_red = os.path.join(COMMON_CONFIG.sfc_test_dir, COMMON_CONFIG.vnfd_dir, TESTCASE_CONFIG.test_vnfd_red) - os_tacker.create_vnfd(tacker_client, - tosca_file=tosca_red, - vnfd_name='test-vnfd1') + os_sfc_utils.create_vnfd(tacker_client, + tosca_file=tosca_red, + vnfd_name='test-vnfd1') tosca_blue = os.path.join(COMMON_CONFIG.sfc_test_dir, COMMON_CONFIG.vnfd_dir, TESTCASE_CONFIG.test_vnfd_blue) - os_tacker.create_vnfd(tacker_client, - tosca_file=tosca_blue, - vnfd_name='test-vnfd2') + os_sfc_utils.create_vnfd(tacker_client, + tosca_file=tosca_blue, + vnfd_name='test-vnfd2') default_param_file = os.path.join( COMMON_CONFIG.sfc_test_dir, COMMON_CONFIG.vnfd_dir, COMMON_CONFIG.vnfd_default_params_file) - test_utils.create_vnf_in_av_zone( + os_sfc_utils.create_vnf_in_av_zone( tacker_client, vnf_names[0], 'test-vnfd1', 'test-vim', default_param_file, testTopology[vnf_names[0]]) - test_utils.create_vnf_in_av_zone( + os_sfc_utils.create_vnf_in_av_zone( tacker_client, vnf_names[1], 'test-vnfd2', 'test-vim', default_param_file, testTopology[vnf_names[1]]) - vnf1_id = os_tacker.wait_for_vnf(tacker_client, vnf_name=vnf_names[0]) - vnf2_id = os_tacker.wait_for_vnf(tacker_client, vnf_name=vnf_names[1]) + vnf1_id = os_sfc_utils.wait_for_vnf(tacker_client, vnf_name=vnf_names[0]) + vnf2_id = os_sfc_utils.wait_for_vnf(tacker_client, vnf_name=vnf_names[1]) if vnf1_id is None or vnf2_id is None: logger.error('ERROR while booting vnfs') sys.exit(1) - vnf1_instance_id = test_utils.get_nova_id(tacker_client, 'VDU1', vnf1_id) + vnf1_instance_id = os_sfc_utils.get_nova_id(tacker_client, 'VDU1', vnf1_id) - vnf2_instance_id = test_utils.get_nova_id(tacker_client, 'VDU1', vnf2_id) + vnf2_instance_id = os_sfc_utils.get_nova_id(tacker_client, 'VDU1', vnf2_id) tosca_file = os.path.join(COMMON_CONFIG.sfc_test_dir, COMMON_CONFIG.vnffgd_dir, TESTCASE_CONFIG.test_vnffgd_red) - os_tacker.create_vnffgd(tacker_client, - tosca_file=tosca_file, - vnffgd_name='red') + os_sfc_utils.create_vnffgd(tacker_client, + tosca_file=tosca_file, + vnffgd_name='red') - neutron_port = test_utils.get_client_port_id(client_instance) - test_utils.create_vnffg_with_param_file(tacker_client, 'red', - 'red_http', - default_param_file, - neutron_port) + neutron_port = os_sfc_utils.get_client_port_id(client_instance) + os_sfc_utils.create_vnffg_with_param_file(tacker_client, 'red', + 'red_http', + default_param_file, + neutron_port) # Start measuring the time it takes to implement the classification rules - t1 = threading.Thread(target=test_utils.wait_for_classification_rules, + t1 = threading.Thread(target=odl_utils.wait_for_classification_rules, args=(ovs_logger, compute_nodes, odl_ip, odl_port,)) try: @@ -199,13 +200,13 @@ def main(): logger.error("Unable to start the thread that counts time %s" % e) logger.info("Assigning floating IPs to instances") - server_floating_ip = test_utils.assign_floating_ip( + server_floating_ip = os_sfc_utils.assign_floating_ip( nova_client, neutron_client, server_instance.id) - client_floating_ip = test_utils.assign_floating_ip( + client_floating_ip = os_sfc_utils.assign_floating_ip( nova_client, neutron_client, client_instance.id) - sf1_floating_ip = test_utils.assign_floating_ip( + sf1_floating_ip = os_sfc_utils.assign_floating_ip( nova_client, neutron_client, vnf1_instance_id) - sf2_floating_ip = test_utils.assign_floating_ip( + sf2_floating_ip = os_sfc_utils.assign_floating_ip( nova_client, neutron_client, vnf2_instance_id) for ip in (server_floating_ip, @@ -215,8 +216,8 @@ def main(): logger.info("Checking connectivity towards floating IP [%s]" % ip) if not test_utils.ping(ip, retries=50, retry_timeout=3): logger.error("Cannot ping floating IP [%s]" % ip) - os_tacker.get_tacker_items() - test_utils.get_odl_items(odl_ip, odl_port) + os_sfc_utils.get_tacker_items() + odl_utils.get_odl_items(odl_ip, odl_port) sys.exit(1) logger.info("Successful ping to floating IP [%s]" % ip) @@ -260,25 +261,25 @@ def main(): logger.info("Changing the classification") - os_tacker.delete_vnffg(tacker_client, vnffg_name='red_http_works') + os_sfc_utils.delete_vnffg(tacker_client, vnffg_name='red_http_works') - os_tacker.delete_vnffgd(tacker_client, vnffgd_name='red') + os_sfc_utils.delete_vnffgd(tacker_client, vnffgd_name='red') tosca_file = os.path.join(COMMON_CONFIG.sfc_test_dir, COMMON_CONFIG.vnffgd_dir, TESTCASE_CONFIG.test_vnffgd_blue) - os_tacker.create_vnffgd(tacker_client, - tosca_file=tosca_file, - vnffgd_name='blue') + os_sfc_utils.create_vnffgd(tacker_client, + tosca_file=tosca_file, + vnffgd_name='blue') - test_utils.create_vnffg_with_param_file(tacker_client, 'blue', - 'blue_ssh', - default_param_file, - neutron_port) + os_sfc_utils.create_vnffg_with_param_file(tacker_client, 'blue', + 'blue_ssh', + default_param_file, + neutron_port) # Start measuring the time it takes to implement the classification rules - t2 = threading.Thread(target=test_utils.wait_for_classification_rules, + t2 = threading.Thread(target=odl_utils.wait_for_classification_rules, args=(ovs_logger, compute_nodes, odl_ip, odl_port,)) try: t2.start() -- cgit 1.2.3-korg