storage_backend_scsi.c 17.5 KB
Newer Older
1 2 3
/*
 * storage_backend_scsi.c: storage backend for SCSI handling
 *
C
Cédric Bosdonnat 已提交
4
 * Copyright (C) 2007-2008, 2013-2014 Red Hat, Inc.
5 6 7 8 9 10 11 12 13 14 15 16 17
 * 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
18
 * License along with this library.  If not, see
O
Osier Yang 已提交
19
 * <http://www.gnu.org/licenses/>.
20 21 22 23 24 25 26 27 28 29
 *
 * Author: Daniel P. Berrange <berrange redhat com>
 */

#include <config.h>

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>

30
#include "virerror.h"
31
#include "storage_backend_scsi.h"
32
#include "viralloc.h"
33
#include "virlog.h"
E
Eric Blake 已提交
34
#include "virfile.h"
35
#include "vircommand.h"
36
#include "virscsihost.h"
37
#include "virstring.h"
38
#include "virvhba.h"
39
#include "storage_util.h"
40
#include "node_device_conf.h"
41 42 43

#define VIR_FROM_THIS VIR_FROM_STORAGE

44 45
VIR_LOG_INIT("storage.storage_backend_scsi");

46 47 48 49
#define LINUX_SYSFS_SCSI_HOST_PREFIX "/sys/class/scsi_host"
#define LINUX_SYSFS_SCSI_HOST_POSTFIX "device"
#define LINUX_SYSFS_SCSI_HOST_SCAN_STRING "- - -"

50 51 52
typedef struct _virStoragePoolFCRefreshInfo virStoragePoolFCRefreshInfo;
typedef virStoragePoolFCRefreshInfo *virStoragePoolFCRefreshInfoPtr;
struct _virStoragePoolFCRefreshInfo {
53
    char *fchost_name;
54
    unsigned char pool_uuid[VIR_UUID_BUFLEN];
55 56
};

57

