Merge "Trex_speed_improvement: Add logic for dealing with high speed cards"
[vswitchperf.git] / vswitches / ovs.py
index 243619b..76cabb0 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright 2015-2016 Intel Corporation.
+# Copyright 2015-2017 Intel Corporation.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 
 import logging
 import os
-import pexpect
 import re
 import time
+import datetime
+import random
+import pexpect
 
 from conf import settings
 from src.ovs import OFBridge, flow_key, flow_match
-from tools import tasks
 from vswitches.vswitch import IVSwitch
-
-_OVS_VAR_DIR = settings.getValue('OVS_VAR_DIR')
-_OVS_ETC_DIR = settings.getValue('OVS_ETC_DIR')
-
-
+from tools import tasks
+from tools.module_manager import ModuleManager
+# pylint: disable=too-many-public-methods
 class IVSwitchOvs(IVSwitch, tasks.Process):
     """Open vSwitch base class implementation
 
@@ -37,23 +36,28 @@ class IVSwitchOvs(IVSwitch, tasks.Process):
     implementation. For generic information of the nature of the methods,
     see the interface.
     """
-    _logfile = os.path.join(settings.getValue('LOG_DIR'), settings.getValue('LOG_FILE_VSWITCHD'))
-    _ovsdb_pidfile_path = os.path.join(_OVS_VAR_DIR, "ovsdb-server.pid")
-    _vswitchd_pidfile_path = os.path.join(_OVS_VAR_DIR, "ovs-vswitchd.pid")
     _proc_name = 'ovs-vswitchd'
 
     def __init__(self):
         """See IVswitch for general description
         """
+        self._logfile = os.path.join(settings.getValue('LOG_DIR'),
+                                     settings.getValue('LOG_FILE_VSWITCHD'))
+        self._ovsdb_pidfile_path = os.path.join(settings.getValue('TOOLS')['ovs_var_tmp'],
+                                                "ovsdb-server.pid")
+        self._vswitchd_pidfile_path = os.path.join(settings.getValue('TOOLS')['ovs_var_tmp'],
+                                                   "{}.pid".format(self._proc_name))
         self._logger = logging.getLogger(__name__)
-        self._expect = r'bridge|INFO|ovs-vswitchd'
+        # sign '|' must be escaped or avoided, otherwise it is handled as 'or' by regex
+        self._expect = r'bridge.INFO.{}'.format(self._proc_name)
         self._timeout = 30
         self._bridges = {}
         self._vswitchd_args = ['--pidfile=' + self._vswitchd_pidfile_path,
                                '--overwrite-pidfile', '--log-file=' + self._logfile]
         self._cmd = []
-        self._cmd_template = ['sudo', '-E', os.path.join(settings.getValue('OVS_DIR'),
-                                                         'vswitchd', 'ovs-vswitchd')]
+        self._cmd_template = ['sudo', '-E', settings.getValue('TOOLS')['ovs-vswitchd']]
+        self._stamp = None
+        self._module_manager = ModuleManager()
 
     def start(self):
         """ Start ``ovsdb-server`` and ``ovs-vswitchd`` instance.
@@ -62,6 +66,10 @@ class IVSwitchOvs(IVSwitch, tasks.Process):
         """
         self._logger.info("Starting vswitchd...")
 
+        # insert kernel modules if required
+        if 'vswitch_modules' in settings.getValue('TOOLS'):
+            self._module_manager.insert_modules(settings.getValue('TOOLS')['vswitch_modules'])
+
         self._cmd = self._cmd_template + self._vswitchd_args
 
         # DB must be started before vswitchd
@@ -83,16 +91,46 @@ class IVSwitchOvs(IVSwitch, tasks.Process):
 
         self._logger.info("Vswitchd...Started.")
 
+    def restart(self):
+        """ Restart ``ovs-vswitchd`` instance. ``ovsdb-server`` is not restarted.
+
+        :raises: pexpect.EOF, pexpect.TIMEOUT
+        """
+        self._logger.info("Restarting vswitchd...")
+        if os.path.isfile(self._vswitchd_pidfile_path):
+            self._logger.info('Killing ovs-vswitchd...')
+            with open(self._vswitchd_pidfile_path, "r") as pidfile:
+                vswitchd_pid = pidfile.read().strip()
+                tasks.terminate_task(vswitchd_pid, logger=self._logger)
+
+        try:
+            tasks.Process.start(self)
+            self.relinquish()
+        except (pexpect.EOF, pexpect.TIMEOUT) as exc:
+            logging.error("Exception during VSwitch start.")
+            self._kill_ovsdb()
+            raise exc
+        self._logger.info("Vswitchd...Started.")
+
     def configure(self):
         """ Configure vswitchd through ovsdb if needed
         """
         pass
 
+    # Method could be a function
+    # pylint: disable=no-self-use
+    def get_version(self):
+        """See IVswitch for general description
+        """
+        # OVS version can be read offline
+        return []
+
     def stop(self):
         """See IVswitch for general description
         """
         self._logger.info("Terminating vswitchd...")
         self.kill()
