diff options
-rw-r--r-- | conf/integration/01_testcases.conf | 168 | ||||
-rw-r--r-- | core/component_factory.py | 3 | ||||
-rw-r--r-- | core/traffic_controller_rfc2544.py | 13 | ||||
-rw-r--r-- | core/vswitch_controller_clean.py | 79 | ||||
-rw-r--r-- | core/vswitch_controller_p2p.py | 4 | ||||
-rwxr-xr-x | docs/userguide/integration.rst | 22 | ||||
-rw-r--r-- | src/ovs/ofctl.py | 24 | ||||
-rw-r--r-- | testcases/__init__.py | 2 | ||||
-rw-r--r-- | testcases/integration.py | 173 | ||||
-rw-r--r-- | testcases/performance.py | 38 | ||||
-rw-r--r-- | testcases/testcase.py | 106 | ||||
-rw-r--r-- | tools/report/report.py | 18 | ||||
-rwxr-xr-x | vsperf | 24 | ||||
-rw-r--r-- | vswitches/ovs.py | 229 | ||||
-rw-r--r-- | vswitches/ovs_dpdk_vhost.py | 130 | ||||
-rw-r--r-- | vswitches/ovs_vanilla.py | 129 |
16 files changed, 864 insertions, 298 deletions
diff --git a/conf/integration/01_testcases.conf b/conf/integration/01_testcases.conf index 5e9fc66d..fff148d4 100644 --- a/conf/integration/01_testcases.conf +++ b/conf/integration/01_testcases.conf @@ -27,6 +27,16 @@ SUPPORTED_TUNNELING_PROTO = ['vxlan', 'gre', 'geneve'] # # biDirectional testing for OP2P is not yet supported. # biDirectional must be set to False. +# +# "TestSteps": [] # Definition of integration test steps. +# # In case that this list is defined, then +# # vsperf will execute defined test steps +# # one by one. It can be used to configure +# # vswitch, insert flows and transmit traffic. +# # It is possible to refer to result of any +# # previous step through #STEP[i][j] macro. +# # Where i is a number of step (starts from 0) +# # and j is index of result returned by step i. INTEGRATION_TESTS = [ { @@ -65,5 +75,163 @@ INTEGRATION_TESTS = [ "Tunnel Operation": "decapsulation", "Description": "Overlay Decapsulation Continuous Stream", }, + { + "Name": "vswitch_add_del_bridge", + "Deployment": "clean", + "Description": "vSwitch - add and delete bridge", + "TestSteps": [ + ['vswitch', 'add_switch', 'int_br0'], + ['vswitch', 'del_switch', 'int_br0'], + ] + }, + { + "Name": "vswitch_add_del_bridges", + "Deployment": "clean", + "Description": "vSwitch - add and delete bridges", + "TestSteps": [ + ['vswitch', 'add_switch', 'int_br0'], + ['vswitch', 'add_switch', 'int_br1'], + ['vswitch', 'del_switch', 'int_br0'], + ['vswitch', 'del_switch', 'int_br1'], + ] + }, + { + "Name": "vswitch_add_del_phy_port", + "Deployment": "clean", + "Description": "vSwitch - add and delete physical port", + "TestSteps": [ + ['vswitch', 'add_switch', 'int_br0'], + ['vswitch', 'add_phy_port', 'int_br0'], + ['vswitch', 'del_port', 'int_br0', '#STEP[1][0]'], + ['vswitch', 'del_switch', 'int_br0'], + ] + }, + { + "Name": "vswitch_add_del_phy_ports", + "Deployment": "clean", + "Description": "vSwitch - add and delete physical ports", + "TestSteps": [ + ['vswitch', 'add_switch', 'int_br0'], + ['vswitch', 'add_phy_port', 'int_br0'], + ['vswitch', 'add_phy_port', 'int_br0'], + ['vswitch', 'del_port', 'int_br0', '#STEP[1][0]'], + ['vswitch', 'del_port', 'int_br0', '#STEP[2][0]'], + ['vswitch', 'del_switch', 'int_br0'], + ] + }, + { + "Name": "vswitch_add_del_vport", + "Deployment": "clean", + "Description": "vSwitch - add and delete virtual port", + "TestSteps": [ + ['vswitch', 'add_switch', 'int_br0'], + ['vswitch', 'add_vport', 'int_br0'], + ['vswitch', 'del_port', 'int_br0', '#STEP[1][0]'], + ['vswitch', 'del_switch', 'int_br0'], + ] + }, + { + "Name": "vswitch_add_del_vports", + "Deployment": "clean", + "Description": "vSwitch - add and delete virtual ports", + "TestSteps": [ + ['vswitch', 'add_switch', 'int_br0'], + ['vswitch', 'add_vport', 'int_br0'], + ['vswitch', 'add_vport', 'int_br0'], + ['vswitch', 'del_port', 'int_br0', '#STEP[1][0]'], + ['vswitch', 'del_port', 'int_br0', '#STEP[2][0]'], + ['vswitch', 'del_switch', 'int_br0'], + ] + }, + { + "Name": "vswitch_add_del_flow", + "Deployment": "clean", + "Description": "vSwitch - add and delete flow", + "TestSteps": [ + ['vswitch', 'add_switch', 'int_br0'], + ['vswitch', 'add_phy_port', 'int_br0'], + ['vswitch', 'add_phy_port', 'int_br0'], + ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[1][1]', 'actions': ['output:#STEP[2][1]'], 'idle_timeout': '0'}], + ['vswitch', 'del_flow', 'int_br0', {'in_port': '#STEP[1][1]'}], + ['vswitch', 'del_port', 'int_br0', '#STEP[1][0]'], + ['vswitch', 'del_port', 'int_br0', '#STEP[2][0]'], + ['vswitch', 'del_switch', 'int_br0'], + ] + }, + { + "Name": "vswitch_add_del_flows", + "Deployment": "clean", + "Description": "vSwitch - add and delete flows", + "TestSteps": [ + ['vswitch', 'add_switch', 'int_br0'], + ['vswitch', 'add_phy_port', 'int_br0'], + ['vswitch', 'add_phy_port', 'int_br0'], + ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[1][1]', 'actions': ['output:#STEP[2][1]'], 'idle_timeout': '0'}], + ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[2][1]', 'actions': ['output:#STEP[1][1]'], 'idle_timeout': '0'}], + ['vswitch', 'dump_flows', 'int_br0'], + ['vswitch', 'del_flow', 'int_br0', {'in_port': '#STEP[1][1]'}], + ['vswitch', 'del_flow', 'int_br0', {'in_port': '#STEP[2][1]'}], + ['vswitch', 'del_port', 'int_br0', '#STEP[1][0]'], + ['vswitch', 'del_port', 'int_br0', '#STEP[2][0]'], + ['vswitch', 'del_switch', 'int_br0'], + ] + }, + { + "Name": "vswitch_throughput", + "Deployment": "clean", + "Description": "vSwitch - configure switch and execute RFC2544 throughput test", + "TestSteps": [ + ['vswitch', 'add_switch', 'int_br0'], + ['vswitch', 'add_phy_port', 'int_br0'], + ['vswitch', 'add_phy_port', 'int_br0'], + ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[1][1]', 'actions': ['output:#STEP[2][1]'], 'idle_timeout': '0'}], + ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[2][1]', 'actions': ['output:#STEP[1][1]'], 'idle_timeout': '0'}], + ['trafficgen', 'send_traffic', {'traffic_type' : 'throughput', 'bidir' : True, 'frame_rate' : 100, 'multistream' : 0, 'stream_type' : 'L4'}], + ['vswitch', 'dump_flows', 'int_br0'], + ['vswitch', 'del_flow', 'int_br0', {'in_port': '#STEP[1][1]'}], + ['vswitch', 'del_flow', 'int_br0', {'in_port': '#STEP[2][1]'}], + ['vswitch', 'del_port', 'int_br0', '#STEP[1][0]'], + ['vswitch', 'del_port', 'int_br0', '#STEP[2][0]'], + ['vswitch', 'del_switch', 'int_br0'], + ] + }, + { + "Name": "vswitch_back2back", + "Deployment": "clean", + "Description": "vSwitch - configure switch and execute RFC2544 back2back test", + "TestSteps": [ + ['vswitch', 'add_switch', 'int_br0'], + ['vswitch', 'add_phy_port', 'int_br0'], + ['vswitch', 'add_phy_port', 'int_br0'], + ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[1][1]', 'actions': ['output:#STEP[2][1]'], 'idle_timeout': '0'}], + ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[2][1]', 'actions': ['output:#STEP[1][1]'], 'idle_timeout': '0'}], + ['trafficgen', 'send_traffic', {'traffic_type' : 'back2back', 'bidir' : True, 'frame_rate' : 100, 'multistream' : 0, 'stream_type' : 'L4'}], + ['vswitch', 'dump_flows', 'int_br0'], + ['vswitch', 'del_flow', 'int_br0', {'in_port': '#STEP[1][1]'}], + ['vswitch', 'del_flow', 'int_br0', {'in_port': '#STEP[2][1]'}], + ['vswitch', 'del_port', 'int_br0', '#STEP[1][0]'], + ['vswitch', 'del_port', 'int_br0', '#STEP[2][0]'], + ['vswitch', 'del_switch', 'int_br0'], + ] + }, + { + "Name": "vswitch_continuous", + "Deployment": "clean", + "Description": "vSwitch - configure switch and execute continuous stream test", + "TestSteps": [ + ['vswitch', 'add_switch', 'int_br0'], + ['vswitch', 'add_phy_port', 'int_br0'], + ['vswitch', 'add_phy_port', 'int_br0'], + ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[1][1]', 'actions': ['output:#STEP[2][1]'], 'idle_timeout': '0'}], + ['vswitch', 'add_flow', 'int_br0', {'in_port': '#STEP[2][1]', 'actions': ['output:#STEP[1][1]'], 'idle_timeout': '0'}], + ['trafficgen', 'send_traffic', {'traffic_type' : 'continuous', 'bidir' : True, 'frame_rate' : 100, 'multistream' : 0, 'stream_type' : 'L4'}], + ['vswitch', 'dump_flows', 'int_br0'], + ['vswitch', 'del_flow', 'int_br0', {'in_port': '#STEP[1][1]'}], + ['vswitch', 'del_flow', 'int_br0', {'in_port': '#STEP[2][1]'}], + ['vswitch', 'del_port', 'int_br0', '#STEP[1][0]'], + ['vswitch', 'del_port', 'int_br0', '#STEP[2][0]'], + ['vswitch', 'del_switch', 'int_br0'], + ] + }, ] diff --git a/core/component_factory.py b/core/component_factory.py index cb5af211..9c58fc5c 100644 --- a/core/component_factory.py +++ b/core/component_factory.py @@ -16,6 +16,7 @@ """ from core.traffic_controller_rfc2544 import TrafficControllerRFC2544 +from core.vswitch_controller_clean import VswitchControllerClean from core.vswitch_controller_p2p import VswitchControllerP2P from core.vswitch_controller_pvp import VswitchControllerPVP from core.vswitch_controller_pvvp import VswitchControllerPVVP @@ -72,6 +73,8 @@ def create_vswitch(deployment_scenario, vswitch_class, traffic, return VswitchControllerPVVP(vswitch_class, traffic) elif deployment_scenario.find("op2p") >= 0: return VswitchControllerOP2P(vswitch_class, traffic, tunnel_operation) + elif deployment_scenario.find("clean") >= 0: + return VswitchControllerClean(vswitch_class, traffic) def create_vnf(deployment_scenario, vnf_class): diff --git a/core/traffic_controller_rfc2544.py b/core/traffic_controller_rfc2544.py index 020b4aec..2630101f 100644 --- a/core/traffic_controller_rfc2544.py +++ b/core/traffic_controller_rfc2544.py @@ -154,3 +154,16 @@ class TrafficControllerRFC2544(ITrafficController, IResults): """IResult interface implementation. """ return self._results + + def validate_send_traffic(self, result, traffic): + """Verify that send traffic has succeeded + """ + if len(self._results): + if 'b2b_frames' in self._results[-1]: + return float(self._results[-1]['b2b_frames']) > 0 + elif 'throughput_rx_fps' in self._results[-1]: + return float(self._results[-1]['throughput_rx_fps']) > 0 + else: + return True + else: + return False diff --git a/core/vswitch_controller_clean.py b/core/vswitch_controller_clean.py new file mode 100644 index 00000000..61724b9b --- /dev/null +++ b/core/vswitch_controller_clean.py @@ -0,0 +1,79 @@ +# Copyright 2015-2016 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""VSwitch controller for basic initialization of vswitch +""" + +import logging + +from core.vswitch_controller import IVswitchController + +class VswitchControllerClean(IVswitchController): + """VSwitch controller for Clean deployment scenario. + + Attributes: + _vswitch_class: The vSwitch class to be used. + _vswitch: The vSwitch object controlled by this controller + _deployment_scenario: A string describing the scenario to set-up in the + constructor. + """ + def __init__(self, vswitch_class, traffic): + """Initializes up the prerequisites for the Clean deployment scenario. + + :vswitch_class: the vSwitch class to be used. + """ + self._logger = logging.getLogger(__name__) + self._vswitch_class = vswitch_class + self._vswitch = vswitch_class() + self._deployment_scenario = "Clean" + self._logger.debug('Creation using ' + str(self._vswitch_class)) + self._traffic = traffic.copy() + + def setup(self): + """Sets up the switch for Clean. + """ + self._logger.debug('Setup using ' + str(self._vswitch_class)) + + try: + self._vswitch.start() + except: + self._vswitch.stop() + raise + + def stop(self): + """Tears down the switch created in setup(). + """ + self._logger.debug('Stop using ' + str(self._vswitch_class)) + self._vswitch.stop() + + def __enter__(self): + self.setup() + + def __exit__(self, type_, value, traceback): + self.stop() + + def get_vswitch(self): + """See IVswitchController for description + """ + return self._vswitch + + def get_ports_info(self): + """See IVswitchController for description + """ + pass + + def dump_vswitch_flows(self): + """See IVswitchController for description + """ + pass diff --git a/core/vswitch_controller_p2p.py b/core/vswitch_controller_p2p.py index 91c4e8a0..e9ab5cc4 100644 --- a/core/vswitch_controller_p2p.py +++ b/core/vswitch_controller_p2p.py @@ -81,12 +81,12 @@ class VswitchControllerP2P(IVswitchController): flow = flow_template.copy() flow.update({'table':'1', 'priority':'1', 'in_port':'1', - 'actions': ['write_actions(output:2)', 'write_metadata:2', + 'actions': ['write_actions(output:2)', 'write_metadata:0x2', 'goto_table:2']}) self.process_flow_template(bridge, flow) flow = flow_template.copy() flow.update({'table':'1', 'priority':'1', 'in_port':'2', - 'actions': ['write_actions(output:1)', 'write_metadata:1', + 'actions': ['write_actions(output:1)', 'write_metadata:0x1', 'goto_table:2']}) self.process_flow_template(bridge, flow) diff --git a/docs/userguide/integration.rst b/docs/userguide/integration.rst index dc871280..27bf2cd0 100755 --- a/docs/userguide/integration.rst +++ b/docs/userguide/integration.rst @@ -6,7 +6,7 @@ Integration tests ================= VSPERF includes a set of integration tests defined in conf/integration. -These tests can be run by specifying --run-integration as a parameter to vsperf. +These tests can be run by specifying --integration as a parameter to vsperf. Current tests in conf/integration are Overlay tests. VSPERF supports VXLAN, GRE and GENEVE tunneling protocols. @@ -59,21 +59,21 @@ To run VXLAN encapsulation tests: .. code-block:: console - ./vsperf --conf-file user_settings.py --run-integration + ./vsperf --conf-file user_settings.py --integration --test-params 'tunnel_type=vxlan' overlay_p2p_tput To run GRE encapsulation tests: .. code-block:: console - ./vsperf --conf-file user_settings.py --run-integration + ./vsperf --conf-file user_settings.py --integration --test-params 'tunnel_type=gre' overlay_p2p_tput To run GENEVE encapsulation tests: .. code-block:: console - ./vsperf --conf-file user_settings.py --run-integration + ./vsperf --conf-file user_settings.py --integration --test-params 'tunnel_type=geneve' overlay_p2p_tput To run OVS NATIVE tunnel tests (VXLAN/GRE/GENEVE): @@ -102,7 +102,7 @@ To run OVS NATIVE tunnel tests (VXLAN/GRE/GENEVE): .. code-block:: console - ./vsperf --conf-file user_settings.py --run-integration + ./vsperf --conf-file user_settings.py --integration --test-params 'tunnel_type=vxlan' overlay_p2p_tput @@ -123,7 +123,7 @@ To run VXLAN decapsulation tests: .. code-block:: console - ./vsperf --conf-file user_settings.py --run-integration overlay_p2p_decap_cont + ./vsperf --conf-file user_settings.py --integration overlay_p2p_decap_cont If you want to use different values for your VXLAN frame, you may set: @@ -165,7 +165,7 @@ To run GRE decapsulation tests: .. code-block:: console ./vsperf --conf-file user_settings.py --test-params 'tunnel_type=gre' - --run-integration overlay_p2p_decap_cont + --integration overlay_p2p_decap_cont If you want to use different values for your GRE frame, you may set: @@ -222,7 +222,7 @@ To run GENEVE decapsulation tests: .. code-block:: console ./vsperf --conf-file user_settings.py --test-params 'tunnel_type=geneve' - --run-integration overlay_p2p_decap_cont + --integration overlay_p2p_decap_cont If you want to use different values for your GENEVE frame, you may set: @@ -301,7 +301,7 @@ To run VXLAN decapsulation tests: .. code-block:: console - ./vsperf --conf-file user_settings.py --run-integration + ./vsperf --conf-file user_settings.py --integration --test-params 'tunnel_type=vxlan' overlay_p2p_decap_cont Executing Native/Vanilla OVS GRE decapsulation tests @@ -356,7 +356,7 @@ To run GRE decapsulation tests: .. code-block:: console - ./vsperf --conf-file user_settings.py --run-integration + ./vsperf --conf-file user_settings.py --integration --test-params 'tunnel_type=gre' overlay_p2p_decap_cont Executing Native/Vanilla OVS GENEVE decapsulation tests @@ -411,6 +411,6 @@ To run GENEVE decapsulation tests: .. code-block:: console - ./vsperf --conf-file user_settings.py --run-integration + ./vsperf --conf-file user_settings.py --integration --test-params 'tunnel_type=geneve' overlay_p2p_decap_cont diff --git a/src/ovs/ofctl.py b/src/ovs/ofctl.py index 43151d3a..93894889 100644 --- a/src/ovs/ofctl.py +++ b/src/ovs/ofctl.py @@ -23,6 +23,7 @@ https://github.com/openstack/neutron/blob/6eac1dc99124ca024d6a69b3abfa3bc69c7356 import os import logging import string +import re from tools import tasks from conf import settings @@ -389,3 +390,26 @@ def flow_key(flow): flow_str = _flow_del_key.substitute(_flow_key_param) return flow_str + +def flow_match(flow_dump, flow_src): + """ Compares two flows + + :param flow_dump: string - a string with flow obtained by ovs-ofctl dump-flows + :param flow_src: string - a string with flow obtained by call of flow_key() + + :return: boolean + """ + # perform unifications on both source and destination flows + flow_dump = flow_dump.replace('actions=', 'action=') + flow_src = flow_src.replace('actions=', 'action=') + + # split flow strings into lists of comparable elements + flow_dump_list = re.findall(r"[\w.:=()]+", flow_dump) + flow_src_list = re.findall(r"[\w.:=()]+", flow_src) + + # check if all items from source flow are present in dump flow + flow_src_ctrl = list(flow_src_list) + for rule in flow_src_list: + if rule in flow_dump_list: + flow_src_ctrl.remove(rule) + return True if not len(flow_src_ctrl) else False diff --git a/testcases/__init__.py b/testcases/__init__.py index addf63df..0b6b77e4 100644 --- a/testcases/__init__.py +++ b/testcases/__init__.py @@ -15,3 +15,5 @@ """This module contains test definitions. """ from testcases.testcase import (TestCase) +from testcases.performance import (PerformanceTestCase) +from testcases.integration import (IntegrationTestCase) diff --git a/testcases/integration.py b/testcases/integration.py new file mode 100644 index 00000000..ecaed14f --- /dev/null +++ b/testcases/integration.py @@ -0,0 +1,173 @@ +# Copyright 2015-2016 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""IntegrationTestCase class +""" + +import os +import time +import logging + +from testcases import TestCase +from conf import settings as S +from collections import OrderedDict + +CHECK_PREFIX = 'validate_' + +class IntegrationTestCase(TestCase): + """IntegrationTestCase class + """ + + def __init__(self, cfg, results_dir): + """ Testcase initialization + """ + self._type = 'integration' + super(IntegrationTestCase, self).__init__(cfg, results_dir) + self._logger = logging.getLogger(__name__) + self._inttest = None + + def report_status(self, label, status): + """ Log status of test step + """ + self._logger.debug("%s ... %s", label, 'OK' if status else 'FAILED') + + def run_initialize(self): + """ Prepare test execution environment + """ + super(IntegrationTestCase, self).run_initialize() + self._inttest = {'status' : True, 'details' : ''} + + def run(self): + """Run the test + + All setup and teardown through controllers is included. + """ + def eval_step_params(params, step_result): + """ Evaluates referrences to results from previous steps + """ + def eval_param(param, STEP): + """ Helper function + """ + if isinstance(param, str): + tmp_param = '' + # evaluate every #STEP reference inside parameter itself + for chunk in param.split('#'): + if chunk.startswith('STEP['): + tmp_param = tmp_param + str(eval(chunk)) + else: + tmp_param = tmp_param + chunk + return tmp_param + elif isinstance(param, list) or isinstance(param, tuple): + tmp_list = [] + for item in param: + tmp_list.append(eval_param(item, STEP)) + return tmp_list + elif isinstance(param, dict): + tmp_dict = {} + for (key, value) in param.items(): + tmp_dict[key] = eval_param(value, STEP) + return tmp_dict + else: + return param + + eval_params = [] + # evaluate all parameters if needed + for param in params: + eval_params.append(eval_param(param, step_result)) + return eval_params + + # prepare test execution environment + self.run_initialize() + + with self._vswitch_ctl, self._loadgen: + with self._vnf_ctl, self._collector: + if not self._vswitch_none: + self._add_flows() + + # run traffic generator if requested, otherwise wait for manual termination + if S.getValue('mode') == 'trafficgen-off': + time.sleep(2) + self._logger.debug("All is set. Please run traffic generator manually.") + input(os.linesep + "Press Enter to terminate vswitchperf..." + os.linesep + os.linesep) + else: + with self._traffic_ctl: + if not self.test: + self._traffic_ctl.send_traffic(self._traffic) + else: + # execute test based on TestSteps definition + if self.test: + step_result = [None] * len(self.test) + for i, step in enumerate(self.test): + step_ok = False + if step[0] == 'vswitch': + test_object = self._vswitch_ctl.get_vswitch() + elif step[0] == 'trafficgen': + test_object = self._traffic_ctl + else: + self._logger.error("Unsupported test object %s", step[0]) + self._inttest = {'status' : False, 'details' : ' '.join(step)} + self.report_status("Step '{}'".format(' '.join(step)), self._inttest['status']) + break + + test_method = getattr(test_object, step[1]) + test_method_check = getattr(test_object, CHECK_PREFIX + step[1]) + + step_params = [] + if test_method and test_method_check and \ + callable(test_method) and callable(test_method_check): + + try: + step_params = eval_step_params(step[2:], step_result) + step_log = '{} {}'.format(' '.join(step[:2]), step_params) + step_result[i] = test_method(*step_params) + self._logger.debug("Step {} '{}' results '{}'".format( + i, step_log, step_result[i])) + time.sleep(2) + step_ok = test_method_check(step_result[i], *step_params) + except AssertionError: + self._inttest = {'status' : False, 'details' : step_log} + self._logger.error("Step {} raised assertion error".format(i)) + break + except IndexError: + self._inttest = {'status' : False, 'details' : step_log} + self._logger.error("Step {} result index error {}".format( + i, ' '.join(step[2:]))) + break + + self.report_status("Step {} - '{}'".format(i, step_log), step_ok) + if not step_ok: + self._inttest = {'status' : False, 'details' : step_log} + break + + # dump vswitch flows before they are affected by VNF termination + if not self._vswitch_none: + self._vswitch_ctl.dump_vswitch_flows() + + # tear down test execution environment and log results + self.run_finalize() + + # report test results + self.run_report() + + def run_report(self): + """ Report test results + """ + if self.test: + results = OrderedDict() + results['status'] = 'OK' if self._inttest['status'] else 'FAILED' + results['details'] = self._inttest['details'] + TestCase._write_result_to_file([results], self._output_file) + self.report_status("Test '{}'".format(self.name), self._inttest['status']) + # inform vsperf about testcase failure + if not self._inttest['status']: + raise Exception diff --git a/testcases/performance.py b/testcases/performance.py new file mode 100644 index 00000000..0ae3ea77 --- /dev/null +++ b/testcases/performance.py @@ -0,0 +1,38 @@ +# Copyright 2015-2016 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""PerformanceTestCase class +""" + +import logging + +from testcases import TestCase +from tools.report import report +from conf import settings as S + +class PerformanceTestCase(TestCase): + """PerformanceTestCase class + + In this basic form runs RFC2544 throughput test + """ + def __init__(self, cfg, results_dir): + """ Testcase initialization + """ + self._type = 'performance' + super(PerformanceTestCase, self).__init__(cfg, results_dir) + self._logger = logging.getLogger(__name__) + + def run_report(self): + super(PerformanceTestCase, self).run_report() + if S.getValue('mode') != 'trafficgen-off': + report.generate(self._output_file, self._tc_results, self._collector.get_results(), self._type) diff --git a/testcases/testcase.py b/testcases/testcase.py index dfc766d2..0effce75 100644 --- a/testcases/testcase.py +++ b/testcases/testcase.py @@ -16,18 +16,17 @@ import csv import os +import time import logging import subprocess import copy -import time from collections import OrderedDict -from core.results.results_constants import ResultsConstants import core.component_factory as component_factory from core.loader import Loader +from core.results.results_constants import ResultsConstants from tools import tasks from tools import hugepages -from tools.report import report from tools.pkt_gen.trafficgen.trafficgenhelper import TRAFFIC_DEFAULTS from conf import settings as S from conf import get_test_param @@ -37,7 +36,7 @@ class TestCase(object): In this basic form runs RFC2544 throughput test """ - def __init__(self, cfg, results_dir, performance_test=True): + def __init__(self, cfg, results_dir): """Pull out fields from test config :param cfg: A dictionary of string-value pairs describing the test @@ -46,11 +45,19 @@ class TestCase(object): :param results_dir: Where the csv formatted results are written. """ self._hugepages_mounted = False + self._traffic_ctl = None + self._vnf_ctl = None + self._vswitch_ctl = None + self._collector = None + self._loadgen = None + self._output_file = None + self._tc_results = None # set test parameters; CLI options take precedence to testcase settings self._logger = logging.getLogger(__name__) self.name = cfg['Name'] self.desc = cfg.get('Description', 'No description given.') + self.test = cfg.get('TestSteps', None) bidirectional = cfg.get('biDirectional', TRAFFIC_DEFAULTS['bidir']) bidirectional = get_test_param('bidirectional', bidirectional) @@ -63,7 +70,6 @@ class TestCase(object): self.deployment = cfg['Deployment'] self._frame_mod = cfg.get('Frame Modification', None) - self._performance_test = performance_test self._tunnel_type = None self._tunnel_operation = None @@ -131,10 +137,8 @@ class TestCase(object): # Packet Forwarding mode self._vswitch_none = 'none' == S.getValue('VSWITCH').strip().lower() - def run(self): - """Run the test - - All setup and teardown through controllers is included. + def run_initialize(self): + """ Prepare test execution environment """ self._logger.debug(self.name) @@ -163,35 +167,67 @@ class TestCase(object): self._logger.debug("Controllers:") loader = Loader() - traffic_ctl = component_factory.create_traffic( + self._traffic_ctl = component_factory.create_traffic( self._traffic['traffic_type'], loader.get_trafficgen_class()) - vnf_ctl = component_factory.create_vnf( + + self._vnf_ctl = component_factory.create_vnf( self.deployment, loader.get_vnf_class()) if self._vswitch_none: - vswitch_ctl = component_factory.create_pktfwd( + self._vswitch_ctl = component_factory.create_pktfwd( loader.get_pktfwd_class()) else: - vswitch_ctl = component_factory.create_vswitch( + self._vswitch_ctl = component_factory.create_vswitch( self.deployment, loader.get_vswitch_class(), self._traffic, self._tunnel_operation) - collector = component_factory.create_collector( + self._collector = component_factory.create_collector( loader.get_collector_class(), self._results_dir, self.name) - loadgen = component_factory.create_loadgen( + self._loadgen = component_factory.create_loadgen( self._loadgen, self._load_cfg) + self._output_file = os.path.join(self._results_dir, "result_" + self.name + + "_" + self.deployment + ".csv") + self._logger.debug("Setup:") - with vswitch_ctl, loadgen: - with vnf_ctl, collector: + + def run_finalize(self): + """ Tear down test execution environment and record test results + """ + # umount hugepages if mounted + self._umount_hugepages() + + def run_report(self): + """ Report test results + """ + self._logger.debug("self._collector Results:") + self._collector.print_results() + + if S.getValue('mode') != 'trafficgen-off': + self._logger.debug("Traffic Results:") + self._traffic_ctl.print_results() + + self._tc_results = self._append_results(self._traffic_ctl.get_results()) + TestCase._write_result_to_file(self._tc_results, self._output_file) + + def run(self): + """Run the test + + All setup and teardown through controllers is included. + """ + # prepare test execution environment + self.run_initialize() + + with self._vswitch_ctl, self._loadgen: + with self._vnf_ctl, self._collector: if not self._vswitch_none: - self._add_flows(vswitch_ctl) + self._add_flows() # run traffic generator if requested, otherwise wait for manual termination if S.getValue('mode') == 'trafficgen-off': @@ -209,30 +245,18 @@ class TestCase(object): print('Please respond with \'yes\' or \'y\' ', end='') else: break - with traffic_ctl: - traffic_ctl.send_traffic(self._traffic) + with self._traffic_ctl: + self._traffic_ctl.send_traffic(self._traffic) # dump vswitch flows before they are affected by VNF termination if not self._vswitch_none: - vswitch_ctl.dump_vswitch_flows() + self._vswitch_ctl.dump_vswitch_flows() - # umount hugepages if mounted - self._umount_hugepages() + # tear down test execution environment and log results + self.run_finalize() - self._logger.debug("Collector Results:") - collector.print_results() - - if S.getValue('mode') != 'trafficgen-off': - self._logger.debug("Traffic Results:") - traffic_ctl.print_results() - - output_file = os.path.join(self._results_dir, "result_" + self.name + - "_" + self.deployment + ".csv") - - tc_results = self._append_results(traffic_ctl.get_results()) - TestCase._write_result_to_file(tc_results, output_file) - - report.generate(output_file, tc_results, collector.get_results(), self._performance_test) + # report test results + self.run_report() def _append_results(self, results): """ @@ -335,7 +359,6 @@ class TestCase(object): for result in results: writer.writerow(result) - @staticmethod def _get_unique_keys(list_of_dicts): """Gets unique key values as ordered list of strings in given dicts @@ -351,13 +374,10 @@ class TestCase(object): return list(result.keys()) - - def _add_flows(self, vswitch_ctl): + def _add_flows(self): """Add flows to the vswitch - - :param vswitch_ctl vswitch controller """ - vswitch = vswitch_ctl.get_vswitch() + vswitch = self._vswitch_ctl.get_vswitch() # TODO BOM 15-08-07 the frame mod code assumes that the # physical ports are ports 1 & 2. The actual numbers # need to be retrived from the vSwitch and the metadata value diff --git a/tools/report/report.py b/tools/report/report.py index 92463f26..7d991011 100644 --- a/tools/report/report.py +++ b/tools/report/report.py @@ -70,13 +70,21 @@ def _get_env(result): return env -def generate(input_file, tc_results, tc_stats, performance_test=True): +def generate(input_file, tc_results, tc_stats, test_type='performance'): """Generate actual report. - Generate a Markdown-formatted file using results of tests and some + Generate a Markdown and RST formatted files using results of tests and some parsed system info. :param input_file: Path to CSV results file + :param tc_results: A list of dictionaries with detailed test results. + Each dictionary represents test results for one of specified packet + sizes. + :param tc_stats: System statistics collected during testcase execution. + These statistics are overall statistics for all specified packet + sizes. + :test_type: Specifies type of the testcase. Supported values are + 'performance' and 'integration'. :returns: Path to generated report """ @@ -89,16 +97,18 @@ def generate(input_file, tc_results, tc_stats, performance_test=True): try: for result in tc_results: test_config = {} - if performance_test: + if test_type == 'performance': for tc_conf in S.getValue('PERFORMANCE_TESTS'): if tc_conf['Name'] == result[ResultsConstants.ID]: test_config = tc_conf break - else: + elif test_type == 'integration': for tc_conf in S.getValue('INTEGRATION_TESTS'): if tc_conf['Name'] == result[ResultsConstants.ID]: test_config = tc_conf break + else: + logging.error("Unsupported test type '%s'. Test details are not known.", test_type) # pass test results, env details and configuration to template tests.append({ @@ -36,7 +36,8 @@ sys.dont_write_bytecode = True from conf import settings from conf import get_test_param from core.loader import Loader -from testcases import TestCase +from testcases import PerformanceTestCase +from testcases import IntegrationTestCase from tools import tasks from tools.pkt_gen import trafficgen from tools.opnfvdashboard import opnfvdashboard @@ -156,7 +157,7 @@ def parse_arguments(): name contains RFC2544 less those containing "p2p"') group.add_argument('--verbosity', choices=list_logging_levels(), help='debug level') - group.add_argument('--run-integration', action='store_true', help='run integration tests') + group.add_argument('--integration', action='store_true', help='execute integration tests') group.add_argument('--trafficgen', help='traffic generator to use') group.add_argument('--vswitch', help='vswitch implementation to use') group.add_argument('--fwdapp', help='packet forwarding application to use') @@ -343,11 +344,8 @@ def main(): settings.load_from_dir('conf') - performance_test = True - # Load non performance/integration tests - if args['run_integration']: - performance_test = False + if args['integration']: settings.load_from_dir('conf/integration') # load command line parameters first in case there are settings files @@ -472,14 +470,18 @@ def main(): traffic_ctl.print_results() else: # configure tests - testcases = settings.getValue('PERFORMANCE_TESTS') - if args['run_integration']: + if args['integration']: testcases = settings.getValue('INTEGRATION_TESTS') + else: + testcases = settings.getValue('PERFORMANCE_TESTS') all_tests = [] for cfg in testcases: try: - all_tests.append(TestCase(cfg, results_path, performance_test)) + if args['integration']: + all_tests.append(IntegrationTestCase(cfg, results_path)) + else: + all_tests.append(PerformanceTestCase(cfg, results_path)) except (Exception) as _: logger.exception("Failed to create test: %s", cfg.get('Name', '<Name not set>')) @@ -489,9 +491,9 @@ def main(): if args['list']: print("Available Tests:") - print("======") + print("================") for test in all_tests: - print('* %-18s%s' % ('%s:' % test.name, test.desc)) + print('* %-30s %s' % ('%s:' % test.name, test.desc)) exit() if args['list_trafficgens']: diff --git a/vswitches/ovs.py b/vswitches/ovs.py new file mode 100644 index 00000000..06dc7a1a --- /dev/null +++ b/vswitches/ovs.py @@ -0,0 +1,229 @@ +# Copyright 2015-2016 Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""VSPERF Open vSwitch base class +""" + +import logging +import re +from conf import settings +from vswitches.vswitch import IVSwitch +from src.ovs import OFBridge, flow_key, flow_match + +_VSWITCHD_CONST_ARGS = ['--', '--pidfile', '--log-file'] + +class IVSwitchOvs(IVSwitch): + """Open vSwitch base class implementation + + The method docstrings document only considerations specific to this + implementation. For generic information of the nature of the methods, + see the interface. + """ + + def __init__(self): + """See IVswitch for general description + """ + self._vswitchd = None + self._logger = logging.getLogger(__name__) + self._bridges = {} + self._vswitchd_args = _VSWITCHD_CONST_ARGS + + def start(self): + """See IVswitch for general description + """ + self._logger.info("Starting vswitchd...") + self._vswitchd.start() + self._logger.info("Vswitchd...Started.") + + def stop(self): + """See IVswitch for general description + """ + self._logger.info("Terminating vswitchd...") + self._vswitchd.kill() + self._logger.info("Vswitchd...Terminated.") + + def add_switch(self, switch_name, params=None): + """See IVswitch for general description + """ + bridge = OFBridge(switch_name) + bridge.create(params) + bridge.set_db_attribute('Open_vSwitch', '.', + 'other_config:max-idle', + settings.getValue('VSWITCH_FLOW_TIMEOUT')) + self._bridges[switch_name] = bridge + + def del_switch(self, switch_name): + """See IVswitch for general description + """ + bridge = self._bridges[switch_name] + self._bridges.pop(switch_name) + bridge.destroy() + + def add_phy_port(self, switch_name): + """See IVswitch for general description + """ + raise NotImplementedError + + def add_vport(self, switch_name): + """See IVswitch for general description + """ + raise NotImplementedError + + def add_tunnel_port(self, switch_name, remote_ip, tunnel_type='vxlan', params=None): + """Creates tunneling port + """ + bridge = self._bridges[switch_name] + pcount = str(self._get_port_count('type=' + tunnel_type)) + port_name = tunnel_type + pcount + local_params = ['--', 'set', 'Interface', port_name, + 'type=' + tunnel_type, + 'options:remote_ip=' + remote_ip] + + if params is not None: + local_params = local_params + params + + of_port = bridge.add_port(port_name, local_params) + return (port_name, of_port) + + def get_ports(self, switch_name): + """See IVswitch for general description + """ + bridge = self._bridges[switch_name] + ports = list(bridge.get_ports().items()) + return [(name, of_port) for (name, (of_port, _)) in ports] + + def del_port(self, switch_name, port_name): + """See IVswitch for general description + """ + bridge = self._bridges[switch_name] + bridge.del_port(port_name) + + def add_flow(self, switch_name, flow, cache='off'): + """See IVswitch for general description + """ + bridge = self._bridges[switch_name] + bridge.add_flow(flow, cache=cache) + + def del_flow(self, switch_name, flow=None): + """See IVswitch for general description + """ + flow = flow or {} + bridge = self._bridges[switch_name] + bridge.del_flow(flow) + + def dump_flows(self, switch_name): + """See IVswitch for general description + """ + bridge = self._bridges[switch_name] + bridge.dump_flows() + + def add_route(self, switch_name, network, destination): + """See IVswitch for general description + """ + bridge = self._bridges[switch_name] + bridge.add_route(network, destination) + + def set_tunnel_arp(self, ip_addr, mac_addr, switch_name): + """See IVswitch for general description + """ + bridge = self._bridges[switch_name] + bridge.set_tunnel_arp(ip_addr, mac_addr, switch_name) + + def _get_port_count(self, param): + """Returns the number of ports having a certain parameter + """ + cnt = 0 + for k in self._bridges: + pparams = [c for (_, (_, c)) in list(self._bridges[k].get_ports().items())] + phits = [i for i in pparams if param in i] + cnt += len(phits) + + if cnt is None: + cnt = 0 + return cnt + + def validate_add_switch(self, result, switch_name, params=None): + """Validate - Create a new logical switch with no ports + """ + bridge = self._bridges[switch_name] + output = bridge.run_vsctl(['show'], check_error=True) + assert not output[1] # there shouldn't be any stderr, but in case + assert re.search('Bridge ["\']?%s["\']?' % switch_name, output[0]) is not None + return True + + def validate_del_switch(self, result, switch_name): + """Validate removal of switch + """ + bridge = OFBridge('tmp') + output = bridge.run_vsctl(['show'], check_error=True) + assert not output[1] # there shouldn't be any stderr, but in case + assert re.search('Bridge ["\']?%s["\']?' % switch_name, output[0]) is None + return True + + def validate_add_phy_port(self, result, switch_name): + """ Validate that physical port was added to bridge. + """ + bridge = self._bridges[switch_name] + output = bridge.run_vsctl(['show'], check_error=True) + assert not output[1] # there shouldn't be any stderr, but in case + assert re.search('Port ["\']?%s["\']?' % result[0], output[0]) is not None + assert re.search('Interface ["\']?%s["\']?' % result[0], output[0]) is not None + return True + + def validate_add_vport(self, result, switch_name): + """ Validate that virtual port was added to bridge. + """ + return self.validate_add_phy_port(result, switch_name) + + def validate_del_port(self, result, switch_name, port_name): + """ Validate that port_name was removed from bridge. + """ + bridge = self._bridges[switch_name] + output = bridge.run_vsctl(['show'], check_error=True) + assert not output[1] # there shouldn't be any stderr, but in case + assert 'Port "%s"' % port_name not in output[0] + return True + + def validate_add_flow(self, result, switch_name, flow, cache='off'): + """ Validate insertion of the flow into the switch + """ + if 'idle_timeout' in flow: + del(flow['idle_timeout']) + + # Note: it should be possible to call `ovs-ofctl dump-flows switch flow` + # to verify flow insertion, but it doesn't accept the same flow syntax + # as add-flow, so we have to compare it the hard way + + # get dump of flows and compare them one by one + flow_src = flow_key(flow) + bridge = self._bridges[switch_name] + output = bridge.run_ofctl(['dump-flows', switch_name], check_error=True) + for flow_dump in output[0].split('\n'): + if flow_match(flow_dump, flow_src): + # flow was added correctly + return True + return False + + def validate_del_flow(self, result, switch_name, flow=None): + """ Validate removal of the flow + """ + if not flow: + # what else we can do? + return True + return not self.validate_add_flow(result, switch_name, flow) + + def validate_dump_flows(self, result, switch_name): + """ Validate call of flow dump + """ + return True diff --git a/vswitches/ovs_dpdk_vhost.py b/vswitches/ovs_dpdk_vhost.py index 2ace64a2..9d29c9d1 100644 --- a/vswitches/ovs_dpdk_vhost.py +++ b/vswitches/ovs_dpdk_vhost.py @@ -17,13 +17,11 @@ import logging from conf import settings -from vswitches.vswitch import IVSwitch -from src.ovs import VSwitchd, OFBridge +from vswitches.ovs import IVSwitchOvs +from src.ovs import VSwitchd from src.dpdk import dpdk -_VSWITCHD_CONST_ARGS = ['--', '--pidfile', '--log-file'] - -class OvsDpdkVhost(IVSwitch): +class OvsDpdkVhost(IVSwitchOvs): """ Open vSwitch with DPDK support Generic OVS wrapper functionality in src.ovs is maximally used. This @@ -35,21 +33,19 @@ class OvsDpdkVhost(IVSwitch): see the interface. """ - _logger = logging.getLogger() - def __init__(self): - vswitchd_args = ['--dpdk'] - vswitchd_args += settings.getValue('VSWITCHD_DPDK_ARGS') - vswitchd_args += _VSWITCHD_CONST_ARGS + super(OvsDpdkVhost, self).__init__() + self._logger = logging.getLogger(__name__) + self._vswitchd_args = ['--dpdk'] + self._vswitchd_args += settings.getValue('VSWITCHD_DPDK_ARGS') if settings.getValue('VNF').endswith('Cuse'): self._logger.info("Inserting VHOST Cuse modules into kernel...") dpdk.insert_vhost_modules() - self._vswitchd = VSwitchd(vswitchd_args=vswitchd_args, + self._vswitchd = VSwitchd(vswitchd_args=self._vswitchd_args, expected_cmd= r'EAL: Master l*core \d+ is ready') - self._bridges = {} def start(self): """See IVswitch for general description @@ -57,47 +53,32 @@ class OvsDpdkVhost(IVSwitch): Activates DPDK kernel modules, ovsdb and vswitchd. """ dpdk.init() - self._vswitchd.start() + super(OvsDpdkVhost, self).start() def stop(self): """See IVswitch for general description Kills ovsdb and vswitchd and removes DPDK kernel modules. """ - self._vswitchd.kill() + super(OvsDpdkVhost, self).stop() dpdk.cleanup() dpdk.remove_vhost_modules() def add_switch(self, switch_name, params=None): """See IVswitch for general description """ - bridge = OFBridge(switch_name) - if params is None: - bridge.create(['--', 'set', 'bridge', switch_name, - 'datapath_type=netdev']) - else: - bridge.create(['--', 'set', 'bridge', switch_name, - 'datapath_type=netdev'] + params) + switch_params = ['--', 'set', 'bridge', switch_name, 'datapath_type=netdev'] + if params: + switch_params = switch_params + params - bridge.set_db_attribute('Open_vSwitch', '.', - 'other_config:max-idle', - settings.getValue('VSWITCH_FLOW_TIMEOUT')) + super(OvsDpdkVhost, self).add_switch(switch_name, switch_params) if settings.getValue('VSWITCH_AFFINITIZATION_ON') == 1: # Sets the PMD core mask to VSWITCH_PMD_CPU_MASK # for CPU core affinitization - bridge.set_db_attribute('Open_vSwitch', '.', - 'other_config:pmd-cpu-mask', - settings.getValue('VSWITCH_PMD_CPU_MASK')) - - self._bridges[switch_name] = bridge - - def del_switch(self, switch_name): - """See IVswitch for general description - """ - bridge = self._bridges[switch_name] - self._bridges.pop(switch_name) - bridge.destroy() + self._bridges[switch_name].set_db_attribute('Open_vSwitch', '.', + 'other_config:pmd-cpu-mask', + settings.getValue('VSWITCH_PMD_CPU_MASK')) def add_phy_port(self, switch_name): """See IVswitch for general description @@ -134,80 +115,3 @@ class OvsDpdkVhost(IVSwitch): of_port = bridge.add_port(port_name, params) return (port_name, of_port) - - def add_tunnel_port(self, switch_name, remote_ip, tunnel_type='vxlan', params=None): - """Creates tunneling port - """ - bridge = self._bridges[switch_name] - pcount = str(self._get_port_count('type=' + tunnel_type)) - port_name = tunnel_type + pcount - local_params = ['--', 'set', 'Interface', port_name, - 'type=' + tunnel_type, - 'options:remote_ip=' + remote_ip] - - if params is not None: - local_params = local_params + params - - of_port = bridge.add_port(port_name, local_params) - return (port_name, of_port) - - def get_ports(self, switch_name): - """See IVswitch for general description - """ - bridge = self._bridges[switch_name] - ports = list(bridge.get_ports().items()) - return [(name, of_port) for (name, (of_port, _)) in ports] - - def del_port(self, switch_name, port_name): - """See IVswitch for general description - """ - bridge = self._bridges[switch_name] - bridge.del_port(port_name) - - def add_flow(self, switch_name, flow, cache='off'): - """See IVswitch for general description - """ - bridge = self._bridges[switch_name] - bridge.add_flow(flow, cache=cache) - - def del_flow(self, switch_name, flow=None): - """See IVswitch for general description - """ - flow = flow or {} - bridge = self._bridges[switch_name] - bridge.del_flow(flow) - - def dump_flows(self, switch_name): - """See IVswitch for general description - """ - bridge = self._bridges[switch_name] - bridge.dump_flows() - - def add_route(self, switch_name, network, destination): - """See IVswitch for general description - """ - bridge = self._bridges[switch_name] - bridge.add_route(network, destination) - - def set_tunnel_arp(self, ip_addr, mac_addr, switch_name): - """See IVswitch for general description - """ - bridge = self._bridges[switch_name] - bridge.set_tunnel_arp(ip_addr, mac_addr, switch_name) - - def _get_port_count(self, param): - """Returns the number of ports having a certain parameter - - :param bridge: The src.ovs.ofctl.OFBridge on which to operate - :param param: The parameter to search for - :returns: Count of matches - """ - cnt = 0 - for k in self._bridges: - pparams = [c for (_, (_, c)) in list(self._bridges[k].get_ports().items())] - phits = [i for i in pparams if param in i] - cnt += len(phits) - - if cnt is None: - cnt = 0 - return cnt diff --git a/vswitches/ovs_vanilla.py b/vswitches/ovs_vanilla.py index 078d7006..6a380b1b 100644 --- a/vswitches/ovs_vanilla.py +++ b/vswitches/ovs_vanilla.py @@ -17,15 +17,12 @@ import logging from conf import settings -from vswitches.vswitch import IVSwitch -from src.ovs import VSwitchd, OFBridge, DPCtl +from vswitches.ovs import IVSwitchOvs +from src.ovs import VSwitchd, DPCtl from tools.module_manager import ModuleManager from tools import tasks -_LOGGER = logging.getLogger(__name__) -VSWITCHD_CONST_ARGS = ['--', '--log-file'] - -class OvsVanilla(IVSwitch): +class OvsVanilla(IVSwitchOvs): """ Open vSwitch This is wrapper for functionality implemented in src.ovs. @@ -35,16 +32,16 @@ class OvsVanilla(IVSwitch): see the interface definition. """ - _logger = logging.getLogger() _ports = settings.getValue('VSWITCH_VANILLA_PHY_PORT_NAMES') _current_id = 0 _vport_id = 0 def __init__(self): - #vswitchd_args = VSWITCHD_CONST_ARGS - vswitchd_args = ["unix:%s" % VSwitchd.get_db_sock_path()] - vswitchd_args += settings.getValue('VSWITCHD_VANILLA_ARGS') - self._vswitchd = VSwitchd(vswitchd_args=vswitchd_args, + super(OvsVanilla, self).__init__() + self._logger = logging.getLogger(__name__) + self._vswitchd_args = ["unix:%s" % VSwitchd.get_db_sock_path()] + self._vswitchd_args += settings.getValue('VSWITCHD_VANILLA_ARGS') + self._vswitchd = VSwitchd(vswitchd_args=self._vswitchd_args, expected_cmd="db.sock: connected") self._bridges = {} self._module_manager = ModuleManager() @@ -56,9 +53,7 @@ class OvsVanilla(IVSwitch): """ self._module_manager.insert_modules( settings.getValue('VSWITCH_VANILLA_KERNEL_MODULES')) - self._logger.info("Starting Vswitchd...") - self._vswitchd.start() - self._logger.info("Vswitchd...Started.") + super(OvsVanilla, self).start() def stop(self): """See IVswitch for general description @@ -70,32 +65,16 @@ class OvsVanilla(IVSwitch): tapx = 'tap' + str(i) tasks.run_task(['sudo', 'ip', 'tuntap', 'del', tapx, 'mode', 'tap'], - _LOGGER, 'Deleting ' + tapx, False) + self._logger, 'Deleting ' + tapx, False) self._vport_id = 0 - self._vswitchd.kill() + super(OvsVanilla, self).stop() dpctl = DPCtl() dpctl.del_dp() self._module_manager.remove_modules() - def add_switch(self, switch_name, params=None): - """See IVswitch for general description - """ - bridge = OFBridge(switch_name) - bridge.create(params) - bridge.set_db_attribute('Open_vSwitch', '.', - 'other_config:max-idle', '60000') - self._bridges[switch_name] = bridge - - def del_switch(self, switch_name): - """See IVswitch for general description - """ - bridge = self._bridges[switch_name] - self._bridges.pop(switch_name) - bridge.destroy() - def add_phy_port(self, switch_name): """ Method adds port based on configured VSWITCH_VANILLA_PHY_PORT_NAMES @@ -119,7 +98,7 @@ class OvsVanilla(IVSwitch): # For PVP only tasks.run_task(['sudo', 'ifconfig', port_name, '0'], - _LOGGER, 'Remove IP', False) + self._logger, 'Remove IP', False) of_port = bridge.add_port(port_name, params) self._current_id += 1 @@ -137,95 +116,17 @@ class OvsVanilla(IVSwitch): tasks.run_task(['sudo', 'ip', 'tuntap', 'del', tap_name, 'mode', 'tap'], - _LOGGER, 'Creating tap device...', False) + self._logger, 'Creating tap device...', False) tasks.run_task(['sudo', 'ip', 'tuntap', 'add', tap_name, 'mode', 'tap'], - _LOGGER, 'Creating tap device...', False) + self._logger, 'Creating tap device...', False) tasks.run_task(['sudo', 'ifconfig', tap_name, '0'], - _LOGGER, 'Bring up ' + tap_name, False) + self._logger, 'Bring up ' + tap_name, False) bridge = self._bridges[switch_name] of_port = bridge.add_port(tap_name, []) return (tap_name, of_port) - def add_tunnel_port(self, switch_name, remote_ip, tunnel_type='vxlan', - params=None): - """Creates tunneling port - """ - bridge = self._bridges[switch_name] - pcount = str(self._get_port_count('type=' + tunnel_type)) - port_name = tunnel_type + pcount - local_params = ['--', 'set', 'Interface', port_name, - 'type=' + tunnel_type, - 'options:remote_ip=' + remote_ip] - - if params is not None: - local_params = local_params + params - - of_port = bridge.add_port(port_name, local_params) - return (port_name, of_port) - - def get_ports(self, switch_name): - """See IVswitch for general description - """ - bridge = self._bridges[switch_name] - ports = list(bridge.get_ports().items()) - return [(name, of_port) for (name, (of_port, _)) in ports] - - def del_port(self, switch_name, port_name): - """See IVswitch for general description - """ - bridge = self._bridges[switch_name] - bridge.del_port(port_name) - - def add_flow(self, switch_name, flow, cache='off'): - """See IVswitch for general description - """ - bridge = self._bridges[switch_name] - bridge.add_flow(flow, cache=cache) - - def del_flow(self, switch_name, flow=None): - """See IVswitch for general description - """ - flow = flow or {} - bridge = self._bridges[switch_name] - bridge.del_flow(flow) - - def dump_flows(self, switch_name): - """See IVswitch for general description - """ - bridge = self._bridges[switch_name] - bridge.dump_flows() - - def add_route(self, switch_name, network, destination): - """See IVswitch for general description - """ - bridge = self._bridges[switch_name] - bridge.add_route(network, destination) - - def set_tunnel_arp(self, ip_addr, mac_addr, switch_name): - """See IVswitch for general description - """ - bridge = self._bridges[switch_name] - bridge.set_tunnel_arp(ip_addr, mac_addr, switch_name) - - def _get_port_count(self, param): - """Returns the number of ports having a certain parameter - - :param bridge: The src.ovs.ofctl.OFBridge on which to operate - :param param: The parameter to search for - :returns: Count of matches - """ - cnt = 0 - for k in self._bridges: - pparams = [c for (_, (_, c)) in list(self._bridges[k].get_ports().items())] - phits = [i for i in pparams if param in i] - cnt += len(phits) - - if cnt is None: - cnt = 0 - return cnt - |