<html>
<head>
</head>
<body class='hmmessage'><div dir='ltr'>

<style><!--
.hmmessage P
{
margin:0px;
padding:0px
}
body.hmmessage
{
font-size: 12pt;
font-family:Calibri
}
--></style>
<div dir="ltr"><font size="3" face="Times New Roman">Please see inline.<br id="FontBreak"></font> <div>> Date: Sun, 9 Sep 2012 19:39:34 +0200<br>> Subject: Re: [libvirt] [PATCH] Refactor ESX storage driver and add iSCSI support<br>> From: matthias.bolte@googlemail.com<br>> To: ata.husain@hotmail.com<br>> CC: libvir-list@redhat.com<br>> <br>> 2012/8/20 Ata E Husain Bohra <ata.husain@hotmail.com>:<br>> > The patch refactors the current ESX storage driver due to following reasons:<br>> ><br>> > 1. Given most of the public APIs exposed by the storage driver in Libvirt<br>> >    remains same, ESX storage driver should not implement logic specific<br>> >    for only one supported format (current implementation only supports VMFS).<br>> > 2. Decoupling interface from specific storage implementation gives us an<br>> >    extensible design to hook implementation for other supported storage<br>> >    formats.<br>> ><br>> > This patch refactors the current driver to implement it as a facade pattern i.e.<br>> > the driver exposes all the public libvirt APIs, but uses backend drivers to get<br>> > the required task done. The backend drivers provide implementation specific to<br>> > the type of storage device.<br>> ><br>> > File changes:<br>> > ------------------<br>> >  esx_storage_driver.c ----> esx_storage_driver.c (base storage driver)<br>> >                       |<br>> >                       |---> esx_storage_backend_vmfs.c (VMFS backend)<br>> >                       |<br>> >                       |---> esx_storage_backend_iscsi.c (iSCSI backend)<br>> ><br>> > The patch adds the backend driver to support iSCSI format storage pools<br>> > and volumes for ESX host. The mapping of ESX iSCSI specifics to Libvirt<br>> > is as follows:<br>> ><br>> > 1. ESX static iSCSI target <------> Libvirt Storage Pools<br>> > 2. ESX iSCSI LUNs          <------> Libvirt Storage Volumes.<br>> ><br>> > The above understanding is based on http://libvirt.org/storage.html.<br>> ><br>> > The operation supported on iSCSI pools includes:<br>> ><br>> > 1. List storage pools & volumes.<br>> > 2. Get xml descriptor operaion on pools & volumes.<br>> > 3. Lookup operation on pools & volumes by name, uuid and path (if applicable).<br>> ><br>> > iSCSI pools does not support operations such as: Create / remove pools<br>> > and volumes<br>> > ---<br>> <br>> Sorry, that it took me so long to start reviewing your work.<br>> <br>> > diff --git a/src/esx/esx_storage_backend_iscsi.c b/src/esx/esx_storage_backend_iscsi.c<br>> > new file mode 100644<br>> > index 0000000..4f79a0f<br>> > --- /dev/null<br>> > +++ b/src/esx/esx_storage_backend_iscsi.c<br>> > @@ -0,0 +1,794 @@<br>> > +/*<br>> > + * esx_storage_backend_iscsi.c: ESX storage backend for iSCSI handling<br>> > + *<br>> > + * Copyright (C) 2007-2008, 2010-2012 Red Hat, Inc.<br>> <br>> I think this is wrong. This is all new code, isn't it? So the<br>> copyright line should read as this<br>> <br>> Copyright (C) 2012 Ata E Husain Bohra <your email address><br>> <br>> > +#include <string.h><br>> > +#include <stdio.h><br>> > +#include <unistd.h><br>> > +#include <sys/stat.h><br>> <br>> You're not using anything from sys/stat.h, so this include can be removed.<br>[AB]: Sure will remove that. </div><div> </div><div>> > +static int<br>> > +esxStorageBackendISCSINumberOfStoragePools(virConnectPtr conn)<br>> > +{<br>> > +    int count = 0;<br>> > +    esxPrivate *priv = conn->storagePrivateData;<br>> > +    esxVI_HostInternetScsiHba *hostInternetScsiHba = NULL;<br>> > +    bool success = false;<br>> > +<br>> > +    if (esxVI_LookupHostInternetScsiHba(<br>> > +          priv->primary, &hostInternetScsiHba) < 0) {<br>> > +        virReportError(VIR_ERR_INTERNAL_ERROR,<br>> > +          _("Unable to obtain iSCSI adapter"));<br>> > +        goto cleanup;<br>> > +    }<br>> > +<br>> > +    if (hostInternetScsiHba == NULL) {<br>> > +        /* iSCSI adapter may not be enabled for this host */<br>> > +        return 0;<br>> > +    }<br>> <br>> This logic suggests that there can only be at most one<br>> HostInternetScsiHba per ESX server. Is that really true?<br>[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 :).</div><div> </div><div>> > +    if (hostInternetScsiHba->configuredStaticTarget) {<br>> > +        const esxVI_HostInternetScsiHbaStaticTarget *target = NULL;<br>> > +        for (target = hostInternetScsiHba->configuredStaticTarget;<br>> > +             target != NULL; target = target->_next) {<br>> > +            ++count;<br>> > +        }<br>> > +    }<br>> <br>> The if around the for loop is not necessary ans can be removed.<br>[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. </div><div><br>> > +static int<br>> > +esxStorageBackendISCSIListStoragePools(virConnectPtr conn,<br>> > +                                       char **const names,<br>> > +                                       const int maxnames)<br>> > +{<br>> <br>> > +    if (hostInternetScsiHba->configuredStaticTarget) {<br>> > +        const esxVI_HostInternetScsiHbaStaticTarget *target = NULL;<br>> > +        for (target = hostInternetScsiHba->configuredStaticTarget;<br>> > +          target != NULL && count < maxnames;<br>> > +          target = target->_next, ++count) {<br>> > +            names[count] = strdup(target->iScsiName);<br>> > +<br>> > +            if (names[count] == NULL) {<br>> > +                virReportOOMError();<br>> > +                goto cleanup;<br>> > +            }<br>> > +        }<br>> > +    }<br>> <br>> The if around the for loop is not necessary ans can be removed.<br>> <br>> > +<br>> > +static virStoragePoolPtr<br>> > +esxStorageBackendISCSIPoolLookupByName(virConnectPtr conn,<br>> > +                                       const char *name)<br>> > +{<br>> > +    esxPrivate *priv = conn->storagePrivateData;<br>> > +    esxVI_HostInternetScsiHbaStaticTarget *target = NULL;<br>> > +    /* MD5_DIGEST_SIZE = VIR_UUID_BUFLEN = 16 */<br>> > +    unsigned char md5[MD5_DIGEST_SIZE];<br>> > +    virStoragePoolPtr pool = NULL;<br>> > +<br>> > +    if (esxVI_LookupHostInternetScsiHbaStaticTargetByName(<br>> > +          priv->primary, name, &target, esxVI_Occurrence_OptionalItem) < 0) {<br>> > +        goto cleanup;<br>> > +    }<br>> > +<br>> > +    /**<br>> > +     * HostInternetScsiHbaStaticTarget does not provide a uuid field,<br>> > +     * but iSsiName (or widely known as IQN) is unique across the multiple<br>> <br>> Typo in 'iSsiName'.<br>> <br>> > +     * hosts, using it to compute key<br>> > +     */<br>> > +<br>> > +    md5_buffer(target->iScsiName, strlen(target->iScsiName), md5);<br>> <br>> esxVI_LookupHostInternetScsiHbaStaticTargetByName has an occurrence<br>> parameter that is set to esxVI_Occurrence_OptionalItem. This means<br>> that esxVI_LookupHostInternetScsiHbaStaticTargetByName is allowed to<br>> return target as NULL. But you're blindly dereferencing it here. I<br>> think occurrence should be passed as esxVI_Occurrence_RequiredItem<br>> here instead.<br>[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.</div><div><br>> > +static virStoragePoolPtr<br>> > +esxStorageBackendISCSIPoolLookupByUUID(virConnectPtr conn,<br>> > +                                       const unsigned char *uuid)<br>> > +{<br>> <br>> > +    if (hostInternetScsiHba->configuredStaticTarget) {<br>> > +        for (target = hostInternetScsiHba->configuredStaticTarget;<br>> > +          target != NULL; target = target->_next) {<br>> > +            md5_buffer(target->iScsiName, strlen(target->iScsiName), md5);<br>> > +<br>> > +            if (memcmp(uuid, md5, VIR_UUID_STRING_BUFLEN) == 0) {<br>> > +                break;<br>> > +            }<br>> > +        }<br>> > +    }<br>> <br>> Again, the if around the for is not necessary here.<br>> <br>> > +    if (target == NULL) {<br>> > +        /* pool not found */<br>> > +        goto cleanup;<br>> > +    }<br>> <br>> Error reporting is missing here.<br>> <br>> > +static int<br>> > +esxStorageBackendISCSIPoolGetInfo(virStoragePoolPtr pool ATTRIBUTE_UNUSED,<br>> > +                                  virStoragePoolInfoPtr info)<br>> > +{<br>> > +    /* these fields are not valid for iSCSI pool */<br>> > +    info->allocation = info->capacity = info->available = 0;<br>> > +    info->state = esxVI_Boolean_True;<br>> <br>> This is wrong. info->state should be VIR_STORAGE_POOL_RUNNING or<br>> another value from the virStoragePoolState if the state can be<br>> determined in more detail.<br></div><div>> > +static char *<br>> > +esxStorageBackendISCSIPoolGetXMLDesc(virStoragePoolPtr pool, unsigned int flags)<br>> > +{<br>> <br>> > +    if (hostInternetScsiHba->configuredStaticTarget) {<br>> > +        for (target = hostInternetScsiHba->configuredStaticTarget;<br>> > +             target != NULL; target = target->_next) {<br>> > +            if (STREQ(target->iScsiName, pool->name)) {<br>> > +                break;<br>> > +            }<br>> > +        }<br>> > +    }<br>> <br>> Again, the if around the for is not necessary here.<br>> <br>> > +    if (target == NULL) {<br>> > +        goto cleanup;<br>> > +    }<br>> <br>> Error reporting is missing here.<br>> <br>> > +static int<br>> > +esxStorageBackendISCSIPoolListStorageVolumes(virStoragePoolPtr pool,<br>> > +                                             char **const names,<br>> > +                                             int maxnames)<br>> > +{<br>> <br>> > +    if (esxVI_LookupScsiLunList(priv->primary, &scsiLunList) < 0) {<br>> > +        goto cleanup;<br>> > +    }<br>> > +<br>> > +    /* O^2 but still faster than hash given N is not that large */<br>> > +    for (scsiLun = scsiLunList; scsiLun != NULL && count < maxnames;<br>> > +         scsiLun = scsiLun->_next) {<br>> > +        for (hostScsiTopologyLun = hostScsiTopologyLunList;<br>> > +             hostScsiTopologyLun != NULL && count < maxnames;<br>> > +             hostScsiTopologyLun = hostScsiTopologyLun->_next) {<br>> > +            if (STREQ(hostScsiTopologyLun->scsiLun, scsiLun->key)) {<br>> > +                names[count] = strdup(scsiLun->deviceName);<br>> <br>> What does a typical device name look like? Is it easily<br>> distinguishable from the volume names of the VMFS backend driver? Can<br>> you give some examples?<br>[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".</div><div> </div><div>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.</div><div><br>> <br>> > +static virStorageVolPtr<br>> > +esxStorageBackendISCSIVolumeLookupByName(virStoragePoolPtr pool,<br>> > +                                         const char *name)<br>> > +{<br>> <br>> > +    for (scsiLun = scsiLunList; scsiLun != NULL;<br>> > +         scsiLun = scsiLun->_next) {<br>> > +        if (STREQ(scsiLun->deviceName, name)) {<br>> > +            /**<br>> > +             * ScsiLun provides an UUID field that is unique accross<br>> > +             * multiple servers. But this field length is ~55 characters<br>> > +             * compute MD5 hash to transform it to an acceptable<br>> > +             * libvirt format<br>> > +             */<br>> > +            md5_buffer(scsiLun->uuid, strlen(scsiLun->uuid), md5);<br>> > +<br>> > +            virUUIDFormat(md5, uuid_string);<br>> <br>> I'm not sure if this is the best approach. What does a typical ScsiLun<br>> look like? Can you give some examples?<br>[AB]: I think you mean sample scssilun->uuid, but I am printing the compelte structure just incase along with specific mention of scsilun->uuid:</div><div> </div><div>(gdb) p *scsiLun<br>$2 = {_next = 0x0, _type = esxVI_Type_HostScsiDisk, <br>  deviceName = 0x682da0 "/vmfs/devices/disks/t10.F405E46494C45425F49615840723D214D476A4D2E457B416", deviceType = 0x704af0 "disk", <br>  lunType = 0x704b70 "disk", operationalState = 0x707570, uuid = 0x6832a0 "01000000004f69514870322d414d674a2d4e754b61564952545541", <br>  alternateName = 0x618660, canonicalName = 0x683160 "t10.F405E46494C45425F49615840723D214D476A4D2E457B416", capabilities = 0x0, <br>  descriptor = 0x0, displayName = 0x0, durableName = 0x61b340, <br>  key = 0x704b10 "key-vim.host.ScsiDisk-01000000004f69514870322d414d674a2d4e754b61564952545541", <br>  model = 0x704bb0 "VIRTUAL-DISK    ", queueDepth = 0x0, revision = 0x704bd0 "0   ", scsiLevel = 0x704bf0, <br>  serialNumber = 0x704c10 "unavailable", standardInquiry = 0x705590, vendor = 0x704b90 "OPNFILER"}<br></div><div>scsilun->uuid looks like:</div><div>(gdb) p scsiLun->uuid<br>$4 = 0x6832a0 "01000000004f69514870322d414d674a2d4e754b61564952545541".</div><div><br>> <br>> > +static virStorageVolPtr<br>> > +esxStorageBackendISCSIVolumeLookupByPath(virConnectPtr conn, const char *path)<br>> > +{<br>> > +    virStorageVolPtr volume = NULL;<br>> > +    esxPrivate *priv = conn->storagePrivateData;<br>> > +    char *poolName = NULL;<br>> > +    esxVI_ScsiLun *scsiLunList = NULL;<br>> > +    const esxVI_ScsiLun *scsiLun = NULL;<br>> > +    const esxVI_HostScsiDisk *hostScsiDisk = NULL;<br>> <br>> Remove the const here...</div><div>[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.<br>> <br>> > +    /* MD5_DIGEST_SIZE = VIR_UUID_BUFLEN = 16 */<br>> > +    unsigned char md5[MD5_DIGEST_SIZE];<br>> > +    char uuid_string[VIR_UUID_STRING_BUFLEN] = "";<br>> > +<br>> > +    if (esxVI_LookupScsiLunList(priv->primary, &scsiLunList) < 0) {<br>> > +        goto cleanup;<br>> > +    }<br>> > +<br>> > +    for (scsiLun = scsiLunList ; scsiLun != NULL;<br>> > +         scsiLun = scsiLun->_next) {<br>> > +         hostScsiDisk =<br>> > +            esxVI_HostScsiDisk_DynamicCast((esxVI_ScsiLun *)scsiLun);<br>> <br>> .. so that the (esxVI_ScsiLun *) cast is no longer necessary.<br>> <br>> > +        if (hostScsiDisk != NULL &&<br>> > +            STREQ(hostScsiDisk->devicePath, path)) {<br>> > +            /* Found matching device */<br>> > +            if (esxVI_LookupStoragePoolNameByScsiLunKey(<br>> > +                  priv->primary, hostScsiDisk->key, &poolName) < 0) {<br>> > +                goto cleanup;<br>> > +            }<br>> > +<br>> > +            md5_buffer(scsiLun->uuid, strlen(scsiLun->uuid), md5);<br>> > +<br>> > +            virUUIDFormat(md5, uuid_string);<br>> > +<br>> > +            volume = virGetStorageVol(conn, poolName, path, uuid_string);<br>> <br>> Passing the path is not correct I think, assuming that the volume name <br>> and path are not the same for iSCSI.<br>[AB]: For user deviceName and devicePath should be same. HostDevice dataobjects defines deviceName as:</div><div><table cellSpacing="0">

