[libvirt] [PATCH 0/5] qemu: Refactor placement of per-storage-source data (blockdev-add saga)

These patches are part of John's original series located here: https://www.redhat.com/archives/libvir-list/2017-October/msg00228.html The patches are reordered and fixed to make more sense. Patch 1/5 is originally 5/16, without any modification. Patches 2/5 and 3/5 are split from patch 2/16 from original series. Patches 3/16 and 4/16 were dropped. Allocating the private data via xmlopt does not make sense since the storage-driver originating virStorageSources would not have them allocated anyways. We will allocate them when necessary in the qemu driver. The copy function was dropped, since the private data should not really be copied. It can be added later if necessary. The rest of the series will be posted later. I'm interrested in parsing of auth/encryption for backing chain members (1/16, 7/16) and the JSON generator from 15/16. I'll extract those parts in my upcoming blockdev-add saga postings and post the rest later. John Ferlan (5): qemu: Add missing encinfo cleanup util: storage: Introduce privateData for _virStorageSource qemu: Introduce privateData object for virStorageSource qemu: Relocate qemuDomainSecretInfoPtr to qemuDomainStorageSourcePrivate qemu: Move encinfo from private disk to private disk src src/qemu/qemu_command.c | 12 +++++----- src/qemu/qemu_domain.c | 60 ++++++++++++++++++++++++++++++++++++++--------- src/qemu/qemu_domain.h | 26 +++++++++++++------- src/qemu/qemu_hotplug.c | 11 +++++---- src/util/virstoragefile.c | 1 + src/util/virstoragefile.h | 3 +++ 6 files changed, 82 insertions(+), 31 deletions(-) -- 2.14.1

From: John Ferlan <jferlan@redhat.com> When commit id 'da86c6c22' added support for diskPriv->encinfo in qemuDomainSecretDiskPrepare a change to qemuDomainSecretDiskDestroy to was missed. Although qemuDomainDiskPrivateDispose probably would do the trick. Signed-off-by: John Ferlan <jferlan@redhat.com> --- src/qemu/qemu_domain.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 23b9c6c13..41009331a 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -1302,10 +1302,11 @@ qemuDomainSecretDiskDestroy(virDomainDiskDefPtr disk) { qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); - if (!diskPriv || !diskPriv->secinfo) - return; + if (diskPriv && diskPriv->secinfo) + qemuDomainSecretInfoFree(&diskPriv->secinfo); - qemuDomainSecretInfoFree(&diskPriv->secinfo); + if (diskPriv && diskPriv->encinfo) + qemuDomainSecretInfoFree(&diskPriv->encinfo); } -- 2.14.1

From: John Ferlan <jferlan@redhat.com> Introduce the bare necessities to add privateData to _virStorageSource. Signed-off-by: John Ferlan <jferlan@redhat.com> Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/util/virstoragefile.c | 1 + src/util/virstoragefile.h | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/util/virstoragefile.c b/src/util/virstoragefile.c index d2b21a816..d88183591 100644 --- a/src/util/virstoragefile.c +++ b/src/util/virstoragefile.c @@ -2295,6 +2295,7 @@ virStorageSourceClear(virStorageSourcePtr def) virStorageNetHostDefFree(def->nhosts, def->hosts); virStorageAuthDefFree(def->auth); + virObjectUnref(def->privateData); VIR_FREE(def->nodestorage); VIR_FREE(def->nodeformat); diff --git a/src/util/virstoragefile.h b/src/util/virstoragefile.h index 1e36a6671..2f56aea1d 100644 --- a/src/util/virstoragefile.h +++ b/src/util/virstoragefile.h @@ -27,6 +27,7 @@ # include <sys/stat.h> # include "virbitmap.h" +# include "virobject.h" # include "virseclabel.h" # include "virstorageencryption.h" # include "virutil.h" @@ -241,6 +242,8 @@ struct _virStorageSource { virStorageAuthDefPtr auth; virStorageEncryptionPtr encryption; + virObjectPtr privateData; + char *driverName; int format; /* virStorageFileFormat in domain backing chains, but * pool-specific enum for storage volumes */ -- 2.14.1

