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