qemu_block.c 17.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
/*
 * qemu_block.c: helper functions for QEMU block subsystem
 *
 * 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/>.
 */

#include <config.h>

#include "qemu_block.h"
#include "qemu_domain.h"

#include "viralloc.h"
#include "virstring.h"

#define VIR_FROM_THIS VIR_FROM_QEMU


30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
static int
qemuBlockNamedNodesArrayToHash(size_t pos ATTRIBUTE_UNUSED,
                               virJSONValuePtr item,
                               void *opaque)
{
    virHashTablePtr table = opaque;
    const char *name;

    if (!(name = virJSONValueObjectGetString(item, "node-name")))
        return 1;

    if (virHashAddEntry(table, name, item) < 0)
        return -1;

    return 0;
}


48 49 50 51 52 53 54 55 56 57
static void
qemuBlockNodeNameBackingChainDataFree(qemuBlockNodeNameBackingChainDataPtr data)
{
    if (!data)
        return;

    VIR_FREE(data->nodeformat);
    VIR_FREE(data->nodestorage);

    VIR_FREE(data->qemufilename);
58

59 60 61
    VIR_FREE(data->drvformat);
    VIR_FREE(data->drvstorage);

62
    qemuBlockNodeNameBackingChainDataFree(data->backing);
63 64 65 66 67 68 69 70 71 72 73 74 75

    VIR_FREE(data);
}


static void
qemuBlockNodeNameBackingChainDataHashEntryFree(void *opaque,
                                               const void *name ATTRIBUTE_UNUSED)
{
    qemuBlockNodeNameBackingChainDataFree(opaque);
}


76 77 78 79
/* list of driver names of layers that qemu automatically adds into the
 * backing chain */
static const char *qemuBlockDriversBlockjob[] = {
    "mirror_top", "commit_top", NULL };
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95

static bool
qemuBlockDriverMatch(const char *drvname,
                     const char **drivers)
{
    while (*drivers) {
        if (STREQ(drvname, *drivers))
            return true;

        drivers++;
    }

    return false;
}


96 97 98 99 100 101
struct qemuBlockNodeNameGetBackingChainData {
    virHashTablePtr nodenamestable;
    virHashTablePtr disks;
};


102
static int
103 104 105
qemuBlockNodeNameGetBackingChainBacking(virJSONValuePtr next,
                                        virHashTablePtr nodenamestable,
                                        qemuBlockNodeNameBackingChainDataPtr *nodenamedata)
106
{
107 108 109 110 111 112 113
    qemuBlockNodeNameBackingChainDataPtr data = NULL;
    qemuBlockNodeNameBackingChainDataPtr backingdata = NULL;
    virJSONValuePtr backing = virJSONValueObjectGetObject(next, "backing");
    virJSONValuePtr parent = virJSONValueObjectGetObject(next, "parent");
    virJSONValuePtr parentnodedata;
    virJSONValuePtr nodedata;
    const char *nodename = virJSONValueObjectGetString(next, "node-name");
114 115
    const char *drvname = NULL;
    const char *drvparent = NULL;
116 117 118
    const char *parentnodename = NULL;
    const char *filename = NULL;
    int ret = -1;
119

120
    if (!nodename)
121 122
        return 0;

123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
    if ((nodedata = virHashLookup(nodenamestable, nodename)) &&
        (drvname = virJSONValueObjectGetString(nodedata, "drv"))) {

        /* qemu 2.9 reports layers in the backing chain which don't correspond
         * to files. skip them */
        if (qemuBlockDriverMatch(drvname, qemuBlockDriversBlockjob)) {
            if (backing) {
                return qemuBlockNodeNameGetBackingChainBacking(backing,
                                                               nodenamestable,
                                                               nodenamedata);
            } else {
                return 0;
            }
        }
    }
138

139 140
    if (parent &&
        (parentnodename = virJSONValueObjectGetString(parent, "node-name"))) {
141
        if ((parentnodedata = virHashLookup(nodenamestable, parentnodename))) {
142
            filename = virJSONValueObjectGetString(parentnodedata, "file");
143 144
            drvparent = virJSONValueObjectGetString(parentnodedata, "drv");
        }
145
    }
146

147 148
    if (VIR_ALLOC(data) < 0)
        goto cleanup;
149

150 151
    if (VIR_STRDUP(data->nodeformat, nodename) < 0 ||
        VIR_STRDUP(data->nodestorage, parentnodename) < 0 ||
152 153 154
        VIR_STRDUP(data->qemufilename, filename) < 0 ||
        VIR_STRDUP(data->drvformat, drvname) < 0 ||
        VIR_STRDUP(data->drvstorage, drvparent) < 0)
155
        goto cleanup;
156

157 158 159 160
    if (backing &&
        qemuBlockNodeNameGetBackingChainBacking(backing, nodenamestable,
                                                &backingdata) < 0)
        goto cleanup;
161

162 163
    VIR_STEAL_PTR(data->backing, backingdata);
    VIR_STEAL_PTR(*nodenamedata, data);
164

165 166 167 168 169
    ret = 0;

 cleanup:
    qemuBlockNodeNameBackingChainDataFree(data);
    return ret;
170 171 172 173
}