From: John Ferlan <jferlan@redhat.com> Add the object definition and helpers to store security-related private data for virStorageSources. Signed-off-by: John Ferlan <jferlan@redhat.com> Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_domain.c | 43 +++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_domain.h | 17 +++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 41009331a..f44e1436d 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -932,6 +932,49 @@ qemuDomainDiskPrivateDispose(void *obj) } +static virClassPtr qemuDomainStorageSourcePrivateClass; +static void qemuDomainStorageSourcePrivateDispose(void *obj); + +static int +qemuDomainStorageSourcePrivateOnceInit(void) +{ + qemuDomainStorageSourcePrivateClass = virClassNew(virClassForObject(), + "qemuDomainStorageSourcePrivate", + sizeof(qemuDomainStorageSourcePrivate), + qemuDomainStorageSourcePrivateDispose); + if (!qemuDomainStorageSourcePrivateClass) + return -1; + else + return 0; +} + +VIR_ONCE_GLOBAL_INIT(qemuDomainStorageSourcePrivate) + +virObjectPtr +qemuDomainStorageSourcePrivateNew(void) +{ + qemuDomainStorageSourcePrivatePtr priv; + + if (qemuDomainStorageSourcePrivateInitialize() < 0) + return NULL; + + if (!(priv = virObjectNew(qemuDomainStorageSourcePrivateClass))) + return NULL; + + return (virObjectPtr) priv; +} + + +static void +qemuDomainStorageSourcePrivateDispose(void *obj) +{ + qemuDomainStorageSourcePrivatePtr priv = obj; + + qemuDomainSecretInfoFree(&priv->secinfo); + qemuDomainSecretInfoFree(&priv->encinfo); +} + + static virClassPtr qemuDomainHostdevPrivateClass; static void qemuDomainHostdevPrivateDispose(void *obj); diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index 7c9364f35..2e1515fa1 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -363,6 +363,23 @@ struct _qemuDomainDiskPrivate { bool removable; /* device media can be removed/changed */ }; +# define QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src) \ + ((qemuDomainStorageSourcePrivatePtr) (src)->privateData) + +typedef struct _qemuDomainStorageSourcePrivate qemuDomainStorageSourcePrivate; +typedef qemuDomainStorageSourcePrivate *qemuDomainStorageSourcePrivatePtr; +struct _qemuDomainStorageSourcePrivate { + virObject parent; + + /* data required for authentication to the storage source */ + qemuDomainSecretInfoPtr secinfo; + + /* data required for decryption of encrypted storage source */ + qemuDomainSecretInfoPtr encinfo; +}; + +virObjectPtr qemuDomainStorageSourcePrivateNew(void); + # define QEMU_DOMAIN_HOSTDEV_PRIVATE(hostdev) \ ((qemuDomainHostdevPrivatePtr) (hostdev)->privateData) -- 2.14.1

