/* * 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: */