58
static int
59
virStorageBackendSCSITriggerRescan(uint32_t host)
60 61 62 63 64
{
    int fd = -1;
    int retval = 0;
    char *path;

65
    VIR_DEBUG("Triggering rescan of host %d", host);
66

67 68
    if (virAsprintf(&path, "%s/host%u/scan",
                    LINUX_SYSFS_SCSI_HOST_PREFIX, host) < 0) {
69 70 71 72
        retval = -1;
        goto out;
    }

73
    VIR_DEBUG("Scan trigger path is '%s'", path);
74 75 76 77

    fd = open(path, O_WRONLY);

    if (fd < 0) {
78
        virReportSystemError(errno,
79 80 81 82 83 84 85 86 87
                             _("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) {
88
        VIR_FORCE_CLOSE(fd);
89
        virReportSystemError(errno,
90 91 92 93 94
                             _("Write to '%s' to trigger host scan failed"),
                             path);
        retval = -1;
    }

95
    VIR_FORCE_CLOSE(fd);
96
 free_path:
97
    VIR_FREE(path);
98
 out:
99
    VIR_DEBUG("Rescan of host %d complete", host);
100 101 102
    return retval;
}

103 104 105 106 107 108 109 110 111 112
/**
 * Frees opaque data
 *
 * @opaque Data to be freed
 */
static void
virStoragePoolFCRefreshDataFree(void *opaque)
{
    virStoragePoolFCRefreshInfoPtr cbdata = opaque;

113
    VIR_FREE(cbdata->fchost_name);
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
    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;
141
    const char *fchost_name = cbdata->fchost_name;
142 143
    const unsigned char *pool_uuid = cbdata->pool_uuid;
    virStoragePoolObjPtr pool = NULL;
144
    unsigned int host;
J
John Ferlan 已提交
145
    int found = 0;
146 147 148 149 150
    int tries = 2;

    do {
        sleep(5); /* Give it time */

151 152 153 154 155 156
        /* 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
157 158
         */
        VIR_DEBUG("Attempt FC Refresh for pool='%s' name='%s' tries='%d'",
159
                  pool->def->name, fchost_name, tries);
160 161 162

        pool->def->allocation = pool->def->capacity = pool->def->available = 0;

163
        if (virStoragePoolObjIsActive(pool) &&
164
            virSCSIHostGetNumber(fchost_name, &host) == 0 &&
165 166
            virStorageBackendSCSITriggerRescan(host) == 0) {
            virStoragePoolObjClearVols(pool);
167
            found = virStorageBackendSCSIFindLUs(pool, host);
168 169 170 171
        }
        virStoragePoolObjUnlock(pool);
    } while (!found && --tries);

172
    if (pool && !found)
173 174 175 176 177
        VIR_DEBUG("FC Refresh Thread failed to find LU's");

    virStoragePoolFCRefreshDataFree(cbdata);
}

178 179 180 181
static char *
getAdapterName(virStoragePoolSourceAdapter adapter)
{
    char *name = NULL;
182
    char *parentaddr = NULL;
183

184
    if (adapter.type == VIR_STORAGE_POOL_SOURCE_ADAPTER_TYPE_SCSI_HOST) {
185
        if (adapter.data.scsi_host.has_parent) {
186
            virPCIDeviceAddress addr = adapter.data.scsi_host.parentaddr;
187 188
            unsigned int unique_id = adapter.data.scsi_host.unique_id;

189
            if (!(name = virSCSIHostGetNameByParentaddr(addr.domain,
190 191 192 193
                                                        addr.bus,
                                                        addr.slot,
                                                        addr.function,
                                                        unique_id)))
194 195 196 197
                goto cleanup;
        } else {
            ignore_value(VIR_STRDUP(name, adapter.data.scsi_host.name));
        }
198
    } else if (adapter.type == VIR_STORAGE_POOL_SOURCE_ADAPTER_TYPE_FC_HOST) {
199 200 201
        if (!(name = virVHBAGetHostByWWN(NULL,
                                         adapter.data.fchost.wwnn,
                                         adapter.data.fchost.wwpn))) {
202 203 204 205 206
            virReportError(VIR_ERR_XML_ERROR,
                           _("Failed to find SCSI host with wwnn='%s', "
                             "wwpn='%s'"), adapter.data.fchost.wwnn,
                           adapter.data.fchost.wwpn);
        }
207 208
    }

209 210
 cleanup:
    VIR_FREE(parentaddr);
211 212 213
    return name;
}

214 215 216 217 218
/*
 * 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
219 220 221
checkParent(virConnectPtr conn,
            const char *name,
            const char *parent_name)
222
{
223
    char *scsi_host_name = NULL;
224 225 226 227 228 229 230 231 232
    char *vhba_parent = NULL;
    bool retval = false;

    VIR_DEBUG("conn=%p, name=%s, parent_name=%s", conn, name, parent_name);

    /* autostarted pool - assume we're OK */
    if (!conn)
        return true;

233 234 235 236
    if (virAsprintf(&scsi_host_name, "scsi_%s", name) < 0)
        goto cleanup;

    if (!(vhba_parent = virNodeDeviceGetParentName(conn, scsi_host_name)))
237 238 239 240 241 242 243 244 245 246 247 248 249 250
        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:
    VIR_FREE(vhba_parent);
251
    VIR_FREE(scsi_host_name);
252 253 254
    return retval;
}

255
static int
256
createVport(virConnectPtr conn,
257
            virStoragePoolObjPtr pool)
258
{
259 260
    const char *configFile = pool->configFile;
    virStoragePoolSourceAdapterPtr adapter = &pool->def->source.adapter;
261 262
    unsigned int parent_host;
    char *name = NULL;
263
    char *parent_hoststr = NULL;
264
    bool skip_capable_check = false;
265 266
    virStoragePoolFCRefreshInfoPtr cbdata = NULL;
    virThread thread;
267
    int ret = -1;
268

269
    if (adapter->type != VIR_STORAGE_POOL_SOURCE_ADAPTER_TYPE_FC_HOST)
270 271
        return 0;

272 273 274 275
    VIR_DEBUG("conn=%p, configFile='%s' parent='%s', wwnn='%s' wwpn='%s'",
              conn, NULLSTR(configFile), NULLSTR(adapter->data.fchost.parent),
              adapter->data.fchost.wwnn, adapter->data.fchost.wwpn);

276 277 278 279
    /* 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
     */
280 281
    if ((name = virVHBAGetHostByWWN(NULL, adapter->data.fchost.wwnn,
                                    adapter->data.fchost.wwpn))) {
282 283 284
        /* If a parent was provided, let's make sure the 'name' we've
         * retrieved has the same parent
         */
285
        if (adapter->data.fchost.parent &&
286
            checkParent(conn, name, adapter->data.fchost.parent))
287
            ret = 0;
288

289
        goto cleanup;
290 291
    }

292
    if (adapter->data.fchost.parent) {
293
        if (VIR_STRDUP(parent_hoststr, adapter->data.fchost.parent) < 0)
294
            goto cleanup;
295 296 297
    } else if (adapter->data.fchost.parent_wwnn &&
               adapter->data.fchost.parent_wwpn) {
        if (!(parent_hoststr =
298 299
              virVHBAGetHostByWWN(NULL, adapter->data.fchost.parent_wwnn,
                                  adapter->data.fchost.parent_wwpn))) {
300 301 302 303 304 305
            virReportError(VIR_ERR_XML_ERROR, "%s",
                           _("cannot find parent using provided wwnn/wwpn"));
            goto cleanup;
        }
    } else if (adapter->data.fchost.parent_fabric_wwn) {
        if (!(parent_hoststr =
306 307
              virVHBAGetHostByFabricWWN(NULL,
                                        adapter->data.fchost.parent_fabric_wwn))) {
308 309 310 311
            virReportError(VIR_ERR_XML_ERROR, "%s",
                           _("cannot find parent using provided fabric_wwn"));
            goto cleanup;
        }
312
    } else {
313
        if (!(parent_hoststr = virVHBAFindVportHost(NULL))) {
314 315 316
            virReportError(VIR_ERR_XML_ERROR, "%s",
                           _("'parent' for vHBA not specified, and "
                             "cannot find one on this host"));
317
            goto cleanup;
318
        }
319
        skip_capable_check = true;
320
    }
321

322
    if (virSCSIHostGetNumber(parent_hoststr, &parent_host) < 0)
323
        goto cleanup;
324

325 326 327 328 329 330 331 332
    /* NOTE:
     * We do not save the parent_hoststr in adapter->data.fchost.parent
     * since we could be writing out the 'def' to the saved XML config.
     * If we wrote out the name in the XML, then future starts would
     * always use the same parent rather than finding the "best available"
     * parent. Besides we have a way to determine the parent based on
     * the 'name' field.
     */
333
    if (!skip_capable_check && !virVHBAPathExists(NULL, parent_host)) {
334
        virReportError(VIR_ERR_XML_ERROR,
335
                       _("parent '%s' specified for vHBA does not exist"),
336 337
                       parent_hoststr);
        goto cleanup;
338 339 340 341 342 343 344 345 346 347
    }

    /* 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 (adapter->data.fchost.managed != VIR_TRISTATE_BOOL_YES) {
        adapter->data.fchost.managed = VIR_TRISTATE_BOOL_YES;
        if (configFile) {
348
            if (virStoragePoolSaveConfig(configFile, pool->def) < 0)
349
                goto cleanup;
350
        }
351
    }
352

353 354
    if (virVHBAManageVport(parent_host, adapter->data.fchost.wwpn,
                           adapter->data.fchost.wwnn, VPORT_CREATE) < 0)
355
        goto cleanup;
356 357

    virFileWaitForDevices();
358 359 360 361 362 363

    /* 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.
     */
364 365
    if ((name = virVHBAGetHostByWWN(NULL, adapter->data.fchost.wwnn,
                                    adapter->data.fchost.wwpn))) {
366
        if (VIR_ALLOC(cbdata) == 0) {
367
            memcpy(cbdata->pool_uuid, pool->def->uuid, VIR_UUID_BUFLEN);
368
            VIR_STEAL_PTR(cbdata->fchost_name, name);
369 370 371 372 373 374 375 376 377 378

            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);
            }
        }
    }

