1import re
2from autotest_lib.client.common_lib import error
3from autotest_lib.client.virt import virt_utils, virt_vm, aexpect
4
5
6def run_pci_hotplug(test, params, env):
7    """
8    Test hotplug of PCI devices.
9
10    (Elements between [] are configurable test parameters)
11    1) PCI add a deivce (NIC / block)
12    2) Compare output of monitor command 'info pci'.
13    3) Compare output of guest command [reference_cmd].
14    4) Verify whether pci_model is shown in [pci_find_cmd].
15    5) Check whether the newly added PCI device works fine.
16    6) PCI delete the device, verify whether could remove the PCI device.
17
18    @param test:   KVM test object.
19    @param params: Dictionary with the test parameters.
20    @param env:    Dictionary with test environment.
21    """
22    vm = env.get_vm(params["main_vm"])
23    vm.verify_alive()
24    timeout = int(params.get("login_timeout", 360))
25    session = vm.wait_for_login(timeout=timeout)
26
27    # Modprobe the module if specified in config file
28    module = params.get("modprobe_module")
29    if module:
30        session.cmd("modprobe %s" % module)
31
32    # Get output of command 'info pci' as reference
33    info_pci_ref = vm.monitor.info("pci")
34
35    # Get output of command as reference
36    reference = session.cmd_output(params.get("reference_cmd"))
37
38    tested_model = params.get("pci_model")
39    test_type = params.get("pci_type")
40    image_format = params.get("image_format_stg")
41
42    # Probe qemu to verify what is the supported syntax for PCI hotplug
43    cmd_output = vm.monitor.cmd("?")
44    if len(re.findall("\ndevice_add", cmd_output)) > 0:
45        cmd_type = "device_add"
46    elif len(re.findall("\npci_add", cmd_output)) > 0:
47        cmd_type = "pci_add"
48    else:
49        raise error.TestError("Unknow version of qemu")
50
51    # Determine syntax of drive hotplug
52    # __com.redhat_drive_add == qemu-kvm-0.12 on RHEL 6
53    if len(re.findall("\n__com.redhat_drive_add", cmd_output)) > 0:
54        drive_cmd_type = "__com.redhat_drive_add"
55    # drive_add == qemu-kvm-0.13 onwards
56    elif len(re.findall("\ndrive_add", cmd_output)) > 0:
57        drive_cmd_type = "drive_add"
58    else:
59        raise error.TestError("Unknow version of qemu")
60
61    # Probe qemu for a list of supported devices
62    devices_support = vm.monitor.cmd("%s ?" % cmd_type)
63
64    if cmd_type == "pci_add":
65        if test_type == "nic":
66            pci_add_cmd = "pci_add pci_addr=auto nic model=%s" % tested_model
67        elif test_type == "block":
68            image_params = params.object_params("stg")
69            image_filename = virt_vm.get_image_filename(image_params,
70                                                       test.bindir)
71            pci_add_cmd = ("pci_add pci_addr=auto storage file=%s,if=%s" %
72                           (image_filename, tested_model))
73        # Execute pci_add (should be replaced by a proper monitor method call)
74        add_output = vm.monitor.cmd(pci_add_cmd)
75        if not "OK domain" in add_output:
76            raise error.TestFail("Add PCI device failed. "
77                                 "Monitor command is: %s, Output: %r" %
78                                 (pci_add_cmd, add_output))
79        after_add = vm.monitor.info("pci")
80
81    elif cmd_type == "device_add":
82        driver_id = test_type + "-" + virt_utils.generate_random_id()
83        device_id = test_type + "-" + virt_utils.generate_random_id()
84        if test_type == "nic":
85            if tested_model == "virtio":
86                tested_model = "virtio-net-pci"
87            pci_add_cmd = "device_add id=%s,driver=%s" % (device_id,
88                                                          tested_model)
89
90        elif test_type == "block":
91            image_params = params.object_params("stg")
92            image_filename = virt_vm.get_image_filename(image_params,
93                                                       test.bindir)
94            controller_model = None
95            if tested_model == "virtio":
96                tested_model = "virtio-blk-pci"
97
98            if tested_model == "scsi":
99                tested_model = "scsi-disk"
100                controller_model = "lsi53c895a"
101                if len(re.findall(controller_model, devices_support)) == 0:
102                    raise error.TestError("scsi controller device (%s) not "
103                                          "supported by qemu" %
104                                          controller_model)
105
106            if controller_model is not None:
107                controller_id = "controller-" + device_id
108                controller_add_cmd = ("device_add %s,id=%s" %
109                                      (controller_model, controller_id))
110                vm.monitor.cmd(controller_add_cmd)
111
112            if drive_cmd_type == "drive_add":
113                driver_add_cmd = ("drive_add auto "
114                                  "file=%s,if=none,id=%s,format=%s" %
115                                  (image_filename, driver_id, image_format))
116            elif drive_cmd_type == "__com.redhat_drive_add":
117                driver_add_cmd = ("__com.redhat_drive_add "
118                                  "file=%s,format=%s,id=%s" %
119                                  (image_filename, image_format, driver_id))
120
121            pci_add_cmd = ("device_add id=%s,driver=%s,drive=%s" %
122                           (device_id, tested_model, driver_id))
123            vm.monitor.cmd(driver_add_cmd)
124
125        # Check if the device is support in qemu
126        if len(re.findall(tested_model, devices_support)) > 0:
127            add_output = vm.monitor.cmd(pci_add_cmd)
128        else:
129            raise error.TestError("%s doesn't support device: %s" %
130                                  (cmd_type, tested_model))
131        after_add = vm.monitor.info("pci")
132
133        if not device_id in after_add:
134            raise error.TestFail("Add device failed. Monitor command is: %s"
135                                 ". Output: %r" % (pci_add_cmd, add_output))
136
137    # Define a helper function to delete the device
138    def pci_del(ignore_failure=False):
139        if cmd_type == "pci_add":
140            result_domain, bus, slot, function = add_output.split(',')
141            domain = int(result_domain.split()[2])
142            bus = int(bus.split()[1])
143            slot = int(slot.split()[1])
144            pci_addr = "%x:%x:%x" % (domain, bus, slot)
145            cmd = "pci_del pci_addr=%s" % pci_addr
146        elif cmd_type == "device_add":
147            cmd = "device_del %s" % device_id
148        # This should be replaced by a proper monitor method call
149        vm.monitor.cmd(cmd)
150
151        def device_removed():
152            after_del = vm.monitor.info("pci")
153            return after_del != after_add
154
155        if (not virt_utils.wait_for(device_removed, 10, 0, 1)
156            and not ignore_failure):
157            raise error.TestFail("Failed to hot remove PCI device: %s. "
158                                 "Monitor command: %s" %
159                                 (tested_model, cmd))
160
161    try:
162        # Compare the output of 'info pci'
163        if after_add == info_pci_ref:
164            raise error.TestFail("No new PCI device shown after executing "
165                                 "monitor command: 'info pci'")
166
167        # Define a helper function to compare the output
168        def new_shown():
169            o = session.cmd_output(params.get("reference_cmd"))
170            return o != reference
171
172        secs = int(params.get("wait_secs_for_hook_up"))
173        if not virt_utils.wait_for(new_shown, 30, secs, 3):
174            raise error.TestFail("No new device shown in output of command "
175                                 "executed inside the guest: %s" %
176                                 params.get("reference_cmd"))
177
178        # Define a helper function to catch PCI device string
179        def find_pci():
180            o = session.cmd_output(params.get("find_pci_cmd"))
181            return params.get("match_string") in o
182
183        if not virt_utils.wait_for(find_pci, 30, 3, 3):
184            raise error.TestFail("PCI %s %s device not found in guest. "
185                                 "Command was: %s" %
186                                 (tested_model, test_type,
187                                  params.get("find_pci_cmd")))
188
189        # Test the newly added device
190        try:
191            session.cmd(params.get("pci_test_cmd"))
192        except aexpect.ShellError, e:
193            raise error.TestFail("Check for %s device failed after PCI "
194                                 "hotplug. Output: %r" % (test_type, e.output))
195
196        session.close()
197
198    except:
199        pci_del(ignore_failure=True)
200        raise
201
202    else:
203        pci_del()
204