docs: add vm2vm testing methodology
[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_tunnel_port(self, switch_name, remote_ip, tunnel_type='vxlan', params=None):
126         """Creates tunneling port
127         """
128         bridge = self._bridges[switch_name]
129         pcount = str(self._get_port_count('type=' + tunnel_type))
130         port_name = tunnel_type + pcount
131         local_params = ['--', 'set', 'Interface', port_name,
132                         'type=' + tunnel_type,
133                         'options:remote_ip=' + remote_ip]
134
135         if params is not None:
136             local_params = local_params + params
137
138         of_port = bridge.add_port(port_name, local_params)
139         return (port_name, of_port)
140
141     def get_ports(self, switch_name):
142         """See IVswitch for general description
143         """
144         bridge = self._bridges[switch_name]
145         ports = list(bridge.get_ports().items())
146         return [(name, of_port) for (name, (of_port, _)) in ports]
147
148     def del_port(self, switch_name, port_name):
149         """See IVswitch for general description
150         """
151         bridge = self._bridges[switch_name]
152         bridge.del_port(port_name)
153
154     def add_flow(self, switch_name, flow, cache='off'):
155         """See IVswitch for general description
156         """
157         bridge = self._bridges[switch_name]
158         bridge.add_flow(flow, cache=cache)
159
160     def del_flow(self, switch_name, flow=None):
161         """See IVswitch for general description
162         """
163         flow = flow or {}
164         bridge = self._bridges[switch_name]
165         bridge.del_flow(flow)
166
167     def dump_flows(self, switch_name):
168         """See IVswitch for general description
169         """
170         bridge = self._bridges[switch_name]
171         bridge.dump_flows()
172
173     def add_route(self, switch_name, network, destination):
174         """See IVswitch for general description
175         """
176         bridge = self._bridges[switch_name]
177         bridge.add_route(network, destination)
178
179     def set_tunnel_arp(self, ip_addr, mac_addr, switch_name):
180         """See IVswitch for general description
181         """
182         bridge = self._bridges[switch_name]
183         bridge.set_tunnel_arp(ip_addr, mac_addr, switch_name)
184
185     def _get_port_count(self, param):
186         """Returns the number of ports having a certain parameter
187         """
188         cnt = 0
189         for k in self._bridges:
190             pparams = [c for (_, (_, c)) in list(self._bridges[k].get_ports().items())]
191             phits = [i for i in pparams if param in i]
192             cnt += len(phits)
193
194         if cnt is None:
195             cnt = 0
196         return cnt
197
198     def disable_stp(self, switch_name):
199         """
200         Disable stp protocol on the bridge
201         :param switch_name: bridge to disable stp
202         :return: None
203         """
204         bridge = self._bridges[switch_name]
205         bridge.set_stp(False)
206         self._logger.info('Sleeping for 50 secs to allow stp to stop.')
207         time.sleep(50)  # needs time to disable
208
209     def enable_stp(self, switch_name):
210         """
211         Enable stp protocol on the bridge
212         :param switch_name: bridge to enable stp
213         :return: None
214         """
215         bridge = self._bridges[switch_name]
216         bridge.set_stp(True)
217         self._logger.info('Sleeping for 50 secs to allow stp to start.')
218         time.sleep(50)  # needs time to enable
219
220     def disable_rstp(self, switch_name):
221         """
222         Disable rstp on the bridge
223         :param switch_name: bridge to disable rstp
224         :return: None
225         """
226         bridge = self._bridges[switch_name]
227         bridge.set_rstp(False)
228         self._logger.info('Sleeping for 15 secs to allow rstp to stop.')
229         time.sleep(15)  # needs time to disable
230
231     def enable_rstp(self, switch_name):
232         """
233         Enable rstp on the bridge
234         :param switch_name: bridge to enable rstp
235         :return: None
236         """
237         bridge = self._bridges[switch_name]
238         bridge.set_rstp(True)
239         self._logger.info('Sleeping for 15 secs to allow rstp to start.')
240         time.sleep(15)  # needs time to enable
241
242     def kill(self, signal='-15', sleep=10):
243         """Kill ``ovs-vswitchd`` and ``ovs-ovsdb`` instances if they are alive.
244
245         :returns: None
246         """
247         if os.path.isfile(self._vswitchd_pidfile_path):
248             self._logger.info('Killing ovs-vswitchd...')
249             with open(self._vswitchd_pidfile_path, "r") as pidfile:
250                 vswitchd_pid = pidfile.read().strip()
251                 tasks.terminate_task(vswitchd_pid, logger=self._logger)
252
253         self._kill_ovsdb()  # ovsdb must be killed after vswitchd
254
255         # just for case, that sudo envelope has not been terminated yet
256         tasks.Process.kill(self, signal, sleep)
257
258     # helper functions
259
260     def _reset_ovsdb(self):
261         """Reset system for 'ovsdb'.
262
263         :returns: None
264         """
265         self._logger.info('Resetting system after last run...')
266
267         tasks.run_task(['sudo', 'rm', '-rf', _OVS_VAR_DIR], self._logger)
268         tasks.run_task(['sudo', 'mkdir', '-p', _OVS_VAR_DIR], self._logger)
269         tasks.run_task(['sudo', 'rm', '-rf', _OVS_ETC_DIR], self._logger)
270         tasks.run_task(['sudo', 'mkdir', '-p', _OVS_ETC_DIR], self._logger)
271
272         tasks.run_task(['sudo', 'rm', '-f',
273                         os.path.join(_OVS_ETC_DIR, 'conf.db')],
274                        self._logger)
275
276         self._logger.info('System reset after last run.')
277
278     def _start_ovsdb(self):
279         """Start ``ovsdb-server`` instance.
280
281         :returns: None
282         """
283         ovsdb_tool_bin = os.path.join(
284             settings.getValue('OVS_DIR'), 'ovsdb', 'ovsdb-tool')
285         tasks.run_task(['sudo', ovsdb_tool_bin, 'create',
286                         os.path.join(_OVS_ETC_DIR, 'conf.db'),
287                         os.path.join(settings.getValue('OVS_DIR'), 'vswitchd',
288                                      'vswitch.ovsschema')],
289                        self._logger,
290                        'Creating ovsdb configuration database...')
291
292         ovsdb_server_bin = os.path.join(
293             settings.getValue('OVS_DIR'), 'ovsdb', 'ovsdb-server')
294
295         tasks.run_background_task(
296             ['sudo', ovsdb_server_bin,
297              '--remote=punix:%s' % os.path.join(_OVS_VAR_DIR, 'db.sock'),
298              '--remote=db:Open_vSwitch,Open_vSwitch,manager_options',
299              '--pidfile=' + self._ovsdb_pidfile_path, '--overwrite-pidfile'],
300             self._logger,
301             'Starting ovsdb-server...')
302
303     def _kill_ovsdb(self):
304         """Kill ``ovsdb-server`` instance.
305
306         :returns: None
307         """
308         if os.path.isfile(self._ovsdb_pidfile_path):
309             with open(self._ovsdb_pidfile_path, "r") as pidfile:
310                 ovsdb_pid = pidfile.read().strip()
311
312             self._logger.info("Killing ovsdb with pid: " + ovsdb_pid)
313
314             if ovsdb_pid:
315                 tasks.terminate_task(ovsdb_pid, logger=self._logger)
316
317     @staticmethod
318     def get_db_sock_path():
319         """Method returns location of db.sock file
320
321         :returns: path to db.sock file.
322         """
323         return os.path.join(_OVS_VAR_DIR, 'db.sock')
324
325     #
326     # validate methods required for integration testcases
327     #
328
329     def validate_add_switch(self, result, switch_name, params=None):
330         """Validate - Create a new logical switch with no ports
331         """
332         bridge = self._bridges[switch_name]
333         output = bridge.run_vsctl(['show'], check_error=True)
334         assert not output[1]  # there shouldn't be any stderr, but in case
335         assert re.search('Bridge ["\']?%s["\']?' % switch_name, output[0]) is not None
336         return True
337
338     def validate_del_switch(self, result, switch_name):
339         """Validate removal of switch
340         """
341         bridge = OFBridge('tmp')
342         output = bridge.run_vsctl(['show'], check_error=True)
343         assert not output[1]  # there shouldn't be any stderr, but in case
344         assert re.search('Bridge ["\']?%s["\']?' % switch_name, output[0]) is None
345         return True
346
347     def validate_add_phy_port(self, result, switch_name):
348         """ Validate that physical port was added to bridge.
349         """
350         bridge = self._bridges[switch_name]
351         output = bridge.run_vsctl(['show'], check_error=True)
352         assert not output[1]  # there shouldn't be any stderr, but in case
353         assert re.search('Port ["\']?%s["\']?' % result[0], output[0]) is not None
354         assert re.search('Interface ["\']?%s["\']?' % result[0], output[0]) is not None
355         return True
356
357     def validate_add_vport(self, result, switch_name):
358         """ Validate that virtual port was added to bridge.
359         """
360         return self.validate_add_phy_port(result, switch_name)
361
362     def validate_del_port(self, result, switch_name, port_name):
363         """ Validate that port_name was removed from bridge.
364         """
365         bridge = self._bridges[switch_name]
366         output = bridge.run_vsctl(['show'], check_error=True)
367         assert not output[1]  # there shouldn't be any stderr, but in case
368         assert 'Port "%s"' % port_name not in output[0]
369         return True
370
371     def validate_add_flow(self, result, switch_name, flow, cache='off'):
372         """ Validate insertion of the flow into the switch
373         """
374         if 'idle_timeout' in flow:
375             del(flow['idle_timeout'])
376
377         # Note: it should be possible to call `ovs-ofctl dump-flows switch flow`
378         # to verify flow insertion, but it doesn't accept the same flow syntax
379         # as add-flow, so we have to compare it the hard way
380
381         # get dump of flows and compare them one by one
382         flow_src = flow_key(flow)
383         bridge = self._bridges[switch_name]
384         output = bridge.run_ofctl(['dump-flows', switch_name], check_error=True)
385         for flow_dump in output[0].split('\n'):
386             if flow_match(flow_dump, flow_src):
387                 # flow was added correctly
388                 return True
389         return False
390
391     def validate_del_flow(self, result, switch_name, flow=None):
392         """ Validate removal of the flow
393         """
394         if not flow:
395             # what else we can do?
396             return True
397         return not self.validate_add_flow(result, switch_name, flow)
398
399     def validate_dump_flows(self, result, switch_name):
400         """ Validate call of flow dump
401         """
402         return True
403
404     def validate_disable_rstp(self, result, switch_name):
405         """ Validate rstp disable
406         """
407         bridge = self._bridges[switch_name]
408         return 'rstp_enable         : false' in ''.join(bridge.bridge_info())
409
410     def validate_enable_rstp(self, result, switch_name):
411         """ Validate rstp enable
412         """
413         bridge = self._bridges[switch_name]
414         return 'rstp_enable         : true' in ''.join(bridge.bridge_info())
415
416     def validate_disable_stp(self, result, switch_name):
417         """ Validate stp disable
418         """
419         bridge = self._bridges[switch_name]
420         return 'stp_enable          : false' in ''.join(bridge.bridge_info())
421
422     def validate_enable_stp(self, result, switch_name):
423         """ Validate stp enable
424         """
425         bridge = self._bridges[switch_name]
426         return 'stp_enable          : true' in ''.join(bridge.bridge_info())