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