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