qemu/block/vxhs.c
<<
>>
Prefs
   1/*
   2 * QEMU Block driver for Veritas HyperScale (VxHS)
   3 *
   4 * Copyright (c) 2017 Veritas Technologies LLC.
   5 *
   6 * This work is licensed under the terms of the GNU GPL, version 2 or later.
   7 * See the COPYING file in the top-level directory.
   8 *
   9 */
  10
  11#include "qemu/osdep.h"
  12#include <qnio/qnio_api.h>
  13#include <sys/param.h>
  14#include "block/block_int.h"
  15#include "qapi/qmp/qerror.h"
  16#include "qapi/qmp/qdict.h"
  17#include "qapi/qmp/qstring.h"
  18#include "trace.h"
  19#include "qemu/uri.h"
  20#include "qapi/error.h"
  21#include "qemu/uuid.h"
  22#include "crypto/tlscredsx509.h"
  23
  24#define VXHS_OPT_FILENAME           "filename"
  25#define VXHS_OPT_VDISK_ID           "vdisk-id"
  26#define VXHS_OPT_SERVER             "server"
  27#define VXHS_OPT_HOST               "host"
  28#define VXHS_OPT_PORT               "port"
  29
  30/* Only accessed under QEMU global mutex */
  31static uint32_t vxhs_ref;
  32
  33typedef enum {
  34    VDISK_AIO_READ,
  35    VDISK_AIO_WRITE,
  36} VDISKAIOCmd;
  37
  38/*
  39 * HyperScale AIO callbacks structure
  40 */
  41typedef struct VXHSAIOCB {
  42    BlockAIOCB common;
  43    int err;
  44} VXHSAIOCB;
  45
  46typedef struct VXHSvDiskHostsInfo {
  47    void *dev_handle; /* Device handle */
  48    char *host; /* Host name or IP */
  49    int port; /* Host's port number */
  50} VXHSvDiskHostsInfo;
  51
  52/*
  53 * Structure per vDisk maintained for state
  54 */
  55typedef struct BDRVVXHSState {
  56    VXHSvDiskHostsInfo vdisk_hostinfo; /* Per host info */
  57    char *vdisk_guid;
  58    char *tlscredsid; /* tlscredsid */
  59} BDRVVXHSState;
  60
  61static void vxhs_complete_aio_bh(void *opaque)
  62{
  63    VXHSAIOCB *acb = opaque;
  64    BlockCompletionFunc *cb = acb->common.cb;
  65    void *cb_opaque = acb->common.opaque;
  66    int ret = 0;
  67
  68    if (acb->err != 0) {
  69        trace_vxhs_complete_aio(acb, acb->err);
  70        ret = (-EIO);
  71    }
  72
  73    qemu_aio_unref(acb);
  74    cb(cb_opaque, ret);
  75}
  76
  77/*
  78 * Called from a libqnio thread
  79 */
  80static void vxhs_iio_callback(void *ctx, uint32_t opcode, uint32_t error)
  81{
  82    VXHSAIOCB *acb = NULL;
  83
  84    switch (opcode) {
  85    case IRP_READ_REQUEST:
  86    case IRP_WRITE_REQUEST:
  87
  88        /*
  89         * ctx is VXHSAIOCB*
  90         * ctx is NULL if error is QNIOERROR_CHANNEL_HUP
  91         */
  92        if (ctx) {
  93            acb = ctx;
  94        } else {
  95            trace_vxhs_iio_callback(error);
  96            goto out;
  97        }
  98
  99        if (error) {
 100            if (!acb->err) {
 101                acb->err = error;
 102            }
 103            trace_vxhs_iio_callback(error);
 104        }
 105
 106        aio_bh_schedule_oneshot(bdrv_get_aio_context(acb->common.bs),
 107                                vxhs_complete_aio_bh, acb);
 108        break;
 109
 110    default:
 111        if (error == QNIOERROR_HUP) {
 112            /*
 113             * Channel failed, spontaneous notification,
 114             * not in response to I/O
 115             */
 116            trace_vxhs_iio_callback_chnfail(error, errno);
 117        } else {
 118            trace_vxhs_iio_callback_unknwn(opcode, error);
 119        }
 120        break;
 121    }
 122out:
 123    return;
 124}
 125
 126static QemuOptsList runtime_opts = {
 127    .name = "vxhs",
 128    .head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
 129    .desc = {
 130        {
 131            .name = VXHS_OPT_FILENAME,
 132            .type = QEMU_OPT_STRING,
 133            .help = "URI to the Veritas HyperScale image",
 134        },
 135        {
 136            .name = VXHS_OPT_VDISK_ID,
 137            .type = QEMU_OPT_STRING,
 138            .help = "UUID of the VxHS vdisk",
 139        },
 140        {
 141            .name = "tls-creds",
 142            .type = QEMU_OPT_STRING,
 143            .help = "ID of the TLS/SSL credentials to use",
 144        },
 145        { /* end of list */ }
 146    },
 147};
 148
 149static QemuOptsList runtime_tcp_opts = {
 150    .name = "vxhs_tcp",
 151    .head = QTAILQ_HEAD_INITIALIZER(runtime_tcp_opts.head),
 152    .desc = {
 153        {
 154            .name = VXHS_OPT_HOST,
 155            .type = QEMU_OPT_STRING,
 156            .help = "host address (ipv4 addresses)",
 157        },
 158        {
 159            .name = VXHS_OPT_PORT,
 160            .type = QEMU_OPT_NUMBER,
 161            .help = "port number on which VxHSD is listening (default 9999)",
 162            .def_value_str = "9999"
 163        },
 164        { /* end of list */ }
 165    },
 166};
 167
 168/*
 169 * Parse incoming URI and populate *options with the host
 170 * and device information
 171 */
 172static int vxhs_parse_uri(const char *filename, QDict *options)
 173{
 174    URI *uri = NULL;
 175    char *port;
 176    int ret = 0;
 177
 178    trace_vxhs_parse_uri_filename(filename);
 179    uri = uri_parse(filename);
 180    if (!uri || !uri->server || !uri->path) {
 181        uri_free(uri);
 182        return -EINVAL;
 183    }
 184
 185    qdict_put_str(options, VXHS_OPT_SERVER ".host", uri->server);
 186
 187    if (uri->port) {
 188        port = g_strdup_printf("%d", uri->port);
 189        qdict_put_str(options, VXHS_OPT_SERVER ".port", port);
 190        g_free(port);
 191    }
 192
 193    qdict_put_str(options, "vdisk-id", uri->path);
 194
 195    trace_vxhs_parse_uri_hostinfo(uri->server, uri->port);
 196    uri_free(uri);
 197
 198    return ret;
 199}
 200
 201static void vxhs_parse_filename(const char *filename, QDict *options,
 202                                Error **errp)
 203{
 204    if (qdict_haskey(options, "vdisk-id") || qdict_haskey(options, "server")) {
 205        error_setg(errp, "vdisk-id/server and a file name may not be specified "
 206                         "at the same time");
 207        return;
 208    }
 209
 210    if (strstr(filename, "://")) {
 211        int ret = vxhs_parse_uri(filename, options);
 212        if (ret < 0) {
 213            error_setg(errp, "Invalid URI. URI should be of the form "
 214                       "  vxhs://<host_ip>:<port>/<vdisk-id>");
 215        }
 216    }
 217}
 218
 219static int vxhs_init_and_ref(void)
 220{
 221    if (vxhs_ref++ == 0) {
 222        if (iio_init(QNIO_VERSION, vxhs_iio_callback)) {
 223            return -ENODEV;
 224        }
 225    }
 226    return 0;
 227}
 228
 229static void vxhs_unref(void)
 230{
 231    if (--vxhs_ref == 0) {
 232        iio_fini();
 233    }
 234}
 235
 236static void vxhs_get_tls_creds(const char *id, char **cacert,
 237                               char **key, char **cert, Error **errp)
 238{
 239    Object *obj;
 240    QCryptoTLSCreds *creds;
 241    QCryptoTLSCredsX509 *creds_x509;
 242
 243    obj = object_resolve_path_component(
 244        object_get_objects_root(), id);
 245
 246    if (!obj) {
 247        error_setg(errp, "No TLS credentials with id '%s'",
 248                   id);
 249        return;
 250    }
 251
 252    creds_x509 = (QCryptoTLSCredsX509 *)
 253        object_dynamic_cast(obj, TYPE_QCRYPTO_TLS_CREDS_X509);
 254
 255    if (!creds_x509) {
 256        error_setg(errp, "Object with id '%s' is not TLS credentials",
 257                   id);
 258        return;
 259    }
 260
 261    creds = &creds_x509->parent_obj;
 262
 263    if (creds->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT) {
 264        error_setg(errp,
 265                   "Expecting TLS credentials with a client endpoint");
 266        return;
 267    }
 268
 269    /*
 270     * Get the cacert, client_cert and client_key file names.
 271     */
 272    if (!creds->dir) {
 273        error_setg(errp, "TLS object missing 'dir' property value");
 274        return;
 275    }
 276
 277    *cacert = g_strdup_printf("%s/%s", creds->dir,
 278                              QCRYPTO_TLS_CREDS_X509_CA_CERT);
 279    *cert = g_strdup_printf("%s/%s", creds->dir,
 280                            QCRYPTO_TLS_CREDS_X509_CLIENT_CERT);
 281    *key = g_strdup_printf("%s/%s", creds->dir,
 282                           QCRYPTO_TLS_CREDS_X509_CLIENT_KEY);
 283}
 284
 285static int vxhs_open(BlockDriverState *bs, QDict *options,
 286                     int bdrv_flags, Error **errp)
 287{
 288    BDRVVXHSState *s = bs->opaque;
 289    void *dev_handlep;
 290    QDict *backing_options = NULL;
 291    QemuOpts *opts = NULL;
 292    QemuOpts *tcp_opts = NULL;
 293    char *of_vsa_addr = NULL;
 294    Error *local_err = NULL;
 295    const char *vdisk_id_opt;
 296    const char *server_host_opt;
 297    int ret = 0;
 298    char *cacert = NULL;
 299    char *client_key = NULL;
 300    char *client_cert = NULL;
 301
 302    ret = vxhs_init_and_ref();
 303    if (ret < 0) {
 304        ret = -EINVAL;
 305        goto out;
 306    }
 307
 308    /* Create opts info from runtime_opts and runtime_tcp_opts list */
 309    opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
 310    tcp_opts = qemu_opts_create(&runtime_tcp_opts, NULL, 0, &error_abort);
 311
 312    qemu_opts_absorb_qdict(opts, options, &local_err);
 313    if (local_err) {
 314        ret = -EINVAL;
 315        goto out;
 316    }
 317
 318    /* vdisk-id is the disk UUID */
 319    vdisk_id_opt = qemu_opt_get(opts, VXHS_OPT_VDISK_ID);
 320    if (!vdisk_id_opt) {
 321        error_setg(&local_err, QERR_MISSING_PARAMETER, VXHS_OPT_VDISK_ID);
 322        ret = -EINVAL;
 323        goto out;
 324    }
 325
 326    /* vdisk-id may contain a leading '/' */
 327    if (strlen(vdisk_id_opt) > UUID_FMT_LEN + 1) {
 328        error_setg(&local_err, "vdisk-id cannot be more than %d characters",
 329                   UUID_FMT_LEN);
 330        ret = -EINVAL;
 331        goto out;
 332    }
 333
 334    s->vdisk_guid = g_strdup(vdisk_id_opt);
 335    trace_vxhs_open_vdiskid(vdisk_id_opt);
 336
 337    /* get the 'server.' arguments */
 338    qdict_extract_subqdict(options, &backing_options, VXHS_OPT_SERVER".");
 339
 340    qemu_opts_absorb_qdict(tcp_opts, backing_options, &local_err);
 341    if (local_err != NULL) {
 342        ret = -EINVAL;
 343        goto out;
 344    }
 345
 346    server_host_opt = qemu_opt_get(tcp_opts, VXHS_OPT_HOST);
 347    if (!server_host_opt) {
 348        error_setg(&local_err, QERR_MISSING_PARAMETER,
 349                   VXHS_OPT_SERVER"."VXHS_OPT_HOST);
 350        ret = -EINVAL;
 351        goto out;
 352    }
 353
 354    if (strlen(server_host_opt) > MAXHOSTNAMELEN) {
 355        error_setg(&local_err, "server.host cannot be more than %d characters",
 356                   MAXHOSTNAMELEN);
 357        ret = -EINVAL;
 358        goto out;
 359    }
 360
 361    /* check if we got tls-creds via the --object argument */
 362    s->tlscredsid = g_strdup(qemu_opt_get(opts, "tls-creds"));
 363    if (s->tlscredsid) {
 364        vxhs_get_tls_creds(s->tlscredsid, &cacert, &client_key,
 365                           &client_cert, &local_err);
 366        if (local_err != NULL) {
 367            ret = -EINVAL;
 368            goto out;
 369        }
 370        trace_vxhs_get_creds(cacert, client_key, client_cert);
 371    }
 372
 373    s->vdisk_hostinfo.host = g_strdup(server_host_opt);
 374    s->vdisk_hostinfo.port = g_ascii_strtoll(qemu_opt_get(tcp_opts,
 375                                                          VXHS_OPT_PORT),
 376                                                          NULL, 0);
 377
 378    trace_vxhs_open_hostinfo(s->vdisk_hostinfo.host,
 379                             s->vdisk_hostinfo.port);
 380
 381    of_vsa_addr = g_strdup_printf("of://%s:%d",
 382                                  s->vdisk_hostinfo.host,
 383                                  s->vdisk_hostinfo.port);
 384
 385    /*
 386     * Open qnio channel to storage agent if not opened before
 387     */
 388    dev_handlep = iio_open(of_vsa_addr, s->vdisk_guid, 0,
 389                           cacert, client_key, client_cert);
 390    if (dev_handlep == NULL) {
 391        trace_vxhs_open_iio_open(of_vsa_addr);
 392        ret = -ENODEV;
 393        goto out;
 394    }
 395    s->vdisk_hostinfo.dev_handle = dev_handlep;
 396
 397out:
 398    g_free(of_vsa_addr);
 399    QDECREF(backing_options);
 400    qemu_opts_del(tcp_opts);
 401    qemu_opts_del(opts);
 402    g_free(cacert);
 403    g_free(client_key);
 404    g_free(client_cert);
 405
 406    if (ret < 0) {
 407        vxhs_unref();
 408        error_propagate(errp, local_err);
 409        g_free(s->vdisk_hostinfo.host);
 410        g_free(s->vdisk_guid);
 411        g_free(s->tlscredsid);
 412        s->vdisk_guid = NULL;
 413    }
 414
 415    return ret;
 416}
 417
 418static const AIOCBInfo vxhs_aiocb_info = {
 419    .aiocb_size = sizeof(VXHSAIOCB)
 420};
 421
 422/*
 423 * This allocates QEMU-VXHS callback for each IO
 424 * and is passed to QNIO. When QNIO completes the work,
 425 * it will be passed back through the callback.
 426 */
 427static BlockAIOCB *vxhs_aio_rw(BlockDriverState *bs, int64_t sector_num,
 428                               QEMUIOVector *qiov, int nb_sectors,
 429                               BlockCompletionFunc *cb, void *opaque,
 430                               VDISKAIOCmd iodir)
 431{
 432    VXHSAIOCB *acb = NULL;
 433    BDRVVXHSState *s = bs->opaque;
 434    size_t size;
 435    uint64_t offset;
 436    int iio_flags = 0;
 437    int ret = 0;
 438    void *dev_handle = s->vdisk_hostinfo.dev_handle;
 439
 440    offset = sector_num * BDRV_SECTOR_SIZE;
 441    size = nb_sectors * BDRV_SECTOR_SIZE;
 442    acb = qemu_aio_get(&vxhs_aiocb_info, bs, cb, opaque);
 443
 444    /*
 445     * Initialize VXHSAIOCB.
 446     */
 447    acb->err = 0;
 448
 449    iio_flags = IIO_FLAG_ASYNC;
 450
 451    switch (iodir) {
 452    case VDISK_AIO_WRITE:
 453            ret = iio_writev(dev_handle, acb, qiov->iov, qiov->niov,
 454                             offset, (uint64_t)size, iio_flags);
 455            break;
 456    case VDISK_AIO_READ:
 457            ret = iio_readv(dev_handle, acb, qiov->iov, qiov->niov,
 458                            offset, (uint64_t)size, iio_flags);
 459            break;
 460    default:
 461            trace_vxhs_aio_rw_invalid(iodir);
 462            goto errout;
 463    }
 464
 465    if (ret != 0) {
 466        trace_vxhs_aio_rw_ioerr(s->vdisk_guid, iodir, size, offset,
 467                                acb, ret, errno);
 468        goto errout;
 469    }
 470    return &acb->common;
 471
 472errout:
 473    qemu_aio_unref(acb);
 474    return NULL;
 475}
 476
 477static BlockAIOCB *vxhs_aio_readv(BlockDriverState *bs,
 478                                   int64_t sector_num, QEMUIOVector *qiov,
 479                                   int nb_sectors,
 480                                   BlockCompletionFunc *cb, void *opaque)
 481{
 482    return vxhs_aio_rw(bs, sector_num, qiov, nb_sectors, cb,
 483                       opaque, VDISK_AIO_READ);
 484}
 485
 486static BlockAIOCB *vxhs_aio_writev(BlockDriverState *bs,
 487                                   int64_t sector_num, QEMUIOVector *qiov,
 488                                   int nb_sectors,
 489                                   BlockCompletionFunc *cb, void *opaque)
 490{
 491    return vxhs_aio_rw(bs, sector_num, qiov, nb_sectors,
 492                       cb, opaque, VDISK_AIO_WRITE);
 493}
 494
 495static void vxhs_close(BlockDriverState *bs)
 496{
 497    BDRVVXHSState *s = bs->opaque;
 498
 499    trace_vxhs_close(s->vdisk_guid);
 500
 501    g_free(s->vdisk_guid);
 502    s->vdisk_guid = NULL;
 503
 504    /*
 505     * Close vDisk device
 506     */
 507    if (s->vdisk_hostinfo.dev_handle) {
 508        iio_close(s->vdisk_hostinfo.dev_handle);
 509        s->vdisk_hostinfo.dev_handle = NULL;
 510    }
 511
 512    vxhs_unref();
 513
 514    /*
 515     * Free the dynamically allocated host string etc
 516     */
 517    g_free(s->vdisk_hostinfo.host);
 518    g_free(s->tlscredsid);
 519    s->tlscredsid = NULL;
 520    s->vdisk_hostinfo.host = NULL;
 521    s->vdisk_hostinfo.port = 0;
 522}
 523
 524static int64_t vxhs_get_vdisk_stat(BDRVVXHSState *s)
 525{
 526    int64_t vdisk_size = -1;
 527    int ret = 0;
 528    void *dev_handle = s->vdisk_hostinfo.dev_handle;
 529
 530    ret = iio_ioctl(dev_handle, IOR_VDISK_STAT, &vdisk_size, 0);
 531    if (ret < 0) {
 532        trace_vxhs_get_vdisk_stat_err(s->vdisk_guid, ret, errno);
 533        return -EIO;
 534    }
 535
 536    trace_vxhs_get_vdisk_stat(s->vdisk_guid, vdisk_size);
 537    return vdisk_size;
 538}
 539
 540/*
 541 * Returns the size of vDisk in bytes. This is required
 542 * by QEMU block upper block layer so that it is visible
 543 * to guest.
 544 */
 545static int64_t vxhs_getlength(BlockDriverState *bs)
 546{
 547    BDRVVXHSState *s = bs->opaque;
 548    int64_t vdisk_size;
 549
 550    vdisk_size = vxhs_get_vdisk_stat(s);
 551    if (vdisk_size < 0) {
 552        return -EIO;
 553    }
 554
 555    return vdisk_size;
 556}
 557
 558static BlockDriver bdrv_vxhs = {
 559    .format_name                  = "vxhs",
 560    .protocol_name                = "vxhs",
 561    .instance_size                = sizeof(BDRVVXHSState),
 562    .bdrv_file_open               = vxhs_open,
 563    .bdrv_parse_filename          = vxhs_parse_filename,
 564    .bdrv_close                   = vxhs_close,
 565    .bdrv_getlength               = vxhs_getlength,
 566    .bdrv_aio_readv               = vxhs_aio_readv,
 567    .bdrv_aio_writev              = vxhs_aio_writev,
 568};
 569
 570static void bdrv_vxhs_init(void)
 571{
 572    bdrv_register(&bdrv_vxhs);
 573}
 574
 575block_init(bdrv_vxhs_init);
 576