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 """Wrapper for an OVS bridge for convenient use of ``ovs-vsctl`` and
18 Much of this code is based on ``ovs-lib.py`` from Open Stack:
20 https://github.com/openstack/neutron/blob/6eac1dc99124ca024d6a69b3abfa3bc69c735667/neutron/agent/linux/ovs_lib.py
27 from tools import tasks
28 from conf import settings
30 _OVS_VSCTL_BIN = os.path.join(settings.getValue('OVS_DIR'), 'utilities',
32 _OVS_OFCTL_BIN = os.path.join(settings.getValue('OVS_DIR'), 'utilities',
34 _OVS_APPCTL_BIN = os.path.join(settings.getValue('OVS_DIR'), 'utilities',
37 _OVS_BRIDGE_NAME = settings.getValue('VSWITCH_BRIDGE_NAME')
39 _CACHE_FILE_NAME = '/tmp/vsperf_flows_cache'
42 """Add/remove/show datapaths using ``ovs-ofctl``.
44 def __init__(self, timeout=10):
47 :param timeout: Timeout to be used for each command
51 self.logger = logging.getLogger(__name__)
52 self.timeout = timeout
56 def run_vsctl(self, args, check_error=False):
57 """Run ``ovs-vsctl`` with supplied arguments.
59 :param args: Arguments to pass to ``ovs-vsctl``
60 :param check_error: Throw exception on error
64 cmd = ['sudo', _OVS_VSCTL_BIN, '--timeout', str(self.timeout)] + args
65 return tasks.run_task(
66 cmd, self.logger, 'Running ovs-vsctl...', check_error)
69 def run_appctl(self, args, check_error=False):
70 """Run ``ovs-appctl`` with supplied arguments.
72 :param args: Arguments to pass to ``ovs-appctl``
73 :param check_error: Throw exception on error
77 cmd = ['sudo', _OVS_APPCTL_BIN,
79 str(self.timeout)] + args
80 return tasks.run_task(
81 cmd, self.logger, 'Running ovs-appctl...', check_error)
86 def add_br(self, br_name=_OVS_BRIDGE_NAME, params=None):
89 :param br_name: Name of bridge
91 :return: Instance of :class OFBridge:
96 self.logger.debug('add bridge')
97 self.run_vsctl(['add-br', br_name]+params)
99 return OFBridge(br_name, self.timeout)
101 def del_br(self, br_name=_OVS_BRIDGE_NAME):
104 :param br_name: Name of bridge
108 self.logger.debug('delete bridge')
109 self.run_vsctl(['del-br', br_name])
111 # Route and ARP functions
113 def add_route(self, network, destination):
114 """Add route to tunneling routing table.
116 :param network: Network
117 :param destination: Gateway
121 self.logger.debug('add ovs/route')
122 self.run_appctl(['ovs/route/add', network, destination])
125 def set_tunnel_arp(self, ip_addr, mac_addr, br_name=_OVS_BRIDGE_NAME):
126 """Add OVS arp entry for tunneling
128 :param ip: IP of bridge
129 :param mac_addr: MAC address of the bridge
130 :param br_name: Name of the bridge
134 self.logger.debug('tnl/arp/set')
135 self.run_appctl(['tnl/arp/set', br_name, ip_addr, mac_addr])
138 class OFBridge(OFBase):
139 """Control a bridge instance using ``ovs-vsctl`` and ``ovs-ofctl``.
141 def __init__(self, br_name=_OVS_BRIDGE_NAME, timeout=10):
142 """Initialise bridge.
144 :param br_name: Bridge name
145 :param timeout: Timeout to be used for each command
149 super(OFBridge, self).__init__(timeout)
150 self.br_name = br_name
152 self._cache_file = None
163 def __exit__(self, type_, value, traceback):
171 def run_ofctl(self, args, check_error=False, timeout=None):
172 """Run ``ovs-ofctl`` with supplied arguments.
174 :param args: Arguments to pass to ``ovs-ofctl``
175 :param check_error: Throw exception on error
179 tmp_timeout = self.timeout if timeout == None else timeout
180 cmd = ['sudo', _OVS_OFCTL_BIN, '-O', 'OpenFlow13', '--timeout',
181 str(tmp_timeout)] + args
182 return tasks.run_task(
183 cmd, self.logger, 'Running ovs-ofctl...', check_error)
185 def create(self, params=None):
191 self.logger.debug('create bridge')
192 self.add_br(self.br_name, params=params)
197 self.logger.debug('destroy bridge')
198 self.del_br(self.br_name)
203 self.logger.debug('reset bridge')
209 def add_port(self, port_name, params):
210 """Add port to bridge.
212 :param port_name: Name of port
213 :param params: Additional list of parameters to add-port
215 :return: OpenFlow port number for the port
217 self.logger.debug('add port')
218 self.run_vsctl(['add-port', self.br_name, port_name]+params)
220 # This is how port number allocation works currently
221 # This possibly will not work correctly if there are port deletions
223 of_port = len(self._ports) + 1
224 self._ports[port_name] = (of_port, params)
227 def del_port(self, port_name):
228 """Remove port from bridge.
230 :param port_name: Name of port
234 self.logger.debug('delete port')
235 self.run_vsctl(['del-port', self.br_name, port_name])
236 self._ports.pop(port_name)
238 def set_db_attribute(self, table_name, record, column, value):
239 """Set database attribute.
241 :param table_name: Name of table
242 :param record: Name of record
243 :param column: Name of column
244 :param value: Value to set
248 self.logger.debug('set attribute')
249 self.run_vsctl(['set', table_name, record, '%s=%s' % (column, value)])
252 """Get the ports of this bridge
254 Structure of the returned ports dictionary is
255 'portname': (openflow_port_number, extra_parameters)
260 (1, ['--', 'set', 'Interface', 'dpdkport0', 'type=dpdk']),
262 (2, ['--', 'set', 'Interface', 'dpdkvhostport0',
266 :return: Dictionary of ports
270 def clear_db_attribute(self, table_name, record, column):
271 """Clear database attribute.
273 :param table_name: Name of table
274 :param record: Name of record
275 :param column: Name of column
279 self.logger.debug('clear attribute')
280 self.run_vsctl(['clear', table_name, record, column])
284 def add_flow(self, flow, cache='off'):
285 """Add flow to bridge.
287 :param flow: Flow description as a dictionary
288 For flow dictionary structure, see function flow_key
292 # insert flows from cache into OVS if needed
294 if self._cache_file == None:
295 self.logger.error('flow cache flush called, but nothing is cached')
297 self.logger.debug('flows cached in %s will be added to the bridge', _CACHE_FILE_NAME)
298 self._cache_file.close()
299 self._cache_file = None
300 self.run_ofctl(['add-flows', self.br_name, _CACHE_FILE_NAME], timeout=600)
303 if not flow.get('actions'):
304 self.logger.error('add flow requires actions')
307 _flow_key = flow_key(flow)
308 self.logger.debug('key : %s', _flow_key)
310 # insert flow to the cache or OVS
312 # create and open cache file if needed
313 if self._cache_file == None:
314 self._cache_file = open(_CACHE_FILE_NAME, 'w')
315 self._cache_file.write(_flow_key + '\n')
317 self.run_ofctl(['add-flow', self.br_name, _flow_key])
319 def del_flow(self, flow):
320 """Delete flow from bridge.
322 :param flow: Flow description as a dictionary
323 For flow dictionary structure, see function flow_key
324 flow=None will delete all flows
328 self.logger.debug('delete flow')
329 _flow_key = flow_key(flow)
330 self.logger.debug('key : %s', _flow_key)
331 self.run_ofctl(['del-flows', self.br_name, _flow_key])
334 """Delete all flows from bridge.
336 self.logger.debug('delete flows')
337 self.run_ofctl(['del-flows', self.br_name])
339 def dump_flows(self):
340 """Dump all flows from bridge.
342 self.logger.debug('dump flows')
343 self.run_ofctl(['dump-flows', self.br_name], timeout=120)
350 """Model a flow key string for ``ovs-ofctl``.
352 Syntax taken from ``ovs-ofctl`` manpages:
353 http://openvswitch.org/cgi-bin/ovsman.cgi?page=utilities%2Fovs-ofctl.8
355 Example flow dictionary:
359 'actions': ['output:3']
362 :param flow: Flow description as a dictionary
367 _flow_add_key = string.Template('${fields},action=${actions}')
368 _flow_del_key = string.Template('${fields}')
372 user_params = (x for x in list(flow.items()) if x[0] != 'actions')
373 for (key, default) in user_params:
374 field_params.append('%(field)s=%(value)s' %
375 {'field': key, 'value': default})
377 field_params_str = ','.join(field_params)
380 'fields': field_params_str,
383 # no actions == delete key
384 if 'actions' in flow:
385 _flow_key_param['actions'] = ','.join(flow['actions'])
387 flow_str = _flow_add_key.substitute(_flow_key_param)
389 flow_str = _flow_del_key.substitute(_flow_key_param)