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