/* * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see * . */ #include #include "testutils.h" #if WITH_YAJL # include "testutilsqemu.h" # include "testutilsqemuschema.h" # include "virstoragefile.h" # include "virstring.h" # include "virlog.h" # include "qemu/qemu_block.h" # include "qemu/qemu_qapi.h" # include "qemu/qemu_command.h" # define LIBVIRT_SNAPSHOT_CONF_PRIV_H_ALLOW # include "conf/snapshot_conf_priv.h" # define VIR_FROM_THIS VIR_FROM_NONE VIR_LOG_INIT("tests.storagetest"); struct testBackingXMLjsonXMLdata { int type; const char *xml; }; static int testBackingXMLjsonXML(const void *args) { const struct testBackingXMLjsonXMLdata *data = args; g_autoptr(xmlDoc) xml = NULL; g_autoptr(xmlXPathContext) ctxt = NULL; g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; g_autoptr(virJSONValue) backendprops = NULL; g_autoptr(virJSONValue) wrapper = NULL; g_autofree char *propsstr = NULL; g_autofree char *protocolwrapper = NULL; g_autofree char *actualxml = NULL; g_autoptr(virStorageSource) xmlsrc = NULL; g_autoptr(virStorageSource) jsonsrc = NULL; if (!(xmlsrc = virStorageSourceNew())) return -1; xmlsrc->type = data->type; if (!(xml = virXMLParseStringCtxt(data->xml, "(test storage source XML)", &ctxt))) return -1; if (virDomainStorageSourceParse(ctxt->node, ctxt, xmlsrc, 0, NULL) < 0) { fprintf(stderr, "failed to parse disk source xml\n"); return -1; } if (!(backendprops = qemuBlockStorageSourceGetBackendProps(xmlsrc, true, false, false))) { fprintf(stderr, "failed to format disk source json\n"); return -1; } if (virJSONValueObjectCreate(&wrapper, "a:file", &backendprops, NULL) < 0) return -1; if (!(propsstr = virJSONValueToString(wrapper, false))) return -1; protocolwrapper = g_strdup_printf("json:%s", propsstr); if (virStorageSourceNewFromBackingAbsolute(protocolwrapper, &jsonsrc) < 0) { fprintf(stderr, "failed to parse disk json\n"); return -1; } if (virDomainDiskSourceFormat(&buf, jsonsrc, "source", 0, false, 0, NULL) < 0 || !(actualxml = virBufferContentAndReset(&buf))) { fprintf(stderr, "failed to format disk source xml\n"); return -1; } if (STRNEQ(actualxml, data->xml)) { fprintf(stderr, "\n expected storage source xml:\n'%s'\n" "actual storage source xml:\n%s\n" "intermediate json:\n%s\n", data->xml, actualxml, protocolwrapper); return -1; } return 0; } struct testQemuDiskXMLToJSONData { virQEMUDriverPtr driver; virHashTablePtr schema; virJSONValuePtr schemaroot; const char *name; bool fail; virJSONValuePtr *props; size_t nprops; virJSONValuePtr *propssrc; size_t npropssrc; virQEMUCapsPtr qemuCaps; }; static void testQemuDiskXMLToPropsClear(struct testQemuDiskXMLToJSONData *data) { size_t i; for (i = 0; i < data->nprops; i++) virJSONValueFree(data->props[i]); for (i = 0; i < data->npropssrc; i++) virJSONValueFree(data->propssrc[i]); data->nprops = 0; VIR_FREE(data->props); data->npropssrc = 0; VIR_FREE(data->propssrc); } static int testQemuDiskXMLToJSONFakeSecrets(virStorageSourcePtr src) { qemuDomainStorageSourcePrivatePtr srcpriv; if (!src->privateData && !(src->privateData = qemuDomainStorageSourcePrivateNew())) return -1; srcpriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src); if (src->auth) { if (VIR_ALLOC(srcpriv->secinfo) < 0) return -1; srcpriv->secinfo->type = VIR_DOMAIN_SECRET_INFO_TYPE_AES; srcpriv->secinfo->s.aes.username = g_strdup(src->auth->username); srcpriv->secinfo->s.aes.alias = g_strdup_printf("%s-secalias", NULLSTR(src->nodestorage)); } if (src->encryption) { if (VIR_ALLOC(srcpriv->encinfo) < 0) return -1; srcpriv->encinfo->type = VIR_DOMAIN_SECRET_INFO_TYPE_AES; srcpriv->encinfo->s.aes.alias = g_strdup_printf("%s-encalias", NULLSTR(src->nodeformat)); } return 0; } static const char *testQemuDiskXMLToJSONPath = abs_srcdir "/qemublocktestdata/xml2json/"; static int testQemuDiskXMLToProps(const void *opaque) { struct testQemuDiskXMLToJSONData *data = (void *) opaque; virDomainDiskDefPtr disk = NULL; virStorageSourcePtr n; virJSONValuePtr formatProps = NULL; virJSONValuePtr storageProps = NULL; g_autoptr(virJSONValue) storageSrcOnlyProps = NULL; char *xmlpath = NULL; char *xmlstr = NULL; int ret = -1; xmlpath = g_strdup_printf("%s%s.xml", testQemuDiskXMLToJSONPath, data->name); if (virTestLoadFile(xmlpath, &xmlstr) < 0) goto cleanup; /* qemu stores node names in the status XML portion */ if (!(disk = virDomainDiskDefParse(xmlstr, NULL, data->driver->xmlopt, VIR_DOMAIN_DEF_PARSE_STATUS))) goto cleanup; if (qemuCheckDiskConfig(disk, data->qemuCaps) < 0 || qemuDomainDeviceDefValidateDisk(disk, data->qemuCaps) < 0) { VIR_TEST_VERBOSE("invalid configuration for disk"); goto cleanup; } for (n = disk->src; virStorageSourceIsBacking(n); n = n->backingStore) { if (testQemuDiskXMLToJSONFakeSecrets(n) < 0) goto cleanup; if (qemuDomainValidateStorageSource(n, data->qemuCaps) < 0) goto cleanup; qemuDomainPrepareDiskSourceData(disk, n); if (!(formatProps = qemuBlockStorageSourceGetBlockdevProps(n, n->backingStore)) || !(storageSrcOnlyProps = qemuBlockStorageSourceGetBackendProps(n, false, true, true)) || !(storageProps = qemuBlockStorageSourceGetBackendProps(n, false, false, true))) { if (!data->fail) { VIR_TEST_VERBOSE("failed to generate qemu blockdev props"); goto cleanup; } } else if (data->fail) { VIR_TEST_VERBOSE("qemu blockdev props should have failed"); goto cleanup; } if (VIR_APPEND_ELEMENT(data->props, data->nprops, formatProps) < 0 || VIR_APPEND_ELEMENT(data->props, data->nprops, storageProps) < 0 || VIR_APPEND_ELEMENT(data->propssrc, data->npropssrc, storageSrcOnlyProps) < 0) goto cleanup; } ret = 0; cleanup: virJSONValueFree(formatProps); virJSONValueFree(storageProps); virDomainDiskDefFree(disk); VIR_FREE(xmlpath); VIR_FREE(xmlstr); return ret; } static int testQemuDiskXMLToPropsValidateSchema(const void *opaque) { struct testQemuDiskXMLToJSONData *data = (void *) opaque; virBuffer debug = VIR_BUFFER_INITIALIZER; char *propsstr = NULL; char *debugmsg = NULL; int ret = 0; size_t i; if (data->fail) return EXIT_AM_SKIP; for (i = 0; i < data->nprops; i++) { if (testQEMUSchemaValidate(data->props[i], data->schemaroot, data->schema, &debug) < 0) { debugmsg = virBufferContentAndReset(&debug); propsstr = virJSONValueToString(data->props[i], true); VIR_TEST_VERBOSE("json does not conform to QAPI schema"); VIR_TEST_DEBUG("json:\n%s\ndoes not match schema. Debug output:\n %s", propsstr, NULLSTR(debugmsg)); VIR_FREE(debugmsg); VIR_FREE(propsstr); ret = -1; } virBufferFreeAndReset(&debug); } for (i = 0; i < data->npropssrc; i++) { if (testQEMUSchemaValidate(data->propssrc[i], data->schemaroot, data->schema, &debug) < 0) { debugmsg = virBufferContentAndReset(&debug); propsstr = virJSONValueToString(data->propssrc[i], true); VIR_TEST_VERBOSE("json does not conform to QAPI schema"); VIR_TEST_DEBUG("json:\n%s\ndoes not match schema. Debug output:\n %s", propsstr, NULLSTR(debugmsg)); VIR_FREE(debugmsg); VIR_FREE(propsstr); ret = -1; } virBufferFreeAndReset(&debug); } return ret; } static int testQemuDiskXMLToPropsValidateFile(const void *opaque) { struct testQemuDiskXMLToJSONData *data = (void *) opaque; virBuffer buf = VIR_BUFFER_INITIALIZER; char *jsonpath = NULL; char *actual = NULL; int ret = -1; size_t i; if (data->fail) return EXIT_AM_SKIP; jsonpath = g_strdup_printf("%s%s.json", testQemuDiskXMLToJSONPath, data->name); for (i = 0; i < data->nprops; i++) { char *jsonstr; if (!(jsonstr = virJSONValueToString(data->props[i], true))) goto cleanup; virBufferAdd(&buf, jsonstr, -1); VIR_FREE(jsonstr); } actual = virBufferContentAndReset(&buf); ret = virTestCompareToFile(actual, jsonpath); cleanup: VIR_FREE(jsonpath); VIR_FREE(actual); return ret; } struct testQemuImageCreateData { const char *name; const char *backingname; virHashTablePtr schema; virJSONValuePtr schemaroot; virQEMUDriverPtr driver; virQEMUCapsPtr qemuCaps; }; static const char *testQemuImageCreatePath = abs_srcdir "/qemublocktestdata/imagecreate/"; static virStorageSourcePtr testQemuImageCreateLoadDiskXML(const char *name, virDomainXMLOptionPtr xmlopt) { virDomainSnapshotDiskDefPtr diskdef = NULL; g_autoptr(xmlDoc) doc = NULL; g_autoptr(xmlXPathContext) ctxt = NULL; xmlNodePtr node; g_autofree char *xmlpath = NULL; virStorageSourcePtr ret = NULL; xmlpath = g_strdup_printf("%s%s.xml", testQemuImageCreatePath, name); if (!(doc = virXMLParseFileCtxt(xmlpath, &ctxt))) return NULL; if (!(node = virXPathNode("//disk", ctxt))) { VIR_TEST_VERBOSE("failed to find element\n"); return NULL; } if (VIR_ALLOC(diskdef) < 0) return NULL; if (virDomainSnapshotDiskDefParseXML(node, ctxt, diskdef, VIR_DOMAIN_DEF_PARSE_STATUS, xmlopt) == 0) ret = g_steal_pointer(&diskdef->src); virDomainSnapshotDiskDefFree(diskdef); return ret; } static int testQemuImageCreate(const void *opaque) { struct testQemuImageCreateData *data = (void *) opaque; g_autoptr(virJSONValue) protocolprops = NULL; g_autoptr(virJSONValue) formatprops = NULL; g_autoptr(virStorageSource) src = NULL; g_auto(virBuffer) debug = VIR_BUFFER_INITIALIZER; g_auto(virBuffer) actualbuf = VIR_BUFFER_INITIALIZER; g_autofree char *jsonprotocol = NULL; g_autofree char *jsonformat = NULL; g_autofree char *actual = NULL; g_autofree char *jsonpath = NULL; if (!(src = testQemuImageCreateLoadDiskXML(data->name, data->driver->xmlopt))) return -1; if (data->backingname && !(src->backingStore = testQemuImageCreateLoadDiskXML(data->backingname, data->driver->xmlopt))) return -1; if (testQemuDiskXMLToJSONFakeSecrets(src) < 0) return -1; /* fake some sizes */ src->capacity = UINT_MAX * 2ULL; src->physical = UINT_MAX + 1ULL; if (qemuDomainValidateStorageSource(src, data->qemuCaps) < 0) return -1; if (qemuBlockStorageSourceCreateGetStorageProps(src, &protocolprops) < 0) return -1; if (qemuBlockStorageSourceCreateGetFormatProps(src, src->backingStore, &formatprops) < 0) return -1; if (formatprops) { if (!(jsonformat = virJSONValueToString(formatprops, true))) return -1; if (testQEMUSchemaValidate(formatprops, data->schemaroot, data->schema, &debug) < 0) { g_autofree char *debugmsg = virBufferContentAndReset(&debug); VIR_TEST_VERBOSE("blockdev-create format json does not conform to QAPI schema"); VIR_TEST_DEBUG("json:\n%s\ndoes not match schema. Debug output:\n %s", jsonformat, NULLSTR(debugmsg)); return -1; } virBufferFreeAndReset(&debug); } if (protocolprops) { if (!(jsonprotocol = virJSONValueToString(protocolprops, true))) return -1; if (testQEMUSchemaValidate(protocolprops, data->schemaroot, data->schema, &debug) < 0) { g_autofree char *debugmsg = virBufferContentAndReset(&debug); VIR_TEST_VERBOSE("blockdev-create protocol json does not conform to QAPI schema"); VIR_TEST_DEBUG("json:\n%s\ndoes not match schema. Debug output:\n %s", jsonprotocol, NULLSTR(debugmsg)); return -1; } virBufferFreeAndReset(&debug); } virBufferStrcat(&actualbuf, "protocol:\n", NULLSTR(jsonprotocol), "\nformat:\n", NULLSTR(jsonformat), NULL); virBufferTrim(&actualbuf, "\n", -1); virBufferAddLit(&actualbuf, "\n"); jsonpath = g_strdup_printf("%s%s.json", testQemuImageCreatePath, data->name); if (!(actual = virBufferContentAndReset(&actualbuf))) return -1; return virTestCompareToFile(actual, jsonpath); } static int testQemuDiskXMLToPropsValidateFileSrcOnly(const void *opaque) { struct testQemuDiskXMLToJSONData *data = (void *) opaque; g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; g_autofree char *jsonpath = NULL; g_autofree char *actual = NULL; size_t i; if (data->fail) return EXIT_AM_SKIP; jsonpath = g_strdup_printf("%s%s-srconly.json", testQemuDiskXMLToJSONPath, data->name); for (i = 0; i < data->npropssrc; i++) { g_autofree char *jsonstr = NULL; if (!(jsonstr = virJSONValueToString(data->propssrc[i], true))) return -1; virBufferAdd(&buf, jsonstr, -1); } actual = virBufferContentAndReset(&buf); return virTestCompareToFile(actual, jsonpath); } static int mymain(void) { int ret = 0; virQEMUDriver driver; struct testBackingXMLjsonXMLdata xmljsonxmldata; struct testQemuDiskXMLToJSONData diskxmljsondata; struct testQemuImageCreateData imagecreatedata; char *capslatest_x86_64 = NULL; virQEMUCapsPtr caps_x86_64 = NULL; if (qemuTestDriverInit(&driver) < 0) return EXIT_FAILURE; diskxmljsondata.driver = &driver; imagecreatedata.driver = &driver; if (!(capslatest_x86_64 = testQemuGetLatestCapsForArch("x86_64", "xml"))) return EXIT_FAILURE; VIR_TEST_VERBOSE("\nlatest caps x86_64: %s", capslatest_x86_64); if (!(caps_x86_64 = qemuTestParseCapabilitiesArch(virArchFromString("x86_64"), capslatest_x86_64))) return EXIT_FAILURE; diskxmljsondata.qemuCaps = caps_x86_64; imagecreatedata.qemuCaps = caps_x86_64; virTestCounterReset("qemu storage source xml->json->xml "); # define TEST_JSON_FORMAT(tpe, xmlstr) \ do { \ xmljsonxmldata.type = tpe; \ xmljsonxmldata.xml = xmlstr; \ if (virTestRun(virTestCounterNext(), testBackingXMLjsonXML, \ &xmljsonxmldata) < 0) \ ret = -1; \ } while (0) # define TEST_JSON_FORMAT_NET(xmlstr) \ TEST_JSON_FORMAT(VIR_STORAGE_TYPE_NETWORK, xmlstr) TEST_JSON_FORMAT(VIR_STORAGE_TYPE_FILE, "\n"); /* type VIR_STORAGE_TYPE_BLOCK is not tested since it parses back to 'file' */ /* type VIR_STORAGE_TYPE_DIR it is a 'format' driver in qemu */ TEST_JSON_FORMAT_NET("\n" " \n" "\n"); TEST_JSON_FORMAT_NET("\n" " \n" "\n"); TEST_JSON_FORMAT_NET("\n" " \n" "\n"); TEST_JSON_FORMAT_NET("\n" " \n" "\n"); TEST_JSON_FORMAT_NET("\n" " \n" " \n" " \n" "\n"); TEST_JSON_FORMAT_NET("\n" " \n" "\n"); TEST_JSON_FORMAT_NET("\n" " \n" "\n"); TEST_JSON_FORMAT_NET("\n" " \n" "\n"); TEST_JSON_FORMAT_NET("\n" " \n" "\n"); TEST_JSON_FORMAT_NET("\n" " \n" " \n" " \n" " \n" "\n"); TEST_JSON_FORMAT_NET("\n" " \n" "\n"); TEST_JSON_FORMAT_NET("\n" " \n" "\n"); TEST_JSON_FORMAT_NET("\n" " \n" "\n"); TEST_JSON_FORMAT_NET("\n" " \n" "\n"); # define TEST_DISK_TO_JSON_FULL(nme, fl) \ do { \ diskxmljsondata.name = nme; \ diskxmljsondata.props = NULL; \ diskxmljsondata.nprops = 0; \ diskxmljsondata.propssrc = NULL; \ diskxmljsondata.npropssrc = 0; \ diskxmljsondata.fail = fl; \ if (virTestRun("disk xml to props " nme, testQemuDiskXMLToProps, \ &diskxmljsondata) < 0) \ ret = -1; \ if (virTestRun("disk xml to props validate schema " nme, \ testQemuDiskXMLToPropsValidateSchema, &diskxmljsondata) < 0) \ ret = -1; \ if (virTestRun("disk xml to props validate file " nme, \ testQemuDiskXMLToPropsValidateFile, &diskxmljsondata) < 0) \ ret = -1; \ if (virTestRun("disk xml to props source only validate file " nme, \ testQemuDiskXMLToPropsValidateFileSrcOnly, &diskxmljsondata) < 0) \ ret = -1; \ testQemuDiskXMLToPropsClear(&diskxmljsondata); \ } while (0) # define TEST_DISK_TO_JSON(nme) TEST_DISK_TO_JSON_FULL(nme, false) if (!(diskxmljsondata.schema = testQEMUSchemaLoad())) { ret = -1; goto cleanup; } if (virQEMUQAPISchemaPathGet("blockdev-add/arg-type", diskxmljsondata.schema, &diskxmljsondata.schemaroot) < 0 || !diskxmljsondata.schemaroot) { VIR_TEST_VERBOSE("failed to find schema entry for blockdev-add"); ret = -1; goto cleanup; } TEST_DISK_TO_JSON_FULL("nodename-long-format", true); TEST_DISK_TO_JSON_FULL("nodename-long-protocol", true); TEST_DISK_TO_JSON("file-raw-noopts"); TEST_DISK_TO_JSON("file-bochs-noopts"); TEST_DISK_TO_JSON("file-cloop-noopts"); TEST_DISK_TO_JSON("file-dmg-noopts"); TEST_DISK_TO_JSON("file-ploop-noopts"); TEST_DISK_TO_JSON("file-vdi-noopts"); TEST_DISK_TO_JSON("file-vhd-noopts"); TEST_DISK_TO_JSON("file-vpc-noopts"); TEST_DISK_TO_JSON("file-backing_basic-noopts"); TEST_DISK_TO_JSON("dir-fat-readonly"); TEST_DISK_TO_JSON("dir-fat-floppy"); TEST_DISK_TO_JSON("file-raw-aio_native"); TEST_DISK_TO_JSON("file-backing_basic-aio_threads"); TEST_DISK_TO_JSON("file-raw-luks"); TEST_DISK_TO_JSON("file-qcow2-backing-chain-noopts"); TEST_DISK_TO_JSON("file-qcow2-backing-chain-unterminated"); TEST_DISK_TO_JSON("file-qcow2-backing-chain-encryption"); TEST_DISK_TO_JSON("network-qcow2-backing-chain-encryption_auth"); TEST_DISK_TO_JSON("file-backing_basic-unmap"); TEST_DISK_TO_JSON("file-backing_basic-unmap-detect"); TEST_DISK_TO_JSON("file-backing_basic-unmap-ignore"); TEST_DISK_TO_JSON("file-backing_basic-detect"); TEST_DISK_TO_JSON("file-backing_basic-cache-none"); TEST_DISK_TO_JSON("file-backing_basic-cache-writethrough"); TEST_DISK_TO_JSON("file-backing_basic-cache-writeback"); TEST_DISK_TO_JSON("file-backing_basic-cache-directsync"); TEST_DISK_TO_JSON("file-backing_basic-cache-unsafe"); TEST_DISK_TO_JSON("network-qcow2-backing-chain-cache-unsafe"); TEST_DISK_TO_JSON("dir-fat-cache"); TEST_DISK_TO_JSON("network-nbd-tls"); TEST_DISK_TO_JSON("block-raw-noopts"); TEST_DISK_TO_JSON("block-raw-reservations"); # define TEST_IMAGE_CREATE(testname, testbacking) \ do { \ imagecreatedata.name = testname; \ imagecreatedata.backingname = testbacking; \ if (virTestRun("image create xml to props " testname, testQemuImageCreate, \ &imagecreatedata) < 0) \ ret = -1; \ } while (0) imagecreatedata.schema = diskxmljsondata.schema; if (virQEMUQAPISchemaPathGet("blockdev-create/arg-type/options", imagecreatedata.schema, &imagecreatedata.schemaroot) < 0 || !imagecreatedata.schemaroot) { VIR_TEST_VERBOSE("failed to find schema entry for blockdev-create\n"); ret = -1; goto cleanup; } TEST_IMAGE_CREATE("raw", NULL); TEST_IMAGE_CREATE("raw-nbd", NULL); TEST_IMAGE_CREATE("luks-noopts", NULL); TEST_IMAGE_CREATE("luks-encopts", NULL); TEST_IMAGE_CREATE("qcow2", NULL); TEST_IMAGE_CREATE("qcow2-luks-noopts", NULL); TEST_IMAGE_CREATE("qcow2-luks-encopts", NULL); TEST_IMAGE_CREATE("qcow2-backing-raw", "raw"); TEST_IMAGE_CREATE("qcow2-backing-raw-nbd", "raw-nbd"); TEST_IMAGE_CREATE("qcow2-backing-luks", "luks-noopts"); TEST_IMAGE_CREATE("qcow2-luks-encopts-backing", "qcow2"); TEST_IMAGE_CREATE("network-gluster-qcow2", NULL); TEST_IMAGE_CREATE("network-rbd-qcow2", NULL); TEST_IMAGE_CREATE("network-ssh-qcow2", NULL); TEST_IMAGE_CREATE("network-sheepdog-qcow2", NULL); cleanup: virHashFree(diskxmljsondata.schema); qemuTestDriverFree(&driver); VIR_FREE(capslatest_x86_64); virObjectUnref(caps_x86_64); return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE; } #else static int mymain(void) { return EXIT_AM_SKIP; } #endif VIR_TEST_MAIN(mymain)