379 380 381 382 383 384
    ret = 0;

 cleanup:
    VIR_FREE(name);
    VIR_FREE(parent_hoststr);
    return ret;
385 386 387
}

static int
388 389
deleteVport(virConnectPtr conn,
            virStoragePoolSourceAdapter adapter)
390 391
{
    unsigned int parent_host;
O
Osier Yang 已提交
392
    char *name = NULL;
393
    char *scsi_host_name = NULL;
394
    char *vhba_parent = NULL;
O
Osier Yang 已提交
395
    int ret = -1;
396 397 398 399

    if (adapter.type != VIR_STORAGE_POOL_SOURCE_ADAPTER_TYPE_FC_HOST)
        return 0;

400 401 402 403 404 405 406 407
    VIR_DEBUG("conn=%p parent='%s', managed='%d' wwnn='%s' wwpn='%s'",
              conn, NULLSTR(adapter.data.fchost.parent),
              adapter.data.fchost.managed,
              adapter.data.fchost.wwnn,
              adapter.data.fchost.wwpn);

    /* If we're not managing the deletion of the vHBA, then just return */
    if (adapter.data.fchost.managed != VIR_TRISTATE_BOOL_YES)
408 409
        return 0;

410
    /* Find our vHBA by searching the fc_host sysfs tree for our wwnn/wwpn */
411 412
    if (!(name = virVHBAGetHostByWWN(NULL, adapter.data.fchost.wwnn,
                                     adapter.data.fchost.wwpn))) {
413 414 415
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Failed to find fc_host for wwnn='%s' and wwpn='%s'"),
                       adapter.data.fchost.wwnn, adapter.data.fchost.wwpn);
O
Osier Yang 已提交
416
        goto cleanup;
417 418 419 420 421 422 423
    }

    /* If at startup time we provided a parent, then use that to
     * get the parent_host value; otherwise, we have to determine
     * the parent scsi_host which we did not save at startup time
     */
    if (adapter.data.fchost.parent) {
424
        if (virSCSIHostGetNumber(adapter.data.fchost.parent, &parent_host) < 0)
425 426
            goto cleanup;
    } else {
427 428 429 430
        if (virAsprintf(&scsi_host_name, "scsi_%s", name) < 0)
            goto cleanup;

        if (!(vhba_parent = virNodeDeviceGetParentName(conn, scsi_host_name)))
431 432
            goto cleanup;

433
        if (virSCSIHostGetNumber(vhba_parent, &parent_host) < 0)
434 435
            goto cleanup;
    }
