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