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