436

437 438
    if (virVHBAManageVport(parent_host, adapter.data.fchost.wwpn,
                           adapter.data.fchost.wwnn, VPORT_DELETE) < 0)
O
Osier Yang 已提交
439
        goto cleanup;
440

O
Osier Yang 已提交
441
    ret = 0;
442
 cleanup:
O
Osier Yang 已提交
443
    VIR_FREE(name);
444
    VIR_FREE(vhba_parent);
445
    VIR_FREE(scsi_host_name);
O
Osier Yang 已提交
446
    return ret;
447 448 449
}


450
static int
451
virStorageBackendSCSICheckPool(virStoragePoolObjPtr pool,
452 453
                               bool *isActive)
{
454 455
    char *path = NULL;
    char *name = NULL;
456
    unsigned int host;
457
    int ret = -1;
458 459

    *isActive = false;
460

461 462 463 464 465 466 467 468 469 470 471 472 473
    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_POOL_SOURCE_ADAPTER_TYPE_FC_HOST) {
            virResetLastError();
            return 0;
        } else {
            return -1;
        }
    }
474

475
    if (virSCSIHostGetNumber(name, &host) < 0)
476 477
        goto cleanup;

478 479
    if (virAsprintf(&path, "%s/host%d",
                    LINUX_SYSFS_SCSI_HOST_PREFIX, host) < 0)
480
        goto cleanup;
481

482
    *isActive = virFileExists(path);
483

484
    ret = 0;
485
 cleanup:
486
    VIR_FREE(path);
487 488
    VIR_FREE(name);
    return ret;
489
}
490

491
static int
492
virStorageBackendSCSIRefreshPool(virConnectPtr conn ATTRIBUTE_UNUSED,
493 494
                                 virStoragePoolObjPtr pool)
{
495
    char *name = NULL;
496
    unsigned int host;
497
    int ret = -1;
498 499 500

    pool->def->allocation = pool->def->capacity = pool->def->available = 0;

501 502 503
    if (!(name = getAdapterName(pool->def->source.adapter)))
        return -1;

504
    if (virSCSIHostGetNumber(name, &host) < 0)
505 506
        goto out;

507
    VIR_DEBUG("Scanning host%u", host);
508

509
    if (virStorageBackendSCSITriggerRescan(host) < 0)
510 511
        goto out;

512 513
    if (virStorageBackendSCSIFindLUs(pool, host) < 0)
        goto out;
514

515
    ret = 0;
516
 out:
517
    VIR_FREE(name);
518
    return ret;
519 520
}

521
static int
522
virStorageBackendSCSIStartPool(virConnectPtr conn,
523 524
                               virStoragePoolObjPtr pool)
{
525
    return createVport(conn, pool);
526 527 528
}

static int
529
virStorageBackendSCSIStopPool(virConnectPtr conn,
530 531 532
                              virStoragePoolObjPtr pool)
{
    virStoragePoolSourceAdapter adapter = pool->def->source.adapter;
533
    return deleteVport(conn, adapter);
534
}
535 536 537 538

virStorageBackend virStorageBackendSCSI = {
    .type = VIR_STORAGE_POOL_SCSI,

539
    .checkPool = virStorageBackendSCSICheckPool,
540
    .refreshPool = virStorageBackendSCSIRefreshPool,
541 542
    .startPool = virStorageBackendSCSIStartPool,
    .stopPool = virStorageBackendSCSIStopPool,
543 544
    .uploadVol = virStorageBackendVolUploadLocal,
    .downloadVol = virStorageBackendVolDownloadLocal,
545
    .wipeVol = virStorageBackendVolWipeLocal,
546
};
547 548 549 550 551 552 553


int
virStorageBackendSCSIRegister(void)
{
    return virStorageBackendRegister(&virStorageBackendSCSI);
}