libvirt/tests/nodedevmdevctltest.c
Jonathon Jongsma 592031a580 nodedev: Add tests for mdevctl autostart command
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
2021-09-14 14:24:07 -05:00

466 lines
13 KiB
C

#include <config.h>
#include "internal.h"
#include "testutils.h"
#include "datatypes.h"
#include "node_device/node_device_driver.h"
#include "vircommand.h"
#define LIBVIRT_VIRCOMMANDPRIV_H_ALLOW
#include "vircommandpriv.h"
#define VIR_FROM_THIS VIR_FROM_NODEDEV
#define VIRT_TYPE "QEMU"
static virNodeDeviceDefParserCallbacks parser_callbacks = {
.postParse = nodeDeviceDefPostParse,
.validate = nodeDeviceDefValidate
};
struct TestInfo {
const char *filename;
virMdevctlCommand command;
};
/* capture stdin passed to command */
static void
testCommandDryRunCallback(const char *const*args G_GNUC_UNUSED,
const char *const*env G_GNUC_UNUSED,
const char *input,
char **output G_GNUC_UNUSED,
char **error G_GNUC_UNUSED,
int *status G_GNUC_UNUSED,
void *opaque G_GNUC_UNUSED)
{
char **stdinbuf = opaque;
*stdinbuf = g_strdup(input);
}
typedef virCommand * (*MdevctlCmdFunc)(virNodeDeviceDef *, char **, char **);
static int
testMdevctlCmd(virMdevctlCommand cmd_type,
const char *mdevxml,
const char *cmdfile,
const char *jsonfile)
{
g_autoptr(virNodeDeviceDef) def = NULL;
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
const char *actualCmdline = NULL;
g_autofree char *outbuf = NULL;
g_autofree char *errbuf = NULL;
g_autofree char *stdinbuf = NULL;
g_autoptr(virCommand) cmd = NULL;
g_autoptr(virCommandDryRunToken) dryRunToken = virCommandDryRunTokenNew();
int create;
switch (cmd_type) {
case MDEVCTL_CMD_CREATE:
case MDEVCTL_CMD_DEFINE:
create = CREATE_DEVICE;
break;
case MDEVCTL_CMD_START:
case MDEVCTL_CMD_STOP:
case MDEVCTL_CMD_UNDEFINE:
create = EXISTING_DEVICE;
break;
case MDEVCTL_CMD_LAST:
default:
return -1;
}
if (!(def = virNodeDeviceDefParseFile(mdevxml, create, VIRT_TYPE,
&parser_callbacks, NULL)))
return -1;
/* this function will set a stdin buffer containing the json configuration
* of the device. The json value is captured in the callback above */
cmd = nodeDeviceGetMdevctlCommand(def, cmd_type, &outbuf, &errbuf);
if (!cmd)
return -1;
if (create)
virCommandSetDryRun(dryRunToken, &buf, true, true,
testCommandDryRunCallback, &stdinbuf);
else
virCommandSetDryRun(dryRunToken, &buf, true, true, NULL, NULL);
if (virCommandRun(cmd, NULL) < 0)
return -1;
if (!(actualCmdline = virBufferCurrentContent(&buf)))
return -1;
if (virTestCompareToFileFull(actualCmdline, cmdfile, false) < 0)
return -1;
if (create && virTestCompareToFile(stdinbuf, jsonfile) < 0)
return -1;
return 0;
}
static int
testMdevctlHelper(const void *data)
{
const struct TestInfo *info = data;
const char *cmd = virMdevctlCommandTypeToString(info->command);
g_autofree char *mdevxml = NULL;
g_autofree char *cmdlinefile = NULL;
g_autofree char *jsonfile = NULL;
mdevxml = g_strdup_printf("%s/nodedevschemadata/%s.xml", abs_srcdir,
info->filename);
cmdlinefile = g_strdup_printf("%s/nodedevmdevctldata/%s-%s.argv",
abs_srcdir, info->filename, cmd);
jsonfile = g_strdup_printf("%s/nodedevmdevctldata/%s-%s.json", abs_srcdir,
info->filename, cmd);
return testMdevctlCmd(info->command, mdevxml, cmdlinefile, jsonfile);
}
static int
testMdevctlAutostart(const void *data G_GNUC_UNUSED)
{
g_autoptr(virNodeDeviceDef) def = NULL;
virBuffer buf = VIR_BUFFER_INITIALIZER;
const char *actualCmdline = NULL;
int ret = -1;
g_autoptr(virCommand) enablecmd = NULL;
g_autoptr(virCommand) disablecmd = NULL;
g_autofree char *errmsg = NULL;
/* just concatenate both calls into the same output file */
g_autofree char *cmdlinefile =
g_strdup_printf("%s/nodedevmdevctldata/mdevctl-autostart.argv",
abs_srcdir);
g_autofree char *mdevxml =
g_strdup_printf("%s/nodedevschemadata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366.xml",
abs_srcdir);
g_autoptr(virCommandDryRunToken) dryRunToken = virCommandDryRunTokenNew();
if (!(def = virNodeDeviceDefParseFile(mdevxml, CREATE_DEVICE, VIRT_TYPE,
&parser_callbacks, NULL)))
return -1;
virCommandSetDryRun(dryRunToken, &buf, true, true, NULL, NULL);
if (!(enablecmd = nodeDeviceGetMdevctlSetAutostartCommand(def, true, &errmsg)))
goto cleanup;
if (virCommandRun(enablecmd, NULL) < 0)
goto cleanup;
if (!(disablecmd = nodeDeviceGetMdevctlSetAutostartCommand(def, false, &errmsg)))
goto cleanup;
if (virCommandRun(disablecmd, NULL) < 0)
goto cleanup;
if (!(actualCmdline = virBufferCurrentContent(&buf)))
goto cleanup;
if (virTestCompareToFileFull(actualCmdline, cmdlinefile, false) < 0)
goto cleanup;
ret = 0;
cleanup:
virBufferFreeAndReset(&buf);
return ret;
}
static int
testMdevctlListDefined(const void *data G_GNUC_UNUSED)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
const char *actualCmdline = NULL;
int ret = -1;
g_autoptr(virCommand) cmd = NULL;
g_autofree char *output = NULL;
g_autofree char *errmsg = NULL;
g_autofree char *cmdlinefile =
g_strdup_printf("%s/nodedevmdevctldata/mdevctl-list-defined.argv",
abs_srcdir);
g_autoptr(virCommandDryRunToken) dryRunToken = virCommandDryRunTokenNew();
cmd = nodeDeviceGetMdevctlListCommand(true, &output, &errmsg);
if (!cmd)
goto cleanup;
virCommandSetDryRun(dryRunToken, &buf, true, true, NULL, NULL);
if (virCommandRun(cmd, NULL) < 0)
goto cleanup;
if (!(actualCmdline = virBufferCurrentContent(&buf)))
goto cleanup;
if (virTestCompareToFileFull(actualCmdline, cmdlinefile, false) < 0)
goto cleanup;
ret = 0;
cleanup:
virBufferFreeAndReset(&buf);
return ret;
}
static int
testMdevctlParse(const void *data)
{
g_autofree char *buf = NULL;
const char *filename = data;
g_autofree char *jsonfile = g_strdup_printf("%s/nodedevmdevctldata/%s.json",
abs_srcdir, filename);
g_autofree char *xmloutfile = g_strdup_printf("%s/nodedevmdevctldata/%s.out.xml",
abs_srcdir, filename);
int ret = -1;
int nmdevs = 0;
virNodeDeviceDef **mdevs = NULL;
virBuffer xmloutbuf = VIR_BUFFER_INITIALIZER;
size_t i;
if (virFileReadAll(jsonfile, 1024*1024, &buf) < 0) {
VIR_TEST_DEBUG("Unable to read file %s", jsonfile);
return -1;
}
if ((nmdevs = nodeDeviceParseMdevctlJSON(buf, &mdevs)) < 0) {
VIR_TEST_DEBUG("Unable to parse json for %s", filename);
return -1;
}
for (i = 0; i < nmdevs; i++) {
g_autofree char *devxml = virNodeDeviceDefFormat(mdevs[i]);
if (!devxml)
goto cleanup;
virBufferAddStr(&xmloutbuf, devxml);
}
if (virTestCompareToFileFull(virBufferCurrentContent(&xmloutbuf), xmloutfile, false) < 0)
goto cleanup;
ret = 0;
cleanup:
virBufferFreeAndReset(&xmloutbuf);
for (i = 0; i < nmdevs; i++)
virNodeDeviceDefFree(mdevs[i]);
g_free(mdevs);
return ret;
}
static void
nodedevTestDriverFree(virNodeDeviceDriverState *drv)
{
if (!drv)
return;
virNodeDeviceObjListFree(drv->devs);
virCondDestroy(&drv->initCond);
virMutexDestroy(&drv->lock);
g_free(drv->stateDir);
g_free(drv);
}
/* Add a fake root 'computer' device */
static virNodeDeviceDef *
fakeRootDevice(void)
{
virNodeDeviceDef *def = NULL;
def = g_new0(virNodeDeviceDef, 1);
def->caps = g_new0(virNodeDevCapsDef, 1);
def->name = g_strdup("computer");
return def;
}
/* Add a fake pci device that can be used as a parent device for mediated
* devices. For our purposes, it only needs to have a name that matches the
* parent of the mdev, and it needs a PCI address
*/
static virNodeDeviceDef *
fakePCIDevice(void)
{
virNodeDeviceDef *def = NULL;
virNodeDevCapPCIDev *pci_dev;
def = g_new0(virNodeDeviceDef, 1);
def->caps = g_new0(virNodeDevCapsDef, 1);
def->name = g_strdup("pci_0000_00_02_0");
def->parent = g_strdup("computer");
def->caps->data.type = VIR_NODE_DEV_CAP_PCI_DEV;
pci_dev = &def->caps->data.pci_dev;
pci_dev->domain = 0;
pci_dev->bus = 0;
pci_dev->slot = 2;
pci_dev->function = 0;
return def;
}
/* Add a fake matrix device that can be used as a parent device for mediated
* devices. For our purposes, it only needs to have a name that matches the
* parent of the mdev, and it needs the proper name
*/
static virNodeDeviceDef *
fakeMatrixDevice(void)
{
virNodeDeviceDef *def = NULL;
virNodeDevCapAPMatrix *cap;
def = g_new0(virNodeDeviceDef, 1);
def->caps = g_new0(virNodeDevCapsDef, 1);
def->name = g_strdup("ap_matrix");
def->parent = g_strdup("computer");
def->caps->data.type = VIR_NODE_DEV_CAP_AP_MATRIX;
cap = &def->caps->data.ap_matrix;
cap->addr = g_strdup("matrix");
return def;
}
static int
addDevice(virNodeDeviceDef *def)
{
virNodeDeviceObj *obj;
if (!def)
return -1;
obj = virNodeDeviceObjListAssignDef(driver->devs, def);
if (!obj) {
virNodeDeviceDefFree(def);
return -1;
}
virNodeDeviceObjEndAPI(&obj);
return 0;
}
static int
nodedevTestDriverAddTestDevices(void)
{
if (addDevice(fakeRootDevice()) < 0 ||
addDevice(fakePCIDevice()) < 0 ||
addDevice(fakeMatrixDevice()) < 0)
return -1;
return 0;
}
/* Bare minimum driver init to be able to test nodedev functionality */
static int
nodedevTestDriverInit(void)
{
int ret = -1;
driver = g_new0(virNodeDeviceDriverState, 1);
driver->lockFD = -1;
if (virMutexInit(&driver->lock) < 0 ||
virCondInit(&driver->initCond) < 0) {
VIR_TEST_DEBUG("Unable to initialize test nodedev driver");
goto error;
}
if (!(driver->devs = virNodeDeviceObjListNew()))
goto error;
return 0;
error:
nodedevTestDriverFree(driver);
return ret;
}
static int
mymain(void)
{
int ret = 0;
if (nodedevTestDriverInit() < 0)
return EXIT_FAILURE;
/* add a mock device to the device list so it can be used as a parent
* reference */
if (nodedevTestDriverAddTestDevices() < 0) {
ret = EXIT_FAILURE;
goto done;
}
#define DO_TEST_FULL(desc, func, info) \
if (virTestRun(desc, func, info) < 0) \
ret = -1;
#define DO_TEST_CMD(desc, filename, command) \
do { \
struct TestInfo info = { filename, command }; \
DO_TEST_FULL(desc, testMdevctlHelper, &info); \
} \
while (0)
#define DO_TEST_CREATE(filename) \
DO_TEST_CMD("create mdev " filename, filename, MDEVCTL_CMD_CREATE)
#define DO_TEST_DEFINE(filename) \
DO_TEST_CMD("define mdev " filename, filename, MDEVCTL_CMD_DEFINE)
#define DO_TEST_STOP(filename) \
DO_TEST_CMD("stop mdev " filename, filename, MDEVCTL_CMD_STOP)
#define DO_TEST_UNDEFINE(filename) \
DO_TEST_CMD("undefine mdev" filename, filename, MDEVCTL_CMD_UNDEFINE)
#define DO_TEST_START(filename) \
DO_TEST_CMD("start mdev " filename, filename, MDEVCTL_CMD_START)
#define DO_TEST_LIST_DEFINED() \
DO_TEST_FULL("list defined mdevs", testMdevctlListDefined, NULL)
#define DO_TEST_AUTOSTART() \
DO_TEST_FULL("autostart mdevs", testMdevctlAutostart, NULL)
#define DO_TEST_PARSE_JSON(filename) \
DO_TEST_FULL("parse mdevctl json " filename, testMdevctlParse, filename)
DO_TEST_CREATE("mdev_d069d019_36ea_4111_8f0a_8c9a70e21366");
DO_TEST_CREATE("mdev_fedc4916_1ca8_49ac_b176_871d16c13076");
DO_TEST_CREATE("mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9");
/* Test mdevctl stop command, pass an arbitrary uuid */
DO_TEST_STOP("mdev_d069d019_36ea_4111_8f0a_8c9a70e21366");
DO_TEST_LIST_DEFINED();
DO_TEST_PARSE_JSON("mdevctl-list-empty");
DO_TEST_PARSE_JSON("mdevctl-list-multiple");
DO_TEST_DEFINE("mdev_d069d019_36ea_4111_8f0a_8c9a70e21366");
DO_TEST_DEFINE("mdev_fedc4916_1ca8_49ac_b176_871d16c13076");
DO_TEST_DEFINE("mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9");
DO_TEST_UNDEFINE("mdev_d069d019_36ea_4111_8f0a_8c9a70e21366");
DO_TEST_START("mdev_d069d019_36ea_4111_8f0a_8c9a70e21366");
DO_TEST_AUTOSTART();
done:
nodedevTestDriverFree(driver);
return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
VIR_TEST_MAIN(mymain)