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