1 # Copyright 2015-2016 Intel Corporation.
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
7 # http://www.apache.org/licenses/LICENSE-2.0
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.
15 """VSPERF Open vSwitch base class
26 from conf import settings
27 from src.ovs import OFBridge, flow_key, flow_match
28 from tools import tasks
29 from vswitches.vswitch import IVSwitch
31 class IVSwitchOvs(IVSwitch, tasks.Process):
32 """Open vSwitch base class implementation
34 The method docstrings document only considerations specific to this
35 implementation. For generic information of the nature of the methods,
38 _proc_name = 'ovs-vswitchd'
41 """See IVswitch for general description
43 self._logfile = os.path.join(settings.getValue('LOG_DIR'),
44 settings.getValue('LOG_FILE_VSWITCHD'))
45 self._ovsdb_pidfile_path = os.path.join(settings.getValue('TOOLS')['ovs_var_tmp'],
47 self._vswitchd_pidfile_path = os.path.join(settings.getValue('TOOLS')['ovs_var_tmp'],
48 "{}.pid".format(self._proc_name))
49 self._logger = logging.getLogger(__name__)
50 self._expect = r'bridge|INFO|{}'.format(self._proc_name)
53 self._vswitchd_args = ['--pidfile=' + self._vswitchd_pidfile_path,
54 '--overwrite-pidfile', '--log-file=' + self._logfile]
56 self._cmd_template = ['sudo', '-E', settings.getValue('TOOLS')['ovs-vswitchd']]
60 """ Start ``ovsdb-server`` and ``ovs-vswitchd`` instance.
62 :raises: pexpect.EOF, pexpect.TIMEOUT
64 self._logger.info("Starting vswitchd...")
66 # insert kernel modules if required
67 if 'vswitch_modules' in settings.getValue('TOOLS'):
68 self._module_manager.insert_modules(settings.getValue('TOOLS')['vswitch_modules'])
70 self._cmd = self._cmd_template + self._vswitchd_args
72 # DB must be started before vswitchd
76 # DB must be up before vswitchd config is altered or vswitchd started
82 tasks.Process.start(self)
84 except (pexpect.EOF, pexpect.TIMEOUT) as exc:
85 logging.error("Exception during VSwitch start.")
89 self._logger.info("Vswitchd...Started.")
92 """ Configure vswitchd through ovsdb if needed
97 """See IVswitch for general description
99 self._logger.info("Terminating vswitchd...")
101 self._logger.info("Vswitchd...Terminated.")
103 def add_switch(self, switch_name, params=None):
104 """See IVswitch for general description
106 bridge = OFBridge(switch_name)
107 bridge.create(params)
108 bridge.set_db_attribute('Open_vSwitch', '.',
109 'other_config:max-idle',
110 settings.getValue('VSWITCH_FLOW_TIMEOUT'))
111 self._bridges[switch_name] = bridge
113 def del_switch(self, switch_name):
114 """See IVswitch for general description
116 bridge = self._bridges[switch_name]
117 self._bridges.pop(switch_name)
120 def add_phy_port(self, switch_name):
121 """See IVswitch for general description
123 raise NotImplementedError
125 def add_vport(self, switch_name):
126 """See IVswitch for general description
128 raise NotImplementedError
130 def add_veth_pair_port(self, switch_name=None, remote_switch_name=None,
131 local_opts=None, remote_opts=None):
132 """Creates veth-pair port between 'switch_name' and 'remote_switch_name'
135 if switch_name is None or remote_switch_name is None:
138 bridge = self._bridges[switch_name]
139 remote_bridge = self._bridges[remote_switch_name]
140 pcount = str(self._get_port_count('type=patch'))
141 # TODO ::: What if interface name longer than allowed width??
142 local_port_name = switch_name + '-' + remote_switch_name + '-' + pcount
143 remote_port_name = remote_switch_name + '-' + switch_name + '-' + pcount
144 local_params = ['--', 'set', 'Interface', local_port_name,
146 'options:peer=' + remote_port_name]
147 remote_params = ['--', 'set', 'Interface', remote_port_name,
149 'options:peer=' + local_port_name]
151 if local_opts is not None:
152 local_params = local_params + local_opts
154 if remote_opts is not None:
155 remote_params = remote_params + remote_opts
157 local_of_port = bridge.add_port(local_port_name, local_params)
158 remote_of_port = remote_bridge.add_port(remote_port_name, remote_params)
159 return [(local_port_name, local_of_port),
160 (remote_port_name, remote_of_port)]
162 def add_tunnel_port(self, switch_name, remote_ip, tunnel_type='vxlan', params=None):
163 """Creates tunneling port
165 bridge = self._bridges[switch_name]
166 pcount = str(self._get_port_count('type=' + tunnel_type))
167 port_name = tunnel_type + pcount
168 local_params = ['--', 'set', 'Interface', port_name,
169 'type=' + tunnel_type,
170 'options:remote_ip=' + remote_ip]
172 if params is not None:
173 local_params = local_params + params
175 of_port = bridge.add_port(port_name, local_params)
176 return (port_name, of_port)
178 def get_ports(self, switch_name):
179 """See IVswitch for general description
181 bridge = self._bridges[switch_name]
182 ports = list(bridge.get_ports().items())
183 return [(name, of_port) for (name, (of_port, _)) in ports]
185 def del_port(self, switch_name, port_name):
186 """See IVswitch for general description
188 bridge = self._bridges[switch_name]
189 bridge.del_port(port_name)
191 def add_flow(self, switch_name, flow, cache='off'):
192 """See IVswitch for general description
194 bridge = self._bridges[switch_name]
195 bridge.add_flow(flow, cache=cache)
197 def del_flow(self, switch_name, flow=None):
198 """See IVswitch for general description
201 bridge = self._bridges[switch_name]
202 bridge.del_flow(flow)
204 def dump_flows(self, switch_name):
205 """See IVswitch for general description
207 bridge = self._bridges[switch_name]
210 def add_route(self, switch_name, network, destination):
211 """See IVswitch for general description
213 bridge = self._bridges[switch_name]
214 bridge.add_route(network, destination)
216 def set_tunnel_arp(self, ip_addr, mac_addr, switch_name):
217 """See IVswitch for general description
219 bridge = self._bridges[switch_name]
220 bridge.set_tunnel_arp(ip_addr, mac_addr, switch_name)
222 def _get_port_count(self, param):
223 """Returns the number of ports having a certain parameter
226 for k in self._bridges:
227 pparams = [c for (_, (_, c)) in list(self._bridges[k].get_ports().items())]
228 phits = [i for i in pparams if param in i]
235 def disable_stp(self, switch_name):
237 Disable stp protocol on the bridge
238 :param switch_name: bridge to disable stp
241 bridge = self._bridges[switch_name]
242 bridge.set_stp(False)
243 self._logger.info('Sleeping for 50 secs to allow stp to stop.')
244 time.sleep(50) # needs time to disable
246 def enable_stp(self, switch_name):
248 Enable stp protocol on the bridge
249 :param switch_name: bridge to enable stp
252 bridge = self._bridges[switch_name]
254 self._logger.info('Sleeping for 50 secs to allow stp to start.')
255 time.sleep(50) # needs time to enable
257 def disable_rstp(self, switch_name):
259 Disable rstp on the bridge
260 :param switch_name: bridge to disable rstp
263 bridge = self._bridges[switch_name]
264 bridge.set_rstp(False)
265 self._logger.info('Sleeping for 15 secs to allow rstp to stop.')
266 time.sleep(15) # needs time to disable
268 def enable_rstp(self, switch_name):
270 Enable rstp on the bridge
271 :param switch_name: bridge to enable rstp
274 bridge = self._bridges[switch_name]
275 bridge.set_rstp(True)
276 self._logger.info('Sleeping for 15 secs to allow rstp to start.')
277 time.sleep(15) # needs time to enable
279 def kill(self, signal='-15', sleep=10):
280 """Kill ``ovs-vswitchd`` and ``ovs-ovsdb`` instances if they are alive.
284 if os.path.isfile(self._vswitchd_pidfile_path):
285 self._logger.info('Killing ovs-vswitchd...')
286 with open(self._vswitchd_pidfile_path, "r") as pidfile:
287 vswitchd_pid = pidfile.read().strip()
288 tasks.terminate_task(vswitchd_pid, logger=self._logger)
290 self._kill_ovsdb() # ovsdb must be killed after vswitchd
292 # just for case, that sudo envelope has not been terminated yet
293 tasks.Process.kill(self, signal, sleep)
297 def _reset_ovsdb(self):
298 """Reset system for 'ovsdb'.
302 self._logger.info('Resetting system after last run...')
304 # create a backup of ovs_var_tmp and ovs_etc_tmp; It is
305 # essential for OVS installed from binary packages.
306 self._stamp = '{:%Y%m%d_%H%M%S}_{}'.format(datetime.datetime.now(),
307 random.randrange(1000, 9999))
308 for tmp_dir in ['ovs_var_tmp', 'ovs_etc_tmp']:
309 if os.path.exists(settings.getValue('TOOLS')[tmp_dir]):
310 orig_dir = os.path.normpath(settings.getValue('TOOLS')[tmp_dir])
311 self._logger.info('Creating backup of %s directory...', tmp_dir)
312 tasks.run_task(['sudo', 'mv', orig_dir, '{}.{}'.format(orig_dir, self._stamp)],
315 # create fresh tmp dirs
316 tasks.run_task(['sudo', 'mkdir', '-p', settings.getValue('TOOLS')['ovs_var_tmp']], self._logger)
317 tasks.run_task(['sudo', 'mkdir', '-p', settings.getValue('TOOLS')['ovs_etc_tmp']], self._logger)
319 self._logger.info('System reset after last run.')
321 def _start_ovsdb(self):
322 """Start ``ovsdb-server`` instance.
326 ovsdb_tool_bin = settings.getValue('TOOLS')['ovsdb-tool']
327 tasks.run_task(['sudo', ovsdb_tool_bin, 'create',
328 os.path.join(settings.getValue('TOOLS')['ovs_etc_tmp'], 'conf.db'),
329 settings.getValue('TOOLS')['ovsschema']],
331 'Creating ovsdb configuration database...')
333 ovsdb_server_bin = settings.getValue('TOOLS')['ovsdb-server']
335 tasks.run_background_task(
336 ['sudo', ovsdb_server_bin,
337 '--remote=punix:%s' % os.path.join(settings.getValue('TOOLS')['ovs_var_tmp'], 'db.sock'),
338 '--remote=db:Open_vSwitch,Open_vSwitch,manager_options',
339 '--pidfile=' + self._ovsdb_pidfile_path, '--overwrite-pidfile'],
341 'Starting ovsdb-server...')
343 def _kill_ovsdb(self):
344 """Kill ``ovsdb-server`` instance.
348 if os.path.isfile(self._ovsdb_pidfile_path):
349 with open(self._ovsdb_pidfile_path, "r") as pidfile:
350 ovsdb_pid = pidfile.read().strip()
352 self._logger.info("Killing ovsdb with pid: " + ovsdb_pid)
355 tasks.terminate_task(ovsdb_pid, logger=self._logger)
357 # restore original content of ovs_var_tmp and ovs_etc_tmp; It is
358 # essential for OVS installed from binary packages.
360 for tmp_dir in ['ovs_var_tmp', 'ovs_etc_tmp']:
361 orig_dir = os.path.normpath(settings.getValue('TOOLS')[tmp_dir])
362 if os.path.exists('{}.{}'.format(orig_dir, self._stamp)):
363 self._logger.info('Restoring backup of %s directory...', tmp_dir)
364 tasks.run_task(['sudo', 'rm', '-rf', orig_dir], self._logger)
365 tasks.run_task(['sudo', 'mv', '{}.{}'.format(orig_dir, self._stamp), orig_dir],
369 def get_db_sock_path():
370 """Method returns location of db.sock file
372 :returns: path to db.sock file.
374 return os.path.join(settings.getValue('TOOLS')['ovs_var_tmp'], 'db.sock')
377 # validate methods required for integration testcases
380 def validate_add_switch(self, result, switch_name, params=None):
381 """Validate - Create a new logical switch with no ports
383 bridge = self._bridges[switch_name]
384 output = bridge.run_vsctl(['show'], check_error=True)
385 assert not output[1] # there shouldn't be any stderr, but in case
386 assert re.search('Bridge ["\']?%s["\']?' % switch_name, output[0]) is not None
389 def validate_del_switch(self, result, switch_name):
390 """Validate removal of switch
392 bridge = OFBridge('tmp')
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 None
398 def validate_add_phy_port(self, result, switch_name):
399 """ Validate that physical port was added to bridge.
401 bridge = self._bridges[switch_name]
402 output = bridge.run_vsctl(['show'], check_error=True)
403 assert not output[1] # there shouldn't be any stderr, but in case
404 assert re.search('Port ["\']?%s["\']?' % result[0], output[0]) is not None
405 assert re.search('Interface ["\']?%s["\']?' % result[0], output[0]) is not None
408 def validate_add_vport(self, result, switch_name):
409 """ Validate that virtual port was added to bridge.
411 return self.validate_add_phy_port(result, switch_name)
413 def validate_del_port(self, result, switch_name, port_name):
414 """ Validate that port_name was removed from bridge.
416 bridge = self._bridges[switch_name]
417 output = bridge.run_vsctl(['show'], check_error=True)
418 assert not output[1] # there shouldn't be any stderr, but in case
419 assert 'Port "%s"' % port_name not in output[0]
422 def validate_add_flow(self, result, switch_name, flow, cache='off'):
423 """ Validate insertion of the flow into the switch
425 if 'idle_timeout' in flow:
426 del(flow['idle_timeout'])
428 # Note: it should be possible to call `ovs-ofctl dump-flows switch flow`
429 # to verify flow insertion, but it doesn't accept the same flow syntax
430 # as add-flow, so we have to compare it the hard way
432 # get dump of flows and compare them one by one
433 flow_src = flow_key(flow)
434 bridge = self._bridges[switch_name]
435 output = bridge.run_ofctl(['dump-flows', switch_name], check_error=True)
436 for flow_dump in output[0].split('\n'):
437 if flow_match(flow_dump, flow_src):
438 # flow was added correctly
442 def validate_del_flow(self, result, switch_name, flow=None):
443 """ Validate removal of the flow
446 # what else we can do?
448 return not self.validate_add_flow(result, switch_name, flow)
450 def validate_dump_flows(self, result, switch_name):
451 """ Validate call of flow dump
455 def validate_disable_rstp(self, result, switch_name):
456 """ Validate rstp disable
458 bridge = self._bridges[switch_name]
459 return 'rstp_enable : false' in ''.join(bridge.bridge_info())
461 def validate_enable_rstp(self, result, switch_name):
462 """ Validate rstp enable
464 bridge = self._bridges[switch_name]
465 return 'rstp_enable : true' in ''.join(bridge.bridge_info())
467 def validate_disable_stp(self, result, switch_name):
468 """ Validate stp disable
470 bridge = self._bridges[switch_name]
471 return 'stp_enable : false' in ''.join(bridge.bridge_info())
473 def validate_enable_stp(self, result, switch_name):
474 """ Validate stp enable
476 bridge = self._bridges[switch_name]
477 return 'stp_enable : true' in ''.join(bridge.bridge_info())