qemu/tests/qmp-test.c
<<
>>
Prefs
   1/*
   2 * QMP protocol test cases
   3 *
   4 * Copyright (c) 2017 Red Hat Inc.
   5 *
   6 * Authors:
   7 *  Markus Armbruster <armbru@redhat.com>,
   8 *
   9 * This work is licensed under the terms of the GNU GPL, version 2 or later.
  10 * See the COPYING file in the top-level directory.
  11 */
  12
  13#include "qemu/osdep.h"
  14#include "libqtest.h"
  15#include "qapi/error.h"
  16#include "qapi/qapi-visit-introspect.h"
  17#include "qapi/qapi-visit-misc.h"
  18#include "qapi/qmp/qdict.h"
  19#include "qapi/qmp/qlist.h"
  20#include "qapi/qobject-input-visitor.h"
  21#include "qapi/util.h"
  22#include "qapi/visitor.h"
  23#include "qapi/qmp/qstring.h"
  24
  25const char common_args[] = "-nodefaults -machine none";
  26
  27static const char *get_error_class(QDict *resp)
  28{
  29    QDict *error = qdict_get_qdict(resp, "error");
  30    const char *desc = qdict_get_try_str(error, "desc");
  31
  32    g_assert(desc);
  33    return error ? qdict_get_try_str(error, "class") : NULL;
  34}
  35
  36static void test_version(QObject *version)
  37{
  38    Visitor *v;
  39    VersionInfo *vinfo;
  40
  41    g_assert(version);
  42    v = qobject_input_visitor_new(version);
  43    visit_type_VersionInfo(v, "version", &vinfo, &error_abort);
  44    qapi_free_VersionInfo(vinfo);
  45    visit_free(v);
  46}
  47
  48static void test_malformed(QTestState *qts)
  49{
  50    QDict *resp;
  51
  52    /* Not even a dictionary */
  53    resp = qtest_qmp(qts, "null");
  54    g_assert_cmpstr(get_error_class(resp), ==, "GenericError");
  55    qobject_unref(resp);
  56
  57    /* No "execute" key */
  58    resp = qtest_qmp(qts, "{}");
  59    g_assert_cmpstr(get_error_class(resp), ==, "GenericError");
  60    qobject_unref(resp);
  61
  62    /* "execute" isn't a string */
  63    resp = qtest_qmp(qts, "{ 'execute': true }");
  64    g_assert_cmpstr(get_error_class(resp), ==, "GenericError");
  65    qobject_unref(resp);
  66
  67    /* "arguments" isn't a dictionary */
  68    resp = qtest_qmp(qts, "{ 'execute': 'no-such-cmd', 'arguments': [] }");
  69    g_assert_cmpstr(get_error_class(resp), ==, "GenericError");
  70    qobject_unref(resp);
  71
  72    /* extra key */
  73    resp = qtest_qmp(qts, "{ 'execute': 'no-such-cmd', 'extra': true }");
  74    g_assert_cmpstr(get_error_class(resp), ==, "GenericError");
  75    qobject_unref(resp);
  76}
  77
  78static void test_qmp_protocol(void)
  79{
  80    QDict *resp, *q, *ret;
  81    QList *capabilities;
  82    QTestState *qts;
  83
  84    qts = qtest_init_without_qmp_handshake(false, common_args);
  85
  86    /* Test greeting */
  87    resp = qtest_qmp_receive(qts);
  88    q = qdict_get_qdict(resp, "QMP");
  89    g_assert(q);
  90    test_version(qdict_get(q, "version"));
  91    capabilities = qdict_get_qlist(q, "capabilities");
  92    g_assert(capabilities && qlist_empty(capabilities));
  93    qobject_unref(resp);
  94
  95    /* Test valid command before handshake */
  96    resp = qtest_qmp(qts, "{ 'execute': 'query-version' }");
  97    g_assert_cmpstr(get_error_class(resp), ==, "CommandNotFound");
  98    qobject_unref(resp);
  99
 100    /* Test malformed commands before handshake */
 101    test_malformed(qts);
 102
 103    /* Test handshake */
 104    resp = qtest_qmp(qts, "{ 'execute': 'qmp_capabilities' }");
 105    ret = qdict_get_qdict(resp, "return");
 106    g_assert(ret && !qdict_size(ret));
 107    qobject_unref(resp);
 108
 109    /* Test repeated handshake */
 110    resp = qtest_qmp(qts, "{ 'execute': 'qmp_capabilities' }");
 111    g_assert_cmpstr(get_error_class(resp), ==, "CommandNotFound");
 112    qobject_unref(resp);
 113
 114    /* Test valid command */
 115    resp = qtest_qmp(qts, "{ 'execute': 'query-version' }");
 116    test_version(qdict_get(resp, "return"));
 117    qobject_unref(resp);
 118
 119    /* Test malformed commands */
 120    test_malformed(qts);
 121
 122    /* Test 'id' */
 123    resp = qtest_qmp(qts, "{ 'execute': 'query-name', 'id': 'cookie#1' }");
 124    ret = qdict_get_qdict(resp, "return");
 125    g_assert(ret);
 126    g_assert_cmpstr(qdict_get_try_str(resp, "id"), ==, "cookie#1");
 127    qobject_unref(resp);
 128
 129    /* Test command failure with 'id' */
 130    resp = qtest_qmp(qts, "{ 'execute': 'human-monitor-command', 'id': 2 }");
 131    g_assert_cmpstr(get_error_class(resp), ==, "GenericError");
 132    g_assert_cmpint(qdict_get_int(resp, "id"), ==, 2);
 133    qobject_unref(resp);
 134
 135    qtest_quit(qts);
 136}
 137
 138/* Out-of-band tests */
 139
 140char tmpdir[] = "/tmp/qmp-test-XXXXXX";
 141char *fifo_name;
 142
 143static void setup_blocking_cmd(void)
 144{
 145    if (!mkdtemp(tmpdir)) {
 146        g_error("mkdtemp: %s", strerror(errno));
 147    }
 148    fifo_name = g_strdup_printf("%s/fifo", tmpdir);
 149    if (mkfifo(fifo_name, 0666)) {
 150        g_error("mkfifo: %s", strerror(errno));
 151    }
 152}
 153
 154static void cleanup_blocking_cmd(void)
 155{
 156    unlink(fifo_name);
 157    rmdir(tmpdir);
 158}
 159
 160static void send_cmd_that_blocks(QTestState *s, const char *id)
 161{
 162    qtest_async_qmp(s, "{ 'execute': 'blockdev-add',  'id': %s,"
 163                    " 'arguments': {"
 164                    " 'driver': 'blkdebug', 'node-name': %s,"
 165                    " 'config': %s,"
 166                    " 'image': { 'driver': 'null-co' } } }",
 167                    id, id, fifo_name);
 168}
 169
 170static void unblock_blocked_cmd(void)
 171{
 172    int fd = open(fifo_name, O_WRONLY);
 173    g_assert(fd >= 0);
 174    close(fd);
 175}
 176
 177static void send_oob_cmd_that_fails(QTestState *s, const char *id)
 178{
 179    qtest_async_qmp(s, "{ 'exec-oob': 'migrate-pause', 'id': %s }", id);
 180}
 181
 182static void recv_cmd_id(QTestState *s, const char *id)
 183{
 184    QDict *resp = qtest_qmp_receive(s);
 185
 186    g_assert_cmpstr(qdict_get_try_str(resp, "id"), ==, id);
 187    qobject_unref(resp);
 188}
 189
 190static void test_qmp_oob(void)
 191{
 192    QTestState *qts;
 193    QDict *resp, *q;
 194    const QListEntry *entry;
 195    QList *capabilities;
 196    QString *qstr;
 197
 198    qts = qtest_init_without_qmp_handshake(true, common_args);
 199
 200    /* Check the greeting message. */
 201    resp = qtest_qmp_receive(qts);
 202    q = qdict_get_qdict(resp, "QMP");
 203    g_assert(q);
 204    capabilities = qdict_get_qlist(q, "capabilities");
 205    g_assert(capabilities && !qlist_empty(capabilities));
 206    entry = qlist_first(capabilities);
 207    g_assert(entry);
 208    qstr = qobject_to(QString, entry->value);
 209    g_assert(qstr);
 210    g_assert_cmpstr(qstring_get_str(qstr), ==, "oob");
 211    qobject_unref(resp);
 212
 213    /* Try a fake capability, it should fail. */
 214    resp = qtest_qmp(qts,
 215                     "{ 'execute': 'qmp_capabilities', "
 216                     "  'arguments': { 'enable': [ 'cap-does-not-exist' ] } }");
 217    g_assert(qdict_haskey(resp, "error"));
 218    qobject_unref(resp);
 219
 220    /* Now, enable OOB in current QMP session, it should succeed. */
 221    resp = qtest_qmp(qts,
 222                     "{ 'execute': 'qmp_capabilities', "
 223                     "  'arguments': { 'enable': [ 'oob' ] } }");
 224    g_assert(qdict_haskey(resp, "return"));
 225    qobject_unref(resp);
 226
 227    /*
 228     * Try any command that does not support OOB but with OOB flag. We
 229     * should get failure.
 230     */
 231    resp = qtest_qmp(qts, "{ 'exec-oob': 'query-cpus' }");
 232    g_assert(qdict_haskey(resp, "error"));
 233    qobject_unref(resp);
 234
 235    /* OOB command overtakes slow in-band command */
 236    setup_blocking_cmd();
 237    send_cmd_that_blocks(qts, "ib-blocks-1");
 238    qtest_async_qmp(qts, "{ 'execute': 'query-name', 'id': 'ib-quick-1' }");
 239    send_oob_cmd_that_fails(qts, "oob-1");
 240    recv_cmd_id(qts, "oob-1");
 241    unblock_blocked_cmd();
 242    recv_cmd_id(qts, "ib-blocks-1");
 243    recv_cmd_id(qts, "ib-quick-1");
 244
 245    /* Even malformed in-band command fails in-band */
 246    send_cmd_that_blocks(qts, "blocks-2");
 247    qtest_async_qmp(qts, "{ 'id': 'err-2' }");
 248    unblock_blocked_cmd();
 249    recv_cmd_id(qts, "blocks-2");
 250    recv_cmd_id(qts, "err-2");
 251    cleanup_blocking_cmd();
 252
 253    qtest_quit(qts);
 254}
 255
 256/* Query smoke tests */
 257
 258static int query_error_class(const char *cmd)
 259{
 260    static struct {
 261        const char *cmd;
 262        int err_class;
 263    } fails[] = {
 264        /* Success depends on build configuration: */
 265#ifndef CONFIG_SPICE
 266        { "query-spice", ERROR_CLASS_COMMAND_NOT_FOUND },
 267#endif
 268#ifndef CONFIG_VNC
 269        { "query-vnc", ERROR_CLASS_GENERIC_ERROR },
 270        { "query-vnc-servers", ERROR_CLASS_GENERIC_ERROR },
 271#endif
 272#ifndef CONFIG_REPLICATION
 273        { "query-xen-replication-status", ERROR_CLASS_COMMAND_NOT_FOUND },
 274#endif
 275        /* Likewise, and require special QEMU command-line arguments: */
 276        { "query-acpi-ospm-status", ERROR_CLASS_GENERIC_ERROR },
 277        { "query-balloon", ERROR_CLASS_DEVICE_NOT_ACTIVE },
 278        { "query-hotpluggable-cpus", ERROR_CLASS_GENERIC_ERROR },
 279        { "query-vm-generation-id", ERROR_CLASS_GENERIC_ERROR },
 280        { NULL, -1 }
 281    };
 282    int i;
 283
 284    for (i = 0; fails[i].cmd; i++) {
 285        if (!strcmp(cmd, fails[i].cmd)) {
 286            return fails[i].err_class;
 287        }
 288    }
 289    return -1;
 290}
 291
 292static void test_query(const void *data)
 293{
 294    const char *cmd = data;
 295    int expected_error_class = query_error_class(cmd);
 296    QDict *resp, *error;
 297    const char *error_class;
 298
 299    qtest_start(common_args);
 300
 301    resp = qmp("{ 'execute': %s }", cmd);
 302    error = qdict_get_qdict(resp, "error");
 303    error_class = error ? qdict_get_str(error, "class") : NULL;
 304
 305    if (expected_error_class < 0) {
 306        g_assert(qdict_haskey(resp, "return"));
 307    } else {
 308        g_assert(error);
 309        g_assert_cmpint(qapi_enum_parse(&QapiErrorClass_lookup, error_class,
 310                                        -1, &error_abort),
 311                        ==, expected_error_class);
 312    }
 313    qobject_unref(resp);
 314
 315    qtest_end();
 316}
 317
 318static bool query_is_blacklisted(const char *cmd)
 319{
 320    const char *blacklist[] = {
 321        /* Not actually queries: */
 322        "add-fd",
 323        /* Success depends on target arch: */
 324        "query-cpu-definitions",  /* arm, i386, ppc, s390x */
 325        "query-gic-capabilities", /* arm */
 326        /* Success depends on target-specific build configuration: */
 327        "query-pci",              /* CONFIG_PCI */
 328        /* Success depends on launching SEV guest */
 329        "query-sev-launch-measure",
 330        /* Success depends on Host or Hypervisor SEV support */
 331        "query-sev",
 332        "query-sev-capabilities",
 333        NULL
 334    };
 335    int i;
 336
 337    for (i = 0; blacklist[i]; i++) {
 338        if (!strcmp(cmd, blacklist[i])) {
 339            return true;
 340        }
 341    }
 342    return false;
 343}
 344
 345typedef struct {
 346    SchemaInfoList *list;
 347    GHashTable *hash;
 348} QmpSchema;
 349
 350static void qmp_schema_init(QmpSchema *schema)
 351{
 352    QDict *resp;
 353    Visitor *qiv;
 354    SchemaInfoList *tail;
 355
 356    qtest_start(common_args);
 357    resp = qmp("{ 'execute': 'query-qmp-schema' }");
 358
 359    qiv = qobject_input_visitor_new(qdict_get(resp, "return"));
 360    visit_type_SchemaInfoList(qiv, NULL, &schema->list, &error_abort);
 361    visit_free(qiv);
 362
 363    qobject_unref(resp);
 364    qtest_end();
 365
 366    schema->hash = g_hash_table_new(g_str_hash, g_str_equal);
 367
 368    /* Build @schema: hash table mapping entity name to SchemaInfo */
 369    for (tail = schema->list; tail; tail = tail->next) {
 370        g_hash_table_insert(schema->hash, tail->value->name, tail->value);
 371    }
 372}
 373
 374static SchemaInfo *qmp_schema_lookup(QmpSchema *schema, const char *name)
 375{
 376    return g_hash_table_lookup(schema->hash, name);
 377}
 378
 379static void qmp_schema_cleanup(QmpSchema *schema)
 380{
 381    qapi_free_SchemaInfoList(schema->list);
 382    g_hash_table_destroy(schema->hash);
 383}
 384
 385static bool object_type_has_mandatory_members(SchemaInfo *type)
 386{
 387    SchemaInfoObjectMemberList *tail;
 388
 389    g_assert(type->meta_type == SCHEMA_META_TYPE_OBJECT);
 390
 391    for (tail = type->u.object.members; tail; tail = tail->next) {
 392        if (!tail->value->has_q_default) {
 393            return true;
 394        }
 395    }
 396
 397    return false;
 398}
 399
 400static void add_query_tests(QmpSchema *schema)
 401{
 402    SchemaInfoList *tail;
 403    SchemaInfo *si, *arg_type, *ret_type;
 404    char *test_name;
 405
 406    /* Test the query-like commands */
 407    for (tail = schema->list; tail; tail = tail->next) {
 408        si = tail->value;
 409        if (si->meta_type != SCHEMA_META_TYPE_COMMAND) {
 410            continue;
 411        }
 412
 413        if (query_is_blacklisted(si->name)) {
 414            continue;
 415        }
 416
 417        arg_type = qmp_schema_lookup(schema, si->u.command.arg_type);
 418        if (object_type_has_mandatory_members(arg_type)) {
 419            continue;
 420        }
 421
 422        ret_type = qmp_schema_lookup(schema, si->u.command.ret_type);
 423        if (ret_type->meta_type == SCHEMA_META_TYPE_OBJECT
 424            && !ret_type->u.object.members) {
 425            continue;
 426        }
 427
 428        test_name = g_strdup_printf("qmp/%s", si->name);
 429        qtest_add_data_func(test_name, si->name, test_query);
 430        g_free(test_name);
 431    }
 432}
 433
 434/* Preconfig tests */
 435
 436static void test_qmp_preconfig(void)
 437{
 438    QDict *rsp, *ret;
 439    QTestState *qs = qtest_startf("%s --preconfig", common_args);
 440
 441    /* preconfig state */
 442    /* enabled commands, no error expected  */
 443    g_assert(!qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'query-commands' }")));
 444
 445    /* forbidden commands, expected error */
 446    g_assert(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'query-cpus' }")));
 447
 448    /* check that query-status returns preconfig state */
 449    rsp = qtest_qmp(qs, "{ 'execute': 'query-status' }");
 450    ret = qdict_get_qdict(rsp, "return");
 451    g_assert(ret);
 452    g_assert_cmpstr(qdict_get_try_str(ret, "status"), ==, "preconfig");
 453    qobject_unref(rsp);
 454
 455    /* exit preconfig state */
 456    g_assert(!qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'x-exit-preconfig' }")));
 457    qtest_qmp_eventwait(qs, "RESUME");
 458
 459    /* check that query-status returns running state */
 460    rsp = qtest_qmp(qs, "{ 'execute': 'query-status' }");
 461    ret = qdict_get_qdict(rsp, "return");
 462    g_assert(ret);
 463    g_assert_cmpstr(qdict_get_try_str(ret, "status"), ==, "running");
 464    qobject_unref(rsp);
 465
 466    /* check that x-exit-preconfig returns error after exiting preconfig */
 467    g_assert(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'x-exit-preconfig' }")));
 468
 469    /* enabled commands, no error expected  */
 470    g_assert(!qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'query-cpus' }")));
 471
 472    qtest_quit(qs);
 473}
 474
 475int main(int argc, char *argv[])
 476{
 477    QmpSchema schema;
 478    int ret;
 479
 480    g_test_init(&argc, &argv, NULL);
 481
 482    qtest_add_func("qmp/protocol", test_qmp_protocol);
 483    qtest_add_func("qmp/oob", test_qmp_oob);
 484    qmp_schema_init(&schema);
 485    add_query_tests(&schema);
 486    qtest_add_func("qmp/preconfig", test_qmp_preconfig);
 487
 488    ret = g_test_run();
 489
 490    qmp_schema_cleanup(&schema);
 491    return ret;
 492}
 493