From: John Ferlan <jferlan@redhat.com> Since the secret information is really virStorageSource specific piece of data, let's manage the privateData from there instead of at the Disk level. Signed-off-by: John Ferlan <jferlan@redhat.com> Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_command.c | 6 ++++-- src/qemu/qemu_domain.c | 14 ++++++++++---- src/qemu/qemu_domain.h | 4 ---- src/qemu/qemu_hotplug.c | 7 +++++-- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 40d9eeffd..7c511263b 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -1362,7 +1362,8 @@ qemuBuildDriveSourceStr(virDomainDiskDefPtr disk, { int actualType = virStorageSourceGetActualType(disk->src); qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); - qemuDomainSecretInfoPtr secinfo = diskPriv->secinfo; + qemuDomainStorageSourcePrivatePtr srcpriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(disk->src); + qemuDomainSecretInfoPtr secinfo = srcpriv->secinfo; qemuDomainSecretInfoPtr encinfo = diskPriv->encinfo; virJSONValuePtr srcprops = NULL; char *source = NULL; @@ -2239,7 +2240,8 @@ qemuBuildDiskDriveCommandLine(virCommandPtr cmd, bool driveBoot = false; virDomainDiskDefPtr disk = def->disks[i]; qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); - qemuDomainSecretInfoPtr secinfo = diskPriv->secinfo; + qemuDomainStorageSourcePrivatePtr srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(disk->src); + qemuDomainSecretInfoPtr secinfo = srcPriv->secinfo; qemuDomainSecretInfoPtr encinfo = diskPriv->encinfo; if (disk->info.bootIndex) { diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index f44e1436d..aa8370866 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -927,7 +927,6 @@ qemuDomainDiskPrivateDispose(void *obj) { qemuDomainDiskPrivatePtr priv = obj; - qemuDomainSecretInfoFree(&priv->secinfo); qemuDomainSecretInfoFree(&priv->encinfo); } @@ -1344,9 +1343,10 @@ void qemuDomainSecretDiskDestroy(virDomainDiskDefPtr disk) { qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); + qemuDomainStorageSourcePrivatePtr srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(disk->src); - if (diskPriv && diskPriv->secinfo) - qemuDomainSecretInfoFree(&diskPriv->secinfo); + if (srcPriv && srcPriv->secinfo) + qemuDomainSecretInfoFree(&srcPriv->secinfo); if (diskPriv && diskPriv->encinfo) qemuDomainSecretInfoFree(&diskPriv->encinfo); @@ -1395,6 +1395,12 @@ qemuDomainSecretDiskPrepare(virConnectPtr conn, { virStorageSourcePtr src = disk->src; qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); + qemuDomainStorageSourcePrivatePtr srcPriv; + + if (!(disk->src->privateData = qemuDomainStorageSourcePrivateNew())) + return -1; + + srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(disk->src); if (qemuDomainSecretDiskCapable(src)) { virSecretUsageType usageType = VIR_SECRET_USAGE_TYPE_ISCSI; @@ -1402,7 +1408,7 @@ qemuDomainSecretDiskPrepare(virConnectPtr conn, if (src->protocol == VIR_STORAGE_NET_PROTOCOL_RBD) usageType = VIR_SECRET_USAGE_TYPE_CEPH; - if (!(diskPriv->secinfo = + if (!(srcPriv->secinfo = qemuDomainSecretInfoNew(conn, priv, disk->info.alias, usageType, src->auth->username, &src->auth->seclookupdef, false))) diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index 2e1515fa1..30e0a5d8d 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -349,10 +349,6 @@ struct _qemuDomainDiskPrivate { bool migrating; /* the disk is being migrated */ - /* for storage devices using auth/secret - * NB: *not* to be written to qemu domain object XML */ - qemuDomainSecretInfoPtr secinfo; - /* for storage devices using encryption/secret * Can have both <auth> and <encryption> for some disks * NB:*not* to be written to qemu domain object XML */ diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index 5efc60aea..d65aa3a72 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -258,6 +258,7 @@ qemuDomainChangeEjectableMedia(virQEMUDriverPtr driver, char *driveAlias = NULL; qemuDomainObjPrivatePtr priv = vm->privateData; qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); + qemuDomainStorageSourcePrivatePtr srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(disk->src); const char *format = NULL; char *sourcestr = NULL; @@ -299,7 +300,7 @@ qemuDomainChangeEjectableMedia(virQEMUDriverPtr driver, } if (!virStorageSourceIsEmpty(newsrc)) { - if (qemuGetDriveSourceString(newsrc, diskPriv->secinfo, &sourcestr) < 0) + if (qemuGetDriveSourceString(newsrc, srcPriv->secinfo, &sourcestr) < 0) goto error; if (virStorageSourceGetActualType(newsrc) != VIR_STORAGE_TYPE_DIR) { @@ -370,6 +371,7 @@ qemuDomainAttachDiskGeneric(virConnectPtr conn, virJSONValuePtr secobjProps = NULL; virJSONValuePtr encobjProps = NULL; qemuDomainDiskPrivatePtr diskPriv; + qemuDomainStorageSourcePrivatePtr srcPriv; qemuDomainSecretInfoPtr secinfo; qemuDomainSecretInfoPtr encinfo; @@ -383,7 +385,8 @@ qemuDomainAttachDiskGeneric(virConnectPtr conn, goto error; diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); - secinfo = diskPriv->secinfo; + srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(disk->src); + secinfo = srcPriv->secinfo; if (secinfo && secinfo->type == VIR_DOMAIN_SECRET_INFO_TYPE_AES) { if (qemuBuildSecretInfoProps(secinfo, &secobjProps) < 0) goto error; -- 2.14.1

