CSIT-1043: Execute pci rescan only if needed
[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 all DUTs. If parameter force
446         load is set to True, then try to load the modules.
447
448         :param node: DUT 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         :returns: nothing
455         :raises RuntimeError: If module is not loaded or failed to load.
456         """
457
458         ssh = SSH()
459         ssh.connect(node)
460
461         cmd = 'grep -w {0} /proc/modules'.format(module)
462         ret_code, _, _ = ssh.exec_command(cmd)
463
464         if int(ret_code) != 0:
465             if force_load:
466                 # Module is not loaded and we want to load it
467                 DUTSetup.kernel_module_load(node, module)
468             else:
469                 raise RuntimeError('Kernel module {0} is not loaded on host '
470                                    '{1}'.format(module, node['host']))
471
472     @staticmethod
473     def kernel_module_load(node, module):
474         """Load kernel module on node.
475
476         :param node: DUT node.
477         :param module: Module to load.
478         :type node: dict
479         :type module: str
480         :returns: nothing
481         :raises RuntimeError: If loading failed.
482         """
483
484         ssh = SSH()
485         ssh.connect(node)
486
487         ret_code, _, _ = ssh.exec_command_sudo("modprobe {0}".format(module))
488
489         if int(ret_code) != 0:
490             raise RuntimeError('Failed to load {0} kernel module on host {1}'.
491                                format(module, node['host']))
492
493     @staticmethod
494     def vpp_enable_traces_on_all_duts(nodes):
495         """Enable vpp packet traces on all DUTs in the given topology.
496
497         :param nodes: Nodes in the topology.
498         :type nodes: dict
499         """
500         for node in nodes.values():
501             if node['type'] == NodeType.DUT:
502                 DUTSetup.vpp_enable_traces_on_dut(node)
503
504     @staticmethod
505     def vpp_enable_traces_on_dut(node):
506         """Enable vpp packet traces on the DUT node.
507
508         :param node: DUT node to set up.
509         :type node: dict
510         """
511
512         vat = VatExecutor()
513         vat.execute_script("enable_dpdk_traces.vat", node, json_out=False)
514         vat.execute_script("enable_vhost_user_traces.vat", node, json_out=False)
515         vat.execute_script("enable_memif_traces.vat", node, json_out=False)
516
517     @staticmethod
518     def install_vpp_on_all_duts(nodes, vpp_pkg_dir, vpp_rpm_pkgs, vpp_deb_pkgs):
519         """Install VPP on all DUT nodes.
520
521         :param nodes: Nodes in the topology.
522         :param vpp_pkg_dir: Path to directory where VPP packages are stored.
523         :param vpp_rpm_pkgs: List of VPP rpm packages to be installed.
524         :param vpp_deb_pkgs: List of VPP deb packages to be installed.
525         :type nodes: dict
526         :type vpp_pkg_dir: str
527         :type vpp_rpm_pkgs: list
528         :type vpp_deb_pkgs: list
529         :raises RuntimeError: If failed to remove or install VPP.
530         """
531
532         logger.debug("Installing VPP")
533
534         for node in nodes.values():
535             if node['type'] == NodeType.DUT:
536                 logger.debug("Installing VPP on node {0}".format(node['host']))
537
538                 ssh = SSH()
539                 ssh.connect(node)
540
541                 cmd = "[[ -f /etc/redhat-release ]]"
542                 return_code, _, _ = ssh.exec_command(cmd)
543                 if int(return_code) == 0:
544                     # workaroud - uninstall existing vpp installation until
545                     # start-testcase script is updated on all virl servers
546                     rpm_pkgs_remove = "vpp*"
547                     cmd_u = 'yum -y remove "{0}"'.format(rpm_pkgs_remove)
548                     r_rcode, _, r_err = ssh.exec_command_sudo(cmd_u, timeout=90)
549                     if int(r_rcode) != 0:
550                         raise RuntimeError('Failed to remove previous VPP'
551                                            'installation on host {0}:\n{1}'
552                                            .format(node['host'], r_err))
553
554                     rpm_pkgs = "*.rpm ".join(str(vpp_pkg_dir + pkg)
555                                              for pkg in vpp_rpm_pkgs) + "*.rpm"
556                     cmd_i = "rpm -ivh {0}".format(rpm_pkgs)
557                     ret_code, _, err = ssh.exec_command_sudo(cmd_i, timeout=90)
558                     if int(ret_code) != 0:
559                         raise RuntimeError('Failed to install VPP on host {0}:'
560                                            '\n{1}'.format(node['host'], err))
561                     else:
562                         ssh.exec_command_sudo("rpm -qai vpp*")
563                         logger.info("VPP installed on node {0}".
564                                     format(node['host']))
565                 else:
566                     # workaroud - uninstall existing vpp installation until
567                     # start-testcase script is updated on all virl servers
568                     deb_pkgs_remove = "vpp*"
569                     cmd_u = 'apt-get purge -y "{0}"'.format(deb_pkgs_remove)
570                     r_rcode, _, r_err = ssh.exec_command_sudo(cmd_u, timeout=90)
571                     if int(r_rcode) != 0:
572                         raise RuntimeError('Failed to remove previous VPP'
573                                            'installation on host {0}:\n{1}'
574                                            .format(node['host'], r_err))
575                     deb_pkgs = "*.deb ".join(str(vpp_pkg_dir + pkg)
576                                              for pkg in vpp_deb_pkgs) + "*.deb"
577                     cmd_i = "dpkg -i --force-all {0}".format(deb_pkgs)
578                     ret_code, _, err = ssh.exec_command_sudo(cmd_i, timeout=90)
579                     if int(ret_code) != 0:
580                         raise RuntimeError('Failed to install VPP on host {0}:'
581                                            '\n{1}'.format(node['host'], err))
582                     else:
583                         ssh.exec_command_sudo("dpkg -l | grep vpp")
584                         logger.info("VPP installed on node {0}".
585                                     format(node['host']))
586
587                 ssh.disconnect(node)
588
589     @staticmethod
590     def verify_vpp_on_all_duts(nodes):
591         """Verify that VPP is installed on all DUT nodes.
592
593         :param nodes: Nodes in the topology.
594         :type nodes: dict
595         """
596
597         logger.debug("Verify VPP on all DUTs")
598
599         DUTSetup.start_vpp_service_on_all_duts(nodes)
600
601         for node in nodes.values():
602             if node['type'] == NodeType.DUT:
603                 DUTSetup.verify_vpp_on_dut(node)
604
605     @staticmethod
606     def verify_vpp_on_dut(node):
607         """Verify that VPP is installed on DUT node.
608
609         :param node: DUT node.
610         :type node: dict
611         :raises RuntimeError: If failed to restart VPP, get VPP version
612             or get VPP interfaces.
613         """
614
615         logger.debug("Verify VPP on node {0}".format(node['host']))
616
617         DUTSetup.vpp_show_version_verbose(node)
618         DUTSetup.vpp_show_interfaces(node)