libvirt/src/storage/storage_backend_scsi.c
Daniel P. Berrangé 31869efe2a storage: remove virConnectPtr from all backend functions
Now that we can open connections to the secondary drivers on demand,
there is no need to pass a virConnectPtr into all the backend
functions.

Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
2018-01-31 18:34:48 +00:00

498 lines
15 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"
#include "driver.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;
virStoragePoolDefPtr def;
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;
def = virStoragePoolObjGetDef(pool);
/* 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'",
def->name, fchost_name, tries);
def->allocation = def->capacity = def->available = 0;
if (virStoragePoolObjIsActive(pool) &&
virSCSIHostGetNumber(fchost_name, &host) == 0 &&
virStorageBackendSCSITriggerRescan(host) == 0) {
virStoragePoolObjClearVols(pool);
found = virStorageBackendSCSIFindLUs(pool, host);
}
virStoragePoolObjEndAPI(&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;
}
/**
* @name: Name from a wwnn/wwpn lookup
*
* Validate that the @name fetched from the wwnn/wwpn is a vHBA
* and not an HBA as that should be a configuration error. It's only
* possible to use an existing wwnn/wwpn of a vHBA because that's
* what someone would have created using the node device create via XML
* functionality. Using the HBA "just because" it has a wwnn/wwpn and
* the characteristics of a vHBA is just not valid
*
* Returns true if the @name is OK, false on error
*/
static bool
checkName(const char *name)
{
unsigned int host_num;
if (virSCSIHostGetNumber(name, &host_num) &&
virVHBAIsVportCapable(NULL, host_num))
return true;
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("the wwnn/wwpn for '%s' are assigned to an HBA"), name);
return false;
}
/*
* Using the host# name found via wwnn/wwpn lookup in the fc_host
* sysfs tree to get the parent 'scsi_host#' to ensure it matches.
*/
static bool
checkParent(const char *name,
const char *parent_name)
{
unsigned int host_num;
char *scsi_host_name = NULL;
char *vhba_parent = NULL;
bool retval = false;
virConnectPtr conn = NULL;
VIR_DEBUG("name=%s, parent_name=%s", name, parent_name);
conn = virGetConnectNodeDev();
if (!conn)
goto cleanup;
if (virSCSIHostGetNumber(parent_name, &host_num) < 0) {
virReportError(VIR_ERR_XML_ERROR,
_("parent '%s' is not properly formatted"),
parent_name);
goto cleanup;
}
if (!virVHBAPathExists(NULL, host_num)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("parent '%s' is not an fc_host for the wwnn/wwpn"),
parent_name);
goto cleanup;
}
if (virAsprintf(&scsi_host_name, "scsi_%s", name) < 0)
goto cleanup;
if (!(vhba_parent = virNodeDeviceGetParentName(conn, scsi_host_name)))
goto cleanup;
if (STRNEQ(parent_name, vhba_parent)) {
virReportError(VIR_ERR_XML_ERROR,
_("Parent attribute '%s' does not match parent '%s' "
"determined for the '%s' wwnn/wwpn lookup."),
parent_name, vhba_parent, name);
goto cleanup;
}
retval = true;
cleanup:
virObjectUnref(conn);
VIR_FREE(vhba_parent);
VIR_FREE(scsi_host_name);
return retval;
}
static int
createVport(virStoragePoolDefPtr def,
const char *configFile,
virStorageAdapterFCHostPtr fchost)
{
char *name = NULL;
virStoragePoolFCRefreshInfoPtr cbdata = NULL;
virThread thread;
int ret = -1;
VIR_DEBUG("configFile='%s' parent='%s', wwnn='%s' wwpn='%s'",
NULLSTR(configFile), NULLSTR(fchost->parent),
fchost->wwnn, fchost->wwpn);
/* If we find an existing HBA/vHBA within the fc_host sysfs
* using the wwnn/wwpn, then a nodedev is already created for
* this pool and we don't have to create the vHBA
*/
if ((name = virVHBAGetHostByWWN(NULL, fchost->wwnn, fchost->wwpn))) {
if (!(checkName(name)))
goto cleanup;
/* If a parent was provided, let's make sure the 'name' we've
* retrieved has the same parent. If not this will cause failure. */
if (!fchost->parent || checkParent(name, fchost->parent))
ret = 0;
goto cleanup;
}
/* 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(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)
{
virStoragePoolDefPtr def = virStoragePoolObjGetDef(pool);
char *path = NULL;
char *name = NULL;
unsigned int host;
int ret = -1;
*isActive = false;
if (!(name = getAdapterName(&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 (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(virStoragePoolObjPtr pool)
{
virStoragePoolDefPtr def = virStoragePoolObjGetDef(pool);
char *name = NULL;
unsigned int host;
int ret = -1;
def->allocation = def->capacity = def->available = 0;
if (!(name = getAdapterName(&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(virStoragePoolObjPtr pool)
{
virStoragePoolDefPtr def = virStoragePoolObjGetDef(pool);
const char *configFile = virStoragePoolObjGetConfigFile(pool);
if (def->source.adapter.type == VIR_STORAGE_ADAPTER_TYPE_FC_HOST)
return createVport(def, configFile,
&def->source.adapter.data.fchost);
return 0;
}
static int
virStorageBackendSCSIStopPool(virStoragePoolObjPtr pool)
{
virStoragePoolDefPtr def = virStoragePoolObjGetDef(pool);
if (def->source.adapter.type == VIR_STORAGE_ADAPTER_TYPE_FC_HOST) {
virConnectPtr conn;
int ret;
conn = virGetConnectNodeDev();
if (!conn)
return -1;
ret = virNodeDeviceDeleteVport(conn,
&def->source.adapter.data.fchost);
virObjectUnref(conn);
return ret;
}
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);
}