mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-11-01 10:51:12 +00:00
08c0ea16fc
Rather than returning true/false and having the caller check if the vHBA was actually created, let's do that check within the CreateVport function. That way the caller can faithfully assume success based on a name start the thread looking for the LUNs. Prior to this change it's possible that the vHBA wasn't really created (e.g if the call to virVHBAGetHostByWWN returned NULL), we'd claim success, but in reality there'd be no vHBA for the pool. This also fixes a second yet seen issue that if the nodedev was present, but the parent by name wasn't provided (perhaps parent by wwnn/wwpn or by fabric_name), then a failure would be returned. For this path it shouldn't be an error - we should just be happy that something else is managing the device and we don't have to create/delete it. The end result is that the createVport code can now just start the refresh thread once it gets a non NULL name back. Signed-off-by: John Ferlan <jferlan@redhat.com>
384 lines
11 KiB
C
384 lines
11 KiB
C
/*
|
|
* storage_backend_scsi.c: storage backend for SCSI handling
|
|
*
|
|
* Copyright (C) 2007-2008, 2013-2014 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, see
|
|
* <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Author: Daniel P. Berrange <berrange redhat com>
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <fcntl.h>
|
|
|
|
#include "virerror.h"
|
|
#include "storage_backend_scsi.h"
|
|
#include "viralloc.h"
|
|
#include "virlog.h"
|
|
#include "virfile.h"
|
|
#include "vircommand.h"
|
|
#include "virstring.h"
|
|
#include "storage_util.h"
|
|
#include "node_device_conf.h"
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_STORAGE
|
|
|
|
VIR_LOG_INIT("storage.storage_backend_scsi");
|
|
|
|
#define LINUX_SYSFS_SCSI_HOST_PREFIX "/sys/class/scsi_host"
|
|
#define LINUX_SYSFS_SCSI_HOST_POSTFIX "device"
|
|
#define LINUX_SYSFS_SCSI_HOST_SCAN_STRING "- - -"
|
|
|
|
typedef struct _virStoragePoolFCRefreshInfo virStoragePoolFCRefreshInfo;
|
|
typedef virStoragePoolFCRefreshInfo *virStoragePoolFCRefreshInfoPtr;
|
|
struct _virStoragePoolFCRefreshInfo {
|
|
char *fchost_name;
|
|
unsigned char pool_uuid[VIR_UUID_BUFLEN];
|
|
};
|
|
|
|
|
|
static int
|
|
virStorageBackendSCSITriggerRescan(uint32_t host)
|
|
{
|
|
int fd = -1;
|
|
int retval = 0;
|
|
char *path;
|
|
|
|
VIR_DEBUG("Triggering rescan of host %d", host);
|
|
|
|
if (virAsprintf(&path, "%s/host%u/scan",
|
|
LINUX_SYSFS_SCSI_HOST_PREFIX, host) < 0) {
|
|
retval = -1;
|
|
goto out;
|
|
}
|
|
|
|
VIR_DEBUG("Scan trigger path is '%s'", path);
|
|
|
|
fd = open(path, O_WRONLY);
|
|
|
|
if (fd < 0) {
|
|
virReportSystemError(errno,
|
|
_("Could not open '%s' to trigger host scan"),
|
|
path);
|
|
retval = -1;
|
|
goto free_path;
|
|
}
|
|
|
|
if (safewrite(fd,
|
|
LINUX_SYSFS_SCSI_HOST_SCAN_STRING,
|
|
sizeof(LINUX_SYSFS_SCSI_HOST_SCAN_STRING)) < 0) {
|
|
VIR_FORCE_CLOSE(fd);
|
|
virReportSystemError(errno,
|
|
_("Write to '%s' to trigger host scan failed"),
|
|
path);
|
|
retval = -1;
|
|
}
|
|
|
|
VIR_FORCE_CLOSE(fd);
|
|
free_path:
|
|
VIR_FREE(path);
|
|
out:
|
|
VIR_DEBUG("Rescan of host %d complete", host);
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* Frees opaque data
|
|
*
|
|
* @opaque Data to be freed
|
|
*/
|
|
static void
|
|
virStoragePoolFCRefreshDataFree(void *opaque)
|
|
{
|
|
virStoragePoolFCRefreshInfoPtr cbdata = opaque;
|
|
|
|
VIR_FREE(cbdata->fchost_name);
|
|
VIR_FREE(cbdata);
|
|
}
|
|
|
|
/**
|
|
* Thread to handle the pool refresh after a VPORT_CREATE is done. In this
|
|
* case the 'udevEventHandleCallback' will be executed asynchronously as a
|
|
* result of the node device driver callback routine to handle when udev
|
|
* notices some sort of device change (such as adding a new device). It takes
|
|
* some amount of time (usually a few seconds) for udev to go through the
|
|
* process of setting up the new device. Unfortunately, there is nothing
|
|
* that says "when" it's done. The immediate virStorageBackendSCSIRefreshPool
|
|
* done after virStorageBackendSCSIStartPool (and createVport) occurs too
|
|
* quickly to find any devices.
|
|
*
|
|
* So this thread is designed to wait a few seconds (5), then make the query
|
|
* to find the LUs for the pool. If none yet exist, we'll try once more
|
|
* to find the LUs before giving up.
|
|
*
|
|
* Attempting to find devices prior to allowing udev to settle down may result
|
|
* in finding devices that then get deleted.
|
|
*
|
|
* @opaque Pool's Refresh Info containing name and pool object pointer
|
|
*/
|
|
static void
|
|
virStoragePoolFCRefreshThread(void *opaque)
|
|
{
|
|
virStoragePoolFCRefreshInfoPtr cbdata = opaque;
|
|
const char *fchost_name = cbdata->fchost_name;
|
|
const unsigned char *pool_uuid = cbdata->pool_uuid;
|
|
virStoragePoolObjPtr pool = NULL;
|
|
unsigned int host;
|
|
int found = 0;
|
|
int tries = 2;
|
|
|
|
do {
|
|
sleep(5); /* Give it time */
|
|
|
|
/* Let's see if the pool still exists - */
|
|
if (!(pool = virStoragePoolObjFindPoolByUUID(pool_uuid)))
|
|
break;
|
|
|
|
/* Return with pool lock, if active, we can get the host number,
|
|
* successfully, rescan, and find LUN's, then we are happy
|
|
*/
|
|
VIR_DEBUG("Attempt FC Refresh for pool='%s' name='%s' tries='%d'",
|
|
pool->def->name, fchost_name, tries);
|
|
|
|
pool->def->allocation = pool->def->capacity = pool->def->available = 0;
|
|
|
|
if (virStoragePoolObjIsActive(pool) &&
|
|
virSCSIHostGetNumber(fchost_name, &host) == 0 &&
|
|
virStorageBackendSCSITriggerRescan(host) == 0) {
|
|
virStoragePoolObjClearVols(pool);
|
|
found = virStorageBackendSCSIFindLUs(pool, host);
|
|
}
|
|
virStoragePoolObjUnlock(pool);
|
|
} while (!found && --tries);
|
|
|
|
if (pool && !found)
|
|
VIR_DEBUG("FC Refresh Thread failed to find LU's");
|
|
|
|
virStoragePoolFCRefreshDataFree(cbdata);
|
|
}
|
|
|
|
static char *
|
|
getAdapterName(virStorageAdapterPtr adapter)
|
|
{
|
|
char *name = NULL;
|
|
char *parentaddr = NULL;
|
|
|
|
if (adapter->type == VIR_STORAGE_ADAPTER_TYPE_SCSI_HOST) {
|
|
virStorageAdapterSCSIHostPtr scsi_host = &adapter->data.scsi_host;
|
|
|
|
if (scsi_host->has_parent) {
|
|
virPCIDeviceAddressPtr addr = &scsi_host->parentaddr;
|
|
unsigned int unique_id = scsi_host->unique_id;
|
|
|
|
if (!(name = virSCSIHostGetNameByParentaddr(addr->domain,
|
|
addr->bus,
|
|
addr->slot,
|
|
addr->function,
|
|
unique_id)))
|
|
goto cleanup;
|
|
} else {
|
|
ignore_value(VIR_STRDUP(name, scsi_host->name));
|
|
}
|
|
} else if (adapter->type == VIR_STORAGE_ADAPTER_TYPE_FC_HOST) {
|
|
virStorageAdapterFCHostPtr fchost = &adapter->data.fchost;
|
|
|
|
if (!(name = virVHBAGetHostByWWN(NULL, fchost->wwnn, fchost->wwpn))) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("Failed to find SCSI host with wwnn='%s', "
|
|
"wwpn='%s'"), fchost->wwnn, fchost->wwpn);
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
VIR_FREE(parentaddr);
|
|
return name;
|
|
}
|
|
|
|
|
|
static int
|
|
createVport(virConnectPtr conn,
|
|
virStoragePoolDefPtr def,
|
|
const char *configFile,
|
|
virStorageAdapterFCHostPtr fchost)
|
|
{
|
|
char *name = NULL;
|
|
virStoragePoolFCRefreshInfoPtr cbdata = NULL;
|
|
virThread thread;
|
|
int ret = -1;
|
|
|
|
VIR_DEBUG("conn=%p, configFile='%s' parent='%s', wwnn='%s' wwpn='%s'",
|
|
conn, NULLSTR(configFile), NULLSTR(fchost->parent),
|
|
fchost->wwnn, fchost->wwpn);
|
|
|
|
|
|
/* Since we're creating the vHBA, then we need to manage removing it
|
|
* as well. Since we need this setting to "live" through a libvirtd
|
|
* restart, we need to save the persistent configuration. So if not
|
|
* already defined as YES, then force the issue.
|
|
*/
|
|
if (fchost->managed != VIR_TRISTATE_BOOL_YES) {
|
|
fchost->managed = VIR_TRISTATE_BOOL_YES;
|
|
if (configFile) {
|
|
if (virStoragePoolSaveConfig(configFile, def) < 0)
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if (!(name = virNodeDeviceCreateVport(conn, fchost)))
|
|
goto cleanup;
|
|
|
|
/* Creating our own VPORT didn't leave enough time to find any LUN's,
|
|
* so, let's create a thread whose job it is to call the FindLU's with
|
|
* retry logic set to true. If the thread isn't created, then no big
|
|
* deal since it's still possible to refresh the pool later.
|
|
*/
|
|
if (VIR_ALLOC(cbdata) == 0) {
|
|
memcpy(cbdata->pool_uuid, def->uuid, VIR_UUID_BUFLEN);
|
|
VIR_STEAL_PTR(cbdata->fchost_name, name);
|
|
|
|
if (virThreadCreate(&thread, false, virStoragePoolFCRefreshThread,
|
|
cbdata) < 0) {
|
|
/* Oh well - at least someone can still refresh afterwards */
|
|
VIR_DEBUG("Failed to create FC Pool Refresh Thread");
|
|
virStoragePoolFCRefreshDataFree(cbdata);
|
|
}
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
VIR_FREE(name);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
virStorageBackendSCSICheckPool(virStoragePoolObjPtr pool,
|
|
bool *isActive)
|
|
{
|
|
char *path = NULL;
|
|
char *name = NULL;
|
|
unsigned int host;
|
|
int ret = -1;
|
|
|
|
*isActive = false;
|
|
|
|
if (!(name = getAdapterName(&pool->def->source.adapter))) {
|
|
/* It's normal for the pool with "fc_host" type source
|
|
* adapter fails to get the adapter name, since the vHBA
|
|
* the adapter based on might be not created yet.
|
|
*/
|
|
if (pool->def->source.adapter.type ==
|
|
VIR_STORAGE_ADAPTER_TYPE_FC_HOST) {
|
|
virResetLastError();
|
|
return 0;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (virSCSIHostGetNumber(name, &host) < 0)
|
|
goto cleanup;
|
|
|
|
if (virAsprintf(&path, "%s/host%d",
|
|
LINUX_SYSFS_SCSI_HOST_PREFIX, host) < 0)
|
|
goto cleanup;
|
|
|
|
*isActive = virFileExists(path);
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
VIR_FREE(path);
|
|
VIR_FREE(name);
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
virStorageBackendSCSIRefreshPool(virConnectPtr conn ATTRIBUTE_UNUSED,
|
|
virStoragePoolObjPtr pool)
|
|
{
|
|
char *name = NULL;
|
|
unsigned int host;
|
|
int ret = -1;
|
|
|
|
pool->def->allocation = pool->def->capacity = pool->def->available = 0;
|
|
|
|
if (!(name = getAdapterName(&pool->def->source.adapter)))
|
|
return -1;
|
|
|
|
if (virSCSIHostGetNumber(name, &host) < 0)
|
|
goto out;
|
|
|
|
VIR_DEBUG("Scanning host%u", host);
|
|
|
|
if (virStorageBackendSCSITriggerRescan(host) < 0)
|
|
goto out;
|
|
|
|
if (virStorageBackendSCSIFindLUs(pool, host) < 0)
|
|
goto out;
|
|
|
|
ret = 0;
|
|
out:
|
|
VIR_FREE(name);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
virStorageBackendSCSIStartPool(virConnectPtr conn,
|
|
virStoragePoolObjPtr pool)
|
|
{
|
|
if (pool->def->source.adapter.type == VIR_STORAGE_ADAPTER_TYPE_FC_HOST)
|
|
return createVport(conn, pool->def, pool->configFile,
|
|
&pool->def->source.adapter.data.fchost);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virStorageBackendSCSIStopPool(virConnectPtr conn,
|
|
virStoragePoolObjPtr pool)
|
|
{
|
|
if (pool->def->source.adapter.type == VIR_STORAGE_ADAPTER_TYPE_FC_HOST)
|
|
return virNodeDeviceDeleteVport(conn,
|
|
&pool->def->source.adapter.data.fchost);
|
|
|
|
return 0;
|
|
}
|
|
|
|
virStorageBackend virStorageBackendSCSI = {
|
|
.type = VIR_STORAGE_POOL_SCSI,
|
|
|
|
.checkPool = virStorageBackendSCSICheckPool,
|
|
.refreshPool = virStorageBackendSCSIRefreshPool,
|
|
.startPool = virStorageBackendSCSIStartPool,
|
|
.stopPool = virStorageBackendSCSIStopPool,
|
|
.uploadVol = virStorageBackendVolUploadLocal,
|
|
.downloadVol = virStorageBackendVolDownloadLocal,
|
|
.wipeVol = virStorageBackendVolWipeLocal,
|
|
};
|
|
|
|
|
|
int
|
|
virStorageBackendSCSIRegister(void)
|
|
{
|
|
return virStorageBackendRegister(&virStorageBackendSCSI);
|
|
}
|