1 # Copyright 2015-2018 Intel Corporation, Tieto and Others.
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 """VSPERF Open vSwitch base class
26 from conf import settings
27 from src.ovs import OFBridge, flow_key, flow_match
28 from vswitches.vswitch import IVSwitch
29 from tools import tasks
30 from tools.module_manager import ModuleManager
31 # pylint: disable=too-many-public-methods
32 class IVSwitchOvs(IVSwitch, tasks.Process):
33 """Open vSwitch base class implementation
35 The method docstrings document only considerations specific to this
36 implementation. For generic information of the nature of the methods,
39 _proc_name = 'ovs-vswitchd'
42 """See IVswitch for general description
44 self._logfile = os.path.join(settings.getValue('LOG_DIR'),
45 settings.getValue('LOG_FILE_VSWITCHD'))
46 self._ovsdb_pidfile_path = os.path.join(settings.getValue('TOOLS')['ovs_var_tmp'],
48 self._vswitchd_pidfile_path = os.path.join(settings.getValue('TOOLS')['ovs_var_tmp'],
49 "{}.pid".format(self._proc_name))
50 self._logger = logging.getLogger(__name__)
51 # sign '|' must be escaped or avoided, otherwise it is handled as 'or' by regex
52 self._expect = r'bridge.INFO.{}'.format(self._proc_name)
55 self._vswitchd_args = ['--pidfile=' + self._vswitchd_pidfile_path,
56 '--overwrite-pidfile', '--log-file=' + self._logfile]
58 self._cmd_template = ['sudo', '-E', settings.getValue('TOOLS')['ovs-vswitchd']]
60 self._module_manager = ModuleManager()
63 """ Start ``ovsdb-server`` and ``ovs-vswitchd`` instance.
65 :raises: pexpect.EOF, pexpect.TIMEOUT
67 self._logger.info("Starting vswitchd...")
69 # insert kernel modules if required
70 if 'vswitch_modules' in settings.getValue('TOOLS'):
71 self._module_manager.insert_modules(settings.getValue('TOOLS')['vswitch_modules'])
73 self._cmd = self._cmd_template + self._vswitchd_args
75 # DB must be started before vswitchd
79 # DB must be up before vswitchd config is altered or vswitchd started
85 tasks.Process.start(self)
87 except (pexpect.EOF, pexpect.TIMEOUT) as exc:
88 logging.error("Exception during VSwitch start.")
92 self._logger.info("Vswitchd...Started.")
95 """ Restart ``ovs-vswitchd`` instance. ``ovsdb-server`` is not restarted.
97 :raises: pexpect.EOF, pexpect.TIMEOUT
99 self._logger.info("Restarting vswitchd...")
100 if os.path.isfile(self._vswitchd_pidfile_path):
101 self._logger.info('Killing ovs-vswitchd...')
102 with open(self._vswitchd_pidfile_path, "r") as pidfile:
103 vswitchd_pid = pidfile.read().strip()
104 tasks.terminate_task(vswitchd_pid, logger=self._logger)
107 tasks.Process.start(self)
109 except (pexpect.EOF, pexpect.TIMEOUT) as exc:
110 logging.error("Exception during VSwitch start.")
113 self._logger.info("Vswitchd...Started.")
116 """ Configure vswitchd through ovsdb if needed
120 # Method could be a function
121 # pylint: disable=no-self-use
122 def get_version(self):
123 """See IVswitch for general description
125 # OVS version can be read offline
129 """See IVswitch for general description
131 for switch_name in list(self._bridges):
132 self.del_switch(switch_name)
133 self._logger.info("Terminating vswitchd...")
136 self._logger.info("Vswitchd...Terminated.")
138 def add_switch(self, switch_name, params=None):
139 """See IVswitch for general description
141 bridge = OFBridge(switch_name)
142 bridge.create(params)
143 bridge.set_db_attribute('Open_vSwitch', '.',
144 'other_config:max-idle',
145 settings.getValue('VSWITCH_FLOW_TIMEOUT'))
146 self._bridges[switch_name] = bridge
148 def del_switch(self, switch_name):
149 """See IVswitch for general description
151 bridge = self._bridges[switch_name]
153 for port in list(bridge.get_ports()):
154 bridge.del_port(port)
155 self._bridges.pop(switch_name)
158 def add_phy_port(self, switch_name):
159 """See IVswitch for general description
161 raise NotImplementedError
163 def add_vport(self, switch_name):
164 """See IVswitch for general description
166 raise NotImplementedError
168 def add_veth_pair_port(self, switch_name=None, remote_switch_name=None,
169 local_opts=None, remote_opts=None):
170 """Creates veth-pair port between 'switch_name' and 'remote_switch_name'
173 if switch_name is None or remote_switch_name is None:
176 bridge = self._bridges[switch_name]
177 remote_bridge = self._bridges[remote_switch_name]
178 pcount = str(self._get_port_count('type=patch'))
179 # NOTE ::: What if interface name longer than allowed width??
180 local_port_name = switch_name + '-' + remote_switch_name + '-' + pcount
181 remote_port_name = remote_switch_name + '-' + switch_name + '-' + pcount
182 local_params = ['--', 'set', 'Interface', local_port_name,
184 'options:peer=' + remote_port_name]
185 remote_params = ['--', 'set', 'Interface', remote_port_name,
187 'options:peer=' + local_port_name]
189 if local_opts is not None:
190 local_params = local_params + local_opts
192 if remote_opts is not None:
193 remote_params = remote_params + remote_opts
195 local_of_port = bridge.add_port(local_port_name, local_params)
196 remote_of_port = remote_bridge.add_port(remote_port_name, remote_params)
197 return [(local_port_name, local_of_port),
198 (remote_port_name, remote_of_port)]
200 def add_tunnel_port(self, switch_name, remote_ip, tunnel_type='vxlan', params=None):
201 """Creates tunneling port
203 bridge = self._bridges[switch_name]
204 pcount = str(self._get_port_count('type=' + tunnel_type))
205 port_name = tunnel_type + pcount
206 local_params = ['--', 'set', 'Interface', port_name,
207 'type=' + tunnel_type,
208 'options:remote_ip=' + remote_ip]
210 if params is not None:
211 local_params = local_params + params
213 of_port = bridge.add_port(port_name, local_params)
214 return (port_name, of_port)
216 def get_ports(self, switch_name):
217 """See IVswitch for general description
219 bridge = self._bridges[switch_name]
220 ports = list(bridge.get_ports().items())
221 return [(name, of_port) for (name, (of_port, _)) in ports]
223 def del_port(self, switch_name, port_name):
224 """See IVswitch for general description
226 bridge = self._bridges[switch_name]
227 bridge.del_port(port_name)
229 def add_flow(self, switch_name, flow, cache='off'):
230 """See IVswitch for general description
232 bridge = self._bridges[switch_name]
233 bridge.add_flow(flow, cache=cache)
235 def del_flow(self, switch_name, flow=None):
236 """See IVswitch for general description
239 bridge = self._bridges[switch_name]
240 bridge.del_flow(flow)
242 def dump_flows(self, switch_name):
243 """See IVswitch for general description
245 bridge = self._bridges[switch_name]
248 def add_route(self, switch_name, network, destination):
249 """See IVswitch for general description
251 bridge = self._bridges[switch_name]
252 bridge.add_route(network, destination)
254 def set_tunnel_arp(self, ip_addr, mac_addr, switch_name):
255 """See IVswitch for general description
257 bridge = self._bridges[switch_name]
258 bridge.set_tunnel_arp(ip_addr, mac_addr, switch_name)
260 def _get_port_count(self, param):
261 """Returns the number of ports having a certain parameter
264 for k in self._bridges:
265 pparams = [c for (_, (_, c)) in list(self._bridges[k].get_ports().items())]
266 phits = [i for i in pparams if param in i]
273 def disable_stp(self, switch_name):
275 Disable stp protocol on the bridge
276 :param switch_name: bridge to disable stp
279 bridge = self._bridges[switch_name]
280 bridge.set_stp(False)
281 self._logger.info('Sleeping for 50 secs to allow stp to stop.')
282 time.sleep(50) # needs time to disable
284 def enable_stp(self, switch_name):
286 Enable stp protocol on the bridge
287 :param switch_name: bridge to enable stp
290 bridge = self._bridges[switch_name]
292 self._logger.info('Sleeping for 50 secs to allow stp to start.')
293 time.sleep(50) # needs time to enable
295 def disable_rstp(self, switch_name):
297 Disable rstp on the bridge
298 :param switch_name: bridge to disable rstp
301 bridge = self._bridges[switch_name]
302 bridge.set_rstp(False)
303 self._logger.info('Sleeping for 15 secs to allow rstp to stop.')
304 time.sleep(15) # needs time to disable
306 def enable_rstp(self, switch_name):
308 Enable rstp on the bridge
309 :param switch_name: bridge to enable rstp
312 bridge = self._bridges[switch_name]
313 bridge.set_rstp(True)
314 self._logger.info('Sleeping for 15 secs to allow rstp to start.')
315 time.sleep(15) # needs time to enable
317 def kill(self, signal='-15', sleep=10):
318 """Kill ``ovs-vswitchd`` and ``ovs-ovsdb`` instances if they are alive.
322 if os.path.isfile(self._vswitchd_pidfile_path):
323 self._logger.info('Killing ovs-vswitchd...')
324 with open(self._vswitchd_pidfile_path, "r") as pidfile:
325 vswitchd_pid = pidfile.read().strip()
326 tasks.terminate_task(vswitchd_pid, logger=self._logger)
328 self._kill_ovsdb() # ovsdb must be killed after vswitchd
330 # just for case, that sudo envelope has not been terminated yet
331 tasks.Process.kill(self, signal, sleep)
335 def _reset_ovsdb(self):
336 """Reset system for 'ovsdb'.
340 self._logger.info('Resetting system after last run...')
342 # create a backup of ovs_var_tmp and ovs_etc_tmp; It is
343 # essential for OVS installed from binary packages.
344 self._stamp = '{:%Y%m%d_%H%M%S}_{}'.format(datetime.datetime.now(),
345 random.randrange(1000, 9999))
346 for tmp_dir in ['ovs_var_tmp', 'ovs_etc_tmp']:
347 if os.path.exists(settings.getValue('TOOLS')[tmp_dir]):
348 orig_dir = os.path.normpath(settings.getValue('TOOLS')[tmp_dir])
349 self._logger.info('Creating backup of %s directory...', tmp_dir)
350 tasks.run_task(['sudo', 'mv', orig_dir, '{}.{}'.format(orig_dir, self._stamp)],
353 # create fresh tmp dirs
354 tasks.run_task(['sudo', 'mkdir', '-p', settings.getValue('TOOLS')['ovs_var_tmp']], self._logger)
355 tasks.run_task(['sudo', 'mkdir', '-p', settings.getValue('TOOLS')['ovs_etc_tmp']], self._logger)
357 self._logger.info('System reset after last run.')
359 def _start_ovsdb(self):
360 """Start ``ovsdb-server`` instance.
364 ovsdb_tool_bin = settings.getValue('TOOLS')['ovsdb-tool']
365 tasks.run_task(['sudo', ovsdb_tool_bin, 'create',
366 os.path.join(settings.getValue('TOOLS')['ovs_etc_tmp'], 'conf.db'),
367 settings.getValue('TOOLS')['ovsschema']],
369 'Creating ovsdb configuration database...')
371 ovsdb_server_bin = settings.getValue('TOOLS')['ovsdb-server']
373 tasks.run_background_task(
374 ['sudo', ovsdb_server_bin,
375 '--remote=punix:%s' % os.path.join(settings.getValue('TOOLS')['ovs_var_tmp'], 'db.sock'),
376 '--remote=db:Open_vSwitch,Open_vSwitch,manager_options',
377 '--pidfile=' + self._ovsdb_pidfile_path, '--overwrite-pidfile'],
379 'Starting ovsdb-server...')
381 def _kill_ovsdb(self):
382 """Kill ``ovsdb-server`` instance.
386 if os.path.isfile(self._ovsdb_pidfile_path):
387 with open(self._ovsdb_pidfile_path, "r") as pidfile:
388 ovsdb_pid = pidfile.read().strip()
390 self._logger.info("Killing ovsdb with pid: %s", ovsdb_pid)
393 tasks.terminate_task(ovsdb_pid, logger=self._logger)
395 # restore original content of ovs_var_tmp and ovs_etc_tmp; It is
396 # essential for OVS installed from binary packages.
398 for tmp_dir in ['ovs_var_tmp', 'ovs_etc_tmp']:
399 orig_dir = os.path.normpath(settings.getValue('TOOLS')[tmp_dir])
400 if os.path.exists('{}.{}'.format(orig_dir, self._stamp)):
401 self._logger.info('Restoring backup of %s directory...', tmp_dir)
402 tasks.run_task(['sudo', 'rm', '-rf', orig_dir], self._logger)
403 tasks.run_task(['sudo', 'mv', '{}.{}'.format(orig_dir, self._stamp), orig_dir],
407 def get_db_sock_path():
408 """Method returns location of db.sock file
410 :returns: path to db.sock file.
412 return os.path.join(settings.getValue('TOOLS')['ovs_var_tmp'], 'db.sock')
415 # validate methods required for integration testcases
417 def validate_add_switch(self, _dummy_result, switch_name, _dummy_params=None):
418 """Validate - Create a new logical switch with no ports
420 bridge = self._bridges[switch_name]
421 output = bridge.run_vsctl(['show'], check_error=True)
422 assert not output[1] # there shouldn't be any stderr, but in case
423 assert re.search('Bridge ["\']?%s["\']?' % switch_name, output[0]) is not None
426 # Method could be a function
427 # pylint: disable=no-self-use
428 def validate_del_switch(self, _dummy_result, switch_name):
429 """Validate removal of switch
431 bridge = OFBridge('tmp')
432 output = bridge.run_vsctl(['show'], check_error=True)
433 assert not output[1] # there shouldn't be any stderr, but in case
434 assert re.search('Bridge ["\']?%s["\']?' % switch_name, output[0]) is None
437 def validate_add_phy_port(self, result, switch_name):
438 """ Validate that physical port was added to bridge.
440 bridge = self._bridges[switch_name]
441 output = bridge.run_vsctl(['show'], check_error=True)
442 assert not output[1] # there shouldn't be any stderr, but in case
443 assert re.search('Port ["\']?%s["\']?' % result[0], output[0]) is not None
444 assert re.search('Interface ["\']?%s["\']?' % result[0], output[0]) is not None
447 def validate_add_vport(self, result, switch_name):
448 """ Validate that virtual port was added to bridge.
450 return self.validate_add_phy_port(result, switch_name)
452 def validate_del_port(self, _dummy_result, switch_name, port_name):
453 """ Validate that port_name was removed from bridge.
455 bridge = self._bridges[switch_name]
456 output = bridge.run_vsctl(['show'], check_error=True)
457 assert not output[1] # there shouldn't be any stderr, but in case
458 assert 'Port "%s"' % port_name not in output[0]
461 def validate_add_flow(self, _dummy_result, switch_name, flow, _dummy_cache='off'):
462 """ Validate insertion of the flow into the switch
465 if 'idle_timeout' in flow:
466 del flow['idle_timeout']
468 # Note: it should be possible to call `ovs-ofctl dump-flows switch flow`
469 # to verify flow insertion, but it doesn't accept the same flow syntax
470 # as add-flow, so we have to compare it the hard way
472 # get dump of flows and compare them one by one
473 flow_src = flow_key(flow)
474 bridge = self._bridges[switch_name]
475 output = bridge.run_ofctl(['dump-flows', switch_name], check_error=True)
476 for flow_dump in output[0].split('\n'):
477 if flow_match(flow_dump, flow_src):
478 # flow was added correctly
482 def validate_del_flow(self, _dummy_result, switch_name, flow=None):
483 """ Validate removal of the flow
486 # what else we can do?
488 return not self.validate_add_flow(_dummy_result, switch_name, flow)
490 def validate_dump_flows(self, _dummy_result, _dummy_switch_name):
491 """ Validate call of flow dump
495 def validate_disable_rstp(self, _dummy_result, switch_name):
496 """ Validate rstp disable
498 bridge = self._bridges[switch_name]
499 return 'rstp_enable : false' in ''.join(bridge.bridge_info())
501 def validate_enable_rstp(self, _dummy_result, switch_name):
502 """ Validate rstp enable
504 bridge = self._bridges[switch_name]
505 return 'rstp_enable : true' in ''.join(bridge.bridge_info())
507 def validate_disable_stp(self, _dummy_result, switch_name):
508 """ Validate stp disable
510 bridge = self._bridges[switch_name]
511 return 'stp_enable : false' in ''.join(bridge.bridge_info())
513 def validate_enable_stp(self, _dummy_result, switch_name):
514 """ Validate stp enable
516 bridge = self._bridges[switch_name]
517 return 'stp_enable : true' in ''.join(bridge.bridge_info())
519 def validate_restart(self, _dummy_result):