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