static int
174 175 176
qemuBlockNodeNameGetBackingChainDisk(size_t pos ATTRIBUTE_UNUSED,
                                     virJSONValuePtr item,
                                     void *opaque)
177
{
178 179 180 181
    struct qemuBlockNodeNameGetBackingChainData *data = opaque;
    const char *device = virJSONValueObjectGetString(item, "device");
    qemuBlockNodeNameBackingChainDataPtr devicedata = NULL;
    int ret = -1;
182

183 184 185
    if (qemuBlockNodeNameGetBackingChainBacking(item, data->nodenamestable,
                                                &devicedata) < 0)
        goto cleanup;
186

187 188 189
    if (devicedata &&
        virHashAddEntry(data->disks, device, devicedata) < 0)
        goto cleanup;
190

191 192
    devicedata = NULL;
    ret = 1; /* we don't really want to steal @item */
193

194 195
 cleanup:
    qemuBlockNodeNameBackingChainDataFree(devicedata);
196

197
    return ret;
198 199 200 201 202
}


/**
 * qemuBlockNodeNameGetBackingChain:
203 204
 * @namednodes: JSON array of data returned from 'query-named-block-nodes'
 * @blockstats: JSON array of data returned from 'query-blockstats'
205 206 207 208 209 210 211 212 213 214
 *
 * Tries to reconstruct the backing chain from @json to allow detection of
 * node names that were auto-assigned by qemu. This is a best-effort operation
 * and may not be successful. The returned hash table contains the entries as
 * qemuBlockNodeNameBackingChainDataPtr accessible by the node name. The fields
 * then can be used to recover the full backing chain.
 *
 * Returns a hash table on success and NULL on failure.
 */
virHashTablePtr
215 216
qemuBlockNodeNameGetBackingChain(virJSONValuePtr namednodes,
                                 virJSONValuePtr blockstats)
217 218
{
    struct qemuBlockNodeNameGetBackingChainData data;
219 220
    virHashTablePtr namednodestable = NULL;
    virHashTablePtr disks = NULL;
221 222 223 224
    virHashTablePtr ret = NULL;

    memset(&data, 0, sizeof(data));

225
    if (!(namednodestable = virHashCreate(50, virJSONValueHashFree)))
226 227
        goto cleanup;

228 229 230
    if (virJSONValueArrayForeachSteal(namednodes,
                                      qemuBlockNamedNodesArrayToHash,
                                      namednodestable) < 0)
231 232
        goto cleanup;

233
    if (!(disks = virHashCreate(50, qemuBlockNodeNameBackingChainDataHashEntryFree)))
234 235
        goto cleanup;

236 237
    data.nodenamestable = namednodestable;
    data.disks = disks;
238

239 240 241 242
    if (virJSONValueArrayForeachSteal(blockstats,
                                      qemuBlockNodeNameGetBackingChainDisk,
                                      &data) < 0)
        goto cleanup;
243

244
    VIR_STEAL_PTR(ret, disks);
245 246

 cleanup:
247 248
     virHashFree(namednodestable);
     virHashFree(disks);
249 250 251

     return ret;
}
252 253 254 255 256 257 258 259 260


