On Tue, Feb 12, 2008 at 04:39:25AM +0000, Daniel P. Berrange wrote: > This provides a storage pool using the iSCSI protocol. Since there > is no API for iSCSI it is implemented by simply shelling out to the > iscsiadm command line tool. A pool corresponds to a single target > on the iSCSI server. Starting a pool logs into the server and maps > the target's LUNs into the local filesystem. The default nodes are > under /dev, allocated-on-demand and thus not guarenteed to be stable > across reboots. For this reason it is recommended by the pool target > path be configured to point to /dev/disk/by-path or /dev/disk/by-id > whose entries are guarenteed stable for lifetime of the target+LUN. > The 'refresh' operation will rescan the target for new LUNs and purge > old LUNs allowing dynamic updates without needing a pool restart. b/src/storage_backend_iscsi.c | 455 ++++++++++++++++++++++++++++++++++++++++++ b/src/storage_backend_iscsi.h | 45 ++++ configure.in | 22 ++ libvirt.spec.in | 4 po/POTFILES.in | 1 src/Makefile.am | 6 src/storage_backend.c | 15 + 7 files changed, 548 insertions(+) diff -r ee74be65111c configure.in --- a/configure.in Tue Feb 19 17:31:04 2008 -0500 +++ b/configure.in Tue Feb 19 17:38:39 2008 -0500 @@ -561,6 +561,8 @@ AC_ARG_WITH(storage-fs, [ --with-storage-fs with FileSystem backend for the storage driver (on)],[],[with_storage_fs=check]) AC_ARG_WITH(storage-lvm, [ --with-storage-lvm with LVM backend for the storage driver (on)],[],[with_storage_lvm=check]) +AC_ARG_WITH(storage-iscsi, +[ --with-storage-iscsi with iSCSI backend for the storage driver (on)],[],[with_storage_iscsi=check]) if test "$with_storage_fs" = "yes" -o "$with_storage_fs" = "check"; then AC_PATH_PROG(MOUNT, [mount], [], [$PATH:/sbin:/usr/sbin]) @@ -653,6 +655,25 @@ if test "$with_storage_lvm" = "yes" -o " fi fi AM_CONDITIONAL(WITH_STORAGE_LVM, [test "$with_storage_lvm" = "yes"]) + + + +if test "$with_storage_iscsi" = "yes" -o "$with_storage_iscsi" = "check"; then + AC_PATH_PROG(ISCSIADM, [iscsiadm], [], [$PATH:/sbin:/usr/sbin]) + if test "$with_storage_iscsi" = "yes" ; then + if test -z "$ISCSIADM" ; then AC_MSG_ERROR(We need iscsiadm for iSCSI storage driver) ; fi + else + if test -z "$ISCSIADM" ; then with_storage_iscsi=no ; fi + + if test "$with_storage_iscsi" = "check" ; then with_storage_iscsi=yes ; fi + fi + + if test "$with_storage_iscsi" = "yes" ; then + AC_DEFINE_UNQUOTED(WITH_STORAGE_ISCSI, 1, [whether iSCSI backend for storage driver is enabled]) + AC_DEFINE_UNQUOTED([ISCSIADM],["$ISCSIADM"],[Location of iscsiadm program]) + fi +fi +AM_CONDITIONAL(WITH_STORAGE_ISCSI, [test "$with_storage_iscsi" = "yes"]) dnl @@ -869,6 +890,7 @@ AC_MSG_NOTICE([ FS: $with_storage_f AC_MSG_NOTICE([ FS: $with_storage_fs]) AC_MSG_NOTICE([ NetFS: $with_storage_fs]) AC_MSG_NOTICE([ LVM: $with_storage_lvm]) +AC_MSG_NOTICE([ iSCSI: $with_storage_iscsi]) AC_MSG_NOTICE([]) AC_MSG_NOTICE([Libraries]) AC_MSG_NOTICE([]) diff -r ee74be65111c libvirt.spec.in --- a/libvirt.spec.in Tue Feb 19 17:31:04 2008 -0500 +++ b/libvirt.spec.in Tue Feb 19 17:38:39 2008 -0500 @@ -51,6 +51,8 @@ Requires: /usr/sbin/qcow-create %endif # For LVM drivers Requires: lvm2 +# For ISCSI driver +Requires: iscsi-initiator-utils BuildRequires: xen-devel BuildRequires: libxml2-devel BuildRequires: readline-devel @@ -77,6 +79,8 @@ BuildRequires: /usr/sbin/qcow-create %endif # For LVM drivers BuildRequires: lvm2 +# For ISCSI driver +BuildRequires: iscsi-initiator-utils Obsoletes: libvir ExclusiveArch: i386 x86_64 ia64 diff -r ee74be65111c po/POTFILES.in --- a/po/POTFILES.in Tue Feb 19 17:31:04 2008 -0500 +++ b/po/POTFILES.in Tue Feb 19 17:38:39 2008 -0500 @@ -13,6 +13,7 @@ src/storage_backend.c src/storage_backend.c src/storage_backend_fs.c src/storage_backend_logical.c +src/storage_backend_iscsi.c src/storage_conf.c src/storage_driver.c src/sexpr.c diff -r ee74be65111c src/Makefile.am --- a/src/Makefile.am Tue Feb 19 17:31:04 2008 -0500 +++ b/src/Makefile.am Tue Feb 19 17:38:39 2008 -0500 @@ -74,6 +74,12 @@ EXTRA_DIST += storage_backend_logical.h EXTRA_DIST += storage_backend_logical.h storage_backend_logical.c endif +if WITH_STORAGE_ISCSI +CLIENT_SOURCES += storage_backend_iscsi.h storage_backend_iscsi.c +else +EXTRA_DIST += storage_backend_iscsi.h storage_backend_iscsi.c +endif + libvirt_la_SOURCES = $(CLIENT_SOURCES) $(SERVER_SOURCES) diff -r ee74be65111c src/storage_backend.c --- a/src/storage_backend.c Tue Feb 19 17:31:04 2008 -0500 +++ b/src/storage_backend.c Tue Feb 19 17:38:39 2008 -0500 @@ -39,6 +39,10 @@ #if WITH_STORAGE_LVM #include "storage_backend_logical.h" #endif +#if WITH_STORAGE_ISCSI +#include "storage_backend_iscsi.h" +#endif + #include "util.h" @@ -53,6 +57,9 @@ static virStorageBackendPtr backends[] = #endif #if WITH_STORAGE_LVM &virStorageBackendLogical, +#endif +#if WITH_STORAGE_ISCSI + &virStorageBackendISCSI, #endif }; @@ -99,6 +106,10 @@ virStorageBackendFromString(const char * #if WITH_STORAGE_LVM if (STREQ(type, "logical")) return VIR_STORAGE_POOL_LOGICAL; +#endif +#if WITH_STORAGE_ISCSI + if (STREQ(type, "iscsi")) + return VIR_STORAGE_POOL_ISCSI; #endif virStorageReportError(NULL, VIR_ERR_INTERNAL_ERROR, @@ -120,6 +131,10 @@ virStorageBackendToString(int type) { #if WITH_STORAGE_LVM case VIR_STORAGE_POOL_LOGICAL: return "logical"; +#endif +#if WITH_STORAGE_ISCSI + case VIR_STORAGE_POOL_ISCSI: + return "iscsi"; #endif } diff -r ee74be65111c src/storage_backend_iscsi.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/storage_backend_iscsi.c Tue Feb 19 17:38:39 2008 -0500 @@ -0,0 +1,455 @@ +/* + * storage_backend_iscsi.c: storage backend for iSCSI handling + * + * Copyright (C) 2007-2008 Red Hat, Inc. + * Copyright (C) 2007-2008 Daniel P. Berrange + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Daniel P. Berrange <berrange@xxxxxxxxxx> + */ + +#include <config.h> + +#include <sys/socket.h> +#include <netdb.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <string.h> +#include <stdio.h> +#include <regex.h> +#include <fcntl.h> +#include <unistd.h> +#include <dirent.h> + +#include "storage_backend_iscsi.h" +#include "util.h" + +static int +virStorageBackendISCSITargetIP(virConnectPtr conn, + const char *hostname, + char *ipaddr, + size_t ipaddrlen) +{ + struct addrinfo hints; + struct addrinfo *result = NULL; + int ret; + + memset(&hints, 0, sizeof hints); + hints.ai_flags = AI_ADDRCONFIG; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + ret = getaddrinfo(hostname, NULL, &hints, &result); + if (ret != 0) { + virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, + _("host lookup failed %s"), + gai_strerror(ret)); + return -1; + } + + if (result == NULL) { + virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, + _("no IP address for target %s"), + hostname); + return -1; + } + + if (getnameinfo(result->ai_addr, result->ai_addrlen, + ipaddr, ipaddrlen, NULL, 0, + NI_NUMERICHOST) < 0) { + virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, + _("cannot format ip addr for %s"), + hostname); + freeaddrinfo(result); + return -1; + } + + freeaddrinfo(result); + return 0; +} + +static int +virStorageBackendISCSIExtractSession(virConnectPtr conn, + virStoragePoolObjPtr pool, + char **const groups, + void *data) +{ + char **session = data; + + if (STREQ(groups[1], pool->def->source.devices[0].path)) { + if ((*session = strdup(groups[0])) == NULL) { + virStorageReportError(conn, VIR_ERR_NO_MEMORY, _("session")); + return -1; + } + } + + return 0; +} + +static char * +virStorageBackendISCSISession(virConnectPtr conn, + virStoragePoolObjPtr pool) +{ + /* + * # iscsiadm --mode session -P 0 + * tcp: [1] 192.168.122.170:3260,1 demo-tgt-b + * tcp: [2] 192.168.122.170:3260,1 demo-tgt-a + * + * Pull out 2nd and 4th fields + */ + const char *regexes[] = { + "^tcp:\\s+\\[(\\S+)\\]\\s+\\S+\\s+(\\S+)\\s*$" + }; + int vars[] = { + 2, + }; + const char *prog[] = { + ISCSIADM, "--mode", "session", "-P", "0", NULL + }; + char *session = NULL; + + if (virStorageBackendRunProgRegex(conn, pool, + prog, + 1, + regexes, + vars, + virStorageBackendISCSIExtractSession, + &session) < 0) + return NULL; + + if (session == NULL) { + virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, + _("cannot find session")); + return NULL; + } + + return session; +} + +static int +virStorageBackendISCSIConnection(virConnectPtr conn, + virStoragePoolObjPtr pool, + const char *portal, + const char *action) +{ + const char *cmdargv[] = { + ISCSIADM, "--mode", "node", "--portal", portal, + "--targetname", pool->def->source.devices[0].path, action, NULL + }; + + if (virRun(conn, (char **)cmdargv, NULL) < 0) + return -1; + + return 0; +} + + +static int +virStorageBackendISCSIMakeLUN(virConnectPtr conn, + virStoragePoolObjPtr pool, + char **const groups, + void *data ATTRIBUTE_UNUSED) +{ + virStorageVolDefPtr vol; + int fd = -1; + char lunid[100]; + char *dev = groups[4]; + int opentries = 0; + char *devpath = NULL; + + snprintf(lunid, sizeof(lunid)-1, "lun-%s", groups[3]); + + if ((vol = calloc(1, sizeof(virStorageVolDef))) == NULL) { + virStorageReportError(conn, VIR_ERR_NO_MEMORY, _("volume")); + return -1; + } + + if ((vol->name = strdup(lunid)) == NULL) { + virStorageReportError(conn, VIR_ERR_NO_MEMORY, _("name")); + goto cleanup; + } + + if ((devpath = malloc(5 + strlen(dev) + 1)) == NULL) { + virStorageReportError(conn, VIR_ERR_NO_MEMORY, _("devpath")); + goto cleanup; + } + strcpy(devpath, "/dev/"); + strcat(devpath, dev); + /* It can take a little while between logging into the ISCSI + * server and udev creating the /dev nodes, so if we get ENOENT + * we must retry a few times - they should eventually appear. + * We currently wait for upto 5 seconds. Is this good enough ? + * Perhaps not on a very heavily loaded system Any other + * options... ? + */ + reopen: + if ((fd = open(devpath, O_RDONLY)) < 0) { + opentries++; + if (errno == ENOENT && opentries < 50) { + usleep(100 * 1000); + goto reopen; + } + virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, + _("cannot open %s: %s"), + devpath, strerror(errno)); + goto cleanup; + } + + /* Now figure out the stable path + * + * XXX this method is O(N) because it scans the pool target + * dir every time its run. Should figure out a more efficient + * way of doing this... + */ + if ((vol->target.path = virStorageBackendStablePath(conn, + pool, + devpath)) == NULL) + goto cleanup; + + if (devpath != vol->target.path) + free(devpath); + devpath = NULL; + + if (virStorageBackendUpdateVolInfoFD(conn, vol, fd, 1) < 0) + goto cleanup; + + /* XXX use unique iSCSI id instead */ + vol->key = strdup(vol->target.path); + if (vol->key == NULL) { + virStorageReportError(conn, VIR_ERR_NO_MEMORY, _("key")); + goto cleanup; + } + + + pool->def->capacity += vol->capacity; + pool->def->allocation += vol->allocation; + + vol->next = pool->volumes; + pool->volumes = vol; + pool->nvolumes++; + + close(fd); + + return 0; + + cleanup: + if (fd != -1) close(fd); + free(devpath); + virStorageVolDefFree(vol); + return -1; +} + +static int +virStorageBackendISCSIFindLUNs(virConnectPtr conn, + virStoragePoolObjPtr pool, + const char *session) +{ + /* + * # iscsiadm --mode session -r $session -P 3 + * + * scsi1 Channel 00 Id 0 Lun: 0 + * scsi1 Channel 00 Id 0 Lun: 1 + * Attached scsi disk sdc State: running + * scsi1 Channel 00 Id 0 Lun: 2 + * Attached scsi disk sdd State: running + * scsi1 Channel 00 Id 0 Lun: 3 + * Attached scsi disk sde State: running + * scsi1 Channel 00 Id 0 Lun: 4 + * Attached scsi disk sdf State: running + * scsi1 Channel 00 Id 0 Lun: 5 + * Attached scsi disk sdg State: running + * + * Need 2 regex to match alternating lines + */ + const char *regexes[] = { + "^\\s*scsi(\\S+)\\s+Channel\\s+(\\S+)\\s+Id\\s+(\\S+)\\s+Lun:\\s+(\\S+)\\s*$", + "^\\s*Attached\\s+scsi\\s+disk\\s+(\\S+)\\s+State:\\s+running\\s*$" + }; + int vars[] = { + 4, 1 + }; + const char *prog[] = { + ISCSIADM, "--mode", "session", "-r", session, "-P", "3", NULL, + }; + + return virStorageBackendRunProgRegex(conn, pool, + prog, + 2, + regexes, + vars, + virStorageBackendISCSIMakeLUN, + NULL); +} + + +static int +virStorageBackendISCSIRescanLUNs(virConnectPtr conn, + virStoragePoolObjPtr pool ATTRIBUTE_UNUSED, + const char *session) +{ + const char *cmdargv[] = { + ISCSIADM, "--mode", "session", "-r", session, "-R", NULL, + }; + + if (virRun(conn, (char **)cmdargv, NULL) < 0) + return -1; + + return 0; +} + + +static int +virStorageBackendISCSILogin(virConnectPtr conn, + virStoragePoolObjPtr pool, + const char *portal) +{ + return virStorageBackendISCSIConnection(conn, pool, portal, "--login"); +} + +static int +virStorageBackendISCSILogout(virConnectPtr conn, + virStoragePoolObjPtr pool, + const char *portal) +{ + return virStorageBackendISCSIConnection(conn, pool, portal, "--logout"); +} + +static char * +virStorageBackendISCSIPortal(virConnectPtr conn, + virStoragePoolObjPtr pool) +{ + char ipaddr[NI_MAXHOST]; + char *portal; + + if (virStorageBackendISCSITargetIP(conn, + pool->def->source.host.name, + ipaddr, sizeof(ipaddr)) < 0) + return NULL; + + portal = malloc(strlen(ipaddr) + 1 + 4 + 2 + 1); + if (portal == NULL) { + virStorageReportError(conn, VIR_ERR_NO_MEMORY, _("portal")); + return NULL; + } + + strcpy(portal, ipaddr); + strcat(portal, ":3260,1"); + + return portal; +} + + +static int +virStorageBackendISCSIStartPool(virConnectPtr conn, + virStoragePoolObjPtr pool) +{ + char *portal = NULL; + + if (pool->def->source.host.name == NULL) { + virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, + _("missing source host")); + return -1; + } + + if (pool->def->source.ndevice != 1 || + pool->def->source.devices[0].path == NULL) { + virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, + _("missing source device")); + return -1; + } + + if ((portal = virStorageBackendISCSIPortal(conn, pool)) == NULL) + return -1; + if (virStorageBackendISCSILogin(conn, pool, portal) < 0) { + free(portal); + return -1; + } + free(portal); + return 0; +} + +static int +virStorageBackendISCSIRefreshPool(virConnectPtr conn, + virStoragePoolObjPtr pool) +{ + char *session = NULL; + + pool->def->allocation = pool->def->capacity = pool->def->available = 0; + + if ((session = virStorageBackendISCSISession(conn, pool)) == NULL) + goto cleanup; + if (virStorageBackendISCSIRescanLUNs(conn, pool, session) < 0) + goto cleanup; + if (virStorageBackendISCSIFindLUNs(conn, pool, session) < 0) + goto cleanup; + free(session); + + return 0; + + cleanup: + free(session); + return -1; +} + + +static int +virStorageBackendISCSIStopPool(virConnectPtr conn, + virStoragePoolObjPtr pool) +{ + char *portal; + + if ((portal = virStorageBackendISCSIPortal(conn, pool)) == NULL) + return -1; + + if (virStorageBackendISCSILogout(conn, pool, portal) < 0) { + free(portal); + return -1; + } + free(portal); + + return 0; +} + + +virStorageBackend virStorageBackendISCSI = { + .type = VIR_STORAGE_POOL_ISCSI, + + .startPool = virStorageBackendISCSIStartPool, + .refreshPool = virStorageBackendISCSIRefreshPool, + .stopPool = virStorageBackendISCSIStopPool, + + .poolOptions = { + .flags = (VIR_STORAGE_BACKEND_POOL_SOURCE_HOST | + VIR_STORAGE_BACKEND_POOL_SOURCE_DEVICE) + }, + + .volType = VIR_STORAGE_VOL_BLOCK, +}; + +/* + * vim: set tabstop=4: + * vim: set shiftwidth=4: + * vim: set expandtab: + */ +/* + * Local variables: + * indent-tabs-mode: nil + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 4 + * End: + */ diff -r ee74be65111c src/storage_backend_iscsi.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/storage_backend_iscsi.h Tue Feb 19 17:38:39 2008 -0500 @@ -0,0 +1,45 @@ +/* + * storage_backend_iscsi.h: storage backend for iSCSI handling + * + * Copyright (C) 2007-2008 Red Hat, Inc. + * Copyright (C) 2007-2008 Daniel P. Berrange + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Daniel P. Berrange <berrange@xxxxxxxxxx> + */ + +#ifndef __VIR_STORAGE_BACKEND_ISCSI_H__ +#define __VIR_STORAGE_BACKEND_ISCSI_H__ + +#include "storage_backend.h" + +extern virStorageBackend virStorageBackendISCSI; + +#endif /* __VIR_STORAGE_BACKEND_ISCSI_H__ */ + +/* + * vim: set tabstop=4: + * vim: set shiftwidth=4: + * vim: set expandtab: + */ +/* + * Local variables: + * indent-tabs-mode: nil + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 4 + * End: + */ -- |=- Red Hat, Engineering, Emerging Technologies, Boston. +1 978 392 2496 -=| |=- Perl modules: http://search.cpan.org/~danberr/ -=| |=- Projects: http://freshmeat.net/~danielpb/ -=| |=- GnuPG: 7D3B9505 F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 -=| -- Libvir-list mailing list Libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list