Merge "sriov: Determine path to the bind-tool"
[vswitchperf.git] / vswitches / ovs.py
1 # Copyright 2015-2017 Intel Corporation.
2 #
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
6 #
7 #   http://www.apache.org/licenses/LICENSE-2.0
8 #
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.
14
15 """VSPERF Open vSwitch base class
16 """
17
18 import logging
19 import os
20 import re
21 import time
22 import datetime
23 import random
24 import pexpect
25
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
34
35     The method docstrings document only considerations specific to this
36     implementation. For generic information of the nature of the methods,
37     see the interface.
38     """
39     _proc_name = 'ovs-vswitchd'
40
41     def __init__(self):
42         """See IVswitch for general description
43         """
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'],
47                                                 "ovsdb-server.pid")
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)
53         self._timeout = 30
54         self._bridges = {}
55         self._vswitchd_args = ['--pidfile=' + self._vswitchd_pidfile_path,
56                                '--overwrite-pidfile', '--log-file=' + self._logfile]
57         self._cmd = []
58         self._cmd_template = ['sudo', '-E', settings.getValue('TOOLS')['ovs-vswitchd']]
59         self._stamp = None
60         self._module_manager = ModuleManager()
61
62     def start(self):
63         """ Start ``ovsdb-server`` and ``ovs-vswitchd`` instance.
64
65         :raises: pexpect.EOF, pexpect.TIMEOUT
66         """
67         self._logger.info("Starting vswitchd...")
68
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'])
72
73         self._cmd = self._cmd_template + self._vswitchd_args
74
75         # DB must be started before vswitchd
76         self._reset_ovsdb()
77         self._start_ovsdb()
78
79         # DB must be up before vswitchd config is altered or vswitchd started
80         time.sleep(3)
81
82         self.configure()
83
84         try:
85             tasks.Process.start(self)
86             self.relinquish()
87         except (pexpect.EOF, pexpect.TIMEOUT) as exc:
88             logging.error("Exception during VSwitch start.")
89             self._kill_ovsdb()
90             raise exc
91
92         self._logger.info("Vswitchd...Started.")
93
94     def restart(self):
95         """ Restart ``ovs-vswitchd`` instance. ``ovsdb-server`` is not restarted.
96
97         :raises: pexpect.EOF, pexpect.TIMEOUT
98         """
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)
105
106         try:
107             tasks.Process.start(self)
108             self.relinquish()
109         except (pexpect.EOF, pexpect.TIMEOUT) as exc:
110             logging.error("Exception during VSwitch start.")
111             self._kill_ovsdb()
112             raise exc
113         self._logger.info("Vswitchd...Started.")
114
115     def configure(self):
116         """ Configure vswitchd through ovsdb if needed
117         """
118         pass
119
120     # Method could be a function
121     # pylint: disable=no-self-use
122     def get_version(self):
123         """See IVswitch for general description
124         """
125         # OVS version can be read offline
126         return []
127
128     def stop(self):
129         """See IVswitch for general description
130         """
131         self._logger.info("Terminating vswitchd...")
132         self.kill()
133         self._bridges = {}
134         self._logger.info("Vswitchd...Terminated.")
135
136     def add_switch(self, switch_name, params=None):
137         """See IVswitch for general description
138         """
139         bridge = OFBridge(switch_name)
140         bridge.create(params)
141         bridge.set_db_attribute('Open_vSwitch', '.',
142                                 'other_config:max-idle',
143                                 settings.getValue('VSWITCH_FLOW_TIMEOUT'))
144         self._bridges[switch_name] = bridge
145
146     def del_switch(self, switch_name):
147         """See IVswitch for general description
148         """
149         bridge = self._bridges[switch_name]
150         self._bridges.pop(switch_name)
151         bridge.destroy()
152
153     def add_phy_port(self, switch_name):
154         """See IVswitch for general description
155         """
156         raise NotImplementedError
157
158     def add_vport(self, switch_name):
159         """See IVswitch for general description
160         """
161         raise NotImplementedError
162
163     def add_veth_pair_port(self, switch_name=None, remote_switch_name=None,
164                            local_opts=None, remote_opts=None):
165         """Creates veth-pair port between 'switch_name' and 'remote_switch_name'
166
167         """
168         if switch_name is None or remote_switch_name is None:
169             return
170
171         bridge = self._bridges[switch_name]
172         remote_bridge = self._bridges[remote_switch_name]
173         pcount = str(self._get_port_count('type=patch'))
174         # NOTE ::: What if interface name longer than allowed width??
175         local_port_name = switch_name + '-' + remote_switch_name + '-' + pcount
176         remote_port_name = remote_switch_name + '-' + switch_name + '-' + pcount
177         local_params = ['--', 'set', 'Interface', local_port_name,
178                         'type=patch',
179                         'options:peer=' + remote_port_name]
180         remote_params = ['--', 'set', 'Interface', remote_port_name,
181                          'type=patch',
182                          'options:peer=' + local_port_name]
183
184         if local_opts is not None:
185             local_params = local_params + local_opts
186
187         if remote_opts is not None:
188             remote_params = remote_params + remote_opts
189
190         local_of_port = bridge.add_port(local_port_name, local_params)
191         remote_of_port = remote_bridge.add_port(remote_port_name, remote_params)
192         return [(local_port_name, local_of_port),
193                 (remote_port_name, remote_of_port)]
194
195     def add_tunnel_port(self, switch_name, remote_ip, tunnel_type='vxlan', params=None):
196         """Creates tunneling port
197         """
198         bridge = self._bridges[switch_name]
199         pcount = str(self._get_port_count('type=' + tunnel_type))
200         port_name = tunnel_type + pcount
201         local_params = ['--', 'set', 'Interface', port_name,
202                         'type=' + tunnel_type,
203                         'options:remote_ip=' + remote_ip]
204
205         if params is not None:
206             local_params = local_params + params
207
208         of_port = bridge.add_port(port_name, local_params)
209         return (port_name, of_port)
210
211     def get_ports(self, switch_name):
212         """See IVswitch for general description
213         """
214         bridge = self._bridges[switch_name]
215         ports = list(bridge.get_ports().items())
216         return [(name, of_port) for (name, (of_port, _)) in ports]
217
218     def del_port(self, switch_name, port_name):
219         """See IVswitch for general description
220         """
221         bridge = self._bridges[switch_name]
222         bridge.del_port(port_name)
223
224     def add_flow(self, switch_name, flow, cache='off'):
225         """See IVswitch for general description
226         """
227         bridge = self._bridges[switch_name]
228         bridge.add_flow(flow, cache=cache)
229
230     def del_flow(self, switch_name, flow=None):
231         """See IVswitch for general description
232         """
233         flow = flow or {}
234         bridge = self._bridges[switch_name]
235         bridge.del_flow(flow)
236
237     def dump_flows(self, switch_name):
238         """See IVswitch for general description
239         """
240         bridge = self._bridges[switch_name]
241         bridge.dump_flows()
242
243     def add_route(self, switch_name, network, destination):
244         """See IVswitch for general description
245         """
246         bridge = self._bridges[switch_name]
247         bridge.add_route(network, destination)
248
249     def set_tunnel_arp(self, ip_addr, mac_addr, switch_name):
250         """See IVswitch for general description
251         """
252         bridge = self._bridges[switch_name]
253         bridge.set_tunnel_arp(ip_addr, mac_addr, switch_name)
254
255     def _get_port_count(self, param):
256         """Returns the number of ports having a certain parameter
257         """
258         cnt = 0
259         for k in self._bridges:
260             pparams = [c for (_, (_, c)) in list(self._bridges[k].get_ports().items())]
261             phits = [i for i in pparams if param in i]
262             cnt += len(phits)
263
264         if cnt is None:
265             cnt = 0
266         return cnt
267
268     def disable_stp(self, switch_name):
269         """
270         Disable stp protocol on the bridge
271         :param switch_name: bridge to disable stp
272         :return: None
273         """
274         bridge = self._bridges[switch_name]
275         bridge.set_stp(False)
276         self._logger.info('Sleeping for 50 secs to allow stp to stop.')
277         time.sleep(50)  # needs time to disable
278
279     def enable_stp(self, switch_name):
280         """
281         Enable stp protocol on the bridge
282         :param switch_name: bridge to enable stp
283         :return: None
284         """
285         bridge = self._bridges[switch_name]
286         bridge.set_stp(True)
287         self._logger.info('Sleeping for 50 secs to allow stp to start.')
288         time.sleep(50)  # needs time to enable
289
290     def disable_rstp(self, switch_name):
291         """
292         Disable rstp on the bridge
293         :param switch_name: bridge to disable rstp
294         :return: None
295         """
296         bridge = self._bridges[switch_name]
297         bridge.set_rstp(False)
298         self._logger.info('Sleeping for 15 secs to allow rstp to stop.')
299         time.sleep(15)  # needs time to disable
300
301     def enable_rstp(self, switch_name):
302         """
303         Enable rstp on the bridge
304         :param switch_name: bridge to enable rstp
305         :return: None
306         """
307         bridge = self._bridges[switch_name]
308         bridge.set_rstp(True)
309         self._logger.info('Sleeping for 15 secs to allow rstp to start.')
310         time.sleep(15)  # needs time to enable
311
312     def kill(self, signal='-15', sleep=10):
313         """Kill ``ovs-vswitchd`` and ``ovs-ovsdb`` instances if they are alive.
314
315         :returns: None
316         """
317         if os.path.isfile(self._vswitchd_pidfile_path):
318             self._logger.info('Killing ovs-vswitchd...')
319             with open(self._vswitchd_pidfile_path, "r") as pidfile:
320                 vswitchd_pid = pidfile.read().strip()
321                 tasks.terminate_task(vswitchd_pid, logger=self._logger)
322
323         self._kill_ovsdb()  # ovsdb must be killed after vswitchd
324
325         # just for case, that sudo envelope has not been terminated yet
326         tasks.Process.kill(self, signal, sleep)
327
328     # helper functions
329
330     def _reset_ovsdb(self):
331         """Reset system for 'ovsdb'.
332
333         :returns: None
334         """
335         self._logger.info('Resetting system after last run...')
336
337         # create a backup of ovs_var_tmp and ovs_etc_tmp; It is
338         # essential for OVS installed from binary packages.
339         self._stamp = '{:%Y%m%d_%H%M%S}_{}'.format(datetime.datetime.now(),
340                                                    random.randrange(1000, 9999))
341         for tmp_dir in ['ovs_var_tmp', 'ovs_etc_tmp']:
342             if os.path.exists(settings.getValue('TOOLS')[tmp_dir]):
343                 orig_dir = os.path.normpath(settings.getValue('TOOLS')[tmp_dir])
344                 self._logger.info('Creating backup of %s directory...', tmp_dir)
345                 tasks.run_task(['sudo', 'mv', orig_dir, '{}.{}'.format(orig_dir, self._stamp)],
346                                self._logger)
347
348         # create fresh tmp dirs
349         tasks.run_task(['sudo', 'mkdir', '-p', settings.getValue('TOOLS')['ovs_var_tmp']], self._logger)
350         tasks.run_task(['sudo', 'mkdir', '-p', settings.getValue('TOOLS')['ovs_etc_tmp']], self._logger)
351
352         self._logger.info('System reset after last run.')
353
354     def _start_ovsdb(self):
355         """Start ``ovsdb-server`` instance.
356
357         :returns: None
358         """
359         ovsdb_tool_bin = settings.getValue('TOOLS')['ovsdb-tool']
360         tasks.run_task(['sudo', ovsdb_tool_bin, 'create',
361                         os.path.join(settings.getValue('TOOLS')['ovs_etc_tmp'], 'conf.db'),
362                         settings.getValue('TOOLS')['ovsschema']],
363                        self._logger,
364                        'Creating ovsdb configuration database...')
365
366         ovsdb_server_bin = settings.getValue('TOOLS')['ovsdb-server']
367
368         tasks.run_background_task(
369             ['sudo', ovsdb_server_bin,
370              '--remote=punix:%s' % os.path.join(settings.getValue('TOOLS')['ovs_var_tmp'], 'db.sock'),
371              '--remote=db:Open_vSwitch,Open_vSwitch,manager_options',
372              '--pidfile=' + self._ovsdb_pidfile_path, '--overwrite-pidfile'],
373             self._logger,
374             'Starting ovsdb-server...')
375
376     def _kill_ovsdb(self):
377         """Kill ``ovsdb-server`` instance.
378
379         :returns: None
380         """
381         if os.path.isfile(self._ovsdb_pidfile_path):
382             with open(self._ovsdb_pidfile_path, "r") as pidfile:
383                 ovsdb_pid = pidfile.read().strip()
384
385             self._logger.info("Killing ovsdb with pid: " + ovsdb_pid)
386
387             if ovsdb_pid:
388                 tasks.terminate_task(ovsdb_pid, logger=self._logger)
389
390         # restore original content of ovs_var_tmp and ovs_etc_tmp; It is
391         # essential for OVS installed from binary packages.
392         if self._stamp:
393             for tmp_dir in ['ovs_var_tmp', 'ovs_etc_tmp']:
394                 orig_dir = os.path.normpath(settings.getValue('TOOLS')[tmp_dir])
395                 if os.path.exists('{}.{}'.format(orig_dir, self._stamp)):
396                     self._logger.info('Restoring backup of %s directory...', tmp_dir)
397                     tasks.run_task(['sudo', 'rm', '-rf', orig_dir], self._logger)
398                     tasks.run_task(['sudo', 'mv', '{}.{}'.format(orig_dir, self._stamp), orig_dir],
399                                    self._logger)
400
401     @staticmethod
402     def get_db_sock_path():
403         """Method returns location of db.sock file
404
405         :returns: path to db.sock file.
406         """
407         return os.path.join(settings.getValue('TOOLS')['ovs_var_tmp'], 'db.sock')
408
409     #
410     # validate methods required for integration testcases
411     #
412     def validate_add_switch(self, dummy_result, switch_name, dummy_params=None):
413         """Validate - Create a new logical switch with no ports
414         """
415         bridge = self._bridges[switch_name]
416         output = bridge.run_vsctl(['show'], check_error=True)
417         assert not output[1]  # there shouldn't be any stderr, but in case
418         assert re.search('Bridge ["\']?%s["\']?' % switch_name, output[0]) is not None
419         return True
420
421     # Method could be a function
422     # pylint: disable=no-self-use
423     def validate_del_switch(self, dummy_result, switch_name):
424         """Validate removal of switch
425         """
426         bridge = OFBridge('tmp')
427         output = bridge.run_vsctl(['show'], check_error=True)
428         assert not output[1]  # there shouldn't be any stderr, but in case
429         assert re.search('Bridge ["\']?%s["\']?' % switch_name, output[0]) is None
430         return True
431
432     def validate_add_phy_port(self, result, switch_name):
433         """ Validate that physical port was added to bridge.
434         """
435         bridge = self._bridges[switch_name]
436         output = bridge.run_vsctl(['show'], check_error=True)
437         assert not output[1]  # there shouldn't be any stderr, but in case
438         assert re.search('Port ["\']?%s["\']?' % result[0], output[0]) is not None
439         assert re.search('Interface ["\']?%s["\']?' % result[0], output[0]) is not None
440         return True
441
442     def validate_add_vport(self, result, switch_name):
443         """ Validate that virtual port was added to bridge.
444         """
445         return self.validate_add_phy_port(result, switch_name)
446
447     def validate_del_port(self, dummy_result, switch_name, port_name):
448         """ Validate that port_name was removed from bridge.
449         """
450         bridge = self._bridges[switch_name]
451         output = bridge.run_vsctl(['show'], check_error=True)
452         assert not output[1]  # there shouldn't be any stderr, but in case
453         assert 'Port "%s"' % port_name not in output[0]
454         return True
455
456     def validate_add_flow(self, dummy_result, switch_name, flow, dummy_cache='off'):
457         """ Validate insertion of the flow into the switch
458         """
459
460         if 'idle_timeout' in flow:
461             del flow['idle_timeout']
462
463         # Note: it should be possible to call `ovs-ofctl dump-flows switch flow`
464         # to verify flow insertion, but it doesn't accept the same flow syntax
465         # as add-flow, so we have to compare it the hard way
466
467         # get dump of flows and compare them one by one
468         flow_src = flow_key(flow)
469         bridge = self._bridges[switch_name]
470         output = bridge.run_ofctl(['dump-flows', switch_name], check_error=True)
471         for flow_dump in output[0].split('\n'):
472             if flow_match(flow_dump, flow_src):
473                 # flow was added correctly
474                 return True
475         return False
476
477     def validate_del_flow(self, dummy_result, switch_name, flow=None):
478         """ Validate removal of the flow
479         """
480         if not flow:
481             # what else we can do?
482             return True
483         return not self.validate_add_flow(dummy_result, switch_name, flow)
484
485     def validate_dump_flows(self, dummy_result, dummy_switch_name):
486         """ Validate call of flow dump
487         """
488         return True
489
490     def validate_disable_rstp(self, dummy_result, switch_name):
491         """ Validate rstp disable
492         """
493         bridge = self._bridges[switch_name]
494         return 'rstp_enable         : false' in ''.join(bridge.bridge_info())
495
496     def validate_enable_rstp(self, dummy_result, switch_name):
497         """ Validate rstp enable
498         """
499         bridge = self._bridges[switch_name]
500         return 'rstp_enable         : true' in ''.join(bridge.bridge_info())
501
502     def validate_disable_stp(self, dummy_result, switch_name):
503         """ Validate stp disable
504         """
505         bridge = self._bridges[switch_name]
506         return 'stp_enable          : false' in ''.join(bridge.bridge_info())
507
508     def validate_enable_stp(self, dummy_result, switch_name):
509         """ Validate stp enable
510         """
511         bridge = self._bridges[switch_name]
512         return 'stp_enable          : true' in ''.join(bridge.bridge_info())
513
514     def validate_restart(self, dummy_result):
515         """ Validate restart
516         """
517         return True