+        self._bridges = {}
         self._logger.info("Vswitchd...Terminated.")
 
     def add_switch(self, switch_name, params=None):
@@ -122,6 +160,38 @@ class IVSwitchOvs(IVSwitch, tasks.Process):
         """
         raise NotImplementedError
 
+    def add_veth_pair_port(self, switch_name=None, remote_switch_name=None,
+                           local_opts=None, remote_opts=None):
+        """Creates veth-pair port between 'switch_name' and 'remote_switch_name'
+
+        """
+        if switch_name is None or remote_switch_name is None:
+            return
+
+        bridge = self._bridges[switch_name]
+        remote_bridge = self._bridges[remote_switch_name]
+        pcount = str(self._get_port_count('type=patch'))
+        # NOTE ::: What if interface name longer than allowed width??
+        local_port_name = switch_name + '-' + remote_switch_name + '-' + pcount
+        remote_port_name = remote_switch_name + '-' + switch_name + '-' + pcount
+        local_params = ['--', 'set', 'Interface', local_port_name,
+                        'type=patch',
+                        'options:peer=' + remote_port_name]
+        remote_params = ['--', 'set', 'Interface', remote_port_name,
+                         'type=patch',
+                         'options:peer=' + local_port_name]
+
+        if local_opts is not None:
+            local_params = local_params + local_opts
+
+        if remote_opts is not None:
+            remote_params = remote_params + remote_opts
+
+        local_of_port = bridge.add_port(local_port_name, local_params)
+        remote_of_port = remote_bridge.add_port(remote_port_name, remote_params)
+        return [(local_port_name, local_of_port),
+                (remote_port_name, remote_of_port)]
+
     def add_tunnel_port(self, switch_name, remote_ip, tunnel_type='vxlan', params=None):
         """Creates tunneling port
         """
@@ -264,14 +334,20 @@ class IVSwitchOvs(IVSwitch, tasks.Process):
         """
         self._logger.info('Resetting system after last run...')
 
-        tasks.run_task(['sudo', 'rm', '-rf', _OVS_VAR_DIR], self._logger)
-        tasks.run_task(['sudo', 'mkdir', '-p', _OVS_VAR_DIR], self._logger)
-        tasks.run_task(['sudo', 'rm', '-rf', _OVS_ETC_DIR], self._logger)
-        tasks.run_task(['sudo', 'mkdir', '-p', _OVS_ETC_DIR], self._logger)
-
-        tasks.run_task(['sudo', 'rm', '-f',
-                        os.path.join(_OVS_ETC_DIR, 'conf.db')],
-                       self._logger)
+        # create a backup of ovs_var_tmp and ovs_etc_tmp; It is
+        # essential for OVS installed from binary packages.
+        self._stamp = '{:%Y%m%d_%H%M%S}_{}'.format(datetime.datetime.now(),
+                                                   random.randrange(1000, 9999))
+        for tmp_dir in ['ovs_var_tmp', 'ovs_etc_tmp']:
+            if os.path.exists(settings.getValue('TOOLS')[tmp_dir]):
+                orig_dir = os.path.normpath(settings.getValue('TOOLS')[tmp_dir])
+                self._logger.info('Creating backup of %s directory...', tmp_dir)
+                tasks.run_task(['sudo', 'mv', orig_dir, '{}.{}'.format(orig_dir, self._stamp)],
+                               self._logger)
+
+        # create fresh tmp dirs
+        tasks.run_task(['sudo', 'mkdir', '-p', settings.getValue('TOOLS')['ovs_var_tmp']], self._logger)
+        tasks.run_task(['sudo', 'mkdir', '-p', settings.getValue('TOOLS')['ovs_etc_tmp']], self._logger)
 
         self._logger.info('System reset after last run.')
 
