fix(core): wrong to unbind driver if pci device in unbinded driver state
[csit.git] / resources / libraries / python / DUTSetup.py
1 # Copyright (c) 2023 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 """DUT setup library."""
15
16 from time import sleep
17 from robot.api import logger
18
19 from resources.libraries.python.Constants import Constants
20 from resources.libraries.python.ssh import SSH, exec_cmd, exec_cmd_no_error
21 from resources.libraries.python.topology import NodeType, Topology
22
23
24 class DUTSetup:
25     """Contains methods for setting up DUTs."""
26
27     @staticmethod
28     def get_service_logs(node, service):
29         """Get specific service unit logs from node.
30
31         :param node: Node in the topology.
32         :param service: Service unit name.
33         :type node: dict
34         :type service: str
35         """
36         command = u"cat /tmp/*supervisor*.log"\
37             if DUTSetup.running_in_container(node) \
38             else f"journalctl --no-pager _SYSTEMD_INVOCATION_ID=$(systemctl " \
39             f"show -p InvocationID --value {service})"
40
41         message = f"Node {node[u'host']} failed to get logs from unit {service}"
42
43         exec_cmd_no_error(
44             node, command, timeout=30, sudo=True, message=message
45         )
46
47     @staticmethod
48     def get_service_logs_on_all_duts(nodes, service):
49         """Get specific service unit logs from all DUTs.
50
51         :param nodes: Nodes in the topology.
52         :param service: Service unit name.
53         :type nodes: dict
54         :type service: str
55         """
56         for node in nodes.values():
57             if node[u"type"] == NodeType.DUT:
58                 DUTSetup.get_service_logs(node, service)
59
60     @staticmethod
61     def restart_service(node, service):
62         """Restart the named service on node.
63
64         :param node: Node in the topology.
65         :param service: Service unit name.
66         :type node: dict
67         :type service: str
68         """
69         command = f"supervisorctl restart {service}" \
70             if DUTSetup.running_in_container(node) \
71             else f"service {service} restart"
72         message = f"Node {node[u'host']} failed to restart service {service}"
73
74         exec_cmd_no_error(
75             node, command, timeout=180, sudo=True, message=message
76         )
77
78         DUTSetup.get_service_logs(node, service)
79
80     @staticmethod
81     def restart_service_on_all_duts(nodes, service):
82         """Restart the named service on all DUTs.
83
84         :param nodes: Nodes in the topology.
85         :param service: Service unit name.
86         :type nodes: dict
87         :type service: str
88         """
89         for node in nodes.values():
90             if node[u"type"] == NodeType.DUT:
91                 DUTSetup.restart_service(node, service)
92
93     @staticmethod
94     def start_service(node, service):
95         """Start up the named service on node.
96
97         :param node: Node in the topology.
98         :param service: Service unit name.
99         :type node: dict
100         :type service: str
101         """
102         # TODO: change command to start once all parent function updated.
103         command = f"supervisorctl restart {service}" \
104             if DUTSetup.running_in_container(node) \
105             else f"service {service} restart"
106         message = f"Node {node[u'host']} failed to start service {service}"
107
108         exec_cmd_no_error(
109             node, command, timeout=180, sudo=True, message=message
110         )
111
112         DUTSetup.get_service_logs(node, service)
113
114     @staticmethod
115     def start_service_on_all_duts(nodes, service):
116         """Start up the named service on all DUTs.
117
118         :param nodes: Nodes in the topology.
119         :param service: Service unit name.
120         :type nodes: dict
121         :type service: str
122         """
123         for node in nodes.values():
124             if node[u"type"] == NodeType.DUT:
125                 DUTSetup.start_service(node, service)
126
127     @staticmethod
128     def stop_service(node, service):
129         """Stop the named service on node.
130
131         :param node: Node in the topology.
132         :param service: Service unit name.
133         :type node: dict
134         :type service: str
135         """
136         DUTSetup.get_service_logs(node, service)
137
138         command = f"supervisorctl stop {service}" \
139             if DUTSetup.running_in_container(node) \
140             else f"service {service} stop"
141         message = f"Node {node[u'host']} failed to stop service {service}"
142
143         exec_cmd_no_error(
144             node, command, timeout=180, sudo=True, message=message
145         )
146
147     @staticmethod
148     def stop_service_on_all_duts(nodes, service):
149         """Stop the named service on all DUTs.
150
151         :param nodes: Nodes in the topology.
152         :param service: Service unit name.
153         :type nodes: dict
154         :type service: str
155         """
156         for node in nodes.values():
157             if node[u"type"] == NodeType.DUT:
158                 DUTSetup.stop_service(node, service)
159
160     @staticmethod
161     def kill_program(node, program, namespace=None):
162         """Kill program on the specified topology node.
163
164         :param node: Topology node.
165         :param program: Program name.
166         :param namespace: Namespace program is running in.
167         :type node: dict
168         :type program: str
169         :type namespace: str
170         """
171         host = node[u"host"]
172         cmd_timeout = 5
173         if namespace in (None, u"default"):
174             shell_cmd = u"sh -c"
175         else:
176             shell_cmd = f"ip netns exec {namespace} sh -c"
177
178         pgrep_cmd = f"{shell_cmd} \'pgrep -c {program}\'"
179         _, stdout, _ = exec_cmd(node, pgrep_cmd, timeout=cmd_timeout,
180                                 sudo=True)
181         if int(stdout) == 0:
182             logger.trace(f"{program} is not running on {host}")
183             return
184         exec_cmd(node, f"{shell_cmd} \'pkill {program}\'",
185                  timeout=cmd_timeout, sudo=True)
186         for attempt in range(5):
187             _, stdout, _ = exec_cmd(node, pgrep_cmd, timeout=cmd_timeout,
188                                     sudo=True)
189             if int(stdout) == 0:
190                 logger.trace(f"Attempt {attempt}: {program} is dead on {host}")
191                 return
192             sleep(1)
193         logger.trace(f"SIGKILLing {program} on {host}")
194         exec_cmd(node, f"{shell_cmd} \'pkill -9 {program}\'",
195                  timeout=cmd_timeout, sudo=True)
196
197     @staticmethod
198     def verify_program_installed(node, program):
199         """Verify that program is installed on the specified topology node.
200
201         :param node: Topology node.
202         :param program: Program name.
203         :type node: dict
204         :type program: str
205         """
206         cmd = f"command -v {program}"
207         exec_cmd_no_error(node, cmd, message=f"{program} is not installed")
208
209     @staticmethod
210     def get_pid(node, process, retries=3):
211         """Get PID of running process.
212
213         :param node: DUT node.
214         :param process: process name.
215         :param retries: How many times to retry on failure.
216         :type node: dict
217         :type process: str
218         :type retries: int
219         :returns: PID
220         :rtype: int
221         :raises RuntimeError: If it is not possible to get the PID.
222         """
223         cmd = f"pidof {process}"
224         stdout, _ = exec_cmd_no_error(
225             node, cmd, retries=retries,
226             message=f"No {process} PID found on node {node[u'host']}")
227         pid_list = stdout.split()
228         return [int(pid) for pid in pid_list]
229
230     @staticmethod
231     def get_vpp_pids(nodes):
232         """Get PID of running VPP process on all DUTs.
233
234         :param nodes: DUT nodes.
235         :type nodes: dict
236         :returns: PIDs
237         :rtype: dict
238         """
239         pids = dict()
240         for node in nodes.values():
241             if node[u"type"] == NodeType.DUT:
242                 pids[node[u"host"]] = DUTSetup.get_pid(node, u"vpp")
243         return pids
244
245     @staticmethod
246     def crypto_device_verify(node, crypto_type, numvfs, force_init=False):
247         """Verify if Crypto QAT device virtual functions are initialized on all
248         DUTs. If parameter force initialization is set to True, then try to
249         initialize or remove VFs on QAT.
250
251         :param node: DUT node.
252         :crypto_type: Crypto device type - HW_DH895xcc, HW_C3xxx or HW_C4xxx.
253         :param numvfs: Number of VFs to initialize, 0 - disable the VFs.
254         :param force_init: If True then try to initialize to specific value.
255         :type node: dict
256         :type crypto_type: string
257         :type numvfs: int
258         :type force_init: bool
259         :returns: nothing
260         :raises RuntimeError: If QAT VFs are not created and force init is set
261                               to False.
262         """
263         pci_addr = Topology.get_cryptodev(node)
264         sriov_numvfs = DUTSetup.get_sriov_numvfs(node, pci_addr)
265
266         if sriov_numvfs != numvfs:
267             if force_init:
268                 # QAT is not initialized and we want to initialize with numvfs
269                 DUTSetup.crypto_device_init(node, crypto_type, numvfs)
270             else:
271                 raise RuntimeError(
272                     f"QAT device failed to create VFs on {node[u'host']}"
273                 )
274
275     @staticmethod
276     def crypto_device_init(node, crypto_type, numvfs):
277         """Init Crypto QAT device virtual functions on DUT.
278
279         :param node: DUT node.
280         :crypto_type: Crypto device type - HW_DH895xcc, HW_C3xxx or HW_C4xxx.
281         :param numvfs: Number of VFs to initialize, 0 - disable the VFs.
282         :type node: dict
283         :type crypto_type: string
284         :type numvfs: int
285         :returns: nothing
286         :raises RuntimeError: If failed to stop VPP or QAT failed to initialize.
287         """
288         if crypto_type == u"HW_DH895xcc":
289             kernel_mod = u"qat_dh895xcc"
290             kernel_drv = u"dh895xcc"
291         elif crypto_type == u"HW_C3xxx":
292             kernel_mod = u"qat_c3xxx"
293             kernel_drv = u"c3xxx"
294         elif crypto_type == u"HW_C4xxx":
295             kernel_mod = u"qat_c4xxx"
296             kernel_drv = u"c4xxx"
297         else:
298             raise RuntimeError(
299                 f"Unsupported crypto device type on {node[u'host']}"
300             )
301
302         pci_addr = Topology.get_cryptodev(node)
303
304         # QAT device must be re-bound to kernel driver before initialization.
305         DUTSetup.verify_kernel_module(node, kernel_mod, force_load=True)
306
307         # Stop VPP to prevent deadlock.
308         DUTSetup.stop_service(node, Constants.VPP_UNIT)
309
310         current_driver = DUTSetup.get_pci_dev_driver(
311             node, pci_addr.replace(u":", r"\:")
312         )
313         if current_driver is not None:
314             DUTSetup.pci_driver_unbind(node, pci_addr)
315
316         # Bind to kernel driver.
317         DUTSetup.pci_driver_bind(node, pci_addr, kernel_drv)
318
319         # Initialize QAT VFs.
320         if numvfs > 0:
321             DUTSetup.set_sriov_numvfs(node, pci_addr, numvfs)
322
323     @staticmethod
324     def get_virtfn_pci_addr(node, pf_pci_addr, vf_id):
325         """Get PCI address of Virtual Function.
326
327         :param node: DUT node.
328         :param pf_pci_addr: Physical Function PCI address.
329         :param vf_id: Virtual Function number.
330         :type node: dict
331         :type pf_pci_addr: str
332         :type vf_id: int
333         :returns: Virtual Function PCI address.
334         :rtype: str
335         :raises RuntimeError: If failed to get Virtual Function PCI address.
336         """
337         command = f"sh -c \"basename $(readlink " \
338             f"/sys/bus/pci/devices/{pf_pci_addr}/virtfn{vf_id})\""
339         message = u"Failed to get virtual function PCI address."
340
341         stdout, _ = exec_cmd_no_error(
342             node, command, timeout=30, sudo=True, message=message
343         )
344
345         return stdout.strip()
346
347     @staticmethod
348     def get_sriov_numvfs(node, pf_pci_addr):
349         """Get number of SR-IOV VFs.
350
351         :param node: DUT node.
352         :param pf_pci_addr: Physical Function PCI device address.
353         :type node: dict
354         :type pf_pci_addr: str
355         :returns: Number of VFs.
356         :rtype: int
357         :raises RuntimeError: If PCI device is not SR-IOV capable.
358         """
359         pci = pf_pci_addr.replace(u":", r"\:")
360         command = f"cat /sys/bus/pci/devices/{pci}/sriov_numvfs"
361         message = f"PCI device {pf_pci_addr} is not a SR-IOV device."
362
363         for _ in range(3):
364             stdout, _ = exec_cmd_no_error(
365                 node, command, timeout=30, sudo=True, message=message
366             )
367             try:
368                 sriov_numvfs = int(stdout)
369             except ValueError:
370                 logger.trace(
371                     f"Reading sriov_numvfs info failed on {node[u'host']}"
372                 )
373             else:
374                 return sriov_numvfs
375
376     @staticmethod
377     def set_sriov_numvfs(node, pf_pci_addr, numvfs=0):
378         """Init or reset SR-IOV virtual functions by setting its number on PCI
379         device on DUT. Setting to zero removes all VFs.
380
381         :param node: DUT node.
382         :param pf_pci_addr: Physical Function PCI device address.
383         :param numvfs: Number of VFs to initialize, 0 - removes the VFs.
384         :type node: dict
385         :type pf_pci_addr: str
386         :type numvfs: int
387         :raises RuntimeError: Failed to create VFs on PCI.
388         """
389         cmd = f"test -f /sys/bus/pci/devices/{pf_pci_addr}/sriov_numvfs"
390         sriov_unsupported, _, _ = exec_cmd(node, cmd)
391         # if sriov_numvfs doesn't exist, then sriov_unsupported != 0
392         if int(sriov_unsupported):
393             if numvfs == 0:
394                 # sriov is not supported and we want 0 VFs
395                 # no need to do anything
396                 return
397
398             raise RuntimeError(
399                 f"Can't configure {numvfs} VFs on {pf_pci_addr} device "
400                 f"on {node[u'host']} since it doesn't support SR-IOV."
401             )
402
403         pci = pf_pci_addr.replace(u":", r"\:")
404         command = f"sh -c \"echo {numvfs} | " \
405             f"tee /sys/bus/pci/devices/{pci}/sriov_numvfs\""
406         message = f"Failed to create {numvfs} VFs on {pf_pci_addr} device " \
407             f"on {node[u'host']}"
408
409         exec_cmd_no_error(
410             node, command, timeout=120, sudo=True, message=message
411         )
412
413     @staticmethod
414     def pci_driver_unbind(node, pci_addr):
415         """Unbind PCI device from current driver on node.
416
417         :param node: DUT node.
418         :param pci_addr: PCI device address.
419         :type node: dict
420         :type pci_addr: str
421         :raises RuntimeError: If PCI device unbind failed.
422         """
423         pci = pci_addr.replace(u":", r"\:")
424         command = f"sh -c \"echo {pci_addr} | " \
425             f"tee /sys/bus/pci/devices/{pci}/driver/unbind\""
426         message = f"Failed to unbind PCI device {pci_addr} on {node[u'host']}"
427
428         exec_cmd_no_error(
429             node, command, timeout=120, sudo=True, message=message
430         )
431
432     @staticmethod
433     def unbind_pci_devices_from_other_driver(node, driver, *pci_addrs):
434         """Unbind PCI devices from driver other than input driver on node.
435
436         :param node: DUT node.
437         :param driver: Driver to not unbind from. If None or empty string,
438             will attempt to unbind from the current driver.
439         :param pci_addrs: PCI device addresses.
440         :type node: dict
441         :type driver: str
442         :type pci_addrs: list
443         """
444         for pci_addr in pci_addrs:
445             cur_driver = DUTSetup.get_pci_dev_driver(node, pci_addr)
446             if not cur_driver:
447                 return
448             if not driver or cur_driver != driver:
449                 DUTSetup.pci_driver_unbind(node, pci_addr)
450
451     @staticmethod
452     def pci_driver_bind(node, pci_addr, driver):
453         """Bind PCI device to driver on node.
454
455         :param node: DUT node.
456         :param pci_addr: PCI device address.
457         :param driver: Driver to bind.
458         :type node: dict
459         :type pci_addr: str
460         :type driver: str
461         :raises RuntimeError: If PCI device bind failed.
462         """
463         message = f"Failed to bind PCI device {pci_addr} to {driver} " \
464             f"on host {node[u'host']}"
465         pci = pci_addr.replace(u":", r"\:")
466         command = f"sh -c \"echo {driver} | " \
467             f"tee /sys/bus/pci/devices/{pci}/driver_override\""
468
469         exec_cmd_no_error(
470             node, command, timeout=120, sudo=True, message=message
471         )
472
473         command = f"sh -c \"echo {pci_addr} | " \
474             f"tee /sys/bus/pci/drivers/{driver}/bind\""
475
476         exec_cmd_no_error(
477             node, command, timeout=120, sudo=True, message=message
478         )
479
480         command = f"sh -c \"echo  | " \
481             f"tee /sys/bus/pci/devices/{pci}/driver_override\""
482
483         exec_cmd_no_error(
484             node, command, timeout=120, sudo=True, message=message
485         )
486
487     @staticmethod
488     def pci_vf_driver_unbind(node, pf_pci_addr, vf_id):
489         """Unbind Virtual Function from driver on node.
490
491         :param node: DUT node.
492         :param pf_pci_addr: PCI device address.
493         :param vf_id: Virtual Function ID.
494         :type node: dict
495         :type pf_pci_addr: str
496         :type vf_id: int
497         :raises RuntimeError: If Virtual Function unbind failed.
498         """
499         vf_pci_addr = DUTSetup.get_virtfn_pci_addr(node, pf_pci_addr, vf_id)
500         pf_pci = pf_pci_addr.replace(u":", r"\:")
501         vf_path = f"/sys/bus/pci/devices/{pf_pci}/virtfn{vf_id}"
502
503         command = f"sh -c \"echo {vf_pci_addr} | tee {vf_path}/driver/unbind\""
504         message = f"Failed to unbind VF {vf_pci_addr} on {node[u'host']}"
505
506         exec_cmd_no_error(
507             node, command, timeout=120, sudo=True, message=message
508         )
509
510     @staticmethod
511     def pci_vf_driver_bind(node, pf_pci_addr, vf_id, driver):
512         """Bind Virtual Function to driver on node.
513
514         :param node: DUT node.
515         :param pf_pci_addr: PCI device address.
516         :param vf_id: Virtual Function ID.
517         :param driver: Driver to bind.
518         :type node: dict
519         :type pf_pci_addr: str
520         :type vf_id: int
521         :type driver: str
522         :raises RuntimeError: If PCI device bind failed.
523         """
524         vf_pci_addr = DUTSetup.get_virtfn_pci_addr(node, pf_pci_addr, vf_id)
525         pf_pci = pf_pci_addr.replace(u":", r'\:')
526         vf_path = f"/sys/bus/pci/devices/{pf_pci}/virtfn{vf_id}"
527
528         message = f"Failed to bind VF {vf_pci_addr} to {driver} " \
529             f"on {node[u'host']}"
530         command = f"sh -c \"echo {driver} | tee {vf_path}/driver_override\""
531
532         exec_cmd_no_error(
533             node, command, timeout=120, sudo=True, message=message
534         )
535
536         command = f"sh -c \"echo {vf_pci_addr} | " \
537             f"tee /sys/bus/pci/drivers/{driver}/bind\""
538
539         exec_cmd_no_error(
540             node, command, timeout=120, sudo=True, message=message
541         )
542
543         command = f"sh -c \"echo  | tee {vf_path}/driver_override\""
544
545         exec_cmd_no_error(
546             node, command, timeout=120, sudo=True, message=message
547         )
548
549     @staticmethod
550     def get_pci_dev_driver(node, pci_addr):
551         """Get current PCI device driver on node.
552
553         :param node: DUT node.
554         :param pci_addr: PCI device address.
555         :type node: dict
556         :type pci_addr: str
557         :returns: Driver or None
558         :raises RuntimeError: If it is not possible to get the interface driver
559             information from the node.
560         """
561         driver_path = f"/sys/bus/pci/devices/{pci_addr}/driver"
562         cmd = f"test -d {driver_path}"
563         ret_code, ret_val, _ = exec_cmd(node, cmd)
564         if int(ret_code):
565             # the directory doesn't exist which means the device is not bound
566             # to any driver
567             return None
568         cmd = f"basename $(readlink -f {driver_path})"
569         ret_val, _ = exec_cmd_no_error(node, cmd)
570         return ret_val.strip()
571
572     @staticmethod
573     def verify_kernel_module(node, module, force_load=False):
574         """Verify if kernel module is loaded on node. If parameter force
575         load is set to True, then try to load the modules.
576
577         :param node: Node.
578         :param module: Module to verify.
579         :param force_load: If True then try to load module.
580         :type node: dict
581         :type module: str
582         :type force_load: bool
583         :raises RuntimeError: If module is not loaded or failed to load.
584         """
585         command = f"grep -w {module} /proc/modules"
586         message = f"Kernel module {module} is not loaded " \
587             f"on host {node[u'host']}"
588
589         try:
590             exec_cmd_no_error(
591                 node, command, timeout=30, sudo=False, message=message
592             )
593         except RuntimeError:
594             if force_load:
595                 # Module is not loaded and we want to load it
596                 DUTSetup.load_kernel_module(node, module)
597             else:
598                 raise
599
600     @staticmethod
601     def verify_kernel_module_on_all_duts(nodes, module, force_load=False):
602         """Verify if kernel module is loaded on all DUTs. If parameter force
603         load is set to True, then try to load the modules.
604
605         :param nodes: DUT nodes.
606         :param module: Module to verify.
607         :param force_load: If True then try to load module.
608         :type nodes: dict
609         :type module: str
610         :type force_load: bool
611         """
612         for node in nodes.values():
613             if node[u"type"] == NodeType.DUT:
614                 DUTSetup.verify_kernel_module(node, module, force_load)
615
616     @staticmethod
617     def verify_uio_driver_on_all_duts(nodes):
618         """Verify if uio driver kernel module is loaded on all DUTs. If module
619         is not present it will try to load it.
620
621         :param nodes: DUT nodes.
622         :type nodes: dict
623         """
624         for node in nodes.values():
625             if node[u"type"] == NodeType.DUT:
626                 uio_driver = Topology.get_uio_driver(node)
627                 DUTSetup.verify_kernel_module(node, uio_driver, force_load=True)
628
629     @staticmethod
630     def load_kernel_module(node, module):
631         """Load kernel module on node.
632
633         :param node: DUT node.
634         :param module: Module to load.
635         :type node: dict
636         :type module: str
637         :returns: nothing
638         :raises RuntimeError: If loading failed.
639         """
640         command = f"modprobe {module}"
641         message = f"Failed to load {module} on host {node[u'host']}"
642
643         exec_cmd_no_error(node, command, timeout=30, sudo=True, message=message)
644
645     @staticmethod
646     def install_vpp_on_all_duts(nodes, vpp_pkg_dir):
647         """Install VPP on all DUT nodes. Start the VPP service in case of
648         systemd is not available or does not support autostart.
649
650         :param nodes: Nodes in the topology.
651         :param vpp_pkg_dir: Path to directory where VPP packages are stored.
652         :type nodes: dict
653         :type vpp_pkg_dir: str
654         :raises RuntimeError: If failed to remove or install VPP.
655         """
656         for node in nodes.values():
657             message = f"Failed to install VPP on host {node[u'host']}!"
658             if node[u"type"] == NodeType.DUT:
659                 command = u"ln -s /dev/null /etc/sysctl.d/80-vpp.conf || true"
660                 exec_cmd_no_error(node, command, sudo=True)
661
662                 command = u". /etc/lsb-release; echo \"${DISTRIB_ID}\""
663                 stdout, _ = exec_cmd_no_error(node, command)
664
665                 if stdout.strip() == u"Ubuntu":
666                     exec_cmd_no_error(
667                         node, u"apt-get purge -y '*vpp*' || true",
668                         timeout=120, sudo=True
669                     )
670                     # workaround to avoid installation of vpp-api-python
671                     exec_cmd_no_error(
672                         node, f"rm -f {vpp_pkg_dir}vpp-api-python.deb",
673                         timeout=120, sudo=True
674                     )
675                     exec_cmd_no_error(
676                         node, f"dpkg -i --force-all {vpp_pkg_dir}*.deb",
677                         timeout=120, sudo=True, message=message
678                     )
679                     exec_cmd_no_error(node, u"dpkg -l | grep vpp", sudo=True)
680                     if DUTSetup.running_in_container(node):
681                         DUTSetup.restart_service(node, Constants.VPP_UNIT)
682                 else:
683                     exec_cmd_no_error(
684                         node, u"yum -y remove '*vpp*' || true",
685                         timeout=120, sudo=True
686                     )
687                     # workaround to avoid installation of vpp-api-python
688                     exec_cmd_no_error(
689                         node, f"rm -f {vpp_pkg_dir}vpp-api-python.rpm",
690                         timeout=120, sudo=True
691                     )
692                     exec_cmd_no_error(
693                         node, f"rpm -ivh {vpp_pkg_dir}*.rpm",
694                         timeout=120, sudo=True, message=message
695                     )
696                     exec_cmd_no_error(node, u"rpm -qai '*vpp*'", sudo=True)
697                     DUTSetup.restart_service(node, Constants.VPP_UNIT)
698
699     @staticmethod
700     def running_in_container(node):
701         """This method tests if topology node is running inside container.
702
703         :param node: Topology node.
704         :type node: dict
705         :returns: True if running in docker container, false if not or failed
706             to detect.
707         :rtype: bool
708         """
709         command = u"fgrep docker /proc/1/cgroup"
710         message = u"Failed to get cgroup settings."
711         try:
712             exec_cmd_no_error(
713                 node, command, timeout=30, sudo=False, message=message
714             )
715         except RuntimeError:
716             return False
717         return True
718
719     @staticmethod
720     def get_docker_mergeddir(node, uuid):
721         """Get Docker overlay for MergedDir diff.
722
723         :param node: DUT node.
724         :param uuid: Docker UUID.
725         :type node: dict
726         :type uuid: str
727         :returns: Docker container MergedDir.
728         :rtype: str
729         :raises RuntimeError: If getting output failed.
730         """
731         command = f"docker inspect " \
732             f"--format='{{{{.GraphDriver.Data.MergedDir}}}}' {uuid}"
733         message = f"Failed to get directory of {uuid} on host {node[u'host']}"
734
735         stdout, _ = exec_cmd_no_error(node, command, sudo=True, message=message)
736         return stdout.strip()
737
738     @staticmethod
739     def get_hugepages_info(node, hugesize=None):
740         """Get number of huge pages in system.
741
742         :param node: Node in the topology.
743         :param hugesize: Size of hugepages. Default system huge size if None.
744         :type node: dict
745         :type hugesize: int
746         :returns: Number of huge pages in system.
747         :rtype: dict
748         :raises RuntimeError: If reading failed.
749         """
750         if not hugesize:
751             hugesize = "$(grep Hugepagesize /proc/meminfo | awk '{ print $2 }')"
752         command = f"cat /sys/kernel/mm/hugepages/hugepages-{hugesize}kB/*"
753         stdout, _ = exec_cmd_no_error(node, command)
754         try:
755             line = stdout.splitlines()
756             return {
757                 "free_hugepages": int(line[0]),
758                 "nr_hugepages": int(line[1]),
759                 "nr_hugepages_mempolicy": int(line[2]),
760                 "nr_overcommit_hugepages": int(line[3]),
761                 "resv_hugepages": int(line[4]),
762                 "surplus_hugepages": int(line[5])
763             }
764         except ValueError:
765             logger.trace(u"Reading huge pages information failed!")
766
767     @staticmethod
768     def check_huge_page(
769             node, huge_mnt, mem_size, hugesize=2048, allocate=False):
770         """Check if there is enough HugePages in system. If allocate is set to
771         true, try to allocate more HugePages.
772
773         :param node: Node in the topology.
774         :param huge_mnt: HugePage mount point.
775         :param mem_size: Reqeusted memory in MB.
776         :param hugesize: HugePage size in KB.
777         :param allocate: Whether to allocate more memory if not enough.
778         :type node: dict
779         :type huge_mnt: str
780         :type mem_size: int
781         :type hugesize: int
782         :type allocate: bool
783         :raises RuntimeError: Mounting hugetlbfs failed or not enough HugePages
784             or increasing map count failed.
785         """
786         # Get huge pages information.
787         hugepages = DUTSetup.get_hugepages_info(node, hugesize=hugesize)
788
789         # Check if hugepages requested are available on node.
790         if hugepages[u"nr_overcommit_hugepages"]:
791             # If overcommit is used, we need to know how many additional pages
792             # we can allocate
793             huge_available = hugepages[u"nr_overcommit_hugepages"] - \
794                 hugepages[u"surplus_hugepages"]
795         else:
796             # Fallbacking to free_hugepages which were used before to detect.
797             huge_available = hugepages[u"free_hugepages"]
798
799         if ((mem_size * 1024) // hugesize) > huge_available:
800             # If we want to allocate hugepage dynamically.
801             if allocate:
802                 huge_needed = ((mem_size * 1024) // hugesize) - huge_available
803                 huge_to_allocate = huge_needed + hugepages[u"nr_hugepages"]
804                 max_map_count = huge_to_allocate * 4
805                 # Check if huge pages mount point exist.
806                 try:
807                     exec_cmd_no_error(node, u"fgrep 'hugetlbfs' /proc/mounts")
808                 except RuntimeError:
809                     exec_cmd_no_error(node, f"mkdir -p {huge_mnt}", sudo=True)
810                     exec_cmd_no_error(
811                         node,
812                         f"mount -t hugetlbfs -o pagesize={hugesize}k none "
813                         f"{huge_mnt}",
814                         sudo=True)
815                 # Increase maximum number of memory map areas for process.
816                 exec_cmd_no_error(
817                     node,
818                     f"echo \"{max_map_count}\" | "
819                     f"sudo tee /proc/sys/vm/max_map_count",
820                     message=f"Increase map count failed on {node[u'host']}!"
821                 )
822                 # Increase hugepage count.
823                 exec_cmd_no_error(
824                     node,
825                     f"echo \"{huge_to_allocate}\" | "
826                     f"sudo tee /proc/sys/vm/nr_hugepages",
827                     message=f"Mount huge pages failed on {node[u'host']}!"
828                 )
829             # If we do not want to allocate dynamically end with error.
830             else:
831                 raise RuntimeError(
832                     f"Not enough availablehuge pages: {huge_available}!"
833                 )