diff --git a/ChangeLog b/ChangeLog index 529d2a0b85..e866793edd 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +Wed Feb 20 10:44:27 EST 2008 Daniel P. Berrange + + * configure.in: Added check for iscsiadm tool + * libvirt.spec.in: Added dep on iscsi-initiator-utils + * po/POTFILES.in: Added storage_backend_iscsi.c + * src/Makefile.am, src/storage_backend.c, + src/storage_backend_iscsi.c, src/storage_backend_iscsi.h: + Added iSCSI storage pool backend + Wed Feb 20 10:42:27 EST 2008 Daniel P. Berrange * qemud/qemud.c: Set close-on-exec flag for signal pipe diff --git a/configure.in b/configure.in index 2cdc80ac01..45d54886de 100644 --- a/configure.in +++ b/configure.in @@ -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]) @@ -655,6 +657,25 @@ 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 dnl check for python dnl @@ -869,6 +890,7 @@ AC_MSG_NOTICE([ Dir: yes]) 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 --git a/libvirt.spec.in b/libvirt.spec.in index a95b22ac12..9ffb524059 100644 --- a/libvirt.spec.in +++ b/libvirt.spec.in @@ -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 --git a/po/POTFILES.in b/po/POTFILES.in index 3f2351e074..274ed3624f 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -13,6 +13,7 @@ src/remote_internal.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 --git a/src/Makefile.am b/src/Makefile.am index f95aad2fb1..0dae35d875 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -74,6 +74,12 @@ else 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 --git a/src/storage_backend.c b/src/storage_backend.c index 698e1c9c55..3555841e9c 100644 --- a/src/storage_backend.c +++ b/src/storage_backend.c @@ -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" @@ -54,6 +58,9 @@ static virStorageBackendPtr backends[] = { #if WITH_STORAGE_LVM &virStorageBackendLogical, #endif +#if WITH_STORAGE_ISCSI + &virStorageBackendISCSI, +#endif }; @@ -100,6 +107,10 @@ virStorageBackendFromString(const char *type) { 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, _("unknown storage backend type %s"), type); @@ -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 --git a/src/storage_backend_iscsi.c b/src/storage_backend_iscsi.c new file mode 100644 index 0000000000..2f7d7461f2 --- /dev/null +++ b/src/storage_backend_iscsi.c @@ -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 + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 --git a/src/storage_backend_iscsi.h b/src/storage_backend_iscsi.h new file mode 100644 index 0000000000..fb902fdecf --- /dev/null +++ b/src/storage_backend_iscsi.h @@ -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 + */ + +#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: + */