The idea here is pretty simple. We have a synchronous interface that when called, does a migration to a file, kills the QEMU instance, and spawns a new one using the saved file state.
We an then sprinkle calls to qtest_save_restore() thorough test cases to validate that we are properly saving and restoring state. Signed-off-by: Anthony Liguori <aligu...@us.ibm.com> --- tests/libqtest.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/libqtest.h | 46 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) diff --git a/tests/libqtest.c b/tests/libqtest.c index 235ec62..bc2e84e 100644 --- a/tests/libqtest.c +++ b/tests/libqtest.c @@ -44,6 +44,7 @@ struct QTestState gchar *pid_file; /* QEMU PID file */ int child_pid; /* Child process created to execute QEMU */ char *socket_path, *qmp_socket_path; + char *extra_args; }; #define g_assert_no_errno(ret) do { \ @@ -104,6 +105,14 @@ static pid_t qtest_qemu_pid(QTestState *s) return pid; } +void qtest_qmp_wait_event(QTestState *s, const char *event) +{ + char *d; + /* This is cheating */ + d = qtest_qmp(s, ""); + g_free(d); +} + QTestState *qtest_init(const char *extra_args) { QTestState *s; @@ -118,6 +127,7 @@ QTestState *qtest_init(const char *extra_args) s = g_malloc(sizeof(*s)); + s->extra_args = g_strdup(extra_args); s->socket_path = g_strdup_printf("/tmp/qtest-%d.sock", getpid()); s->qmp_socket_path = g_strdup_printf("/tmp/qtest-%d.qmp", getpid()); pid_file = g_strdup_printf("/tmp/qtest-%d.pid", getpid()); @@ -177,6 +187,61 @@ void qtest_quit(QTestState *s) g_free(s->pid_file); g_free(s->socket_path); g_free(s->qmp_socket_path); + g_free(s->extra_args); +} + +QTestState *qtest_save_restore(QTestState *s) +{ + char *filename; + char *d, *p, *extra_args; + char *n; + + filename = g_strdup_printf("/tmp/qtest-%d.savevm", getpid()); + + /* Start migration to a temporary file */ + d = qtest_qmp(s, + "{ 'execute': 'migrate', " + " 'arguments': { 'uri': 'exec:dd of=%s 2>/dev/null' } }", + filename); + g_free(d); + + /* Wait for critical section to be entered */ + qtest_qmp_wait_event(s, "STOP"); + + /* Not strictly needed as we can't possibly respond to this command until + * we've completed migration by virtue of the fact that STOP has been sent + * but it's good to be rigorious. */ + do { + d = qtest_qmp(s, "{ 'execute': 'query-migrate' }"); + p = strstr(d, "\"status\": \"completed\","); + g_free(d); + if (!p) { + g_usleep(100); + } + } while (p == NULL); + + /* Save arguments to this qtest instance */ + extra_args = s->extra_args; + s->extra_args = NULL; + + /* Quit src instance */ + qtest_quit(s); + + /* Spawn destination */ + n = g_strdup_printf("%s -incoming exec:\"dd if=%s 2>/dev/null\"", + extra_args, filename); + s = qtest_init(n); + + /* Wait for incoming migration to complete */ + qtest_qmp_wait_event(s, "RESUME"); + + /* Fixup extra arg so we can call repeatedly */ + g_free(s->extra_args); + s->extra_args = extra_args; + + g_free(filename); + + return s; } static void socket_sendf(int fd, const char *fmt, va_list ap) diff --git a/tests/libqtest.h b/tests/libqtest.h index 5cdcae7..f2c6e52 100644 --- a/tests/libqtest.h +++ b/tests/libqtest.h @@ -67,6 +67,15 @@ char *qtest_qmp(QTestState *s, const char *fmt, ...); char *qtest_qmpv(QTestState *s, const char *fmt, va_list ap); /** + * qtest_qmp_wait_event: + * @s: #QTestState instance to operate on. + * @event: the event to wait for. + * + * Waits for a specific QMP event to occur. + */ +void qtest_qmp_wait_event(QTestState *s, const char *event); + +/** * qtest_get_irq: * @s: #QTestState instance to operate on. * @num: Interrupt to observe. @@ -291,6 +300,19 @@ int64_t qtest_clock_step(QTestState *s, int64_t step); int64_t qtest_clock_set(QTestState *s, int64_t val); /** + * qtest_save_restore: + * @s: QTest instance to operate on. + * + * This function will save and restore the state of the running QEMU + * instance. If the savevm code is implemented correctly for a device, + * this function should behave like a nop. If a test case fails because + * this function is called, the savevm code for the device is broken. + * + * Returns: the new QTest instance + */ +QTestState *qtest_save_restore(QTestState *s); + +/** * qtest_spapr_hcall9: * @s: QTestState instance to operate on. * @nr: The hypercall index @@ -337,6 +359,17 @@ static inline QTestState *qtest_start(const char *args) } /** + * qmp_wait_event: + * @event: the event to wait for. + * + * Waits for a specific QMP event to occur. + */ +static inline void qmp_wait_event(const char *event) +{ + qtest_qmp_wait_event(global_qtest, event); +} + +/** * qmp: * @fmt...: QMP message to send to qemu * @@ -628,6 +661,19 @@ static inline int64_t clock_set(int64_t val) return qtest_clock_set(global_qtest, val); } +/** + * save_restore: + * + * This function will save and restore the state of the running QEMU + * instance. If the savevm code is implemented correctly for a device, + * this function should behave like a nop. If a test case fails because + * this function is called, the savevm code for the device is broken. + */ +static inline void save_restore(void) +{ + global_qtest = qtest_save_restore(global_qtest); +} + static inline uint64_t spapr_hcall0(uint64_t nr) { return qtest_spapr_hcall9(global_qtest, nr, 0, 0, 0, 0, 0, 0, 0, 0, 0); -- 1.8.0