67edefb930fc132355f21126472c176a52dc245f
[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             pid_list = stdout.split()
180             if len(pid_list) == 1:
181                 return int(stdout)
182             elif not pid_list:
183                 logger.debug("No VPP PID found on node {0}".
184                              format(node['host']))
185                 continue
186             else:
187                 logger.debug("More then one VPP PID found on node {0}".
188                              format(node['host']))
189                 return [int(pid) for pid in pid_list]
190
191         return None
192
193     @staticmethod
194     def get_vpp_pids(nodes):
195         """Get PID of running VPP process on all DUTs.
196
197         :param nodes: DUT nodes.
198         :type nodes: dict
199         :returns: PIDs
200         :rtype: dict
201         """
202         pids = dict()
203         for node in nodes.values():
204             if node['type'] == NodeType.DUT:
205                 pids[node['host']] = DUTSetup.get_vpp_pid(node)
206         return pids
207
208     @staticmethod
209     def crypto_device_verify(node, crypto_type, numvfs, force_init=False):
210         """Verify if Crypto QAT device virtual functions are initialized on all
211         DUTs. If parameter force initialization is set to True, then try to
212         initialize or remove VFs on QAT.
213
214         :param node: DUT node.
215         :crypto_type: Crypto device type - HW_DH895xcc or HW_C3xxx.
216         :param numvfs: Number of VFs to initialize, 0 - disable the VFs.
217         :param force_init: If True then try to initialize to specific value.
218         :type node: dict
219         :type crypto_type: string
220         :type numvfs: int
221         :type force_init: bool
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, crypto_type, 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, crypto_type, numvfs):
239         """Init Crypto QAT device virtual functions on DUT.
240
241         :param node: DUT node.
242         :crypto_type: Crypto device type - HW_DH895xcc or HW_C3xxx.
243         :param numvfs: Number of VFs to initialize, 0 - disable the VFs.
244         :type node: dict
245         :type crypto_type: string
246         :type numvfs: int
247         :returns: nothing
248         :raises RuntimeError: If failed to stop VPP or QAT failed to initialize.
249         """
250         if crypto_type == "HW_DH895xcc":
251             kernel_mod = "qat_dh895xcc"
252             kernel_drv = "dh895xcc"
253         elif crypto_type == "HW_C3xxx":
254             kernel_mod = "qat_c3xxx"
255             kernel_drv = "c3xxx"
256         else:
257             raise RuntimeError('Unsupported crypto device type on {host}'.
258                                format(host=node['host']))
259
260         pci_addr = Topology.get_cryptodev(node)
261
262         # QAT device must be re-bound to kernel driver before initialization.
263         DUTSetup.verify_kernel_module(node, kernel_mod, force_load=True)
264
265         # Stop VPP to prevent deadlock.
266         DUTSetup.stop_service(node, Constants.VPP_UNIT)
267
268         current_driver = DUTSetup.get_pci_dev_driver(
269             node, pci_addr.replace(':', r'\:'))
270         if current_driver is not None:
271             DUTSetup.pci_driver_unbind(node, pci_addr)
272
273         # Bind to kernel driver.
274         DUTSetup.pci_driver_bind(node, pci_addr, kernel_drv)
275
276         # Initialize QAT VFs.
277         if numvfs > 0:
278             DUTSetup.set_sriov_numvfs(node, pci_addr, numvfs)
279
280     @staticmethod
281     def get_virtfn_pci_addr(node, pf_pci_addr, vf_id):
282         """Get PCI address of Virtual Function.
283
284         :param node: DUT node.
285         :param pf_pci_addr: Physical Function PCI address.
286         :param vf_id: Virtual Function number.
287         :type node: dict
288         :type pf_pci_addr: str
289         :type vf_id: int
290         :returns: Virtual Function PCI address.
291         :rtype: int
292         :raises RuntimeError: If failed to get Virtual Function PCI address.
293         """
294         command = "sh -c "\
295             "'basename $(readlink /sys/bus/pci/devices/{pci}/virtfn{vf_id})'".\
296             format(pci=pf_pci_addr, vf_id=vf_id)
297         message = 'Failed to get virtual function PCI address.'
298
299         stdout, _ = exec_cmd_no_error(node, command, timeout=30, sudo=True,
300                                       message=message)
301
302         return stdout.strip()
303
304     @staticmethod
305     def get_sriov_numvfs(node, pf_pci_addr):
306         """Get number of SR-IOV VFs.
307
308         :param node: DUT node.
309         :param pf_pci_addr: Physical Function PCI device address.
310         :type node: dict
311         :type pf_pci_addr: str
312         :returns: Number of VFs.
313         :rtype: int
314         :raises RuntimeError: If PCI device is not SR-IOV capable.
315         """
316         command = 'cat /sys/bus/pci/devices/{pci}/sriov_numvfs'.\
317             format(pci=pf_pci_addr.replace(':', r'\:'))
318         message = 'PCI device {pci} is not a SR-IOV device.'.\
319             format(pci=pf_pci_addr)
320
321         for _ in range(3):
322             stdout, _ = exec_cmd_no_error(node, command, timeout=30, sudo=True,
323                                           message=message)
324             try:
325                 sriov_numvfs = int(stdout)
326             except ValueError:
327                 logger.trace('Reading sriov_numvfs info failed on {host}'.
328                              format(host=node['host']))
329             else:
330                 return sriov_numvfs
331
332     @staticmethod
333     def set_sriov_numvfs(node, pf_pci_addr, numvfs=0):
334         """Init or reset SR-IOV virtual functions by setting its number on PCI
335         device on DUT. Setting to zero removes all VFs.
336
337         :param node: DUT node.
338         :param pf_pci_addr: Physical Function PCI device address.
339         :param numvfs: Number of VFs to initialize, 0 - removes the VFs.
340         :type node: dict
341         :type pf_pci_addr: str
342         :type numvfs: int
343         :raises RuntimeError: Failed to create VFs on PCI.
344         """
345         command = "sh -c "\
346             "'echo {num} | tee /sys/bus/pci/devices/{pci}/sriov_numvfs'".\
347             format(num=numvfs, pci=pf_pci_addr.replace(':', r'\:'))
348         message = 'Failed to create {num} VFs on {pci} device on {host}'.\
349             format(num=numvfs, pci=pf_pci_addr, host=node['host'])
350
351         exec_cmd_no_error(node, command, timeout=120, sudo=True,
352                           message=message)
353
354     @staticmethod
355     def pci_driver_unbind(node, pci_addr):
356         """Unbind PCI device from current driver on node.
357
358         :param node: DUT node.
359         :param pci_addr: PCI device address.
360         :type node: dict
361         :type pci_addr: str
362         :raises RuntimeError: If PCI device unbind failed.
363         """
364         command = "sh -c "\
365             "'echo {pci} | tee /sys/bus/pci/devices/{pcie}/driver/unbind'".\
366             format(pci=pci_addr, pcie=pci_addr.replace(':', r'\:'))
367         message = 'Failed to unbind PCI device {pci} on {host}'.\
368             format(pci=pci_addr, host=node['host'])
369
370         exec_cmd_no_error(node, command, timeout=120, sudo=True,
371                           message=message)
372
373     @staticmethod
374     def pci_driver_bind(node, pci_addr, driver):
375         """Bind PCI device to driver on node.
376
377         :param node: DUT node.
378         :param pci_addr: PCI device address.
379         :param driver: Driver to bind.
380         :type node: dict
381         :type pci_addr: str
382         :type driver: str
383         :raises RuntimeError: If PCI device bind failed.
384         """
385         message = 'Failed to bind PCI device {pci} to {driver} on host {host}'.\
386             format(pci=pci_addr, driver=driver, host=node['host'])
387
388         command = "sh -c "\
389             "'echo {driver} | tee /sys/bus/pci/devices/{pci}/driver_override'".\
390             format(driver=driver, pci=pci_addr.replace(':', r'\:'))
391
392         exec_cmd_no_error(node, command, timeout=120, sudo=True,
393                           message=message)
394
395         command = "sh -c "\
396             "'echo {pci} | tee /sys/bus/pci/drivers/{driver}/bind'".\
397             format(pci=pci_addr, driver=driver)
398
399         exec_cmd_no_error(node, command, timeout=120, sudo=True,
400                           message=message)
401
402         command = "sh -c "\
403             "'echo  | tee /sys/bus/pci/devices/{pci}/driver_override'".\
404             format(pci=pci_addr.replace(':', r'\:'))
405
406         exec_cmd_no_error(node, command, timeout=120, sudo=True,
407                           message=message)
408
409     @staticmethod
410     def pci_vf_driver_unbind(node, pf_pci_addr, vf_id):
411         """Unbind Virtual Function from driver on node.
412
413         :param node: DUT node.
414         :param pf_pci_addr: PCI device address.
415         :param vf_id: Virtual Function ID.
416         :type node: dict
417         :type pf_pci_addr: str
418         :type vf_id: int
419         :raises RuntimeError: If Virtual Function unbind failed.
420         """
421         vf_pci_addr = DUTSetup.get_virtfn_pci_addr(node, pf_pci_addr, vf_id)
422         vf_path = "/sys/bus/pci/devices/{pf_pci_addr}/virtfn{vf_id}".\
423             format(pf_pci_addr=pf_pci_addr.replace(':', r'\:'), vf_id=vf_id)
424
425         command = "sh -c "\
426             "'echo {vf_pci_addr} | tee {vf_path}/driver/unbind'".\
427             format(vf_pci_addr=vf_pci_addr, vf_path=vf_path)
428
429         message = 'Failed to unbind VF {vf_pci_addr} to on {host}'.\
430             format(vf_pci_addr=vf_pci_addr, host=node['host'])
431
432         exec_cmd_no_error(node, command, timeout=120, sudo=True,
433                           message=message)
434
435     @staticmethod
436     def pci_vf_driver_bind(node, pf_pci_addr, vf_id, driver):
437         """Bind Virtual Function to driver on node.
438
439         :param node: DUT node.
440         :param pf_pci_addr: PCI device address.
441         :param vf_id: Virtual Function ID.
442         :param driver: Driver to bind.
443         :type node: dict
444         :type pf_pci_addr: str
445         :type vf_id: int
446         :type driver: str
447         :raises RuntimeError: If PCI device bind failed.
448         """
449         vf_pci_addr = DUTSetup.get_virtfn_pci_addr(node, pf_pci_addr, vf_id)
450         vf_path = "/sys/bus/pci/devices/{pf_pci_addr}/virtfn{vf_id}".\
451             format(pf_pci_addr=pf_pci_addr.replace(':', r'\:'), vf_id=vf_id)
452
453         message = 'Failed to bind VF {vf_pci_addr} to {driver} on {host}'.\
454             format(vf_pci_addr=vf_pci_addr, driver=driver, host=node['host'])
455
456         command = "sh -c "\
457             "'echo {driver} | tee {vf_path}/driver_override'".\
458             format(driver=driver, vf_path=vf_path)
459
460         exec_cmd_no_error(node, command, timeout=120, sudo=True,
461                           message=message)
462
463         command = "sh -c "\
464             "'echo {vf_pci_addr} | tee /sys/bus/pci/drivers/{driver}/bind'".\
465             format(vf_pci_addr=vf_pci_addr, driver=driver)
466
467         exec_cmd_no_error(node, command, timeout=120, sudo=True,
468                           message=message)
469
470         command = "sh -c "\
471             "'echo  | tee {vf_path}/driver_override'".\
472             format(vf_path=vf_path)
473
474         exec_cmd_no_error(node, command, timeout=120, sudo=True,
475                           message=message)
476
477     @staticmethod
478     def get_pci_dev_driver(node, pci_addr):
479         """Get current PCI device driver on node.
480
481         .. note::
482             # lspci -vmmks 0000:00:05.0
483             Slot:   00:05.0
484             Class:  Ethernet controller
485             Vendor: Red Hat, Inc
486             Device: Virtio network device
487             SVendor:        Red Hat, Inc
488             SDevice:        Device 0001
489             PhySlot:        5
490             Driver: virtio-pci
491
492         :param node: DUT node.
493         :param pci_addr: PCI device address.
494         :type node: dict
495         :type pci_addr: str
496         :returns: Driver or None
497         :raises RuntimeError: If PCI rescan or lspci command execution failed.
498         :raises RuntimeError: If it is not possible to get the interface driver
499             information from the node.
500         """
501         ssh = SSH()
502         ssh.connect(node)
503
504         for i in range(3):
505             logger.trace('Try number {0}: Get PCI device driver'.format(i))
506
507             cmd = 'lspci -vmmks {0}'.format(pci_addr)
508             ret_code, stdout, _ = ssh.exec_command(cmd)
509             if int(ret_code):
510                 raise RuntimeError("'{0}' failed on '{1}'"
511                                    .format(cmd, node['host']))
512
513             for line in stdout.splitlines():
514                 if not line:
515                     continue
516                 name = None
517                 value = None
518                 try:
519                     name, value = line.split("\t", 1)
520                 except ValueError:
521                     if name == "Driver:":
522                         return None
523                 if name == 'Driver:':
524                     return value
525
526             if i < 2:
527                 logger.trace('Driver for PCI device {} not found, executing '
528                              'pci rescan and retrying'.format(pci_addr))
529                 cmd = 'sh -c "echo 1 > /sys/bus/pci/rescan"'
530                 ret_code, _, _ = ssh.exec_command_sudo(cmd)
531                 if int(ret_code) != 0:
532                     raise RuntimeError("'{0}' failed on '{1}'"
533                                        .format(cmd, node['host']))
534
535         return None
536
537     @staticmethod
538     def verify_kernel_module(node, module, force_load=False):
539         """Verify if kernel module is loaded on node. If parameter force
540         load is set to True, then try to load the modules.
541
542         :param node: Node.
543         :param module: Module to verify.
544         :param force_load: If True then try to load module.
545         :type node: dict
546         :type module: str
547         :type force_load: bool
548         :raises RuntimeError: If module is not loaded or failed to load.
549         """
550         command = 'grep -w {module} /proc/modules'.format(module=module)
551         message = 'Kernel module {module} is not loaded on host {host}'.\
552             format(module=module, host=node['host'])
553
554         try:
555             exec_cmd_no_error(node, command, timeout=30, sudo=False,
556                               message=message)
557         except RuntimeError:
558             if force_load:
559                 # Module is not loaded and we want to load it
560                 DUTSetup.load_kernel_module(node, module)
561             else:
562                 raise
563
564     @staticmethod
565     def verify_kernel_module_on_all_duts(nodes, module, force_load=False):
566         """Verify if kernel module is loaded on all DUTs. If parameter force
567         load is set to True, then try to load the modules.
568
569         :param node: DUT nodes.
570         :param module: Module to verify.
571         :param force_load: If True then try to load module.
572         :type node: dict
573         :type module: str
574         :type force_load: bool
575         """
576         for node in nodes.values():
577             if node['type'] == NodeType.DUT:
578                 DUTSetup.verify_kernel_module(node, module, force_load)
579
580     @staticmethod
581     def verify_uio_driver_on_all_duts(nodes):
582         """Verify if uio driver kernel module is loaded on all DUTs. If module
583         is not present it will try to load it.
584
585         :param node: DUT nodes.
586         :type node: dict
587         """
588         for node in nodes.values():
589             if node['type'] == NodeType.DUT:
590                 uio_driver = Topology.get_uio_driver(node)
591                 DUTSetup.verify_kernel_module(node, uio_driver, force_load=True)
592
593     @staticmethod
594     def load_kernel_module(node, module):
595         """Load kernel module on node.
596
597         :param node: DUT node.
598         :param module: Module to load.
599         :type node: dict
600         :type module: str
601         :returns: nothing
602         :raises RuntimeError: If loading failed.
603         """
604         command = 'modprobe {module}'.format(module=module)
605         message = 'Failed to load {module} on host {host}'.\
606             format(module=module, host=node['host'])
607
608         exec_cmd_no_error(node, command, timeout=30, sudo=True, message=message)
609
610     @staticmethod
611     def install_vpp_on_all_duts(nodes, vpp_pkg_dir):
612         """Install VPP on all DUT nodes.
613
614         :param nodes: Nodes in the topology.
615         :param vpp_pkg_dir: Path to directory where VPP packages are stored.
616         :type nodes: dict
617         :type vpp_pkg_dir: str
618         :raises RuntimeError: If failed to remove or install VPP.
619         """
620         for node in nodes.values():
621             message = 'Failed to install VPP on host {host}!'.\
622                 format(host=node['host'])
623             if node['type'] == NodeType.DUT:
624                 command = 'ln -s /dev/null /etc/sysctl.d/80-vpp.conf || true'
625                 exec_cmd_no_error(node, command, sudo=True)
626
627                 command = '. /etc/lsb-release; echo "${DISTRIB_ID}"'
628                 stdout, _ = exec_cmd_no_error(node, command)
629
630                 if stdout.strip() == 'Ubuntu':
631                     exec_cmd_no_error(node, 'apt-get purge -y "*vpp*" || true',
632                                       timeout=120, sudo=True)
633                     exec_cmd_no_error(node, 'dpkg -i --force-all {dir}*.deb'.
634                                       format(dir=vpp_pkg_dir), timeout=120,
635                                       sudo=True, message=message)
636                     exec_cmd_no_error(node, 'dpkg -l | grep vpp', sudo=True)
637                 else:
638                     exec_cmd_no_error(node, 'yum -y remove "*vpp*" || true',
639                                       timeout=120, sudo=True)
640                     exec_cmd_no_error(node, 'rpm -ivh {dir}*.rpm'.
641                                       format(dir=vpp_pkg_dir), timeout=120,
642                                       sudo=True, message=message)
643                     exec_cmd_no_error(node, 'rpm -qai *vpp*', sudo=True)
644
645     @staticmethod
646     def running_in_container(node):
647         """This method tests if topology node is running inside container.
648
649         :param node: Topology node.
650         :type node: dict
651         :returns: True if running in docker container, false if not or failed
652         to detect.
653         :rtype: bool
654         """
655         command = "fgrep docker /proc/1/cgroup"
656         message = 'Failed to get cgroup settings.'
657         try:
658             exec_cmd_no_error(node, command, timeout=30, sudo=False,
659                               message=message)
660         except RuntimeError:
661             return False
662         return True
663
664     @staticmethod
665     def get_docker_mergeddir(node, uuid):
666         """Get Docker overlay for MergedDir diff.
667
668         :param node: DUT node.
669         :param uuid: Docker UUID.
670         :type node: dict
671         :type uuid: str
672         :returns: Docker container MergedDir.
673         :rtype: str
674         :raises RuntimeError: If getting output failed.
675         """
676         command = "docker inspect --format='"\
677             "{{{{.GraphDriver.Data.MergedDir}}}}' {uuid}".format(uuid=uuid)
678         message = 'Failed to get directory of {uuid} on host {host}'.\
679             format(uuid=uuid, host=node['host'])
680
681         stdout, _ = exec_cmd_no_error(node, command, sudo=True, message=message)
682         return stdout.strip()
683
684     @staticmethod
685     def get_huge_page_size(node):
686         """Get default size of huge pages in system.
687
688         :param node: Node in the topology.
689         :type node: dict
690         :returns: Default size of free huge pages in system.
691         :rtype: int
692         :raises RuntimeError: If reading failed for three times.
693         """
694         ssh = SSH()
695         ssh.connect(node)
696
697         for _ in range(3):
698             ret_code, stdout, _ = ssh.exec_command_sudo(
699                 "grep Hugepagesize /proc/meminfo | awk '{ print $2 }'")
700             if ret_code == 0:
701                 try:
702                     huge_size = int(stdout)
703                 except ValueError:
704                     logger.trace('Reading huge page size information failed')
705                 else:
706                     break
707         else:
708             raise RuntimeError('Getting huge page size information failed.')
709         return huge_size
710
711     @staticmethod
712     def get_huge_page_free(node, huge_size):
713         """Get number of free huge pages in system.
714
715         :param node: Node in the topology.
716         :param huge_size: Size of hugepages.
717         :type node: dict
718         :type huge_size: int
719         :returns: Number of free huge pages in system.
720         :rtype: int
721         :raises RuntimeError: If reading failed for three times.
722         """
723         # TODO: add numa aware option
724         ssh = SSH()
725         ssh.connect(node)
726
727         for _ in range(3):
728             ret_code, stdout, _ = ssh.exec_command_sudo(
729                 'cat /sys/kernel/mm/hugepages/hugepages-{0}kB/free_hugepages'.
730                 format(huge_size))
731             if ret_code == 0:
732                 try:
733                     huge_free = int(stdout)
734                 except ValueError:
735                     logger.trace('Reading free huge pages information failed')
736                 else:
737                     break
738         else:
739             raise RuntimeError('Getting free huge pages information failed.')
740         return huge_free
741
742     @staticmethod
743     def get_huge_page_total(node, huge_size):
744         """Get total number of huge pages in system.
745
746         :param node: Node in the topology.
747         :param huge_size: Size of hugepages.
748         :type node: dict
749         :type huge_size: int
750
751         :returns: Total number of huge pages in system.
752         :rtype: int
753         :raises RuntimeError: If reading failed for three times.
754         """
755         # TODO: add numa aware option
756         ssh = SSH()
757         ssh.connect(node)
758
759         for _ in range(3):
760             ret_code, stdout, _ = ssh.exec_command_sudo(
761                 'cat /sys/kernel/mm/hugepages/hugepages-{0}kB/nr_hugepages'.
762                 format(huge_size))
763             if ret_code == 0:
764                 try:
765                     huge_total = int(stdout)
766                 except ValueError:
767                     logger.trace('Reading total huge pages information failed')
768                 else:
769                     break
770         else:
771             raise RuntimeError('Getting total huge pages information failed.')
772         return huge_total
773
774     @staticmethod
775     def check_huge_page(node, huge_mnt, mem_size, allocate=False):
776         """Check if there is enough HugePages in system. If allocate is set to
777         true, try to allocate more HugePages.
778
779         :param node: Node in the topology.
780         :param huge_mnt: HugePage mount point.
781         :param mem_size: Requested memory in MB.
782         :param allocate: Whether to allocate more memory if not enough.
783         :type node: dict
784         :type huge_mnt: str
785         :type mem_size: str
786         :type allocate: bool
787
788         :raises RuntimeError: Mounting hugetlbfs failed or not enough HugePages
789         or increasing map count failed.
790         """
791         # TODO: split function into smaller parts.
792         ssh = SSH()
793         ssh.connect(node)
794
795         # Get huge pages information
796         huge_size = DUTSetup.get_huge_page_size(node)
797         huge_free = DUTSetup.get_huge_page_free(node, huge_size)
798         huge_total = DUTSetup.get_huge_page_total(node, huge_size)
799
800         # Check if memory reqested is available on host
801         if (mem_size * 1024) > (huge_free * huge_size):
802             # If we want to allocate hugepage dynamically
803             if allocate:
804                 mem_needed = (mem_size * 1024) - (huge_free * huge_size)
805                 huge_to_allocate = ((mem_needed / huge_size) * 2) + huge_total
806                 max_map_count = huge_to_allocate*4
807                 # Increase maximum number of memory map areas a process may have
808                 ret_code, _, _ = ssh.exec_command_sudo(
809                     'echo "{0}" | sudo tee /proc/sys/vm/max_map_count'.
810                     format(max_map_count))
811                 if int(ret_code) != 0:
812                     raise RuntimeError('Increase map count failed on {host}'.
813                                        format(host=node['host']))
814                 # Increase hugepage count
815                 ret_code, _, _ = ssh.exec_command_sudo(
816                     'echo "{0}" | sudo tee /proc/sys/vm/nr_hugepages'.
817                     format(huge_to_allocate))
818                 if int(ret_code) != 0:
819                     raise RuntimeError('Mount huge pages failed on {host}'.
820                                        format(host=node['host']))
821             # If we do not want to allocate dynamicaly end with error
822             else:
823                 raise RuntimeError('Not enough free huge pages: {0}, {1} MB'.
824                                    format(huge_free, huge_free * huge_size))
825         # Check if huge pages mount point exist
826         has_huge_mnt = False
827         ret_code, stdout, _ = ssh.exec_command('cat /proc/mounts')
828         if int(ret_code) == 0:
829             for line in stdout.splitlines():
830                 # Try to find something like:
831                 # none /mnt/huge hugetlbfs rw,relatime,pagesize=2048k 0 0
832                 mount = line.split()
833                 if mount[2] == 'hugetlbfs' and mount[1] == huge_mnt:
834                     has_huge_mnt = True
835                     break
836         # If huge page mount point not exist create one
837         if not has_huge_mnt:
838             ret_code, _, _ = ssh.exec_command_sudo(
839                 'mkdir -p {mnt}'.format(mnt=huge_mnt))
840             if int(ret_code) != 0:
841                 raise RuntimeError('Create mount dir failed on {host}'.
842                                    format(host=node['host']))
843             ret_code, _, _ = ssh.exec_command_sudo(
844                 'mount -t hugetlbfs -o pagesize=2048k none {mnt}'.
845                 format(mnt=huge_mnt))
846             if int(ret_code) != 0:
847                 raise RuntimeError('Mount huge pages failed on {host}'.
848                                    format(host=node['host']))

©2016 FD.io a Linux Foundation Collaborative Project. All Rights Reserved.
Linux Foundation is a registered trademark of The Linux Foundation. Linux is a registered trademark of Linus Torvalds.
Please see our privacy policy and terms of use.