libvirt/src/storage/storage_backend_iscsi.c
Daniel P. Berrange 1b7e0b1a9c Check whether pools are already active upon libvirtd startup
When libvirt starts up all storage pools default to the inactive
state, even if the underlying storage is already active on the
host. This introduces a new API into the internal storage backend
drivers that checks whether a storage pool is already active. If
the pool is active at libvirtd startup, the volume list will be
immediately populated.

* src/storage/storage_backend.h: New internal API for checking
  storage pool state
* src/storage/storage_driver.c: Check whether a pool is active
  upon driver startup
* src/storage/storage_backend_fs.c, src/storage/storage_backend_iscsi.c,
  src/storage/storage_backend_logical.c, src/storage/storage_backend_mpath.c,
  src/storage/storage_backend_scsi.c: Add checks for pool state
2010-11-23 15:00:35 +00:00

772 lines
22 KiB
C

/*
* storage_backend_iscsi.c: storage backend for iSCSI handling
*
* Copyright (C) 2007-2008, 2010 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@redhat.com>
*/
#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 <sys/stat.h>
#include "virterror_internal.h"
#include "storage_backend_scsi.h"
#include "storage_backend_iscsi.h"
#include "util.h"
#include "memory.h"
#include "logging.h"
#include "files.h"
#define VIR_FROM_THIS VIR_FROM_STORAGE
static int
virStorageBackendISCSITargetIP(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(VIR_ERR_INTERNAL_ERROR,
_("host lookup failed %s"),
gai_strerror(ret));
return -1;
}
if (result == NULL) {
virStorageReportError(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(VIR_ERR_INTERNAL_ERROR,
_("cannot format ip addr for %s"),
hostname);
freeaddrinfo(result);
return -1;
}
freeaddrinfo(result);
return 0;
}
static char *
virStorageBackendISCSIPortal(virStoragePoolSourcePtr source)
{
char ipaddr[NI_MAXHOST];
char *portal;
if (virStorageBackendISCSITargetIP(source->host.name,
ipaddr, sizeof(ipaddr)) < 0)
return NULL;
if (virAsprintf(&portal, "%s:%d,1", ipaddr,
source->host.port ?
source->host.port : 3260) < 0) {
virReportOOMError();
return NULL;
}
return portal;
}
static int
virStorageBackendISCSIExtractSession(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) {
virReportOOMError();
return -1;
}
}
return 0;
}
static char *
virStorageBackendISCSISession(virStoragePoolObjPtr pool,
int probe)
{
/*
* # iscsiadm --mode session
* 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 *const prog[] = {
ISCSIADM, "--mode", "session", NULL
};
char *session = NULL;
/* Note that we ignore the exitstatus. Older versions of iscsiadm tools
* returned an exit status of > 0, even if they succeeded. We will just
* rely on whether session got filled in properly.
*/
if (virStorageBackendRunProgRegex(pool,
prog,
1,
regexes,
vars,
virStorageBackendISCSIExtractSession,
&session,
NULL) < 0)
return NULL;
if (session == NULL &&
!probe) {
virStorageReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("cannot find session"));
return NULL;
}
return session;
}
#define LINE_SIZE 4096
static int
virStorageBackendIQNFound(const char *initiatoriqn,
char **ifacename)
{
int ret = IQN_MISSING, fd = -1;
char ebuf[64];
FILE *fp = NULL;
pid_t child = 0;
char *line = NULL, *newline = NULL, *iqn = NULL, *token = NULL,
*saveptr = NULL;
const char *const prog[] = {
ISCSIADM, "--mode", "iface", NULL
};
if (VIR_ALLOC_N(line, LINE_SIZE) != 0) {
ret = IQN_ERROR;
virStorageReportError(VIR_ERR_INTERNAL_ERROR,
_("Could not allocate memory for output of '%s'"),
prog[0]);
goto out;
}
memset(line, 0, LINE_SIZE);
if (virExec(prog, NULL, NULL, &child, -1, &fd, NULL, VIR_EXEC_NONE) < 0) {
virStorageReportError(VIR_ERR_INTERNAL_ERROR,
_("Failed to run '%s' when looking for existing interface with IQN '%s'"),
prog[0], initiatoriqn);
ret = IQN_ERROR;
goto out;
}
if ((fp = VIR_FDOPEN(fd, "r")) == NULL) {
virStorageReportError(VIR_ERR_INTERNAL_ERROR,
_("Failed to open stream for file descriptor "
"when reading output from '%s': '%s'"),
prog[0], virStrerror(errno, ebuf, sizeof ebuf));
ret = IQN_ERROR;
goto out;
}
while (fgets(line, LINE_SIZE, fp) != NULL) {
newline = strrchr(line, '\n');
if (newline == NULL) {
ret = IQN_ERROR;
virStorageReportError(VIR_ERR_INTERNAL_ERROR,
_("Unexpected line > %d characters "
"when parsing output of '%s'"),
LINE_SIZE, prog[0]);
goto out;
}
*newline = '\0';
iqn = strrchr(line, ',');
if (iqn == NULL) {
continue;
}
iqn++;
if (STREQ(iqn, initiatoriqn)) {
token = strtok_r(line, " ", &saveptr);
*ifacename = strdup(token);
if (*ifacename == NULL) {
ret = IQN_ERROR;
virReportOOMError();
goto out;
}
VIR_DEBUG("Found interface '%s' with IQN '%s'", *ifacename, iqn);
ret = IQN_FOUND;
break;
}
}
out:
if (ret == IQN_MISSING) {
VIR_DEBUG("Could not find interface with IQN '%s'", iqn);
}
VIR_FREE(line);
VIR_FORCE_FCLOSE(fp);
VIR_FORCE_CLOSE(fd);
return ret;
}
static int
virStorageBackendCreateIfaceIQN(const char *initiatoriqn,
char **ifacename)
{
int ret = -1, exitstatus = -1;
char temp_ifacename[32];
if (virRandomInitialize(time(NULL) ^ getpid()) == -1) {
virStorageReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Failed to initialize random generator "
"when creating iscsi interface"));
goto out;
}
snprintf(temp_ifacename, sizeof(temp_ifacename), "libvirt-iface-%08x", virRandom(1024 * 1024 * 1024));
const char *const cmdargv1[] = {
ISCSIADM, "--mode", "iface", "--interface",
&temp_ifacename[0], "--op", "new", NULL
};
VIR_DEBUG("Attempting to create interface '%s' with IQN '%s'",
&temp_ifacename[0], initiatoriqn);
/* Note that we ignore the exitstatus. Older versions of iscsiadm
* tools returned an exit status of > 0, even if they succeeded.
* We will just rely on whether the interface got created
* properly. */
if (virRun(cmdargv1, &exitstatus) < 0) {
virStorageReportError(VIR_ERR_INTERNAL_ERROR,
_("Failed to run command '%s' to create new iscsi interface"),
cmdargv1[0]);
goto out;
}
const char *const cmdargv2[] = {
ISCSIADM, "--mode", "iface", "--interface", &temp_ifacename[0],
"--op", "update", "--name", "iface.initiatorname", "--value",
initiatoriqn, NULL
};
/* Note that we ignore the exitstatus. Older versions of iscsiadm tools
* returned an exit status of > 0, even if they succeeded. We will just
* rely on whether iface file got updated properly. */
if (virRun(cmdargv2, &exitstatus) < 0) {
virStorageReportError(VIR_ERR_INTERNAL_ERROR,
_("Failed to run command '%s' to update iscsi interface with IQN '%s'"),
cmdargv2[0], initiatoriqn);
goto out;
}
/* Check again to make sure the interface was created. */
if (virStorageBackendIQNFound(initiatoriqn, ifacename) != IQN_FOUND) {
VIR_DEBUG("Failed to find interface '%s' with IQN '%s' "
"after attempting to create it",
&temp_ifacename[0], initiatoriqn);
goto out;
} else {
VIR_DEBUG("Interface '%s' with IQN '%s' was created successfully",
*ifacename, initiatoriqn);
}
ret = 0;
out:
if (ret != 0)
VIR_FREE(*ifacename);
return ret;
}
static int
virStorageBackendISCSIConnection(const char *portal,
const char *initiatoriqn,
const char *target,
const char **extraargv)
{
int ret = -1;
const char *const baseargv[] = {
ISCSIADM,
"--mode", "node",
"--portal", portal,
"--targetname", target,
NULL
};
int i;
int nargs = 0;
char *ifacename = NULL;
const char **cmdargv;
for (i = 0 ; baseargv[i] != NULL ; i++)
nargs++;
for (i = 0 ; extraargv[i] != NULL ; i++)
nargs++;
if (initiatoriqn)
nargs += 2;
if (VIR_ALLOC_N(cmdargv, nargs+1) < 0) {
virReportOOMError();
return -1;
}
nargs = 0;
for (i = 0 ; baseargv[i] != NULL ; i++)
cmdargv[nargs++] = baseargv[i];
for (i = 0 ; extraargv[i] != NULL ; i++)
cmdargv[nargs++] = extraargv[i];
if (initiatoriqn) {
switch (virStorageBackendIQNFound(initiatoriqn, &ifacename)) {
case IQN_FOUND:
VIR_DEBUG("ifacename: '%s'", ifacename);
break;
case IQN_MISSING:
if (virStorageBackendCreateIfaceIQN(initiatoriqn, &ifacename) != 0) {
goto cleanup;
}
break;
case IQN_ERROR:
default:
goto cleanup;
}
cmdargv[nargs++] = "--interface";
cmdargv[nargs++] = ifacename;
}
cmdargv[nargs++] = NULL;
if (virRun(cmdargv, NULL) < 0)
goto cleanup;
ret = 0;
cleanup:
VIR_FREE(cmdargv);
VIR_FREE(ifacename);
return ret;
}
static int
virStorageBackendISCSIFindLUs(virStoragePoolObjPtr pool,
const char *session)
{
char sysfs_path[PATH_MAX];
int retval = 0;
uint32_t host;
snprintf(sysfs_path, PATH_MAX,
"/sys/class/iscsi_session/session%s/device", session);
if (virStorageBackendSCSIGetHostNumber(sysfs_path, &host) < 0) {
virReportSystemError(errno,
_("Failed to get host number for iSCSI session "
"with path '%s'"),
sysfs_path);
retval = -1;
}
if (virStorageBackendSCSIFindLUs(pool, host) < 0) {
virReportSystemError(errno,
_("Failed to find LUs on host %u"), host);
retval = -1;
}
return retval;
}
static int
virStorageBackendISCSIRescanLUNs(virStoragePoolObjPtr pool ATTRIBUTE_UNUSED,
const char *session)
{
const char *const cmdargv[] = {
ISCSIADM, "--mode", "session", "-r", session, "-R", NULL,
};
if (virRun(cmdargv, NULL) < 0)
return -1;
return 0;
}
struct virStorageBackendISCSITargetList {
size_t ntargets;
char **targets;
};
static int
virStorageBackendISCSIGetTargets(virStoragePoolObjPtr pool ATTRIBUTE_UNUSED,
char **const groups,
void *data)
{
struct virStorageBackendISCSITargetList *list = data;
char *target;
if (!(target = strdup(groups[1]))) {
virReportOOMError();
return -1;
}
if (VIR_REALLOC_N(list->targets, list->ntargets + 1) < 0) {
VIR_FREE(target);
virReportOOMError();
return -1;
}
list->targets[list->ntargets] = target;
list->ntargets++;
return 0;
}
static int
virStorageBackendISCSITargetAutologin(const char *portal,
const char *initiatoriqn,
const char *target,
bool enable)
{
const char *extraargv[] = { "--op", "update",
"--name", "node.startup",
"--value", enable ? "automatic" : "manual",
NULL };
return virStorageBackendISCSIConnection(portal, initiatoriqn, target, extraargv);
}
static int
virStorageBackendISCSIScanTargets(const char *portal,
const char *initiatoriqn,
size_t *ntargetsret,
char ***targetsret)
{
/**
*
* The output of sendtargets is very simple, just two columns,
* portal then target name
*
* 192.168.122.185:3260,1 iqn.2004-04.com:fedora14:iscsi.demo0.bf6d84
* 192.168.122.185:3260,1 iqn.2004-04.com:fedora14:iscsi.demo1.bf6d84
* 192.168.122.185:3260,1 iqn.2004-04.com:fedora14:iscsi.demo2.bf6d84
* 192.168.122.185:3260,1 iqn.2004-04.com:fedora14:iscsi.demo3.bf6d84
*/
const char *regexes[] = {
"^\\s*(\\S+)\\s+(\\S+)\\s*$"
};
int vars[] = { 2 };
const char *const cmdsendtarget[] = {
ISCSIADM, "--mode", "discovery", "--type", "sendtargets",
"--portal", portal, NULL
};
struct virStorageBackendISCSITargetList list;
int i;
int exitstatus;
memset(&list, 0, sizeof(list));
if (virStorageBackendRunProgRegex(NULL, /* No pool for callback */
cmdsendtarget,
1,
regexes,
vars,
virStorageBackendISCSIGetTargets,
&list,
&exitstatus) < 0) {
virStorageReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("iscsiadm command failed"));
return -1;
}
if (exitstatus != 0) {
virStorageReportError(VIR_ERR_INTERNAL_ERROR,
_("iscsiadm sendtargets command failed with exitstatus %d"),
exitstatus);
return -1;
}
for (i = 0 ; i < list.ntargets ; i++) {
/* We have to ignore failure, because we can't undo
* the results of 'sendtargets', unless we go scrubbing
* around in the dirt in /var/lib/iscsi.
*/
if (virStorageBackendISCSITargetAutologin(portal,
initiatoriqn,
list.targets[i], false) < 0)
VIR_WARN("Unable to disable auto-login on iSCSI target %s: %s",
portal, list.targets[i]);
}
if (ntargetsret && targetsret) {
*ntargetsret = list.ntargets;
*targetsret = list.targets;
} else {
for (i = 0 ; i < list.ntargets ; i++) {
VIR_FREE(list.targets[i]);
}
VIR_FREE(list.targets);
}
return 0;
}
static char *
virStorageBackendISCSIFindPoolSources(virConnectPtr conn ATTRIBUTE_UNUSED,
const char *srcSpec,
unsigned int flags ATTRIBUTE_UNUSED)
{
virStoragePoolSourcePtr source = NULL;
size_t ntargets = 0;
char **targets = NULL;
char *ret = NULL;
int i;
virStoragePoolSourceList list = {
.type = VIR_STORAGE_POOL_ISCSI,
.nsources = 0,
.sources = NULL
};
char *portal = NULL;
if (!(source = virStoragePoolDefParseSourceString(srcSpec,
list.type)))
return NULL;
if (!(portal = virStorageBackendISCSIPortal(source)))
goto cleanup;
if (virStorageBackendISCSIScanTargets(portal,
source->initiator.iqn,
&ntargets, &targets) < 0)
goto cleanup;
if (VIR_ALLOC_N(list.sources, ntargets) < 0) {
virReportOOMError();
goto cleanup;
}
for (i = 0 ; i < ntargets ; i++) {
if (VIR_ALLOC_N(list.sources[i].devices, 1) < 0) {
virReportOOMError();
goto cleanup;
}
list.sources[i].host = source->host;
list.sources[i].initiator = source->initiator;
list.sources[i].ndevice = 1;
list.sources[i].devices[0].path = targets[i];
list.nsources++;
}
if (!(ret = virStoragePoolSourceListFormat(&list))) {
virReportOOMError();
goto cleanup;
}
cleanup:
if (list.sources) {
for (i = 0 ; i < ntargets ; i++)
VIR_FREE(list.sources[i].devices);
VIR_FREE(list.sources);
}
for (i = 0 ; i < ntargets ; i++)
VIR_FREE(targets[i]);
VIR_FREE(targets);
VIR_FREE(portal);
virStoragePoolSourceFree(source);
return ret;
}
static int
virStorageBackendISCSICheckPool(virConnectPtr conn ATTRIBUTE_UNUSED,
virStoragePoolObjPtr pool,
bool *isActive)
{
char *session = NULL;
int ret = -1;
*isActive = false;
if (pool->def->source.host.name == NULL) {
virStorageReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("missing source host"));
return -1;
}
if (pool->def->source.ndevice != 1 ||
pool->def->source.devices[0].path == NULL) {
virStorageReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("missing source device"));
return -1;
}
if ((session = virStorageBackendISCSISession(pool, 1)) != NULL) {
*isActive = true;
VIR_FREE(session);
}
ret = 0;
return ret;
}
static int
virStorageBackendISCSIStartPool(virConnectPtr conn ATTRIBUTE_UNUSED,
virStoragePoolObjPtr pool)
{
char *portal = NULL;
char *session = NULL;
int ret = -1;
const char *loginargv[] = { "--login", NULL };
if (pool->def->source.host.name == NULL) {
virStorageReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("missing source host"));
return -1;
}
if (pool->def->source.ndevice != 1 ||
pool->def->source.devices[0].path == NULL) {
virStorageReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("missing source device"));
return -1;
}
if ((session = virStorageBackendISCSISession(pool, 1)) == NULL) {
if ((portal = virStorageBackendISCSIPortal(&pool->def->source)) == NULL)
goto cleanup;
/*
* iscsiadm doesn't let you login to a target, unless you've
* first issued a 'sendtargets' command to the portal :-(
*/
if (virStorageBackendISCSIScanTargets(portal,
pool->def->source.initiator.iqn,
NULL, NULL) < 0)
goto cleanup;
if (virStorageBackendISCSIConnection(portal,
pool->def->source.initiator.iqn,
pool->def->source.devices[0].path,
loginargv) < 0)
goto cleanup;
}
ret = 0;
cleanup:
VIR_FREE(session);
return ret;
}
static int
virStorageBackendISCSIRefreshPool(virConnectPtr conn ATTRIBUTE_UNUSED,
virStoragePoolObjPtr pool)
{
char *session = NULL;
pool->def->allocation = pool->def->capacity = pool->def->available = 0;
if ((session = virStorageBackendISCSISession(pool, 0)) == NULL)
goto cleanup;
if (virStorageBackendISCSIRescanLUNs(pool, session) < 0)
goto cleanup;
if (virStorageBackendISCSIFindLUs(pool, session) < 0)
goto cleanup;
VIR_FREE(session);
return 0;
cleanup:
VIR_FREE(session);
return -1;
}
static int
virStorageBackendISCSIStopPool(virConnectPtr conn ATTRIBUTE_UNUSED,
virStoragePoolObjPtr pool)
{
const char *logoutargv[] = { "--logout", NULL };
char *portal;
int ret = -1;
if ((portal = virStorageBackendISCSIPortal(&pool->def->source)) == NULL)
return -1;
if (virStorageBackendISCSIConnection(portal,
pool->def->source.initiator.iqn,
pool->def->source.devices[0].path,
logoutargv) < 0)
goto cleanup;
ret = 0;
cleanup:
VIR_FREE(portal);
return ret;
}
virStorageBackend virStorageBackendISCSI = {
.type = VIR_STORAGE_POOL_ISCSI,
.checkPool = virStorageBackendISCSICheckPool,
.startPool = virStorageBackendISCSIStartPool,
.refreshPool = virStorageBackendISCSIRefreshPool,
.stopPool = virStorageBackendISCSIStopPool,
.findPoolSources = virStorageBackendISCSIFindPoolSources,
};