qemu_block.c 15.3 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
/**
 * 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.
 */
390
static virJSONValuePtr
391 392
qemuBlockStorageSourceBuildHostsJSONSocketAddress(virStorageSourcePtr src,
                                                  bool legacy)
393 394 395 396 397 398
{
    virJSONValuePtr servers = NULL;
    virJSONValuePtr server = NULL;
    virJSONValuePtr ret = NULL;
    virStorageNetHostDefPtr host;
    const char *transport;
399
    char *port = NULL;
400 401 402 403 404 405 406 407 408 409
    size_t i;

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

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

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

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

418 419 420
            if (virJSONValueObjectCreate(&server,
                                         "s:type", transport,
                                         "s:host", host->name,
421
                                         "s:port", port,
422
                                         NULL) < 0)
423 424 425 426
                goto cleanup;
            break;

        case VIR_STORAGE_NET_HOST_TRANS_UNIX:
427 428 429 430
            if (virJSONValueObjectCreate(&server,
                                         "s:type", "unix",
                                         "s:socket", host->socket,
                                         NULL) < 0)
431 432 433 434 435 436 437
                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"),
438
                           virStorageNetHostTransportTypeToString(host->transport));
439 440 441 442 443 444 445 446 447
            goto cleanup;
        }

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

        server = NULL;
    }

448
    VIR_STEAL_PTR(ret, servers);
449 450 451 452

 cleanup:
    virJSONValueFree(servers);
    virJSONValueFree(server);
453
    VIR_FREE(port);
454 455 456 457 458 459

    return ret;
}


static virJSONValuePtr
460
qemuBlockStorageSourceGetGlusterProps(virStorageSourcePtr src)
461 462 463 464
{
    virJSONValuePtr servers = NULL;
    virJSONValuePtr ret = NULL;

465
    if (!(servers = qemuBlockStorageSourceBuildHostsJSONSocketAddress(src, true)))
466 467 468 469 470 471 472 473 474
        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,
475
                                 "s:driver", "gluster",
476 477 478 479 480 481 482 483 484
                                 "s:volume", src->volume,
                                 "s:path", src->path,
                                 "a:server", servers, NULL) < 0)
          virJSONValueFree(servers);

    return ret;
}


485 486 487 488 489 490 491 492 493
/**
 * 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)
494 495 496
{
    int actualType = virStorageSourceGetActualType(src);
    virJSONValuePtr fileprops = NULL;
497
    virJSONValuePtr ret = NULL;
498 499 500 501 502 503 504 505 506 507 508

    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:
509 510
        switch ((virStorageNetProtocol) src->protocol) {
        case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
511
            if (!(fileprops = qemuBlockStorageSourceGetGlusterProps(src)))
512 513 514 515 516 517 518 519 520 521 522 523 524
                goto cleanup;
            break;

        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:
525
        case VIR_STORAGE_NET_PROTOCOL_VXHS:
526 527 528
        case VIR_STORAGE_NET_PROTOCOL_NONE:
        case VIR_STORAGE_NET_PROTOCOL_LAST:
            break;
529 530 531 532
        }
        break;
    }

533 534
    if (virJSONValueObjectCreate(&ret, "a:file", fileprops, NULL) < 0)
        goto cleanup;
535

536 537 538 539 540
    fileprops = NULL;

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