<tbody>

<tr class="r1"><td noWrap=""><a id="deviceName" name="deviceName"></a>"<strong>deviceName</strong></td>
<td>xsd:string </td>
<td>
<br>The name of the device on the host. For example, /dev/cdrom or 
\\serverX\device_name. </td></tr>
</tbody>
</table></div><div>"</div><div>As mentioned earlier, sample deviceName looks like:</div><div>"/vmfs/devices/disks/t10.F405E46494C45425F49615840723D214D476A4D2E457B416"; it is a valid ESX device path as well.</div><div> </div><div>I will document it to make it more clear.</div><div><br>> > +virStorageDriver esxStorageBackendISCSIDrv = {<br>> > +    .name = "ESX ISCSI backend",<br>> > +    .open = NULL, /* 0.10.0 */<br>> > +    .close = NULL, /* 0.10.0 */<br>> > +    .numOfPools = esxStorageBackendISCSINumberOfStoragePools, /* 0.10.0 */<br>> > +    .listPools = esxStorageBackendISCSIListStoragePools, /* 0.10.0 */<br>> > +    .poolLookupByName = esxStorageBackendISCSIPoolLookupByName, /* 0.10.0 */<br>> > +    .poolLookupByUUID = esxStorageBackendISCSIPoolLookupByUUID, /* 0.10.0 */<br>> > +    .poolRefresh = esxStorageBackendISCSIPoolRefresh, /* 0.10.0 */<br>> > +    .poolGetInfo = esxStorageBackendISCSIPoolGetInfo, /* 0.10.0 */<br>> > +    .poolGetXMLDesc = esxStorageBackendISCSIPoolGetXMLDesc, /* 0.10.0 */<br>> > +    .poolNumOfVolumes = esxStorageBackendISCSIPoolNumberOfStorageVolumes, /* 0.10.0 */<br>> > +    .poolListVolumes = esxStorageBackendISCSIPoolListStorageVolumes, /* 0.10.0 */<br>> > +    .volLookupByName = esxStorageBackendISCSIVolumeLookupByName, /* 0.10.0 */<br>> > +    .volLookupByKey = esxStorageBackendISCSIVolumeLookupByKey, /* 0.10.0 */<br>> > +    .volLookupByPath = esxStorageBackendISCSIVolumeLookupByPath, /* 0.10.0 */<br>> > +    .volCreateXML = esxStorageBackendISCSIVolumeCreateXML, /* 0.10.0 */<br>> > +    .volCreateXMLFrom = esxStorageBackendISCSIVolumeCreateXMLFrom, /* 0.10.0 */<br>> > +    .volGetXMLDesc = esxStorageBackendISCSIVolumeGetXMLDesc, /* 0.10.0 */<br>> > +    .volDelete = esxStorageBackendISCSIVolumeDelete, /* 0.10.0 */<br>> > +    .volWipe = esxStorageBackendISCSIVolumeWipe, /* 0.10.0 */<br>> > +    .volGetPath = esxStorageBackendISCSIVolumeGetPath, /* 0.10.0 */<br>> > +<br>> > +};<br>> <br>> The version number here are outdated now. It should be 0.10.2.<br>> <br>> > diff --git a/src/esx/esx_storage_backend_iscsi.h b/src/esx/esx_storage_backend_iscsi.h<br>> > new file mode 100644<br>> > index 0000000..ca756ac<br>> > --- /dev/null<br>> > +++ b/src/esx/esx_storage_backend_iscsi.h<br>> > @@ -0,0 +1,31 @@<br>> > +/*<br>> > + * esx_storage_backend_iscsi.h: ESX storage backend for iSCSI handling<br>> > + *<br>> > + * Copyright (C) 2007-2008 Red Hat, Inc.<br>> <br>> Again, that's not the right copyright notice.<br>> <br>> > diff --git a/src/esx/esx_storage_backend_vmfs.c b/src/esx/esx_storage_backend_vmfs.c<br>> > new file mode 100644<br>> > index 0000000..6550196<br>> > --- /dev/null<br>> > +++ b/src/esx/esx_storage_backend_vmfs.c<br>> > @@ -0,0 +1,1471 @@<br>> > +<br>> > +/*<br>> > + * esx_storage_backend_vmfs.c: ESX storage backend for VMFS datastores<br>> > + *<br>> > + * Copyright (C) 2010-2011 Red Hat, Inc.<br>> > + * Copyright (C) 2010 Matthias Bolte <matthias.bolte@googlemail.com><br>> <br>> Put your copyright line here in the form of<br>[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 :).<br>> Copyright (C) 2012 Ata E Husain Bohra <your email address><br>> <br>> instead of an author list on the end of the license statement.<br>> <br>> > +#include <string.h><br>> > +#include <stdio.h><br>> > +#include <unistd.h><br>> > +#include <sys/stat.h><br>> <br>> This include is unused and can be removed.<br>> <br>> <br>> This is as far as I got today with the review. I'll continue in the<br>> next days, hopefully it won't take me weeks again to get back to this<br>> :)<br>[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.<br>> -- <br>> Matthias Bolte<br>> <a href="http://photron.blogspot.com">http://photron.blogspot.com</a><br></div><div>Thanks again!</div><div> </div><div>Regards,</div><div>Ata</div></div>
                                          </div></body>
</html>