qmp_basic.py 14.5 KB
Newer Older
1
from autotest.client.shared import error
2
from virttest import qemu_monitor
3 4


5
def run(test, params, env):
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
    """
    QMP Specification test-suite: this checks if the *basic* protocol conforms
    to its specification, which is file QMP/qmp-spec.txt in QEMU's source tree.

    IMPORTANT NOTES:

        o Most tests depend heavily on QMP's error information (eg. classes),
          this might have bad implications as the error interface is going to
          change in QMP

        o Command testing is *not* covered in this suite. Each command has its
          own specification and should be tested separately

        o We use the same terminology as used by the QMP specification,
          specially with regard to JSON types (eg. a Python dict is called
          a json-object)

        o This is divided in sub test-suites, please check the bottom of this
          file to check the order in which they are run

    TODO:

        o Finding which test failed is not as easy as it should be

        o Are all those check_*() functions really needed? Wouldn't a
          specialized class (eg. a Response class) do better?
    """
    def fail_no_key(qmp_dict, key):
        if not isinstance(qmp_dict, dict):
            raise error.TestFail("qmp_dict is not a dict (it's '%s')" %
                                 type(qmp_dict))
37
        if key not in qmp_dict:
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
            raise error.TestFail("'%s' key doesn't exist in dict ('%s')" %
                                 (key, str(qmp_dict)))

    def check_dict_key(qmp_dict, key, keytype):
        """
        Performs the following checks on a QMP dict key:

        1. qmp_dict is a dict
        2. key exists in qmp_dict
        3. key is of type keytype

        If any of these checks fails, error.TestFail is raised.
        """
        fail_no_key(qmp_dict, key)
        if not isinstance(qmp_dict[key], keytype):
            raise error.TestFail("'%s' key is not of type '%s', it's '%s'" %
                                 (key, keytype, type(qmp_dict[key])))

    def check_key_is_dict(qmp_dict, key):
        check_dict_key(qmp_dict, key, dict)

    def check_key_is_list(qmp_dict, key):
        check_dict_key(qmp_dict, key, list)

    def check_key_is_str(qmp_dict, key):
        check_dict_key(qmp_dict, key, unicode)

    def check_str_key(qmp_dict, keyname, value=None):
        check_dict_key(qmp_dict, keyname, unicode)
        if value and value != qmp_dict[keyname]:
            raise error.TestFail("'%s' key value '%s' should be '%s'" %
                                 (keyname, str(qmp_dict[keyname]), str(value)))

    def check_key_is_int(qmp_dict, key):
        fail_no_key(qmp_dict, key)
        try:
            int(qmp_dict[key])
        except Exception:
            raise error.TestFail("'%s' key is not of type int, it's '%s'" %
                                 (key, type(qmp_dict[key])))

    def check_bool_key(qmp_dict, keyname, value=None):
        check_dict_key(qmp_dict, keyname, bool)
        if value and value != qmp_dict[keyname]:
            raise error.TestFail("'%s' key value '%s' should be '%s'" %
                                 (keyname, str(qmp_dict[keyname]), str(value)))

    def check_success_resp(resp, empty=False):
        """
        Check QMP OK response.

L
Lucas Meneghel Rodrigues 已提交
89 90
        :param resp: QMP response
        :param empty: if True, response should not contain data to return
91 92 93 94 95 96 97 98 99 100
        """
        check_key_is_dict(resp, "return")
        if empty and len(resp["return"]) > 0:
            raise error.TestFail("success response is not empty ('%s')" %
                                 str(resp))

    def check_error_resp(resp, classname=None, datadict=None):
        """
        Check QMP error response.

L
Lucas Meneghel Rodrigues 已提交
101 102 103
        :param resp: QMP response
        :param classname: Expected error class name
        :param datadict: Expected error data dictionary
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
        """
        check_key_is_dict(resp, "error")
        check_key_is_str(resp["error"], "class")
        if classname and resp["error"]["class"] != classname:
            raise error.TestFail("got error class '%s' expected '%s'" %
                                 (resp["error"]["class"], classname))

    def test_version(version):
        """
        Check the QMP greeting message version key which, according to QMP's
        documentation, should be:

        { "qemu": { "major": json-int, "minor": json-int, "micro": json-int }
          "package": json-string }
        """
        check_key_is_dict(version, "qemu")