static void
qemuBlockDiskClearDetectedNodes(virDomainDiskDefPtr disk)
{
    virStorageSourcePtr next = disk->src;

    while (next) {
        VIR_FREE(next->nodeformat);
261
        VIR_FREE(next->nodestorage);
262 263 264 265 266 267 268 269

        next = next->backingStore;
    }
}


static int
qemuBlockDiskDetectNodes(virDomainDiskDefPtr disk,
270
                         virHashTablePtr disktable)
271 272 273 274
{
    qemuBlockNodeNameBackingChainDataPtr entry = NULL;
    virStorageSourcePtr src = disk->src;

275
    if (!(entry = virHashLookup(disktable, disk->info.alias)))
276 277
        return 0;

278 279 280
    /* don't attempt the detection if the top level already has node names */
    if (src->nodeformat || src->nodestorage)
        return 0;
281

282
    while (src && entry) {
283
        if (src->nodeformat || src->nodestorage) {
284
            if (STRNEQ_NULLABLE(src->nodeformat, entry->nodeformat) ||
285
                STRNEQ_NULLABLE(src->nodestorage, entry->nodestorage))
286 287 288 289 290
                goto error;

            break;
        } else {
            if (VIR_STRDUP(src->nodeformat, entry->nodeformat) < 0 ||
291
                VIR_STRDUP(src->nodestorage, entry->nodestorage) < 0)
292 293 294
                goto error;
        }

295
        entry = entry->backing;
296 297 298 299 300 301 302 303 304 305 306 307 308
        src = src->backingStore;
    }

    return 0;

 error:
    qemuBlockDiskClearDetectedNodes(disk);
    return -1;
}


int
qemuBlockNodeNamesDetect(virQEMUDriverPtr driver,
309 310
                         virDomainObjPtr vm,
                         qemuDomainAsyncJob asyncJob)
311 312 313 314
{
    qemuDomainObjPrivatePtr priv = vm->privateData;
    virHashTablePtr disktable = NULL;
    virJSONValuePtr data = NULL;
315
    virJSONValuePtr blockstats = NULL;
316 317 318 319 320 321 322
    virDomainDiskDefPtr disk;
    size_t i;
    int ret = -1;

    if (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_QUERY_NAMED_BLOCK_NODES))
        return 0;

323 324
    if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0)
        return -1;
325 326

    data = qemuMonitorQueryNamedBlockNodes(qemuDomainGetMonitor(vm));
327
    blockstats = qemuMonitorQueryBlockstats(qemuDomainGetMonitor(vm));
328

329
    if (qemuDomainObjExitMonitor(driver, vm) < 0 || !data || !blockstats)
330 331
        goto cleanup;

332
    if (!(disktable = qemuBlockNodeNameGetBackingChain(data, blockstats)))
333 334 335 336 337
        goto cleanup;

    for (i = 0; i < vm->def->ndisks; i++) {
        disk = vm->def->disks[i];

338
        if (qemuBlockDiskDetectNodes(disk, disktable) < 0)
339 340 341 342 343 344 345
            goto cleanup;
    }

    ret = 0;

 cleanup:
    virJSONValueFree(data);
346
    virJSONValueFree(blockstats);
347 348 349 350
    virHashFree(disktable);

    return ret;
}
351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369


/**
 * qemuBlockGetNodeData:
 * @data: JSON object returned from query-named-block-nodes
 *
 * Returns a hash table organized by the node name of the JSON value objects of
 * data for given qemu block nodes.
 *
 * Returns a filled virHashTablePtr on success NULL on error.
 */
virHashTablePtr
qemuBlockGetNodeData(virJSONValuePtr data)
{
    virHashTablePtr ret = NULL;

    if (!(ret = virHashCreate(50, virJSONValueHashFree)))
        return NULL;

370 371
    if (virJSONValueArrayForeachSteal(data,
                                      qemuBlockNamedNodesArrayToHash, ret) < 0)
372 373 374 375 376 377 378 379
        goto error;

    return ret;

 error:
    virHashFree(ret);
    return NULL;
}
380 381


