
Please see inline. > Date: Sun, 9 Sep 2012 19:39:34 +0200
Subject: Re: [libvirt] [PATCH] Refactor ESX storage driver and add iSCSI support From: matthias.bolte@googlemail.com To: ata.husain@hotmail.com CC: libvir-list@redhat.com
2012/8/20 Ata E Husain Bohra <ata.husain@hotmail.com>:
The patch refactors the current ESX storage driver due to following reasons:
1. Given most of the public APIs exposed by the storage driver in Libvirt remains same, ESX storage driver should not implement logic specific for only one supported format (current implementation only supports VMFS). 2. Decoupling interface from specific storage implementation gives us an extensible design to hook implementation for other supported storage formats.
This patch refactors the current driver to implement it as a facade pattern i.e. the driver exposes all the public libvirt APIs, but uses backend drivers to get the required task done. The backend drivers provide implementation specific to the type of storage device.
File changes: ------------------ esx_storage_driver.c ----> esx_storage_driver.c (base storage driver) | |---> esx_storage_backend_vmfs.c (VMFS backend) | |---> esx_storage_backend_iscsi.c (iSCSI backend)
The patch adds the backend driver to support iSCSI format storage pools and volumes for ESX host. The mapping of ESX iSCSI specifics to Libvirt is as follows:
1. ESX static iSCSI target <------> Libvirt Storage Pools 2. ESX iSCSI LUNs <------> Libvirt Storage Volumes.
The above understanding is based on http://libvirt.org/storage.html.
The operation supported on iSCSI pools includes:
1. List storage pools & volumes. 2. Get xml descriptor operaion on pools & volumes. 3. Lookup operation on pools & volumes by name, uuid and path (if applicable).
iSCSI pools does not support operations such as: Create / remove pools and volumes ---
Sorry, that it took me so long to start reviewing your work.
diff --git a/src/esx/esx_storage_backend_iscsi.c b/src/esx/esx_storage_backend_iscsi.c new file mode 100644 index 0000000..4f79a0f --- /dev/null +++ b/src/esx/esx_storage_backend_iscsi.c @@ -0,0 +1,794 @@ +/* + * esx_storage_backend_iscsi.c: ESX storage backend for iSCSI handling + * + * Copyright (C) 2007-2008, 2010-2012 Red Hat, Inc.
I think this is wrong. This is all new code, isn't it? So the copyright line should read as this
Copyright (C) 2012 Ata E Husain Bohra <your email address>
+#include <string.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/stat.h>
You're not using anything from sys/stat.h, so this include can be removed. [AB]: Sure will remove that. > > +static int
+esxStorageBackendISCSINumberOfStoragePools(virConnectPtr conn) +{ + int count = 0; + esxPrivate *priv = conn->storagePrivateData; + esxVI_HostInternetScsiHba *hostInternetScsiHba = NULL; + bool success = false; + + if (esxVI_LookupHostInternetScsiHba( + priv->primary, &hostInternetScsiHba) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unable to obtain iSCSI adapter")); + goto cleanup; + } + + if (hostInternetScsiHba == NULL) { + /* iSCSI adapter may not be enabled for this host */ + return 0; + }
This logic suggests that there can only be at most one HostInternetScsiHba per ESX server. Is that really true? [AB]: The proposed solution works with software iSCSI adapter, there is only one software iSCSI adapter per ESX host. Thanks for pointing this out, I wanted to add these comments but missed it :). > > + if (hostInternetScsiHba->configuredStaticTarget) {
+ const esxVI_HostInternetScsiHbaStaticTarget *target = NULL; + for (target = hostInternetScsiHba->configuredStaticTarget; + target != NULL; target = target->_next) { + ++count; + } + }
The if around the for loop is not necessary ans can be removed. [AB]: I guess I am spoiled with the coding standards followed at my workplace; we try to protect conditional loops with parenthesis even if its a single line code statements. I will make necessary correction if this is not acceptable style with Libvirt community.
+static int +esxStorageBackendISCSIListStoragePools(virConnectPtr conn, + char **const names, + const int maxnames) +{
+ if (hostInternetScsiHba->configuredStaticTarget) { + const esxVI_HostInternetScsiHbaStaticTarget *target = NULL; + for (target = hostInternetScsiHba->configuredStaticTarget; + target != NULL && count < maxnames; + target = target->_next, ++count) { + names[count] = strdup(target->iScsiName); + + if (names[count] == NULL) { + virReportOOMError(); + goto cleanup; + } + } + }
The if around the for loop is not necessary ans can be removed.
+ +static virStoragePoolPtr +esxStorageBackendISCSIPoolLookupByName(virConnectPtr conn, + const char *name) +{ + esxPrivate *priv = conn->storagePrivateData; + esxVI_HostInternetScsiHbaStaticTarget *target = NULL; + /* MD5_DIGEST_SIZE = VIR_UUID_BUFLEN = 16 */ + unsigned char md5[MD5_DIGEST_SIZE]; + virStoragePoolPtr pool = NULL; + + if (esxVI_LookupHostInternetScsiHbaStaticTargetByName( + priv->primary, name, &target, esxVI_Occurrence_OptionalItem) < 0) { + goto cleanup; + } + + /** + * HostInternetScsiHbaStaticTarget does not provide a uuid field, + * but iSsiName (or widely known as IQN) is unique across the multiple
Typo in 'iSsiName'.
+ * hosts, using it to compute key + */ + + md5_buffer(target->iScsiName, strlen(target->iScsiName), md5);
esxVI_LookupHostInternetScsiHbaStaticTargetByName has an occurrence parameter that is set to esxVI_Occurrence_OptionalItem. This means that esxVI_LookupHostInternetScsiHbaStaticTargetByName is allowed to return target as NULL. But you're blindly dereferencing it here. I think occurrence should be passed as esxVI_Occurrence_RequiredItem here instead. [AB]: "poolLookupByName" is also used by esx_storage_drive.c:esxStorageGetBackendDriver(), in this function we want to go over both VMFS as well iSCSI pools list to determine the matching backend driver. I think the flaw is I need to check for valid target pointer (NON-NULL) before deferencing it.
+static virStoragePoolPtr +esxStorageBackendISCSIPoolLookupByUUID(virConnectPtr conn, + const unsigned char *uuid) +{
+ if (hostInternetScsiHba->configuredStaticTarget) { + for (target = hostInternetScsiHba->configuredStaticTarget; + target != NULL; target = target->_next) { + md5_buffer(target->iScsiName, strlen(target->iScsiName), md5); + + if (memcmp(uuid, md5, VIR_UUID_STRING_BUFLEN) == 0) { + break; + } + } + }
Again, the if around the for is not necessary here.
+ if (target == NULL) { + /* pool not found */ + goto cleanup; + }
Error reporting is missing here.
+static int +esxStorageBackendISCSIPoolGetInfo(virStoragePoolPtr pool ATTRIBUTE_UNUSED, + virStoragePoolInfoPtr info) +{ + /* these fields are not valid for iSCSI pool */ + info->allocation = info->capacity = info->available = 0; + info->state = esxVI_Boolean_True;
This is wrong. info->state should be VIR_STORAGE_POOL_RUNNING or another value from the virStoragePoolState if the state can be determined in more detail.
+static char * +esxStorageBackendISCSIPoolGetXMLDesc(virStoragePoolPtr pool, unsigned int flags) +{
+ if (hostInternetScsiHba->configuredStaticTarget) { + for (target = hostInternetScsiHba->configuredStaticTarget; + target != NULL; target = target->_next) { + if (STREQ(target->iScsiName, pool->name)) { + break; + } + } + }
Again, the if around the for is not necessary here.
+ if (target == NULL) { + goto cleanup; + }
Error reporting is missing here.
+static int +esxStorageBackendISCSIPoolListStorageVolumes(virStoragePoolPtr pool, + char **const names, + int maxnames) +{
+ if (esxVI_LookupScsiLunList(priv->primary, &scsiLunList) < 0) { + goto cleanup; + } + + /* O^2 but still faster than hash given N is not that large */ + for (scsiLun = scsiLunList; scsiLun != NULL && count < maxnames; + scsiLun = scsiLun->_next) { + for (hostScsiTopologyLun = hostScsiTopologyLunList; + hostScsiTopologyLun != NULL && count < maxnames; + hostScsiTopologyLun = hostScsiTopologyLun->_next) { + if (STREQ(hostScsiTopologyLun->scsiLun, scsiLun->key)) { + names[count] = strdup(scsiLun->deviceName);
What does a typical device name look like? Is it easily distinguishable from the volume names of the VMFS backend driver? Can you give some examples? [AB]: Device name looks similar to a device path in linux, for instance on my sample machine device path for a iSCSI lun is "/vmfs/devices/disks/t10.F405E46494C45425F49615840723D214D476A4D2E457B416". As mentioned in one of my comments, technically we can use "scsiLun->canonicalName" as "t10.F405E46494C45425F49615840723D214D476A4D2E457B416" is nothing but a canonicalName for that iSCSI lun. Only reason I used deviceName was it is a mandatory field, whereas, canonicalName is an options field inside scsiLun data object.
+static virStorageVolPtr +esxStorageBackendISCSIVolumeLookupByName(virStoragePoolPtr pool, + const char *name) +{
+ for (scsiLun = scsiLunList; scsiLun != NULL; + scsiLun = scsiLun->_next) { + if (STREQ(scsiLun->deviceName, name)) { + /** + * ScsiLun provides an UUID field that is unique accross + * multiple servers. But this field length is ~55 characters + * compute MD5 hash to transform it to an acceptable + * libvirt format + */ + md5_buffer(scsiLun->uuid, strlen(scsiLun->uuid), md5); + + virUUIDFormat(md5, uuid_string);
I'm not sure if this is the best approach. What does a typical ScsiLun look like? Can you give some examples? [AB]: I think you mean sample scssilun->uuid, but I am printing the compelte structure just incase along with specific mention of scsilun->uuid: (gdb) p *scsiLun $2 = {_next = 0x0, _type = esxVI_Type_HostScsiDisk, deviceName = 0x682da0 "/vmfs/devices/disks/t10.F405E46494C45425F49615840723D214D476A4D2E457B416", deviceType = 0x704af0 "disk", lunType = 0x704b70 "disk", operationalState = 0x707570, uuid = 0x6832a0 "01000000004f69514870322d414d674a2d4e754b61564952545541", alternateName = 0x618660, canonicalName = 0x683160 "t10.F405E46494C45425F49615840723D214D476A4D2E457B416", capabilities = 0x0, descriptor = 0x0, displayName = 0x0, durableName = 0x61b340, key = 0x704b10 "key-vim.host.ScsiDisk-01000000004f69514870322d414d674a2d4e754b61564952545541", model = 0x704bb0 "VIRTUAL-DISK ", queueDepth = 0x0, revision = 0x704bd0 "0 ", scsiLevel = 0x704bf0, serialNumber = 0x704c10 "unavailable", standardInquiry = 0x705590, vendor = 0x704b90 "OPNFILER"} scsilun->uuid looks like:(gdb) p scsiLun->uuid $4 = 0x6832a0 "01000000004f69514870322d414d674a2d4e754b61564952545541".
+static virStorageVolPtr +esxStorageBackendISCSIVolumeLookupByPath(virConnectPtr conn, const char *path) +{ + virStorageVolPtr volume = NULL; + esxPrivate *priv = conn->storagePrivateData; + char *poolName = NULL; + esxVI_ScsiLun *scsiLunList = NULL; + const esxVI_ScsiLun *scsiLun = NULL; + const esxVI_HostScsiDisk *hostScsiDisk = NULL;
Remove the const here...[AB]: As the variable is used just to iterate the list IMHO "const" assures the reader that the memory need not to be freed. I aggree there is an extra static_cast needed to perform "dynamic_cast" operation but I thought code readibility will surpass ths cost. Please let me know if this can be accepted.
+ /* MD5_DIGEST_SIZE = VIR_UUID_BUFLEN = 16 */ + unsigned char md5[MD5_DIGEST_SIZE]; + char uuid_string[VIR_UUID_STRING_BUFLEN] = ""; + + if (esxVI_LookupScsiLunList(priv->primary, &scsiLunList) < 0) { + goto cleanup; + } + + for (scsiLun = scsiLunList ; scsiLun != NULL; + scsiLun = scsiLun->_next) { + hostScsiDisk = + esxVI_HostScsiDisk_DynamicCast((esxVI_ScsiLun *)scsiLun);
.. so that the (esxVI_ScsiLun *) cast is no longer necessary.
+ if (hostScsiDisk != NULL && + STREQ(hostScsiDisk->devicePath, path)) { + /* Found matching device */ + if (esxVI_LookupStoragePoolNameByScsiLunKey( + priv->primary, hostScsiDisk->key, &poolName) < 0) { + goto cleanup; + } + + md5_buffer(scsiLun->uuid, strlen(scsiLun->uuid), md5); + + virUUIDFormat(md5, uuid_string); + + volume = virGetStorageVol(conn, poolName, path, uuid_string);
Passing the path is not correct I think, assuming that the volume name and path are not the same for iSCSI. [AB]: For user deviceName and devicePath should be same. HostDevice dataobjects defines deviceName as:
"deviceName xsd:string The name of the device on the host. For example, /dev/cdrom or \\serverX\device_name. "As mentioned earlier, sample deviceName looks like:"/vmfs/devices/disks/t10.F405E46494C45425F49615840723D214D476A4D2E457B416"; it is a valid ESX device path as well. I will document it to make it more clear.
+virStorageDriver esxStorageBackendISCSIDrv = { + .name = "ESX ISCSI backend", + .open = NULL, /* 0.10.0 */ + .close = NULL, /* 0.10.0 */ + .numOfPools = esxStorageBackendISCSINumberOfStoragePools, /* 0.10.0 */ + .listPools = esxStorageBackendISCSIListStoragePools, /* 0.10.0 */ + .poolLookupByName = esxStorageBackendISCSIPoolLookupByName, /* 0.10.0 */ + .poolLookupByUUID = esxStorageBackendISCSIPoolLookupByUUID, /* 0.10.0 */ + .poolRefresh = esxStorageBackendISCSIPoolRefresh, /* 0.10.0 */ + .poolGetInfo = esxStorageBackendISCSIPoolGetInfo, /* 0.10.0 */ + .poolGetXMLDesc = esxStorageBackendISCSIPoolGetXMLDesc, /* 0.10.0 */ + .poolNumOfVolumes = esxStorageBackendISCSIPoolNumberOfStorageVolumes, /* 0.10.0 */ + .poolListVolumes = esxStorageBackendISCSIPoolListStorageVolumes, /* 0.10.0 */ + .volLookupByName = esxStorageBackendISCSIVolumeLookupByName, /* 0.10.0 */ + .volLookupByKey = esxStorageBackendISCSIVolumeLookupByKey, /* 0.10.0 */ + .volLookupByPath = esxStorageBackendISCSIVolumeLookupByPath, /* 0.10.0 */ + .volCreateXML = esxStorageBackendISCSIVolumeCreateXML, /* 0.10.0 */ + .volCreateXMLFrom = esxStorageBackendISCSIVolumeCreateXMLFrom, /* 0.10.0 */ + .volGetXMLDesc = esxStorageBackendISCSIVolumeGetXMLDesc, /* 0.10.0 */ + .volDelete = esxStorageBackendISCSIVolumeDelete, /* 0.10.0 */ + .volWipe = esxStorageBackendISCSIVolumeWipe, /* 0.10.0 */ + .volGetPath = esxStorageBackendISCSIVolumeGetPath, /* 0.10.0 */ + +};
The version number here are outdated now. It should be 0.10.2.
diff --git a/src/esx/esx_storage_backend_iscsi.h b/src/esx/esx_storage_backend_iscsi.h new file mode 100644 index 0000000..ca756ac --- /dev/null +++ b/src/esx/esx_storage_backend_iscsi.h @@ -0,0 +1,31 @@ +/* + * esx_storage_backend_iscsi.h: ESX storage backend for iSCSI handling + * + * Copyright (C) 2007-2008 Red Hat, Inc.
Again, that's not the right copyright notice.
diff --git a/src/esx/esx_storage_backend_vmfs.c b/src/esx/esx_storage_backend_vmfs.c new file mode 100644 index 0000000..6550196 --- /dev/null +++ b/src/esx/esx_storage_backend_vmfs.c @@ -0,0 +1,1471 @@ + +/* + * esx_storage_backend_vmfs.c: ESX storage backend for VMFS datastores + * + * Copyright (C) 2010-2011 Red Hat, Inc. + * Copyright (C) 2010 Matthias Bolte <matthias.bolte@googlemail.com>
Put your copyright line here in the form of [AB]: I intentionally left it "unchanged". Almost all the code written in this file is written by you, I just renamed the file and I do not want to put myself as an author just for renaming the filename :). Copyright (C) 2012 Ata E Husain Bohra <your email address>
instead of an author list on the end of the license statement.
+#include <string.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/stat.h>
This include is unused and can be removed.
This is as far as I got today with the review. I'll continue in the next days, hopefully it won't take me weeks again to get back to this :) [AB]: I highly appreciate your attention and time on my patches. I'm looking forward for your comments on above mentioned points before updating a newer version for the patch. -- Matthias Bolte http://photron.blogspot.com Thanks again! Regards,Ata