This is basically a copy of the existing qemuxml2argvtest, with a couple of obvious changes. We're not using most of the features at the moment, but as the JSON-based APIs evolve and get closer to feature-parity with the XML-based ones we're definitely going to need more and more of them, so we might as well start with the full package. Signed-off-by: Andrea Bolognani <abologna@xxxxxxxxxx> --- tests/Makefile.am | 15 + tests/qemujson2argvdata/tiny.json | 29 + .../qemujson2argvdata/tiny.x86_64-latest.args | 33 + tests/qemujson2argvtest.c | 1001 +++++++++++++++++ 4 files changed, 1078 insertions(+) create mode 100644 tests/qemujson2argvdata/tiny.json create mode 100644 tests/qemujson2argvdata/tiny.x86_64-latest.args create mode 100644 tests/qemujson2argvtest.c diff --git a/tests/Makefile.am b/tests/Makefile.am index d3cdbff8bb..df82c7dc22 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -162,6 +162,7 @@ EXTRA_DIST = \ virstorageutildata \ virfilecachedata \ virresctrldata \ + qemujson2argvdata \ $(NULL) test_helpers = commandhelper ssh @@ -283,6 +284,7 @@ test_programs += qemuxml2argvtest qemuxml2xmltest \ qemumigparamstest \ qemusecuritytest \ qemufirmwaretest \ + qemujson2argvtest \ $(NULL) test_helpers += qemucapsprobe test_libraries += libqemumonitortestutils.la \ @@ -692,6 +694,18 @@ qemufirmwaretest_SOURCES = \ $(NULL) qemufirmwaretest_LDADD = $(qemu_LDADDS) $(LDADDS) +qemujson2argvtest_SOURCES = \ + virfilewrapper.c virfilewrapper.h \ + testutilsqemu.c testutilsqemu.h \ + testutils.c testutils.h \ + qemujson2argvtest.c \ + $(NULL) +qemujson2argvtest_LDADD = \ + libqemutestdriver.la \ + $(LIBXML_LIBS) \ + $(LDADDS) \ + $(NULL) + else ! WITH_QEMU EXTRA_DIST += qemuxml2argvtest.c qemuxml2xmltest.c qemuargv2xmltest.c \ domainsnapshotxml2xmltest.c \ @@ -706,6 +720,7 @@ EXTRA_DIST += qemuxml2argvtest.c qemuxml2xmltest.c qemuargv2xmltest.c \ qemusecuritytest.c qemusecuritytest.h \ qemusecuritymock.c \ qemufirmwaretest.c \ + qemujson2argvtest.c \ $(QEMUMONITORTESTUTILS_SOURCES) endif ! WITH_QEMU diff --git a/tests/qemujson2argvdata/tiny.json b/tests/qemujson2argvdata/tiny.json new file mode 100644 index 0000000000..99071c5ec0 --- /dev/null +++ b/tests/qemujson2argvdata/tiny.json @@ -0,0 +1,29 @@ +{ + "domain": { + "attributes": { + "type": "qemu" + }, + "children": { + "name": { + "value": "guest" + }, + "uuid": { + "value": "4f49aff1-4f74-45c3-87a7-51a6ad052a54" + }, + "memory": { + "value": 4194304 + }, + "os": { + "children": { + "type": { + "attributes": { + "arch": "x86_64", + "machine": "pc" + }, + "value": "hvm" + } + } + } + } + } +} diff --git a/tests/qemujson2argvdata/tiny.x86_64-latest.args b/tests/qemujson2argvdata/tiny.x86_64-latest.args new file mode 100644 index 0000000000..e8e67d7727 --- /dev/null +++ b/tests/qemujson2argvdata/tiny.x86_64-latest.args @@ -0,0 +1,33 @@ +LC_ALL=C \ +PATH=/bin \ +HOME=/tmp/lib/domain--1-guest \ +USER=test \ +LOGNAME=test \ +XDG_DATA_HOME=/tmp/lib/domain--1-guest/.local/share \ +XDG_CACHE_HOME=/tmp/lib/domain--1-guest/.cache \ +XDG_CONFIG_HOME=/tmp/lib/domain--1-guest/.config \ +QEMU_AUDIO_DRV=none \ +/usr/bin/qemu-system-x86_64 \ +-name guest=guest,debug-threads=on \ +-S \ +-object secret,id=masterKey0,format=raw,\ +file=/tmp/lib/domain--1-guest/master-key.aes \ +-machine pc,accel=tcg,usb=off,dump-guest-core=off \ +-m 4096 \ +-realtime mlock=off \ +-smp 1,sockets=1,cores=1,threads=1 \ +-uuid 4f49aff1-4f74-45c3-87a7-51a6ad052a54 \ +-display none \ +-no-user-config \ +-nodefaults \ +-chardev socket,id=charmonitor,fd=1729,server,nowait \ +-mon chardev=charmonitor,id=monitor,mode=control \ +-rtc base=utc \ +-no-reboot \ +-no-acpi \ +-boot strict=on \ +-device piix3-usb-uhci,id=usb,bus=pci.0,addr=0x1.0x2 \ +-device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x2 \ +-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,\ +resourcecontrol=deny \ +-msg timestamp=on diff --git a/tests/qemujson2argvtest.c b/tests/qemujson2argvtest.c new file mode 100644 index 0000000000..e754813646 --- /dev/null +++ b/tests/qemujson2argvtest.c @@ -0,0 +1,1001 @@ +#include <config.h> + +#include <unistd.h> + +#include <sys/types.h> +#include <fcntl.h> + +#include "testutils.h" + +#ifdef WITH_QEMU + +# include "internal.h" +# include "viralloc.h" +# include "qemu/qemu_alias.h" +# include "qemu/qemu_capabilities.h" +# include "qemu/qemu_command.h" +# include "qemu/qemu_domain.h" +# include "qemu/qemu_migration.h" +# include "qemu/qemu_process.h" +# include "datatypes.h" +# include "conf/storage_conf.h" +# include "cpu/cpu_map.h" +# include "virstring.h" +# include "storage/storage_driver.h" +# include "virmock.h" +# include "virfilewrapper.h" +# include "configmake.h" + +# define LIBVIRT_QEMU_CAPSPRIV_H_ALLOW +# include "qemu/qemu_capspriv.h" + +# include "testutilsqemu.h" + +# define VIR_FROM_THIS VIR_FROM_QEMU + +static virQEMUDriver driver; + +static unsigned char * +fakeSecretGetValue(virSecretPtr obj ATTRIBUTE_UNUSED, + size_t *value_size, + unsigned int fakeflags ATTRIBUTE_UNUSED, + unsigned int internalFlags ATTRIBUTE_UNUSED) +{ + char *secret; + if (VIR_STRDUP(secret, "AQCVn5hO6HzFAhAAq0NCv8jtJcIcE+HOBlMQ1A") < 0) + return NULL; + *value_size = strlen(secret); + return (unsigned char *) secret; +} + +static virSecretPtr +fakeSecretLookupByUsage(virConnectPtr conn, + int usageType, + const char *usageID) +{ + unsigned char uuid[VIR_UUID_BUFLEN]; + if (usageType == VIR_SECRET_USAGE_TYPE_VOLUME) { + if (!STRPREFIX(usageID, "/storage/guest_disks/")) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "test provided invalid volume storage prefix '%s'", + usageID); + return NULL; + } + } else if (STRNEQ(usageID, "mycluster_myname")) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "test provided incorrect usage '%s'", usageID); + return NULL; + } + + if (virUUIDGenerate(uuid) < 0) + return NULL; + + return virGetSecret(conn, uuid, usageType, usageID); +} + +static virSecretPtr +fakeSecretLookupByUUID(virConnectPtr conn, + const unsigned char *uuid) +{ + /* NB: This mocked value could be "tls" or "volume" depending on + * which test is being run, we'll leave at NONE (or 0) */ + return virGetSecret(conn, uuid, VIR_SECRET_USAGE_TYPE_NONE, ""); +} + +static virSecretDriver fakeSecretDriver = { + .connectNumOfSecrets = NULL, + .connectListSecrets = NULL, + .secretLookupByUUID = fakeSecretLookupByUUID, + .secretLookupByUsage = fakeSecretLookupByUsage, + .secretDefineXML = NULL, + .secretGetXMLDesc = NULL, + .secretSetValue = NULL, + .secretGetValue = fakeSecretGetValue, + .secretUndefine = NULL, +}; + + +# define STORAGE_POOL_XML_PATH "storagepoolxml2xmlout/" +static const unsigned char fakeUUID[VIR_UUID_BUFLEN] = "fakeuuid"; + +static virStoragePoolPtr +fakeStoragePoolLookupByName(virConnectPtr conn, + const char *name) +{ + char *xmlpath = NULL; + virStoragePoolPtr ret = NULL; + + if (STRNEQ(name, "inactive")) { + if (virAsprintf(&xmlpath, "%s/%s%s.xml", + abs_srcdir, + STORAGE_POOL_XML_PATH, + name) < 0) + return NULL; + + if (!virFileExists(xmlpath)) { + virReportError(VIR_ERR_NO_STORAGE_POOL, + "File '%s' not found", xmlpath); + goto cleanup; + } + } + + ret = virGetStoragePool(conn, name, fakeUUID, NULL, NULL); + + cleanup: + VIR_FREE(xmlpath); + return ret; +} + + +static virStorageVolPtr +fakeStorageVolLookupByName(virStoragePoolPtr pool, + const char *name) +{ + char **volinfo = NULL; + virStorageVolPtr ret = NULL; + + if (STREQ(pool->name, "inactive")) { + virReportError(VIR_ERR_OPERATION_INVALID, + "storage pool '%s' is not active", pool->name); + return NULL; + } + + if (STREQ(name, "nonexistent")) { + virReportError(VIR_ERR_NO_STORAGE_VOL, + "no storage vol with matching name '%s'", name); + return NULL; + } + + if (!strchr(name, '+')) + goto fallback; + + if (!(volinfo = virStringSplit(name, "+", 2))) + return NULL; + + if (!volinfo[1]) + goto fallback; + + ret = virGetStorageVol(pool->conn, pool->name, volinfo[1], volinfo[0], + NULL, NULL); + + cleanup: + virStringListFree(volinfo); + return ret; + + fallback: + ret = virGetStorageVol(pool->conn, pool->name, name, "block", NULL, NULL); + goto cleanup; +} + +static int +fakeStorageVolGetInfo(virStorageVolPtr vol, + virStorageVolInfoPtr info) +{ + memset(info, 0, sizeof(*info)); + + info->type = virStorageVolTypeFromString(vol->key); + + if (info->type < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "Invalid volume type '%s'", vol->key); + return -1; + } + + return 0; +} + + +static char * +fakeStorageVolGetPath(virStorageVolPtr vol) +{ + char *ret = NULL; + + ignore_value(virAsprintf(&ret, "/some/%s/device/%s", vol->key, vol->name)); + + return ret; +} + + +static char * +fakeStoragePoolGetXMLDesc(virStoragePoolPtr pool, + unsigned int flags_unused ATTRIBUTE_UNUSED) +{ + char *xmlpath = NULL; + char *xmlbuf = NULL; + + if (STREQ(pool->name, "inactive")) { + virReportError(VIR_ERR_NO_STORAGE_POOL, NULL); + return NULL; + } + + if (virAsprintf(&xmlpath, "%s/%s%s.xml", + abs_srcdir, + STORAGE_POOL_XML_PATH, + pool->name) < 0) + return NULL; + + if (virTestLoadFile(xmlpath, &xmlbuf) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "failed to load XML file '%s'", + xmlpath); + goto cleanup; + } + + cleanup: + VIR_FREE(xmlpath); + + return xmlbuf; +} + +static int +fakeStoragePoolIsActive(virStoragePoolPtr pool) +{ + if (STREQ(pool->name, "inactive")) + return 0; + + return 1; +} + +/* Test storage pool implementation + * + * These functions aid testing of storage pool related stuff when creating a + * qemu command line. + * + * There are a few "magic" values to pass to these functions: + * + * 1) "inactive" as a pool name to create an inactive pool. All other names are + * interpreted as file names in storagepoolxml2xmlout/ and are used as the + * definition for the pool. If the file doesn't exist the pool doesn't exist. + * + * 2) "nonexistent" returns an error while looking up a volume. Otherwise + * pattern VOLUME_TYPE+VOLUME_PATH can be used to simulate a volume in a pool. + * This creates a fake path for this volume. If the '+' sign is omitted, block + * type is assumed. + */ +static virStorageDriver fakeStorageDriver = { + .storagePoolLookupByName = fakeStoragePoolLookupByName, + .storageVolLookupByName = fakeStorageVolLookupByName, + .storagePoolGetXMLDesc = fakeStoragePoolGetXMLDesc, + .storageVolGetPath = fakeStorageVolGetPath, + .storageVolGetInfo = fakeStorageVolGetInfo, + .storagePoolIsActive = fakeStoragePoolIsActive, +}; + + +/* virNetDevOpenvswitchGetVhostuserIfname mocks a portdev name - handle that */ +static virNWFilterBindingPtr +fakeNWFilterBindingLookupByPortDev(virConnectPtr conn, + const char *portdev) +{ + if (STREQ(portdev, "vhost-user0")) + return virGetNWFilterBinding(conn, "fake_vnet0", "fakeFilterName"); + + virReportError(VIR_ERR_NO_NWFILTER_BINDING, + "no nwfilter binding for port dev '%s'", portdev); + return NULL; +} + + +static int +fakeNWFilterBindingDelete(virNWFilterBindingPtr binding ATTRIBUTE_UNUSED) +{ + return 0; +} + + +static virNWFilterDriver fakeNWFilterDriver = { + .nwfilterBindingLookupByPortDev = fakeNWFilterBindingLookupByPortDev, + .nwfilterBindingDelete = fakeNWFilterBindingDelete, +}; + +typedef enum { + FLAG_EXPECT_FAILURE = 1 << 0, + FLAG_EXPECT_PARSE_ERROR = 1 << 1, + FLAG_FIPS = 1 << 2, + FLAG_REAL_CAPS = 1 << 3, + FLAG_SKIP_LEGACY_CPUS = 1 << 4, +} virQemuJSON2ArgvTestFlags; + +struct testInfo { + const char *name; + const char *suffix; + virQEMUCapsPtr qemuCaps; + const char *migrateFrom; + int migrateFd; + unsigned int flags; + unsigned int parseFlags; +}; + + +static int +testAddCPUModels(virQEMUCapsPtr caps, bool skipLegacy) +{ + virArch arch = virQEMUCapsGetArch(caps); + const char *x86Models[] = { + "Opteron_G3", "Opteron_G2", "Opteron_G1", + "Nehalem", "Penryn", "Conroe", + "Haswell-noTSX", "Haswell", + }; + const char *x86LegacyModels[] = { + "n270", "athlon", "pentium3", "pentium2", "pentium", + "486", "coreduo", "kvm32", "qemu32", "kvm64", + "core2duo", "phenom", "qemu64", + }; + const char *armModels[] = { + "cortex-a9", "cortex-a8", "cortex-a57", "cortex-a53", + }; + const char *ppc64Models[] = { + "POWER8", "POWER7", + }; + const char *s390xModels[] = { + "z990", "zEC12", "z13", + }; + + if (ARCH_IS_X86(arch)) { + if (virQEMUCapsAddCPUDefinitions(caps, VIR_DOMAIN_VIRT_KVM, x86Models, + ARRAY_CARDINALITY(x86Models), + VIR_DOMCAPS_CPU_USABLE_UNKNOWN) < 0 || + virQEMUCapsAddCPUDefinitions(caps, VIR_DOMAIN_VIRT_QEMU, x86Models, + ARRAY_CARDINALITY(x86Models), + VIR_DOMCAPS_CPU_USABLE_UNKNOWN) < 0) + return -1; + + if (!skipLegacy) { + if (virQEMUCapsAddCPUDefinitions(caps, VIR_DOMAIN_VIRT_KVM, + x86LegacyModels, + ARRAY_CARDINALITY(x86LegacyModels), + VIR_DOMCAPS_CPU_USABLE_UNKNOWN) < 0 || + virQEMUCapsAddCPUDefinitions(caps, VIR_DOMAIN_VIRT_QEMU, + x86LegacyModels, + ARRAY_CARDINALITY(x86LegacyModels), + VIR_DOMCAPS_CPU_USABLE_UNKNOWN) < 0) + return -1; + } + } else if (ARCH_IS_ARM(arch)) { + if (virQEMUCapsAddCPUDefinitions(caps, VIR_DOMAIN_VIRT_KVM, armModels, + ARRAY_CARDINALITY(armModels), + VIR_DOMCAPS_CPU_USABLE_UNKNOWN) < 0 || + virQEMUCapsAddCPUDefinitions(caps, VIR_DOMAIN_VIRT_QEMU, armModels, + ARRAY_CARDINALITY(armModels), + VIR_DOMCAPS_CPU_USABLE_UNKNOWN) < 0) + return -1; + } else if (ARCH_IS_PPC64(arch)) { + if (virQEMUCapsAddCPUDefinitions(caps, VIR_DOMAIN_VIRT_KVM, ppc64Models, + ARRAY_CARDINALITY(ppc64Models), + VIR_DOMCAPS_CPU_USABLE_UNKNOWN) < 0 || + virQEMUCapsAddCPUDefinitions(caps, VIR_DOMAIN_VIRT_QEMU, ppc64Models, + ARRAY_CARDINALITY(ppc64Models), + VIR_DOMCAPS_CPU_USABLE_UNKNOWN) < 0) + return -1; + } else if (ARCH_IS_S390(arch)) { + if (virQEMUCapsAddCPUDefinitions(caps, VIR_DOMAIN_VIRT_KVM, s390xModels, + ARRAY_CARDINALITY(s390xModels), + VIR_DOMCAPS_CPU_USABLE_UNKNOWN) < 0) + return -1; + } + + return 0; +} + + +static int +testUpdateQEMUCaps(const struct testInfo *info, + virDomainObjPtr vm, + virCapsPtr caps) +{ + int ret = -1; + + if (!caps) + goto cleanup; + + virQEMUCapsSetArch(info->qemuCaps, vm->def->os.arch); + + virQEMUCapsInitQMPBasicArch(info->qemuCaps); + + if (testAddCPUModels(info->qemuCaps, + !!(info->flags & FLAG_SKIP_LEGACY_CPUS)) < 0) + goto cleanup; + + virQEMUCapsInitHostCPUModel(info->qemuCaps, caps->host.arch, + VIR_DOMAIN_VIRT_KVM); + virQEMUCapsInitHostCPUModel(info->qemuCaps, caps->host.arch, + VIR_DOMAIN_VIRT_QEMU); + + ret = 0; + + cleanup: + return ret; +} + + +static int +testCheckExclusiveFlags(int flags) +{ + virCheckFlags(FLAG_EXPECT_FAILURE | + FLAG_EXPECT_PARSE_ERROR | + FLAG_FIPS | + FLAG_REAL_CAPS | + FLAG_SKIP_LEGACY_CPUS | + 0, -1); + + VIR_EXCLUSIVE_FLAGS_RET(FLAG_REAL_CAPS, FLAG_SKIP_LEGACY_CPUS, -1); + return 0; +} + + +# define JSON_BUFSIZE (10*1024*1024) + + +static int +testCompareJSONToArgv(const void *data) +{ + struct testInfo *info = (void *) data; + char *json = NULL; + char *args = NULL; + char *migrateURI = NULL; + char *actualargv = NULL; + const char *suffix = info->suffix; + unsigned int flags = info->flags; + unsigned int parseFlags = info->parseFlags; + int ret = -1; + virDomainObjPtr vm = NULL; + virDomainChrSourceDef monitor_chr; + virConnectPtr conn; + char *log = NULL; + virCommandPtr cmd = NULL; + size_t i; + qemuDomainObjPrivatePtr priv = NULL; + VIR_AUTOFREE(char *) buf = NULL; + + memset(&monitor_chr, 0, sizeof(monitor_chr)); + + if (!(conn = virGetConnect())) + goto cleanup; + + if (!suffix) + suffix = ""; + + conn->secretDriver = &fakeSecretDriver; + conn->storageDriver = &fakeStorageDriver; + conn->nwfilterDriver = &fakeNWFilterDriver; + + virSetConnectInterface(conn); + virSetConnectNetwork(conn); + virSetConnectNWFilter(conn); + virSetConnectNodeDev(conn); + virSetConnectSecret(conn); + virSetConnectStorage(conn); + + if (virQEMUCapsGet(info->qemuCaps, QEMU_CAPS_ENABLE_FIPS)) + flags |= FLAG_FIPS; + + if (testCheckExclusiveFlags(info->flags) < 0) + goto cleanup; + + if (qemuTestCapsCacheInsert(driver.qemuCapsCache, info->qemuCaps) < 0) + goto cleanup; + + if (virAsprintf(&json, "%s/qemujson2argvdata/%s.json", + abs_srcdir, info->name) < 0 || + virAsprintf(&args, "%s/qemujson2argvdata/%s%s.args", + abs_srcdir, info->name, suffix) < 0) + goto cleanup; + + if (info->migrateFrom && + !(migrateURI = qemuMigrationDstGetURI(info->migrateFrom, + info->migrateFd))) + goto cleanup; + + if (!(vm = virDomainObjNew(driver.xmlopt))) + goto cleanup; + + if (virFileReadAll(json, JSON_BUFSIZE, &buf) < 0) + goto cleanup; + + parseFlags |= VIR_DOMAIN_DEF_PARSE_INACTIVE; + if (!(vm->def = virDomainDefParseJSONString(buf, driver.caps, driver.xmlopt, + NULL, parseFlags))) { + if (flags & FLAG_EXPECT_PARSE_ERROR) + goto ok; + goto cleanup; + } + if (flags & FLAG_EXPECT_PARSE_ERROR) { + VIR_TEST_DEBUG("passed instead of expected parse error"); + goto cleanup; + } + priv = vm->privateData; + + if (virBitmapParse("0-3", &priv->autoNodeset, 4) < 0) + goto cleanup; + + if (!virDomainDefCheckABIStability(vm->def, vm->def, driver.xmlopt)) { + VIR_TEST_DEBUG("ABI stability check failed on %s", json); + goto cleanup; + } + + vm->def->id = -1; + + if (qemuProcessPrepareMonitorChr(&monitor_chr, priv->libDir) < 0) + goto cleanup; + + if (!(info->flags & FLAG_REAL_CAPS) && + testUpdateQEMUCaps(info, vm, driver.caps) < 0) + goto cleanup; + + log = virTestLogContentAndReset(); + VIR_FREE(log); + virResetLastError(); + + for (i = 0; i < vm->def->nhostdevs; i++) { + virDomainHostdevDefPtr hostdev = vm->def->hostdevs[i]; + + if (hostdev->mode == VIR_DOMAIN_HOSTDEV_MODE_SUBSYS && + hostdev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI && + hostdev->source.subsys.u.pci.backend == VIR_DOMAIN_HOSTDEV_PCI_BACKEND_DEFAULT) { + hostdev->source.subsys.u.pci.backend = VIR_DOMAIN_HOSTDEV_PCI_BACKEND_KVM; + } + } + + if (vm->def->vsock) { + virDomainVsockDefPtr vsock = vm->def->vsock; + qemuDomainVsockPrivatePtr vsockPriv = + (qemuDomainVsockPrivatePtr)vsock->privateData; + + if (vsock->auto_cid == VIR_TRISTATE_BOOL_YES) + vsock->guest_cid = 42; + + vsockPriv->vhostfd = 6789; + } + + if (vm->def->tpm) { + switch (vm->def->tpm->type) { + case VIR_DOMAIN_TPM_TYPE_EMULATOR: + VIR_FREE(vm->def->tpm->data.emulator.source.data.file.path); + if (VIR_STRDUP(vm->def->tpm->data.emulator.source.data.file.path, + "/dev/test") < 0) + goto cleanup; + vm->def->tpm->data.emulator.source.type = VIR_DOMAIN_CHR_TYPE_FILE; + break; + case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH: + case VIR_DOMAIN_TPM_TYPE_LAST: + break; + } + } + + if (!(cmd = qemuProcessCreatePretendCmd(&driver, vm, migrateURI, + (flags & FLAG_FIPS), false, + VIR_QEMU_PROCESS_START_COLD))) { + if (flags & FLAG_EXPECT_FAILURE) + goto ok; + goto cleanup; + } + if (flags & FLAG_EXPECT_FAILURE) { + VIR_TEST_DEBUG("passed instead of expected failure"); + goto cleanup; + } + + if (!(actualargv = virCommandToString(cmd, false))) + goto cleanup; + + if (virTestCompareToFile(actualargv, args) < 0) + goto cleanup; + + ret = 0; + + ok: + if (ret == 0 && flags & FLAG_EXPECT_FAILURE) { + ret = -1; + VIR_TEST_DEBUG("Error expected but there wasn't any.\n"); + goto cleanup; + } + if (!virTestOOMActive()) { + if (flags & FLAG_EXPECT_FAILURE) { + if ((log = virTestLogContentAndReset())) + VIR_TEST_DEBUG("Got expected error: \n%s", log); + } + virResetLastError(); + ret = 0; + } + + cleanup: + VIR_FREE(log); + VIR_FREE(actualargv); + virDomainChrSourceDefClear(&monitor_chr); + virCommandFree(cmd); + virObjectUnref(vm); + virSetConnectSecret(NULL); + virSetConnectStorage(NULL); + virObjectUnref(conn); + VIR_FREE(migrateURI); + VIR_FREE(json); + VIR_FREE(args); + return ret; +} + +# define TEST_CAPS_PATH abs_srcdir "/qemucapabilitiesdata" + +typedef enum { + ARG_QEMU_CAPS, + ARG_GIC, + ARG_MIGRATE_FROM, + ARG_MIGRATE_FD, + ARG_FLAGS, + ARG_PARSEFLAGS, + ARG_CAPS_ARCH, + ARG_CAPS_VER, + ARG_END, +} testInfoArgName; + +static int +testInfoSetArgs(struct testInfo *info, + virHashTablePtr capslatest, ...) +{ + va_list argptr; + testInfoArgName argname; + virQEMUCapsPtr qemuCaps = NULL; + int gic = GIC_NONE; + char *capsarch = NULL; + char *capsver = NULL; + VIR_AUTOFREE(char *) capsfile = NULL; + int flag; + int ret = -1; + + va_start(argptr, capslatest); + argname = va_arg(argptr, testInfoArgName); + while (argname != ARG_END) { + switch (argname) { + case ARG_QEMU_CAPS: + if (qemuCaps || !(qemuCaps = virQEMUCapsNew())) + goto cleanup; + + while ((flag = va_arg(argptr, int)) < QEMU_CAPS_LAST) + virQEMUCapsSet(qemuCaps, flag); + + /* Some tests are run with NONE capabilities, which is just + * another name for QEMU_CAPS_LAST. If that is the case the + * arguments look like this : + * + * ARG_QEMU_CAPS, NONE, QEMU_CAPS_LAST, ARG_END + * + * Fetch one argument more and if it is QEMU_CAPS_LAST then + * break from the switch() to force getting next argument + * in the line. If it is not QEMU_CAPS_LAST then we've + * fetched real ARG_* and we must process it. + */ + if ((flag = va_arg(argptr, int)) != QEMU_CAPS_LAST) { + argname = flag; + continue; + } + + break; + + case ARG_GIC: + gic = va_arg(argptr, int); + break; + + case ARG_MIGRATE_FROM: + info->migrateFrom = va_arg(argptr, char *); + break; + + case ARG_MIGRATE_FD: + info->migrateFd = va_arg(argptr, int); + break; + + case ARG_FLAGS: + info->flags = va_arg(argptr, int); + break; + + case ARG_PARSEFLAGS: + info->parseFlags = va_arg(argptr, int); + break; + + case ARG_CAPS_ARCH: + capsarch = va_arg(argptr, char *); + break; + + case ARG_CAPS_VER: + capsver = va_arg(argptr, char *); + break; + + case ARG_END: + default: + fprintf(stderr, "Unexpected test info argument"); + goto cleanup; + } + + argname = va_arg(argptr, testInfoArgName); + } + + if (!!capsarch ^ !!capsver) { + fprintf(stderr, "ARG_CAPS_ARCH and ARG_CAPS_VER " + "must be specified together.\n"); + goto cleanup; + } + + if (qemuCaps && (capsarch || capsver)) { + fprintf(stderr, "ARG_QEMU_CAPS can not be combined with ARG_CAPS_ARCH " + "or ARG_CAPS_VER\n"); + goto cleanup; + } + + if (!qemuCaps && capsarch && capsver) { + bool stripmachinealiases = false; + + if (STREQ(capsver, "latest")) { + if (VIR_STRDUP(capsfile, virHashLookup(capslatest, capsarch)) < 0) + goto cleanup; + stripmachinealiases = true; + } else if (virAsprintf(&capsfile, "%s/caps_%s.%s.xml", + TEST_CAPS_PATH, capsver, capsarch) < 0) { + goto cleanup; + } + + if (!(qemuCaps = qemuTestParseCapabilitiesArch(virArchFromString(capsarch), + capsfile))) { + goto cleanup; + } + + if (stripmachinealiases) + virQEMUCapsStripMachineAliases(qemuCaps); + info->flags |= FLAG_REAL_CAPS; + } + + if (!qemuCaps) { + fprintf(stderr, "No qemuCaps generated\n"); + goto cleanup; + } + VIR_STEAL_PTR(info->qemuCaps, qemuCaps); + + if (gic != GIC_NONE && testQemuCapsSetGIC(info->qemuCaps, gic) < 0) + goto cleanup; + + ret = 0; + + cleanup: + virObjectUnref(qemuCaps); + va_end(argptr); + + return ret; +} + +static void +testInfoClear(struct testInfo *info) +{ + virObjectUnref(info->qemuCaps); +} + +# define FAKEROOTDIRTEMPLATE abs_builddir "/fakerootdir-XXXXXX" + +static int +mymain(void) +{ + int ret = 0, i; + char *fakerootdir; + const char *archs[] = { + "aarch64", + "ppc64", + "riscv64", + "s390x", + "x86_64", + }; + virHashTablePtr capslatest = NULL; + + if (VIR_STRDUP_QUIET(fakerootdir, FAKEROOTDIRTEMPLATE) < 0) { + fprintf(stderr, "Out of memory\n"); + abort(); + } + + if (!mkdtemp(fakerootdir)) { + fprintf(stderr, "Cannot create fakerootdir"); + abort(); + } + + setenv("LIBVIRT_FAKE_ROOT_DIR", fakerootdir, 1); + + /* Set the timezone because we are mocking the time() function. + * If we don't do that, then localtime() may return unpredictable + * results. In order to detect things that just work by a blind + * chance, we need to set an virtual timezone that no libvirt + * developer resides in. */ + if (setenv("TZ", "VIR00:30", 1) < 0) { + perror("setenv"); + return EXIT_FAILURE; + } + + if (qemuTestDriverInit(&driver) < 0) + return EXIT_FAILURE; + + driver.privileged = true; + + VIR_FREE(driver.config->defaultTLSx509certdir); + if (VIR_STRDUP_QUIET(driver.config->defaultTLSx509certdir, "/etc/pki/qemu") < 0) + return EXIT_FAILURE; + VIR_FREE(driver.config->vncTLSx509certdir); + if (VIR_STRDUP_QUIET(driver.config->vncTLSx509certdir, "/etc/pki/libvirt-vnc") < 0) + return EXIT_FAILURE; + VIR_FREE(driver.config->spiceTLSx509certdir); + if (VIR_STRDUP_QUIET(driver.config->spiceTLSx509certdir, "/etc/pki/libvirt-spice") < 0) + return EXIT_FAILURE; + VIR_FREE(driver.config->chardevTLSx509certdir); + if (VIR_STRDUP_QUIET(driver.config->chardevTLSx509certdir, "/etc/pki/libvirt-chardev") < 0) + return EXIT_FAILURE; + VIR_FREE(driver.config->vxhsTLSx509certdir); + if (VIR_STRDUP_QUIET(driver.config->vxhsTLSx509certdir, "/etc/pki/libvirt-vxhs/dummy,path") < 0) + return EXIT_FAILURE; + VIR_FREE(driver.config->nbdTLSx509certdir); + if (VIR_STRDUP_QUIET(driver.config->nbdTLSx509certdir, "/etc/pki/libvirt-nbd/dummy,path") < 0) + return EXIT_FAILURE; + + VIR_FREE(driver.config->hugetlbfs); + if (VIR_ALLOC_N(driver.config->hugetlbfs, 2) < 0) + return EXIT_FAILURE; + driver.config->nhugetlbfs = 2; + if (VIR_STRDUP(driver.config->hugetlbfs[0].mnt_dir, "/dev/hugepages2M") < 0 || + VIR_STRDUP(driver.config->hugetlbfs[1].mnt_dir, "/dev/hugepages1G") < 0) + return EXIT_FAILURE; + driver.config->hugetlbfs[0].size = 2048; + driver.config->hugetlbfs[0].deflt = true; + driver.config->hugetlbfs[1].size = 1048576; + driver.config->spiceTLS = 1; + if (VIR_STRDUP_QUIET(driver.config->spicePassword, "123456") < 0) + return EXIT_FAILURE; + VIR_FREE(driver.config->memoryBackingDir); + if (VIR_STRDUP_QUIET(driver.config->memoryBackingDir, "/var/lib/libvirt/qemu/ram") < 0) + return EXIT_FAILURE; + VIR_FREE(driver.config->nvramDir); + if (VIR_STRDUP(driver.config->nvramDir, "/var/lib/libvirt/qemu/nvram") < 0) + return EXIT_FAILURE; + + capslatest = virHashCreate(4, virHashValueFree); + if (!capslatest) + return EXIT_FAILURE; + + VIR_TEST_VERBOSE("\n"); + + for (i = 0; i < ARRAY_CARDINALITY(archs); ++i) { + char *cap = testQemuGetLatestCapsForArch(abs_srcdir "/qemucapabilitiesdata", + archs[i], "xml"); + + if (!cap || virHashAddEntry(capslatest, archs[i], cap) < 0) + return EXIT_FAILURE; + + VIR_TEST_VERBOSE("latest caps for %s: %s\n", archs[i], cap); + } + + VIR_TEST_VERBOSE("\n"); + + virFileWrapperAddPrefix(SYSCONFDIR "/qemu/firmware", + abs_srcdir "/qemufirmwaredata/etc/qemu/firmware"); + virFileWrapperAddPrefix(PREFIX "/share/qemu/firmware", + abs_srcdir "/qemufirmwaredata/usr/share/qemu/firmware"); + virFileWrapperAddPrefix("/home/user/.config/qemu/firmware", + abs_srcdir "/qemufirmwaredata/home/user/.config/qemu/firmware"); + +/** + * The following set of macros allows testing of JSON -> argv conversion with a + * real set of capabilities gathered from a real qemu copy. It is desired to use + * these for positive test cases as it provides combinations of flags which + * can be met in real life. + * + * The capabilities are taken from the real capabilities stored in + * tests/qemucapabilitiesdata. + * + * It is suggested to use the DO_TEST_CAPS_LATEST macro which always takes the + * most recent capability set. In cases when the new code would change behaviour + * the test cases should be forked using DO_TEST_CAPS_VER with the appropriate + * version. + */ +# define DO_TEST_INTERNAL(_name, _suffix, ...) \ + do { \ + static struct testInfo info = { \ + .name = _name, \ + .suffix = _suffix, \ + }; \ + if (testInfoSetArgs(&info, capslatest, \ + __VA_ARGS__, ARG_END) < 0) \ + return EXIT_FAILURE; \ + if (virTestRun("QEMU JSON-2-ARGV " _name _suffix, \ + testCompareJSONToArgv, &info) < 0) \ + ret = -1; \ + testInfoClear(&info); \ + } while (0) + +# define DO_TEST_CAPS_INTERNAL(name, arch, ver, ...) \ + DO_TEST_INTERNAL(name, "." arch "-" ver, \ + ARG_CAPS_ARCH, arch, \ + ARG_CAPS_VER, ver, \ + __VA_ARGS__) + +# define DO_TEST_CAPS_ARCH_VER(name, arch, ver) \ + DO_TEST_CAPS_INTERNAL(name, arch, ver, ARG_END) + +# define DO_TEST_CAPS_VER(name, ver) \ + DO_TEST_CAPS_ARCH_VER(name, "x86_64", ver) + +# define DO_TEST_CAPS_ARCH_LATEST_FULL(name, arch, ...) \ + DO_TEST_CAPS_INTERNAL(name, arch, "latest", __VA_ARGS__) + +# define DO_TEST_CAPS_ARCH_LATEST(name, arch) \ + DO_TEST_CAPS_ARCH_LATEST_FULL(name, arch, ARG_END) + +# define DO_TEST_CAPS_LATEST(name) \ + DO_TEST_CAPS_ARCH_LATEST(name, "x86_64") + +# define DO_TEST_CAPS_LATEST_FAILURE(name) \ + DO_TEST_CAPS_ARCH_LATEST_FULL(name, "x86_64", \ + ARG_FLAGS, FLAG_EXPECT_FAILURE) + +# define DO_TEST_CAPS_LATEST_PARSE_ERROR(name) \ + DO_TEST_CAPS_ARCH_LATEST_FULL(name, "x86_64", \ + ARG_FLAGS, FLAG_EXPECT_PARSE_ERROR) + + +# define DO_TEST_FULL(name, ...) \ + DO_TEST_INTERNAL(name, "", \ + __VA_ARGS__, QEMU_CAPS_LAST) + +/* All the following macros require an explicit QEMU_CAPS_* list + * at the end of the argument list, or the NONE placeholder. + * */ +# define DO_TEST(name, ...) \ + DO_TEST_FULL(name, \ + ARG_QEMU_CAPS, __VA_ARGS__) + +# define DO_TEST_GIC(name, gic, ...) \ + DO_TEST_FULL(name, \ + ARG_GIC, gic, \ + ARG_QEMU_CAPS, __VA_ARGS__) + +# define DO_TEST_FAILURE(name, ...) \ + DO_TEST_FULL(name, \ + ARG_FLAGS, FLAG_EXPECT_FAILURE, \ + ARG_QEMU_CAPS, __VA_ARGS__) + +# define DO_TEST_PARSE_ERROR(name, ...) \ + DO_TEST_FULL(name, \ + ARG_FLAGS, FLAG_EXPECT_PARSE_ERROR | FLAG_EXPECT_FAILURE, \ + ARG_QEMU_CAPS, __VA_ARGS__) + +# define NONE QEMU_CAPS_LAST + + /* Unset or set all envvars here that are copied in qemudBuildCommandLine + * using ADD_ENV_COPY, otherwise these tests may fail due to unexpected + * values for these envvars */ + setenv("PATH", "/bin", 1); + setenv("USER", "test", 1); + setenv("LOGNAME", "test", 1); + setenv("HOME", "/home/test", 1); + unsetenv("TMPDIR"); + unsetenv("LD_PRELOAD"); + unsetenv("LD_LIBRARY_PATH"); + unsetenv("QEMU_AUDIO_DRV"); + unsetenv("SDL_AUDIODRIVER"); + + DO_TEST_CAPS_LATEST("tiny"); + + if (getenv("LIBVIRT_SKIP_CLEANUP") == NULL) + virFileDeleteTree(fakerootdir); + + VIR_FREE(driver.config->nbdTLSx509certdir); + qemuTestDriverFree(&driver); + VIR_FREE(fakerootdir); + virHashFree(capslatest); + virFileWrapperClearPrefixes(); + + return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} + +VIR_TEST_MAIN_PRELOAD(mymain, + abs_builddir "/.libs/qemuxml2argvmock.so", + abs_builddir "/.libs/virrandommock.so", + abs_builddir "/.libs/qemucpumock.so", + abs_builddir "/.libs/virpcimock.so") + +#else + +int main(void) +{ + return EXIT_AM_SKIP; +} + +#endif /* WITH_QEMU */ -- 2.20.1 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list