120
        for key in ("major", "minor", "micro"):
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
            check_key_is_int(version["qemu"], key)
        check_key_is_str(version, "package")

    def test_greeting(greeting):
        check_key_is_dict(greeting, "QMP")
        check_key_is_dict(greeting["QMP"], "version")
        check_key_is_list(greeting["QMP"], "capabilities")

    def greeting_suite(monitor):
        """
        Check the greeting message format, as described in the QMP
        specfication section '2.2 Server Greeting'.

        { "QMP": { "version": json-object, "capabilities": json-array } }
        """
        greeting = monitor.get_greeting()
        test_greeting(greeting)
        test_version(greeting["QMP"]["version"])

    def json_parsing_errors_suite(monitor):
        """
        Check that QMP's parser is able to recover from parsing errors, please
        check the JSON spec for more info on the JSON syntax (RFC 4627).
        """
        # We're quite simple right now and the focus is on parsing errors that
        # have already biten us in the past.
        #
        # TODO: The following test-cases are missing:
        #
        #   - JSON numbers, strings and arrays
        #   - More invalid characters or malformed structures
        #   - Valid, but not obvious syntax, like zillion of spaces or
        #     strings with unicode chars (different suite maybe?)
        bad_json = []

        # A JSON value MUST be an object, array, number, string, true, false,
        # or null
        #
        # NOTE: QMP seems to ignore a number of chars, like: | and ?
        bad_json.append(":")
        bad_json.append(",")

        # Malformed json-objects
        #
        # NOTE: sending only "}" seems to break QMP
        # NOTE: Duplicate keys are accepted (should it?)
        bad_json.append("{ \"execute\" }")
        bad_json.append("{ \"execute\": \"query-version\", }")
        bad_json.append("{ 1: \"query-version\" }")
        bad_json.append("{ true: \"query-version\" }")
        bad_json.append("{ []: \"query-version\" }")
        bad_json.append("{ {}: \"query-version\" }")

        for cmd in bad_json:
            resp = monitor.cmd_raw(cmd)
176
            check_error_resp(resp, "GenericError")
177 178 179 180 181 182

    def test_id_key(monitor):
        """
        Check that QMP's "id" key is correctly handled.
        """
        # The "id" key must be echoed back in error responses
183
        id_key = "virt-test"
184
        resp = monitor.cmd_qmp("eject", {"foobar": True}, q_id=id_key)
185 186 187 188
        check_error_resp(resp)
        check_str_key(resp, "id", id_key)

        # The "id" key must be echoed back in success responses
189
        resp = monitor.cmd_qmp("query-status", q_id=id_key)
190 191 192 193
        check_success_resp(resp)
        check_str_key(resp, "id", id_key)

        # The "id" key can be any json-object
194 195
        for id_key in (True, 1234, "string again!", [1, [], {}, True, "foo"],
                       {"key": {}}):
196
            resp = monitor.cmd_qmp("query-status", q_id=id_key)
197 198 199 200 201 202 203 204 205 206 207
            check_success_resp(resp)
            if resp["id"] != id_key:
                raise error.TestFail("expected id '%s' but got '%s'" %
                                     (str(id_key), str(resp["id"])))

    def test_invalid_arg_key(monitor):
        """
        Currently, the only supported keys in the input object are: "execute",
        "arguments" and "id". Although expansion is supported, invalid key
        names must be detected.
        """
208 209
        resp = monitor.cmd_obj({"execute": "eject", "foobar": True})
        check_error_resp(resp, "GenericError", {"member": "foobar"})
210 211 212 213 214 215 216 217 218

    def test_bad_arguments_key_type(monitor):
        """
        The "arguments" key must be an json-object.

        We use the eject command to perform the tests, but that's a random
        choice, any command that accepts arguments will do, as the command
        doesn't get called.
        """
219 220
        for item in (True, [], 1, "foo"):
            resp = monitor.cmd_obj({"execute": "eject", "arguments": item})
221
            check_error_resp(resp, "GenericError",
222
                             {"member": "arguments", "expected": "object"})
223 224 225 226 227

    def test_bad_execute_key_type(monitor):
        """
        The "execute" key must be a json-string.
        """
228 229
        for item in (False, 1, {}, []):
            resp = monitor.cmd_obj({"execute": item})
230
            check_error_resp(resp, "GenericError",
231
                             {"member": "execute", "expected": "string"})
232 233 234 235 236 237

    def test_no_execute_key(monitor):
        """
        The "execute" key must exist, we also test for some stupid parsing
        errors.
        """
L
Lucas Meneghel Rodrigues 已提交
238 239
        for cmd in ({}, {"execut": "qmp_capabilities"},
                    {"executee": "qmp_capabilities"}, {"foo": "bar"}):
240
            resp = monitor.cmd_obj(cmd)
241
            check_error_resp(resp)  # XXX: check class and data dict?
242 243 244 245 246

    def test_bad_input_obj_type(monitor):
        """
        The input object must be... an json-object.
        """
247
        for cmd in ("foo", [], True, 1):
248
            resp = monitor.cmd_obj(cmd)
249
            check_error_resp(resp, "GenericError", {"expected": "object"})
250 251 252 253 254 255 256

    def test_good_input_obj(monitor):
        """
        Basic success tests for issuing QMP commands.
        """
        # NOTE: We don't use the cmd_qmp() method here because the command
        # object is in a 'random' order
257
        resp = monitor.cmd_obj({"execute": "query-version"})
258 259
        check_success_resp(resp)

