commit a30078cb832646177defd256e77c632905f1e6d0
Author: Michal Prívozník <mprivozn@redhat.com>
Date:   Wed Nov 13 15:34:50 2019 +0100

    qemu: Create multipath targets for PRs
    
    If a disk has persistent reservations enabled, qemu-pr-helper
    might open not only /dev/mapper/control but also individual
    targets of the multipath device. We are already querying for them
    in CGroups, but now we have to create them in the namespace too.
    This was brought up in [1].
    
    1: https://bugzilla.redhat.com/show_bug.cgi?id=1711045#c61
    
    Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
    Tested-by: Lin Ma <LMa@suse.com>
    Reviewed-by: Jim Fehlig <jfehlig@suse.com>

Index: libvirt-6.0.0/src/qemu/qemu_domain.c
===================================================================
--- libvirt-6.0.0.orig/src/qemu/qemu_domain.c
+++ libvirt-6.0.0/src/qemu/qemu_domain.c
@@ -62,6 +62,7 @@
 #include "virdomainsnapshotobjlist.h"
 #include "virdomaincheckpointobjlist.h"
 #include "backup_conf.h"
+#include "virdevmapper.h"
 
 #ifdef MAJOR_IN_MKDEV
 # include <sys/mkdev.h>
@@ -14559,6 +14560,9 @@ qemuDomainSetupDisk(virQEMUDriverConfigP
     int ret = -1;
 
     for (next = disk->src; virStorageSourceIsBacking(next); next = next->backingStore) {
+        VIR_AUTOSTRINGLIST targetPaths = NULL;
+        size_t i;
+
         if (next->type == VIR_STORAGE_TYPE_NVME) {
             g_autofree char *nvmePath = NULL;
 
@@ -14577,6 +14581,19 @@ qemuDomainSetupDisk(virQEMUDriverConfigP
 
             if (qemuDomainCreateDevice(next->path, data, false) < 0)
                 goto cleanup;
+
+            if (virDevMapperGetTargets(next->path, &targetPaths) < 0 &&
+                errno != ENOSYS && errno != EBADF) {
+                virReportSystemError(errno,
+                                     _("Unable to get devmapper targets for %s"),
+                                     next->path);
+                goto cleanup;
+            }
+
+            for (i = 0; targetPaths && targetPaths[i]; i++) {
+                if (qemuDomainCreateDevice(targetPaths[i], data, false) < 0)
+                    goto cleanup;
+            }
         }
     }
 
@@ -15603,21 +15620,19 @@ qemuDomainNamespaceSetupDisk(virDomainOb
                              virStorageSourcePtr src)
 {
     virStorageSourcePtr next;
-    char **paths = NULL;
+    VIR_AUTOSTRINGLIST paths = NULL;
     size_t npaths = 0;
     bool hasNVMe = false;
-    g_autofree char *dmPath = NULL;
-    g_autofree char *vfioPath = NULL;
-    int ret = -1;
 
     for (next = src; virStorageSourceIsBacking(next); next = next->backingStore) {
+        VIR_AUTOSTRINGLIST targetPaths = NULL;
         g_autofree char *tmpPath = NULL;
 
         if (next->type == VIR_STORAGE_TYPE_NVME) {
             hasNVMe = true;
 
             if (!(tmpPath = virPCIDeviceAddressGetIOMMUGroupDev(&next->nvme->pciAddr)))
-                goto cleanup;
+                return -1;
         } else {
             if (virStorageSourceIsEmpty(next) ||
                 !virStorageSourceIsLocalStorage(next)) {
@@ -15628,30 +15643,35 @@ qemuDomainNamespaceSetupDisk(virDomainOb
             tmpPath = g_strdup(next->path);
         }
 
-        if (VIR_APPEND_ELEMENT(paths, npaths, tmpPath) < 0)
-            goto cleanup;
+        if (virStringListAdd(&paths, tmpPath) < 0)
+            return -1;
+
+        if (virDevMapperGetTargets(next->path, &targetPaths) < 0 &&
+            errno != ENOSYS && errno != EBADF) {
+            virReportSystemError(errno,
+                                 _("Unable to get devmapper targets for %s"),
+                                 next->path);
+            return -1;
+        }
+
+        if (virStringListMerge(&paths, &targetPaths) < 0)
+            return -1;
     }
 
     /* qemu-pr-helper might require access to /dev/mapper/control. */
-    if (src->pr) {
-        dmPath = g_strdup(QEMU_DEVICE_MAPPER_CONTROL_PATH);
-        if (VIR_APPEND_ELEMENT_COPY(paths, npaths, dmPath) < 0)
-            goto cleanup;
-    }
+    if (src->pr &&
+        virStringListAdd(&paths, QEMU_DEVICE_MAPPER_CONTROL_PATH) < 0)
+        return -1;
 
-    if (hasNVMe) {
-        vfioPath = g_strdup(QEMU_DEV_VFIO);
-        if (VIR_APPEND_ELEMENT(paths, npaths, vfioPath) < 0)
-            goto cleanup;
-    }
+    if (hasNVMe &&
+        virStringListAdd(&paths, QEMU_DEV_VFIO) < 0)
+        return -1;
 
+    npaths = virStringListLength((const char **) paths);
     if (qemuDomainNamespaceMknodPaths(vm, (const char **) paths, npaths) < 0)
-        goto cleanup;
+        return -1;
 
-    ret = 0;
- cleanup:
-    virStringListFreeCount(paths, npaths);
-    return ret;
+    return 0;
 }
 
 
Index: libvirt-6.0.0/src/util/virdevmapper.h
===================================================================
--- libvirt-6.0.0.orig/src/util/virdevmapper.h
+++ libvirt-6.0.0/src/util/virdevmapper.h
@@ -20,6 +20,8 @@
 
 #pragma once
 
+#include "internal.h"
+
 int
 virDevMapperGetTargets(const char *path,
-                       char ***devPaths);
+                       char ***devPaths) G_GNUC_NO_INLINE;
Index: libvirt-6.0.0/src/util/virutil.h
===================================================================
--- libvirt-6.0.0.orig/src/util/virutil.h
+++ libvirt-6.0.0/src/util/virutil.h
@@ -122,7 +122,7 @@ bool virValidateWWN(const char *wwn);
 
 int virGetDeviceID(const char *path,
                    int *maj,
-                   int *min);
+                   int *min) G_GNUC_NO_INLINE;
 int virSetDeviceUnprivSGIO(const char *path,
                            const char *sysfs_dir,
                            int unpriv_sgio);
Index: libvirt-6.0.0/tests/qemuhotplugmock.c
===================================================================
--- libvirt-6.0.0.orig/tests/qemuhotplugmock.c
+++ libvirt-6.0.0/tests/qemuhotplugmock.c
@@ -19,7 +19,24 @@
 #include <config.h>
 
 #include "qemu/qemu_hotplug.h"
+#include "qemu/qemu_process.h"
 #include "conf/domain_conf.h"
+#include "virdevmapper.h"
+#include "virutil.h"
+#include "virmock.h"
+
+static int (*real_virGetDeviceID)(const char *path, int *maj, int *min);
+static bool (*real_virFileExists)(const char *path);
+
+static void
+init_syms(void)
+{
+    if (real_virFileExists)
+        return;
+
+    VIR_MOCK_REAL_INIT(virGetDeviceID);
+    VIR_MOCK_REAL_INIT(virFileExists);
+}
 
 unsigned long long
 qemuDomainGetUnplugTimeout(virDomainObjPtr vm G_GNUC_UNUSED)
@@ -31,3 +48,61 @@ qemuDomainGetUnplugTimeout(virDomainObjP
         return 200;
     return 100;
 }
+
+
+int
+virDevMapperGetTargets(const char *path,
+                       char ***devPaths)
+{
+    *devPaths = NULL;
+
+    if (STREQ(path, "/dev/mapper/virt")) {
+        *devPaths = g_new(char *, 4);
+        (*devPaths)[0] = g_strdup("/dev/block/8:0");  /* /dev/sda */
+        (*devPaths)[1] = g_strdup("/dev/block/8:16"); /* /dev/sdb */
+        (*devPaths)[2] = g_strdup("/dev/block/8:32"); /* /dev/sdc */
+        (*devPaths)[3] = NULL;
+    }
+
+    return 0;
+}
+
+
+int
+virGetDeviceID(const char *path, int *maj, int *min)
+{
+    init_syms();
+
+    if (STREQ(path, "/dev/mapper/virt")) {
+        *maj = 254;
+        *min = 0;
+        return 0;
+    }
+
+    return real_virGetDeviceID(path, maj, min);
+}
+
+
+bool
+virFileExists(const char *path)
+{
+    init_syms();
+
+    if (STREQ(path, "/dev/mapper/virt"))
+        return true;
+
+    return real_virFileExists(path);
+}
+
+
+int
+qemuProcessStartManagedPRDaemon(virDomainObjPtr vm G_GNUC_UNUSED)
+{
+    return 0;
+}
+
+
+void
+qemuProcessKillManagedPRDaemon(virDomainObjPtr vm G_GNUC_UNUSED)
+{
+}
Index: libvirt-6.0.0/tests/qemuhotplugtest.c
===================================================================
--- libvirt-6.0.0.orig/tests/qemuhotplugtest.c
+++ libvirt-6.0.0/tests/qemuhotplugtest.c
@@ -87,6 +87,8 @@ qemuHotplugCreateObjects(virDomainXMLOpt
     virQEMUCapsSet(priv->qemuCaps, QEMU_CAPS_VNC);
     virQEMUCapsSet(priv->qemuCaps, QEMU_CAPS_SPICE);
     virQEMUCapsSet(priv->qemuCaps, QEMU_CAPS_SPICE_FILE_XFER_DISABLE);
+    virQEMUCapsSet(priv->qemuCaps, QEMU_CAPS_PR_MANAGER_HELPER);
+    virQEMUCapsSet(priv->qemuCaps, QEMU_CAPS_SCSI_BLOCK);
 
     if (qemuTestCapsCacheInsert(driver.qemuCapsCache, priv->qemuCaps) < 0)
         return -1;
@@ -743,6 +745,17 @@ mymain(void)
                    "device_del", QMP_DEVICE_DELETED("scsi3-0-5-6") QMP_OK,
                    "human-monitor-command", HMP(""));
 
+    DO_TEST_ATTACH("base-live", "disk-scsi-multipath", false, true,
+                   "object-add", QMP_OK,
+                   "human-monitor-command", HMP("OK\\r\\n"),
+                   "device_add", QMP_OK);
+    DO_TEST_DETACH("base-live", "disk-scsi-multipath", true, true,
+                   "device_del", QMP_OK,
+                   "human-monitor-command", HMP(""));
+    DO_TEST_DETACH("base-live", "disk-scsi-multipath", false, false,
+                   "device_del", QMP_DEVICE_DELETED("scsi0-0-0-0") QMP_OK,
+                   "human-monitor-command", HMP(""));
+
     DO_TEST_ATTACH("base-live", "qemu-agent", false, true,
                    "chardev-add", QMP_OK,
                    "device_add", QMP_OK);
Index: libvirt-6.0.0/tests/qemuhotplugtestdevices/qemuhotplug-disk-scsi-multipath.xml
===================================================================
--- /dev/null
+++ libvirt-6.0.0/tests/qemuhotplugtestdevices/qemuhotplug-disk-scsi-multipath.xml
@@ -0,0 +1,8 @@
+<disk type='block' device='lun'>
+    <driver name='qemu' type='raw'/>
+    <source dev='/dev/mapper/virt'>
+        <reservations managed='yes'/>
+    </source>
+    <target dev='sda' bus='scsi'/>
+  <address type='drive' controller='0' bus='0' target='0' unit='0'/>
+</disk>
Index: libvirt-6.0.0/tests/qemuhotplugtestdomains/qemuhotplug-base-live+disk-scsi-multipath.xml
===================================================================
--- /dev/null
+++ libvirt-6.0.0/tests/qemuhotplugtestdomains/qemuhotplug-base-live+disk-scsi-multipath.xml
@@ -0,0 +1,62 @@
+<domain type='kvm' id='7'>
+  <name>hotplug</name>
+  <uuid>d091ea82-29e6-2e34-3005-f02617b36e87</uuid>
+  <memory unit='KiB'>4194304</memory>
+  <currentMemory unit='KiB'>4194304</currentMemory>
+  <vcpu placement='static'>4</vcpu>
+  <os>
+    <type arch='x86_64' machine='pc'>hvm</type>
+    <boot dev='hd'/>
+  </os>
+  <features>
+    <acpi/>
+    <apic/>
+    <pae/>
+  </features>
+  <clock offset='utc'/>
+  <on_poweroff>destroy</on_poweroff>
+  <on_reboot>restart</on_reboot>
+  <on_crash>restart</on_crash>
+  <devices>
+    <emulator>/usr/bin/qemu-system-x86_64</emulator>
+    <disk type='block' device='lun'>
+      <driver name='qemu' type='raw'/>
+      <source dev='/dev/mapper/virt'>
+        <reservations managed='yes'>
+          <source type='unix' path='/tmp/lib/domain-7-hotplug/pr-helper0.sock' mode='client'/>
+        </reservations>
+      </source>
+      <backingStore/>
+      <target dev='sda' bus='scsi'/>
+      <alias name='scsi0-0-0-0'/>
+      <address type='drive' controller='0' bus='0' target='0' unit='0'/>
+    </disk>
+    <controller type='usb' index='0'>
+      <alias name='usb'/>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/>
+    </controller>
+    <controller type='ide' index='0'>
+      <alias name='ide'/>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/>
+    </controller>
+    <controller type='scsi' index='0' model='virtio-scsi'>
+      <alias name='scsi0'/>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
+    </controller>
+    <controller type='pci' index='0' model='pci-root'>
+      <alias name='pci'/>
+    </controller>
+    <controller type='virtio-serial' index='0'>
+      <alias name='virtio-serial0'/>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
+    </controller>
+    <input type='mouse' bus='ps2'>
+      <alias name='input0'/>
+    </input>
+    <input type='keyboard' bus='ps2'>
+      <alias name='input1'/>
+    </input>
+    <memballoon model='none'/>
+  </devices>
+  <seclabel type='none' model='none'/>
+</domain>
