src: update make install for DPDK
[vswitchperf.git] / src / ovs / ofctl.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 """Wrapper for an OVS bridge for convenient use of ``ovs-vsctl`` and
16 ``ovs-ofctl`` on it.
17
18 Much of this code is based on ``ovs-lib.py`` from Open Stack:
19
20 https://github.com/openstack/neutron/blob/6eac1dc99124ca024d6a69b3abfa3bc69c735667/neutron/agent/linux/ovs_lib.py
21 """
22
23 import os
24 import logging
25 import string
26
27 from tools import tasks
28 from conf import settings
29
30 _OVS_VSCTL_BIN = os.path.join(settings.getValue('OVS_DIR'), 'utilities',
31                               'ovs-vsctl')
32 _OVS_OFCTL_BIN = os.path.join(settings.getValue('OVS_DIR'), 'utilities',
33                               'ovs-ofctl')
34 _OVS_APPCTL_BIN = os.path.join(settings.getValue('OVS_DIR'), 'utilities',
35                                'ovs-appctl')
36
37 _OVS_BRIDGE_NAME = settings.getValue('VSWITCH_BRIDGE_NAME')
38
39 _CACHE_FILE_NAME = '/tmp/vsperf_flows_cache'
40
41 class OFBase(object):
42     """Add/remove/show datapaths using ``ovs-ofctl``.
43     """
44     def __init__(self, timeout=10):
45         """Initialise logger.
46
47         :param timeout: Timeout to be used for each command
48
49         :returns: None
50         """
51         self.logger = logging.getLogger(__name__)
52         self.timeout = timeout
53
54     # helpers
55
56     def run_vsctl(self, args, check_error=False):
57         """Run ``ovs-vsctl`` with supplied arguments.
58
59         :param args: Arguments to pass to ``ovs-vsctl``
60         :param check_error: Throw exception on error
61
62         :return: None
63         """
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)
67
68
69     def run_appctl(self, args, check_error=False):
70         """Run ``ovs-appctl`` with supplied arguments.
71
72         :param args: Arguments to pass to ``ovs-appctl``
73         :param check_error: Throw exception on error
74
75         :return: None
76         """
77         cmd = ['sudo', _OVS_APPCTL_BIN,
78                '--timeout',
79                str(self.timeout)] + args
80         return tasks.run_task(
81             cmd, self.logger, 'Running ovs-appctl...', check_error)
82
83
84     # datapath management
85
86     def add_br(self, br_name=_OVS_BRIDGE_NAME, params=None):
87         """Add datapath.
88
89         :param br_name: Name of bridge
90
91         :return: Instance of :class OFBridge:
92         """
93         if params is None:
94             params = []
95
96         self.logger.debug('add bridge')
97         self.run_vsctl(['add-br', br_name]+params)
98
99         return OFBridge(br_name, self.timeout)
100
101     def del_br(self, br_name=_OVS_BRIDGE_NAME):
102         """Delete datapath.
103
104         :param br_name: Name of bridge
105
106         :return: None
107         """
108         self.logger.debug('delete bridge')
109         self.run_vsctl(['del-br', br_name])
110
111     # Route and ARP functions
112
113     def add_route(self, network, destination):
114         """Add route to tunneling routing table.
115
116         :param network: Network
117         :param destination: Gateway
118
119         :return: None
120         """
121         self.logger.debug('add ovs/route')
122         self.run_appctl(['ovs/route/add', network, destination])
123
124
125     def set_tunnel_arp(self, ip_addr, mac_addr, br_name=_OVS_BRIDGE_NAME):
126         """Add OVS arp entry for tunneling
127
128         :param ip: IP of bridge
129         :param mac_addr: MAC address of the bridge
130         :param br_name: Name of the bridge
131
132         :return: None
133         """
134         self.logger.debug('tnl/arp/set')
135         self.run_appctl(['tnl/arp/set', br_name, ip_addr, mac_addr])
136
137
138 class OFBridge(OFBase):
139     """Control a bridge instance using ``ovs-vsctl`` and ``ovs-ofctl``.
140     """
141     def __init__(self, br_name=_OVS_BRIDGE_NAME, timeout=10):
142         """Initialise bridge.
143
144         :param br_name: Bridge name
145         :param timeout: Timeout to be used for each command
146
147         :returns: None
148         """
149         super(OFBridge, self).__init__(timeout)
150         self.br_name = br_name
151         self._ports = {}
152         self._cache_file = None
153
154     # context manager
155
156     def __enter__(self):
157         """Create datapath
158
159         :returns: self
160         """
161         return self
162
163     def __exit__(self, type_, value, traceback):
164         """Remove datapath.
165         """
166         if not traceback:
167             self.destroy()
168
169     # helpers
170
171     def run_ofctl(self, args, check_error=False, timeout=None):
172         """Run ``ovs-ofctl`` with supplied arguments.
173
174         :param args: Arguments to pass to ``ovs-ofctl``
175         :param check_error: Throw exception on error
176
177         :return: None
178         """
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)
184
185     def create(self, params=None):
186         """Create bridge.
187         """
188         if params is None:
189             params = []
190
191         self.logger.debug('create bridge')
192         self.add_br(self.br_name, params=params)
193
194     def destroy(self):
195         """Destroy bridge.
196         """
197         self.logger.debug('destroy bridge')
198         self.del_br(self.br_name)
199
200     def reset(self):
201         """Reset bridge.
202         """
203         self.logger.debug('reset bridge')
204         self.destroy()
205         self.create()
206
207     # port management
208
209     def add_port(self, port_name, params):
210         """Add port to bridge.
211
212         :param port_name: Name of port
213         :param params: Additional list of parameters to add-port
214
215         :return: OpenFlow port number for the port
216         """
217         self.logger.debug('add port')
218         self.run_vsctl(['add-port', self.br_name, port_name]+params)
219
220         # This is how port number allocation works currently
221         # This possibly will not work correctly if there are port deletions
222         # in between
223         of_port = len(self._ports) + 1
224         self._ports[port_name] = (of_port, params)
225         return of_port
226
227     def del_port(self, port_name):
228         """Remove port from bridge.
229
230         :param port_name: Name of port
231
232         :return: None
233         """
234         self.logger.debug('delete port')
235         self.run_vsctl(['del-port', self.br_name, port_name])
236         self._ports.pop(port_name)
237
238     def set_db_attribute(self, table_name, record, column, value):
239         """Set database attribute.
240
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
245
246         :return: None
247         """
248         self.logger.debug('set attribute')
249         self.run_vsctl(['set', table_name, record, '%s=%s' % (column, value)])
250
251     def get_ports(self):
252         """Get the ports of this bridge
253
254         Structure of the returned ports dictionary is
255         'portname': (openflow_port_number, extra_parameters)
256
257         Example:
258         ports = {
259             'dpdkport0':
260                 (1, ['--', 'set', 'Interface', 'dpdkport0', 'type=dpdk']),
261             'dpdkvhostport0':
262                 (2, ['--', 'set', 'Interface', 'dpdkvhostport0',
263                      'type=dpdkvhost'])
264         }
265
266         :return: Dictionary of ports
267         """
268         return self._ports
269
270     def clear_db_attribute(self, table_name, record, column):
271         """Clear database attribute.
272
273         :param table_name: Name of table
274         :param record: Name of record
275         :param column: Name of column
276
277         :return: None
278         """
279         self.logger.debug('clear attribute')
280         self.run_vsctl(['clear', table_name, record, column])
281
282     # flow mangement
283
284     def add_flow(self, flow, cache='off'):
285         """Add flow to bridge.
286
287         :param flow: Flow description as a dictionary
288         For flow dictionary structure, see function flow_key
289
290         :return: None
291         """
292         # insert flows from cache into OVS if needed
293         if cache == 'flush':
294             if self._cache_file == None:
295                 self.logger.error('flow cache flush called, but nothing is cached')
296                 return
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)
301             return
302
303         if not flow.get('actions'):
304             self.logger.error('add flow requires actions')
305             return
306
307         _flow_key = flow_key(flow)
308         self.logger.debug('key : %s', _flow_key)
309
310         # insert flow to the cache or OVS
311         if cache == 'on':
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')
316         else:
317             self.run_ofctl(['add-flow', self.br_name, _flow_key])
318
319     def del_flow(self, flow):
320         """Delete flow from bridge.
321
322         :param flow: Flow description as a dictionary
323         For flow dictionary structure, see function flow_key
324         flow=None will delete all flows
325
326         :return: None
327         """
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])
332
333     def del_flows(self):
334         """Delete all flows from bridge.
335         """
336         self.logger.debug('delete flows')
337         self.run_ofctl(['del-flows', self.br_name])
338
339     def dump_flows(self):
340         """Dump all flows from bridge.
341         """
342         self.logger.debug('dump flows')
343         self.run_ofctl(['dump-flows', self.br_name], timeout=120)
344
345 #
346 # helper functions
347 #
348
349 def flow_key(flow):
350     """Model a flow key string for ``ovs-ofctl``.
351
352     Syntax taken from ``ovs-ofctl`` manpages:
353         http://openvswitch.org/cgi-bin/ovsman.cgi?page=utilities%2Fovs-ofctl.8
354
355     Example flow dictionary:
356     flow = {
357         'in_port': '1',
358         'idle_timeout': '0',
359         'actions': ['output:3']
360     }
361
362     :param flow: Flow description as a dictionary
363
364     :return: String
365     :rtype: str
366     """
367     _flow_add_key = string.Template('${fields},action=${actions}')
368     _flow_del_key = string.Template('${fields}')
369
370     field_params = []
371
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})
376
377     field_params_str = ','.join(field_params)
378
379     _flow_key_param = {
380         'fields': field_params_str,
381     }
382
383     # no actions == delete key
384     if 'actions' in flow:
385         _flow_key_param['actions'] = ','.join(flow['actions'])
386
387         flow_str = _flow_add_key.substitute(_flow_key_param)
388     else:
389         flow_str = _flow_del_key.substitute(_flow_key_param)
390
391     return flow_str