/* * esx_storage_backend_iscsi.c: ESX storage backend for iSCSI handling * * Copyright (C) 2012 Ata E Husain Bohra * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see * . * */ #include #include #include #include #include "internal.h" #include "md5.h" #include "viralloc.h" #include "virlog.h" #include "viruuid.h" #include "storage_conf.h" #include "virstoragefile.h" #include "esx_storage_backend_iscsi.h" #include "esx_private.h" #include "esx_vi.h" #include "esx_vi_methods.h" #include "esx_util.h" #include "virstring.h" #define VIR_FROM_THIS VIR_FROM_ESX /* * The UUID of a storage pool is the MD5 sum of it's mount path. Therefore, * verify that UUID and MD5 sum match in size, because we rely on that. */ verify(MD5_DIGEST_SIZE == VIR_UUID_BUFLEN); static int esxConnectNumOfStoragePools(virConnectPtr conn) { bool success = false; int count = 0; esxPrivate *priv = conn->storagePrivateData; esxVI_HostInternetScsiHba *hostInternetScsiHba = NULL; esxVI_HostInternetScsiHbaStaticTarget *target; if (esxVI_LookupHostInternetScsiHba(priv->primary, &hostInternetScsiHba) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Unable to obtain iSCSI adapter")); goto cleanup; } /* FIXME: code looks for software iSCSI adapter only */ if (hostInternetScsiHba == NULL) { /* iSCSI adapter may not be enabled for this host */ return 0; } /* * ESX has two kind of targets: * 1. staticIscsiTargets * 2. dynamicIscsiTargets * For each dynamic target if its reachable a static target is added. * return iSCSI names for all static targets to avoid duplicate names. */ for (target = hostInternetScsiHba->configuredStaticTarget; target != NULL; target = target->_next) { ++count; } success = true; cleanup: esxVI_HostInternetScsiHba_Free(&hostInternetScsiHba); return success ? count : -1; } static int esxConnectListStoragePools(virConnectPtr conn, char **const names, const int maxnames) { bool success = false; int count = 0; esxPrivate *priv = conn->storagePrivateData; esxVI_HostInternetScsiHba *hostInternetScsiHba = NULL; esxVI_HostInternetScsiHbaStaticTarget *target; int i; if (maxnames == 0) { return 0; } if (esxVI_LookupHostInternetScsiHba(priv->primary, &hostInternetScsiHba) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Unable to obtain iSCSI adapter")); goto cleanup; } /* FIXME: code looks for software iSCSI adapter only */ if (hostInternetScsiHba == NULL) { /* iSCSI adapter may not be enabled for this host */ return 0; } /* * ESX has two kind of targets: * 1. staticIscsiTargets * 2. dynamicIscsiTargets * For each dynamic target if its reachable a static target is added. * return iSCSI names for all static targets to avoid duplicate names. */ for (target = hostInternetScsiHba->configuredStaticTarget; target != NULL && count < maxnames; target = target->_next) { if (VIR_STRDUP(names[count], target->iScsiName) < 0) goto cleanup; ++count; } success = true; cleanup: if (! success) { for (i = 0; i < count; ++i) { VIR_FREE(names[i]); } } esxVI_HostInternetScsiHba_Free(&hostInternetScsiHba); return success ? count : -1; } static virStoragePoolPtr esxStoragePoolLookupByName(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; /* * Lookup routine are used by the base driver to determine * appropriate backend driver, lookup targetName as optional * parameter */ if (esxVI_LookupHostInternetScsiHbaStaticTargetByName (priv->primary, name, &target, esxVI_Occurrence_OptionalItem) < 0) { goto cleanup; } if (target == NULL) { /* pool not found, error handling done by the base driver */ goto cleanup; } /* * HostInternetScsiHbaStaticTarget does not provide a uuid field, * but iScsiName (or widely known as IQN) is unique across the multiple * hosts, using it to compute key */ md5_buffer(target->iScsiName, strlen(target->iScsiName), md5); pool = virGetStoragePool(conn, name, md5, &esxStorageBackendISCSI, NULL); cleanup: esxVI_HostInternetScsiHbaStaticTarget_Free(&target); return pool; } static virStoragePoolPtr esxStoragePoolLookupByUUID(virConnectPtr conn, const unsigned char *uuid) { virStoragePoolPtr pool = NULL; esxPrivate *priv = conn->storagePrivateData; esxVI_HostInternetScsiHba *hostInternetScsiHba = NULL; esxVI_HostInternetScsiHbaStaticTarget *target; /* MD5_DIGEST_SIZE = VIR_UUID_BUFLEN = 16 */ unsigned char md5[MD5_DIGEST_SIZE]; if (esxVI_LookupHostInternetScsiHba(priv->primary, &hostInternetScsiHba) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Unable to obtain iSCSI adapter")); goto cleanup; } /* FIXME: code just looks for software iSCSI adapter */ if (hostInternetScsiHba == NULL) { /* iSCSI adapter may not be enabled for this host */ return NULL; } 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; } } if (target == NULL) { /* pool not found, error handling done by the base driver */ goto cleanup; } pool = virGetStoragePool(conn, target->iScsiName, md5, &esxStorageBackendISCSI, NULL); cleanup: esxVI_HostInternetScsiHba_Free(&hostInternetScsiHba); return pool; } static int esxStoragePoolRefresh(virStoragePoolPtr pool, unsigned int flags) { int result = -1; esxPrivate *priv = pool->conn->storagePrivateData; esxVI_HostInternetScsiHba *hostInternetScsiHba = NULL; virCheckFlags(0, -1); if (esxVI_LookupHostInternetScsiHba(priv->primary, &hostInternetScsiHba) < 0) { goto cleanup; } /* * ESX does not allow rescan on a particular target, * rescan all the static targets */ if (esxVI_RescanHba(priv->primary, priv->primary->hostSystem->configManager->storageSystem, hostInternetScsiHba->device) < 0) { goto cleanup; } result = 0; cleanup: esxVI_HostInternetScsiHba_Free(&hostInternetScsiHba); return result; } static int esxStoragePoolGetInfo(virStoragePoolPtr pool ATTRIBUTE_UNUSED, virStoragePoolInfoPtr info) { /* These fields are not valid for iSCSI pool */ info->allocation = info->capacity = info->available = 0; info->state = VIR_STORAGE_POOL_RUNNING; return 0; } static char * esxStoragePoolGetXMLDesc(virStoragePoolPtr pool, unsigned int flags) { char *xml = NULL; esxPrivate *priv = pool->conn->storagePrivateData; esxVI_HostInternetScsiHba *hostInternetScsiHba = NULL; esxVI_HostInternetScsiHbaStaticTarget *target; virStoragePoolDef def; virCheckFlags(0, NULL); memset(&def, 0, sizeof(def)); if (esxVI_LookupHostInternetScsiHba(priv->primary, &hostInternetScsiHba)) { goto cleanup; } for (target = hostInternetScsiHba->configuredStaticTarget; target != NULL; target = target->_next) { if (STREQ(target->iScsiName, pool->name)) { break; } } if (target == NULL) { /* pool not found */ virReportError(VIR_ERR_INTERNAL_ERROR, _("Could not find storage pool with name '%s'"), pool->name); goto cleanup; } def.name = pool->name; memcpy(def.uuid, pool->uuid, VIR_UUID_BUFLEN); def.type = VIR_STORAGE_POOL_ISCSI; def.source.initiator.iqn = target->iScsiName; def.source.nhost = 1; if (VIR_ALLOC_N(def.source.hosts, def.source.nhost) < 0) goto cleanup; def.source.hosts[0].name = target->address; if (target->port != NULL) { def.source.hosts[0].port = target->port->value; } /* TODO: add CHAP authentication params */ xml = virStoragePoolDefFormat(&def); cleanup: VIR_FREE(def.source.hosts); esxVI_HostInternetScsiHba_Free(&hostInternetScsiHba); return xml; } static int esxStoragePoolNumOfVolumes(virStoragePoolPtr pool) { int count = 0; esxPrivate *priv = pool->conn->storagePrivateData; esxVI_HostScsiTopologyLun *hostScsiTopologyLunList = NULL; esxVI_HostScsiTopologyLun *hostScsiTopologyLun; if (esxVI_LookupHostScsiTopologyLunListByTargetName (priv->primary, pool->name, &hostScsiTopologyLunList) < 0) { return -1; } for (hostScsiTopologyLun = hostScsiTopologyLunList; hostScsiTopologyLun != NULL; hostScsiTopologyLun = hostScsiTopologyLun->_next) { ++count; } esxVI_HostScsiTopologyLun_Free(&hostScsiTopologyLunList); return count; } static int esxStoragePoolListVolumes(virStoragePoolPtr pool, char **const names, int maxnames) { bool success = false; int count = 0; esxPrivate *priv = pool->conn->storagePrivateData; esxVI_HostScsiTopologyLun *hostScsiTopologyLunList = NULL; esxVI_HostScsiTopologyLun *hostScsiTopologyLun; esxVI_ScsiLun *scsiLunList = NULL; esxVI_ScsiLun *scsiLun = NULL; int i; if (esxVI_LookupHostScsiTopologyLunListByTargetName (priv->primary, pool->name, &hostScsiTopologyLunList) < 0) { goto cleanup; } if (hostScsiTopologyLunList == NULL) { /* iSCSI adapter may not be enabled on ESX host */ return 0; } if (esxVI_LookupScsiLunList(priv->primary, &scsiLunList) < 0) { goto cleanup; } 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)) { if (VIR_STRDUP(names[count], scsiLun->deviceName) < 0) goto cleanup; ++count; } } } success = true; cleanup: if (! success) { for (i = 0; i < count; ++i) { VIR_FREE(names[i]); } count = -1; } esxVI_HostScsiTopologyLun_Free(&hostScsiTopologyLunList); esxVI_ScsiLun_Free(&scsiLunList); return count; } static virStorageVolPtr esxStorageVolLookupByName(virStoragePoolPtr pool, const char *name) { virStorageVolPtr volume = NULL; esxPrivate *priv = pool->conn->storagePrivateData; esxVI_ScsiLun *scsiLunList = NULL; esxVI_ScsiLun *scsiLun; /* 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) { 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); /* * ScsiLun provides displayName and canonicalName but both are * optional and its observed that they can be NULL, using * deviceName to create volume. */ volume = virGetStorageVol(pool->conn, pool->name, name, uuid_string, &esxStorageBackendISCSI, NULL); break; } } cleanup: esxVI_ScsiLun_Free(&scsiLunList); return volume; } static virStorageVolPtr esxStorageVolLookupByPath(virConnectPtr conn, const char *path) { virStorageVolPtr volume = NULL; esxPrivate *priv = conn->storagePrivateData; esxVI_ScsiLun *scsiLunList = NULL; esxVI_ScsiLun *scsiLun; esxVI_HostScsiDisk *hostScsiDisk = NULL; char *poolName = NULL; /* 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(scsiLun); if (hostScsiDisk != NULL && STREQ(hostScsiDisk->devicePath, path)) { /* Found matching device */ VIR_FREE(poolName); 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, &esxStorageBackendISCSI, NULL); break; } } cleanup: esxVI_ScsiLun_Free(&scsiLunList); VIR_FREE(poolName); return volume; } static virStorageVolPtr esxStorageVolLookupByKey(virConnectPtr conn, const char *key) { virStorageVolPtr volume = NULL; esxPrivate *priv = conn->storagePrivateData; char *poolName = NULL; esxVI_ScsiLun *scsiLunList = NULL; esxVI_ScsiLun *scsiLun; /* MD5_DIGEST_SIZE = VIR_UUID_BUFLEN = 16 */ unsigned char md5[MD5_DIGEST_SIZE]; char uuid_string[VIR_UUID_STRING_BUFLEN] = ""; /* key may be LUN device path */ if (STRPREFIX(key, "/")) { return esxStorageVolLookupByPath(conn, key); } if (esxVI_LookupScsiLunList(priv->primary, &scsiLunList) < 0) { goto cleanup; } for (scsiLun = scsiLunList; scsiLun != NULL; scsiLun = scsiLun->_next) { memset(uuid_string, '\0', sizeof(uuid_string)); memset(md5, '\0', sizeof(md5)); md5_buffer(scsiLun->uuid, strlen(scsiLun->uuid), md5); virUUIDFormat(md5, uuid_string); if (STREQ(key, uuid_string)) { /* Found matching UUID */ VIR_FREE(poolName); if (esxVI_LookupStoragePoolNameByScsiLunKey(priv->primary, scsiLun->key, &poolName) < 0) { goto cleanup; } volume = virGetStorageVol(conn, poolName, scsiLun->deviceName, uuid_string, &esxStorageBackendISCSI, NULL); break; } } cleanup: esxVI_ScsiLun_Free(&scsiLunList); VIR_FREE(poolName); return volume; } static virStorageVolPtr esxStorageVolCreateXML(virStoragePoolPtr pool ATTRIBUTE_UNUSED, const char *xmldesc ATTRIBUTE_UNUSED, unsigned int flags) { virCheckFlags(0, NULL); virReportError(VIR_ERR_NO_SUPPORT, "%s", _("iSCSI storage pool does not support volume creation")); return NULL; } static virStorageVolPtr esxStorageVolCreateXMLFrom(virStoragePoolPtr pool ATTRIBUTE_UNUSED, const char *xmldesc ATTRIBUTE_UNUSED, virStorageVolPtr sourceVolume ATTRIBUTE_UNUSED, unsigned int flags) { virCheckFlags(0, NULL); virReportError(VIR_ERR_NO_SUPPORT, "%s", _("iSCSI storage pool does not support volume creation")); return NULL; } static char * esxStorageVolGetXMLDesc(virStorageVolPtr volume, unsigned int flags) { char *xml = NULL; esxPrivate *priv = volume->conn->storagePrivateData; virStoragePoolDef pool; esxVI_ScsiLun *scsiLunList = NULL; esxVI_ScsiLun *scsiLun; esxVI_HostScsiDisk *hostScsiDisk = NULL; virStorageVolDef def; /* MD5_DIGEST_SIZE = VIR_UUID_BUFLEN = 16 */ unsigned char md5[MD5_DIGEST_SIZE]; char uuid_string[VIR_UUID_STRING_BUFLEN] = ""; virCheckFlags(0, NULL); memset(&pool, 0, sizeof(pool)); memset(&def, 0, sizeof(def)); if (esxVI_LookupScsiLunList(priv->primary, &scsiLunList) < 0) { goto cleanup; } for (scsiLun = scsiLunList; scsiLun != NULL; scsiLun = scsiLun->_next) { hostScsiDisk = esxVI_HostScsiDisk_DynamicCast(scsiLun); if (hostScsiDisk != NULL && STREQ(hostScsiDisk->deviceName, volume->name)) { break; } } if (hostScsiDisk == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Could find volume with name: %s"), volume->name); goto cleanup; } pool.type = VIR_STORAGE_POOL_ISCSI; def.name = volume->name; md5_buffer(scsiLun->uuid, strlen(hostScsiDisk->uuid), md5); virUUIDFormat(md5, uuid_string); if (VIR_STRDUP(def.key, uuid_string) < 0) { goto cleanup; } /* iSCSI LUN exposes a block device */ def.type = VIR_STORAGE_VOL_BLOCK; def.target.path = hostScsiDisk->devicePath; def.capacity = hostScsiDisk->capacity->block->value * hostScsiDisk->capacity->blockSize->value; def.allocation = def.capacity; /* iSCSI LUN(s) hosting a datastore will be auto-mounted by ESX host */ def.target.format = VIR_STORAGE_FILE_RAW; xml = virStorageVolDefFormat(&pool, &def); cleanup: esxVI_ScsiLun_Free(&scsiLunList); VIR_FREE(def.key); return xml; } static int esxStorageVolDelete(virStorageVolPtr volume ATTRIBUTE_UNUSED, unsigned int flags) { virCheckFlags(0, -1); virReportError(VIR_ERR_NO_SUPPORT, "%s", _("iSCSI storage pool does not support volume deletion")); return -1; } static int esxStorageVolWipe(virStorageVolPtr volume ATTRIBUTE_UNUSED, unsigned int flags) { virCheckFlags(0, -1); virReportError(VIR_ERR_NO_SUPPORT, "%s", _("iSCSI storage pool does not support volume wiping")); return -1; } static char * esxStorageVolGetPath(virStorageVolPtr volume) { char *path; ignore_value(VIR_STRDUP(path, volume->name)); return path; } virStorageDriver esxStorageBackendISCSI = { .connectNumOfStoragePools = esxConnectNumOfStoragePools, /* 1.0.1 */ .connectListStoragePools = esxConnectListStoragePools, /* 1.0.1 */ .storagePoolLookupByName = esxStoragePoolLookupByName, /* 1.0.1 */ .storagePoolLookupByUUID = esxStoragePoolLookupByUUID, /* 1.0.1 */ .storagePoolRefresh = esxStoragePoolRefresh, /* 1.0.1 */ .storagePoolGetInfo = esxStoragePoolGetInfo, /* 1.0.1 */ .storagePoolGetXMLDesc = esxStoragePoolGetXMLDesc, /* 1.0.1 */ .storagePoolNumOfVolumes = esxStoragePoolNumOfVolumes, /* 1.0.1 */ .storagePoolListVolumes = esxStoragePoolListVolumes, /* 1.0.1 */ .storageVolLookupByName = esxStorageVolLookupByName, /* 1.0.1 */ .storageVolLookupByPath = esxStorageVolLookupByPath, /* 1.0.1 */ .storageVolLookupByKey = esxStorageVolLookupByKey, /* 1.0.1 */ .storageVolCreateXML = esxStorageVolCreateXML, /* 1.0.1 */ .storageVolCreateXMLFrom = esxStorageVolCreateXMLFrom, /* 1.0.1 */ .storageVolGetXMLDesc = esxStorageVolGetXMLDesc, /* 1.0.1 */ .storageVolDelete = esxStorageVolDelete, /* 1.0.1 */ .storageVolWipe = esxStorageVolWipe, /* 1.0.1 */ .storageVolGetPath = esxStorageVolGetPath, /* 1.0.1 */ };