new file mode 100644
@@ -0,0 +1,268 @@
+import logging, json
+from autotest_lib.client.common_lib import error
+import kvm_subprocess, kvm_test_utils, kvm_utils
+
+def run_qmp_basic(test, params, env):
+ """
+ QMP Specification test-suite: this checks if the *basic* protocol conforms
+ to its specification.
+
+ Please, check it suite for details.
+ """
+ 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))
+ if not key in qmp_dict:
+ 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:
+ value = int(qmp_dict[key])
+ except:
+ 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_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_key_is_dict(resp, "error")
+ check_key_is_str(resp["error"], "class")
+ if classname and resp["error"]["class"] != classname:
+ raise error.TestFail("error class is '%s' but should be '%s'"
+ % (resp["error"]["class"], classname))
+ check_key_is_dict(resp["error"], "data")
+ if datadict and resp["error"]["data"] != datadict:
+ raise error.TestFail("data dict is '%s' but should be '%s'"
+ % (resp["error"]["data"], datadict))
+
+ def test_version(version):
+ """
+ Test QMP greeting message's 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")
+ for key in [ "major", "minor", "micro" ]:
+ 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 QMP's greeting message which, according to QMP's spec section
+ '2.2 Server Greeting', is:
+
+ { "QMP": { "version": json-object, "capabilities": json-array } }
+ """
+ greeting = monitor.get_greeting()
+ test_greeting(greeting)
+ test_version(greeting["QMP"]["version"])
+
+
+ def json_parsing_suite(monitor):
+ """
+ Check the QMP's parser conforms to the JSON's spec (RFC 4627).
+ """
+ # We're quite simple right now and the focus is parsing problems
+ # that have already biten us in the past.
+ #
+ # However, 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)
+ bad_json = []
+
+ # A JSON value MUST be an object, array, number, string, or true,
+ # false, 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.send(cmd)
+ check_error_resp(resp, "JSONParsing")
+
+
+ def test_id_key(monitor):
+ """
+ Check if QMP's "id" key is handled correctly.
+ """
+ # The "id" key must be echoed back in error responses
+ id = "kvm-autotest"
+ resp = monitor.send_cmd({ "execute": "eject", "id": id,
+ "arguments": { "foobar": True }})
+ check_error_resp(resp)
+ check_str_key(resp, "id", id)
+
+ # The "id" key must be echoed back in success responses
+ resp = monitor.send_cmd({ "execute": "query-status", "id": id })
+ check_success_resp(resp)
+ check_str_key(resp, "id", id)
+
+ # The "id" key can be any json-object
+ for id in [ True, 1234, "string again!", [1, 2, 3, 4], { "key": {} } ]:
+ resp = monitor.send_cmd({ "execute": "query-status", "id": id })
+ check_success_resp(resp)
+ if resp["id"] != id:
+ raise error.TestFail("expected id '%s' but got '%s'"
+ % (str(id), 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.
+ """
+ resp = monitor.send_cmd({ "execute": "eject", "foobar": True })
+ check_error_resp(resp, "QMPExtraInputObjectMember",
+ { "member": "foobar" })
+
+ def test_arguments_key(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.
+ """
+ for item in [ True, [], 1, "foo" ]:
+ resp = monitor.send_cmd({ "execute": "eject", "arguments": item })
+ check_error_resp(resp, "QMPBadInputObjectMember",
+ { "member": "arguments", "expected": "object" })
+
+ def test_execute_key_type(monitor):
+ """
+ The "execute" key must be a json-string.
+ """
+ for item in [ False, 1, {}, [] ]:
+ resp = monitor.send_cmd({ "execute": item })
+ check_error_resp(resp, "QMPBadInputObjectMember",
+ { "member": "execute", "expected": "string" })
+
+ def test_no_execute_key(monitor):
+ """
+ The "execute" key must exist, we also test for some stupid parsing
+ errors.
+ """
+ for cmd in [ {}, { "execut": "qmp_capabilities" },
+ { "executee": "qmp_capabilities" }, { "foo": "bar" }]:
+ resp = monitor.send_cmd(cmd)
+ check_error_resp(resp) # XXX: check class and data dict?
+
+ def test_input_obj_type(monitor):
+ """
+ The input object must be... an object.
+ """
+ for cmd in [ "foo", [], True, 1 ]:
+ resp = monitor.send_cmd(cmd)
+ check_error_resp(resp, "QMPBadInputObject", { "expected":"object" })
+
+ def input_object_suite(monitor):
+ """
+ Check QMP's input object which, according to QMP's spec section
+ '2.3 Issuing Commands', is:
+
+ { "execute": json-string, "arguments": json-object, "id": json-value }
+ """
+ test_input_obj_type(monitor)
+ test_no_execute_key(monitor)
+ test_execute_key_type(monitor)
+ test_arguments_key(monitor)
+ test_invalid_arg_key(monitor)
+ test_id_key(monitor)
+
+ def argument_checker_suite(monitor):
+ """
+ Checks if QMP's argument checker is detecting all possible errors.
+
+ We use a number of different commands to perform the checks, but most
+ of the time the command used doesn't matter because QMP performs the
+ argument checking _before_ calling the command.
+ """
+ # system_reset doesn't take arguments
+ resp = monitor.send_cmd({ "execute": "system_reset", "arguments": { "foo": 1 }})
+ check_error_resp(resp, "InvalidParameter", { "name": "foo" })
+
+ def unknown_commands_suite(monitor):
+ """
+ Check if QMP handles unknown commands correctly.
+ """
+ # We also call a HMP-only command, to be sure it will fail as expected
+ for cmd in [ "bar", "query-", "query-foo", "q", "help" ]:
+ resp = monitor.send_cmd({ "execute": cmd })
+ check_error_resp(resp, "CommandNotFound", { "name": cmd })
+
+
+ vm = kvm_test_utils.get_living_vm(env, params.get("main_vm"))
+
+ # Run all suites
+ greeting_suite(vm.monitor)
+ json_parsing_suite(vm.monitor)
+ input_object_suite(vm.monitor)
+ argument_checker_suite(vm.monitor)
+ unknown_commands_suite(vm.monitor)
+
+ # check if QMP is still alive
+ resp = vm.monitor.cmd("query-status")
+ check_bool_key(resp, "running", True)
@@ -456,6 +456,9 @@ variants:
- fmt_raw:
image_format_stg = raw
+ - qmp_basic: install setup unattended_install.cdrom
+ type = qmp_basic
+
- vlan_tag: install setup unattended_install.cdrom
type = vlan_tag
# subnet should not be used by host