backup: Wire up qemu full pull backup commands over QMP
[libvirt/ericb.git] / tests / testutilsqemuschema.c
blobf1365e8846bb66f26b7e9493891461fd53f6275c
1 /*
2 * testutilsqemuschema.c: helper functions for QEMU QAPI schema testing
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see
16 * <http://www.gnu.org/licenses/>.
18 #include <config.h>
19 #include "testutils.h"
20 #include "testutilsqemu.h"
21 #include "testutilsqemuschema.h"
22 #include "qemu/qemu_qapi.h"
24 static int
25 testQEMUSchemaValidateRecurse(virJSONValuePtr obj,
26 virJSONValuePtr root,
27 virHashTablePtr schema,
28 virBufferPtr debug);
30 static int
31 testQEMUSchemaValidateBuiltin(virJSONValuePtr obj,
32 virJSONValuePtr root,
33 virBufferPtr debug)
35 const char *t = virJSONValueObjectGetString(root, "json-type");
36 const char *s = NULL;
37 bool b = false;
38 int ret = -1;
40 if (STREQ_NULLABLE(t, "value")) {
41 s = "{any}";
42 ret = 0;
43 goto cleanup;
46 switch (virJSONValueGetType(obj)) {
47 case VIR_JSON_TYPE_STRING:
48 if (STRNEQ_NULLABLE(t, "string"))
49 goto cleanup;
50 s = virJSONValueGetString(obj);
51 break;
53 case VIR_JSON_TYPE_NUMBER:
54 if (STRNEQ_NULLABLE(t, "int") &&
55 STRNEQ_NULLABLE(t, "number"))
56 goto cleanup;
57 s = "{number}";
58 break;
60 case VIR_JSON_TYPE_BOOLEAN:
61 if (STRNEQ_NULLABLE(t, "boolean"))
62 goto cleanup;
63 virJSONValueGetBoolean(obj, &b);
64 if (b)
65 s = "true";
66 else
67 s = "false";
68 break;
70 case VIR_JSON_TYPE_NULL:
71 if (STRNEQ_NULLABLE(t, "null"))
72 goto cleanup;
73 break;
75 case VIR_JSON_TYPE_OBJECT:
76 case VIR_JSON_TYPE_ARRAY:
77 goto cleanup;
80 ret = 0;
82 cleanup:
83 if (ret == 0)
84 virBufferAsprintf(debug, "'%s': OK", s);
85 else
86 virBufferAsprintf(debug, "ERROR: expected type '%s', actual type %d",
87 t, virJSONValueGetType(obj));
88 return ret;
91 struct testQEMUSchemaValidateObjectMemberData {
92 virJSONValuePtr rootmembers;
93 virHashTablePtr schema;
94 virBufferPtr debug;
95 bool missingMandatory;
99 static virJSONValuePtr
100 testQEMUSchemaStealObjectMemberByName(const char *name,
101 virJSONValuePtr members)
103 virJSONValuePtr member;
104 virJSONValuePtr ret = NULL;
105 size_t i;
107 for (i = 0; i < virJSONValueArraySize(members); i++) {
108 member = virJSONValueArrayGet(members, i);
110 if (STREQ_NULLABLE(name, virJSONValueObjectGetString(member, "name"))) {
111 ret = virJSONValueArraySteal(members, i);
112 break;
116 return ret;
120 static int
121 testQEMUSchemaValidateObjectMember(const char *key,
122 virJSONValuePtr value,
123 void *opaque)
125 struct testQEMUSchemaValidateObjectMemberData *data = opaque;
126 virJSONValuePtr keymember = NULL;
127 const char *keytype;
128 virJSONValuePtr keyschema = NULL;
129 int ret = -1;
131 virBufferStrcat(data->debug, key, ": ", NULL);
133 /* lookup 'member' entry for key */
134 if (!(keymember = testQEMUSchemaStealObjectMemberByName(key, data->rootmembers))) {
135 virBufferAddLit(data->debug, "ERROR: attribute not in schema");
136 goto cleanup;
139 /* lookup schema entry for keytype */
140 if (!(keytype = virJSONValueObjectGetString(keymember, "type")) ||
141 !(keyschema = virHashLookup(data->schema, keytype))) {
142 virBufferAsprintf(data->debug, "ERROR: can't find schema for type '%s'",
143 NULLSTR(keytype));
144 ret = -2;
145 goto cleanup;
148 /* recurse */
149 ret = testQEMUSchemaValidateRecurse(value, keyschema, data->schema,
150 data->debug);
152 cleanup:
153 virBufferAddLit(data->debug, "\n");
154 virJSONValueFree(keymember);
155 return ret;
159 static int
160 testQEMUSchemaValidateObjectMergeVariantMember(size_t pos ATTRIBUTE_UNUSED,
161 virJSONValuePtr item,
162 void *opaque)
164 virJSONValuePtr array = opaque;
165 virJSONValuePtr copy;
167 if (!(copy = virJSONValueCopy(item)))
168 return -1;
170 if (virJSONValueArrayAppend(array, copy) < 0)
171 return -1;
173 return 1;
178 * testQEMUSchemaValidateObjectMergeVariant:
180 * Merges schema of variant @variantname in @root into @root and removes the
181 * 'variants' array from @root.
183 static int
184 testQEMUSchemaValidateObjectMergeVariant(virJSONValuePtr root,
185 const char *variantfield,
186 const char *variantname,
187 virHashTablePtr schema,
188 virBufferPtr debug)
190 size_t i;
191 virJSONValuePtr variants = NULL;
192 virJSONValuePtr variant;
193 virJSONValuePtr variantschema;
194 virJSONValuePtr variantschemamembers;
195 virJSONValuePtr rootmembers;
196 const char *varianttype = NULL;
197 int ret = -1;
199 if (!(variants = virJSONValueObjectStealArray(root, "variants"))) {
200 virBufferAddLit(debug, "ERROR: missing 'variants' in schema\n");
201 return -2;
204 for (i = 0; i < virJSONValueArraySize(variants); i++) {
205 variant = virJSONValueArrayGet(variants, i);
207 if (STREQ_NULLABLE(variantname,
208 virJSONValueObjectGetString(variant, "case"))) {
209 varianttype = virJSONValueObjectGetString(variant, "type");
210 break;
214 if (!varianttype) {
215 virBufferAsprintf(debug, "ERROR: variant '%s' for discriminator '%s' not found\n",
216 variantname, variantfield);
217 goto cleanup;
221 if (!(variantschema = virHashLookup(schema, varianttype)) ||
222 !(variantschemamembers = virJSONValueObjectGetArray(variantschema, "members"))) {
223 virBufferAsprintf(debug,
224 "ERROR: missing schema or schema members for variant '%s'(%s)\n",
225 variantname, varianttype);
226 ret = -2;
227 goto cleanup;
230 rootmembers = virJSONValueObjectGetArray(root, "members");
232 if (virJSONValueArrayForeachSteal(variantschemamembers,
233 testQEMUSchemaValidateObjectMergeVariantMember,
234 rootmembers) < 0) {
235 ret = -2;
236 goto cleanup;
239 ret = 0;
241 cleanup:
242 virJSONValueFree(variants);
243 return ret;
247 static int
248 testQEMUSchemaValidateObjectMandatoryMember(size_t pos ATTRIBUTE_UNUSED,
249 virJSONValuePtr item,
250 void *opaque ATTRIBUTE_UNUSED)
252 struct testQEMUSchemaValidateObjectMemberData *data = opaque;
254 if (virJSONValueObjectHasKey(item, "default") != 1) {
255 virBufferAsprintf(data->debug, "ERROR: missing mandatory attribute '%s'\n",
256 NULLSTR(virJSONValueObjectGetString(item, "name")));
257 data->missingMandatory = true;
260 return 1;
264 static int
265 testQEMUSchemaValidateObject(virJSONValuePtr obj,
266 virJSONValuePtr root,
267 virHashTablePtr schema,
268 virBufferPtr debug)
270 struct testQEMUSchemaValidateObjectMemberData data = { NULL, schema,
271 debug, false };
272 virJSONValuePtr localroot = NULL;
273 const char *variantfield;
274 const char *variantname;
275 int ret = -1;
277 if (virJSONValueGetType(obj) != VIR_JSON_TYPE_OBJECT) {
278 virBufferAddLit(debug, "ERROR: not an object");
279 return -1;
282 virBufferAddLit(debug, "{\n");
283 virBufferAdjustIndent(debug, 3);
285 /* copy schema */
286 if (!(localroot = virJSONValueCopy(root))) {
287 ret = -2;
288 goto cleanup;
291 /* remove variant */
292 if ((variantfield = virJSONValueObjectGetString(localroot, "tag"))) {
293 if (!(variantname = virJSONValueObjectGetString(obj, variantfield))) {
294 virBufferAsprintf(debug, "ERROR: missing variant discriminator attribute '%s'\n",
295 variantfield);
296 goto cleanup;
299 if (testQEMUSchemaValidateObjectMergeVariant(localroot, variantfield,
300 variantname,
301 schema, debug) < 0)
302 goto cleanup;
306 /* validate members */
307 data.rootmembers = virJSONValueObjectGetArray(localroot, "members");
308 if (virJSONValueObjectForeachKeyValue(obj,
309 testQEMUSchemaValidateObjectMember,
310 &data) < 0)
311 goto cleanup;
313 /* check missing mandatory values */
314 if (virJSONValueArrayForeachSteal(data.rootmembers,
315 testQEMUSchemaValidateObjectMandatoryMember,
316 &data) < 0) {
317 ret = -2;
318 goto cleanup;
321 if (data.missingMandatory)
322 goto cleanup;
324 virBufferAdjustIndent(debug, -3);
325 virBufferAddLit(debug, "} OK");
326 ret = 0;
328 cleanup:
329 virJSONValueFree(localroot);
330 return ret;
334 static int
335 testQEMUSchemaValidateEnum(virJSONValuePtr obj,
336 virJSONValuePtr root,
337 virBufferPtr debug)
339 const char *objstr;
340 virJSONValuePtr values = NULL;
341 virJSONValuePtr value;
342 size_t i;
344 if (virJSONValueGetType(obj) != VIR_JSON_TYPE_STRING) {
345 virBufferAddLit(debug, "ERROR: not a string");
346 return -1;
349 objstr = virJSONValueGetString(obj);
351 if (!(values = virJSONValueObjectGetArray(root, "values"))) {
352 virBufferAsprintf(debug, "ERROR: missing enum values in schema '%s'",
353 NULLSTR(virJSONValueObjectGetString(root, "name")));
354 return -2;
357 for (i = 0; i < virJSONValueArraySize(values); i++) {
358 value = virJSONValueArrayGet(values, i);
360 if (STREQ_NULLABLE(objstr, virJSONValueGetString(value))) {
361 virBufferAsprintf(debug, "'%s' OK", NULLSTR(objstr));
362 return 0;
366 virBufferAsprintf(debug, "ERROR: enum value '%s' is not in schema",
367 NULLSTR(objstr));
368 return -1;
372 static int
373 testQEMUSchemaValidateArray(virJSONValuePtr objs,
374 virJSONValuePtr root,
375 virHashTablePtr schema,
376 virBufferPtr debug)
378 const char *elemtypename = virJSONValueObjectGetString(root, "element-type");
379 virJSONValuePtr elementschema;
380 virJSONValuePtr obj;
381 size_t i;
383 if (virJSONValueGetType(objs) != VIR_JSON_TYPE_ARRAY) {
384 virBufferAddLit(debug, "ERROR: not an array\n");
385 return -1;
388 if (!elemtypename ||
389 !(elementschema = virHashLookup(schema, elemtypename))) {
390 virBufferAsprintf(debug, "ERROR: missing schema for array element type '%s'",
391 NULLSTR(elemtypename));
392 return -2;
395 virBufferAddLit(debug, "[\n");
396 virBufferAdjustIndent(debug, 3);
398 for (i = 0; i < virJSONValueArraySize(objs); i++) {
399 obj = virJSONValueArrayGet(objs, i);
401 if (testQEMUSchemaValidateRecurse(obj, elementschema, schema, debug) < 0)
402 return -1;
403 virBufferAddLit(debug, ",\n");
405 virBufferAddLit(debug, "] OK");
406 virBufferAdjustIndent(debug, -3);
408 return 0;
411 static int
412 testQEMUSchemaValidateAlternate(virJSONValuePtr obj,
413 virJSONValuePtr root,
414 virHashTablePtr schema,
415 virBufferPtr debug)
417 virJSONValuePtr members;
418 virJSONValuePtr member;
419 size_t i;
420 size_t n;
421 const char *membertype;
422 virJSONValuePtr memberschema;
423 int indent;
424 int rc;
426 if (!(members = virJSONValueObjectGetArray(root, "members"))) {
427 virBufferAddLit(debug, "ERROR: missing 'members' for alternate schema");
428 return -2;
431 virBufferAddLit(debug, "(\n");
432 virBufferAdjustIndent(debug, 3);
433 indent = virBufferGetIndent(debug, false);
435 n = virJSONValueArraySize(members);
436 for (i = 0; i < n; i++) {
437 membertype = NULL;
439 /* P != NP */
440 virBufferAsprintf(debug, "(alternate %zu/%zu)\n", i + 1, n);
441 virBufferAdjustIndent(debug, 3);
443 if (!(member = virJSONValueArrayGet(members, i)) ||
444 !(membertype = virJSONValueObjectGetString(member, "type")) ||
445 !(memberschema = virHashLookup(schema, membertype))) {
446 virBufferAsprintf(debug, "ERROR: missing schema for alternate type '%s'",
447 NULLSTR(membertype));
448 return -2;
451 rc = testQEMUSchemaValidateRecurse(obj, memberschema, schema, debug);
453 virBufferAddLit(debug, "\n");
454 virBufferSetIndent(debug, indent);
455 virBufferAsprintf(debug, "(/alternate %zu/%zu)\n", i + 1, n);
457 if (rc == 0) {
458 virBufferAdjustIndent(debug, -3);
459 virBufferAddLit(debug, ") OK");
460 return 0;
464 virBufferAddLit(debug, "ERROR: no alternate type was matched");
465 return -1;
469 static int
470 testQEMUSchemaValidateRecurse(virJSONValuePtr obj,
471 virJSONValuePtr root,
472 virHashTablePtr schema,
473 virBufferPtr debug)
475 const char *n = virJSONValueObjectGetString(root, "name");
476 const char *t = virJSONValueObjectGetString(root, "meta-type");
478 if (STREQ_NULLABLE(t, "builtin")) {
479 return testQEMUSchemaValidateBuiltin(obj, root, debug);
480 } else if (STREQ_NULLABLE(t, "object")) {
481 return testQEMUSchemaValidateObject(obj, root, schema, debug);
482 } else if (STREQ_NULLABLE(t, "enum")) {
483 return testQEMUSchemaValidateEnum(obj, root, debug);
484 } else if (STREQ_NULLABLE(t, "array")) {
485 return testQEMUSchemaValidateArray(obj, root, schema, debug);
486 } else if (STREQ_NULLABLE(t, "alternate")) {
487 return testQEMUSchemaValidateAlternate(obj, root, schema, debug);
490 virBufferAsprintf(debug,
491 "qapi schema meta-type '%s' of type '%s' not handled\n",
492 NULLSTR(t), NULLSTR(n));
493 return -2;
498 * testQEMUSchemaValidate:
499 * @obj: object to validate
500 * @root: schema entry to start from
501 * @schema: hash table containing schema entries
502 * @debug: a virBuffer which will be filled with debug information if provided
504 * Validates whether @obj conforms to the QAPI schema passed in via @schema,
505 * starting from the node @root. Returns 0, if @obj matches @schema, -1 if it
506 * does not and -2 if there is a problem with the schema or with internals.
508 * @debug is filled with information regarding the validation process
511 testQEMUSchemaValidate(virJSONValuePtr obj,
512 virJSONValuePtr root,
513 virHashTablePtr schema,
514 virBufferPtr debug)
516 return testQEMUSchemaValidateRecurse(obj, root, schema, debug);
521 * testQEMUSchemaGetLatest:
523 * Returns the schema data as the qemu monitor would reply from the latest
524 * replies file used for qemucapabilitiestest for the x86_64 architecture.
526 virJSONValuePtr
527 testQEMUSchemaGetLatest(void)
529 char *capsLatestFile = NULL;
530 char *capsLatest = NULL;
531 char *schemaReply;
532 char *end;
533 virJSONValuePtr reply = NULL;
534 virJSONValuePtr schema = NULL;
536 if (!(capsLatestFile = testQemuGetLatestCapsForArch("x86_64", "replies"))) {
537 VIR_TEST_VERBOSE("failed to find latest caps replies\n");
538 return NULL;
541 VIR_TEST_DEBUG("replies file: '%s'\n", capsLatestFile);
543 if (virTestLoadFile(capsLatestFile, &capsLatest) < 0)
544 goto cleanup;
546 if (!(schemaReply = strstr(capsLatest, "\"execute\": \"query-qmp-schema\"")) ||
547 !(schemaReply = strstr(schemaReply, "\n\n")) ||
548 !(end = strstr(schemaReply + 2, "\n\n"))) {
549 VIR_TEST_VERBOSE("failed to find reply to 'query-qmp-schema' in '%s'\n",
550 capsLatestFile);
551 goto cleanup;
554 schemaReply += 2;
555 *end = '\0';
557 if (!(reply = virJSONValueFromString(schemaReply))) {
558 VIR_TEST_VERBOSE("failed to parse 'query-qmp-schema' reply from '%s'\n",
559 capsLatestFile);
560 goto cleanup;
563 if (!(schema = virJSONValueObjectStealArray(reply, "return"))) {
564 VIR_TEST_VERBOSE("missing qapi schema data in reply in '%s'\n",
565 capsLatestFile);
566 goto cleanup;
569 cleanup:
570 VIR_FREE(capsLatestFile);
571 VIR_FREE(capsLatest);
572 virJSONValueFree(reply);
573 return schema;
577 virHashTablePtr
578 testQEMUSchemaLoad(void)
580 virJSONValuePtr schema;
582 if (!(schema = testQEMUSchemaGetLatest()))
583 return NULL;
585 return virQEMUQAPISchemaConvert(schema);