31defd6704c36dfe1223c198c089b81cf7315df2
[csit.git] / resources / libraries / python / VppConfigGenerator.py
1 # Copyright (c) 2016 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 """VPP Configuration File Generator library."""
15
16 import re
17 import time
18
19 from resources.libraries.python.ssh import SSH
20 from resources.libraries.python.topology import NodeType
21 from resources.libraries.python.topology import Topology
22
23 __all__ = ['VppConfigGenerator']
24
25 class VppConfigGenerator(object):
26     """VPP Configuration File Generator."""
27
28     def __init__(self):
29         # VPP Node to apply configuration on
30         self._node = ''
31         # VPP Hostname
32         self._hostname = ''
33         # VPP Configuration
34         self._nodeconfig = {}
35         # Serialized VPP Configuration
36         self._vpp_config = ''
37         # VPP Service name
38         self._vpp_service_name = 'vpp'
39         # VPP Configuration file path
40         self._vpp_config_filename = '/etc/vpp/startup.conf'
41
42     def set_node(self, node):
43         """Set DUT node.
44
45         :param node: Node to store configuration on.
46         :type node: dict
47         :raises RuntimeError: If Node type is not DUT.
48         """
49
50         if node['type'] != NodeType.DUT:
51             raise RuntimeError('Startup config can only be applied to DUT'
52                                'node.')
53         self._node = node
54         self._hostname = Topology.get_node_hostname(node)
55
56     def set_config_filename(self, filename):
57         """Set startup configuration filename.
58
59         :param filename: Startup configuration filename.
60         :type filename: str
61         """
62         self._vpp_config_filename = filename
63
64     def add_config_item(self, config, value, path):
65         """Add startup configuration item.
66
67         :param config: Startup configuration of node.
68         :param value: Value to insert.
69         :param path: Path where to insert item.
70         :type config: dict
71         :type value: str
72         :type path: list
73         """
74
75         if len(path) == 1:
76             config[path[0]] = value
77             return
78         if not config.has_key(path[0]):
79             config[path[0]] = {}
80         self.add_config_item(config[path[0]], value, path[1:])
81
82     def dump_config(self, obj, level=-1):
83         """Dump the startup configuration in VPP config format.
84
85         :param obj: Python Object to print.
86         :param nested_level: Nested level for indentation.
87         :type obj: Obj
88         :type nested_level: int
89         :returns: nothing
90         """
91         indent = '  '
92         if level >= 0:
93             self._vpp_config += '{}{{\n'.format((level) * indent)
94         if isinstance(obj, dict):
95             for key, val in obj.items():
96                 if hasattr(val, '__iter__'):
97                     self._vpp_config += '{}{}\n'.format((level + 1) * indent,
98                                                         key)
99                     self.dump_config(val, level + 1)
100                 else:
101                     self._vpp_config += '{}{} {}\n'.format((level + 1) * indent,
102                                                            key, val)
103         else:
104             for val in obj:
105                 self._vpp_config += '{}{}\n'.format((level + 1) * indent, val)
106         if level >= 0:
107             self._vpp_config += '{}}}\n'.format(level * indent)
108
109     def add_unix_log(self, value='/tmp/vpe.log'):
110         """Add UNIX log configuration.
111
112         :param value: Log file.
113         :type value: str
114         """
115         path = ['unix', 'log']
116         self.add_config_item(self._nodeconfig, value, path)
117
118     def add_unix_cli_listen(self, value='localhost:5002'):
119         """Add UNIX cli-listen configuration.
120
121         :param value: CLI listen address and port.
122         :type value: str
123         """
124         path = ['unix', 'cli-listen']
125         self.add_config_item(self._nodeconfig, value, path)
126
127     def add_unix_nodaemon(self):
128         """Add UNIX nodaemon configuration."""
129         path = ['unix', 'nodaemon']
130         self.add_config_item(self._nodeconfig, '', path)
131
132     def add_dpdk_dev(self, *devices):
133         """Add DPDK PCI device configuration.
134
135         :param devices: PCI device(s) (format xxxx:xx:xx.x)
136         :type devices: tuple
137         :raises ValueError: If PCI address format is not valid.
138         """
139
140         pattern = re.compile("^[0-9A-Fa-f]{4}:[0-9A-Fa-f]{2}:"
141                              "[0-9A-Fa-f]{2}\\.[0-9A-Fa-f]$")
142         for device in devices:
143             if not pattern.match(device):
144                 raise ValueError('PCI address {} to be added to host {} '
145                                  'is not in valid format xxxx:xx:xx.x'.
146                                  format(device, self._hostname))
147             path = ['dpdk', 'dev {0}'.format(device)]
148             self.add_config_item(self._nodeconfig, '', path)
149
150     def add_dpdk_cryptodev(self, count):
151         """Add DPDK Crypto PCI device configuration.
152
153         :param count: Number of crypto devices to add.
154         :type count: int
155         """
156         cryptodev = Topology.get_cryptodev(self._node)
157         for i in range(count):
158             cryptodev_config = 'dev {0}'.format(
159                 re.sub(r'\d.\d$', '1.'+str(i), cryptodev))
160             path = ['dpdk', cryptodev_config]
161             self.add_config_item(self._nodeconfig, '', path)
162         self.add_dpdk_uio_driver('igb_uio')
163
164     def add_dpdk_dev_default_rxq(self, value):
165         """Add DPDK dev default rxq configuration.
166
167         :param value: Default number of rxqs.
168         :type value: str
169         """
170         path = ['dpdk', 'dev default', 'num-rx-queues']
171         self.add_config_item(self._nodeconfig, value, path)
172
173     def add_dpdk_dev_default_txq(self, value):
174         """Add DPDK dev default txq configuration.
175
176         :param value: Default number of txqs.
177         :type value: str
178         """
179         path = ['dpdk', 'dev default', 'num-tx-queues']
180         self.add_config_item(self._nodeconfig, value, path)
181
182     def add_dpdk_socketmem(self, value):
183         """Add DPDK socket memory configuration.
184
185         :param value: Socket memory size.
186         :type value: str
187         """
188         path = ['dpdk', 'socket-mem']
189         self.add_config_item(self._nodeconfig, value, path)
190
191     def add_dpdk_uio_driver(self, value):
192         """Add DPDK uio-driver configuration.
193
194         :param value: DPDK uio-driver configuration.
195         :type value: str
196         """
197         path = ['dpdk', 'uio-driver']
198         self.add_config_item(self._nodeconfig, value, path)
199
200     def add_cpu_main_core(self, value):
201         """Add CPU main core configuration.
202
203         :param value: Main core option.
204         :type value: str
205         """
206         path = ['cpu', 'main-core']
207         self.add_config_item(self._nodeconfig, value, path)
208
209     def add_cpu_corelist_workers(self, value):
210         """Add CPU corelist-workers configuration.
211
212         :param value: Corelist-workers option.
213         :type value: str
214         """
215         path = ['cpu', 'corelist-workers']
216         self.add_config_item(self._nodeconfig, value, path)
217
218     def add_heapsize(self, value):
219         """Add Heapsize configuration.
220
221         :param value: Amount of heapsize.
222         :type value: str
223         """
224         path = ['heapsize']
225         self.add_config_item(self._nodeconfig, value, path)
226
227     def add_api_trace(self):
228         """Add API trace configuration."""
229         path = ['api-trace', 'on']
230         self.add_config_item(self._nodeconfig, '', path)
231
232     def add_ip6_hash_buckets(self, value):
233         """Add IP6 hash buckets configuration.
234
235         :param value: Number of IP6 hash buckets.
236         :type value: str
237         """
238         path = ['ip6', 'hash-buckets']
239         self.add_config_item(self._nodeconfig, value, path)
240
241     def add_ip6_heap_size(self, value):
242         """Add IP6 heap-size configuration.
243
244         :param value: IP6 Heapsize amount.
245         :type value: str
246         """
247         path = ['ip6', 'heap-size']
248         self.add_config_item(self._nodeconfig, value, path)
249
250     def add_plugin_disable(self, *plugins):
251         """Add plugin disable for specific plugin.
252
253         :param plugins: Plugin(s) to disable.
254         :type plugins: list
255         """
256         for plugin in plugins:
257             path = ['plugins', 'plugin {0}'.format(plugin), 'disable']
258             self.add_config_item(self._nodeconfig, ' ', path)
259
260     def add_dpdk_no_multi_seg(self):
261         """Add DPDK no-multi-seg configuration."""
262         path = ['dpdk', 'no-multi-seg']
263         self.add_config_item(self._nodeconfig, '', path)
264
265     def add_snat(self, value='deterministic'):
266         """Add SNAT configuration.
267
268         :param value: SNAT mode.
269         :type value: str
270         """
271         path = ['snat']
272         self.add_config_item(self._nodeconfig, value, path)
273
274     def apply_config(self, waittime=5, retries=12):
275         """Generate and apply VPP configuration for node.
276
277         Use data from calls to this class to form a startup.conf file and
278         replace /etc/vpp/startup.conf with it on node.
279
280         :param waittime: Time to wait for VPP to restart (default 5 seconds).
281         :param retries: Number of times (default 12) to re-try waiting.
282         :type waittime: int
283         :type retries: int
284         :raises RuntimeError: If writing config file failed, or restarting of
285         VPP failed.
286         """
287         self.dump_config(self._nodeconfig)
288
289         ssh = SSH()
290         ssh.connect(self._node)
291
292         # We're using this "| sudo tee" construct because redirecting
293         # a sudo's output ("sudo echo xxx > /path/to/file") does not
294         # work on most platforms...
295         (ret, _, _) = \
296             ssh.exec_command('echo "{0}" | sudo tee {1}'.
297                              format(self._vpp_config,
298                                     self._vpp_config_filename))
299
300         if ret != 0:
301             raise RuntimeError('Writing config file failed to node {}'.
302                                format(self._hostname))
303
304         # Instead of restarting, we'll do separate start and stop
305         # actions. This way we don't care whether VPP was running
306         # to begin with.
307         ssh.exec_command('sudo service {} stop'
308                          .format(self._vpp_service_name))
309         (ret, _, _) = \
310             ssh.exec_command('sudo service {} start'
311                              .format(self._vpp_service_name))
312         if ret != 0:
313             raise RuntimeError('Restarting VPP failed on node {}'.
314                                format(self._hostname))
315
316         # Sleep <waittime> seconds, up to <retry> times,
317         # and verify if VPP is running.
318         for _ in range(retries):
319             time.sleep(waittime)
320             (ret, _, _) = \
321                 ssh.exec_command('echo show hardware-interfaces | '
322                                  'nc 0 5002 || echo "VPP not yet running"')
323             if ret == 0:
324                 break
325         else:
326             raise RuntimeError('VPP failed to restart on node {}'.
327                                format(self._hostname))
328
329     def apply_config_lxc(self, lxc_name, waittime=5, retries=12):
330         """Generate and apply VPP configuration for node in a container.
331
332         Use data from calls to this class to form a startup.conf file and
333         replace /etc/vpp/startup.conf with it on node inside a container.
334
335         :param lxc_name: LXC container name.
336         :param waittime: Time to wait for VPP to restart (default 5 seconds).
337         :param retries: Number of times (default 12) to re-try waiting.
338         :type lxc_name: str
339         :type waittime: int
340         :type retries: int
341         :raises RuntimeError: If writing config file failed, or restarting of
342         VPP failed.
343         """
344         self.dump_config(self._nodeconfig)
345
346         ssh = SSH()
347         ssh.connect(self._node)
348
349         # We're using this "| sudo tee" construct because redirecting
350         # a sudo's output ("sudo echo xxx > /path/to/file") does not
351         # work on most platforms...
352         (ret, _, _) = \
353             ssh.exec_command_lxc('echo "{0}" | sudo tee {1}'.
354                                  format(self._vpp_config,
355                                         self._vpp_config_filename), lxc_name)
356
357         if ret != 0:
358             raise RuntimeError('Writing config file failed in {0} to node {1}'.
359                                format(lxc_name, self._hostname))
360
361         # Instead of restarting, we'll do separate start and stop
362         # actions. This way we don't care whether VPP was running
363         # to begin with.
364         ssh.exec_command_lxc('service {0} stop'.
365                              format(self._vpp_service_name), lxc_name)
366         (ret, _, _) = \
367             ssh.exec_command_lxc('service {0} start'.
368                                  format(self._vpp_service_name), lxc_name)
369         if ret != 0:
370             raise RuntimeError('Restarting VPP failed in {0} on node {1}'.
371                                format(lxc_name, self._hostname))
372
373         # Sleep <waittime> seconds, up to <retry> times,
374         # and verify if VPP is running.
375         for _ in range(retries):
376             time.sleep(waittime)
377             (ret, _, _) = \
378                 ssh.exec_command_lxc('echo show hardware-interfaces | '
379                                      'nc 0 5002 || echo "VPP not yet running"',
380                                      lxc_name)
381             if ret == 0:
382                 break
383         else:
384             raise RuntimeError('VPP failed to restart in {0} on node {1}'.
385                                format(lxc_name, self._hostname))