From: John Ferlan <jferlan@redhat.com> Since the encryption information can also be disk source specific move it from qemuDomainDiskPrivate to qemuDomainStorageSourcePrivate Since the last allocated element from qemuDomainDiskPrivate is removed, that means we no longer need qemuDomainDiskPrivateDispose. Signed-off-by: John Ferlan <jferlan@redhat.com> Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_command.c | 6 ++---- src/qemu/qemu_domain.c | 20 ++++---------------- src/qemu/qemu_domain.h | 5 ----- src/qemu/qemu_hotplug.c | 4 +--- 4 files changed, 7 insertions(+), 28 deletions(-) diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 7c511263b..b1cfafa79 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -1361,10 +1361,9 @@ qemuBuildDriveSourceStr(virDomainDiskDefPtr disk, virQEMUCapsPtr qemuCaps) { int actualType = virStorageSourceGetActualType(disk->src); - qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); qemuDomainStorageSourcePrivatePtr srcpriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(disk->src); qemuDomainSecretInfoPtr secinfo = srcpriv->secinfo; - qemuDomainSecretInfoPtr encinfo = diskPriv->encinfo; + qemuDomainSecretInfoPtr encinfo = srcpriv->encinfo; virJSONValuePtr srcprops = NULL; char *source = NULL; int ret = -1; @@ -2239,10 +2238,9 @@ qemuBuildDiskDriveCommandLine(virCommandPtr cmd, unsigned int bootindex = 0; bool driveBoot = false; virDomainDiskDefPtr disk = def->disks[i]; - qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); qemuDomainStorageSourcePrivatePtr srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(disk->src); qemuDomainSecretInfoPtr secinfo = srcPriv->secinfo; - qemuDomainSecretInfoPtr encinfo = diskPriv->encinfo; + qemuDomainSecretInfoPtr encinfo = srcPriv->encinfo; if (disk->info.bootIndex) { bootindex = disk->info.bootIndex; diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index aa8370866..2bd3dcb49 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -890,7 +890,6 @@ qemuDomainSecretInfoFree(qemuDomainSecretInfoPtr *secinfo) static virClassPtr qemuDomainDiskPrivateClass; -static void qemuDomainDiskPrivateDispose(void *obj); static int qemuDomainDiskPrivateOnceInit(void) @@ -898,7 +897,7 @@ qemuDomainDiskPrivateOnceInit(void) qemuDomainDiskPrivateClass = virClassNew(virClassForObject(), "qemuDomainDiskPrivate", sizeof(qemuDomainDiskPrivate), - qemuDomainDiskPrivateDispose); + NULL); if (!qemuDomainDiskPrivateClass) return -1; else @@ -922,15 +921,6 @@ qemuDomainDiskPrivateNew(void) } -static void -qemuDomainDiskPrivateDispose(void *obj) -{ - qemuDomainDiskPrivatePtr priv = obj; - - qemuDomainSecretInfoFree(&priv->encinfo); -} - - static virClassPtr qemuDomainStorageSourcePrivateClass; static void qemuDomainStorageSourcePrivateDispose(void *obj); @@ -1342,14 +1332,13 @@ qemuDomainSecretInfoTLSNew(virConnectPtr conn, void qemuDomainSecretDiskDestroy(virDomainDiskDefPtr disk) { - qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); qemuDomainStorageSourcePrivatePtr srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(disk->src); if (srcPriv && srcPriv->secinfo) qemuDomainSecretInfoFree(&srcPriv->secinfo); - if (diskPriv && diskPriv->encinfo) - qemuDomainSecretInfoFree(&diskPriv->encinfo); + if (srcPriv && srcPriv->encinfo) + qemuDomainSecretInfoFree(&srcPriv->encinfo); } @@ -1394,7 +1383,6 @@ qemuDomainSecretDiskPrepare(virConnectPtr conn, virDomainDiskDefPtr disk) { virStorageSourcePtr src = disk->src; - qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); qemuDomainStorageSourcePrivatePtr srcPriv; if (!(disk->src->privateData = qemuDomainStorageSourcePrivateNew())) @@ -1416,7 +1404,7 @@ qemuDomainSecretDiskPrepare(virConnectPtr conn, } if (qemuDomainDiskHasEncryptionSecret(src)) { - if (!(diskPriv->encinfo = + if (!(srcPriv->encinfo = qemuDomainSecretInfoNew(conn, priv, disk->info.alias, VIR_SECRET_USAGE_TYPE_VOLUME, NULL, &src->encryption->secrets[0]->seclookupdef, diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index 30e0a5d8d..39cb68b3c 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -349,11 +349,6 @@ struct _qemuDomainDiskPrivate { bool migrating; /* the disk is being migrated */ - /* for storage devices using encryption/secret - * Can have both <auth> and <encryption> for some disks - * NB:*not* to be written to qemu domain object XML */ - qemuDomainSecretInfoPtr encinfo; - /* information about the device */ bool tray; /* device has tray */ bool removable; /* device media can be removed/changed */ diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index d65aa3a72..ab07ba25f 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -370,7 +370,6 @@ qemuDomainAttachDiskGeneric(virConnectPtr conn, virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); virJSONValuePtr secobjProps = NULL; virJSONValuePtr encobjProps = NULL; - qemuDomainDiskPrivatePtr diskPriv; qemuDomainStorageSourcePrivatePtr srcPriv; qemuDomainSecretInfoPtr secinfo; qemuDomainSecretInfoPtr encinfo; @@ -384,7 +383,6 @@ qemuDomainAttachDiskGeneric(virConnectPtr conn, if (qemuDomainSecretDiskPrepare(conn, priv, disk) < 0) goto error; - diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(disk->src); secinfo = srcPriv->secinfo; if (secinfo && secinfo->type == VIR_DOMAIN_SECRET_INFO_TYPE_AES) { @@ -392,7 +390,7 @@ qemuDomainAttachDiskGeneric(virConnectPtr conn, goto error; } - encinfo = diskPriv->encinfo; + encinfo = srcPriv->encinfo; if (encinfo && qemuBuildSecretInfoProps(encinfo, &encobjProps) < 0) goto error; -- 2.14.1

On 10/19/2017 11:34 AM, Peter Krempa wrote:
These patches are part of John's original series located here:
https://www.redhat.com/archives/libvir-list/2017-October/msg00228.html
The patches are reordered and fixed to make more sense.
Patch 1/5 is originally 5/16, without any modification. Patches 2/5 and 3/5 are split from patch 2/16 from original series.
Patches 3/16 and 4/16 were dropped. Allocating the private data via xmlopt does not make sense since the storage-driver originating virStorageSources would not have them allocated anyways. We will allocate them when necessary in the qemu driver.
The copy function was dropped, since the private data should not really be copied. It can be added later if necessary.
The rest of the series will be posted later. I'm interrested in parsing of auth/encryption for backing chain members (1/16, 7/16) and the JSON generator from 15/16. I'll extract those parts in my upcoming blockdev-add saga postings and post the rest later.
John Ferlan (5): qemu: Add missing encinfo cleanup util: storage: Introduce privateData for _virStorageSource qemu: Introduce privateData object for virStorageSource qemu: Relocate qemuDomainSecretInfoPtr to qemuDomainStorageSourcePrivate qemu: Move encinfo from private disk to private disk src
src/qemu/qemu_command.c | 12 +++++----- src/qemu/qemu_domain.c | 60 ++++++++++++++++++++++++++++++++++++++--------- src/qemu/qemu_domain.h | 26 +++++++++++++------- src/qemu/qemu_hotplug.c | 11 +++++---- src/util/virstoragefile.c | 1 + src/util/virstoragefile.h | 3 +++ 6 files changed, 82 insertions(+), 31 deletions(-)
I'm fine with the refactor - whatever works best. Feels strange to ACK or R-B my own code, but if that's what you need, then consider it provided. John
participants (2)
-
John Ferlan
-
Peter Krempa