Add L2xc/L2bd/Ip4/Ip6 2-node tests for x710
[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.constants import Constants
21 from resources.libraries.python.DUTSetup import DUTSetup
22 from resources.libraries.python.topology import NodeType
23 from resources.libraries.python.topology import Topology
24
25 __all__ = ['VppConfigGenerator']
26
27
28 def pci_dev_check(pci_dev):
29     """Check if provided PCI address is in correct format.
30
31     :param pci_dev: PCI address (expected format: xxxx:xx:xx.x).
32     :type pci_dev: str
33     :returns: True if PCI address is in correct format.
34     :rtype: bool
35     :raises ValueError: If PCI address is in incorrect format.
36     """
37     pattern = re.compile("^[0-9A-Fa-f]{4}:[0-9A-Fa-f]{2}:"
38                          "[0-9A-Fa-f]{2}\\.[0-9A-Fa-f]$")
39     if not pattern.match(pci_dev):
40         raise ValueError('PCI address {addr} is not in valid format '
41                          'xxxx:xx:xx.x'.format(addr=pci_dev))
42     return True
43
44
45 class VppConfigGenerator(object):
46     """VPP Configuration File Generator."""
47
48     def __init__(self):
49         """Initialize library."""
50         # VPP Node to apply configuration on
51         self._node = ''
52         # VPP Hostname
53         self._hostname = ''
54         # VPP Configuration
55         self._nodeconfig = {}
56         # Serialized VPP Configuration
57         self._vpp_config = ''
58         # VPP Service name
59         self._vpp_service_name = 'vpp'
60         # VPP Logfile location
61         self._vpp_logfile = '/tmp/vpe.log'
62         # VPP Startup config location
63         self._vpp_startup_conf = '/etc/vpp/startup.conf'
64         # VPP Startup config backup location
65         self._vpp_startup_conf_backup = None
66
67     def set_node(self, node):
68         """Set DUT node.
69
70         :param node: Node to store configuration on.
71         :type node: dict
72         :raises RuntimeError: If Node type is not DUT.
73         """
74         if node['type'] != NodeType.DUT:
75             raise RuntimeError('Startup config can only be applied to DUT'
76                                'node.')
77         self._node = node
78         self._hostname = Topology.get_node_hostname(node)
79
80     def set_vpp_logfile(self, logfile):
81         """Set VPP logfile location.
82
83         :param logfile: VPP logfile location.
84         :type logfile: str
85         """
86         self._vpp_logfile = logfile
87
88     def set_vpp_startup_conf_backup(self, backup='/etc/vpp/startup.backup'):
89         """Set VPP startup configuration backup.
90
91         :param backup: VPP logfile location.
92         :type backup: str
93         """
94         self._vpp_startup_conf_backup = backup
95
96     def get_config_str(self):
97         """Get dumped startup configuration in VPP config format.
98
99         :returns: Startup configuration in VPP config format.
100         :rtype: str
101         """
102         self.dump_config(self._nodeconfig)
103         return self._vpp_config
104
105     def add_config_item(self, config, value, path):
106         """Add startup configuration item.
107
108         :param config: Startup configuration of node.
109         :param value: Value to insert.
110         :param path: Path where to insert item.
111         :type config: dict
112         :type value: str
113         :type path: list
114         """
115         if len(path) == 1:
116             config[path[0]] = value
117             return
118         if path[0] not in config:
119             config[path[0]] = {}
120         elif isinstance(config[path[0]], str):
121             config[path[0]] = {} if config[path[0]] == '' \
122                 else {config[path[0]]: ''}
123         self.add_config_item(config[path[0]], value, path[1:])
124
125     def dump_config(self, obj, level=-1):
126         """Dump the startup configuration in VPP config format.
127
128         :param obj: Python Object to print.
129         :param level: Nested level for indentation.
130         :type obj: Obj
131         :type level: int
132         :returns: nothing
133         """
134         indent = '  '
135         if level >= 0:
136             self._vpp_config += '{}{{\n'.format((level) * indent)
137         if isinstance(obj, dict):
138             for key, val in obj.items():
139                 if hasattr(val, '__iter__'):
140                     self._vpp_config += '{}{}\n'.format((level + 1) * indent,
141                                                         key)
142                     self.dump_config(val, level + 1)
143                 else:
144                     self._vpp_config += '{}{} {}\n'.format((level + 1) * indent,
145                                                            key, val)
146         else:
147             for val in obj:
148                 self._vpp_config += '{}{}\n'.format((level + 1) * indent, val)
149         if level >= 0:
150             self._vpp_config += '{}}}\n'.format(level * indent)
151
152     def add_unix_log(self, value=None):
153         """Add UNIX log configuration.
154
155         :param value: Log file.
156         :type value: str
157         """
158         path = ['unix', 'log']
159         if value is None:
160             value = self._vpp_logfile
161         self.add_config_item(self._nodeconfig, value, path)
162
163     def add_unix_cli_listen(self, value='/run/vpp/cli.sock'):
164         """Add UNIX cli-listen configuration.
165
166         :param value: CLI listen address and port or path to CLI socket.
167         :type value: str
168         """
169         path = ['unix', 'cli-listen']
170         self.add_config_item(self._nodeconfig, value, path)
171
172     def add_unix_gid(self, value='vpp'):
173         """Add UNIX gid configuration.
174
175         :param value: Gid.
176         :type value: str
177         """
178         path = ['unix', 'gid']
179         self.add_config_item(self._nodeconfig, value, path)
180
181     def add_unix_nodaemon(self):
182         """Add UNIX nodaemon configuration."""
183         path = ['unix', 'nodaemon']
184         self.add_config_item(self._nodeconfig, '', path)
185
186     def add_unix_coredump(self):
187         """Add UNIX full-coredump configuration."""
188         path = ['unix', 'full-coredump']
189         self.add_config_item(self._nodeconfig, '', path)
190
191     def add_unix_exec(self, value):
192         """Add UNIX exec configuration."""
193         path = ['unix', 'exec']
194         self.add_config_item(self._nodeconfig, value, path)
195
196     def add_api_segment_gid(self, value='vpp'):
197         """Add API-SEGMENT gid configuration.
198
199         :param value: Gid.
200         :type value: str
201         """
202         path = ['api-segment', 'gid']
203         self.add_config_item(self._nodeconfig, value, path)
204
205     def add_api_segment_global_size(self, value):
206         """Add API-SEGMENT global-size configuration.
207
208         :param value: Global size.
209         :type value: str
210         """
211         path = ['api-segment', 'global-size']
212         self.add_config_item(self._nodeconfig, value, path)
213
214     def add_api_segment_api_size(self, value):
215         """Add API-SEGMENT api-size configuration.
216
217         :param value: API size.
218         :type value: str
219         """
220         path = ['api-segment', 'api-size']
221         self.add_config_item(self._nodeconfig, value, path)
222
223     def add_dpdk_dev(self, *devices):
224         """Add DPDK PCI device configuration.
225
226         :param devices: PCI device(s) (format xxxx:xx:xx.x)
227         :type devices: tuple
228         """
229         for device in devices:
230             if pci_dev_check(device):
231                 path = ['dpdk', 'dev {0}'.format(device)]
232                 self.add_config_item(self._nodeconfig, '', path)
233
234     def add_dpdk_dev_parameter(self, device, parameter, value):
235         """Add parameter for DPDK device.
236
237         :param device: PCI device (format xxxx:xx:xx.x).
238         :param parameter: Parameter name.
239         :param value: Parameter value.
240         :type device: str
241         :type parameter: str
242         :type value: str
243         """
244         if pci_dev_check(device):
245             path = ['dpdk', 'dev {0}'.format(device), parameter]
246             self.add_config_item(self._nodeconfig, value, path)
247
248     def add_dpdk_cryptodev(self, count):
249         """Add DPDK Crypto PCI device configuration.
250
251         :param count: Number of HW crypto devices to add.
252         :type count: int
253         """
254         cryptodev = Topology.get_cryptodev(self._node)
255         for i in range(count):
256             cryptodev_config = 'dev {0}'.format(
257                 re.sub(r'\d.\d$', '1.'+str(i), cryptodev))
258             path = ['dpdk', cryptodev_config]
259             self.add_config_item(self._nodeconfig, '', path)
260         self.add_dpdk_uio_driver('igb_uio')
261
262     def add_dpdk_sw_cryptodev(self, sw_pmd_type, socket_id, count):
263         """Add DPDK SW Crypto device configuration.
264
265         :param sw_pmd_type: Type of SW crypto device PMD to add.
266         :param socket_id: Socket ID.
267         :param count: Number of SW crypto devices to add.
268         :type sw_pmd_type: str
269         :type socket_id: int
270         :type count: int
271         """
272         for _ in range(count):
273             cryptodev_config = 'vdev cryptodev_{0}_pmd,socket_id={1}'.\
274                 format(sw_pmd_type, str(socket_id))
275             path = ['dpdk', cryptodev_config]
276             self.add_config_item(self._nodeconfig, '', path)
277
278     def add_dpdk_eth_bond_dev(self, ethbond_id, mode, xmit_policy, *slaves):
279         """Add DPDK Eth_bond device configuration.
280
281         :param ethbond_id: Eth_bond device ID.
282         :param mode: Link bonding mode.
283         :param xmit_policy: Transmission policy.
284         :param slaves: PCI device(s) to be bonded (format xxxx:xx:xx.x).
285         :type ethbond_id: str or int
286         :type mode: str or int
287         :type xmit_policy: str
288         :type slaves: list
289         """
290         slaves_config = ',slave=' + \
291                         ',slave='.join(slave if pci_dev_check(slave) else ''
292                                        for slave in slaves)
293         ethbond_config = 'vdev eth_bond{id},mode={mode}{slaves},' \
294                          'xmit_policy={xmit_pol}'.format(id=ethbond_id,
295                                                          mode=mode,
296                                                          slaves=slaves_config,
297                                                          xmit_pol=xmit_policy)
298         path = ['dpdk', ethbond_config]
299         self.add_config_item(self._nodeconfig, '', path)
300
301     def add_dpdk_dev_default_rxq(self, value):
302         """Add DPDK dev default rxq configuration.
303
304         :param value: Default number of rxqs.
305         :type value: str
306         """
307         path = ['dpdk', 'dev default', 'num-rx-queues']
308         self.add_config_item(self._nodeconfig, value, path)
309
310     def add_dpdk_dev_default_txq(self, value):
311         """Add DPDK dev default txq configuration.
312
313         :param value: Default number of txqs.
314         :type value: str
315         """
316         path = ['dpdk', 'dev default', 'num-tx-queues']
317         self.add_config_item(self._nodeconfig, value, path)
318
319     def add_dpdk_dev_default_rxd(self, value):
320         """Add DPDK dev default rxd configuration.
321
322         :param value: Default number of rxds.
323         :type value: str
324         """
325         path = ['dpdk', 'dev default', 'num-rx-desc']
326         self.add_config_item(self._nodeconfig, value, path)
327
328     def add_dpdk_dev_default_txd(self, value):
329         """Add DPDK dev default txd configuration.
330
331         :param value: Default number of txds.
332         :type value: str
333         """
334         path = ['dpdk', 'dev default', 'num-tx-desc']
335         self.add_config_item(self._nodeconfig, value, path)
336
337     def add_dpdk_log_level(self, value):
338         """Add DPDK log-level configuration.
339
340         :param value: Log level.
341         :type value: str
342         """
343         path = ['dpdk', 'log-level']
344         self.add_config_item(self._nodeconfig, value, path)
345
346     def add_dpdk_socketmem(self, value):
347         """Add DPDK socket memory configuration.
348
349         :param value: Socket memory size.
350         :type value: str
351         """
352         path = ['dpdk', 'socket-mem']
353         self.add_config_item(self._nodeconfig, value, path)
354
355     def add_dpdk_num_mbufs(self, value):
356         """Add DPDK number of I/O buffers.
357
358         :param value: Number of I/O buffers.
359         :type value: int
360         """
361         path = ['dpdk', 'num-mbufs']
362         self.add_config_item(self._nodeconfig, value, path)
363
364     def add_dpdk_uio_driver(self, value=None):
365         """Add DPDK uio-driver configuration.
366
367         :param value: DPDK uio-driver configuration. By default, driver will be
368                       loaded automatically from Topology file, still leaving
369                       option to manually override by parameter.
370         :type value: str
371         """
372         if value is None:
373             value = Topology.get_uio_driver(self._node)
374         path = ['dpdk', 'uio-driver']
375         self.add_config_item(self._nodeconfig, value, path)
376
377     def add_cpu_main_core(self, value):
378         """Add CPU main core configuration.
379
380         :param value: Main core option.
381         :type value: str
382         """
383         path = ['cpu', 'main-core']
384         self.add_config_item(self._nodeconfig, value, path)
385
386     def add_cpu_corelist_workers(self, value):
387         """Add CPU corelist-workers configuration.
388
389         :param value: Corelist-workers option.
390         :type value: str
391         """
392         path = ['cpu', 'corelist-workers']
393         self.add_config_item(self._nodeconfig, value, path)
394
395     def add_heapsize(self, value):
396         """Add Heapsize configuration.
397
398         :param value: Amount of heapsize.
399         :type value: str
400         """
401         path = ['heapsize']
402         self.add_config_item(self._nodeconfig, value, path)
403
404     def add_api_trace(self):
405         """Add API trace configuration."""
406         path = ['api-trace', 'on']
407         self.add_config_item(self._nodeconfig, '', path)
408
409     def add_ip6_hash_buckets(self, value):
410         """Add IP6 hash buckets configuration.
411
412         :param value: Number of IP6 hash buckets.
413         :type value: str
414         """
415         path = ['ip6', 'hash-buckets']
416         self.add_config_item(self._nodeconfig, value, path)
417
418     def add_ip6_heap_size(self, value):
419         """Add IP6 heap-size configuration.
420
421         :param value: IP6 Heapsize amount.
422         :type value: str
423         """
424         path = ['ip6', 'heap-size']
425         self.add_config_item(self._nodeconfig, value, path)
426
427     def add_ip_heap_size(self, value):
428         """Add IP heap-size configuration.
429
430         :param value: IP Heapsize amount.
431         :type value: str
432         """
433         path = ['ip', 'heap-size']
434         self.add_config_item(self._nodeconfig, value, path)
435
436     def add_statseg_size(self, value):
437         """Add stats segment heap size configuration.
438
439         :param value: Stats heapsize amount.
440         :type value: str
441         """
442         path = ['statseg', 'size']
443         self.add_config_item(self._nodeconfig, value, path)
444
445     def add_plugin(self, state, *plugins):
446         """Add plugin section for specific plugin(s).
447
448         :param state: State of plugin [enable|disable].
449         :param plugins: Plugin(s) to disable.
450         :type state: str
451         :type plugins: list
452         """
453         for plugin in plugins:
454             path = ['plugins', 'plugin {0}'.format(plugin), state]
455             self.add_config_item(self._nodeconfig, ' ', path)
456
457     def add_dpdk_no_multi_seg(self):
458         """Add DPDK no-multi-seg configuration."""
459         path = ['dpdk', 'no-multi-seg']
460         self.add_config_item(self._nodeconfig, '', path)
461
462     def add_dpdk_no_tx_checksum_offload(self):
463         """Add DPDK no-tx-checksum-offload configuration."""
464         path = ['dpdk', 'no-tx-checksum-offload']
465         self.add_config_item(self._nodeconfig, '', path)
466
467     def add_nat(self, value='deterministic'):
468         """Add NAT configuration.
469
470         :param value: NAT mode.
471         :type value: str
472         """
473         path = ['nat']
474         self.add_config_item(self._nodeconfig, value, path)
475
476     def add_tcp_preallocated_connections(self, value):
477         """Add TCP pre-allocated connections.
478
479         :param value: The number of pre-allocated connections.
480         :type value: int
481         """
482         path = ['tcp', 'preallocated-connections']
483         self.add_config_item(self._nodeconfig, value, path)
484
485     def add_tcp_preallocated_half_open_connections(self, value):
486         """Add TCP pre-allocated half open connections.
487
488         :param value: The number of pre-allocated half open connections.
489         :type value: int
490         """
491         path = ['tcp', 'preallocated-half-open-connections']
492         self.add_config_item(self._nodeconfig, value, path)
493
494     def add_session_event_queue_length(self, value):
495         """Add session event queue length.
496
497         :param value: Session event queue length.
498         :type value: int
499         """
500         path = ['session', 'event-queue-length']
501         self.add_config_item(self._nodeconfig, value, path)
502
503     def add_session_preallocated_sessions(self, value):
504         """Add the number of pre-allocated sessions.
505
506         :param value: Number of pre-allocated sessions.
507         :type value: int
508         """
509         path = ['session', 'preallocated-sessions']
510         self.add_config_item(self._nodeconfig, value, path)
511
512     def add_session_v4_session_table_buckets(self, value):
513         """Add number of v4 session table buckets to the config.
514
515         :param value: Number of v4 session table buckets.
516         :type value: int
517         """
518         path = ['session', 'v4-session-table-buckets']
519         self.add_config_item(self._nodeconfig, value, path)
520
521     def add_session_v4_session_table_memory(self, value):
522         """Add the size of v4 session table memory.
523
524         :param value: Size of v4 session table memory.
525         :type value: str
526         """
527         path = ['session', 'v4-session-table-memory']
528         self.add_config_item(self._nodeconfig, value, path)
529
530     def add_session_v4_halfopen_table_buckets(self, value):
531         """Add the number of v4 halfopen table buckets.
532
533         :param value: Number of v4 halfopen table buckets.
534         :type value: int
535         """
536         path = ['session', 'v4-halfopen-table-buckets']
537         self.add_config_item(self._nodeconfig, value, path)
538
539     def add_session_v4_halfopen_table_memory(self, value):
540         """Add the size of v4 halfopen table memory.
541
542         :param value: Size of v4 halfopen table memory.
543         :type value: str
544         """
545         path = ['session', 'v4-halfopen-table-memory']
546         self.add_config_item(self._nodeconfig, value, path)
547
548     def add_session_local_endpoints_table_buckets(self, value):
549         """Add the number of local endpoints table buckets.
550
551         :param value: Number of local endpoints table buckets.
552         :type value: int
553         """
554         path = ['session', 'local-endpoints-table-buckets']
555         self.add_config_item(self._nodeconfig, value, path)
556
557     def add_session_local_endpoints_table_memory(self, value):
558         """Add the size of local endpoints table memory.
559
560         :param value: Size of local endpoints table memory.
561         :type value: str
562         """
563         path = ['session', 'local-endpoints-table-memory']
564         self.add_config_item(self._nodeconfig, value, path)
565
566     def apply_config(self, filename=None, retries=60, restart_vpp=True):
567         """Generate and apply VPP configuration for node.
568
569         Use data from calls to this class to form a startup.conf file and
570         replace /etc/vpp/startup.conf with it on node.
571
572         :param filename: Startup configuration file name.
573         :param retries: Number of times (default 60) to re-try waiting.
574         :param restart_vpp: Whether to restart VPP.
575         :type filename: str
576         :type retries: int
577         :type restart_vpp: bool.
578         :raises RuntimeError: If writing config file failed or restart of VPP
579             failed or backup of VPP startup.conf failed.
580         """
581         self.dump_config(self._nodeconfig)
582
583         ssh = SSH()
584         ssh.connect(self._node)
585
586         if filename is None:
587             filename = self._vpp_startup_conf
588
589         if self._vpp_startup_conf_backup is not None:
590             ret, _, _ = \
591                 ssh.exec_command('sudo cp {src} {dest}'.
592                                  format(src=self._vpp_startup_conf,
593                                         dest=self._vpp_startup_conf_backup))
594             if ret != 0:
595                 raise RuntimeError('Backup of config file failed on node '
596                                    '{name}'.format(name=self._hostname))
597
598         ret, _, _ = \
599             ssh.exec_command('echo "{config}" | sudo tee {filename}'.
600                              format(config=self._vpp_config,
601                                     filename=filename))
602
603         if ret != 0:
604             raise RuntimeError('Writing config file failed to node {name}'.
605                                format(name=self._hostname))
606
607         if restart_vpp:
608             DUTSetup.start_service(self._node, Constants.VPP_UNIT)
609
610             # Sleep <waittime> seconds, up to <retry> times,
611             # and verify if VPP is running.
612             for _ in range(retries):
613                 time.sleep(1)
614                 ret, stdout, _ = \
615                     ssh.exec_command_sudo('vppctl show pci')
616                 if ret == 0 and 'Connection refused' not in stdout:
617                     break
618             else:
619                 raise RuntimeError('VPP failed to restart on node {name}'.
620                                    format(name=self._hostname))
621
622     def restore_config(self):
623         """Restore VPP startup.conf from backup.
624
625         :raises RuntimeError: When restoration of startup.conf file failed.
626         """
627
628         ssh = SSH()
629         ssh.connect(self._node)
630
631         ret, _, _ = ssh.exec_command('sudo cp {src} {dest}'.
632                                      format(src=self._vpp_startup_conf_backup,
633                                             dest=self._vpp_startup_conf))
634         if ret != 0:
635             raise RuntimeError('Restoration of config file failed on node '
636                                '{name}'.format(name=self._hostname))