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