9d16ef76aa818124cff2a84e03ed10446909824b
[vswitchperf.git] / src / ovs / ofctl.py
1 # Copyright 2015 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
35 _OVS_BRIDGE_NAME = settings.getValue('VSWITCH_BRIDGE_NAME')
36
37 _CACHE_FILE_NAME = '/tmp/vsperf_flows_cache'
38
39 class OFBase(object):
40     """Add/remove/show datapaths using ``ovs-ofctl``.
41     """
42     def __init__(self, timeout=10):
43         """Initialise logger.
44
45         :param timeout: Timeout to be used for each command
46
47         :returns: None
48         """
49         self.logger = logging.getLogger(__name__)
50         self.timeout = timeout
51
52     # helpers
53
54     def run_vsctl(self, args, check_error=False):
55         """Run ``ovs-vsctl`` with supplied arguments.
56
57         :param args: Arguments to pass to ``ovs-vsctl``
58         :param check_error: Throw exception on error
59
60         :return: None
61         """
62         cmd = ['sudo', _OVS_VSCTL_BIN, '--timeout', str(self.timeout)] + args
63         return tasks.run_task(
64             cmd, self.logger, 'Running ovs-vsctl...', check_error)
65
66     # datapath management
67
68     def add_br(self, br_name=_OVS_BRIDGE_NAME, params=None):
69         """Add datapath.
70
71         :param br_name: Name of bridge
72
73         :return: Instance of :class OFBridge:
74         """
75         if params is None:
76             params = []
77
78         self.logger.debug('add bridge')
79         self.run_vsctl(['add-br', br_name]+params)
80
81         return OFBridge(br_name, self.timeout)
82
83     def del_br(self, br_name=_OVS_BRIDGE_NAME):
84         """Delete datapath.
85
86         :param br_name: Name of bridge
87
88         :return: None
89         """
90         self.logger.debug('delete bridge')
91         self.run_vsctl(['del-br', br_name])
92
93
94 class OFBridge(OFBase):
95     """Control a bridge instance using ``ovs-vsctl`` and ``ovs-ofctl``.
96     """
97     def __init__(self, br_name=_OVS_BRIDGE_NAME, timeout=10):
98         """Initialise bridge.
99
100         :param br_name: Bridge name
101         :param timeout: Timeout to be used for each command
102
103         :returns: None
104         """
105         super(OFBridge, self).__init__(timeout)
106         self.br_name = br_name
107         self._ports = {}
108         self._cache_file = None
109
110     # context manager
111
112     def __enter__(self):
113         """Create datapath
114
115         :returns: self
116         """
117         return self
118
119     def __exit__(self, type_, value, traceback):
120         """Remove datapath.
121         """
122         if not traceback:
123             self.destroy()
124
125     # helpers
126
127     def run_ofctl(self, args, check_error=False, timeout=None):
128         """Run ``ovs-ofctl`` with supplied arguments.
129
130         :param args: Arguments to pass to ``ovs-ofctl``
131         :param check_error: Throw exception on error
132
133         :return: None
134         """
135         tmp_timeout = self.timeout if timeout == None else timeout
136         cmd = ['sudo', _OVS_OFCTL_BIN, '-O', 'OpenFlow13', '--timeout',
137                str(tmp_timeout)] + args
138         return tasks.run_task(
139             cmd, self.logger, 'Running ovs-ofctl...', check_error)
140
141     def create(self, params=None):
142         """Create bridge.
143         """
144         if params is None:
145             params = []
146
147         self.logger.debug('create bridge')
148         self.add_br(self.br_name, params=params)
149
150     def destroy(self):
151         """Destroy bridge.
152         """
153         self.logger.debug('destroy bridge')
154         self.del_br(self.br_name)
155
156     def reset(self):
157         """Reset bridge.
158         """
159         self.logger.debug('reset bridge')
160         self.destroy()
161         self.create()
162
163     # port management
164
165     def add_port(self, port_name, params):
166         """Add port to bridge.
167
168         :param port_name: Name of port
169         :param params: Additional list of parameters to add-port
170
171         :return: OpenFlow port number for the port
172         """
173         self.logger.debug('add port')
174         self.run_vsctl(['add-port', self.br_name, port_name]+params)
175
176         # This is how port number allocation works currently
177         # This possibly will not work correctly if there are port deletions
178         # in between
179         of_port = len(self._ports) + 1
180         self._ports[port_name] = (of_port, params)
181         return of_port
182
183     def del_port(self, port_name):
184         """Remove port from bridge.
185
186         :param port_name: Name of port
187
188         :return: None
189         """
190         self.logger.debug('delete port')
191         self.run_vsctl(['del-port', self.br_name, port_name])
192         self._ports.pop(port_name)
193
194     def set_db_attribute(self, table_name, record, column, value):
195         """Set database attribute.
196
197         :param table_name: Name of table
198         :param record: Name of record
199         :param column: Name of column
200         :param value: Value to set
201
202         :return: None
203         """
204         self.logger.debug('set attribute')
205         self.run_vsctl(['set', table_name, record, '%s=%s' % (column, value)])
206
207     def get_ports(self):
208         """Get the ports of this bridge
209
210         Structure of the returned ports dictionary is
211         'portname': (openflow_port_number, extra_parameters)
212
213         Example:
214         ports = {
215             'dpdkport0':
216                 (1, ['--', 'set', 'Interface', 'dpdkport0', 'type=dpdk']),
217             'dpdkvhostport0':
218                 (2, ['--', 'set', 'Interface', 'dpdkvhostport0',
219                      'type=dpdkvhost'])
220         }
221
222         :return: Dictionary of ports
223         """
224         return self._ports
225
226     def clear_db_attribute(self, table_name, record, column):
227         """Clear database attribute.
228
229         :param table_name: Name of table
230         :param record: Name of record
231         :param column: Name of column
232
233         :return: None
234         """
235         self.logger.debug('clear attribute')
236         self.run_vsctl(['clear', table_name, record, column])
237
238     # flow mangement
239
240     def add_flow(self, flow, cache='off'):
241         """Add flow to bridge.
242
243         :param flow: Flow description as a dictionary
244         For flow dictionary structure, see function flow_key
245
246         :return: None
247         """
248         # insert flows from cache into OVS if needed
249         if cache == 'flush':
250             if self._cache_file == None:
251                 self.logger.error('flow cache flush called, but nothing is cached')
252                 return
253             self.logger.debug('flows cached in %s will be added to the bridge', _CACHE_FILE_NAME)
254             self._cache_file.close()
255             self._cache_file = None
256             self.run_ofctl(['add-flows', self.br_name, _CACHE_FILE_NAME], timeout=600)
257             return
258
259         if not flow.get('actions'):
260             self.logger.error('add flow requires actions')
261             return
262
263         _flow_key = flow_key(flow)
264         self.logger.debug('key : %s', _flow_key)
265
266         # insert flow to the cache or OVS
267         if cache == 'on':
268             # create and open cache file if needed
269             if self._cache_file == None:
270                 self._cache_file = open(_CACHE_FILE_NAME, 'w')
271             self._cache_file.write(_flow_key + '\n')
272         else:
273             self.run_ofctl(['add-flow', self.br_name, _flow_key])
274
275     def del_flow(self, flow):
276         """Delete flow from bridge.
277
278         :param flow: Flow description as a dictionary
279         For flow dictionary structure, see function flow_key
280         flow=None will delete all flows
281
282         :return: None
283         """
284         self.logger.debug('delete flow')
285         _flow_key = flow_key(flow)
286         self.logger.debug('key : %s', _flow_key)
287         self.run_ofctl(['del-flows', self.br_name, _flow_key])
288
289     def del_flows(self):
290         """Delete all flows from bridge.
291         """
292         self.logger.debug('delete flows')
293         self.run_ofctl(['del-flows', self.br_name])
294
295     def dump_flows(self):
296         """Dump all flows from bridge.
297         """
298         self.logger.debug('dump flows')
299         self.run_ofctl(['dump-flows', self.br_name], timeout=120)
300
301 #
302 # helper functions
303 #
304
305 def flow_key(flow):
306     """Model a flow key string for ``ovs-ofctl``.
307
308     Syntax taken from ``ovs-ofctl`` manpages:
309         http://openvswitch.org/cgi-bin/ovsman.cgi?page=utilities%2Fovs-ofctl.8
310
311     Example flow dictionary:
312     flow = {
313         'in_port': '1',
314         'idle_timeout': '0',
315         'actions': ['output:3']
316     }
317
318     :param flow: Flow description as a dictionary
319
320     :return: String
321     :rtype: str
322     """
323     _flow_add_key = string.Template('${fields},action=${actions}')
324     _flow_del_key = string.Template('${fields}')
325
326     field_params = []
327
328     user_params = (x for x in list(flow.items()) if x[0] != 'actions')
329     for (key, default) in user_params:
330         field_params.append('%(field)s=%(value)s' %
331                             {'field': key, 'value': default})
332
333     field_params = ','.join(field_params)
334
335     _flow_key_param = {
336         'fields': field_params,
337     }
338
339     # no actions == delete key
340     if 'actions' in flow:
341         _flow_key_param['actions'] = ','.join(flow['actions'])
342
343         flow_str = _flow_add_key.substitute(_flow_key_param)
344     else:
345         flow_str = _flow_del_key.substitute(_flow_key_param)
346
347     return flow_str