1 #include <config.h>
2 
3 #include "internal.h"
4 #include "testutils.h"
5 #include "datatypes.h"
6 #include "node_device/node_device_driver.h"
7 #include "vircommand.h"
8 #define LIBVIRT_VIRCOMMANDPRIV_H_ALLOW
9 #include "vircommandpriv.h"
10 
11 #define VIR_FROM_THIS VIR_FROM_NODEDEV
12 
13 #define VIRT_TYPE "QEMU"
14 
15 static virNodeDeviceDefParserCallbacks parser_callbacks = {
16     .postParse = nodeDeviceDefPostParse,
17     .validate = nodeDeviceDefValidate
18 };
19 
20 struct TestInfo {
21     const char *filename;
22     virMdevctlCommand command;
23 };
24 
25 /* capture stdin passed to command */
26 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)27 testCommandDryRunCallback(const char *const*args G_GNUC_UNUSED,
28                           const char *const*env G_GNUC_UNUSED,
29                           const char *input,
30                           char **output G_GNUC_UNUSED,
31                           char **error G_GNUC_UNUSED,
32                           int *status G_GNUC_UNUSED,
33                           void *opaque G_GNUC_UNUSED)
34 {
35     char **stdinbuf = opaque;
36 
37     *stdinbuf = g_strdup(input);
38 }
39 
40 typedef virCommand * (*MdevctlCmdFunc)(virNodeDeviceDef *, char **, char **);
41 
42 
43 static int
testMdevctlCmd(virMdevctlCommand cmd_type,const char * mdevxml,const char * cmdfile,const char * jsonfile)44 testMdevctlCmd(virMdevctlCommand cmd_type,
45                const char *mdevxml,
46                const char *cmdfile,
47                const char *jsonfile)
48 {
49     g_autoptr(virNodeDeviceDef) def = NULL;
50     g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
51     const char *actualCmdline = NULL;
52     g_autofree char *outbuf = NULL;
53     g_autofree char *errbuf = NULL;
54     g_autofree char *stdinbuf = NULL;
55     g_autoptr(virCommand) cmd = NULL;
56     g_autoptr(virCommandDryRunToken) dryRunToken = virCommandDryRunTokenNew();
57     int create;
58 
59     switch (cmd_type) {
60         case MDEVCTL_CMD_CREATE:
61         case MDEVCTL_CMD_DEFINE:
62             create = CREATE_DEVICE;
63             break;
64         case MDEVCTL_CMD_START:
65         case MDEVCTL_CMD_STOP:
66         case MDEVCTL_CMD_UNDEFINE:
67             create = EXISTING_DEVICE;
68             break;
69         case MDEVCTL_CMD_LAST:
70         default:
71             return -1;
72     }
73 
74     if (!(def = virNodeDeviceDefParseFile(mdevxml, create, VIRT_TYPE,
75                                           &parser_callbacks, NULL)))
76         return -1;
77 
78     /* this function will set a stdin buffer containing the json configuration
79      * of the device. The json value is captured in the callback above */
80     cmd = nodeDeviceGetMdevctlCommand(def, cmd_type, &outbuf, &errbuf);
81 
82     if (!cmd)
83         return -1;
84 
85     if (create)
86         virCommandSetDryRun(dryRunToken, &buf, true, true,
87                             testCommandDryRunCallback, &stdinbuf);
88     else
89         virCommandSetDryRun(dryRunToken, &buf, true, true, NULL, NULL);
90 
91     if (virCommandRun(cmd, NULL) < 0)
92         return -1;
93 
94     if (!(actualCmdline = virBufferCurrentContent(&buf)))
95         return -1;
96 
97     if (virTestCompareToFileFull(actualCmdline, cmdfile, false) < 0)
98         return -1;
99 
100     if (create && virTestCompareToFile(stdinbuf, jsonfile) < 0)
101         return -1;
102 
103     return 0;
104 }
105 
106 
107 static int
testMdevctlHelper(const void * data)108 testMdevctlHelper(const void *data)
109 {
110     const struct TestInfo *info = data;
111     const char *cmd = virMdevctlCommandTypeToString(info->command);
112     g_autofree char *mdevxml = NULL;
113     g_autofree char *cmdlinefile = NULL;
114     g_autofree char *jsonfile = NULL;
115 
116     mdevxml = g_strdup_printf("%s/nodedevschemadata/%s.xml", abs_srcdir,
117                               info->filename);
118     cmdlinefile = g_strdup_printf("%s/nodedevmdevctldata/%s-%s.argv",
119                                   abs_srcdir, info->filename, cmd);
120     jsonfile = g_strdup_printf("%s/nodedevmdevctldata/%s-%s.json", abs_srcdir,
121                                info->filename, cmd);
122 
123     return testMdevctlCmd(info->command, mdevxml, cmdlinefile, jsonfile);
124 }
125 
126 
127 static int
testMdevctlAutostart(const void * data G_GNUC_UNUSED)128 testMdevctlAutostart(const void *data G_GNUC_UNUSED)
129 {
130     g_autoptr(virNodeDeviceDef) def = NULL;
131     virBuffer buf = VIR_BUFFER_INITIALIZER;
132     const char *actualCmdline = NULL;
133     int ret = -1;
134     g_autoptr(virCommand) enablecmd = NULL;
135     g_autoptr(virCommand) disablecmd = NULL;
136     g_autofree char *errmsg = NULL;
137     /* just concatenate both calls into the same output file */
138     g_autofree char *cmdlinefile =
139         g_strdup_printf("%s/nodedevmdevctldata/mdevctl-autostart.argv",
140                         abs_srcdir);
141     g_autofree char *mdevxml =
142         g_strdup_printf("%s/nodedevschemadata/mdev_d069d019_36ea_4111_8f0a_8c9a70e21366.xml",
143                         abs_srcdir);
144     g_autoptr(virCommandDryRunToken) dryRunToken = virCommandDryRunTokenNew();
145 
146     if (!(def = virNodeDeviceDefParseFile(mdevxml, CREATE_DEVICE, VIRT_TYPE,
147                                           &parser_callbacks, NULL)))
148         return -1;
149 
150     virCommandSetDryRun(dryRunToken, &buf, true, true, NULL, NULL);
151 
152     if (!(enablecmd = nodeDeviceGetMdevctlSetAutostartCommand(def, true, &errmsg)))
153         goto cleanup;
154 
155     if (virCommandRun(enablecmd, NULL) < 0)
156         goto cleanup;
157 
158     if (!(disablecmd = nodeDeviceGetMdevctlSetAutostartCommand(def, false, &errmsg)))
159         goto cleanup;
160 
161     if (virCommandRun(disablecmd, NULL) < 0)
162         goto cleanup;
163 
164     if (!(actualCmdline = virBufferCurrentContent(&buf)))
165         goto cleanup;
166 
167     if (virTestCompareToFileFull(actualCmdline, cmdlinefile, false) < 0)
168         goto cleanup;
169 
170     ret = 0;
171 
172  cleanup:
173     virBufferFreeAndReset(&buf);
174     return ret;
175 }
176 
177 static int
testMdevctlListDefined(const void * data G_GNUC_UNUSED)178 testMdevctlListDefined(const void *data G_GNUC_UNUSED)
179 {
180     virBuffer buf = VIR_BUFFER_INITIALIZER;
181     const char *actualCmdline = NULL;
182     int ret = -1;
183     g_autoptr(virCommand) cmd = NULL;
184     g_autofree char *output = NULL;
185     g_autofree char *errmsg = NULL;
186     g_autofree char *cmdlinefile =
187         g_strdup_printf("%s/nodedevmdevctldata/mdevctl-list-defined.argv",
188                         abs_srcdir);
189     g_autoptr(virCommandDryRunToken) dryRunToken = virCommandDryRunTokenNew();
190 
191     cmd = nodeDeviceGetMdevctlListCommand(true, &output, &errmsg);
192 
193     if (!cmd)
194         goto cleanup;
195 
196     virCommandSetDryRun(dryRunToken, &buf, true, true, NULL, NULL);
197     if (virCommandRun(cmd, NULL) < 0)
198         goto cleanup;
199 
200     if (!(actualCmdline = virBufferCurrentContent(&buf)))
201         goto cleanup;
202 
203     if (virTestCompareToFileFull(actualCmdline, cmdlinefile, false) < 0)
204         goto cleanup;
205 
206     ret = 0;
207 
208  cleanup:
209     virBufferFreeAndReset(&buf);
210     return ret;
211 }
212 
213 static int
testMdevctlParse(const void * data)214 testMdevctlParse(const void *data)
215 {
216     g_autofree char *buf = NULL;
217     const char *filename = data;
218     g_autofree char *jsonfile = g_strdup_printf("%s/nodedevmdevctldata/%s.json",
219                                                 abs_srcdir, filename);
220     g_autofree char *xmloutfile = g_strdup_printf("%s/nodedevmdevctldata/%s.out.xml",
221                                                   abs_srcdir, filename);
222     int ret = -1;
223     int nmdevs = 0;
224     virNodeDeviceDef **mdevs = NULL;
225     virBuffer xmloutbuf = VIR_BUFFER_INITIALIZER;
226     size_t i;
227 
228     if (virFileReadAll(jsonfile, 1024*1024, &buf) < 0) {
229         VIR_TEST_DEBUG("Unable to read file %s", jsonfile);
230         return -1;
231     }
232 
233     if ((nmdevs = nodeDeviceParseMdevctlJSON(buf, &mdevs)) < 0) {
234         VIR_TEST_DEBUG("Unable to parse json for %s", filename);
235         return -1;
236     }
237 
238     for (i = 0; i < nmdevs; i++) {
239         g_autofree char *devxml = virNodeDeviceDefFormat(mdevs[i]);
240         if (!devxml)
241             goto cleanup;
242         virBufferAddStr(&xmloutbuf, devxml);
243     }
244 
245     if (virTestCompareToFileFull(virBufferCurrentContent(&xmloutbuf), xmloutfile, false) < 0)
246         goto cleanup;
247 
248     ret = 0;
249 
250  cleanup:
251     virBufferFreeAndReset(&xmloutbuf);
252     for (i = 0; i < nmdevs; i++)
253         virNodeDeviceDefFree(mdevs[i]);
254     g_free(mdevs);
255 
256     return ret;
257 }
258 
259 static void
nodedevTestDriverFree(virNodeDeviceDriverState * drv)260 nodedevTestDriverFree(virNodeDeviceDriverState *drv)
261 {
262     if (!drv)
263         return;
264 
265     virNodeDeviceObjListFree(drv->devs);
266     virCondDestroy(&drv->initCond);
267     virMutexDestroy(&drv->lock);
268     g_free(drv->stateDir);
269     g_free(drv);
270 }
271 
272 /* Add a fake root 'computer' device */
273 static virNodeDeviceDef *
fakeRootDevice(void)274 fakeRootDevice(void)
275 {
276     virNodeDeviceDef *def = NULL;
277 
278     def = g_new0(virNodeDeviceDef, 1);
279     def->caps = g_new0(virNodeDevCapsDef, 1);
280     def->name = g_strdup("computer");
281 
282     return def;
283 }
284 
285 /* Add a fake pci device that can be used as a parent device for mediated
286  * devices. For our purposes, it only needs to have a name that matches the
287  * parent of the mdev, and it needs a PCI address
288  */
289 static virNodeDeviceDef *
fakePCIDevice(void)290 fakePCIDevice(void)
291 {
292     virNodeDeviceDef *def = NULL;
293     virNodeDevCapPCIDev *pci_dev;
294 
295     def = g_new0(virNodeDeviceDef, 1);
296     def->caps = g_new0(virNodeDevCapsDef, 1);
297 
298     def->name = g_strdup("pci_0000_00_02_0");
299     def->parent = g_strdup("computer");
300 
301     def->caps->data.type = VIR_NODE_DEV_CAP_PCI_DEV;
302     pci_dev = &def->caps->data.pci_dev;
303     pci_dev->domain = 0;
304     pci_dev->bus = 0;
305     pci_dev->slot = 2;
306     pci_dev->function = 0;
307 
308     return def;
309 }
310 
311 
312 /* Add a fake matrix device that can be used as a parent device for mediated
313  * devices. For our purposes, it only needs to have a name that matches the
314  * parent of the mdev, and it needs the proper name
315  */
316 static virNodeDeviceDef *
fakeMatrixDevice(void)317 fakeMatrixDevice(void)
318 {
319     virNodeDeviceDef *def = NULL;
320     virNodeDevCapAPMatrix *cap;
321 
322     def = g_new0(virNodeDeviceDef, 1);
323     def->caps = g_new0(virNodeDevCapsDef, 1);
324 
325     def->name = g_strdup("ap_matrix");
326     def->parent = g_strdup("computer");
327 
328     def->caps->data.type = VIR_NODE_DEV_CAP_AP_MATRIX;
329     cap = &def->caps->data.ap_matrix;
330     cap->addr = g_strdup("matrix");
331 
332     return def;
333 }
334 static int
addDevice(virNodeDeviceDef * def)335 addDevice(virNodeDeviceDef *def)
336 {
337     virNodeDeviceObj *obj;
338     if (!def)
339         return -1;
340 
341     obj = virNodeDeviceObjListAssignDef(driver->devs, def);
342 
343     if (!obj) {
344         virNodeDeviceDefFree(def);
345         return -1;
346     }
347 
348     virNodeDeviceObjEndAPI(&obj);
349     return 0;
350 }
351 
352 static int
nodedevTestDriverAddTestDevices(void)353 nodedevTestDriverAddTestDevices(void)
354 {
355     if (addDevice(fakeRootDevice()) < 0 ||
356         addDevice(fakePCIDevice()) < 0 ||
357         addDevice(fakeMatrixDevice()) < 0)
358         return -1;
359 
360     return 0;
361 }
362 
363 /* Bare minimum driver init to be able to test nodedev functionality */
364 static int
nodedevTestDriverInit(void)365 nodedevTestDriverInit(void)
366 {
367     int ret = -1;
368     driver = g_new0(virNodeDeviceDriverState, 1);
369 
370     driver->lockFD = -1;
371     if (virMutexInit(&driver->lock) < 0 ||
372         virCondInit(&driver->initCond) < 0) {
373         VIR_TEST_DEBUG("Unable to initialize test nodedev driver");
374         goto error;
375     }
376 
377     if (!(driver->devs = virNodeDeviceObjListNew()))
378         goto error;
379 
380     return 0;
381 
382  error:
383     nodedevTestDriverFree(driver);
384     return ret;
385 }
386 
387 static int
mymain(void)388 mymain(void)
389 {
390     int ret = 0;
391 
392     if (nodedevTestDriverInit() < 0)
393         return EXIT_FAILURE;
394 
395     /* add a mock device to the device list so it can be used as a parent
396      * reference */
397     if (nodedevTestDriverAddTestDevices() < 0) {
398         ret = EXIT_FAILURE;
399         goto done;
400     }
401 
402 #define DO_TEST_FULL(desc, func, info) \
403     if (virTestRun(desc, func, info) < 0) \
404         ret = -1;
405 
406 #define DO_TEST_CMD(desc, filename, command) \
407     do { \
408         struct TestInfo info = { filename, command }; \
409         DO_TEST_FULL(desc, testMdevctlHelper, &info); \
410        } \
411     while (0)
412 
413 #define DO_TEST_CREATE(filename) \
414     DO_TEST_CMD("create mdev " filename, filename, MDEVCTL_CMD_CREATE)
415 
416 #define DO_TEST_DEFINE(filename) \
417     DO_TEST_CMD("define mdev " filename, filename, MDEVCTL_CMD_DEFINE)
418 
419 #define DO_TEST_STOP(filename) \
420     DO_TEST_CMD("stop mdev " filename, filename, MDEVCTL_CMD_STOP)
421 
422 #define DO_TEST_UNDEFINE(filename) \
423     DO_TEST_CMD("undefine mdev" filename, filename, MDEVCTL_CMD_UNDEFINE)
424 
425 #define DO_TEST_START(filename) \
426     DO_TEST_CMD("start mdev " filename, filename, MDEVCTL_CMD_START)
427 
428 #define DO_TEST_LIST_DEFINED() \
429     DO_TEST_FULL("list defined mdevs", testMdevctlListDefined, NULL)
430 
431 #define DO_TEST_AUTOSTART() \
432     DO_TEST_FULL("autostart mdevs", testMdevctlAutostart, NULL)
433 
434 #define DO_TEST_PARSE_JSON(filename) \
435     DO_TEST_FULL("parse mdevctl json " filename, testMdevctlParse, filename)
436 
437     DO_TEST_CREATE("mdev_d069d019_36ea_4111_8f0a_8c9a70e21366");
438     DO_TEST_CREATE("mdev_fedc4916_1ca8_49ac_b176_871d16c13076");
439     DO_TEST_CREATE("mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9");
440 
441     /* Test mdevctl stop command, pass an arbitrary uuid */
442     DO_TEST_STOP("mdev_d069d019_36ea_4111_8f0a_8c9a70e21366");
443 
444     DO_TEST_LIST_DEFINED();
445 
446     DO_TEST_PARSE_JSON("mdevctl-list-empty");
447     DO_TEST_PARSE_JSON("mdevctl-list-multiple");
448 
449     DO_TEST_DEFINE("mdev_d069d019_36ea_4111_8f0a_8c9a70e21366");
450     DO_TEST_DEFINE("mdev_fedc4916_1ca8_49ac_b176_871d16c13076");
451     DO_TEST_DEFINE("mdev_d2441d39_495e_4243_ad9f_beb3f14c23d9");
452 
453     DO_TEST_UNDEFINE("mdev_d069d019_36ea_4111_8f0a_8c9a70e21366");
454 
455     DO_TEST_START("mdev_d069d019_36ea_4111_8f0a_8c9a70e21366");
456 
457     DO_TEST_AUTOSTART();
458 
459  done:
460     nodedevTestDriverFree(driver);
461 
462     return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
463 }
464 
465 VIR_TEST_MAIN(mymain)
466