CSIT-859: KW to add crypto SW device to startup.conf
[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
26 class VppConfigGenerator(object):
27     """VPP Configuration File Generator."""
28
29     def __init__(self):
30         """Initialize library."""
31         # VPP Node to apply configuration on
32         self._node = ''
33         # VPP Hostname
34         self._hostname = ''
35         # VPP Configuration
36         self._nodeconfig = {}
37         # Serialized VPP Configuration
38         self._vpp_config = ''
39         # VPP Service name
40         self._vpp_service_name = 'vpp'
41         # VPP Logfile location
42         self._vpp_logfile = '/tmp/vpe.log'
43         # VPP Startup config location
44         self._vpp_startup_conf = '/etc/vpp/startup.conf'
45         # VPP Startup config backup location
46         self._vpp_startup_conf_backup = None
47
48     def set_node(self, node):
49         """Set DUT node.
50
51         :param node: Node to store configuration on.
52         :type node: dict
53         :raises RuntimeError: If Node type is not DUT.
54         """
55         if node['type'] != NodeType.DUT:
56             raise RuntimeError('Startup config can only be applied to DUT'
57                                'node.')
58         self._node = node
59         self._hostname = Topology.get_node_hostname(node)
60
61     def set_vpp_logfile(self, logfile):
62         """Set VPP logfile location.
63
64         :param logfile: VPP logfile location.
65         :type logfile: str
66         """
67         self._vpp_logfile = logfile
68
69     def set_vpp_startup_conf_backup(self, backup='/etc/vpp/startup.backup'):
70         """Set VPP startup configuration backup.
71
72         :param backup: VPP logfile location.
73         :type backup: str
74         """
75         self._vpp_startup_conf_backup = backup
76
77     def get_config_str(self):
78         """Get dumped startup configuration in VPP config format.
79
80         :returns: Startup configuration in VPP config format.
81         :rtype: str
82         """
83         self.dump_config(self._nodeconfig)
84         return self._vpp_config
85
86     def add_config_item(self, config, value, path):
87         """Add startup configuration item.
88
89         :param config: Startup configuration of node.
90         :param value: Value to insert.
91         :param path: Path where to insert item.
92         :type config: dict
93         :type value: str
94         :type path: list
95         """
96         if len(path) == 1:
97             config[path[0]] = value
98             return
99         if path[0] not in config:
100             config[path[0]] = {}
101         self.add_config_item(config[path[0]], value, path[1:])
102
103     def dump_config(self, obj, level=-1):
104         """Dump the startup configuration in VPP config format.
105
106         :param obj: Python Object to print.
107         :param level: Nested level for indentation.
108         :type obj: Obj
109         :type level: int
110         :returns: nothing
111         """
112         indent = '  '
113         if level >= 0:
114             self._vpp_config += '{}{{\n'.format((level) * indent)
115         if isinstance(obj, dict):
116             for key, val in obj.items():
117                 if hasattr(val, '__iter__'):
118                     self._vpp_config += '{}{}\n'.format((level + 1) * indent,
119                                                         key)
120                     self.dump_config(val, level + 1)
121                 else:
122                     self._vpp_config += '{}{} {}\n'.format((level + 1) * indent,
123                                                            key, val)
124         else:
125             for val in obj:
126                 self._vpp_config += '{}{}\n'.format((level + 1) * indent, val)
127         if level >= 0:
128             self._vpp_config += '{}}}\n'.format(level * indent)
129
130     def add_unix_log(self, value=None):
131         """Add UNIX log configuration.
132
133         :param value: Log file.
134         :type value: str
135         """
136         path = ['unix', 'log']
137         if value is None:
138             value = self._vpp_logfile
139         self.add_config_item(self._nodeconfig, value, path)
140
141     def add_unix_cli_listen(self, value='localhost:5002'):
142         """Add UNIX cli-listen configuration.
143
144         :param value: CLI listen address and port or path to CLI socket.
145         :type value: str
146         """
147         path = ['unix', 'cli-listen']
148         self.add_config_item(self._nodeconfig, value, path)
149
150     def add_unix_gid(self, value='vpp'):
151         """Add UNIX gid configuration.
152
153         :param value: Gid.
154         :type value: str
155         """
156         path = ['unix', 'gid']
157         self.add_config_item(self._nodeconfig, value, path)
158
159     def add_unix_nodaemon(self):
160         """Add UNIX nodaemon configuration."""
161         path = ['unix', 'nodaemon']
162         self.add_config_item(self._nodeconfig, '', path)
163
164     def add_unix_coredump(self):
165         """Add UNIX full-coredump configuration."""
166         path = ['unix', 'full-coredump']
167         self.add_config_item(self._nodeconfig, '', path)
168
169     def add_unix_exec(self, value):
170         """Add UNIX exec configuration."""
171         path = ['unix', 'exec']
172         self.add_config_item(self._nodeconfig, value, path)
173
174     def add_api_segment_gid(self, value='vpp'):
175         """Add API-SEGMENT gid configuration.
176
177         :param value: Gid.
178         :type value: str
179         """
180         path = ['api-segment', 'gid']
181         self.add_config_item(self._nodeconfig, value, path)
182
183     def add_dpdk_dev(self, *devices):
184         """Add DPDK PCI device configuration.
185
186         :param devices: PCI device(s) (format xxxx:xx:xx.x)
187         :type devices: tuple
188         :raises ValueError: If PCI address format is not valid.
189         """
190         pattern = re.compile("^[0-9A-Fa-f]{4}:[0-9A-Fa-f]{2}:"
191                              "[0-9A-Fa-f]{2}\\.[0-9A-Fa-f]$")
192         for device in devices:
193             if not pattern.match(device):
194                 raise ValueError('PCI address {} to be added to host {} '
195                                  'is not in valid format xxxx:xx:xx.x'.
196                                  format(device, self._hostname))
197             path = ['dpdk', 'dev {0}'.format(device)]
198             self.add_config_item(self._nodeconfig, '', path)
199
200     def add_dpdk_cryptodev(self, count):
201         """Add DPDK Crypto PCI device configuration.
202
203         :param count: Number of HW crypto devices to add.
204         :type count: int
205         """
206         cryptodev = Topology.get_cryptodev(self._node)
207         for i in range(count):
208             cryptodev_config = 'dev {0}'.format(
209                 re.sub(r'\d.\d$', '1.'+str(i), cryptodev))
210             path = ['dpdk', cryptodev_config]
211             self.add_config_item(self._nodeconfig, '', path)
212         self.add_dpdk_uio_driver('igb_uio')
213
214     def add_dpdk_sw_cryptodev(self, count):
215         """Add DPDK Crypto SW device configuration.
216
217         :param count: Number of crypto SW devices to add.
218         :type count: int
219         """
220         for i in range(count):
221             cryptodev_config = 'vdev cryptodev_aesni_mb_pmd,socket_id={0}'.\
222                 format(str(i))
223             path = ['dpdk', cryptodev_config]
224             self.add_config_item(self._nodeconfig, '', path)
225
226     def add_dpdk_dev_default_rxq(self, value):
227         """Add DPDK dev default rxq configuration.
228
229         :param value: Default number of rxqs.
230         :type value: str
231         """
232         path = ['dpdk', 'dev default', 'num-rx-queues']
233         self.add_config_item(self._nodeconfig, value, path)
234
235     def add_dpdk_dev_default_txq(self, value):
236         """Add DPDK dev default txq configuration.
237
238         :param value: Default number of txqs.
239         :type value: str
240         """
241         path = ['dpdk', 'dev default', 'num-tx-queues']
242         self.add_config_item(self._nodeconfig, value, path)
243
244     def add_dpdk_dev_default_rxd(self, value):
245         """Add DPDK dev default rxd configuration.
246
247         :param value: Default number of rxds.
248         :type value: str
249         """
250         path = ['dpdk', 'dev default', 'num-rx-desc']
251         self.add_config_item(self._nodeconfig, value, path)
252
253     def add_dpdk_dev_default_txd(self, value):
254         """Add DPDK dev default txd configuration.
255
256         :param value: Default number of txds.
257         :type value: str
258         """
259         path = ['dpdk', 'dev default', 'num-tx-desc']
260         self.add_config_item(self._nodeconfig, value, path)
261
262     def add_dpdk_socketmem(self, value):
263         """Add DPDK socket memory configuration.
264
265         :param value: Socket memory size.
266         :type value: str
267         """
268         path = ['dpdk', 'socket-mem']
269         self.add_config_item(self._nodeconfig, value, path)
270
271     def add_dpdk_uio_driver(self, value):
272         """Add DPDK uio-driver configuration.
273
274         :param value: DPDK uio-driver configuration.
275         :type value: str
276         """
277         path = ['dpdk', 'uio-driver']
278         self.add_config_item(self._nodeconfig, value, path)
279
280     def add_cpu_main_core(self, value):
281         """Add CPU main core configuration.
282
283         :param value: Main core option.
284         :type value: str
285         """
286         path = ['cpu', 'main-core']
287         self.add_config_item(self._nodeconfig, value, path)
288
289     def add_cpu_corelist_workers(self, value):
290         """Add CPU corelist-workers configuration.
291
292         :param value: Corelist-workers option.
293         :type value: str
294         """
295         path = ['cpu', 'corelist-workers']
296         self.add_config_item(self._nodeconfig, value, path)
297
298     def add_heapsize(self, value):
299         """Add Heapsize configuration.
300
301         :param value: Amount of heapsize.
302         :type value: str
303         """
304         path = ['heapsize']
305         self.add_config_item(self._nodeconfig, value, path)
306
307     def add_api_trace(self):
308         """Add API trace configuration."""
309         path = ['api-trace', 'on']
310         self.add_config_item(self._nodeconfig, '', path)
311
312     def add_ip6_hash_buckets(self, value):
313         """Add IP6 hash buckets configuration.
314
315         :param value: Number of IP6 hash buckets.
316         :type value: str
317         """
318         path = ['ip6', 'hash-buckets']
319         self.add_config_item(self._nodeconfig, value, path)
320
321     def add_ip6_heap_size(self, value):
322         """Add IP6 heap-size configuration.
323
324         :param value: IP6 Heapsize amount.
325         :type value: str
326         """
327         path = ['ip6', 'heap-size']
328         self.add_config_item(self._nodeconfig, value, path)
329
330     def add_plugin_disable(self, *plugins):
331         """Add plugin disable for specific plugin.
332
333         :param plugins: Plugin(s) to disable.
334         :type plugins: list
335         """
336         for plugin in plugins:
337             path = ['plugins', 'plugin {0}'.format(plugin), 'disable']
338             self.add_config_item(self._nodeconfig, ' ', path)
339
340     def add_dpdk_no_multi_seg(self):
341         """Add DPDK no-multi-seg configuration."""
342         path = ['dpdk', 'no-multi-seg']
343         self.add_config_item(self._nodeconfig, '', path)
344
345     def add_nat(self, value='deterministic'):
346         """Add NAT configuration.
347
348         :param value: NAT mode.
349         :type value: str
350         """
351         path = ['nat']
352         self.add_config_item(self._nodeconfig, value, path)
353
354     def apply_config(self, filename=None, waittime=5,
355                      retries=12, restart_vpp=True):
356         """Generate and apply VPP configuration for node.
357
358         Use data from calls to this class to form a startup.conf file and
359         replace /etc/vpp/startup.conf with it on node.
360
361         :param filename: Startup configuration file name.
362         :param waittime: Time to wait for VPP to restart (default 5 seconds).
363         :param retries: Number of times (default 12) to re-try waiting.
364         :param restart_vpp: Whether to restart VPP.
365         :type filename: str
366         :type waittime: int
367         :type retries: int
368         :type restart_vpp: bool.
369         :raises RuntimeError: If writing config file failed or restart of VPP
370             failed or backup of VPP startup.conf failed.
371         """
372         self.dump_config(self._nodeconfig)
373
374         ssh = SSH()
375         ssh.connect(self._node)
376
377         if filename is None:
378             filename = self._vpp_startup_conf
379
380         if self._vpp_startup_conf_backup is not None:
381             (ret, _, _) = \
382                 ssh.exec_command('sudo cp {0} {1}'.
383                                  format(self._vpp_startup_conf,
384                                         self._vpp_startup_conf_backup))
385             if ret != 0:
386                 raise RuntimeError('Backup of config file failed on node {}'.
387                                    format(self._hostname))
388
389         (ret, _, _) = \
390             ssh.exec_command('echo "{config}" | sudo tee {filename}'.
391                              format(config=self._vpp_config,
392                                     filename=filename))
393
394         if ret != 0:
395             raise RuntimeError('Writing config file failed to node {}'.
396                                format(self._hostname))
397
398         if restart_vpp:
399             # Instead of restarting, we'll do separate start and stop
400             # actions. This way we don't care whether VPP was running
401             # to begin with.
402             ssh.exec_command('sudo service {} stop'
403                              .format(self._vpp_service_name))
404             (ret, _, _) = \
405                 ssh.exec_command('sudo service {} start'
406                                  .format(self._vpp_service_name))
407             if ret != 0:
408                 raise RuntimeError('Restarting VPP failed on node {}'.
409                                    format(self._hostname))
410
411             # Sleep <waittime> seconds, up to <retry> times,
412             # and verify if VPP is running.
413             for _ in range(retries):
414                 time.sleep(waittime)
415                 (ret, stdout, _) = \
416                     ssh.exec_command('echo show hardware-interfaces | '
417                                      'nc 0 5002 || echo "VPP not yet running"')
418                 if ret == 0 and stdout != 'VPP not yet running':
419                     break
420             else:
421                 raise RuntimeError('VPP failed to restart on node {}'.
422                                    format(self._hostname))
423
424     def restore_config(self):
425         """Restore VPP startup.conf from backup.
426
427         :raises RuntimeError: When restoration of startup.conf file failed.
428         """
429
430         ssh = SSH()
431         ssh.connect(self._node)
432
433         (ret, _, _) = ssh.exec_command('sudo cp {0} {1}'.
434                                        format(self._vpp_startup_conf_backup,
435                                               self._vpp_startup_conf))
436         if ret != 0:
437             raise RuntimeError('Restoration of config file failed on node {}'.
438                                format(self._hostname))