@@ -280,21 +356,18 @@ class IVSwitchOvs(IVSwitch, tasks.Process):
 
         :returns: None
         """
-        ovsdb_tool_bin = os.path.join(
-            settings.getValue('OVS_DIR'), 'ovsdb', 'ovsdb-tool')
+        ovsdb_tool_bin = settings.getValue('TOOLS')['ovsdb-tool']
         tasks.run_task(['sudo', ovsdb_tool_bin, 'create',
-                        os.path.join(_OVS_ETC_DIR, 'conf.db'),
-                        os.path.join(settings.getValue('OVS_DIR'), 'vswitchd',
-                                     'vswitch.ovsschema')],
+                        os.path.join(settings.getValue('TOOLS')['ovs_etc_tmp'], 'conf.db'),
+                        settings.getValue('TOOLS')['ovsschema']],
                        self._logger,
                        'Creating ovsdb configuration database...')
 
-        ovsdb_server_bin = os.path.join(
-            settings.getValue('OVS_DIR'), 'ovsdb', 'ovsdb-server')
+        ovsdb_server_bin = settings.getValue('TOOLS')['ovsdb-server']
 
         tasks.run_background_task(
             ['sudo', ovsdb_server_bin,
-             '--remote=punix:%s' % os.path.join(_OVS_VAR_DIR, 'db.sock'),
+             '--remote=punix:%s' % os.path.join(settings.getValue('TOOLS')['ovs_var_tmp'], 'db.sock'),
              '--remote=db:Open_vSwitch,Open_vSwitch,manager_options',
              '--pidfile=' + self._ovsdb_pidfile_path, '--overwrite-pidfile'],
             self._logger,
@@ -314,19 +387,29 @@ class IVSwitchOvs(IVSwitch, tasks.Process):
             if ovsdb_pid:
                 tasks.terminate_task(ovsdb_pid, logger=self._logger)
 
+        # restore original content of ovs_var_tmp and ovs_etc_tmp; It is
+        # essential for OVS installed from binary packages.
+        if self._stamp:
+            for tmp_dir in ['ovs_var_tmp', 'ovs_etc_tmp']:
+                orig_dir = os.path.normpath(settings.getValue('TOOLS')[tmp_dir])
+                if os.path.exists('{}.{}'.format(orig_dir, self._stamp)):
+                    self._logger.info('Restoring backup of %s directory...', tmp_dir)
+                    tasks.run_task(['sudo', 'rm', '-rf', orig_dir], self._logger)
+                    tasks.run_task(['sudo', 'mv', '{}.{}'.format(orig_dir, self._stamp), orig_dir],
+                                   self._logger)
+
     @staticmethod
     def get_db_sock_path():
         """Method returns location of db.sock file
 
         :returns: path to db.sock file.
         """
-        return os.path.join(_OVS_VAR_DIR, 'db.sock')
+        return os.path.join(settings.getValue('TOOLS')['ovs_var_tmp'], 'db.sock')
 
     #
     # validate methods required for integration testcases
     #
-
-    def validate_add_switch(self, result, switch_name, params=None):
+    def validate_add_switch(self, dummy_result, switch_name, dummy_params=None):
         """Validate - Create a new logical switch with no ports
         """
         bridge = self._bridges[switch_name]
@@ -335,7 +418,9 @@ class IVSwitchOvs(IVSwitch, tasks.Process):
         assert re.search('Bridge ["\']?%s["\']?' % switch_name, output[0]) is not None
         return True
 
-    def validate_del_switch(self, result, switch_name):
+    # Method could be a function
+    # pylint: disable=no-self-use
+    def validate_del_switch(self, dummy_result, switch_name):
         """Validate removal of switch
         """
         bridge = OFBridge('tmp')
@@ -359,7 +444,7 @@ class IVSwitchOvs(IVSwitch, tasks.Process):
         """
         return self.validate_add_phy_port(result, switch_name)
 
-    def validate_del_port(self, result, switch_name, port_name):
+    def validate_del_port(self, dummy_result, switch_name, port_name):
         """ Validate that port_name was removed from bridge.
         """
         bridge = self._bridges[switch_name]
@@ -368,11 +453,12 @@ class IVSwitchOvs(IVSwitch, tasks.Process):
         assert 'Port "%s"' % port_name not in output[0]
         return True
 
-    def validate_add_flow(self, result, switch_name, flow, cache='off'):
+    def validate_add_flow(self, dummy_result, switch_name, flow, dummy_cache='off'):
         """ Validate insertion of the flow into the switch
         """
+
         if 'idle_timeout' in flow:
-            del(flow['idle_timeout'])
+            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
@@ -388,39 +474,44 @@ class IVSwitchOvs(IVSwitch, tasks.Process):
                 return True
         return False
 
-    def validate_del_flow(self, result, switch_name, flow=None):
+    def validate_del_flow(self, dummy_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)
+        return not self.validate_add_flow(dummy_result, switch_name, flow)
 
-    def validate_dump_flows(self, result, switch_name):
+    def validate_dump_flows(self, dummy_result, dummy_switch_name):
         """ Validate call of flow dump
         """
         return True
 
-    def validate_disable_rstp(self, result, switch_name):
+    def validate_disable_rstp(self, dummy_result, switch_name):
         """ Validate rstp disable
         """
         bridge = self._bridges[switch_name]
         return 'rstp_enable         : false' in ''.join(bridge.bridge_info())
 
-    def validate_enable_rstp(self, result, switch_name):
+    def validate_enable_rstp(self, dummy_result, switch_name):
         """ Validate rstp enable
         """
         bridge = self._bridges[switch_name]
         return 'rstp_enable         : true' in ''.join(bridge.bridge_info())
 
-    def validate_disable_stp(self, result, switch_name):
+    def validate_disable_stp(self, dummy_result, switch_name):
         """ Validate stp disable
         """
         bridge = self._bridges[switch_name]
         return 'stp_enable          : false' in ''.join(bridge.bridge_info())
 
-    def validate_enable_stp(self, result, switch_name):
+    def validate_enable_stp(self, dummy_result, switch_name):
         """ Validate stp enable
         """
         bridge = self._bridges[switch_name]
         return 'stp_enable          : true' in ''.join(bridge.bridge_info())
+
+    def validate_restart(self, dummy_result):
+        """ Validate restart
+        """
+        return True