382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449
/**
 * qemuBlockStorageSourceBuildJSONSocketAddress
 * @host: the virStorageNetHostDefPtr definition to build
 * @legacy: use 'tcp' instead of 'inet' for compatibility reasons
 *
 * Formats @hosts into a json object conforming to the 'SocketAddress' type
 * in qemu.
 *
 * This function can be used when only 1 src->nhosts is expected in order
 * to build a command without the array indices after "server.". That is
 * to see "server.type", "server.host", and "server.port" instead of
 * "server.#.type", "server.#.host", and "server.#.port".
 *
 * Returns a virJSONValuePtr for a single server.
 */
static virJSONValuePtr
qemuBlockStorageSourceBuildJSONSocketAddress(virStorageNetHostDefPtr host,
                                             bool legacy)
{
    virJSONValuePtr server = NULL;
    virJSONValuePtr ret = NULL;
    const char *transport;
    char *port = NULL;

    switch ((virStorageNetHostTransport) host->transport) {
    case VIR_STORAGE_NET_HOST_TRANS_TCP:
        if (legacy)
            transport = "tcp";
        else
            transport = "inet";

        if (virAsprintf(&port, "%u", host->port) < 0)
            goto cleanup;

        if (virJSONValueObjectCreate(&server,
                                     "s:type", transport,
                                     "s:host", host->name,
                                     "s:port", port,
                                     NULL) < 0)
            goto cleanup;
        break;

    case VIR_STORAGE_NET_HOST_TRANS_UNIX:
        if (virJSONValueObjectCreate(&server,
                                     "s:type", "unix",
                                     "s:socket", host->socket,
                                     NULL) < 0)
            goto cleanup;
        break;

    case VIR_STORAGE_NET_HOST_TRANS_RDMA:
    case VIR_STORAGE_NET_HOST_TRANS_LAST:
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("transport protocol '%s' is not yet supported"),
                       virStorageNetHostTransportTypeToString(host->transport));
        goto cleanup;
    }

    VIR_STEAL_PTR(ret, server);

 cleanup:
    VIR_FREE(port);
    virJSONValueFree(server);

    return ret;
}


450 451 452 453 454 455 456 457
/**
 * qemuBlockStorageSourceBuildHostsJSONSocketAddress:
 * @src: disk storage source
 * @legacy: use 'tcp' instead of 'inet' for compatibility reasons
 *
 * Formats src->hosts into a json object conforming to the 'SocketAddress' type
 * in qemu.
 */
458
static virJSONValuePtr
459 460
qemuBlockStorageSourceBuildHostsJSONSocketAddress(virStorageSourcePtr src,
                                                  bool legacy)
461 462 463 464 465 466 467 468 469 470 471 472 473
{
    virJSONValuePtr servers = NULL;
    virJSONValuePtr server = NULL;
    virJSONValuePtr ret = NULL;
    virStorageNetHostDefPtr host;
    size_t i;

    if (!(servers = virJSONValueNewArray()))
        goto cleanup;

    for (i = 0; i < src->nhosts; i++) {
        host = src->hosts + i;

474 475
        if (!(server = qemuBlockStorageSourceBuildJSONSocketAddress(host, legacy)))
              goto cleanup;
476 477 478 479 480 481 482

        if (virJSONValueArrayAppend(servers, server) < 0)
            goto cleanup;

        server = NULL;
    }

483
    VIR_STEAL_PTR(ret, servers);
484 485 486 487 488 489 490 491 492 493

 cleanup:
    virJSONValueFree(servers);
    virJSONValueFree(server);

    return ret;
}


static virJSONValuePtr
494
qemuBlockStorageSourceGetGlusterProps(virStorageSourcePtr src)
495 496 497 498
{
    virJSONValuePtr servers = NULL;
    virJSONValuePtr ret = NULL;

499
    if (!(servers = qemuBlockStorageSourceBuildHostsJSONSocketAddress(src, true)))
500 501 502 503 504 505 506 507 508
        return NULL;

     /* { driver:"gluster",
      *   volume:"testvol",
      *   path:"/a.img",
      *   server :[{type:"tcp", host:"1.2.3.4", port:24007},
      *            {type:"unix", socket:"/tmp/glusterd.socket"}, ...]}
      */
    if (virJSONValueObjectCreate(&ret,
509
                                 "s:driver", "gluster",
510 511 512 513 514 515 516 517 518
                                 "s:volume", src->volume,
                                 "s:path", src->path,
                                 "a:server", servers, NULL) < 0)
          virJSONValueFree(servers);

    return ret;
}


