[PATCH 0/5] qemu: Two block job fixes

Peter Krempa (5): qemu: monitor: Wire up 'replaces' attribute for 'blockdev-mirror' qemu: Do not replace filter nodes with virDomainBlockCopy qemu: Remove return value from 'qemuHotplugRemoveManagedPR' qemuDomainChangeEjectableMedia: Separate rollback and success code paths qemuHotplugRemoveManagedPR: Integrate check whether removal is needed src/qemu/qemu_blockjob.c | 2 +- src/qemu/qemu_driver.c | 3 +- src/qemu/qemu_hotplug.c | 58 +++++++++++++++--------------------- src/qemu/qemu_hotplug.h | 3 +- src/qemu/qemu_migration.c | 1 + src/qemu/qemu_monitor.c | 9 +++--- src/qemu/qemu_monitor.h | 1 + src/qemu/qemu_monitor_json.c | 2 ++ src/qemu/qemu_monitor_json.h | 1 + tests/qemumonitorjsontest.c | 2 +- 10 files changed, 40 insertions(+), 42 deletions(-) -- 2.48.1

From: Peter Krempa <pkrempa@redhat.com> The 'replaces' field controls which node will be replaced by the job. This can be used to e.g. keep filter nodes in place after the copy finishes. This will be used to keep the 'copy-on-read' and 'throttle' layers in place after a copy. This patch wires up the monitor and test, but the real callers pass NULL for now. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_driver.c | 1 + src/qemu/qemu_migration.c | 1 + src/qemu/qemu_monitor.c | 9 +++++---- src/qemu/qemu_monitor.h | 1 + src/qemu/qemu_monitor_json.c | 2 ++ src/qemu/qemu_monitor_json.h | 1 + tests/qemumonitorjsontest.c | 2 +- 7 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index f0e9681161..84564b0658 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -14363,6 +14363,7 @@ qemuDomainBlockCopyCommon(virDomainObj *vm, ret = qemuMonitorBlockdevMirror(priv->mon, job->name, true, qemuDomainDiskGetTopNodename(disk), qemuBlockStorageSourceGetEffectiveNodename(mirror), + NULL, bandwidth, granularity, buf_size, mirror_shallow, syncWrites); diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index 02ba35dc59..75f5b0fa95 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -1213,6 +1213,7 @@ qemuMigrationSrcNBDStorageCopyBlockdev(virDomainObj *vm, mon_ret = qemuMonitorBlockdevMirror(qemuDomainGetMonitor(vm), diskAlias, true, qemuDomainDiskGetTopNodename(disk), qemuBlockStorageSourceGetEffectiveNodename(copysrc), + NULL, mirror_speed, 0, 0, mirror_shallow, syncWrites); diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index 8d8e73d38d..a903e4ac7d 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -2784,20 +2784,21 @@ qemuMonitorBlockdevMirror(qemuMonitor *mon, bool persistjob, const char *device, const char *target, + const char *replaces, unsigned long long bandwidth, unsigned int granularity, unsigned long long buf_size, bool shallow, bool syncWrite) { - VIR_DEBUG("jobname=%s, persistjob=%d, device=%s, target=%s, bandwidth=%lld, " + VIR_DEBUG("jobname=%s, persistjob=%d, device=%s, target=%s, replaces=%s, bandwidth=%lld, " "granularity=%#x, buf_size=%lld, shallow=%d syncWrite=%d", - NULLSTR(jobname), persistjob, device, target, bandwidth, granularity, - buf_size, shallow, syncWrite); + NULLSTR(jobname), persistjob, device, target, NULLSTR(replaces), + bandwidth, granularity, buf_size, shallow, syncWrite); QEMU_CHECK_MONITOR(mon); - return qemuMonitorJSONBlockdevMirror(mon, jobname, persistjob, device, target, + return qemuMonitorJSONBlockdevMirror(mon, jobname, persistjob, device, target, replaces, bandwidth, granularity, buf_size, shallow, syncWrite); } diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h index f7b9263b64..8c3fb5e131 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -981,6 +981,7 @@ int qemuMonitorBlockdevMirror(qemuMonitor *mon, bool persistjob, const char *device, const char *target, + const char *replaces, unsigned long long bandwidth, unsigned int granularity, unsigned long long buf_size, diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index 9c60807926..646d8df242 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -4004,6 +4004,7 @@ qemuMonitorJSONBlockdevMirror(qemuMonitor *mon, bool persistjob, const char *device, const char *target, + const char *replaces, unsigned long long speed, unsigned int granularity, unsigned long long buf_size, @@ -4032,6 +4033,7 @@ qemuMonitorJSONBlockdevMirror(qemuMonitor *mon, "S:job-id", jobname, "s:device", device, "s:target", target, + "S:replaces", replaces, "Y:speed", speed, "z:granularity", granularity, "P:buf-size", buf_size, diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h index 25e3ae2cbb..77b8bf4a1b 100644 --- a/src/qemu/qemu_monitor_json.h +++ b/src/qemu/qemu_monitor_json.h @@ -272,6 +272,7 @@ qemuMonitorJSONBlockdevMirror(qemuMonitor *mon, bool persistjob, const char *device, const char *target, + const char *replaces, unsigned long long speed, unsigned int granularity, unsigned long long buf_size, diff --git a/tests/qemumonitorjsontest.c b/tests/qemumonitorjsontest.c index f0f6a329c8..afc2e54fd0 100644 --- a/tests/qemumonitorjsontest.c +++ b/tests/qemumonitorjsontest.c @@ -1155,7 +1155,7 @@ GEN_TEST_FUNC(qemuMonitorJSONGraphicsRelocate, VIR_DOMAIN_GRAPHICS_TYPE_SPICE, "localhost", 12345, 12346, "certsubjectval") GEN_TEST_FUNC(qemuMonitorJSONRemoveNetdev, "net0") GEN_TEST_FUNC(qemuMonitorJSONDelDevice, "ide0") -GEN_TEST_FUNC(qemuMonitorJSONBlockdevMirror, "jobname", true, "vdb", "targetnode", 1024, 1234, 31234, true, true) +GEN_TEST_FUNC(qemuMonitorJSONBlockdevMirror, "jobname", true, "vdb", "targetnode", "replacenode", 1024, 1234, 31234, true, true) GEN_TEST_FUNC(qemuMonitorJSONBlockStream, "vdb", "jobname", "backingnode", "backingfilename", 1024) GEN_TEST_FUNC(qemuMonitorJSONBlockCommit, "vdb", "jobname", "topnode", "basenode", "backingfilename", 1024, VIR_TRISTATE_BOOL_YES) GEN_TEST_FUNC(qemuMonitorJSONScreendump, "devicename", 1, NULL, "/foo/bar") -- 2.48.1

From: Peter Krempa <pkrempa@redhat.com> The block copy operation is supposed to just move the disk to a new destination. While in certain scenarios it'd make sense to drop the copy-on-read layer, the definition would not correspond to it. This was caused by a fix to the behaviour of the block job after conversion to -blockdev as 'blockdev-mirror' requires the top node of the disk to be selected. This also causes that the 'copy-on-read' filter is ejected but libvirt doesn't unplug it. Instead we need to use the 'replaces' argument of 'blockdev-mirror' which allows to keep filters in place. This will preserve the configuration (which can be optimized later) and also fixes a spurious error logged when trying to unplug the first real file node after copy-on-read which still looks used to qemu. This is also needed for the upcoming feature which adds 'throttle' filter layers as we need to keep those in place too to facilitate the throttling. Resolves: https://issues.redhat.com/browse/RHEL-40077 Fixes: e3137539a9c4af25ab085506d5467ec0847b0ecc Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_driver.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 84564b0658..cf7069a34a 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -14363,7 +14363,7 @@ qemuDomainBlockCopyCommon(virDomainObj *vm, ret = qemuMonitorBlockdevMirror(priv->mon, job->name, true, qemuDomainDiskGetTopNodename(disk), qemuBlockStorageSourceGetEffectiveNodename(mirror), - NULL, + qemuBlockStorageSourceGetEffectiveNodename(disk->src), bandwidth, granularity, buf_size, mirror_shallow, syncWrites); -- 2.48.1

From: Peter Krempa <pkrempa@redhat.com> The only place which actually checked the return value would skip code e.g. to delete unused files or stop no longer used services. The rest of the callers ignored the value. As this is expected to be used on cleanup code paths which have no possibility to report errors we should remove the return value completely. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_blockjob.c | 2 +- src/qemu/qemu_driver.c | 2 +- src/qemu/qemu_hotplug.c | 17 +++++++---------- src/qemu/qemu_hotplug.h | 2 +- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/qemu/qemu_blockjob.c b/src/qemu/qemu_blockjob.c index 7b7d54fdca..c1b29f2fde 100644 --- a/src/qemu/qemu_blockjob.c +++ b/src/qemu/qemu_blockjob.c @@ -709,7 +709,7 @@ qemuBlockJobEventProcessConcludedRemoveChain(virQEMUDriver *driver, qemuDomainStorageSourceChainAccessRevoke(driver, vm, chain); - ignore_value(qemuHotplugRemoveManagedPR(vm, asyncJob)); + qemuHotplugRemoveManagedPR(vm, asyncJob); } diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index cf7069a34a..37cd3a8b7b 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -14396,7 +14396,7 @@ qemuDomainBlockCopyCommon(virDomainObj *vm, if (need_revoke) qemuDomainStorageSourceChainAccessRevoke(driver, vm, mirror); - ignore_value(qemuHotplugRemoveManagedPR(vm, VIR_ASYNC_JOB_NONE)); + qemuHotplugRemoveManagedPR(vm, VIR_ASYNC_JOB_NONE); } if (need_unlink && virStorageSourceUnlink(mirror) < 0) VIR_WARN("%s", _("unable to remove just-created copy target")); diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index b6ef10edf9..bb599d1505 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -469,31 +469,29 @@ qemuHotplugAttachManagedPR(virDomainObj *vm, * Removes the managed PR object from @vm if the configuration does not require * it any more. */ -int +void qemuHotplugRemoveManagedPR(virDomainObj *vm, virDomainAsyncJob asyncJob) { qemuDomainObjPrivate *priv = vm->privateData; virErrorPtr orig_err; - int ret = -1; if (qemuDomainDefHasManagedPR(vm)) - return 0; + return; virErrorPreserveLast(&orig_err); if (qemuDomainObjEnterMonitorAsync(vm, asyncJob) < 0) goto cleanup; + ignore_value(qemuMonitorDelObject(priv->mon, qemuDomainGetManagedPRAlias(), false)); qemuDomainObjExitMonitor(vm); qemuProcessKillManagedPRDaemon(vm); - ret = 0; cleanup: virErrorRestore(&orig_err); - return ret; } @@ -665,7 +663,7 @@ qemuDomainChangeEjectableMedia(virQEMUDriver *driver, /* remove PR manager object if unneeded */ if (managedpr) - ignore_value(qemuHotplugRemoveManagedPR(vm, VIR_ASYNC_JOB_NONE)); + qemuHotplugRemoveManagedPR(vm, VIR_ASYNC_JOB_NONE); /* revert old image do the disk definition */ if (oldsrc) @@ -1099,7 +1097,7 @@ qemuDomainAttachDeviceDiskLiveInternal(virQEMUDriver *driver, ignore_value(qemuDomainStorageSourceChainAccessRevoke(driver, vm, disk->src)); if (virStorageSourceChainHasManagedPR(disk->src)) - ignore_value(qemuHotplugRemoveManagedPR(vm, VIR_ASYNC_JOB_NONE)); + qemuHotplugRemoveManagedPR(vm, VIR_ASYNC_JOB_NONE); } qemuDomainSecretDiskDestroy(disk); qemuDomainCleanupStorageSourceFD(disk->src); @@ -4739,9 +4737,8 @@ qemuDomainRemoveDiskDevice(virQEMUDriver *driver, if (diskBackend) qemuDomainStorageSourceChainAccessRevoke(driver, vm, disk->src); - if (virStorageSourceChainHasManagedPR(disk->src) && - qemuHotplugRemoveManagedPR(vm, VIR_ASYNC_JOB_NONE) < 0) - goto cleanup; + if (virStorageSourceChainHasManagedPR(disk->src)) + qemuHotplugRemoveManagedPR(vm, VIR_ASYNC_JOB_NONE); qemuNbdkitStopStorageSource(disk->src, vm, true); diff --git a/src/qemu/qemu_hotplug.h b/src/qemu/qemu_hotplug.h index d51f649bac..6a94dbd0cb 100644 --- a/src/qemu/qemu_hotplug.h +++ b/src/qemu/qemu_hotplug.h @@ -130,6 +130,6 @@ int qemuHotplugAttachManagedPR(virDomainObj *vm, virStorageSource *src, virDomainAsyncJob asyncJob); -int +void qemuHotplugRemoveManagedPR(virDomainObj *vm, virDomainAsyncJob asyncJob); -- 2.48.1

From: Peter Krempa <pkrempa@redhat.com> Do not use the rollback code path on success just to avoid extra call to qemuHotplugRemoveManagedPR. Rename the label and use it only when rolling back. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_hotplug.c | 37 +++++++++++++++---------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index bb599d1505..d6de9afccd 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -613,9 +613,6 @@ qemuDomainChangeEjectableMedia(virQEMUDriver *driver, qemuDomainObjPrivate *priv = vm->privateData; virStorageSource *oldsrc = disk->src; qemuDomainDiskPrivate *diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); - bool managedpr = virStorageSourceChainHasManagedPR(oldsrc) || - virStorageSourceChainHasManagedPR(newsrc); - int ret = -1; int rc; if (diskPriv->blockjob && qemuBlockJobIsRunning(diskPriv->blockjob)) { @@ -627,49 +624,45 @@ qemuDomainChangeEjectableMedia(virQEMUDriver *driver, disk->src = newsrc; if (virDomainDiskTranslateSourcePool(disk) < 0) - goto cleanup; + goto rollback; if (qemuDomainDetermineDiskChain(driver, vm, disk, NULL) < 0) - goto cleanup; + goto rollback; if (qemuDomainPrepareDiskSource(disk, priv, cfg) < 0) - goto cleanup; + goto rollback; if (qemuDomainStorageSourceChainAccessAllow(driver, vm, newsrc) < 0) - goto cleanup; + goto rollback; if (qemuHotplugAttachManagedPR(vm, newsrc, VIR_ASYNC_JOB_NONE) < 0) - goto cleanup; + goto rollback; rc = qemuDomainChangeMediaBlockdev(vm, disk, oldsrc, newsrc, force); virDomainAuditDisk(vm, oldsrc, newsrc, "update", rc >= 0); if (rc < 0) - goto cleanup; + goto rollback; ignore_value(qemuDomainStorageSourceChainAccessRevoke(driver, vm, oldsrc)); + if (virStorageSourceChainHasManagedPR(oldsrc)) + qemuHotplugRemoveManagedPR(vm, VIR_ASYNC_JOB_NONE); + /* media was changed, so we can remove the old media definition now */ g_clear_pointer(&oldsrc, virObjectUnref); + return 0; - ret = 0; - - cleanup: - /* undo changes to the new disk */ - if (ret < 0) { - ignore_value(qemuDomainStorageSourceChainAccessRevoke(driver, vm, newsrc)); - } + rollback: + ignore_value(qemuDomainStorageSourceChainAccessRevoke(driver, vm, newsrc)); - /* remove PR manager object if unneeded */ - if (managedpr) + if (virStorageSourceChainHasManagedPR(newsrc)) qemuHotplugRemoveManagedPR(vm, VIR_ASYNC_JOB_NONE); /* revert old image do the disk definition */ - if (oldsrc) - disk->src = oldsrc; - - return ret; + disk->src = oldsrc; + return -1; } -- 2.48.1

From: Peter Krempa <pkrempa@redhat.com> Calls to 'qemuHotplugRemoveManagedPR' needed to be guarded by a check if the removed elements actually caused us to add the manager in the first place. The two new calls added in commit 1697323bfe6000c2f5a2519c06f0ba81 were not guarded by such check and thus would spam the debug log with: [{"id": "libvirt-59", "error": {"class": "GenericError", "desc": "object 'pr-helper0' not found"}}] Luckily 'qemuHotplugRemoveManagedPR' didn't request the error to be reported as a proper error. Don't attempt the removal unless needed. Fixes: 1697323bfe6000c2f5a2519c06f0ba81f7b792eb Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_blockjob.c | 2 +- src/qemu/qemu_driver.c | 2 +- src/qemu/qemu_hotplug.c | 18 +++++++++--------- src/qemu/qemu_hotplug.h | 1 + 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/qemu/qemu_blockjob.c b/src/qemu/qemu_blockjob.c index c1b29f2fde..c7462e2838 100644 --- a/src/qemu/qemu_blockjob.c +++ b/src/qemu/qemu_blockjob.c @@ -709,7 +709,7 @@ qemuBlockJobEventProcessConcludedRemoveChain(virQEMUDriver *driver, qemuDomainStorageSourceChainAccessRevoke(driver, vm, chain); - qemuHotplugRemoveManagedPR(vm, asyncJob); + qemuHotplugRemoveManagedPR(vm, chain, asyncJob); } diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 37cd3a8b7b..4594a201c0 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -14396,7 +14396,7 @@ qemuDomainBlockCopyCommon(virDomainObj *vm, if (need_revoke) qemuDomainStorageSourceChainAccessRevoke(driver, vm, mirror); - qemuHotplugRemoveManagedPR(vm, VIR_ASYNC_JOB_NONE); + qemuHotplugRemoveManagedPR(vm, mirror, VIR_ASYNC_JOB_NONE); } if (need_unlink && virStorageSourceUnlink(mirror) < 0) VIR_WARN("%s", _("unable to remove just-created copy target")); diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index d6de9afccd..db0d888194 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -462,8 +462,8 @@ qemuHotplugAttachManagedPR(virDomainObj *vm, /** * qemuHotplugRemoveManagedPR: - * @driver: QEMU driver object * @vm: domain object + * @src: storage source that is being removed * @asyncJob: asynchronous job identifier * * Removes the managed PR object from @vm if the configuration does not require @@ -471,11 +471,15 @@ qemuHotplugAttachManagedPR(virDomainObj *vm, */ void qemuHotplugRemoveManagedPR(virDomainObj *vm, + virStorageSource *src, virDomainAsyncJob asyncJob) { qemuDomainObjPrivate *priv = vm->privateData; virErrorPtr orig_err; + if (!virStorageSourceChainHasManagedPR(src)) + return; + if (qemuDomainDefHasManagedPR(vm)) return; @@ -647,8 +651,7 @@ qemuDomainChangeEjectableMedia(virQEMUDriver *driver, ignore_value(qemuDomainStorageSourceChainAccessRevoke(driver, vm, oldsrc)); - if (virStorageSourceChainHasManagedPR(oldsrc)) - qemuHotplugRemoveManagedPR(vm, VIR_ASYNC_JOB_NONE); + qemuHotplugRemoveManagedPR(vm, oldsrc, VIR_ASYNC_JOB_NONE); /* media was changed, so we can remove the old media definition now */ g_clear_pointer(&oldsrc, virObjectUnref); @@ -657,8 +660,7 @@ qemuDomainChangeEjectableMedia(virQEMUDriver *driver, rollback: ignore_value(qemuDomainStorageSourceChainAccessRevoke(driver, vm, newsrc)); - if (virStorageSourceChainHasManagedPR(newsrc)) - qemuHotplugRemoveManagedPR(vm, VIR_ASYNC_JOB_NONE); + qemuHotplugRemoveManagedPR(vm, newsrc, VIR_ASYNC_JOB_NONE); /* revert old image do the disk definition */ disk->src = oldsrc; @@ -1089,8 +1091,7 @@ qemuDomainAttachDeviceDiskLiveInternal(virQEMUDriver *driver, if (releaseSeclabel) ignore_value(qemuDomainStorageSourceChainAccessRevoke(driver, vm, disk->src)); - if (virStorageSourceChainHasManagedPR(disk->src)) - qemuHotplugRemoveManagedPR(vm, VIR_ASYNC_JOB_NONE); + qemuHotplugRemoveManagedPR(vm, disk->src, VIR_ASYNC_JOB_NONE); } qemuDomainSecretDiskDestroy(disk); qemuDomainCleanupStorageSourceFD(disk->src); @@ -4730,8 +4731,7 @@ qemuDomainRemoveDiskDevice(virQEMUDriver *driver, if (diskBackend) qemuDomainStorageSourceChainAccessRevoke(driver, vm, disk->src); - if (virStorageSourceChainHasManagedPR(disk->src)) - qemuHotplugRemoveManagedPR(vm, VIR_ASYNC_JOB_NONE); + qemuHotplugRemoveManagedPR(vm, disk->src, VIR_ASYNC_JOB_NONE); qemuNbdkitStopStorageSource(disk->src, vm, true); diff --git a/src/qemu/qemu_hotplug.h b/src/qemu/qemu_hotplug.h index 6a94dbd0cb..d3c0b45a5f 100644 --- a/src/qemu/qemu_hotplug.h +++ b/src/qemu/qemu_hotplug.h @@ -132,4 +132,5 @@ qemuHotplugAttachManagedPR(virDomainObj *vm, virDomainAsyncJob asyncJob); void qemuHotplugRemoveManagedPR(virDomainObj *vm, + virStorageSource *src, virDomainAsyncJob asyncJob); -- 2.48.1

On Mon, Mar 17, 2025 at 06:27:27PM +0100, Peter Krempa via Devel wrote:
Peter Krempa (5): qemu: monitor: Wire up 'replaces' attribute for 'blockdev-mirror' qemu: Do not replace filter nodes with virDomainBlockCopy qemu: Remove return value from 'qemuHotplugRemoveManagedPR' qemuDomainChangeEjectableMedia: Separate rollback and success code paths qemuHotplugRemoveManagedPR: Integrate check whether removal is needed
Reviewed-by: Pavel Hrdina <phrdina@redhat.com>
participants (2)
-
Pavel Hrdina
-
Peter Krempa