Add no-tx-checksum-offload to startup.conf in case of perf tests
[csit.git] / resources / libraries / python / VppConfigGenerator.py
1 # Copyright (c) 2018 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_api_segment_global_size(self, value):
184         """Add API-SEGMENT global-size configuration.
185
186         :param value: Global size.
187         :type value: str
188         """
189         path = ['api-segment', 'global-size']
190         self.add_config_item(self._nodeconfig, value, path)
191
192     def add_api_segment_api_size(self, value):
193         """Add API-SEGMENT api-size configuration.
194
195         :param value: API size.
196         :type value: str
197         """
198         path = ['api-segment', 'api-size']
199         self.add_config_item(self._nodeconfig, value, path)
200
201     def add_dpdk_dev(self, *devices):
202         """Add DPDK PCI device configuration.
203
204         :param devices: PCI device(s) (format xxxx:xx:xx.x)
205         :type devices: tuple
206         :raises ValueError: If PCI address format is not valid.
207         """
208         pattern = re.compile("^[0-9A-Fa-f]{4}:[0-9A-Fa-f]{2}:"
209                              "[0-9A-Fa-f]{2}\\.[0-9A-Fa-f]$")
210         for device in devices:
211             if not pattern.match(device):
212                 raise ValueError('PCI address {} to be added to host {} '
213                                  'is not in valid format xxxx:xx:xx.x'.
214                                  format(device, self._hostname))
215             path = ['dpdk', 'dev {0}'.format(device)]
216             self.add_config_item(self._nodeconfig, '', path)
217
218     def add_dpdk_cryptodev(self, count):
219         """Add DPDK Crypto PCI device configuration.
220
221         :param count: Number of HW crypto devices to add.
222         :type count: int
223         """
224         cryptodev = Topology.get_cryptodev(self._node)
225         for i in range(count):
226             cryptodev_config = 'dev {0}'.format(
227                 re.sub(r'\d.\d$', '1.'+str(i), cryptodev))
228             path = ['dpdk', cryptodev_config]
229             self.add_config_item(self._nodeconfig, '', path)
230         self.add_dpdk_uio_driver('igb_uio')
231
232     def add_dpdk_sw_cryptodev(self, sw_pmd_type, socket_id, count):
233         """Add DPDK SW Crypto device configuration.
234
235         :param sw_pmd_type: Type of SW crypto device PMD to add.
236         :param socket_id: Socket ID.
237         :param count: Number of SW crypto devices to add.
238         :type sw_pmd_type: str
239         :type socket_id: int
240         :type count: int
241         """
242         for _ in range(count):
243             cryptodev_config = 'vdev cryptodev_{0}_pmd,socket_id={1}'.\
244                 format(sw_pmd_type, str(socket_id))
245             path = ['dpdk', cryptodev_config]
246             self.add_config_item(self._nodeconfig, '', path)
247
248     def add_dpdk_dev_default_rxq(self, value):
249         """Add DPDK dev default rxq configuration.
250
251         :param value: Default number of rxqs.
252         :type value: str
253         """
254         path = ['dpdk', 'dev default', 'num-rx-queues']
255         self.add_config_item(self._nodeconfig, value, path)
256
257     def add_dpdk_dev_default_txq(self, value):
258         """Add DPDK dev default txq configuration.
259
260         :param value: Default number of txqs.
261         :type value: str
262         """
263         path = ['dpdk', 'dev default', 'num-tx-queues']
264         self.add_config_item(self._nodeconfig, value, path)
265
266     def add_dpdk_dev_default_rxd(self, value):
267         """Add DPDK dev default rxd configuration.
268
269         :param value: Default number of rxds.
270         :type value: str
271         """
272         path = ['dpdk', 'dev default', 'num-rx-desc']
273         self.add_config_item(self._nodeconfig, value, path)
274
275     def add_dpdk_dev_default_txd(self, value):
276         """Add DPDK dev default txd configuration.
277
278         :param value: Default number of txds.
279         :type value: str
280         """
281         path = ['dpdk', 'dev default', 'num-tx-desc']
282         self.add_config_item(self._nodeconfig, value, path)
283
284     def add_dpdk_socketmem(self, value):
285         """Add DPDK socket memory configuration.
286
287         :param value: Socket memory size.
288         :type value: str
289         """
290         path = ['dpdk', 'socket-mem']
291         self.add_config_item(self._nodeconfig, value, path)
292
293     def add_dpdk_uio_driver(self, value):
294         """Add DPDK uio-driver configuration.
295
296         :param value: DPDK uio-driver configuration.
297         :type value: str
298         """
299         path = ['dpdk', 'uio-driver']
300         self.add_config_item(self._nodeconfig, value, path)
301
302     def add_cpu_main_core(self, value):
303         """Add CPU main core configuration.
304
305         :param value: Main core option.
306         :type value: str
307         """
308         path = ['cpu', 'main-core']
309         self.add_config_item(self._nodeconfig, value, path)
310
311     def add_cpu_corelist_workers(self, value):
312         """Add CPU corelist-workers configuration.
313
314         :param value: Corelist-workers option.
315         :type value: str
316         """
317         path = ['cpu', 'corelist-workers']
318         self.add_config_item(self._nodeconfig, value, path)
319
320     def add_heapsize(self, value):
321         """Add Heapsize configuration.
322
323         :param value: Amount of heapsize.
324         :type value: str
325         """
326         path = ['heapsize']
327         self.add_config_item(self._nodeconfig, value, path)
328
329     def add_api_trace(self):
330         """Add API trace configuration."""
331         path = ['api-trace', 'on']
332         self.add_config_item(self._nodeconfig, '', path)
333
334     def add_ip6_hash_buckets(self, value):
335         """Add IP6 hash buckets configuration.
336
337         :param value: Number of IP6 hash buckets.
338         :type value: str
339         """
340         path = ['ip6', 'hash-buckets']
341         self.add_config_item(self._nodeconfig, value, path)
342
343     def add_ip6_heap_size(self, value):
344         """Add IP6 heap-size configuration.
345
346         :param value: IP6 Heapsize amount.
347         :type value: str
348         """
349         path = ['ip6', 'heap-size']
350         self.add_config_item(self._nodeconfig, value, path)
351
352     def add_ip_heap_size(self, value):
353         """Add IP heap-size configuration.
354
355         :param value: IP Heapsize amount.
356         :type value: str
357         """
358         path = ['ip', 'heap-size']
359         self.add_config_item(self._nodeconfig, value, path)
360
361     def add_plugin_disable(self, *plugins):
362         """Add plugin disable for specific plugin.
363
364         :param plugins: Plugin(s) to disable.
365         :type plugins: list
366         """
367         for plugin in plugins:
368             path = ['plugins', 'plugin {0}'.format(plugin), 'disable']
369             self.add_config_item(self._nodeconfig, ' ', path)
370
371     def add_dpdk_no_multi_seg(self):
372         """Add DPDK no-multi-seg configuration."""
373         path = ['dpdk', 'no-multi-seg']
374         self.add_config_item(self._nodeconfig, '', path)
375
376     def add_dpdk_no_tx_checksum_offload(self):
377         """Add DPDK no-tx-checksum-offload configuration."""
378         path = ['dpdk', 'no-tx-checksum-offload']
379         self.add_config_item(self._nodeconfig, '', path)
380
381     def add_nat(self, value='deterministic'):
382         """Add NAT configuration.
383
384         :param value: NAT mode.
385         :type value: str
386         """
387         path = ['nat']
388         self.add_config_item(self._nodeconfig, value, path)
389
390     def add_tcp_preallocated_connections(self, value):
391         """Add TCP pre-allocated connections.
392
393         :param value: The number of pre-allocated connections.
394         :type value: int
395         """
396         path = ['tcp', 'preallocated-connections']
397         self.add_config_item(self._nodeconfig, value, path)
398
399     def add_tcp_preallocated_half_open_connections(self, value):
400         """Add TCP pre-allocated half open connections.
401
402         :param value: The number of pre-allocated half open connections.
403         :type value: int
404         """
405         path = ['tcp', 'preallocated-half-open-connections']
406         self.add_config_item(self._nodeconfig, value, path)
407
408     def add_session_event_queue_length(self, value):
409         """Add session event queue length.
410
411         :param value: Session event queue length.
412         :type value: int
413         """
414         path = ['session', 'event-queue-length']
415         self.add_config_item(self._nodeconfig, value, path)
416
417     def add_session_preallocated_sessions(self, value):
418         """Add the number of pre-allocated sessions.
419
420         :param value: Number of pre-allocated sessions.
421         :type value: int
422         """
423         path = ['session', 'preallocated-sessions']
424         self.add_config_item(self._nodeconfig, value, path)
425
426     def add_session_v4_session_table_buckets(self, value):
427         """Add number of v4 session table buckets to the config.
428
429         :param value: Number of v4 session table buckets.
430         :type value: int
431         """
432         path = ['session', 'v4-session-table-buckets']
433         self.add_config_item(self._nodeconfig, value, path)
434
435     def add_session_v4_session_table_memory(self, value):
436         """Add the size of v4 session table memory.
437
438         :param value: Size of v4 session table memory.
439         :type value: str
440         """
441         path = ['session', 'v4-session-table-memory']
442         self.add_config_item(self._nodeconfig, value, path)
443
444     def add_session_v4_halfopen_table_buckets(self, value):
445         """Add the number of v4 halfopen table buckets.
446
447         :param value: Number of v4 halfopen table buckets.
448         :type value: int
449         """
450         path = ['session', 'v4-halfopen-table-buckets']
451         self.add_config_item(self._nodeconfig, value, path)
452
453     def add_session_v4_halfopen_table_memory(self, value):
454         """Add the size of v4 halfopen table memory.
455
456         :param value: Size of v4 halfopen table memory.
457         :type value: str
458         """
459         path = ['session', 'v4-halfopen-table-memory']
460         self.add_config_item(self._nodeconfig, value, path)
461
462     def add_session_local_endpoints_table_buckets(self, value):
463         """Add the number of local endpoints table buckets.
464
465         :param value: Number of local endpoints table buckets.
466         :type value: int
467         """
468         path = ['session', 'local-endpoints-table-buckets']
469         self.add_config_item(self._nodeconfig, value, path)
470
471     def add_session_local_endpoints_table_memory(self, value):
472         """Add the size of local endpoints table memory.
473
474         :param value: Size of local endpoints table memory.
475         :type value: str
476         """
477         path = ['session', 'local-endpoints-table-memory']
478         self.add_config_item(self._nodeconfig, value, path)
479
480     def apply_config(self, filename=None, waittime=5,
481                      retries=12, restart_vpp=True):
482         """Generate and apply VPP configuration for node.
483
484         Use data from calls to this class to form a startup.conf file and
485         replace /etc/vpp/startup.conf with it on node.
486
487         :param filename: Startup configuration file name.
488         :param waittime: Time to wait for VPP to restart (default 5 seconds).
489         :param retries: Number of times (default 12) to re-try waiting.
490         :param restart_vpp: Whether to restart VPP.
491         :type filename: str
492         :type waittime: int
493         :type retries: int
494         :type restart_vpp: bool.
495         :raises RuntimeError: If writing config file failed or restart of VPP
496             failed or backup of VPP startup.conf failed.
497         """
498         self.dump_config(self._nodeconfig)
499
500         ssh = SSH()
501         ssh.connect(self._node)
502
503         if filename is None:
504             filename = self._vpp_startup_conf
505
506         if self._vpp_startup_conf_backup is not None:
507             (ret, _, _) = \
508                 ssh.exec_command('sudo cp {0} {1}'.
509                                  format(self._vpp_startup_conf,
510                                         self._vpp_startup_conf_backup))
511             if ret != 0:
512                 raise RuntimeError('Backup of config file failed on node {}'.
513                                    format(self._hostname))
514
515         (ret, _, _) = \
516             ssh.exec_command('echo "{config}" | sudo tee {filename}'.
517                              format(config=self._vpp_config,
518                                     filename=filename))
519
520         if ret != 0:
521             raise RuntimeError('Writing config file failed to node {}'.
522                                format(self._hostname))
523
524         if restart_vpp:
525             # Instead of restarting, we'll do separate start and stop
526             # actions. This way we don't care whether VPP was running
527             # to begin with.
528             ssh.exec_command('sudo service {} stop'
529                              .format(self._vpp_service_name))
530             (ret, _, _) = \
531                 ssh.exec_command('sudo service {} start'
532                                  .format(self._vpp_service_name))
533             if ret != 0:
534                 raise RuntimeError('Restarting VPP failed on node {}'.
535                                    format(self._hostname))
536
537             # Sleep <waittime> seconds, up to <retry> times,
538             # and verify if VPP is running.
539             for _ in range(retries):
540                 time.sleep(waittime)
541                 (ret, stdout, _) = \
542                     ssh.exec_command('echo show hardware-interfaces | '
543                                      'nc 0 5002 || echo "VPP not yet running"')
544                 if ret == 0 and stdout != 'VPP not yet running':
545                     break
546             else:
547                 raise RuntimeError('VPP failed to restart on node {}'.
548                                    format(self._hostname))
549
550     def restore_config(self):
551         """Restore VPP startup.conf from backup.
552
553         :raises RuntimeError: When restoration of startup.conf file failed.
554         """
555
556         ssh = SSH()
557         ssh.connect(self._node)
558
559         (ret, _, _) = ssh.exec_command('sudo cp {0} {1}'.
560                                        format(self._vpp_startup_conf_backup,
561                                               self._vpp_startup_conf))
562         if ret != 0:
563             raise RuntimeError('Restoration of config file failed on node {}'.
564                                format(self._hostname))