From 50ad0d757b2015067c2b13adbbe59b746477b207 Mon Sep 17 00:00:00 2001 From: gvrangan Date: Thu, 3 Jan 2019 11:32:11 +0000 Subject: Fix Two Chains Test and Enabled all Testcases - Method added to support Port Chain update - Used the new method to modify the test as follows - Create two Port Chains (one VNF per chain) - Block ssh in one vnf and http in the other - Test communication - Swap the flow classifiers in the chains so that ssh packets are sent to vnf where http is blocked and vice versa - Fix extracting odl username/password from ml2_conf - Checking flow classifiers are implemented - Fixed odl cleanup Change-Id: I1f0f3a3b829d6c73d1bb1a774ebf3484912b84b7 Signed-off-by: gvrangan --- sfc/lib/cleanup.py | 6 +- sfc/lib/odl_utils.py | 31 ++++-- sfc/lib/openstack_utils.py | 57 ++++++++++ sfc/tests/functest/config.yaml | 6 +- sfc/tests/functest/sfc_parent_function.py | 124 ++++++++++++++++++++-- sfc/tests/functest/sfc_two_chains_SSH_and_HTTP.py | 24 +++-- sfc/unit_tests/unit/lib/test_cleanup.py | 5 +- sfc/unit_tests/unit/lib/test_odl_utils.py | 26 ++++- sfc/unit_tests/unit/lib/test_openstack_utils.py | 91 ++++++++++++++++ 9 files changed, 335 insertions(+), 35 deletions(-) diff --git a/sfc/lib/cleanup.py b/sfc/lib/cleanup.py index 7ac7903a..214ff1cb 100644 --- a/sfc/lib/cleanup.py +++ b/sfc/lib/cleanup.py @@ -83,9 +83,9 @@ def delete_untracked_security_groups(): def cleanup_odl(odl_ip, odl_port): delete_odl_resources(odl_ip, odl_port, 'service-function-forwarder') - delete_odl_resources(odl_ip, odl_port, 'service-function-chain') - delete_odl_resources(odl_ip, odl_port, 'service-function-path') - delete_odl_resources(odl_ip, odl_port, 'service-function') + # delete_odl_resources(odl_ip, odl_port, 'service-function-chain') + # delete_odl_resources(odl_ip, odl_port, 'service-function-path') + # delete_odl_resources(odl_ip, odl_port, 'service-function') delete_odl_ietf_access_lists(odl_ip, odl_port) diff --git a/sfc/lib/odl_utils.py b/sfc/lib/odl_utils.py index 031aaa5f..459c83ec 100644 --- a/sfc/lib/odl_utils.py +++ b/sfc/lib/odl_utils.py @@ -10,7 +10,8 @@ import time import sfc.lib.openstack_utils as os_sfc_utils logger = logging.getLogger(__name__) - +odl_username = 'admin' +odl_password = 'admin' ODL_MODULE_EXCEPTIONS = { "service-function-path-state": "service-function-path" @@ -201,6 +202,11 @@ def wait_for_classification_rules(ovs_logger, compute_nodes, odl_ip, odl_port, time.sleep(3) while timeout > 0: + # When swapping classifiers promised_rsps update takes time to + # get updated + # TODO: Need to optimise this code + promised_rsps = promised_rsps_in_compute(odl_ip, odl_port, + neutron_ports) logger.info("RSPs in ODL Operational DataStore" "for compute '{}':".format(compute_name)) logger.info("{0}".format(promised_rsps)) @@ -246,6 +252,17 @@ def get_odl_ip_port(nodes): return ip, port +def get_odl_username_password(): + local_ml2_conf_file = os.path.join(os.getcwd(), 'ml2_conf.ini') + con_par = ConfigParser.RawConfigParser() + con_par.read(local_ml2_conf_file) + global odl_username + odl_username = con_par.get('ml2_odl', 'username') + global odl_password + odl_password = con_par.get('ml2_odl', 'password') + return odl_username, odl_password + + def pluralize(resource): plural = ODL_PLURAL_EXCEPTIONS.get(resource, None) if not plural: @@ -261,11 +278,11 @@ def get_module(resource): def format_odl_resource_list_url(odl_ip, odl_port, resource, - datastore='config', odl_user='admin', - odl_pwd='admin'): + datastore='config', odl_user=odl_username, + odl_pwd=odl_password): 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=get_module(resource), + .format(usr=odl_username, pwd=odl_password, ip=odl_ip, + port=odl_port, ds=datastore, rsrc=get_module(resource), rsrcs=pluralize(resource))) @@ -315,10 +332,10 @@ def odl_acl_types_names(acl_json): def format_odl_acl_list_url(odl_ip, odl_port, - odl_user='admin', odl_pwd='admin'): + odl_user=odl_username, odl_pwd=odl_password): acl_list_url = ('http://{usr}:{pwd}@{ip}:{port}/restconf/config/' 'ietf-access-control-list:access-lists' - .format(usr=odl_user, pwd=odl_pwd, + .format(usr=odl_username, pwd=odl_password, ip=odl_ip, port=odl_port)) return acl_list_url diff --git a/sfc/lib/openstack_utils.py b/sfc/lib/openstack_utils.py index 3d620e2b..8bc4543a 100644 --- a/sfc/lib/openstack_utils.py +++ b/sfc/lib/openstack_utils.py @@ -400,6 +400,31 @@ class OpenStackSFC: return port_pair_group_info['port_pair_group']['id'] + def create_classifier(self, neutron_port, port, protocol, fc_name, + symmetrical, server_port=None, server_ip=None): + ''' + Create the classifier + ''' + logger.info("Creating the classifier...") + + if symmetrical: + sfc_classifier_params = {'name': fc_name, + 'destination_ip_prefix': server_ip, + 'logical_source_port': neutron_port, + 'logical_destination_port': server_port, + 'destination_port_range_min': port, + 'destination_port_range_max': port, + 'protocol': protocol} + else: + sfc_classifier_params = {'name': fc_name, + 'logical_source_port': neutron_port, + 'destination_port_range_min': port, + 'destination_port_range_max': port, + 'protocol': protocol} + + fc_config = {'flow_classifier': sfc_classifier_params} + self.neutron_client.create_sfc_flow_classifier(fc_config) + def create_chain(self, port_groups, neutron_port, port, protocol, vnffg_name, symmetrical, server_port=None, server_ip=None): @@ -440,6 +465,38 @@ class OpenStackSFC: chain_config = {'port_chain': port_chain} return self.neutron_client.create_sfc_port_chain(chain_config) + def update_chain(self, vnffg_name, fc_name, symmetrical): + ''' + Update the new Flow Classifier ID + ''' + fc_id = self.neutron_client.find_resource('flow_classifier', + fc_name)['id'] + logger.info("Update the chain...") + port_chain = {} + port_chain['name'] = vnffg_name + '-port-chain' + port_chain['flow_classifiers'] = [] + port_chain['flow_classifiers'].append(fc_id) + if symmetrical: + port_chain['chain_parameters'] = {} + port_chain['chain_parameters']['symmetric'] = True + chain_config = {'port_chain': port_chain} + pc_id = self.neutron_client.find_resource('port_chain', + port_chain['name'])['id'] + return self.neutron_client.update_sfc_port_chain(pc_id, chain_config) + + def swap_classifiers(self, vnffg_1_name, vnffg_2_name, symmetric=False): + + ''' + Swap Classifiers + ''' + logger.info("Swap classifiers...") + + self.update_chain(vnffg_1_name, 'dummy', symmetric) + vnffg_1_classifier_name = vnffg_1_name + '-classifier' + self.update_chain(vnffg_2_name, vnffg_1_classifier_name, symmetric) + vnffg_2_classifier_name = vnffg_2_name + '-classifier' + self.update_chain(vnffg_1_name, vnffg_2_classifier_name, symmetric) + def delete_port_groups(self): ''' Delete all port groups and port pairs diff --git a/sfc/tests/functest/config.yaml b/sfc/tests/functest/config.yaml index d595f0cf..021b4c39 100644 --- a/sfc/tests/functest/config.yaml +++ b/sfc/tests/functest/config.yaml @@ -60,7 +60,7 @@ testcases: sfc_two_chains_SSH_and_HTTP: class_name: "SfcTwoChainsSSHandHTTP" - enabled: false + enabled: true order: 1 description: "ODL-SFC tests with two chains and one SF per chain" net_name: example-net @@ -84,7 +84,7 @@ testcases: sfc_symmetric_chain: class_name: "SfcSymmetricChain" - enabled: false + enabled: true order: 2 description: "Verify the behavior of a symmetric service chain" net_name: example-net @@ -106,7 +106,7 @@ testcases: sfc_chain_deletion: class_name: "SfcChainDeletion" - enabled: false + enabled: true order: 3 description: "Verify if chains work correctly after deleting one" net_name: example-net diff --git a/sfc/tests/functest/sfc_parent_function.py b/sfc/tests/functest/sfc_parent_function.py index 9558eb8c..40d5d1a1 100644 --- a/sfc/tests/functest/sfc_parent_function.py +++ b/sfc/tests/functest/sfc_parent_function.py @@ -154,6 +154,7 @@ class SfcCommonTestCase(object): self.creators = openstack_sfc.creators self.odl_ip, self.odl_port = odl_utils.get_odl_ip_port(openstack_nodes) + odl_utils.get_odl_username_password() self.default_param_file = os.path.join( COMMON_CONFIG.sfc_test_dir, @@ -267,6 +268,9 @@ class SfcCommonTestCase(object): port_security=False) self.vnf_objects[vnf_name] = [vnf_instance, vnf_port] + logger.info("Creating VNF with name...%s", vnf_name) + logger.info("Port associated with VNF...%s", + self.vnf_objects[vnf_name][1]) def assign_floating_ip_client_server(self): """Assign floating IPs on the router about server and the client @@ -403,14 +407,43 @@ class SfcCommonTestCase(object): openstack_sfc.delete_chain() openstack_sfc.delete_port_groups() + def create_classifier(self, fc_name, port=85, + protocol='tcp', symmetric=False): + """Create the classifier component following the instructions from + relevant templates. + + :param fc_name: The name of the classifier + :param port: Input port number + :param protocol: Input protocol + :param symmetric: Check symmetric + :return: Create the classifier component + """ + + logger.info("Creating the classifier...") + + self.neutron_port = self.port_client + if COMMON_CONFIG.mano_component == 'no-mano': + openstack_sfc.create_classifier(self.neutron_port.id, + port, + protocol, + fc_name, + symmetric) + + elif COMMON_CONFIG.mano_component == 'tacker': + logger.info("Creating classifier with tacker is not supported") + def create_vnffg(self, testcase_config_name, vnffgd_name, vnffg_name, - port=80, protocol='tcp', symmetric=False): + port=80, protocol='tcp', symmetric=False, vnf_index=-1): """Create the vnffg components following the instructions from relevant templates. :param testcase_config_name: The config input of the test case :param vnffgd_name: The name of the vnffgd template :param vnffg_name: The name for the vnffg + :param port: Input port number + :param protocol: Input protocol + :param symmetric: Check symmetric + :param vnf_index: Index to specify vnf :return: Create the vnffg component """ @@ -448,9 +481,29 @@ class SfcCommonTestCase(object): self.neutron_port.id) elif COMMON_CONFIG.mano_component == 'no-mano': + logger.info("Creating the vnffg without any mano component...") port_groups = [] - for vnf in self.vnfs: - # vnf_instance is in [0] and vnf_port in [1] + if vnf_index == -1: + for vnf in self.vnfs: + # vnf_instance is in [0] and vnf_port in [1] + vnf_instance = self.vnf_objects[vnf][0] + vnf_port = self.vnf_objects[vnf][1] + if symmetric: + # VNFs have two ports + neutron_port1 = vnf_port[0] + neutron_port2 = vnf_port[1] + neutron_ports = [neutron_port1, neutron_port2] + else: + neutron_port1 = vnf_port[0] + neutron_ports = [neutron_port1] + + port_group = \ + openstack_sfc.create_port_groups(neutron_ports, + vnf_instance) + port_groups.append(port_group) + + else: + vnf = self.vnfs[vnf_index] vnf_instance = self.vnf_objects[vnf][0] vnf_port = self.vnf_objects[vnf][1] if symmetric: @@ -462,10 +515,9 @@ class SfcCommonTestCase(object): neutron_port1 = vnf_port[0] neutron_ports = [neutron_port1] - port_group = \ - openstack_sfc.create_port_groups(neutron_ports, - vnf_instance) - port_groups.append(port_group) + port_group = openstack_sfc.create_port_groups( + neutron_ports, vnf_instance) + port_groups.append(port_group) self.neutron_port = self.port_client @@ -486,6 +538,64 @@ class SfcCommonTestCase(object): port, protocol, vnffg_name, symmetric) + def update_vnffg(self, testcase_config_name, vnffgd_name, vnffg_name, + port=80, protocol='tcp', symmetric=False, + vnf_index=0, fc_name='red'): + """Update the vnffg components following the instructions from + relevant templates. + + :param testcase_config_name: The config input of the test case + :param vnffgd_name: The name of the vnffgd template + :param vnffg_name: The name for the vnffg + :param port: To input port number + :param protocol: To input protocol + :param symmetric: To check symmetric + :param vnf_index: Index to identify vnf + :param fc_name: The name of the flow classifier + :return: Update the vnffg component + """ + + logger.info("Update the vnffg...") + + if COMMON_CONFIG.mano_component == 'no-mano': + port_groups = [] + for vnf in self.vnfs: + # vnf_instance is in [0] and vnf_port in [1] + vnf_instance = self.vnf_objects[vnf][0] + vnf_port = self.vnf_objects[vnf][1] + if symmetric: + # VNFs have two ports + neutron_port1 = vnf_port[0] + neutron_port2 = vnf_port[1] + neutron_ports = [neutron_port1, neutron_port2] + else: + neutron_port1 = vnf_port[0] + neutron_ports = [neutron_port1] + + port_group = \ + openstack_sfc.create_port_groups(neutron_ports, + vnf_instance) + port_groups.append(port_group) + + openstack_sfc.update_chain(vnffg_name, fc_name, symmetric) + + elif COMMON_CONFIG.mano_component == 'tacker': + logger.info("update for tacker is not supported") + + def swap_classifiers(self, vnffg_1_name, vnffg_2_name, symmetric=False): + """Interchange classifiers between port chains + + :param vnffg_1_name: Reference to port_chain_1 + :param vnffg_2_name: Reference to port_chain_2 + :param symmetric: To check symmetric + :return: Interchange the classifiers + """ + + if COMMON_CONFIG.mano_component == 'no-mano': + openstack_sfc.swap_classifiers(vnffg_1_name, + vnffg_2_name, + symmetric=False) + def present_results_http(self): """Check whether the connection between server and client using HTTP protocol is blocked or not. 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 0cfbea22..92c2711e 100644 --- a/sfc/tests/functest/sfc_two_chains_SSH_and_HTTP.py +++ b/sfc/tests/functest/sfc_two_chains_SSH_and_HTTP.py @@ -40,8 +40,15 @@ class SfcTwoChainsSSHandHTTP(sfc_parent_function.SfcCommonTestCase): self.create_vnf(self.vnfs[0], 'test-vnfd1', 'test-vim') self.create_vnf(self.vnfs[1], 'test-vnfd2', 'test-vim') + logger.info("Call Parent create_vnffg with index") self.create_vnffg(self.testcase_config.test_vnffgd_red, 'red', - 'red_http', port=80, protocol='tcp', symmetric=False) + 'red_http', port=80, protocol='tcp', + symmetric=False, vnf_index=0) + + self.create_vnffg(self.testcase_config.test_vnffgd_blue, 'blue', + 'blue_ssh', port=22, protocol='tcp', + symmetric=False, vnf_index=1) + self.create_classifier('dummy') t1 = threading.Thread(target=odl_utils.wait_for_classification_rules, args=(self.ovs_logger, self.compute_nodes, @@ -60,21 +67,18 @@ class SfcTwoChainsSSHandHTTP(sfc_parent_function.SfcCommonTestCase): self.check_floating_ips() self.start_services_in_vm() - self.vxlan_blocking_start(self.fips_sfs[0], "22") - self.vxlan_blocking_start(self.fips_sfs[1], "80") + self.vxlan_blocking_start(self.fips_sfs[0], "80") + self.vxlan_blocking_start(self.fips_sfs[1], "22") logger.info("Wait for ODL to update the classification rules in OVS") t1.join() results = self.present_results_ssh() - results = self.present_results_allowed_http() + results = self.present_results_http() logger.info("Changing the classification") - self.remove_vnffg('red_http', 'red') - - self.create_vnffg(self.testcase_config.test_vnffgd_blue, 'blue', - 'blue_ssh') + self.swap_classifiers('red_http', 'blue_ssh') # Start measuring the time it takes to implement the classification # rules @@ -82,7 +86,7 @@ class SfcTwoChainsSSHandHTTP(sfc_parent_function.SfcCommonTestCase): args=(self.ovs_logger, self.compute_nodes, self.odl_ip, self.odl_port, self.client_instance.hypervisor_hostname, - self.neutron_port,)) + [self.neutron_port],)) try: t2.start() except Exception as e: @@ -91,7 +95,7 @@ class SfcTwoChainsSSHandHTTP(sfc_parent_function.SfcCommonTestCase): logger.info("Wait for ODL to update the classification rules in OVS") t2.join() - results = self.present_results_http() + results = self.present_results_allowed_http() results = self.present_results_allowed_ssh() if __name__ == '__main__': diff --git a/sfc/unit_tests/unit/lib/test_cleanup.py b/sfc/unit_tests/unit/lib/test_cleanup.py index 8e68ce5b..e6f59d23 100644 --- a/sfc/unit_tests/unit/lib/test_cleanup.py +++ b/sfc/unit_tests/unit/lib/test_cleanup.py @@ -275,10 +275,7 @@ class SfcCleanupTesting(unittest.TestCase): def test_cleanup_odl(self, mock_del_odl_ietf, mock_del_odl_res): - resources = ['service-function-forwarder', - 'service-function-chain', - 'service-function-path', - 'service-function'] + resources = ['service-function-forwarder'] odl_res_calls = [call(self.odl_ip, self.odl_port, item) for item in resources] diff --git a/sfc/unit_tests/unit/lib/test_odl_utils.py b/sfc/unit_tests/unit/lib/test_odl_utils.py index 04eeeff2..1dfcf1ed 100644 --- a/sfc/unit_tests/unit/lib/test_odl_utils.py +++ b/sfc/unit_tests/unit/lib/test_odl_utils.py @@ -331,11 +331,35 @@ class SfcOdlUtilsTesting(unittest.TestCase): '/etc/ml2_conf.ini') mock_rawconfigparser.return_value.read.assert_called_once_with( '/etc/ml2_conf.ini') - mock_rawconfigparser.return_value.get.assert_called_once_with( + mock_rawconfigparser.return_value.get.assert_called_with( 'ml2_odl', 'url') mock_search.assert_called_once_with(r'[0-9]+(?:\.[0-9]+){3}\:[0-9]+', 'config') + @patch('re.search', autospec=True) + @patch('ConfigParser.RawConfigParser', autospec=True) + @patch('os.getcwd', autospec=True, return_value='/etc') + @patch('os.path.join', autospec=True, return_value='/etc/ml2_conf.ini') + def test_get_odl_username_password(self, mock_join, + mock_getcwd, + mock_rawconfigparser, + mock_search): + """ + Check the proper functionality of get odl_username_password + function + """ + + mock_rawconfigparser.return_value.get.return_value = 'odl_username' + result = odl_utils.get_odl_username_password() + self.assertEqual(('odl_username'), result[0]) + mock_getcwd.assert_called_once_with() + mock_join.assert_called_once_with('/etc', 'ml2_conf.ini') + mock_rawconfigparser.return_value.read.assert_called_once_with( + '/etc/ml2_conf.ini') + mock_rawconfigparser.return_value.get.return_value = 'odl_password' + result = odl_utils.get_odl_username_password() + self.assertEqual(('odl_password'), result[1]) + def test_pluralize(self): """ Checks the proper functionality of pluralize diff --git a/sfc/unit_tests/unit/lib/test_openstack_utils.py b/sfc/unit_tests/unit/lib/test_openstack_utils.py index ffaace68..8915c45d 100644 --- a/sfc/unit_tests/unit/lib/test_openstack_utils.py +++ b/sfc/unit_tests/unit/lib/test_openstack_utils.py @@ -1033,6 +1033,64 @@ class SfcOpenStackUtilsTesting(unittest.TestCase): [call({'port_pair_group': expected_port_pair_gr})]) mock_log.info.assert_has_calls(log_calls_info) + @patch('sfc.lib.openstack_utils.logger', autospec=True) + def test_create_classifier(self, mock_log): + """ + Checks the create_classifier method + """ + + log_calls = [call('Creating the classifier...')] + neutron_port = 'neutron_port_id' + port = 80 + protocol = 'tcp' + fc_name = 'red_http' + symmetrical = False + self.neutron_client.create_sfc_flow_classifier.return_value = \ + {'flow_classifier': {'id': 'fc_id'}} + + expected_sfc_classifier_params = {'name': fc_name, + 'logical_source_port': neutron_port, + 'destination_port_range_min': port, + 'destination_port_range_max': port, + 'protocol': protocol} + self.os_sfc.create_classifier(neutron_port, port, + protocol, fc_name, symmetrical) + self.neutron_client.create_sfc_flow_classifier.assert_has_calls( + [call({'flow_classifier': expected_sfc_classifier_params})]) + mock_log.info.assert_has_calls(log_calls) + + @patch('sfc.lib.openstack_utils.logger', autospec=True) + def test_create_classifier_symmetric(self, mock_log): + """ + Checks the create_chain method + """ + + log_calls = [call('Creating the classifier...')] + neutron_port = 'neutron_port_id' + port = 80 + protocol = 'tcp' + fc_name = 'red_http' + symmetrical = True + serv_p = '123' + server_ip = '1.1.1.2' + self.neutron_client.create_sfc_flow_classifier.return_value = \ + {'flow_classifier': {'id': 'fc_id'}} + + expected_sfc_classifier_params = {'name': fc_name, + 'logical_source_port': neutron_port, + 'destination_port_range_min': port, + 'destination_port_range_max': port, + 'destination_ip_prefix': server_ip, + 'logical_destination_port': serv_p, + 'protocol': protocol} + self.os_sfc.create_classifier(neutron_port, port, + protocol, fc_name, symmetrical, + server_port='123', + server_ip='1.1.1.2') + self.neutron_client.create_sfc_flow_classifier.assert_has_calls( + [call({'flow_classifier': expected_sfc_classifier_params})]) + mock_log.info.assert_has_calls(log_calls) + @patch('sfc.lib.openstack_utils.logger', autospec=True) def test_create_chain(self, mock_log): """ @@ -1115,6 +1173,39 @@ class SfcOpenStackUtilsTesting(unittest.TestCase): [call({'port_chain': expected_chain_config})]) mock_log.info.assert_has_calls(log_calls) + @patch('sfc.lib.openstack_utils.logger', autospec=True) + def test_update_chain_symmetric(self, mock_log): + """ + Checks the update_chain method + """ + + log_calls = [call('Update the chain...')] + vnffg_name = 'red_http' + fc_name = 'blue_ssh' + symmetrical = True + self.neutron_client.find_resource.return_value = \ + {'id': 'fc_id'} + expected_chain_config = {'name': vnffg_name + '-port-chain', + 'flow_classifiers': ['fc_id'], + 'chain_parameters': {'symmetric': True}} + self.os_sfc.update_chain(vnffg_name, fc_name, symmetrical) + self.neutron_client.update_sfc_port_chain.assert_has_calls( + [call('fc_id', {'port_chain': expected_chain_config})]) + mock_log.info.assert_has_calls(log_calls) + + @patch('sfc.lib.openstack_utils.logger', autospec=True) + def test_swap_classifiers(self, mock_log): + """ + Checks the swap_classifiers method + """ + + log_calls = [call('Swap classifiers...')] + vnffg_1_name = 'red_http' + vnffg_2_name = 'blue_ssh' + symmetrical = False + self.os_sfc.swap_classifiers(vnffg_1_name, vnffg_2_name, symmetrical) + mock_log.info.assert_has_calls(log_calls) + @patch('sfc.lib.openstack_utils.logger', autospec=True) def test_delete_port_groups(self, mock_log): """ -- cgit 1.2.3-korg