260
        resp = monitor.cmd_obj({"arguments": {}, "execute": "query-version"})
261 262 263
        check_success_resp(resp)

        idd = "1234foo"
264 265
        resp = monitor.cmd_obj({"id": idd, "execute": "query-version",
                                "arguments": {}})
266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
        check_success_resp(resp)
        check_str_key(resp, "id", idd)

        # TODO: would be good to test simple argument usage, but we don't have
        # a read-only command that accepts arguments.

    def input_object_suite(monitor):
        """
        Check the input object format, as described in the QMP specfication
        section '2.3 Issuing Commands'.

        { "execute": json-string, "arguments": json-object, "id": json-value }
        """
        test_good_input_obj(monitor)
        test_bad_input_obj_type(monitor)
        test_no_execute_key(monitor)
        test_bad_execute_key_type(monitor)
        test_bad_arguments_key_type(monitor)
        test_id_key(monitor)
        test_invalid_arg_key(monitor)

    def argument_checker_suite(monitor):
        """
        Check that QMP's argument checker is detecting all possible errors.

        We use a number of different commands to perform the checks, but the
        command used doesn't matter much as QMP performs argument checking
        _before_ calling the command.
        """
        # stop doesn't take arguments
296 297
        resp = monitor.cmd_qmp("stop", {"foo": 1})
        check_error_resp(resp, "GenericError", {"name": "foo"})
298 299 300

        # required argument omitted
        resp = monitor.cmd_qmp("screendump")
301
        check_error_resp(resp, "GenericError", {"name": "filename"})
302 303

        # 'bar' is not a valid argument
304 305 306
        resp = monitor.cmd_qmp("screendump", {"filename": "outfile",
                                              "bar": "bar"})
        check_error_resp(resp, "GenericError", {"name": "bar"})
307 308 309 310

        # test optional argument: 'force' is omitted, but it's optional, so
        # the handler has to be called. Test this happens by checking an
        # error that is generated by the handler itself.
311
        resp = monitor.cmd_qmp("eject", {"device": "foobar"})
312 313 314
        check_error_resp(resp, "DeviceNotFound")

        # filename argument must be a json-string
315 316
        for arg in ({}, [], 1, True):
            resp = monitor.cmd_qmp("screendump", {"filename": arg})
317
            check_error_resp(resp, "GenericError",
318
                             {"name": "filename", "expected": "string"})
319 320

        # force argument must be a json-bool
321 322
        for arg in ({}, [], 1, "foo"):
            resp = monitor.cmd_qmp("eject", {"force": arg, "device": "foo"})
323
            check_error_resp(resp, "GenericError",
324
                             {"name": "force", "expected": "bool"})
325 326

        # val argument must be a json-int
327 328 329
        for arg in ({}, [], True, "foo"):
            resp = monitor.cmd_qmp("memsave", {"val": arg, "filename": "foo",
                                               "size": 10})
330
            check_error_resp(resp, "GenericError",
331
                             {"name": "val", "expected": "int"})
332 333

        # value argument must be a json-number
334 335
        for arg in ({}, [], True, "foo"):
            resp = monitor.cmd_qmp("migrate_set_speed", {"value": arg})
336
            check_error_resp(resp, "GenericError",
337
                             {"name": "value", "expected": "number"})
338 339 340 341 342

        # qdev-type commands have their own argument checker, all QMP does
        # is to skip its checking and pass arguments through. Check this
        # works by providing invalid options to device_add and expecting
        # an error message from qdev
343
        resp = monitor.cmd_qmp("device_add", {"driver": "e1000", "foo": "bar"})
344
        check_error_resp(resp, "GenericError",
345 346 347 348 349 350 351
                               {"device": "e1000", "property": "foo"})

    def unknown_commands_suite(monitor):
        """
        Check that QMP handles unknown commands correctly.
        """
        # We also call a HMP-only command, to be sure it will fail as expected
352
        for cmd in ("bar", "query-", "query-foo", "q", "help"):
353
            resp = monitor.cmd_qmp(cmd)
354
            check_error_resp(resp, "CommandNotFound", {"name": cmd})
355 356 357 358 359

    vm = env.get_vm(params["main_vm"])
    vm.verify_alive()

    # Look for the first qmp monitor available, otherwise, fail the test
360 361 362 363
    qmp_monitor = vm.get_monitors_by_type("qmp")
    if qmp_monitor:
        qmp_monitor = qmp_monitor[0]
    else:
364 365 366 367 368 369 370 371 372 373 374 375
        raise error.TestError('Could not find a QMP monitor, aborting test')

    # Run all suites
    greeting_suite(qmp_monitor)
    input_object_suite(qmp_monitor)
    argument_checker_suite(qmp_monitor)
    unknown_commands_suite(qmp_monitor)
    json_parsing_errors_suite(qmp_monitor)

    # check if QMP is still alive
    if not qmp_monitor.is_responsive():
        raise error.TestFail('QMP monitor is not responsive after testing')