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
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
29 _OVS_VAR_DIR = settings.getValue('OVS_VAR_DIR')
30 _OVS_ETC_DIR = settings.getValue('OVS_ETC_DIR')
33 class IVSwitchOvs(IVSwitch, tasks.Process):
34 """Open vSwitch base class implementation
36 The method docstrings document only considerations specific to this
37 implementation. For generic information of the nature of the methods,
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'
46 """See IVswitch for general description
48 self._logger = logging.getLogger(__name__)
49 self._expect = r'bridge|INFO|ovs-vswitchd'
52 self._vswitchd_args = ['--pidfile=' + self._vswitchd_pidfile_path,
53 '--overwrite-pidfile', '--log-file=' + self._logfile]
55 self._cmd_template = ['sudo', '-E', os.path.join(settings.getValue('OVS_DIR'),
56 'vswitchd', 'ovs-vswitchd')]
59 """ Start ``ovsdb-server`` and ``ovs-vswitchd`` instance.
61 :raises: pexpect.EOF, pexpect.TIMEOUT
63 self._logger.info("Starting vswitchd...")
65 self._cmd = self._cmd_template + self._vswitchd_args
67 # DB must be started before vswitchd
71 # DB must be up before vswitchd config is altered or vswitchd started
77 tasks.Process.start(self)
79 except (pexpect.EOF, pexpect.TIMEOUT) as exc:
80 logging.error("Exception during VSwitch start.")
84 self._logger.info("Vswitchd...Started.")
87 """ Configure vswitchd through ovsdb if needed
92 """See IVswitch for general description
94 self._logger.info("Terminating vswitchd...")
96 self._logger.info("Vswitchd...Terminated.")
98 def add_switch(self, switch_name, params=None):
99 """See IVswitch for general description
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
108 def del_switch(self, switch_name):
109 """See IVswitch for general description
111 bridge = self._bridges[switch_name]
112 self._bridges.pop(switch_name)
115 def add_phy_port(self, switch_name):
116 """See IVswitch for general description
118 raise NotImplementedError
120 def add_vport(self, switch_name):
121 """See IVswitch for general description
123 raise NotImplementedError
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'
130 if switch_name is None or remote_switch_name is None:
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,
141 'options:peer=' + remote_port_name]
142 remote_params = ['--', 'set', 'Interface', remote_port_name,
144 'options:peer=' + local_port_name]
146 if local_opts is not None:
147 local_params = local_params + local_opts
149 if remote_opts is not None:
150 remote_params = remote_params + remote_opts
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)]
157 def add_tunnel_port(self, switch_name, remote_ip, tunnel_type='vxlan', params=None):
158 """Creates tunneling port
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]
167 if params is not None:
168 local_params = local_params + params
170 of_port = bridge.add_port(port_name, local_params)
171 return (port_name, of_port)
173 def get_ports(self, switch_name):
174 """See IVswitch for general description
176 bridge = self._bridges[switch_name]
177 ports = list(bridge.get_ports().items())
178 return [(name, of_port) for (name, (of_port, _)) in ports]
180 def del_port(self, switch_name, port_name):
181 """See IVswitch for general description
183 bridge = self._bridges[switch_name]
184 bridge.del_port(port_name)
186 def add_flow(self, switch_name, flow, cache='off'):
187 """See IVswitch for general description
189 bridge = self._bridges[switch_name]
190 bridge.add_flow(flow, cache=cache)
192 def del_flow(self, switch_name, flow=None):
193 """See IVswitch for general description
196 bridge = self._bridges[switch_name]
197 bridge.del_flow(flow)
199 def dump_flows(self, switch_name):
200 """See IVswitch for general description
202 bridge = self._bridges[switch_name]
205 def add_route(self, switch_name, network, destination):
206 """See IVswitch for general description
208 bridge = self._bridges[switch_name]
209 bridge.add_route(network, destination)
211 def set_tunnel_arp(self, ip_addr, mac_addr, switch_name):
212 """See IVswitch for general description
214 bridge = self._bridges[switch_name]
215 bridge.set_tunnel_arp(ip_addr, mac_addr, switch_name)
217 def _get_port_count(self, param):
218 """Returns the number of ports having a certain parameter
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]
230 def disable_stp(self, switch_name):
232 Disable stp protocol on the bridge
233 :param switch_name: bridge to disable stp
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
241 def enable_stp(self, switch_name):
243 Enable stp protocol on the bridge
244 :param switch_name: bridge to enable stp
247 bridge = self._bridges[switch_name]
249 self._logger.info('Sleeping for 50 secs to allow stp to start.')
250 time.sleep(50) # needs time to enable
252 def disable_rstp(self, switch_name):
254 Disable rstp on the bridge
255 :param switch_name: bridge to disable rstp
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
263 def enable_rstp(self, switch_name):
265 Enable rstp on the bridge
266 :param switch_name: bridge to enable rstp
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
274 def kill(self, signal='-15', sleep=10):
275 """Kill ``ovs-vswitchd`` and ``ovs-ovsdb`` instances if they are alive.
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)
285 self._kill_ovsdb() # ovsdb must be killed after vswitchd
287 # just for case, that sudo envelope has not been terminated yet
288 tasks.Process.kill(self, signal, sleep)
292 def _reset_ovsdb(self):
293 """Reset system for 'ovsdb'.
297 self._logger.info('Resetting system after last run...')
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)
304 tasks.run_task(['sudo', 'rm', '-f',
305 os.path.join(_OVS_ETC_DIR, 'conf.db')],
308 self._logger.info('System reset after last run.')
310 def _start_ovsdb(self):
311 """Start ``ovsdb-server`` instance.
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')],
322 'Creating ovsdb configuration database...')
324 ovsdb_server_bin = os.path.join(
325 settings.getValue('OVS_DIR'), 'ovsdb', 'ovsdb-server')
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'],
333 'Starting ovsdb-server...')
335 def _kill_ovsdb(self):
336 """Kill ``ovsdb-server`` instance.
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()
344 self._logger.info("Killing ovsdb with pid: " + ovsdb_pid)
347 tasks.terminate_task(ovsdb_pid, logger=self._logger)
350 def get_db_sock_path():
351 """Method returns location of db.sock file
353 :returns: path to db.sock file.
355 return os.path.join(_OVS_VAR_DIR, 'db.sock')
358 # validate methods required for integration testcases
361 def validate_add_switch(self, result, switch_name, params=None):
362 """Validate - Create a new logical switch with no ports
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
370 def validate_del_switch(self, result, switch_name):
371 """Validate removal of switch
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
379 def validate_add_phy_port(self, result, switch_name):
380 """ Validate that physical port was added to bridge.
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
389 def validate_add_vport(self, result, switch_name):
390 """ Validate that virtual port was added to bridge.
392 return self.validate_add_phy_port(result, switch_name)
394 def validate_del_port(self, result, switch_name, port_name):
395 """ Validate that port_name was removed from bridge.
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]
403 def validate_add_flow(self, result, switch_name, flow, cache='off'):
404 """ Validate insertion of the flow into the switch
406 if 'idle_timeout' in flow:
407 del(flow['idle_timeout'])
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
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
423 def validate_del_flow(self, result, switch_name, flow=None):
424 """ Validate removal of the flow
427 # what else we can do?
429 return not self.validate_add_flow(result, switch_name, flow)
431 def validate_dump_flows(self, result, switch_name):
432 """ Validate call of flow dump
436 def validate_disable_rstp(self, result, switch_name):
437 """ Validate rstp disable
439 bridge = self._bridges[switch_name]
440 return 'rstp_enable : false' in ''.join(bridge.bridge_info())
442 def validate_enable_rstp(self, result, switch_name):
443 """ Validate rstp enable
445 bridge = self._bridges[switch_name]
446 return 'rstp_enable : true' in ''.join(bridge.bridge_info())
448 def validate_disable_stp(self, result, switch_name):
449 """ Validate stp disable
451 bridge = self._bridges[switch_name]
452 return 'stp_enable : false' in ''.join(bridge.bridge_info())
454 def validate_enable_stp(self, result, switch_name):
455 """ Validate stp enable
457 bridge = self._bridges[switch_name]
458 return 'stp_enable : true' in ''.join(bridge.bridge_info())