CSIT-1009: SRv6 proxy tests
[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         :param node: DUT node.
389         :param pci_addr: PCI device address.
390         :type node: dict
391         :type pci_addr: str
392         :returns: Driver or None
393         :raises RuntimeError: If PCI rescan or lspci command execution failed.
394         """
395         ssh = SSH()
396         ssh.connect(node)
397
398         for i in range(3):
399             logger.trace('Try {0}: Get interface driver'.format(i))
400             cmd = 'sh -c "echo 1 > /sys/bus/pci/rescan"'
401             ret_code, _, _ = ssh.exec_command_sudo(cmd)
402             if int(ret_code) != 0:
403                 raise RuntimeError("'{0}' failed on '{1}'"
404                                    .format(cmd, node['host']))
405
406             cmd = 'lspci -vmmks {0}'.format(pci_addr)
407             ret_code, stdout, _ = ssh.exec_command(cmd)
408             if int(ret_code) != 0:
409                 raise RuntimeError("'{0}' failed on '{1}'"
410                                    .format(cmd, node['host']))
411
412             for line in stdout.splitlines():
413                 if len(line) == 0:
414                     continue
415                 name = None
416                 value = None
417                 try:
418                     name, value = line.split("\t", 1)
419                 except ValueError:
420                     if name == "Driver:":
421                         return None
422                 if name == 'Driver:':
423                     return value
424         else:
425             return None
426
427     @staticmethod
428     def kernel_module_verify(node, module, force_load=False):
429         """Verify if kernel module is loaded on all DUTs. If parameter force
430         load is set to True, then try to load the modules.
431
432         :param node: DUT node.
433         :param module: Module to verify.
434         :param force_load: If True then try to load module.
435         :type node: dict
436         :type module: str
437         :type force_load: bool
438         :returns: nothing
439         :raises RuntimeError: If module is not loaded or failed to load.
440         """
441
442         ssh = SSH()
443         ssh.connect(node)
444
445         cmd = 'grep -w {0} /proc/modules'.format(module)
446         ret_code, _, _ = ssh.exec_command(cmd)
447
448         if int(ret_code) != 0:
449             if force_load:
450                 # Module is not loaded and we want to load it
451                 DUTSetup.kernel_module_load(node, module)
452             else:
453                 raise RuntimeError('Kernel module {0} is not loaded on host '
454                                    '{1}'.format(module, node['host']))
455
456     @staticmethod
457     def kernel_module_load(node, module):
458         """Load kernel module on node.
459
460         :param node: DUT node.
461         :param module: Module to load.
462         :type node: dict
463         :type module: str
464         :returns: nothing
465         :raises RuntimeError: If loading failed.
466         """
467
468         ssh = SSH()
469         ssh.connect(node)
470
471         ret_code, _, _ = ssh.exec_command_sudo("modprobe {0}".format(module))
472
473         if int(ret_code) != 0:
474             raise RuntimeError('Failed to load {0} kernel module on host {1}'.
475                                format(module, node['host']))
476
477     @staticmethod
478     def vpp_enable_traces_on_all_duts(nodes):
479         """Enable vpp packet traces on all DUTs in the given topology.
480
481         :param nodes: Nodes in the topology.
482         :type nodes: dict
483         """
484         for node in nodes.values():
485             if node['type'] == NodeType.DUT:
486                 DUTSetup.vpp_enable_traces_on_dut(node)
487
488     @staticmethod
489     def vpp_enable_traces_on_dut(node):
490         """Enable vpp packet traces on the DUT node.
491
492         :param node: DUT node to set up.
493         :type node: dict
494         """
495
496         vat = VatExecutor()
497         vat.execute_script("enable_dpdk_traces.vat", node, json_out=False)
498         vat.execute_script("enable_vhost_user_traces.vat", node, json_out=False)
499         vat.execute_script("enable_memif_traces.vat", node, json_out=False)
500
501     @staticmethod
502     def install_vpp_on_all_duts(nodes, vpp_pkg_dir, vpp_rpm_pkgs, vpp_deb_pkgs):
503         """Install VPP on all DUT nodes.
504
505         :param nodes: Nodes in the topology.
506         :param vpp_pkg_dir: Path to directory where VPP packages are stored.
507         :param vpp_rpm_pkgs: List of VPP rpm packages to be installed.
508         :param vpp_deb_pkgs: List of VPP deb packages to be installed.
509         :type nodes: dict
510         :type vpp_pkg_dir: str
511         :type vpp_rpm_pkgs: list
512         :type vpp_deb_pkgs: list
513         :raises: RuntimeError if failed to remove or install VPP
514         """
515
516         logger.debug("Installing VPP")
517
518         for node in nodes.values():
519             if node['type'] == NodeType.DUT:
520                 logger.debug("Installing VPP on node {0}".format(node['host']))
521
522                 ssh = SSH()
523                 ssh.connect(node)
524
525                 cmd = "[[ -f /etc/redhat-release ]]"
526                 return_code, _, _ = ssh.exec_command(cmd)
527                 if int(return_code) == 0:
528                     # workaroud - uninstall existing vpp installation until
529                     # start-testcase script is updated on all virl servers
530                     rpm_pkgs_remove = "vpp*"
531                     cmd_u = 'yum -y remove "{0}"'.format(rpm_pkgs_remove)
532                     r_rcode, _, r_err = ssh.exec_command_sudo(cmd_u, timeout=90)
533                     if int(r_rcode) != 0:
534                         raise RuntimeError('Failed to remove previous VPP'
535                                            'installation on host {0}:\n{1}'
536                                            .format(node['host'], r_err))
537
538                     rpm_pkgs = "*.rpm ".join(str(vpp_pkg_dir + pkg)
539                                              for pkg in vpp_rpm_pkgs) + "*.rpm"
540                     cmd_i = "rpm -ivh {0}".format(rpm_pkgs)
541                     ret_code, _, err = ssh.exec_command_sudo(cmd_i, timeout=90)
542                     if int(ret_code) != 0:
543                         raise RuntimeError('Failed to install VPP on host {0}:'
544                                            '\n{1}'.format(node['host'], err))
545                     else:
546                         ssh.exec_command_sudo("rpm -qai vpp*")
547                         logger.info("VPP installed on node {0}".
548                                     format(node['host']))
549                 else:
550                     # workaroud - uninstall existing vpp installation until
551                     # start-testcase script is updated on all virl servers
552                     deb_pkgs_remove = "vpp*"
553                     cmd_u = 'apt-get purge -y "{0}"'.format(deb_pkgs_remove)
554                     r_rcode, _, r_err = ssh.exec_command_sudo(cmd_u, timeout=90)
555                     if int(r_rcode) != 0:
556                         raise RuntimeError('Failed to remove previous VPP'
557                                            'installation on host {0}:\n{1}'
558                                            .format(node['host'], r_err))
559                     deb_pkgs = "*.deb ".join(str(vpp_pkg_dir + pkg)
560                                              for pkg in vpp_deb_pkgs) + "*.deb"
561                     cmd_i = "dpkg -i --force-all {0}".format(deb_pkgs)
562                     ret_code, _, err = ssh.exec_command_sudo(cmd_i, timeout=90)
563                     if int(ret_code) != 0:
564                         raise RuntimeError('Failed to install VPP on host {0}:'
565                                            '\n{1}'.format(node['host'], err))
566                     else:
567                         ssh.exec_command_sudo("dpkg -l | grep vpp")
568                         logger.info("VPP installed on node {0}".
569                                     format(node['host']))
570
571                 ssh.disconnect(node)
572
573     @staticmethod
574     def verify_vpp_on_all_duts(nodes):
575         """Verify that VPP is installed on all DUT nodes.
576
577         :param nodes: Nodes in the topology.
578         :type nodes: dict
579         """
580
581         logger.debug("Verify VPP on all DUTs")
582
583         DUTSetup.start_vpp_service_on_all_duts(nodes)
584
585         for node in nodes.values():
586             if node['type'] == NodeType.DUT:
587                 DUTSetup.verify_vpp_on_dut(node)
588
589     @staticmethod
590     def verify_vpp_on_dut(node):
591         """Verify that VPP is installed on DUT node.
592
593         :param node: DUT node.
594         :type node: dict
595         :raises: RuntimeError if failed to restart VPP, get VPP version or
596         get VPP interfaces
597         """
598
599         logger.debug("Verify VPP on node {0}".format(node['host']))
600
601         DUTSetup.vpp_show_version_verbose(node)
602         DUTSetup.vpp_show_interfaces(node)