FIX: SNAT -> NAT renaming of API
[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_unix_exec(self, value):
133         """Add UNIX exec configuration."""
134         path = ['unix', 'exec']
135         self.add_config_item(self._nodeconfig, value, path)
136
137     def add_dpdk_dev(self, *devices):
138         """Add DPDK PCI device configuration.
139
140         :param devices: PCI device(s) (format xxxx:xx:xx.x)
141         :type devices: tuple
142         :raises ValueError: If PCI address format is not valid.
143         """
144
145         pattern = re.compile("^[0-9A-Fa-f]{4}:[0-9A-Fa-f]{2}:"
146                              "[0-9A-Fa-f]{2}\\.[0-9A-Fa-f]$")
147         for device in devices:
148             if not pattern.match(device):
149                 raise ValueError('PCI address {} to be added to host {} '
150                                  'is not in valid format xxxx:xx:xx.x'.
151                                  format(device, self._hostname))
152             path = ['dpdk', 'dev {0}'.format(device)]
153             self.add_config_item(self._nodeconfig, '', path)
154
155     def add_dpdk_cryptodev(self, count):
156         """Add DPDK Crypto PCI device configuration.
157
158         :param count: Number of crypto devices to add.
159         :type count: int
160         """
161         cryptodev = Topology.get_cryptodev(self._node)
162         for i in range(count):
163             cryptodev_config = 'dev {0}'.format(
164                 re.sub(r'\d.\d$', '1.'+str(i), cryptodev))
165             path = ['dpdk', cryptodev_config]
166             self.add_config_item(self._nodeconfig, '', path)
167         self.add_dpdk_uio_driver('igb_uio')
168
169     def add_dpdk_dev_default_rxq(self, value):
170         """Add DPDK dev default rxq configuration.
171
172         :param value: Default number of rxqs.
173         :type value: str
174         """
175         path = ['dpdk', 'dev default', 'num-rx-queues']
176         self.add_config_item(self._nodeconfig, value, path)
177
178     def add_dpdk_dev_default_txq(self, value):
179         """Add DPDK dev default txq configuration.
180
181         :param value: Default number of txqs.
182         :type value: str
183         """
184         path = ['dpdk', 'dev default', 'num-tx-queues']
185         self.add_config_item(self._nodeconfig, value, path)
186
187     def add_dpdk_dev_default_rxd(self, value):
188         """Add DPDK dev default rxd configuration.
189
190         :param value: Default number of rxds.
191         :type value: str
192         """
193         path = ['dpdk', 'dev default', 'num-rx-desc']
194         self.add_config_item(self._nodeconfig, value, path)
195
196     def add_dpdk_dev_default_txd(self, value):
197         """Add DPDK dev default txd configuration.
198
199         :param value: Default number of txds.
200         :type value: str
201         """
202         path = ['dpdk', 'dev default', 'num-tx-desc']
203         self.add_config_item(self._nodeconfig, value, path)
204
205
206     def add_dpdk_socketmem(self, value):
207         """Add DPDK socket memory configuration.
208
209         :param value: Socket memory size.
210         :type value: str
211         """
212         path = ['dpdk', 'socket-mem']
213         self.add_config_item(self._nodeconfig, value, path)
214
215     def add_dpdk_uio_driver(self, value):
216         """Add DPDK uio-driver configuration.
217
218         :param value: DPDK uio-driver configuration.
219         :type value: str
220         """
221         path = ['dpdk', 'uio-driver']
222         self.add_config_item(self._nodeconfig, value, path)
223
224     def add_cpu_main_core(self, value):
225         """Add CPU main core configuration.
226
227         :param value: Main core option.
228         :type value: str
229         """
230         path = ['cpu', 'main-core']
231         self.add_config_item(self._nodeconfig, value, path)
232
233     def add_cpu_corelist_workers(self, value):
234         """Add CPU corelist-workers configuration.
235
236         :param value: Corelist-workers option.
237         :type value: str
238         """
239         path = ['cpu', 'corelist-workers']
240         self.add_config_item(self._nodeconfig, value, path)
241
242     def add_heapsize(self, value):
243         """Add Heapsize configuration.
244
245         :param value: Amount of heapsize.
246         :type value: str
247         """
248         path = ['heapsize']
249         self.add_config_item(self._nodeconfig, value, path)
250
251     def add_api_trace(self):
252         """Add API trace configuration."""
253         path = ['api-trace', 'on']
254         self.add_config_item(self._nodeconfig, '', path)
255
256     def add_ip6_hash_buckets(self, value):
257         """Add IP6 hash buckets configuration.
258
259         :param value: Number of IP6 hash buckets.
260         :type value: str
261         """
262         path = ['ip6', 'hash-buckets']
263         self.add_config_item(self._nodeconfig, value, path)
264
265     def add_ip6_heap_size(self, value):
266         """Add IP6 heap-size configuration.
267
268         :param value: IP6 Heapsize amount.
269         :type value: str
270         """
271         path = ['ip6', 'heap-size']
272         self.add_config_item(self._nodeconfig, value, path)
273
274     def add_plugin_disable(self, *plugins):
275         """Add plugin disable for specific plugin.
276
277         :param plugins: Plugin(s) to disable.
278         :type plugins: list
279         """
280         for plugin in plugins:
281             path = ['plugins', 'plugin {0}'.format(plugin), 'disable']
282             self.add_config_item(self._nodeconfig, ' ', path)
283
284     def add_dpdk_no_multi_seg(self):
285         """Add DPDK no-multi-seg configuration."""
286         path = ['dpdk', 'no-multi-seg']
287         self.add_config_item(self._nodeconfig, '', path)
288
289     def add_nat(self, value='deterministic'):
290         """Add NAT configuration.
291
292         :param value: NAT mode.
293         :type value: str
294         """
295         path = ['nat']
296         self.add_config_item(self._nodeconfig, value, path)
297
298     def apply_config(self, waittime=5, retries=12):
299         """Generate and apply VPP configuration for node.
300
301         Use data from calls to this class to form a startup.conf file and
302         replace /etc/vpp/startup.conf with it on node.
303
304         :param waittime: Time to wait for VPP to restart (default 5 seconds).
305         :param retries: Number of times (default 12) to re-try waiting.
306         :type waittime: int
307         :type retries: int
308         :raises RuntimeError: If writing config file failed, or restarting of
309         VPP failed.
310         """
311         self.dump_config(self._nodeconfig)
312
313         ssh = SSH()
314         ssh.connect(self._node)
315
316         # We're using this "| sudo tee" construct because redirecting
317         # a sudo's output ("sudo echo xxx > /path/to/file") does not
318         # work on most platforms...
319         (ret, _, _) = \
320             ssh.exec_command('echo "{0}" | sudo tee {1}'.
321                              format(self._vpp_config,
322                                     self._vpp_config_filename))
323
324         if ret != 0:
325             raise RuntimeError('Writing config file failed to node {}'.
326                                format(self._hostname))
327
328         # Instead of restarting, we'll do separate start and stop
329         # actions. This way we don't care whether VPP was running
330         # to begin with.
331         ssh.exec_command('sudo service {} stop'
332                          .format(self._vpp_service_name))
333         (ret, _, _) = \
334             ssh.exec_command('sudo service {} start'
335                              .format(self._vpp_service_name))
336         if ret != 0:
337             raise RuntimeError('Restarting VPP failed on node {}'.
338                                format(self._hostname))
339
340         # Sleep <waittime> seconds, up to <retry> times,
341         # and verify if VPP is running.
342         for _ in range(retries):
343             time.sleep(waittime)
344             (ret, _, _) = \
345                 ssh.exec_command('echo show hardware-interfaces | '
346                                  'nc 0 5002 || echo "VPP not yet running"')
347             if ret == 0:
348                 break
349         else:
350             raise RuntimeError('VPP failed to restart on node {}'.
351                                format(self._hostname))
352
353     def apply_config_lxc(self, lxc_name, waittime=5, retries=12):
354         """Generate and apply VPP configuration for node in a container.
355
356         Use data from calls to this class to form a startup.conf file and
357         replace /etc/vpp/startup.conf with it on node inside a container.
358
359         :param lxc_name: LXC container name.
360         :param waittime: Time to wait for VPP to restart (default 5 seconds).
361         :param retries: Number of times (default 12) to re-try waiting.
362         :type lxc_name: str
363         :type waittime: int
364         :type retries: int
365         :raises RuntimeError: If writing config file failed, or restarting of
366         VPP failed.
367         """
368         self.dump_config(self._nodeconfig)
369
370         ssh = SSH()
371         ssh.connect(self._node)
372
373         # We're using this "| sudo tee" construct because redirecting
374         # a sudo's output ("sudo echo xxx > /path/to/file") does not
375         # work on most platforms...
376         (ret, _, _) = \
377             ssh.exec_command_lxc('echo "{0}" | sudo tee {1}'.
378                                  format(self._vpp_config,
379                                         self._vpp_config_filename), lxc_name)
380
381         if ret != 0:
382             raise RuntimeError('Writing config file failed in {0} to node {1}'.
383                                format(lxc_name, self._hostname))
384
385         # Instead of restarting, we'll do separate start and stop
386         # actions. This way we don't care whether VPP was running
387         # to begin with.
388         ssh.exec_command_lxc('service {0} stop'.
389                              format(self._vpp_service_name), lxc_name)
390         (ret, _, _) = \
391             ssh.exec_command_lxc('service {0} start'.
392                                  format(self._vpp_service_name), lxc_name)
393         if ret != 0:
394             raise RuntimeError('Restarting VPP failed in {0} on node {1}'.
395                                format(lxc_name, self._hostname))
396
397         # Sleep <waittime> seconds, up to <retry> times,
398         # and verify if VPP is running.
399         for _ in range(retries):
400             time.sleep(waittime)
401             (ret, _, _) = \
402                 ssh.exec_command_lxc('echo show hardware-interfaces | '
403                                      'nc 0 5002 || echo "VPP not yet running"',
404                                      lxc_name)
405             if ret == 0:
406                 break
407         else:
408             raise RuntimeError('VPP failed to restart in {0} on node {1}'.
409                                format(lxc_name, self._hostname))