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