519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549
static virJSONValuePtr
qemuBlockStorageSourceGetVxHSProps(virStorageSourcePtr src)
{
    const char *protocol = virStorageNetProtocolTypeToString(src->protocol);
    virJSONValuePtr server = NULL;
    virJSONValuePtr ret = NULL;

    if (src->nhosts != 1) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("VxHS protocol accepts only one host"));
        return NULL;
    }

    if (!(server = qemuBlockStorageSourceBuildJSONSocketAddress(src->hosts, true)))
        return NULL;

    /* VxHS disk specification example:
     * { driver:"vxhs",
     *   vdisk-id:"eb90327c-8302-4725-4e85ed4dc251",
     *   server:{type:"tcp", host:"1.2.3.4", port:9999}}
     */
    if (virJSONValueObjectCreate(&ret,
                                 "s:driver", protocol,
                                 "s:vdisk-id", src->path,
                                 "a:server", server, NULL) < 0)
        virJSONValueFree(server);

    return ret;
}


550 551 552 553 554 555 556 557 558
/**
 * qemuBlockStorageSourceGetBackendProps:
 * @src: disk source
 *
 * Creates a JSON object describing the underlying storage or protocol of a
 * storage source. Returns NULL on error and reports an appropriate error message.
 */
virJSONValuePtr
qemuBlockStorageSourceGetBackendProps(virStorageSourcePtr src)
559 560 561
{
    int actualType = virStorageSourceGetActualType(src);
    virJSONValuePtr fileprops = NULL;
562
    virJSONValuePtr ret = NULL;
563 564 565 566 567 568 569 570 571 572 573

    switch ((virStorageType) actualType) {
    case VIR_STORAGE_TYPE_BLOCK:
    case VIR_STORAGE_TYPE_FILE:
    case VIR_STORAGE_TYPE_DIR:
    case VIR_STORAGE_TYPE_VOLUME:
    case VIR_STORAGE_TYPE_NONE:
    case VIR_STORAGE_TYPE_LAST:
        break;

    case VIR_STORAGE_TYPE_NETWORK:
574 575
        switch ((virStorageNetProtocol) src->protocol) {
        case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
576
            if (!(fileprops = qemuBlockStorageSourceGetGlusterProps(src)))
577 578 579
                goto cleanup;
            break;

580 581 582 583 584
        case VIR_STORAGE_NET_PROTOCOL_VXHS:
            if (!(fileprops = qemuBlockStorageSourceGetVxHSProps(src)))
                goto cleanup;
            break;

585 586 587 588 589 590 591 592 593 594 595 596 597
        case VIR_STORAGE_NET_PROTOCOL_NBD:
        case VIR_STORAGE_NET_PROTOCOL_RBD:
        case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
        case VIR_STORAGE_NET_PROTOCOL_ISCSI:
        case VIR_STORAGE_NET_PROTOCOL_HTTP:
        case VIR_STORAGE_NET_PROTOCOL_HTTPS:
        case VIR_STORAGE_NET_PROTOCOL_FTP:
        case VIR_STORAGE_NET_PROTOCOL_FTPS:
        case VIR_STORAGE_NET_PROTOCOL_TFTP:
        case VIR_STORAGE_NET_PROTOCOL_SSH:
        case VIR_STORAGE_NET_PROTOCOL_NONE:
        case VIR_STORAGE_NET_PROTOCOL_LAST:
            break;
598 599 600 601
        }
        break;
    }

602 603
    if (virJSONValueObjectCreate(&ret, "a:file", fileprops, NULL) < 0)
        goto cleanup;
604

605 606 607 608 609
    fileprops = NULL;

 cleanup:
    virJSONValueFree(fileprops);
    return ret;
610
}