Index: client/tests/kvm/tests/qmp_basic_rhel6.py |
diff --git a/client/tests/kvm/tests/qmp_basic_rhel6.py b/client/tests/kvm/tests/qmp_basic_rhel6.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..24298b804b7e8447b18699a27df1415e6c7074ee |
--- /dev/null |
+++ b/client/tests/kvm/tests/qmp_basic_rhel6.py |
@@ -0,0 +1,389 @@ |
+import logging |
+from autotest_lib.client.common_lib import error |
+import kvm_monitor |
+ |
+ |
+def run_qmp_basic_rhel6(test, params, env): |
+ """ |
+ 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)) |
+ 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: |
+ 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 QMP OK response. |
+ |
+ @param resp: QMP response |
+ @param empty: if True, response should not contain data to return |
+ """ |
+ 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. |
+ |
+ @param resp: QMP response |
+ @param classname: Expected error class name |
+ @param datadict: Expected error data dictionary |
+ """ |
+ logging.debug("resp %s", str(resp)) |
+ 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)) |
+ check_key_is_dict(resp["error"], "data") |
+ if datadict and resp["error"]["data"] != datadict: |
+ raise error.TestFail("got data dict '%s' expected '%s'" % |
+ (resp["error"]["data"], datadict)) |
+ |
+ |
+ 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_str(version, "qemu") |
+ 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) |
+ check_error_resp(resp, "JSONParsing") |
+ |
+ |
+ def test_id_key(monitor): |
+ """ |
+ Check that QMP's "id" key is correctly handled. |
+ """ |
+ # The "id" key must be echoed back in error responses |
+ id_key = "kvm-autotest" |
+ resp = monitor.cmd_qmp("eject", { "foobar": True }, id=id_key) |
+ check_error_resp(resp) |
+ check_str_key(resp, "id", id_key) |
+ |
+ # The "id" key must be echoed back in success responses |
+ resp = monitor.cmd_qmp("query-status", id=id_key) |
+ check_success_resp(resp) |
+ check_str_key(resp, "id", id_key) |
+ |
+ # The "id" key can be any json-object |
+ for id_key in [ True, 1234, "string again!", [1, [], {}, True, "foo"], |
+ { "key": {} } ]: |
+ resp = monitor.cmd_qmp("query-status", id=id_key) |
+ 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. |
+ """ |
+ resp = monitor.cmd_obj({ "execute": "eject", "foobar": True }) |
+ expected_error = "MissingParameter" |
+ data_dict = {"name": "device"} |
+ check_error_resp(resp, expected_error, data_dict) |
+ |
+ |
+ 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. |
+ """ |
+ for item in [ True, [], 1, "foo" ]: |
+ resp = monitor.cmd_obj({ "execute": "eject", "arguments": item }) |
+ check_error_resp(resp, "QMPBadInputObjectMember", |
+ { "member": "arguments", "expected": "object" }) |
+ |
+ |
+ def test_bad_execute_key_type(monitor): |
+ """ |
+ The "execute" key must be a json-string. |
+ """ |
+ for item in [ False, 1, {}, [] ]: |
+ resp = monitor.cmd_obj({ "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.cmd_obj(cmd) |
+ check_error_resp(resp) # XXX: check class and data dict? |
+ |
+ |
+ def test_bad_input_obj_type(monitor): |
+ """ |
+ The input object must be... an json-object. |
+ """ |
+ for cmd in [ "foo", [], True, 1 ]: |
+ resp = monitor.cmd_obj(cmd) |
+ check_error_resp(resp, "QMPBadInputObject", { "expected":"object" }) |
+ |
+ |
+ 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 |
+ resp = monitor.cmd_obj({ "execute": "query-version" }) |
+ check_success_resp(resp) |
+ |
+ resp = monitor.cmd_obj({ "arguments": {}, "execute": "query-version" }) |
+ check_success_resp(resp) |
+ |
+ id_key = "1234foo" |
+ resp = monitor.cmd_obj({ "id": id_key, "execute": "query-version", |
+ "arguments": {} }) |
+ check_success_resp(resp) |
+ check_str_key(resp, "id", id_key) |
+ |
+ # 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. |
+ """ |
+ # qmp in RHEL6 is different from 0.13.*: |
+ # 1. 'stop' command just return {} evenif stop have arguments. |
+ # 2. there is no 'screendump' command. |
+ # 3. argument isn't checked in 'device' command. |
+ # so skip these tests in RHEL6. |
+ |
+ # 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. |
+ resp = monitor.cmd_qmp("eject", { "device": "foobar" }) |
+ check_error_resp(resp, "DeviceNotFound") |
+ |
+ # val argument must be a json-int |
+ for arg in [ {}, [], True, "foo" ]: |
+ resp = monitor.cmd_qmp("memsave", { "val": arg, "filename": "foo", |
+ "size": 10 }) |
+ check_error_resp(resp, "InvalidParameterType", |
+ { "name": "val", "expected": "int" }) |
+ |
+ # value argument must be a json-number |
+ for arg in [ {}, [], True, "foo" ]: |
+ resp = monitor.cmd_qmp("migrate_set_speed", { "value": arg }) |
+ check_error_resp(resp, "InvalidParameterType", |
+ { "name": "value", "expected": "number" }) |
+ |
+ # 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 |
+ resp = monitor.cmd_qmp("device_add", {"driver": "e1000", |
+ "foo": "bar" }) |
+ check_error_resp(resp, "PropertyNotFound", |
+ {"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 |
+ for cmd in [ "bar", "query-", "query-foo", "q", "help" ]: |
+ resp = monitor.cmd_qmp(cmd) |
+ check_error_resp(resp, "CommandNotFound", { "name": cmd }) |
+ |
+ |
+ vm = env.get_vm(params["main_vm"]) |
+ vm.verify_alive() |
+ |
+ # Look for the first qmp monitor available, otherwise, fail the test |
+ qmp_monitor = None |
+ for m in vm.monitors: |
+ if isinstance(m, kvm_monitor.QMPMonitor): |
+ qmp_monitor = m |
+ |
+ if qmp_monitor is None: |
+ 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') |