Compatibility fixes with Ubuntu 18.04
[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.topology import NodeType, Topology
19 from resources.libraries.python.ssh import SSH
20 from resources.libraries.python.constants import Constants
21 from resources.libraries.python.VatExecutor import VatExecutor
22 from resources.libraries.python.VPPUtil import VPPUtil
23
24
25 class DUTSetup(object):
26     """Contains methods for setting up DUTs."""
27
28     @staticmethod
29     def get_service_logs(node, service):
30         """Get specific service unit logs by journalctl from node.
31
32         :param node: Node in the topology.
33         :param service: Service unit name.
34         :type node: dict
35         :type service: str
36         """
37         ssh = SSH()
38         ssh.connect(node)
39         ret_code, _, _ = \
40             ssh.exec_command_sudo('journalctl --no-pager --unit={name} '
41                                   '--since="$(echo `systemctl show -p '
42                                   'ActiveEnterTimestamp {name}` | '
43                                   'awk \'{{print $2 $3}}\')"'.
44                                   format(name=service))
45         if int(ret_code) != 0:
46             raise RuntimeError('DUT {host} failed to get logs from unit {name}'.
47                                format(host=node['host'], name=service))
48
49     @staticmethod
50     def get_service_logs_on_all_duts(nodes, service):
51         """Get specific service unit logs by journalctl from all DUTs.
52
53         :param nodes: Nodes in the topology.
54         :param service: Service unit name.
55         :type nodes: dict
56         :type service: str
57         """
58         for node in nodes.values():
59             if node['type'] == NodeType.DUT:
60                 DUTSetup.get_service_logs(node, service)
61
62     @staticmethod
63     def start_service(node, service):
64         """Start up the named service on node.
65
66         :param node: Node in the topology.
67         :param service: Service unit name.
68         :type node: dict
69         :type service: str
70         """
71         ssh = SSH()
72         ssh.connect(node)
73         # We are doing restart. With this we do not care if service
74         # was running or not.
75         ret_code, _, _ = \
76             ssh.exec_command_sudo('service {name} restart'.
77                                   format(name=service), timeout=120)
78         if int(ret_code) != 0:
79             raise RuntimeError('DUT {host} failed to start service {name}'.
80                                format(host=node['host'], name=service))
81
82         DUTSetup.get_service_logs(node, service)
83
84     @staticmethod
85     def start_vpp_service_on_all_duts(nodes):
86         """Start up the VPP service on all nodes.
87
88         :param nodes: Nodes in the topology.
89         :type nodes: dict
90         """
91         for node in nodes.values():
92             if node['type'] == NodeType.DUT:
93                 DUTSetup.start_service(node, Constants.VPP_UNIT)
94
95     @staticmethod
96     def vpp_show_version_verbose(node):
97         """Run "show version verbose" CLI command.
98
99         :param node: Node to run command on.
100         :type node: dict
101         """
102         vat = VatExecutor()
103         vat.execute_script("show_version_verbose.vat", node, json_out=False)
104
105         try:
106             vat.script_should_have_passed()
107         except AssertionError:
108             raise RuntimeError('Failed to get VPP version on host: {name}'.
109                                format(name=node['host']))
110
111     @staticmethod
112     def show_vpp_version_on_all_duts(nodes):
113         """Show VPP version verbose on all DUTs.
114
115         :param nodes: VPP nodes
116         :type nodes: dict
117         """
118         for node in nodes.values():
119             if node['type'] == NodeType.DUT:
120                 DUTSetup.vpp_show_version_verbose(node)
121
122     @staticmethod
123     def vpp_show_interfaces(node):
124         """Run "show interface" CLI command.
125
126         :param node: Node to run command on.
127         :type node: dict
128         """
129         vat = VatExecutor()
130         vat.execute_script("show_interface.vat", node, json_out=False)
131
132         try:
133             vat.script_should_have_passed()
134         except AssertionError:
135             raise RuntimeError('Failed to get VPP interfaces on host: {name}'.
136                                format(name=node['host']))
137
138     @staticmethod
139     def vpp_api_trace_save(node):
140         """Run "api trace save" CLI command.
141
142         :param node: Node to run command on.
143         :type node: dict
144         """
145         vat = VatExecutor()
146         vat.execute_script("api_trace_save.vat", node, json_out=False)
147
148     @staticmethod
149     def vpp_api_trace_dump(node):
150         """Run "api trace custom-dump" CLI command.
151
152         :param node: Node to run command on.
153         :type node: dict
154         """
155         vat = VatExecutor()
156         vat.execute_script("api_trace_dump.vat", node, json_out=False)
157
158     @staticmethod
159     def setup_all_duts(nodes):
160         """Prepare all DUTs in given topology for test execution."""
161         for node in nodes.values():
162             if node['type'] == NodeType.DUT:
163                 DUTSetup.setup_dut(node)
164
165     @staticmethod
166     def setup_dut(node):
167         """Run script over SSH to setup the DUT node.
168
169         :param node: DUT node to set up.
170         :type node: dict
171
172         :raises Exception: If the DUT setup fails.
173         """
174         ssh = SSH()
175         ssh.connect(node)
176
177         ret_code, _, _ = \
178             ssh.exec_command('sudo -Sn bash {0}/{1}/dut_setup.sh'.
179                              format(Constants.REMOTE_FW_DIR,
180                                     Constants.RESOURCES_LIB_SH), timeout=120)
181         if int(ret_code) != 0:
182             raise RuntimeError('DUT test setup script failed at node {name}'.
183                                format(name=node['host']))
184
185     @staticmethod
186     def get_vpp_pid(node):
187         """Get PID of running VPP process.
188
189         :param node: DUT node.
190         :type node: dict
191         :returns: PID
192         :rtype: int
193         :raises RuntimeError: If it is not possible to get the PID.
194         """
195
196         ssh = SSH()
197         ssh.connect(node)
198
199         for i in range(3):
200             logger.trace('Try {}: Get VPP PID'.format(i))
201             ret_code, stdout, stderr = ssh.exec_command('pidof vpp')
202
203             if int(ret_code) != 0:
204                 raise RuntimeError('Not possible to get PID of VPP process '
205                                    'on node: {0}\n {1}'.
206                                    format(node['host'], stdout + stderr))
207
208             if len(stdout.splitlines()) == 1:
209                 return int(stdout)
210             elif len(stdout.splitlines()) == 0:
211                 logger.debug("No VPP PID found on node {0}".
212                              format(node['host']))
213                 continue
214             else:
215                 logger.debug("More then one VPP PID found on node {0}".
216                              format(node['host']))
217                 ret_list = list()
218                 for line in stdout.splitlines():
219                     ret_list.append(int(line))
220                 return ret_list
221
222         return None
223
224     @staticmethod
225     def get_vpp_pids(nodes):
226         """Get PID of running VPP process on all DUTs.
227
228         :param nodes: DUT nodes.
229         :type nodes: dict
230         :returns: PIDs
231         :rtype: dict
232         """
233
234         pids = dict()
235         for node in nodes.values():
236             if node['type'] == NodeType.DUT:
237                 pids[node['host']] = DUTSetup.get_vpp_pid(node)
238         return pids
239
240     @staticmethod
241     def vpp_show_crypto_device_mapping(node):
242         """Run "show crypto device mapping" CLI command.
243
244         :param node: Node to run command on.
245         :type node: dict
246         """
247         vat = VatExecutor()
248         vat.execute_script("show_crypto_device_mapping.vat", node,
249                            json_out=False)
250
251     @staticmethod
252     def crypto_device_verify(node, force_init=False, numvfs=32):
253         """Verify if Crypto QAT device virtual functions are initialized on all
254         DUTs. If parameter force initialization is set to True, then try to
255         initialize or disable QAT.
256
257         :param node: DUT node.
258         :param force_init: If True then try to initialize to specific value.
259         :param numvfs: Number of VFs to initialize, 0 - disable the VFs.
260         :type node: dict
261         :type force_init: bool
262         :type numvfs: int
263         :returns: nothing
264         :raises RuntimeError: If QAT is not initialized or failed to initialize.
265         """
266
267         ssh = SSH()
268         ssh.connect(node)
269
270         cryptodev = Topology.get_cryptodev(node)
271         cmd = 'cat /sys/bus/pci/devices/{0}/sriov_numvfs'.\
272             format(cryptodev.replace(':', r'\:'))
273
274         # Try to read number of VFs from PCI address of QAT device
275         for _ in range(3):
276             ret_code, stdout, _ = ssh.exec_command(cmd)
277             if int(ret_code) == 0:
278                 try:
279                     sriov_numvfs = int(stdout)
280                 except ValueError:
281                     logger.trace('Reading sriov_numvfs info failed on {0}'.
282                                  format(node['host']))
283                 else:
284                     if sriov_numvfs != numvfs:
285                         if force_init:
286                             # QAT is not initialized and we want to initialize
287                             # with numvfs
288                             DUTSetup.crypto_device_init(node, numvfs)
289                         else:
290                             raise RuntimeError('QAT device {0} is not '
291                                                'initialized to {1} on host {2}'
292                                                .format(cryptodev, numvfs,
293                                                        node['host']))
294                     break
295
296     @staticmethod
297     def crypto_device_init(node, numvfs):
298         """Init Crypto QAT device virtual functions on DUT.
299
300         :param node: DUT node.
301         :param numvfs: Number of VFs to initialize, 0 - disable the VFs.
302         :type node: dict
303         :type numvfs: int
304         :returns: nothing
305         :raises RuntimeError: If failed to stop VPP or QAT failed to initialize.
306         """
307         cryptodev = Topology.get_cryptodev(node)
308
309         # QAT device must be re-bound to kernel driver before initialization
310         driver = 'dh895xcc'
311         kernel_module = 'qat_dh895xcc'
312         current_driver = DUTSetup.get_pci_dev_driver(
313             node, cryptodev.replace(':', r'\:'))
314
315         DUTSetup.kernel_module_verify(node, kernel_module, force_load=True)
316
317         VPPUtil.stop_vpp_service(node)
318         if current_driver is not None:
319             DUTSetup.pci_driver_unbind(node, cryptodev)
320         DUTSetup.pci_driver_bind(node, cryptodev, driver)
321
322         ssh = SSH()
323         ssh.connect(node)
324
325         # Initialize QAT VFs
326         if numvfs > 0:
327             cmd = 'echo "{0}" | tee /sys/bus/pci/devices/{1}/sriov_numvfs'.\
328                 format(numvfs, cryptodev.replace(':', r'\:'), timeout=180)
329             ret_code, _, _ = ssh.exec_command_sudo("sh -c '{0}'".format(cmd))
330
331             if int(ret_code) != 0:
332                 raise RuntimeError('Failed to initialize {0} VFs on QAT device '
333                                    ' on host {1}'.format(numvfs, node['host']))
334
335     @staticmethod
336     def pci_driver_unbind(node, pci_addr):
337         """Unbind PCI device from current driver on node.
338
339         :param node: DUT node.
340         :param pci_addr: PCI device address.
341         :type node: dict
342         :type pci_addr: str
343         :returns: nothing
344         :raises RuntimeError: If PCI device unbind failed.
345         """
346
347         ssh = SSH()
348         ssh.connect(node)
349
350         ret_code, _, _ = ssh.exec_command_sudo(
351             "sh -c 'echo {0} | tee /sys/bus/pci/devices/{1}/driver/unbind'"
352             .format(pci_addr, pci_addr.replace(':', r'\:')), timeout=180)
353
354         if int(ret_code) != 0:
355             raise RuntimeError('Failed to unbind PCI device {0} from driver on '
356                                'host {1}'.format(pci_addr, node['host']))
357
358     @staticmethod
359     def pci_driver_bind(node, pci_addr, driver):
360         """Bind PCI device to driver on node.
361
362         :param node: DUT node.
363         :param pci_addr: PCI device address.
364         :param driver: Driver to bind.
365         :type node: dict
366         :type pci_addr: str
367         :type driver: str
368         :returns: nothing
369         :raises RuntimeError: If PCI device bind failed.
370         """
371
372         ssh = SSH()
373         ssh.connect(node)
374
375         ret_code, _, _ = ssh.exec_command_sudo(
376             "sh -c 'echo {0} | tee /sys/bus/pci/drivers/{1}/bind'".format(
377                 pci_addr, driver), timeout=180)
378
379         if int(ret_code) != 0:
380             raise RuntimeError('Failed to bind PCI device {0} to {1} driver on '
381                                'host {2}'.format(pci_addr, driver,
382                                                  node['host']))
383
384     @staticmethod
385     def get_pci_dev_driver(node, pci_addr):
386         """Get current PCI device driver on node.
387
388         .. note::
389             # lspci -vmmks 0000:00:05.0
390             Slot:   00:05.0
391             Class:  Ethernet controller
392             Vendor: Red Hat, Inc
393             Device: Virtio network device
394             SVendor:        Red Hat, Inc
395             SDevice:        Device 0001
396             PhySlot:        5
397             Driver: virtio-pci
398
399         :param node: DUT node.
400         :param pci_addr: PCI device address.
401         :type node: dict
402         :type pci_addr: str
403         :returns: Driver or None
404         :raises RuntimeError: If PCI rescan or lspci command execution failed.
405         :raises RuntimeError: If it is not possible to get the interface driver
406             information from the node.
407         """
408         ssh = SSH()
409         ssh.connect(node)
410
411         for i in range(3):
412             logger.trace('Try number {0}: Get PCI device driver'.format(i))
413             cmd = 'lspci -vmmks {0}'.format(pci_addr)
414             ret_code, stdout, _ = ssh.exec_command(cmd)
415             if int(ret_code) != 0:
416                 raise RuntimeError("'{0}' failed on '{1}'"
417                                    .format(cmd, node['host']))
418
419             for line in stdout.splitlines():
420                 if len(line) == 0:
421                     continue
422                 name = None
423                 value = None
424                 try:
425                     name, value = line.split("\t", 1)
426                 except ValueError:
427                     if name == "Driver:":
428                         return None
429                 if name == 'Driver:':
430                     return value
431
432             if i < 2:
433                 logger.trace('Driver for PCI device {} not found, executing '
434                              'pci rescan and retrying'.format(pci_addr))
435                 cmd = 'sh -c "echo 1 > /sys/bus/pci/rescan"'
436                 ret_code, _, _ = ssh.exec_command_sudo(cmd)
437                 if int(ret_code) != 0:
438                     raise RuntimeError("'{0}' failed on '{1}'"
439                                        .format(cmd, node['host']))
440
441         return None
442
443     @staticmethod
444     def kernel_module_verify(node, module, force_load=False):
445         """Verify if kernel module is loaded on node. If parameter force
446         load is set to True, then try to load the modules.
447
448         :param node: Node.
449         :param module: Module to verify.
450         :param force_load: If True then try to load module.
451         :type node: dict
452         :type module: str
453         :type force_load: bool
454         :raises RuntimeError: If module is not loaded or failed to load.
455         """
456         ssh = SSH()
457         ssh.connect(node)
458
459         cmd = 'grep -w {0} /proc/modules'.format(module)
460         ret_code, _, _ = ssh.exec_command(cmd)
461
462         if int(ret_code) != 0:
463             if force_load:
464                 # Module is not loaded and we want to load it
465                 DUTSetup.kernel_module_load(node, module)
466             else:
467                 raise RuntimeError('Kernel module {0} is not loaded on host '
468                                    '{1}'.format(module, node['host']))
469
470     @staticmethod
471     def kernel_module_verify_on_all_duts(nodes, module, force_load=False):
472         """Verify if kernel module is loaded on all DUTs. If parameter force
473         load is set to True, then try to load the modules.
474
475         :param node: DUT nodes.
476         :param module: Module to verify.
477         :param force_load: If True then try to load module.
478         :type node: dict
479         :type module: str
480         :type force_load: bool
481         """
482         for node in nodes.values():
483             if node['type'] == NodeType.DUT:
484                 DUTSetup.kernel_module_verify(node, module, force_load)
485
486     @staticmethod
487     def kernel_module_load(node, module):
488         """Load kernel module on node.
489
490         :param node: DUT node.
491         :param module: Module to load.
492         :type node: dict
493         :type module: str
494         :returns: nothing
495         :raises RuntimeError: If loading failed.
496         """
497
498         ssh = SSH()
499         ssh.connect(node)
500
501         ret_code, _, _ = ssh.exec_command_sudo("modprobe {0}".format(module))
502
503         if int(ret_code) != 0:
504             raise RuntimeError('Failed to load {0} kernel module on host {1}'.
505                                format(module, node['host']))
506
507     @staticmethod
508     def vpp_enable_traces_on_all_duts(nodes):
509         """Enable vpp packet traces on all DUTs in the given topology.
510
511         :param nodes: Nodes in the topology.
512         :type nodes: dict
513         """
514         for node in nodes.values():
515             if node['type'] == NodeType.DUT:
516                 DUTSetup.vpp_enable_traces_on_dut(node)
517
518     @staticmethod
519     def vpp_enable_traces_on_dut(node):
520         """Enable vpp packet traces on the DUT node.
521
522         :param node: DUT node to set up.
523         :type node: dict
524         """
525
526         vat = VatExecutor()
527         vat.execute_script("enable_dpdk_traces.vat", node, json_out=False)
528         vat.execute_script("enable_vhost_user_traces.vat", node, json_out=False)
529         vat.execute_script("enable_memif_traces.vat", node, json_out=False)
530
531     @staticmethod
532     def install_vpp_on_all_duts(nodes, vpp_pkg_dir, vpp_rpm_pkgs, vpp_deb_pkgs):
533         """Install VPP on all DUT nodes.
534
535         :param nodes: Nodes in the topology.
536         :param vpp_pkg_dir: Path to directory where VPP packages are stored.
537         :param vpp_rpm_pkgs: List of VPP rpm packages to be installed.
538         :param vpp_deb_pkgs: List of VPP deb packages to be installed.
539         :type nodes: dict
540         :type vpp_pkg_dir: str
541         :type vpp_rpm_pkgs: list
542         :type vpp_deb_pkgs: list
543         :raises RuntimeError: If failed to remove or install VPP.
544         """
545
546         logger.debug("Installing VPP")
547
548         for node in nodes.values():
549             if node['type'] == NodeType.DUT:
550                 logger.debug("Installing VPP on node {0}".format(node['host']))
551
552                 ssh = SSH()
553                 ssh.connect(node)
554
555                 cmd = "[[ -f /etc/redhat-release ]]"
556                 return_code, _, _ = ssh.exec_command(cmd)
557                 if int(return_code) == 0:
558                     # workaroud - uninstall existing vpp installation until
559                     # start-testcase script is updated on all virl servers
560                     rpm_pkgs_remove = "vpp*"
561                     cmd_u = 'yum -y remove "{0}"'.format(rpm_pkgs_remove)
562                     r_rcode, _, r_err = ssh.exec_command_sudo(cmd_u, timeout=90)
563                     if int(r_rcode) != 0:
564                         raise RuntimeError('Failed to remove previous VPP'
565                                            'installation on host {0}:\n{1}'
566                                            .format(node['host'], r_err))
567
568                     rpm_pkgs = "*.rpm ".join(str(vpp_pkg_dir + pkg)
569                                              for pkg in vpp_rpm_pkgs) + "*.rpm"
570                     cmd_i = "rpm -ivh {0}".format(rpm_pkgs)
571                     ret_code, _, err = ssh.exec_command_sudo(cmd_i, timeout=90)
572                     if int(ret_code) != 0:
573                         raise RuntimeError('Failed to install VPP on host {0}:'
574                                            '\n{1}'.format(node['host'], err))
575                     else:
576                         ssh.exec_command_sudo("rpm -qai vpp*")
577                         logger.info("VPP installed on node {0}".
578                                     format(node['host']))
579                 else:
580                     # workaroud - uninstall existing vpp installation until
581                     # start-testcase script is updated on all virl servers
582                     deb_pkgs_remove = "vpp*"
583                     cmd_u = 'apt-get purge -y "{0}"'.format(deb_pkgs_remove)
584                     r_rcode, _, r_err = ssh.exec_command_sudo(cmd_u, timeout=90)
585                     if int(r_rcode) != 0:
586                         raise RuntimeError('Failed to remove previous VPP'
587                                            'installation on host {0}:\n{1}'
588                                            .format(node['host'], r_err))
589                     deb_pkgs = "*.deb ".join(str(vpp_pkg_dir + pkg)
590                                              for pkg in vpp_deb_pkgs) + "*.deb"
591                     cmd_i = "dpkg -i --force-all {0}".format(deb_pkgs)
592                     ret_code, _, err = ssh.exec_command_sudo(cmd_i, timeout=90)
593                     if int(ret_code) != 0:
594                         raise RuntimeError('Failed to install VPP on host {0}:'
595                                            '\n{1}'.format(node['host'], err))
596                     else:
597                         ssh.exec_command_sudo("dpkg -l | grep vpp")
598                         logger.info("VPP installed on node {0}".
599                                     format(node['host']))
600
601                 ssh.disconnect(node)
602
603     @staticmethod
604     def verify_vpp_on_all_duts(nodes):
605         """Verify that VPP is installed on all DUT nodes.
606
607         :param nodes: Nodes in the topology.
608         :type nodes: dict
609         """
610
611         logger.debug("Verify VPP on all DUTs")
612
613         DUTSetup.start_vpp_service_on_all_duts(nodes)
614
615         for node in nodes.values():
616             if node['type'] == NodeType.DUT:
617                 DUTSetup.verify_vpp_on_dut(node)
618
619     @staticmethod
620     def verify_vpp_on_dut(node):
621         """Verify that VPP is installed on DUT node.
622
623         :param node: DUT node.
624         :type node: dict
625         :raises RuntimeError: If failed to restart VPP, get VPP version
626             or get VPP interfaces.
627         """
628
629         logger.debug("Verify VPP on node {0}".format(node['host']))
630
631         DUTSetup.vpp_show_version_verbose(node)
632         DUTSetup.vpp_show_interfaces(node)