mirror of
https://github.com/Motorhead1991/qemu.git
synced 2025-08-04 00:03:54 -06:00
Qtest pull request
- TIMEOUT_MULTIPLIER setting to allow tests to take longer when asan is enabled - New qtest_system_reset() wrapper to properly wait for a system reset - Split of migration-test.c into multiple files under qtest/migration/ -----BEGIN PGP SIGNATURE----- iQJEBAABCAAuFiEEqhtIsKIjJqWkw2TPx5jcdBvsMZ0FAmda+84QHGZhcm9zYXNA c3VzZS5kZQAKCRDHmNx0G+wxnXWZEACcxW/P9EiLxIeCf4ke7/qq+8HSOOLdQrEa hP5ZCzLlY0K8KQ9fhTGvc6UgRBsScXcLuj4eoR8Gke9e4tID39XVdkion/x6OCiv grC2Ll9/BNrWtR8DiXs3krsU1CXhav6qcT56bMiXV3Qo+KsTmB7RkpXpbkZLZxkn P2aqZlYFS0zZ3PZQceCasQEzB6Dls7o1wNXvgSJXr2E4IuIEcbyWtQpr5DfctnfN STbqGTgMXADyjlqh3CWwSLsZQvIVoN9+3twOrZn4j0D+fyEsTjKsZPlUpRtr72ap FVT+zVIUfxieBlDTsRSwcieD1ePFUUjl7ikQFwypGs33TI9VDTsB88IG7h0/080o 7dYbSMSG0pGEq9QIB8jkV26OZi3y00L/zmLMNDsN6nxKN4mSySlfVD/B/Jb70Smy lwbLklnr921kqsF89Bsq8YRyg8bbGXtx2ifGROgqF+JtrxRW0ElF9K/h4HQtPjuD nZJ4rtrDIv1iMTLJwbNu39fhxvuQowdBYTrbPMgw+xIv++LodqYJSDxbEX+FaDqI LMf772jiAgesl7qe1d9TJizhvonyi9PIdcUILg0SH0H1KvoSjpS3mlGgvo8zgvh+ zvhhzkFGDIHmlCvOaIYyCpadJ5SMMS627q/Q13X3DHjUTkpPUhxGgc9oO7T39+iA 4dOLDRjUcg== =H6xv -----END PGP SIGNATURE----- Merge tag 'qtest-20241212-pull-request' of https://gitlab.com/farosas/qemu into staging Qtest pull request - TIMEOUT_MULTIPLIER setting to allow tests to take longer when asan is enabled - New qtest_system_reset() wrapper to properly wait for a system reset - Split of migration-test.c into multiple files under qtest/migration/ # -----BEGIN PGP SIGNATURE----- # # iQJEBAABCAAuFiEEqhtIsKIjJqWkw2TPx5jcdBvsMZ0FAmda+84QHGZhcm9zYXNA # c3VzZS5kZQAKCRDHmNx0G+wxnXWZEACcxW/P9EiLxIeCf4ke7/qq+8HSOOLdQrEa # hP5ZCzLlY0K8KQ9fhTGvc6UgRBsScXcLuj4eoR8Gke9e4tID39XVdkion/x6OCiv # grC2Ll9/BNrWtR8DiXs3krsU1CXhav6qcT56bMiXV3Qo+KsTmB7RkpXpbkZLZxkn # P2aqZlYFS0zZ3PZQceCasQEzB6Dls7o1wNXvgSJXr2E4IuIEcbyWtQpr5DfctnfN # STbqGTgMXADyjlqh3CWwSLsZQvIVoN9+3twOrZn4j0D+fyEsTjKsZPlUpRtr72ap # FVT+zVIUfxieBlDTsRSwcieD1ePFUUjl7ikQFwypGs33TI9VDTsB88IG7h0/080o # 7dYbSMSG0pGEq9QIB8jkV26OZi3y00L/zmLMNDsN6nxKN4mSySlfVD/B/Jb70Smy # lwbLklnr921kqsF89Bsq8YRyg8bbGXtx2ifGROgqF+JtrxRW0ElF9K/h4HQtPjuD # nZJ4rtrDIv1iMTLJwbNu39fhxvuQowdBYTrbPMgw+xIv++LodqYJSDxbEX+FaDqI # LMf772jiAgesl7qe1d9TJizhvonyi9PIdcUILg0SH0H1KvoSjpS3mlGgvo8zgvh+ # zvhhzkFGDIHmlCvOaIYyCpadJ5SMMS627q/Q13X3DHjUTkpPUhxGgc9oO7T39+iA # 4dOLDRjUcg== # =H6xv # -----END PGP SIGNATURE----- # gpg: Signature made Thu 12 Dec 2024 10:05:50 EST # gpg: using RSA key AA1B48B0A22326A5A4C364CFC798DC741BEC319D # gpg: issuer "farosas@suse.de" # gpg: Good signature from "Fabiano Rosas <farosas@suse.de>" [unknown] # gpg: aka "Fabiano Almeida Rosas <fabiano.rosas@suse.com>" [unknown] # gpg: WARNING: The key's User ID is not certified with a trusted signature! # gpg: There is no indication that the signature belongs to the owner. # Primary key fingerprint: AA1B 48B0 A223 26A5 A4C3 64CF C798 DC74 1BEC 319D * tag 'qtest-20241212-pull-request' of https://gitlab.com/farosas/qemu: (22 commits) tests/qtest/migration: Split validation tests + misc tests/qtest/migration-test: Fix and enable test_ignore_shared tests/qtest/migration: Split CPR tests tests/qtest/migration: Split precopy tests tests/qtest/migration: Split file tests tests/qtest/migration: Split postcopy tests tests/qtest/migration: Split compression tests from migration-test.c tests/qtest/migration: Split TLS tests from migration-test.c tests/qtest/migration: Move common test code tests/qtest/migration: Isolate test initialization tests/qtest/migration: Move kvm_dirty_ring_supported to utils tests/qtest/migration: Move ufd_version_check to utils tests/qtest/migration: Rename migration-helpers.c tests/qtest/migration: Move qmp helpers to a separate file tests/qtest/migration: Move bootfile code to its own file tests/migration: Disambiguate guestperf vs. a-b tests/qtest/migration: Stop calling everything "test" tests/qtest/migration: Standardize hook names tests/qtest: Use qtest_system_reset_nowait() where appropriate tests/qtest: Use qtest_system_reset() instead of open-coded versions ... Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
commit
bdce9bc917
61 changed files with 4871 additions and 4425 deletions
|
@ -118,7 +118,7 @@ F: pc-bios/s390-ccw.img
|
||||||
F: target/s390x/
|
F: target/s390x/
|
||||||
F: docs/system/target-s390x.rst
|
F: docs/system/target-s390x.rst
|
||||||
F: docs/system/s390x/
|
F: docs/system/s390x/
|
||||||
F: tests/migration/s390x/
|
F: tests/qtest/migration/s390x/
|
||||||
K: ^Subject:.*(?i)s390x?
|
K: ^Subject:.*(?i)s390x?
|
||||||
L: qemu-s390x@nongnu.org
|
L: qemu-s390x@nongnu.org
|
||||||
|
|
||||||
|
@ -3429,10 +3429,11 @@ F: include/qemu/userfaultfd.h
|
||||||
F: migration/
|
F: migration/
|
||||||
F: scripts/vmstate-static-checker.py
|
F: scripts/vmstate-static-checker.py
|
||||||
F: tests/vmstate-static-checker-data/
|
F: tests/vmstate-static-checker-data/
|
||||||
|
F: tests/qtest/migration/
|
||||||
F: tests/qtest/migration-*
|
F: tests/qtest/migration-*
|
||||||
F: docs/devel/migration/
|
F: docs/devel/migration/
|
||||||
F: qapi/migration.json
|
F: qapi/migration.json
|
||||||
F: tests/migration/
|
F: tests/migration-stress/
|
||||||
F: util/userfaultfd.c
|
F: util/userfaultfd.c
|
||||||
X: migration/rdma*
|
X: migration/rdma*
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ SPEED = quick
|
||||||
.speed.slow = $(foreach s,$(sort $(filter-out %-thorough, $1)), --suite $s)
|
.speed.slow = $(foreach s,$(sort $(filter-out %-thorough, $1)), --suite $s)
|
||||||
.speed.thorough = $(foreach s,$(sort $1), --suite $s)
|
.speed.thorough = $(foreach s,$(sort $1), --suite $s)
|
||||||
|
|
||||||
TIMEOUT_MULTIPLIER = 1
|
TIMEOUT_MULTIPLIER ?= 1
|
||||||
.mtestargs = --no-rebuild -t $(TIMEOUT_MULTIPLIER)
|
.mtestargs = --no-rebuild -t $(TIMEOUT_MULTIPLIER)
|
||||||
ifneq ($(SPEED), quick)
|
ifneq ($(SPEED), quick)
|
||||||
.mtestargs += --setup $(SPEED)
|
.mtestargs += --setup $(SPEED)
|
||||||
|
|
|
@ -84,5 +84,5 @@ endif
|
||||||
subdir('unit')
|
subdir('unit')
|
||||||
subdir('qapi-schema')
|
subdir('qapi-schema')
|
||||||
subdir('qtest')
|
subdir('qtest')
|
||||||
subdir('migration')
|
subdir('migration-stress')
|
||||||
subdir('functional')
|
subdir('functional')
|
||||||
|
|
|
@ -46,7 +46,8 @@ class BaseShell(object):
|
||||||
parser.add_argument("--binary", dest="binary", default="/usr/bin/qemu-system-x86_64")
|
parser.add_argument("--binary", dest="binary", default="/usr/bin/qemu-system-x86_64")
|
||||||
parser.add_argument("--dst-host", dest="dst_host", default="localhost")
|
parser.add_argument("--dst-host", dest="dst_host", default="localhost")
|
||||||
parser.add_argument("--kernel", dest="kernel", default="/boot/vmlinuz-%s" % platform.release())
|
parser.add_argument("--kernel", dest="kernel", default="/boot/vmlinuz-%s" % platform.release())
|
||||||
parser.add_argument("--initrd", dest="initrd", default="tests/migration/initrd-stress.img")
|
parser.add_argument("--initrd", dest="initrd",
|
||||||
|
default="tests/migration-stress/initrd-stress.img")
|
||||||
parser.add_argument("--transport", dest="transport", default="unix")
|
parser.add_argument("--transport", dest="transport", default="unix")
|
||||||
|
|
||||||
|
|
|
@ -959,7 +959,7 @@ static void test_acpi_piix4_tcg_bridge(void)
|
||||||
free_test_data(&data);
|
free_test_data(&data);
|
||||||
|
|
||||||
/* check that reboot/reset doesn't change any ACPI tables */
|
/* check that reboot/reset doesn't change any ACPI tables */
|
||||||
qtest_qmp_send(data.qts, "{'execute':'system_reset' }");
|
qtest_system_reset(data.qts);
|
||||||
process_acpi_tables(&data);
|
process_acpi_tables(&data);
|
||||||
free_test_data(&data);
|
free_test_data(&data);
|
||||||
}
|
}
|
||||||
|
@ -1216,7 +1216,7 @@ static void test_acpi_q35_multif_bridge(void)
|
||||||
free_test_data(&data);
|
free_test_data(&data);
|
||||||
|
|
||||||
/* check that reboot/reset doesn't change any ACPI tables */
|
/* check that reboot/reset doesn't change any ACPI tables */
|
||||||
qtest_qmp_send(data.qts, "{'execute':'system_reset' }");
|
qtest_system_reset(data.qts);
|
||||||
process_acpi_tables(&data);
|
process_acpi_tables(&data);
|
||||||
free_test_data(&data);
|
free_test_data(&data);
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,12 +40,7 @@ static void test_a_boot_order(const char *machine,
|
||||||
machine ?: "", test_args);
|
machine ?: "", test_args);
|
||||||
actual = read_boot_order(qts);
|
actual = read_boot_order(qts);
|
||||||
g_assert_cmphex(actual, ==, expected_boot);
|
g_assert_cmphex(actual, ==, expected_boot);
|
||||||
qtest_qmp_assert_success(qts, "{ 'execute': 'system_reset' }");
|
qtest_system_reset(qts);
|
||||||
/*
|
|
||||||
* system_reset only requests reset. We get a RESET event after
|
|
||||||
* the actual reset completes. Need to wait for that.
|
|
||||||
*/
|
|
||||||
qtest_qmp_eventwait(qts, "RESET");
|
|
||||||
actual = read_boot_order(qts);
|
actual = read_boot_order(qts);
|
||||||
g_assert_cmphex(actual, ==, expected_reboot);
|
g_assert_cmphex(actual, ==, expected_reboot);
|
||||||
qtest_quit(qts);
|
qtest_quit(qts);
|
||||||
|
|
|
@ -15,15 +15,6 @@
|
||||||
#include "qapi/qmp/qdict.h"
|
#include "qapi/qmp/qdict.h"
|
||||||
#include "qapi/qmp/qstring.h"
|
#include "qapi/qmp/qstring.h"
|
||||||
|
|
||||||
static void system_reset(QTestState *qtest)
|
|
||||||
{
|
|
||||||
QDict *resp;
|
|
||||||
|
|
||||||
resp = qtest_qmp(qtest, "{'execute': 'system_reset'}");
|
|
||||||
g_assert(qdict_haskey(resp, "return"));
|
|
||||||
qobject_unref(resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void wait_device_deleted_event(QTestState *qtest, const char *id)
|
static void wait_device_deleted_event(QTestState *qtest, const char *id)
|
||||||
{
|
{
|
||||||
QDict *resp, *data;
|
QDict *resp, *data;
|
||||||
|
@ -58,7 +49,7 @@ static void process_device_remove(QTestState *qtest, const char *id)
|
||||||
* handled, removing the device.
|
* handled, removing the device.
|
||||||
*/
|
*/
|
||||||
qtest_qmp_device_del_send(qtest, id);
|
qtest_qmp_device_del_send(qtest, id);
|
||||||
system_reset(qtest);
|
qtest_system_reset_nowait(qtest);
|
||||||
wait_device_deleted_event(qtest, id);
|
wait_device_deleted_event(qtest, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -154,15 +154,10 @@ static void device_add(QTestState *qts)
|
||||||
|
|
||||||
static void device_del(QTestState *qts, bool and_reset)
|
static void device_del(QTestState *qts, bool and_reset)
|
||||||
{
|
{
|
||||||
QDict *response;
|
|
||||||
|
|
||||||
qtest_qmp_device_del_send(qts, "dev0");
|
qtest_qmp_device_del_send(qts, "dev0");
|
||||||
|
|
||||||
if (and_reset) {
|
if (and_reset) {
|
||||||
response = qtest_qmp(qts, "{'execute': 'system_reset' }");
|
qtest_system_reset_nowait(qts);
|
||||||
g_assert(response);
|
|
||||||
g_assert(qdict_haskey(response, "return"));
|
|
||||||
qobject_unref(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qtest_qmp_eventwait(qts, "DEVICE_DELETED");
|
qtest_qmp_eventwait(qts, "DEVICE_DELETED");
|
||||||
|
|
|
@ -900,7 +900,6 @@ static void test_override_hot_unplug(TestArgs *args, const char *devid,
|
||||||
QTestState *qts;
|
QTestState *qts;
|
||||||
char *joined_args;
|
char *joined_args;
|
||||||
QFWCFG *fw_cfg;
|
QFWCFG *fw_cfg;
|
||||||
QDict *response;
|
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
joined_args = g_strjoinv(" ", args->argv);
|
joined_args = g_strjoinv(" ", args->argv);
|
||||||
|
@ -913,13 +912,7 @@ static void test_override_hot_unplug(TestArgs *args, const char *devid,
|
||||||
/* unplug device an restart */
|
/* unplug device an restart */
|
||||||
qtest_qmp_device_del_send(qts, devid);
|
qtest_qmp_device_del_send(qts, devid);
|
||||||
|
|
||||||
response = qtest_qmp(qts,
|
qtest_system_reset(qts);
|
||||||
"{ 'execute': 'system_reset', 'arguments': { }}");
|
|
||||||
g_assert(response);
|
|
||||||
g_assert(!qdict_haskey(response, "error"));
|
|
||||||
qobject_unref(response);
|
|
||||||
|
|
||||||
qtest_qmp_eventwait(qts, "RESET");
|
|
||||||
|
|
||||||
read_bootdevices(fw_cfg, expected2);
|
read_bootdevices(fw_cfg, expected2);
|
||||||
|
|
||||||
|
|
|
@ -215,6 +215,22 @@ static void qtest_check_status(QTestState *s)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void qtest_system_reset_nowait(QTestState *s)
|
||||||
|
{
|
||||||
|
/* Request the system reset, but do not wait for it to complete */
|
||||||
|
qtest_qmp_assert_success(s, "{'execute': 'system_reset' }");
|
||||||
|
}
|
||||||
|
|
||||||
|
void qtest_system_reset(QTestState *s)
|
||||||
|
{
|
||||||
|
qtest_system_reset_nowait(s);
|
||||||
|
/*
|
||||||
|
* Wait for the RESET event, which is sent once the system reset
|
||||||
|
* has actually completed.
|
||||||
|
*/
|
||||||
|
qtest_qmp_eventwait(s, "RESET");
|
||||||
|
}
|
||||||
|
|
||||||
void qtest_wait_qemu(QTestState *s)
|
void qtest_wait_qemu(QTestState *s)
|
||||||
{
|
{
|
||||||
if (s->qemu_pid != -1) {
|
if (s->qemu_pid != -1) {
|
||||||
|
|
|
@ -88,6 +88,31 @@ QTestState *qtest_init_without_qmp_handshake(const char *extra_args);
|
||||||
*/
|
*/
|
||||||
QTestState *qtest_init_with_serial(const char *extra_args, int *sock_fd);
|
QTestState *qtest_init_with_serial(const char *extra_args, int *sock_fd);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* qtest_system_reset:
|
||||||
|
* @s: #QTestState instance to operate on.
|
||||||
|
*
|
||||||
|
* Send a "system_reset" command to the QEMU under test, and wait for
|
||||||
|
* the reset to complete before returning.
|
||||||
|
*/
|
||||||
|
void qtest_system_reset(QTestState *s);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* qtest_system_reset_nowait:
|
||||||
|
* @s: #QTestState instance to operate on.
|
||||||
|
*
|
||||||
|
* Send a "system_reset" command to the QEMU under test, but do not
|
||||||
|
* wait for the reset to complete before returning. The caller is
|
||||||
|
* responsible for waiting for either the RESET event or some other
|
||||||
|
* event of interest to them before proceeding.
|
||||||
|
*
|
||||||
|
* This function should only be used if you're specifically testing
|
||||||
|
* for some other event; in that case you can't use qtest_system_reset()
|
||||||
|
* because it will read and discard any other QMP events that arrive
|
||||||
|
* before the RESET event.
|
||||||
|
*/
|
||||||
|
void qtest_system_reset_nowait(QTestState *s);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* qtest_wait_qemu:
|
* qtest_wait_qemu:
|
||||||
* @s: #QTestState instance to operate on.
|
* @s: #QTestState instance to operate on.
|
||||||
|
|
|
@ -332,22 +332,37 @@ endif
|
||||||
|
|
||||||
tpmemu_files = ['tpm-emu.c', 'tpm-util.c', 'tpm-tests.c']
|
tpmemu_files = ['tpm-emu.c', 'tpm-util.c', 'tpm-tests.c']
|
||||||
|
|
||||||
migration_files = [files('migration-helpers.c')]
|
migration_files = [files(
|
||||||
|
'migration/bootfile.c',
|
||||||
|
'migration/framework.c',
|
||||||
|
'migration/migration-qmp.c',
|
||||||
|
'migration/migration-util.c',
|
||||||
|
'migration/compression-tests.c',
|
||||||
|
'migration/cpr-tests.c',
|
||||||
|
'migration/file-tests.c',
|
||||||
|
'migration/misc-tests.c',
|
||||||
|
'migration/precopy-tests.c',
|
||||||
|
'migration/postcopy-tests.c',
|
||||||
|
)]
|
||||||
|
|
||||||
|
migration_tls_files = []
|
||||||
if gnutls.found()
|
if gnutls.found()
|
||||||
migration_files += [files('../unit/crypto-tls-psk-helpers.c'), gnutls]
|
migration_tls_files = [files('migration/tls-tests.c',
|
||||||
|
'../unit/crypto-tls-psk-helpers.c'), gnutls]
|
||||||
|
|
||||||
if tasn1.found()
|
if tasn1.found()
|
||||||
migration_files += [files('../unit/crypto-tls-x509-helpers.c'), tasn1]
|
migration_tls_files += [files('../unit/crypto-tls-x509-helpers.c'), tasn1]
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
qtests = {
|
qtests = {
|
||||||
'bios-tables-test': [io, 'boot-sector.c', 'acpi-utils.c', 'tpm-emu.c'],
|
'bios-tables-test': [io, 'boot-sector.c', 'acpi-utils.c', 'tpm-emu.c'],
|
||||||
'cdrom-test': files('boot-sector.c'),
|
'cdrom-test': files('boot-sector.c'),
|
||||||
'dbus-vmstate-test': files('migration-helpers.c') + dbus_vmstate1,
|
'dbus-vmstate-test': files('migration/migration-qmp.c',
|
||||||
|
'migration/migration-util.c') + dbus_vmstate1,
|
||||||
'erst-test': files('erst-test.c'),
|
'erst-test': files('erst-test.c'),
|
||||||
'ivshmem-test': [rt, '../../contrib/ivshmem-server/ivshmem-server.c'],
|
'ivshmem-test': [rt, '../../contrib/ivshmem-server/ivshmem-server.c'],
|
||||||
'migration-test': migration_files,
|
'migration-test': migration_files + migration_tls_files,
|
||||||
'pxe-test': files('boot-sector.c'),
|
'pxe-test': files('boot-sector.c'),
|
||||||
'pnv-xive2-test': files('pnv-xive2-common.c', 'pnv-xive2-flush-sync.c'),
|
'pnv-xive2-test': files('pnv-xive2-common.c', 'pnv-xive2-flush-sync.c'),
|
||||||
'qos-test': [chardev, io, qos_test_ss.apply({}).sources()],
|
'qos-test': [chardev, io, qos_test_ss.apply({}).sources()],
|
||||||
|
@ -358,7 +373,7 @@ qtests = {
|
||||||
'tpm-tis-i2c-test': [io, tpmemu_files, 'qtest_aspeed.c'],
|
'tpm-tis-i2c-test': [io, tpmemu_files, 'qtest_aspeed.c'],
|
||||||
'tpm-tis-device-swtpm-test': [io, tpmemu_files, 'tpm-tis-util.c'],
|
'tpm-tis-device-swtpm-test': [io, tpmemu_files, 'tpm-tis-util.c'],
|
||||||
'tpm-tis-device-test': [io, tpmemu_files, 'tpm-tis-util.c'],
|
'tpm-tis-device-test': [io, tpmemu_files, 'tpm-tis-util.c'],
|
||||||
'virtio-net-failover': files('migration-helpers.c'),
|
'virtio-net-failover': migration_files,
|
||||||
'vmgenid-test': files('boot-sector.c', 'acpi-utils.c'),
|
'vmgenid-test': files('boot-sector.c', 'acpi-utils.c'),
|
||||||
'netdev-socket': files('netdev-socket.c', '../unit/socket-helpers.c'),
|
'netdev-socket': files('netdev-socket.c', '../unit/socket-helpers.c'),
|
||||||
'aspeed_smc-test': files('aspeed-smc-utils.c', 'aspeed_smc-test.c'),
|
'aspeed_smc-test': files('aspeed-smc-utils.c', 'aspeed_smc-test.c'),
|
||||||
|
|
File diff suppressed because it is too large
Load diff
70
tests/qtest/migration/bootfile.c
Normal file
70
tests/qtest/migration/bootfile.c
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
* Guest code setup for migration tests
|
||||||
|
*
|
||||||
|
* Copyright (c) 2016-2018 Red Hat, Inc. and/or its affiliates
|
||||||
|
* based on the vhost-user-test.c that is:
|
||||||
|
* Copyright (c) 2014 Virtual Open Systems Sarl.
|
||||||
|
*
|
||||||
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||||
|
* See the COPYING file in the top-level directory.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "qemu/osdep.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The boot file modifies memory area in [start_address, end_address)
|
||||||
|
* repeatedly. It outputs a 'B' at a fixed rate while it's still running.
|
||||||
|
*/
|
||||||
|
#include "bootfile.h"
|
||||||
|
#include "i386/a-b-bootblock.h"
|
||||||
|
#include "aarch64/a-b-kernel.h"
|
||||||
|
#include "ppc64/a-b-kernel.h"
|
||||||
|
#include "s390x/a-b-bios.h"
|
||||||
|
|
||||||
|
static char *bootpath;
|
||||||
|
|
||||||
|
void bootfile_delete(void)
|
||||||
|
{
|
||||||
|
if (!bootpath) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
unlink(bootpath);
|
||||||
|
g_free(bootpath);
|
||||||
|
bootpath = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *bootfile_create(const char *arch, const char *dir, bool suspend_me)
|
||||||
|
{
|
||||||
|
unsigned char *content;
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
bootfile_delete();
|
||||||
|
bootpath = g_strdup_printf("%s/bootsect", dir);
|
||||||
|
if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) {
|
||||||
|
/* the assembled x86 boot sector should be exactly one sector large */
|
||||||
|
g_assert(sizeof(x86_bootsect) == 512);
|
||||||
|
x86_bootsect[SYM_suspend_me - SYM_start] = suspend_me;
|
||||||
|
content = x86_bootsect;
|
||||||
|
len = sizeof(x86_bootsect);
|
||||||
|
} else if (g_str_equal(arch, "s390x")) {
|
||||||
|
content = s390x_elf;
|
||||||
|
len = sizeof(s390x_elf);
|
||||||
|
} else if (strcmp(arch, "ppc64") == 0) {
|
||||||
|
content = ppc64_kernel;
|
||||||
|
len = sizeof(ppc64_kernel);
|
||||||
|
} else if (strcmp(arch, "aarch64") == 0) {
|
||||||
|
content = aarch64_kernel;
|
||||||
|
len = sizeof(aarch64_kernel);
|
||||||
|
g_assert(sizeof(aarch64_kernel) <= ARM_TEST_MAX_KERNEL_SIZE);
|
||||||
|
} else {
|
||||||
|
g_assert_not_reached();
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *bootfile = fopen(bootpath, "wb");
|
||||||
|
|
||||||
|
g_assert_cmpint(fwrite(content, len, 1, bootfile), ==, 1);
|
||||||
|
fclose(bootfile);
|
||||||
|
|
||||||
|
return bootpath;
|
||||||
|
}
|
|
@ -5,8 +5,8 @@
|
||||||
* See the COPYING file in the top-level directory.
|
* See the COPYING file in the top-level directory.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef MIGRATION_TEST_H
|
#ifndef BOOTFILE_H
|
||||||
#define MIGRATION_TEST_H
|
#define BOOTFILE_H
|
||||||
|
|
||||||
/* Common */
|
/* Common */
|
||||||
#define TEST_MEM_PAGE_SIZE 4096
|
#define TEST_MEM_PAGE_SIZE 4096
|
||||||
|
@ -33,4 +33,7 @@
|
||||||
*/
|
*/
|
||||||
#define ARM_TEST_MAX_KERNEL_SIZE (512 * 1024)
|
#define ARM_TEST_MAX_KERNEL_SIZE (512 * 1024)
|
||||||
|
|
||||||
#endif /* MIGRATION_TEST_H */
|
void bootfile_delete(void);
|
||||||
|
char *bootfile_create(const char *arch, const char *dir, bool suspend_me);
|
||||||
|
|
||||||
|
#endif /* BOOTFILE_H */
|
239
tests/qtest/migration/compression-tests.c
Normal file
239
tests/qtest/migration/compression-tests.c
Normal file
|
@ -0,0 +1,239 @@
|
||||||
|
/*
|
||||||
|
* QTest testcases for migration compression
|
||||||
|
*
|
||||||
|
* Copyright (c) 2016-2018 Red Hat, Inc. and/or its affiliates
|
||||||
|
* based on the vhost-user-test.c that is:
|
||||||
|
* Copyright (c) 2014 Virtual Open Systems Sarl.
|
||||||
|
*
|
||||||
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||||
|
* See the COPYING file in the top-level directory.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "qemu/osdep.h"
|
||||||
|
#include "libqtest.h"
|
||||||
|
#include "migration/framework.h"
|
||||||
|
#include "migration/migration-qmp.h"
|
||||||
|
#include "migration/migration-util.h"
|
||||||
|
#include "qemu/module.h"
|
||||||
|
|
||||||
|
|
||||||
|
static char *tmpfs;
|
||||||
|
|
||||||
|
#ifdef CONFIG_ZSTD
|
||||||
|
static void *
|
||||||
|
migrate_hook_start_precopy_tcp_multifd_zstd(QTestState *from,
|
||||||
|
QTestState *to)
|
||||||
|
{
|
||||||
|
migrate_set_parameter_int(from, "multifd-zstd-level", 2);
|
||||||
|
migrate_set_parameter_int(to, "multifd-zstd-level", 2);
|
||||||
|
|
||||||
|
return migrate_hook_start_precopy_tcp_multifd_common(from, to, "zstd");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_multifd_tcp_zstd(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.listen_uri = "defer",
|
||||||
|
.start_hook = migrate_hook_start_precopy_tcp_multifd_zstd,
|
||||||
|
};
|
||||||
|
test_precopy_common(&args);
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_ZSTD */
|
||||||
|
|
||||||
|
#ifdef CONFIG_QATZIP
|
||||||
|
static void *
|
||||||
|
migrate_hook_start_precopy_tcp_multifd_qatzip(QTestState *from,
|
||||||
|
QTestState *to)
|
||||||
|
{
|
||||||
|
migrate_set_parameter_int(from, "multifd-qatzip-level", 2);
|
||||||
|
migrate_set_parameter_int(to, "multifd-qatzip-level", 2);
|
||||||
|
|
||||||
|
return migrate_hook_start_precopy_tcp_multifd_common(from, to, "qatzip");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_multifd_tcp_qatzip(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.listen_uri = "defer",
|
||||||
|
.start_hook = migrate_hook_start_precopy_tcp_multifd_qatzip,
|
||||||
|
};
|
||||||
|
test_precopy_common(&args);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_QPL
|
||||||
|
static void *
|
||||||
|
migrate_hook_start_precopy_tcp_multifd_qpl(QTestState *from,
|
||||||
|
QTestState *to)
|
||||||
|
{
|
||||||
|
return migrate_hook_start_precopy_tcp_multifd_common(from, to, "qpl");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_multifd_tcp_qpl(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.listen_uri = "defer",
|
||||||
|
.start_hook = migrate_hook_start_precopy_tcp_multifd_qpl,
|
||||||
|
};
|
||||||
|
test_precopy_common(&args);
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_QPL */
|
||||||
|
|
||||||
|
#ifdef CONFIG_UADK
|
||||||
|
static void *
|
||||||
|
migrate_hook_start_precopy_tcp_multifd_uadk(QTestState *from,
|
||||||
|
QTestState *to)
|
||||||
|
{
|
||||||
|
return migrate_hook_start_precopy_tcp_multifd_common(from, to, "uadk");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *
|
||||||
|
migrate_hook_start_xbzrle(QTestState *from,
|
||||||
|
QTestState *to)
|
||||||
|
{
|
||||||
|
migrate_set_parameter_int(from, "xbzrle-cache-size", 33554432);
|
||||||
|
|
||||||
|
migrate_set_capability(from, "xbzrle", true);
|
||||||
|
migrate_set_capability(to, "xbzrle", true);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_precopy_unix_xbzrle(void)
|
||||||
|
{
|
||||||
|
g_autofree char *uri = g_strdup_printf("unix:%s/migsocket", tmpfs);
|
||||||
|
MigrateCommon args = {
|
||||||
|
.connect_uri = uri,
|
||||||
|
.listen_uri = uri,
|
||||||
|
.start_hook = migrate_hook_start_xbzrle,
|
||||||
|
.iterations = 2,
|
||||||
|
/*
|
||||||
|
* XBZRLE needs pages to be modified when doing the 2nd+ round
|
||||||
|
* iteration to have real data pushed to the stream.
|
||||||
|
*/
|
||||||
|
.live = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_precopy_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *
|
||||||
|
migrate_hook_start_precopy_tcp_multifd_zlib(QTestState *from,
|
||||||
|
QTestState *to)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Overloading this test to also check that set_parameter does not error.
|
||||||
|
* This is also done in the tests for the other compression methods.
|
||||||
|
*/
|
||||||
|
migrate_set_parameter_int(from, "multifd-zlib-level", 2);
|
||||||
|
migrate_set_parameter_int(to, "multifd-zlib-level", 2);
|
||||||
|
|
||||||
|
return migrate_hook_start_precopy_tcp_multifd_common(from, to, "zlib");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_multifd_tcp_zlib(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.listen_uri = "defer",
|
||||||
|
.start_hook = migrate_hook_start_precopy_tcp_multifd_zlib,
|
||||||
|
};
|
||||||
|
test_precopy_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_multifd_tcp_uadk(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.listen_uri = "defer",
|
||||||
|
.start_hook = migrate_hook_start_precopy_tcp_multifd_uadk,
|
||||||
|
};
|
||||||
|
test_precopy_common(&args);
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_UADK */
|
||||||
|
|
||||||
|
|
||||||
|
static void *
|
||||||
|
migrate_hook_start_xbzrle(QTestState *from,
|
||||||
|
QTestState *to)
|
||||||
|
{
|
||||||
|
migrate_set_parameter_int(from, "xbzrle-cache-size", 33554432);
|
||||||
|
|
||||||
|
migrate_set_capability(from, "xbzrle", true);
|
||||||
|
migrate_set_capability(to, "xbzrle", true);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_precopy_unix_xbzrle(void)
|
||||||
|
{
|
||||||
|
g_autofree char *uri = g_strdup_printf("unix:%s/migsocket", tmpfs);
|
||||||
|
MigrateCommon args = {
|
||||||
|
.connect_uri = uri,
|
||||||
|
.listen_uri = uri,
|
||||||
|
.start_hook = migrate_hook_start_xbzrle,
|
||||||
|
.iterations = 2,
|
||||||
|
/*
|
||||||
|
* XBZRLE needs pages to be modified when doing the 2nd+ round
|
||||||
|
* iteration to have real data pushed to the stream.
|
||||||
|
*/
|
||||||
|
.live = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_precopy_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *
|
||||||
|
migrate_hook_start_precopy_tcp_multifd_zlib(QTestState *from,
|
||||||
|
QTestState *to)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Overloading this test to also check that set_parameter does not error.
|
||||||
|
* This is also done in the tests for the other compression methods.
|
||||||
|
*/
|
||||||
|
migrate_set_parameter_int(from, "multifd-zlib-level", 2);
|
||||||
|
migrate_set_parameter_int(to, "multifd-zlib-level", 2);
|
||||||
|
|
||||||
|
return migrate_hook_start_precopy_tcp_multifd_common(from, to, "zlib");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_multifd_tcp_zlib(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.listen_uri = "defer",
|
||||||
|
.start_hook = migrate_hook_start_precopy_tcp_multifd_zlib,
|
||||||
|
};
|
||||||
|
test_precopy_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void migration_test_add_compression(MigrationTestEnv *env)
|
||||||
|
{
|
||||||
|
tmpfs = env->tmpfs;
|
||||||
|
|
||||||
|
#ifdef CONFIG_ZSTD
|
||||||
|
migration_test_add("/migration/multifd/tcp/plain/zstd",
|
||||||
|
test_multifd_tcp_zstd);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_QATZIP
|
||||||
|
migration_test_add("/migration/multifd/tcp/plain/qatzip",
|
||||||
|
test_multifd_tcp_qatzip);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_QPL
|
||||||
|
migration_test_add("/migration/multifd/tcp/plain/qpl",
|
||||||
|
test_multifd_tcp_qpl);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_UADK
|
||||||
|
migration_test_add("/migration/multifd/tcp/plain/uadk",
|
||||||
|
test_multifd_tcp_uadk);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (g_test_slow()) {
|
||||||
|
migration_test_add("/migration/precopy/unix/xbzrle",
|
||||||
|
test_precopy_unix_xbzrle);
|
||||||
|
}
|
||||||
|
|
||||||
|
migration_test_add("/migration/multifd/tcp/plain/zlib",
|
||||||
|
test_multifd_tcp_zlib);
|
||||||
|
}
|
58
tests/qtest/migration/cpr-tests.c
Normal file
58
tests/qtest/migration/cpr-tests.c
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
* QTest testcases for CPR
|
||||||
|
*
|
||||||
|
* Copyright (c) 2016-2018 Red Hat, Inc. and/or its affiliates
|
||||||
|
* based on the vhost-user-test.c that is:
|
||||||
|
* Copyright (c) 2014 Virtual Open Systems Sarl.
|
||||||
|
*
|
||||||
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||||
|
* See the COPYING file in the top-level directory.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "qemu/osdep.h"
|
||||||
|
#include "libqtest.h"
|
||||||
|
#include "migration/framework.h"
|
||||||
|
#include "migration/migration-qmp.h"
|
||||||
|
#include "migration/migration-util.h"
|
||||||
|
|
||||||
|
|
||||||
|
static char *tmpfs;
|
||||||
|
|
||||||
|
static void *migrate_hook_start_mode_reboot(QTestState *from, QTestState *to)
|
||||||
|
{
|
||||||
|
migrate_set_parameter_str(from, "mode", "cpr-reboot");
|
||||||
|
migrate_set_parameter_str(to, "mode", "cpr-reboot");
|
||||||
|
|
||||||
|
migrate_set_capability(from, "x-ignore-shared", true);
|
||||||
|
migrate_set_capability(to, "x-ignore-shared", true);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_mode_reboot(void)
|
||||||
|
{
|
||||||
|
g_autofree char *uri = g_strdup_printf("file:%s/%s", tmpfs,
|
||||||
|
FILE_TEST_FILENAME);
|
||||||
|
MigrateCommon args = {
|
||||||
|
.start.use_shmem = true,
|
||||||
|
.connect_uri = uri,
|
||||||
|
.listen_uri = "defer",
|
||||||
|
.start_hook = migrate_hook_start_mode_reboot,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_file_common(&args, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void migration_test_add_cpr(MigrationTestEnv *env)
|
||||||
|
{
|
||||||
|
tmpfs = env->tmpfs;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Our CI system has problems with shared memory.
|
||||||
|
* Don't run this test until we find a workaround.
|
||||||
|
*/
|
||||||
|
if (getenv("QEMU_TEST_FLAKY_TESTS")) {
|
||||||
|
migration_test_add("/migration/mode/reboot", test_mode_reboot);
|
||||||
|
}
|
||||||
|
}
|
338
tests/qtest/migration/file-tests.c
Normal file
338
tests/qtest/migration/file-tests.c
Normal file
|
@ -0,0 +1,338 @@
|
||||||
|
/*
|
||||||
|
* QTest testcases for migration to file
|
||||||
|
*
|
||||||
|
* Copyright (c) 2016-2018 Red Hat, Inc. and/or its affiliates
|
||||||
|
* based on the vhost-user-test.c that is:
|
||||||
|
* Copyright (c) 2014 Virtual Open Systems Sarl.
|
||||||
|
*
|
||||||
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||||
|
* See the COPYING file in the top-level directory.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "qemu/osdep.h"
|
||||||
|
#include "libqtest.h"
|
||||||
|
#include "migration/framework.h"
|
||||||
|
#include "migration/migration-qmp.h"
|
||||||
|
#include "migration/migration-util.h"
|
||||||
|
#include "qapi/qmp/qlist.h"
|
||||||
|
|
||||||
|
|
||||||
|
static char *tmpfs;
|
||||||
|
|
||||||
|
static void test_precopy_file(void)
|
||||||
|
{
|
||||||
|
g_autofree char *uri = g_strdup_printf("file:%s/%s", tmpfs,
|
||||||
|
FILE_TEST_FILENAME);
|
||||||
|
MigrateCommon args = {
|
||||||
|
.connect_uri = uri,
|
||||||
|
.listen_uri = "defer",
|
||||||
|
};
|
||||||
|
|
||||||
|
test_file_common(&args, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
static void fdset_add_fds(QTestState *qts, const char *file, int flags,
|
||||||
|
int num_fds, bool direct_io)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < num_fds; i++) {
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
#ifdef O_DIRECT
|
||||||
|
/* only secondary channels can use direct-io */
|
||||||
|
if (direct_io && i != 0) {
|
||||||
|
flags |= O_DIRECT;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
fd = open(file, flags, 0660);
|
||||||
|
assert(fd != -1);
|
||||||
|
|
||||||
|
qtest_qmp_fds_assert_success(qts, &fd, 1, "{'execute': 'add-fd', "
|
||||||
|
"'arguments': {'fdset-id': 1}}");
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *migrate_hook_start_file_offset_fdset(QTestState *from,
|
||||||
|
QTestState *to)
|
||||||
|
{
|
||||||
|
g_autofree char *file = g_strdup_printf("%s/%s", tmpfs, FILE_TEST_FILENAME);
|
||||||
|
|
||||||
|
fdset_add_fds(from, file, O_WRONLY, 1, false);
|
||||||
|
fdset_add_fds(to, file, O_RDONLY, 1, false);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_precopy_file_offset_fdset(void)
|
||||||
|
{
|
||||||
|
g_autofree char *uri = g_strdup_printf("file:/dev/fdset/1,offset=%d",
|
||||||
|
FILE_TEST_OFFSET);
|
||||||
|
MigrateCommon args = {
|
||||||
|
.connect_uri = uri,
|
||||||
|
.listen_uri = "defer",
|
||||||
|
.start_hook = migrate_hook_start_file_offset_fdset,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_file_common(&args, false);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void test_precopy_file_offset(void)
|
||||||
|
{
|
||||||
|
g_autofree char *uri = g_strdup_printf("file:%s/%s,offset=%d", tmpfs,
|
||||||
|
FILE_TEST_FILENAME,
|
||||||
|
FILE_TEST_OFFSET);
|
||||||
|
MigrateCommon args = {
|
||||||
|
.connect_uri = uri,
|
||||||
|
.listen_uri = "defer",
|
||||||
|
};
|
||||||
|
|
||||||
|
test_file_common(&args, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_precopy_file_offset_bad(void)
|
||||||
|
{
|
||||||
|
/* using a value not supported by qemu_strtosz() */
|
||||||
|
g_autofree char *uri = g_strdup_printf("file:%s/%s,offset=0x20M",
|
||||||
|
tmpfs, FILE_TEST_FILENAME);
|
||||||
|
MigrateCommon args = {
|
||||||
|
.connect_uri = uri,
|
||||||
|
.listen_uri = "defer",
|
||||||
|
.result = MIG_TEST_QMP_ERROR,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_file_common(&args, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *migrate_hook_start_mapped_ram(QTestState *from,
|
||||||
|
QTestState *to)
|
||||||
|
{
|
||||||
|
migrate_set_capability(from, "mapped-ram", true);
|
||||||
|
migrate_set_capability(to, "mapped-ram", true);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_precopy_file_mapped_ram_live(void)
|
||||||
|
{
|
||||||
|
g_autofree char *uri = g_strdup_printf("file:%s/%s", tmpfs,
|
||||||
|
FILE_TEST_FILENAME);
|
||||||
|
MigrateCommon args = {
|
||||||
|
.connect_uri = uri,
|
||||||
|
.listen_uri = "defer",
|
||||||
|
.start_hook = migrate_hook_start_mapped_ram,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_file_common(&args, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_precopy_file_mapped_ram(void)
|
||||||
|
{
|
||||||
|
g_autofree char *uri = g_strdup_printf("file:%s/%s", tmpfs,
|
||||||
|
FILE_TEST_FILENAME);
|
||||||
|
MigrateCommon args = {
|
||||||
|
.connect_uri = uri,
|
||||||
|
.listen_uri = "defer",
|
||||||
|
.start_hook = migrate_hook_start_mapped_ram,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_file_common(&args, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *migrate_hook_start_multifd_mapped_ram(QTestState *from,
|
||||||
|
QTestState *to)
|
||||||
|
{
|
||||||
|
migrate_hook_start_mapped_ram(from, to);
|
||||||
|
|
||||||
|
migrate_set_parameter_int(from, "multifd-channels", 4);
|
||||||
|
migrate_set_parameter_int(to, "multifd-channels", 4);
|
||||||
|
|
||||||
|
migrate_set_capability(from, "multifd", true);
|
||||||
|
migrate_set_capability(to, "multifd", true);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_multifd_file_mapped_ram_live(void)
|
||||||
|
{
|
||||||
|
g_autofree char *uri = g_strdup_printf("file:%s/%s", tmpfs,
|
||||||
|
FILE_TEST_FILENAME);
|
||||||
|
MigrateCommon args = {
|
||||||
|
.connect_uri = uri,
|
||||||
|
.listen_uri = "defer",
|
||||||
|
.start_hook = migrate_hook_start_multifd_mapped_ram,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_file_common(&args, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_multifd_file_mapped_ram(void)
|
||||||
|
{
|
||||||
|
g_autofree char *uri = g_strdup_printf("file:%s/%s", tmpfs,
|
||||||
|
FILE_TEST_FILENAME);
|
||||||
|
MigrateCommon args = {
|
||||||
|
.connect_uri = uri,
|
||||||
|
.listen_uri = "defer",
|
||||||
|
.start_hook = migrate_hook_start_multifd_mapped_ram,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_file_common(&args, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *migrate_hook_start_multifd_mapped_ram_dio(QTestState *from,
|
||||||
|
QTestState *to)
|
||||||
|
{
|
||||||
|
migrate_hook_start_multifd_mapped_ram(from, to);
|
||||||
|
|
||||||
|
migrate_set_parameter_bool(from, "direct-io", true);
|
||||||
|
migrate_set_parameter_bool(to, "direct-io", true);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_multifd_file_mapped_ram_dio(void)
|
||||||
|
{
|
||||||
|
g_autofree char *uri = g_strdup_printf("file:%s/%s", tmpfs,
|
||||||
|
FILE_TEST_FILENAME);
|
||||||
|
MigrateCommon args = {
|
||||||
|
.connect_uri = uri,
|
||||||
|
.listen_uri = "defer",
|
||||||
|
.start_hook = migrate_hook_start_multifd_mapped_ram_dio,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!probe_o_direct_support(tmpfs)) {
|
||||||
|
g_test_skip("Filesystem does not support O_DIRECT");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
test_file_common(&args, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
static void migrate_hook_end_multifd_mapped_ram_fdset(QTestState *from,
|
||||||
|
QTestState *to,
|
||||||
|
void *opaque)
|
||||||
|
{
|
||||||
|
QDict *resp;
|
||||||
|
QList *fdsets;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove the fdsets after migration, otherwise a second migration
|
||||||
|
* would fail due fdset reuse.
|
||||||
|
*/
|
||||||
|
qtest_qmp_assert_success(from, "{'execute': 'remove-fd', "
|
||||||
|
"'arguments': { 'fdset-id': 1}}");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Make sure no fdsets are left after migration, otherwise a
|
||||||
|
* second migration would fail due fdset reuse.
|
||||||
|
*/
|
||||||
|
resp = qtest_qmp(from, "{'execute': 'query-fdsets', "
|
||||||
|
"'arguments': {}}");
|
||||||
|
g_assert(qdict_haskey(resp, "return"));
|
||||||
|
fdsets = qdict_get_qlist(resp, "return");
|
||||||
|
g_assert(fdsets && qlist_empty(fdsets));
|
||||||
|
qobject_unref(resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *migrate_hook_start_multifd_mapped_ram_fdset_dio(QTestState *from,
|
||||||
|
QTestState *to)
|
||||||
|
{
|
||||||
|
g_autofree char *file = g_strdup_printf("%s/%s", tmpfs, FILE_TEST_FILENAME);
|
||||||
|
|
||||||
|
fdset_add_fds(from, file, O_WRONLY, 2, true);
|
||||||
|
fdset_add_fds(to, file, O_RDONLY, 2, true);
|
||||||
|
|
||||||
|
migrate_hook_start_multifd_mapped_ram(from, to);
|
||||||
|
migrate_set_parameter_bool(from, "direct-io", true);
|
||||||
|
migrate_set_parameter_bool(to, "direct-io", true);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *migrate_hook_start_multifd_mapped_ram_fdset(QTestState *from,
|
||||||
|
QTestState *to)
|
||||||
|
{
|
||||||
|
g_autofree char *file = g_strdup_printf("%s/%s", tmpfs, FILE_TEST_FILENAME);
|
||||||
|
|
||||||
|
fdset_add_fds(from, file, O_WRONLY, 2, false);
|
||||||
|
fdset_add_fds(to, file, O_RDONLY, 2, false);
|
||||||
|
|
||||||
|
migrate_hook_start_multifd_mapped_ram(from, to);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_multifd_file_mapped_ram_fdset(void)
|
||||||
|
{
|
||||||
|
g_autofree char *uri = g_strdup_printf("file:/dev/fdset/1,offset=%d",
|
||||||
|
FILE_TEST_OFFSET);
|
||||||
|
MigrateCommon args = {
|
||||||
|
.connect_uri = uri,
|
||||||
|
.listen_uri = "defer",
|
||||||
|
.start_hook = migrate_hook_start_multifd_mapped_ram_fdset,
|
||||||
|
.end_hook = migrate_hook_end_multifd_mapped_ram_fdset,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_file_common(&args, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_multifd_file_mapped_ram_fdset_dio(void)
|
||||||
|
{
|
||||||
|
g_autofree char *uri = g_strdup_printf("file:/dev/fdset/1,offset=%d",
|
||||||
|
FILE_TEST_OFFSET);
|
||||||
|
MigrateCommon args = {
|
||||||
|
.connect_uri = uri,
|
||||||
|
.listen_uri = "defer",
|
||||||
|
.start_hook = migrate_hook_start_multifd_mapped_ram_fdset_dio,
|
||||||
|
.end_hook = migrate_hook_end_multifd_mapped_ram_fdset,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!probe_o_direct_support(tmpfs)) {
|
||||||
|
g_test_skip("Filesystem does not support O_DIRECT");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
test_file_common(&args, true);
|
||||||
|
}
|
||||||
|
#endif /* !_WIN32 */
|
||||||
|
|
||||||
|
void migration_test_add_file(MigrationTestEnv *env)
|
||||||
|
{
|
||||||
|
tmpfs = env->tmpfs;
|
||||||
|
|
||||||
|
migration_test_add("/migration/precopy/file",
|
||||||
|
test_precopy_file);
|
||||||
|
|
||||||
|
migration_test_add("/migration/precopy/file/offset",
|
||||||
|
test_precopy_file_offset);
|
||||||
|
#ifndef _WIN32
|
||||||
|
migration_test_add("/migration/precopy/file/offset/fdset",
|
||||||
|
test_precopy_file_offset_fdset);
|
||||||
|
#endif
|
||||||
|
migration_test_add("/migration/precopy/file/offset/bad",
|
||||||
|
test_precopy_file_offset_bad);
|
||||||
|
|
||||||
|
migration_test_add("/migration/precopy/file/mapped-ram",
|
||||||
|
test_precopy_file_mapped_ram);
|
||||||
|
migration_test_add("/migration/precopy/file/mapped-ram/live",
|
||||||
|
test_precopy_file_mapped_ram_live);
|
||||||
|
|
||||||
|
migration_test_add("/migration/multifd/file/mapped-ram",
|
||||||
|
test_multifd_file_mapped_ram);
|
||||||
|
migration_test_add("/migration/multifd/file/mapped-ram/live",
|
||||||
|
test_multifd_file_mapped_ram_live);
|
||||||
|
|
||||||
|
migration_test_add("/migration/multifd/file/mapped-ram/dio",
|
||||||
|
test_multifd_file_mapped_ram_dio);
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
migration_test_add("/migration/multifd/file/mapped-ram/fdset",
|
||||||
|
test_multifd_file_mapped_ram_fdset);
|
||||||
|
migration_test_add("/migration/multifd/file/mapped-ram/fdset/dio",
|
||||||
|
test_multifd_file_mapped_ram_fdset_dio);
|
||||||
|
#endif
|
||||||
|
}
|
971
tests/qtest/migration/framework.c
Normal file
971
tests/qtest/migration/framework.c
Normal file
|
@ -0,0 +1,971 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2016-2018 Red Hat, Inc. and/or its affiliates
|
||||||
|
* based on the vhost-user-test.c that is:
|
||||||
|
* Copyright (c) 2014 Virtual Open Systems Sarl.
|
||||||
|
*
|
||||||
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||||
|
* See the COPYING file in the top-level directory.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "qemu/osdep.h"
|
||||||
|
|
||||||
|
#include "chardev/char.h"
|
||||||
|
#include "crypto/tlscredspsk.h"
|
||||||
|
#include "libqtest.h"
|
||||||
|
#include "migration/bootfile.h"
|
||||||
|
#include "migration/framework.h"
|
||||||
|
#include "migration/migration-qmp.h"
|
||||||
|
#include "migration/migration-util.h"
|
||||||
|
#include "ppc-util.h"
|
||||||
|
#include "qapi/qmp/qlist.h"
|
||||||
|
#include "qemu/module.h"
|
||||||
|
#include "qemu/option.h"
|
||||||
|
#include "qemu/range.h"
|
||||||
|
#include "qemu/sockets.h"
|
||||||
|
|
||||||
|
|
||||||
|
#define QEMU_VM_FILE_MAGIC 0x5145564d
|
||||||
|
#define QEMU_ENV_SRC "QTEST_QEMU_BINARY_SRC"
|
||||||
|
#define QEMU_ENV_DST "QTEST_QEMU_BINARY_DST"
|
||||||
|
|
||||||
|
unsigned start_address;
|
||||||
|
unsigned end_address;
|
||||||
|
static QTestMigrationState src_state;
|
||||||
|
static QTestMigrationState dst_state;
|
||||||
|
static char *tmpfs;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* An initial 3 MB offset is used as that corresponds
|
||||||
|
* to ~1 sec of data transfer with our bandwidth setting.
|
||||||
|
*/
|
||||||
|
#define MAGIC_OFFSET_BASE (3 * 1024 * 1024)
|
||||||
|
/*
|
||||||
|
* A further 1k is added to ensure we're not a multiple
|
||||||
|
* of TEST_MEM_PAGE_SIZE, thus avoid clash with writes
|
||||||
|
* from the migration guest workload.
|
||||||
|
*/
|
||||||
|
#define MAGIC_OFFSET_SHUFFLE 1024
|
||||||
|
#define MAGIC_OFFSET (MAGIC_OFFSET_BASE + MAGIC_OFFSET_SHUFFLE)
|
||||||
|
#define MAGIC_MARKER 0xFEED12345678CAFEULL
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wait for some output in the serial output file,
|
||||||
|
* we get an 'A' followed by an endless string of 'B's
|
||||||
|
* but on the destination we won't have the A (unless we enabled suspend/resume)
|
||||||
|
*/
|
||||||
|
void wait_for_serial(const char *side)
|
||||||
|
{
|
||||||
|
g_autofree char *serialpath = g_strdup_printf("%s/%s", tmpfs, side);
|
||||||
|
FILE *serialfile = fopen(serialpath, "r");
|
||||||
|
|
||||||
|
do {
|
||||||
|
int readvalue = fgetc(serialfile);
|
||||||
|
|
||||||
|
switch (readvalue) {
|
||||||
|
case 'A':
|
||||||
|
/* Fine */
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'B':
|
||||||
|
/* It's alive! */
|
||||||
|
fclose(serialfile);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case EOF:
|
||||||
|
fseek(serialfile, 0, SEEK_SET);
|
||||||
|
usleep(1000);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "Unexpected %d on %s serial\n", readvalue, side);
|
||||||
|
g_assert_not_reached();
|
||||||
|
}
|
||||||
|
} while (true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void migrate_prepare_for_dirty_mem(QTestState *from)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* The guest workflow iterates from start_address to
|
||||||
|
* end_address, writing 1 byte every TEST_MEM_PAGE_SIZE
|
||||||
|
* bytes.
|
||||||
|
*
|
||||||
|
* IOW, if we write to mem at a point which is NOT
|
||||||
|
* a multiple of TEST_MEM_PAGE_SIZE, our write won't
|
||||||
|
* conflict with the migration workflow.
|
||||||
|
*
|
||||||
|
* We put in a marker here, that we'll use to determine
|
||||||
|
* when the data has been transferred to the dst.
|
||||||
|
*/
|
||||||
|
qtest_writeq(from, start_address + MAGIC_OFFSET, MAGIC_MARKER);
|
||||||
|
}
|
||||||
|
|
||||||
|
void migrate_wait_for_dirty_mem(QTestState *from, QTestState *to)
|
||||||
|
{
|
||||||
|
uint64_t watch_address = start_address + MAGIC_OFFSET_BASE;
|
||||||
|
uint64_t marker_address = start_address + MAGIC_OFFSET;
|
||||||
|
uint8_t watch_byte;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wait for the MAGIC_MARKER to get transferred, as an
|
||||||
|
* indicator that a migration pass has made some known
|
||||||
|
* amount of progress.
|
||||||
|
*/
|
||||||
|
do {
|
||||||
|
usleep(1000 * 10);
|
||||||
|
} while (qtest_readq(to, marker_address) != MAGIC_MARKER);
|
||||||
|
|
||||||
|
|
||||||
|
/* If suspended, src only iterates once, and watch_byte may never change */
|
||||||
|
if (src_state.suspend_me) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now ensure that already transferred bytes are
|
||||||
|
* dirty again from the guest workload. Note the
|
||||||
|
* guest byte value will wrap around and by chance
|
||||||
|
* match the original watch_byte. This is harmless
|
||||||
|
* as we'll eventually see a different value if we
|
||||||
|
* keep watching
|
||||||
|
*/
|
||||||
|
watch_byte = qtest_readb(from, watch_address);
|
||||||
|
do {
|
||||||
|
usleep(1000 * 10);
|
||||||
|
} while (qtest_readb(from, watch_address) == watch_byte);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void check_guests_ram(QTestState *who)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Our ASM test will have been incrementing one byte from each page from
|
||||||
|
* start_address to < end_address in order. This gives us a constraint
|
||||||
|
* that any page's byte should be equal or less than the previous pages
|
||||||
|
* byte (mod 256); and they should all be equal except for one transition
|
||||||
|
* at the point where we meet the incrementer. (We're running this with
|
||||||
|
* the guest stopped).
|
||||||
|
*/
|
||||||
|
unsigned address;
|
||||||
|
uint8_t first_byte;
|
||||||
|
uint8_t last_byte;
|
||||||
|
bool hit_edge = false;
|
||||||
|
int bad = 0;
|
||||||
|
|
||||||
|
qtest_memread(who, start_address, &first_byte, 1);
|
||||||
|
last_byte = first_byte;
|
||||||
|
|
||||||
|
for (address = start_address + TEST_MEM_PAGE_SIZE; address < end_address;
|
||||||
|
address += TEST_MEM_PAGE_SIZE)
|
||||||
|
{
|
||||||
|
uint8_t b;
|
||||||
|
qtest_memread(who, address, &b, 1);
|
||||||
|
if (b != last_byte) {
|
||||||
|
if (((b + 1) % 256) == last_byte && !hit_edge) {
|
||||||
|
/*
|
||||||
|
* This is OK, the guest stopped at the point of
|
||||||
|
* incrementing the previous page but didn't get
|
||||||
|
* to us yet.
|
||||||
|
*/
|
||||||
|
hit_edge = true;
|
||||||
|
last_byte = b;
|
||||||
|
} else {
|
||||||
|
bad++;
|
||||||
|
if (bad <= 10) {
|
||||||
|
fprintf(stderr, "Memory content inconsistency at %x"
|
||||||
|
" first_byte = %x last_byte = %x current = %x"
|
||||||
|
" hit_edge = %x\n",
|
||||||
|
address, first_byte, last_byte, b, hit_edge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bad >= 10) {
|
||||||
|
fprintf(stderr, "and in another %d pages", bad - 10);
|
||||||
|
}
|
||||||
|
g_assert(bad == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cleanup(const char *filename)
|
||||||
|
{
|
||||||
|
g_autofree char *path = g_strdup_printf("%s/%s", tmpfs, filename);
|
||||||
|
|
||||||
|
unlink(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
int migrate_start(QTestState **from, QTestState **to, const char *uri,
|
||||||
|
MigrateStart *args)
|
||||||
|
{
|
||||||
|
g_autofree gchar *arch_source = NULL;
|
||||||
|
g_autofree gchar *arch_target = NULL;
|
||||||
|
/* options for source and target */
|
||||||
|
g_autofree gchar *arch_opts = NULL;
|
||||||
|
g_autofree gchar *cmd_source = NULL;
|
||||||
|
g_autofree gchar *cmd_target = NULL;
|
||||||
|
const gchar *ignore_stderr;
|
||||||
|
g_autofree char *shmem_opts = NULL;
|
||||||
|
g_autofree char *shmem_path = NULL;
|
||||||
|
const char *kvm_opts = NULL;
|
||||||
|
const char *arch = qtest_get_arch();
|
||||||
|
const char *memory_size;
|
||||||
|
const char *machine_alias, *machine_opts = "";
|
||||||
|
g_autofree char *machine = NULL;
|
||||||
|
const char *bootpath;
|
||||||
|
|
||||||
|
if (args->use_shmem) {
|
||||||
|
if (!g_file_test("/dev/shm", G_FILE_TEST_IS_DIR)) {
|
||||||
|
g_test_skip("/dev/shm is not supported");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dst_state = (QTestMigrationState) { };
|
||||||
|
src_state = (QTestMigrationState) { };
|
||||||
|
bootpath = bootfile_create(arch, tmpfs, args->suspend_me);
|
||||||
|
src_state.suspend_me = args->suspend_me;
|
||||||
|
|
||||||
|
if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) {
|
||||||
|
memory_size = "150M";
|
||||||
|
|
||||||
|
if (g_str_equal(arch, "i386")) {
|
||||||
|
machine_alias = "pc";
|
||||||
|
} else {
|
||||||
|
machine_alias = "q35";
|
||||||
|
}
|
||||||
|
arch_opts = g_strdup_printf(
|
||||||
|
"-drive if=none,id=d0,file=%s,format=raw "
|
||||||
|
"-device ide-hd,drive=d0,secs=1,cyls=1,heads=1", bootpath);
|
||||||
|
start_address = X86_TEST_MEM_START;
|
||||||
|
end_address = X86_TEST_MEM_END;
|
||||||
|
} else if (g_str_equal(arch, "s390x")) {
|
||||||
|
memory_size = "128M";
|
||||||
|
machine_alias = "s390-ccw-virtio";
|
||||||
|
arch_opts = g_strdup_printf("-bios %s", bootpath);
|
||||||
|
start_address = S390_TEST_MEM_START;
|
||||||
|
end_address = S390_TEST_MEM_END;
|
||||||
|
} else if (strcmp(arch, "ppc64") == 0) {
|
||||||
|
memory_size = "256M";
|
||||||
|
start_address = PPC_TEST_MEM_START;
|
||||||
|
end_address = PPC_TEST_MEM_END;
|
||||||
|
machine_alias = "pseries";
|
||||||
|
machine_opts = "vsmt=8";
|
||||||
|
arch_opts = g_strdup_printf(
|
||||||
|
"-nodefaults -machine " PSERIES_DEFAULT_CAPABILITIES " "
|
||||||
|
"-bios %s", bootpath);
|
||||||
|
} else if (strcmp(arch, "aarch64") == 0) {
|
||||||
|
memory_size = "150M";
|
||||||
|
machine_alias = "virt";
|
||||||
|
machine_opts = "gic-version=3";
|
||||||
|
arch_opts = g_strdup_printf("-cpu max -kernel %s", bootpath);
|
||||||
|
start_address = ARM_TEST_MEM_START;
|
||||||
|
end_address = ARM_TEST_MEM_END;
|
||||||
|
} else {
|
||||||
|
g_assert_not_reached();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!getenv("QTEST_LOG") && args->hide_stderr) {
|
||||||
|
#ifndef _WIN32
|
||||||
|
ignore_stderr = "2>/dev/null";
|
||||||
|
#else
|
||||||
|
/*
|
||||||
|
* On Windows the QEMU executable is created via CreateProcess() and
|
||||||
|
* IO redirection does not work, so don't bother adding IO redirection
|
||||||
|
* to the command line.
|
||||||
|
*/
|
||||||
|
ignore_stderr = "";
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
ignore_stderr = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args->use_shmem) {
|
||||||
|
shmem_path = g_strdup_printf("/dev/shm/qemu-%d", getpid());
|
||||||
|
shmem_opts = g_strdup_printf(
|
||||||
|
"-object memory-backend-file,id=mem0,size=%s"
|
||||||
|
",mem-path=%s,share=on -numa node,memdev=mem0",
|
||||||
|
memory_size, shmem_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args->use_dirty_ring) {
|
||||||
|
kvm_opts = ",dirty-ring-size=4096";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!qtest_has_machine(machine_alias)) {
|
||||||
|
g_autofree char *msg = g_strdup_printf("machine %s not supported", machine_alias);
|
||||||
|
g_test_skip(msg);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
machine = resolve_machine_version(machine_alias, QEMU_ENV_SRC,
|
||||||
|
QEMU_ENV_DST);
|
||||||
|
|
||||||
|
g_test_message("Using machine type: %s", machine);
|
||||||
|
|
||||||
|
cmd_source = g_strdup_printf("-accel kvm%s -accel tcg "
|
||||||
|
"-machine %s,%s "
|
||||||
|
"-name source,debug-threads=on "
|
||||||
|
"-m %s "
|
||||||
|
"-serial file:%s/src_serial "
|
||||||
|
"%s %s %s %s %s",
|
||||||
|
kvm_opts ? kvm_opts : "",
|
||||||
|
machine, machine_opts,
|
||||||
|
memory_size, tmpfs,
|
||||||
|
arch_opts ? arch_opts : "",
|
||||||
|
arch_source ? arch_source : "",
|
||||||
|
shmem_opts ? shmem_opts : "",
|
||||||
|
args->opts_source ? args->opts_source : "",
|
||||||
|
ignore_stderr);
|
||||||
|
if (!args->only_target) {
|
||||||
|
*from = qtest_init_with_env(QEMU_ENV_SRC, cmd_source);
|
||||||
|
qtest_qmp_set_event_callback(*from,
|
||||||
|
migrate_watch_for_events,
|
||||||
|
&src_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_target = g_strdup_printf("-accel kvm%s -accel tcg "
|
||||||
|
"-machine %s,%s "
|
||||||
|
"-name target,debug-threads=on "
|
||||||
|
"-m %s "
|
||||||
|
"-serial file:%s/dest_serial "
|
||||||
|
"-incoming %s "
|
||||||
|
"%s %s %s %s %s",
|
||||||
|
kvm_opts ? kvm_opts : "",
|
||||||
|
machine, machine_opts,
|
||||||
|
memory_size, tmpfs, uri,
|
||||||
|
arch_opts ? arch_opts : "",
|
||||||
|
arch_target ? arch_target : "",
|
||||||
|
shmem_opts ? shmem_opts : "",
|
||||||
|
args->opts_target ? args->opts_target : "",
|
||||||
|
ignore_stderr);
|
||||||
|
*to = qtest_init_with_env(QEMU_ENV_DST, cmd_target);
|
||||||
|
qtest_qmp_set_event_callback(*to,
|
||||||
|
migrate_watch_for_events,
|
||||||
|
&dst_state);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove shmem file immediately to avoid memory leak in test failed case.
|
||||||
|
* It's valid because QEMU has already opened this file
|
||||||
|
*/
|
||||||
|
if (args->use_shmem) {
|
||||||
|
unlink(shmem_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Always enable migration events. Libvirt always uses it, let's try
|
||||||
|
* to mimic as closer as that.
|
||||||
|
*/
|
||||||
|
migrate_set_capability(*from, "events", true);
|
||||||
|
migrate_set_capability(*to, "events", true);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void migrate_end(QTestState *from, QTestState *to, bool test_dest)
|
||||||
|
{
|
||||||
|
unsigned char dest_byte_a, dest_byte_b, dest_byte_c, dest_byte_d;
|
||||||
|
|
||||||
|
qtest_quit(from);
|
||||||
|
|
||||||
|
if (test_dest) {
|
||||||
|
qtest_memread(to, start_address, &dest_byte_a, 1);
|
||||||
|
|
||||||
|
/* Destination still running, wait for a byte to change */
|
||||||
|
do {
|
||||||
|
qtest_memread(to, start_address, &dest_byte_b, 1);
|
||||||
|
usleep(1000 * 10);
|
||||||
|
} while (dest_byte_a == dest_byte_b);
|
||||||
|
|
||||||
|
qtest_qmp_assert_success(to, "{ 'execute' : 'stop'}");
|
||||||
|
|
||||||
|
/* With it stopped, check nothing changes */
|
||||||
|
qtest_memread(to, start_address, &dest_byte_c, 1);
|
||||||
|
usleep(1000 * 200);
|
||||||
|
qtest_memread(to, start_address, &dest_byte_d, 1);
|
||||||
|
g_assert_cmpint(dest_byte_c, ==, dest_byte_d);
|
||||||
|
|
||||||
|
check_guests_ram(to);
|
||||||
|
}
|
||||||
|
|
||||||
|
qtest_quit(to);
|
||||||
|
|
||||||
|
cleanup("migsocket");
|
||||||
|
cleanup("src_serial");
|
||||||
|
cleanup("dest_serial");
|
||||||
|
cleanup(FILE_TEST_FILENAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int migrate_postcopy_prepare(QTestState **from_ptr,
|
||||||
|
QTestState **to_ptr,
|
||||||
|
MigrateCommon *args)
|
||||||
|
{
|
||||||
|
QTestState *from, *to;
|
||||||
|
|
||||||
|
if (migrate_start(&from, &to, "defer", &args->start)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args->start_hook) {
|
||||||
|
args->postcopy_data = args->start_hook(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
migrate_set_capability(from, "postcopy-ram", true);
|
||||||
|
migrate_set_capability(to, "postcopy-ram", true);
|
||||||
|
migrate_set_capability(to, "postcopy-blocktime", true);
|
||||||
|
|
||||||
|
if (args->postcopy_preempt) {
|
||||||
|
migrate_set_capability(from, "postcopy-preempt", true);
|
||||||
|
migrate_set_capability(to, "postcopy-preempt", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
migrate_ensure_non_converge(from);
|
||||||
|
|
||||||
|
migrate_prepare_for_dirty_mem(from);
|
||||||
|
qtest_qmp_assert_success(to, "{ 'execute': 'migrate-incoming',"
|
||||||
|
" 'arguments': { "
|
||||||
|
" 'channels': [ { 'channel-type': 'main',"
|
||||||
|
" 'addr': { 'transport': 'socket',"
|
||||||
|
" 'type': 'inet',"
|
||||||
|
" 'host': '127.0.0.1',"
|
||||||
|
" 'port': '0' } } ] } }");
|
||||||
|
|
||||||
|
/* Wait for the first serial output from the source */
|
||||||
|
wait_for_serial("src_serial");
|
||||||
|
wait_for_suspend(from, &src_state);
|
||||||
|
|
||||||
|
migrate_qmp(from, to, NULL, NULL, "{}");
|
||||||
|
|
||||||
|
migrate_wait_for_dirty_mem(from, to);
|
||||||
|
|
||||||
|
*from_ptr = from;
|
||||||
|
*to_ptr = to;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void migrate_postcopy_complete(QTestState *from, QTestState *to,
|
||||||
|
MigrateCommon *args)
|
||||||
|
{
|
||||||
|
MigrationTestEnv *env = migration_get_env();
|
||||||
|
|
||||||
|
wait_for_migration_complete(from);
|
||||||
|
|
||||||
|
if (args->start.suspend_me) {
|
||||||
|
/* wakeup succeeds only if guest is suspended */
|
||||||
|
qtest_qmp_assert_success(to, "{'execute': 'system_wakeup'}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make sure we get at least one "B" on destination */
|
||||||
|
wait_for_serial("dest_serial");
|
||||||
|
|
||||||
|
if (env->uffd_feature_thread_id) {
|
||||||
|
read_blocktime(to);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args->end_hook) {
|
||||||
|
args->end_hook(from, to, args->postcopy_data);
|
||||||
|
args->postcopy_data = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
migrate_end(from, to, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_postcopy_common(MigrateCommon *args)
|
||||||
|
{
|
||||||
|
QTestState *from, *to;
|
||||||
|
|
||||||
|
if (migrate_postcopy_prepare(&from, &to, args)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
migrate_postcopy_start(from, to, &src_state);
|
||||||
|
migrate_postcopy_complete(from, to, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void wait_for_postcopy_status(QTestState *one, const char *status)
|
||||||
|
{
|
||||||
|
wait_for_migration_status(one, status,
|
||||||
|
(const char * []) {
|
||||||
|
"failed", "active",
|
||||||
|
"completed", NULL
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static void postcopy_recover_fail(QTestState *from, QTestState *to,
|
||||||
|
PostcopyRecoveryFailStage stage)
|
||||||
|
{
|
||||||
|
#ifndef _WIN32
|
||||||
|
bool fail_early = (stage == POSTCOPY_FAIL_CHANNEL_ESTABLISH);
|
||||||
|
int ret, pair1[2], pair2[2];
|
||||||
|
char c;
|
||||||
|
|
||||||
|
g_assert(stage > POSTCOPY_FAIL_NONE && stage < POSTCOPY_FAIL_MAX);
|
||||||
|
|
||||||
|
/* Create two unrelated socketpairs */
|
||||||
|
ret = qemu_socketpair(PF_LOCAL, SOCK_STREAM, 0, pair1);
|
||||||
|
g_assert_cmpint(ret, ==, 0);
|
||||||
|
|
||||||
|
ret = qemu_socketpair(PF_LOCAL, SOCK_STREAM, 0, pair2);
|
||||||
|
g_assert_cmpint(ret, ==, 0);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Give the guests unpaired ends of the sockets, so they'll all blocked
|
||||||
|
* at reading. This mimics a wrong channel established.
|
||||||
|
*/
|
||||||
|
qtest_qmp_fds_assert_success(from, &pair1[0], 1,
|
||||||
|
"{ 'execute': 'getfd',"
|
||||||
|
" 'arguments': { 'fdname': 'fd-mig' }}");
|
||||||
|
qtest_qmp_fds_assert_success(to, &pair2[0], 1,
|
||||||
|
"{ 'execute': 'getfd',"
|
||||||
|
" 'arguments': { 'fdname': 'fd-mig' }}");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write the 1st byte as QEMU_VM_COMMAND (0x8) for the dest socket, to
|
||||||
|
* emulate the 1st byte of a real recovery, but stops from there to
|
||||||
|
* keep dest QEMU in RECOVER. This is needed so that we can kick off
|
||||||
|
* the recover process on dest QEMU (by triggering the G_IO_IN event).
|
||||||
|
*
|
||||||
|
* NOTE: this trick is not needed on src QEMUs, because src doesn't
|
||||||
|
* rely on an pre-existing G_IO_IN event, so it will always trigger the
|
||||||
|
* upcoming recovery anyway even if it can read nothing.
|
||||||
|
*/
|
||||||
|
#define QEMU_VM_COMMAND 0x08
|
||||||
|
c = QEMU_VM_COMMAND;
|
||||||
|
ret = send(pair2[1], &c, 1, 0);
|
||||||
|
g_assert_cmpint(ret, ==, 1);
|
||||||
|
|
||||||
|
if (stage == POSTCOPY_FAIL_CHANNEL_ESTABLISH) {
|
||||||
|
/*
|
||||||
|
* This will make src QEMU to fail at an early stage when trying to
|
||||||
|
* resume later, where it shouldn't reach RECOVER stage at all.
|
||||||
|
*/
|
||||||
|
close(pair1[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
migrate_recover(to, "fd:fd-mig");
|
||||||
|
migrate_qmp(from, to, "fd:fd-mig", NULL, "{'resume': true}");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Source QEMU has an extra RECOVER_SETUP phase, dest doesn't have it.
|
||||||
|
* Make sure it appears along the way.
|
||||||
|
*/
|
||||||
|
migration_event_wait(from, "postcopy-recover-setup");
|
||||||
|
|
||||||
|
if (fail_early) {
|
||||||
|
/*
|
||||||
|
* When fails at reconnection, src QEMU will automatically goes
|
||||||
|
* back to PAUSED state. Making sure there is an event in this
|
||||||
|
* case: Libvirt relies on this to detect early reconnection
|
||||||
|
* errors.
|
||||||
|
*/
|
||||||
|
migration_event_wait(from, "postcopy-paused");
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* We want to test "fail later" at RECOVER stage here. Make sure
|
||||||
|
* both QEMU instances will go into RECOVER stage first, then test
|
||||||
|
* kicking them out using migrate-pause.
|
||||||
|
*
|
||||||
|
* Explicitly check the RECOVER event on src, that's what Libvirt
|
||||||
|
* relies on, rather than polling.
|
||||||
|
*/
|
||||||
|
migration_event_wait(from, "postcopy-recover");
|
||||||
|
wait_for_postcopy_status(from, "postcopy-recover");
|
||||||
|
|
||||||
|
/* Need an explicit kick on src QEMU in this case */
|
||||||
|
migrate_pause(from);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For all failure cases, we'll reach such states on both sides now.
|
||||||
|
* Check them.
|
||||||
|
*/
|
||||||
|
wait_for_postcopy_status(from, "postcopy-paused");
|
||||||
|
wait_for_postcopy_status(to, "postcopy-recover");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Kick dest QEMU out too. This is normally not needed in reality
|
||||||
|
* because when the channel is shutdown it should also happen on src.
|
||||||
|
* However here we used separate socket pairs so we need to do that
|
||||||
|
* explicitly.
|
||||||
|
*/
|
||||||
|
migrate_pause(to);
|
||||||
|
wait_for_postcopy_status(to, "postcopy-paused");
|
||||||
|
|
||||||
|
close(pair1[0]);
|
||||||
|
close(pair2[0]);
|
||||||
|
close(pair2[1]);
|
||||||
|
|
||||||
|
if (stage != POSTCOPY_FAIL_CHANNEL_ESTABLISH) {
|
||||||
|
close(pair1[1]);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_postcopy_recovery_common(MigrateCommon *args)
|
||||||
|
{
|
||||||
|
QTestState *from, *to;
|
||||||
|
g_autofree char *uri = NULL;
|
||||||
|
|
||||||
|
/* Always hide errors for postcopy recover tests since they're expected */
|
||||||
|
args->start.hide_stderr = true;
|
||||||
|
|
||||||
|
if (migrate_postcopy_prepare(&from, &to, args)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Turn postcopy speed down, 4K/s is slow enough on any machines */
|
||||||
|
migrate_set_parameter_int(from, "max-postcopy-bandwidth", 4096);
|
||||||
|
|
||||||
|
/* Now we start the postcopy */
|
||||||
|
migrate_postcopy_start(from, to, &src_state);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wait until postcopy is really started; we can only run the
|
||||||
|
* migrate-pause command during a postcopy
|
||||||
|
*/
|
||||||
|
wait_for_migration_status(from, "postcopy-active", NULL);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Manually stop the postcopy migration. This emulates a network
|
||||||
|
* failure with the migration socket
|
||||||
|
*/
|
||||||
|
migrate_pause(from);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wait for destination side to reach postcopy-paused state. The
|
||||||
|
* migrate-recover command can only succeed if destination machine
|
||||||
|
* is in the paused state
|
||||||
|
*/
|
||||||
|
wait_for_postcopy_status(to, "postcopy-paused");
|
||||||
|
wait_for_postcopy_status(from, "postcopy-paused");
|
||||||
|
|
||||||
|
if (args->postcopy_recovery_fail_stage) {
|
||||||
|
/*
|
||||||
|
* Test when a wrong socket specified for recover, and then the
|
||||||
|
* ability to kick it out, and continue with a correct socket.
|
||||||
|
*/
|
||||||
|
postcopy_recover_fail(from, to, args->postcopy_recovery_fail_stage);
|
||||||
|
/* continue with a good recovery */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a new socket to emulate a new channel that is different
|
||||||
|
* from the broken migration channel; tell the destination to
|
||||||
|
* listen to the new port
|
||||||
|
*/
|
||||||
|
uri = g_strdup_printf("unix:%s/migsocket-recover", tmpfs);
|
||||||
|
migrate_recover(to, uri);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Try to rebuild the migration channel using the resume flag and
|
||||||
|
* the newly created channel
|
||||||
|
*/
|
||||||
|
migrate_qmp(from, to, uri, NULL, "{'resume': true}");
|
||||||
|
|
||||||
|
/* Restore the postcopy bandwidth to unlimited */
|
||||||
|
migrate_set_parameter_int(from, "max-postcopy-bandwidth", 0);
|
||||||
|
|
||||||
|
migrate_postcopy_complete(from, to, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_precopy_common(MigrateCommon *args)
|
||||||
|
{
|
||||||
|
QTestState *from, *to;
|
||||||
|
void *data_hook = NULL;
|
||||||
|
|
||||||
|
if (migrate_start(&from, &to, args->listen_uri, &args->start)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args->start_hook) {
|
||||||
|
data_hook = args->start_hook(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wait for the first serial output from the source */
|
||||||
|
if (args->result == MIG_TEST_SUCCEED) {
|
||||||
|
wait_for_serial("src_serial");
|
||||||
|
wait_for_suspend(from, &src_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args->live) {
|
||||||
|
migrate_ensure_non_converge(from);
|
||||||
|
migrate_prepare_for_dirty_mem(from);
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* Testing non-live migration, we allow it to run at
|
||||||
|
* full speed to ensure short test case duration.
|
||||||
|
* For tests expected to fail, we don't need to
|
||||||
|
* change anything.
|
||||||
|
*/
|
||||||
|
if (args->result == MIG_TEST_SUCCEED) {
|
||||||
|
qtest_qmp_assert_success(from, "{ 'execute' : 'stop'}");
|
||||||
|
wait_for_stop(from, &src_state);
|
||||||
|
migrate_ensure_converge(from);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args->result == MIG_TEST_QMP_ERROR) {
|
||||||
|
migrate_qmp_fail(from, args->connect_uri, args->connect_channels, "{}");
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
|
||||||
|
migrate_qmp(from, to, args->connect_uri, args->connect_channels, "{}");
|
||||||
|
|
||||||
|
if (args->result != MIG_TEST_SUCCEED) {
|
||||||
|
bool allow_active = args->result == MIG_TEST_FAIL;
|
||||||
|
wait_for_migration_fail(from, allow_active);
|
||||||
|
|
||||||
|
if (args->result == MIG_TEST_FAIL_DEST_QUIT_ERR) {
|
||||||
|
qtest_set_expected_status(to, EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (args->live) {
|
||||||
|
/*
|
||||||
|
* For initial iteration(s) we must do a full pass,
|
||||||
|
* but for the final iteration, we need only wait
|
||||||
|
* for some dirty mem before switching to converge
|
||||||
|
*/
|
||||||
|
while (args->iterations > 1) {
|
||||||
|
wait_for_migration_pass(from, &src_state);
|
||||||
|
args->iterations--;
|
||||||
|
}
|
||||||
|
migrate_wait_for_dirty_mem(from, to);
|
||||||
|
|
||||||
|
migrate_ensure_converge(from);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We do this first, as it has a timeout to stop us
|
||||||
|
* hanging forever if migration didn't converge
|
||||||
|
*/
|
||||||
|
wait_for_migration_complete(from);
|
||||||
|
|
||||||
|
wait_for_stop(from, &src_state);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
wait_for_migration_complete(from);
|
||||||
|
/*
|
||||||
|
* Must wait for dst to finish reading all incoming
|
||||||
|
* data on the socket before issuing 'cont' otherwise
|
||||||
|
* it'll be ignored
|
||||||
|
*/
|
||||||
|
wait_for_migration_complete(to);
|
||||||
|
|
||||||
|
qtest_qmp_assert_success(to, "{ 'execute' : 'cont'}");
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_resume(to, &dst_state);
|
||||||
|
|
||||||
|
if (args->start.suspend_me) {
|
||||||
|
/* wakeup succeeds only if guest is suspended */
|
||||||
|
qtest_qmp_assert_success(to, "{'execute': 'system_wakeup'}");
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_serial("dest_serial");
|
||||||
|
}
|
||||||
|
|
||||||
|
finish:
|
||||||
|
if (args->end_hook) {
|
||||||
|
args->end_hook(from, to, data_hook);
|
||||||
|
}
|
||||||
|
|
||||||
|
migrate_end(from, to, args->result == MIG_TEST_SUCCEED);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void file_dirty_offset_region(void)
|
||||||
|
{
|
||||||
|
g_autofree char *path = g_strdup_printf("%s/%s", tmpfs, FILE_TEST_FILENAME);
|
||||||
|
size_t size = FILE_TEST_OFFSET;
|
||||||
|
g_autofree char *data = g_new0(char, size);
|
||||||
|
|
||||||
|
memset(data, FILE_TEST_MARKER, size);
|
||||||
|
g_assert(g_file_set_contents(path, data, size, NULL));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void file_check_offset_region(void)
|
||||||
|
{
|
||||||
|
g_autofree char *path = g_strdup_printf("%s/%s", tmpfs, FILE_TEST_FILENAME);
|
||||||
|
size_t size = FILE_TEST_OFFSET;
|
||||||
|
g_autofree char *expected = g_new0(char, size);
|
||||||
|
g_autofree char *actual = NULL;
|
||||||
|
uint64_t *stream_start;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ensure the skipped offset region's data has not been touched
|
||||||
|
* and the migration stream starts at the right place.
|
||||||
|
*/
|
||||||
|
|
||||||
|
memset(expected, FILE_TEST_MARKER, size);
|
||||||
|
|
||||||
|
g_assert(g_file_get_contents(path, &actual, NULL, NULL));
|
||||||
|
g_assert(!memcmp(actual, expected, size));
|
||||||
|
|
||||||
|
stream_start = (uint64_t *)(actual + size);
|
||||||
|
g_assert_cmpint(cpu_to_be64(*stream_start) >> 32, ==, QEMU_VM_FILE_MAGIC);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_file_common(MigrateCommon *args, bool stop_src)
|
||||||
|
{
|
||||||
|
QTestState *from, *to;
|
||||||
|
void *data_hook = NULL;
|
||||||
|
bool check_offset = false;
|
||||||
|
|
||||||
|
if (migrate_start(&from, &to, args->listen_uri, &args->start)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File migration is never live. We can keep the source VM running
|
||||||
|
* during migration, but the destination will not be running
|
||||||
|
* concurrently.
|
||||||
|
*/
|
||||||
|
g_assert_false(args->live);
|
||||||
|
|
||||||
|
if (g_strrstr(args->connect_uri, "offset=")) {
|
||||||
|
check_offset = true;
|
||||||
|
/*
|
||||||
|
* This comes before the start_hook because it's equivalent to
|
||||||
|
* a management application creating the file and writing to
|
||||||
|
* it so hooks should expect the file to be already present.
|
||||||
|
*/
|
||||||
|
file_dirty_offset_region();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args->start_hook) {
|
||||||
|
data_hook = args->start_hook(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
migrate_ensure_converge(from);
|
||||||
|
wait_for_serial("src_serial");
|
||||||
|
|
||||||
|
if (stop_src) {
|
||||||
|
qtest_qmp_assert_success(from, "{ 'execute' : 'stop'}");
|
||||||
|
wait_for_stop(from, &src_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args->result == MIG_TEST_QMP_ERROR) {
|
||||||
|
migrate_qmp_fail(from, args->connect_uri, NULL, "{}");
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
|
||||||
|
migrate_qmp(from, to, args->connect_uri, NULL, "{}");
|
||||||
|
wait_for_migration_complete(from);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We need to wait for the source to finish before starting the
|
||||||
|
* destination.
|
||||||
|
*/
|
||||||
|
migrate_incoming_qmp(to, args->connect_uri, "{}");
|
||||||
|
wait_for_migration_complete(to);
|
||||||
|
|
||||||
|
if (stop_src) {
|
||||||
|
qtest_qmp_assert_success(to, "{ 'execute' : 'cont'}");
|
||||||
|
}
|
||||||
|
wait_for_resume(to, &dst_state);
|
||||||
|
|
||||||
|
wait_for_serial("dest_serial");
|
||||||
|
|
||||||
|
if (check_offset) {
|
||||||
|
file_check_offset_region();
|
||||||
|
}
|
||||||
|
|
||||||
|
finish:
|
||||||
|
if (args->end_hook) {
|
||||||
|
args->end_hook(from, to, data_hook);
|
||||||
|
}
|
||||||
|
|
||||||
|
migrate_end(from, to, args->result == MIG_TEST_SUCCEED);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *migrate_hook_start_precopy_tcp_multifd_common(QTestState *from,
|
||||||
|
QTestState *to,
|
||||||
|
const char *method)
|
||||||
|
{
|
||||||
|
migrate_set_parameter_int(from, "multifd-channels", 16);
|
||||||
|
migrate_set_parameter_int(to, "multifd-channels", 16);
|
||||||
|
|
||||||
|
migrate_set_parameter_str(from, "multifd-compression", method);
|
||||||
|
migrate_set_parameter_str(to, "multifd-compression", method);
|
||||||
|
|
||||||
|
migrate_set_capability(from, "multifd", true);
|
||||||
|
migrate_set_capability(to, "multifd", true);
|
||||||
|
|
||||||
|
/* Start incoming migration from the 1st socket */
|
||||||
|
migrate_incoming_qmp(to, "tcp:127.0.0.1:0", "{}");
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTestMigrationState *get_src(void)
|
||||||
|
{
|
||||||
|
return &src_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
MigrationTestEnv *migration_get_env(void)
|
||||||
|
{
|
||||||
|
static MigrationTestEnv *env;
|
||||||
|
g_autoptr(GError) err = NULL;
|
||||||
|
|
||||||
|
if (env) {
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
|
||||||
|
env = g_new0(MigrationTestEnv, 1);
|
||||||
|
env->qemu_src = getenv(QEMU_ENV_SRC);
|
||||||
|
env->qemu_dst = getenv(QEMU_ENV_DST);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The default QTEST_QEMU_BINARY must always be provided because
|
||||||
|
* that is what helpers use to query the accel type and
|
||||||
|
* architecture.
|
||||||
|
*/
|
||||||
|
if (env->qemu_src && env->qemu_dst) {
|
||||||
|
g_test_message("Only one of %s, %s is allowed",
|
||||||
|
QEMU_ENV_SRC, QEMU_ENV_DST);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
env->has_kvm = qtest_has_accel("kvm");
|
||||||
|
env->has_tcg = qtest_has_accel("tcg");
|
||||||
|
|
||||||
|
if (!env->has_tcg && !env->has_kvm) {
|
||||||
|
g_test_skip("No KVM or TCG accelerator available");
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
|
||||||
|
env->has_dirty_ring = kvm_dirty_ring_supported();
|
||||||
|
env->has_uffd = ufd_version_check(&env->uffd_feature_thread_id);
|
||||||
|
env->arch = qtest_get_arch();
|
||||||
|
env->is_x86 = !strcmp(env->arch, "i386") || !strcmp(env->arch, "x86_64");
|
||||||
|
|
||||||
|
env->tmpfs = g_dir_make_tmp("migration-test-XXXXXX", &err);
|
||||||
|
if (!env->tmpfs) {
|
||||||
|
g_test_message("Can't create temporary directory in %s: %s",
|
||||||
|
g_get_tmp_dir(), err->message);
|
||||||
|
}
|
||||||
|
g_assert(env->tmpfs);
|
||||||
|
|
||||||
|
tmpfs = env->tmpfs;
|
||||||
|
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
|
||||||
|
int migration_env_clean(MigrationTestEnv *env)
|
||||||
|
{
|
||||||
|
char *tmpfs;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
if (!env) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bootfile_delete();
|
||||||
|
|
||||||
|
tmpfs = env->tmpfs;
|
||||||
|
ret = rmdir(tmpfs);
|
||||||
|
if (ret != 0) {
|
||||||
|
g_test_message("unable to rmdir: path (%s): %s",
|
||||||
|
tmpfs, strerror(errno));
|
||||||
|
}
|
||||||
|
g_free(tmpfs);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
230
tests/qtest/migration/framework.h
Normal file
230
tests/qtest/migration/framework.h
Normal file
|
@ -0,0 +1,230 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2016-2018 Red Hat, Inc. and/or its affiliates
|
||||||
|
* based on the vhost-user-test.c that is:
|
||||||
|
* Copyright (c) 2014 Virtual Open Systems Sarl.
|
||||||
|
*
|
||||||
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||||
|
* See the COPYING file in the top-level directory.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TEST_FRAMEWORK_H
|
||||||
|
#define TEST_FRAMEWORK_H
|
||||||
|
|
||||||
|
#include "libqtest.h"
|
||||||
|
|
||||||
|
#define FILE_TEST_FILENAME "migfile"
|
||||||
|
#define FILE_TEST_OFFSET 0x1000
|
||||||
|
#define FILE_TEST_MARKER 'X'
|
||||||
|
|
||||||
|
typedef struct MigrationTestEnv {
|
||||||
|
bool has_kvm;
|
||||||
|
bool has_tcg;
|
||||||
|
bool has_uffd;
|
||||||
|
bool uffd_feature_thread_id;
|
||||||
|
bool has_dirty_ring;
|
||||||
|
bool is_x86;
|
||||||
|
const char *arch;
|
||||||
|
const char *qemu_src;
|
||||||
|
const char *qemu_dst;
|
||||||
|
char *tmpfs;
|
||||||
|
} MigrationTestEnv;
|
||||||
|
|
||||||
|
MigrationTestEnv *migration_get_env(void);
|
||||||
|
int migration_env_clean(MigrationTestEnv *env);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A hook that runs after the src and dst QEMUs have been
|
||||||
|
* created, but before the migration is started. This can
|
||||||
|
* be used to set migration parameters and capabilities.
|
||||||
|
*
|
||||||
|
* Returns: NULL, or a pointer to opaque state to be
|
||||||
|
* later passed to the TestMigrateEndHook
|
||||||
|
*/
|
||||||
|
typedef void * (*TestMigrateStartHook)(QTestState *from,
|
||||||
|
QTestState *to);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A hook that runs after the migration has finished,
|
||||||
|
* regardless of whether it succeeded or failed, but
|
||||||
|
* before QEMU has terminated (unless it self-terminated
|
||||||
|
* due to migration error)
|
||||||
|
*
|
||||||
|
* @opaque is a pointer to state previously returned
|
||||||
|
* by the TestMigrateStartHook if any, or NULL.
|
||||||
|
*/
|
||||||
|
typedef void (*TestMigrateEndHook)(QTestState *from,
|
||||||
|
QTestState *to,
|
||||||
|
void *opaque);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Our goal is to ensure that we run a single full migration
|
||||||
|
* iteration, and also dirty memory, ensuring that at least
|
||||||
|
* one further iteration is required.
|
||||||
|
*
|
||||||
|
* We can't directly synchronize with the start of a migration
|
||||||
|
* so we have to apply some tricks monitoring memory that is
|
||||||
|
* transferred.
|
||||||
|
*
|
||||||
|
* Initially we set the migration bandwidth to an insanely
|
||||||
|
* low value, with tiny max downtime too. This basically
|
||||||
|
* guarantees migration will never complete.
|
||||||
|
*
|
||||||
|
* This will result in a test that is unacceptably slow though,
|
||||||
|
* so we can't let the entire migration pass run at this speed.
|
||||||
|
* Our intent is to let it run just long enough that we can
|
||||||
|
* prove data prior to the marker has been transferred *AND*
|
||||||
|
* also prove this transferred data is dirty again.
|
||||||
|
*
|
||||||
|
* Before migration starts, we write a 64-bit magic marker
|
||||||
|
* into a fixed location in the src VM RAM.
|
||||||
|
*
|
||||||
|
* Then watch dst memory until the marker appears. This is
|
||||||
|
* proof that start_address -> MAGIC_OFFSET_BASE has been
|
||||||
|
* transferred.
|
||||||
|
*
|
||||||
|
* Finally we go back to the source and read a byte just
|
||||||
|
* before the marker until we see it flip in value. This
|
||||||
|
* is proof that start_address -> MAGIC_OFFSET_BASE
|
||||||
|
* is now dirty again.
|
||||||
|
*
|
||||||
|
* IOW, we're guaranteed at least a 2nd migration pass
|
||||||
|
* at this point.
|
||||||
|
*
|
||||||
|
* We can now let migration run at full speed to finish
|
||||||
|
* the test
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
/*
|
||||||
|
* QTEST_LOG=1 may override this. When QTEST_LOG=1, we always dump errors
|
||||||
|
* unconditionally, because it means the user would like to be verbose.
|
||||||
|
*/
|
||||||
|
bool hide_stderr;
|
||||||
|
bool use_shmem;
|
||||||
|
/* only launch the target process */
|
||||||
|
bool only_target;
|
||||||
|
/* Use dirty ring if true; dirty logging otherwise */
|
||||||
|
bool use_dirty_ring;
|
||||||
|
const char *opts_source;
|
||||||
|
const char *opts_target;
|
||||||
|
/* suspend the src before migrating to dest. */
|
||||||
|
bool suspend_me;
|
||||||
|
} MigrateStart;
|
||||||
|
|
||||||
|
typedef enum PostcopyRecoveryFailStage {
|
||||||
|
/*
|
||||||
|
* "no failure" must be 0 as it's the default. OTOH, real failure
|
||||||
|
* cases must be >0 to make sure they trigger by a "if" test.
|
||||||
|
*/
|
||||||
|
POSTCOPY_FAIL_NONE = 0,
|
||||||
|
POSTCOPY_FAIL_CHANNEL_ESTABLISH,
|
||||||
|
POSTCOPY_FAIL_RECOVERY,
|
||||||
|
POSTCOPY_FAIL_MAX
|
||||||
|
} PostcopyRecoveryFailStage;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
/* Optional: fine tune start parameters */
|
||||||
|
MigrateStart start;
|
||||||
|
|
||||||
|
/* Required: the URI for the dst QEMU to listen on */
|
||||||
|
const char *listen_uri;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Optional: the URI for the src QEMU to connect to
|
||||||
|
* If NULL, then it will query the dst QEMU for its actual
|
||||||
|
* listening address and use that as the connect address.
|
||||||
|
* This allows for dynamically picking a free TCP port.
|
||||||
|
*/
|
||||||
|
const char *connect_uri;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Optional: JSON-formatted list of src QEMU URIs. If a port is
|
||||||
|
* defined as '0' in any QDict key a value of '0' will be
|
||||||
|
* automatically converted to the correct destination port.
|
||||||
|
*/
|
||||||
|
const char *connect_channels;
|
||||||
|
|
||||||
|
/* Optional: callback to run at start to set migration parameters */
|
||||||
|
TestMigrateStartHook start_hook;
|
||||||
|
/* Optional: callback to run at finish to cleanup */
|
||||||
|
TestMigrateEndHook end_hook;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Optional: normally we expect the migration process to complete.
|
||||||
|
*
|
||||||
|
* There can be a variety of reasons and stages in which failure
|
||||||
|
* can happen during tests.
|
||||||
|
*
|
||||||
|
* If a failure is expected to happen at time of establishing
|
||||||
|
* the connection, then MIG_TEST_FAIL will indicate that the dst
|
||||||
|
* QEMU is expected to stay running and accept future migration
|
||||||
|
* connections.
|
||||||
|
*
|
||||||
|
* If a failure is expected to happen while processing the
|
||||||
|
* migration stream, then MIG_TEST_FAIL_DEST_QUIT_ERR will indicate
|
||||||
|
* that the dst QEMU is expected to quit with non-zero exit status
|
||||||
|
*/
|
||||||
|
enum {
|
||||||
|
/* This test should succeed, the default */
|
||||||
|
MIG_TEST_SUCCEED = 0,
|
||||||
|
/* This test should fail, dest qemu should keep alive */
|
||||||
|
MIG_TEST_FAIL,
|
||||||
|
/* This test should fail, dest qemu should fail with abnormal status */
|
||||||
|
MIG_TEST_FAIL_DEST_QUIT_ERR,
|
||||||
|
/* The QMP command for this migration should fail with an error */
|
||||||
|
MIG_TEST_QMP_ERROR,
|
||||||
|
} result;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Optional: set number of migration passes to wait for, if live==true.
|
||||||
|
* If zero, then merely wait for a few MB of dirty data
|
||||||
|
*/
|
||||||
|
unsigned int iterations;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Optional: whether the guest CPUs should be running during a precopy
|
||||||
|
* migration test. We used to always run with live but it took much
|
||||||
|
* longer so we reduced live tests to only the ones that have solid
|
||||||
|
* reason to be tested live-only. For each of the new test cases for
|
||||||
|
* precopy please provide justifications to use live explicitly (please
|
||||||
|
* refer to existing ones with live=true), or use live=off by default.
|
||||||
|
*/
|
||||||
|
bool live;
|
||||||
|
|
||||||
|
/* Postcopy specific fields */
|
||||||
|
void *postcopy_data;
|
||||||
|
bool postcopy_preempt;
|
||||||
|
PostcopyRecoveryFailStage postcopy_recovery_fail_stage;
|
||||||
|
} MigrateCommon;
|
||||||
|
|
||||||
|
void wait_for_serial(const char *side);
|
||||||
|
void migrate_prepare_for_dirty_mem(QTestState *from);
|
||||||
|
void migrate_wait_for_dirty_mem(QTestState *from, QTestState *to);
|
||||||
|
int migrate_start(QTestState **from, QTestState **to, const char *uri,
|
||||||
|
MigrateStart *args);
|
||||||
|
void migrate_end(QTestState *from, QTestState *to, bool test_dest);
|
||||||
|
|
||||||
|
void test_postcopy_common(MigrateCommon *args);
|
||||||
|
void test_postcopy_recovery_common(MigrateCommon *args);
|
||||||
|
void test_precopy_common(MigrateCommon *args);
|
||||||
|
void test_file_common(MigrateCommon *args, bool stop_src);
|
||||||
|
void *migrate_hook_start_precopy_tcp_multifd_common(QTestState *from,
|
||||||
|
QTestState *to,
|
||||||
|
const char *method);
|
||||||
|
|
||||||
|
typedef struct QTestMigrationState QTestMigrationState;
|
||||||
|
QTestMigrationState *get_src(void);
|
||||||
|
|
||||||
|
#ifdef CONFIG_GNUTLS
|
||||||
|
void migration_test_add_tls(MigrationTestEnv *env);
|
||||||
|
#else
|
||||||
|
static inline void migration_test_add_tls(MigrationTestEnv *env) {};
|
||||||
|
#endif
|
||||||
|
void migration_test_add_compression(MigrationTestEnv *env);
|
||||||
|
void migration_test_add_postcopy(MigrationTestEnv *env);
|
||||||
|
void migration_test_add_file(MigrationTestEnv *env);
|
||||||
|
void migration_test_add_precopy(MigrationTestEnv *env);
|
||||||
|
void migration_test_add_cpr(MigrationTestEnv *env);
|
||||||
|
void migration_test_add_misc(MigrationTestEnv *env);
|
||||||
|
|
||||||
|
#endif /* TEST_FRAMEWORK_H */
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* QTest migration helpers
|
* QTest QMP helpers for migration
|
||||||
*
|
*
|
||||||
* Copyright (c) 2016-2018 Red Hat, Inc. and/or its affiliates
|
* Copyright (c) 2016-2018 Red Hat, Inc. and/or its affiliates
|
||||||
* based on the vhost-user-test.c that is:
|
* based on the vhost-user-test.c that is:
|
||||||
|
@ -11,16 +11,13 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
#include "qemu/osdep.h"
|
||||||
#include "qemu/ctype.h"
|
#include "libqtest.h"
|
||||||
#include "qapi/qmp/qjson.h"
|
#include "migration-qmp.h"
|
||||||
#include "qapi/qapi-visit-sockets.h"
|
#include "migration-util.h"
|
||||||
#include "qapi/qobject-input-visitor.h"
|
|
||||||
#include "qapi/error.h"
|
#include "qapi/error.h"
|
||||||
|
#include "qapi/qmp/qdict.h"
|
||||||
|
#include "qapi/qmp/qjson.h"
|
||||||
#include "qapi/qmp/qlist.h"
|
#include "qapi/qmp/qlist.h"
|
||||||
#include "qemu/cutils.h"
|
|
||||||
#include "qemu/memalign.h"
|
|
||||||
|
|
||||||
#include "migration-helpers.h"
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Number of seconds we wait when looking for migration
|
* Number of seconds we wait when looking for migration
|
||||||
|
@ -30,141 +27,24 @@
|
||||||
*/
|
*/
|
||||||
#define MIGRATION_STATUS_WAIT_TIMEOUT 120
|
#define MIGRATION_STATUS_WAIT_TIMEOUT 120
|
||||||
|
|
||||||
static char *SocketAddress_to_str(SocketAddress *addr)
|
/*
|
||||||
|
* Wait for a "MIGRATION" event. This is what Libvirt uses to track
|
||||||
|
* migration status changes.
|
||||||
|
*/
|
||||||
|
void migration_event_wait(QTestState *s, const char *target)
|
||||||
{
|
{
|
||||||
switch (addr->type) {
|
QDict *response, *data;
|
||||||
case SOCKET_ADDRESS_TYPE_INET:
|
const char *status;
|
||||||
return g_strdup_printf("tcp:%s:%s",
|
bool found;
|
||||||
addr->u.inet.host,
|
|
||||||
addr->u.inet.port);
|
|
||||||
case SOCKET_ADDRESS_TYPE_UNIX:
|
|
||||||
return g_strdup_printf("unix:%s",
|
|
||||||
addr->u.q_unix.path);
|
|
||||||
case SOCKET_ADDRESS_TYPE_FD:
|
|
||||||
return g_strdup_printf("fd:%s", addr->u.fd.str);
|
|
||||||
case SOCKET_ADDRESS_TYPE_VSOCK:
|
|
||||||
return g_strdup_printf("vsock:%s:%s",
|
|
||||||
addr->u.vsock.cid,
|
|
||||||
addr->u.vsock.port);
|
|
||||||
default:
|
|
||||||
return g_strdup("unknown address type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static QDict *SocketAddress_to_qdict(SocketAddress *addr)
|
do {
|
||||||
{
|
response = qtest_qmp_eventwait_ref(s, "MIGRATION");
|
||||||
QDict *dict = qdict_new();
|
data = qdict_get_qdict(response, "data");
|
||||||
|
g_assert(data);
|
||||||
switch (addr->type) {
|
status = qdict_get_str(data, "status");
|
||||||
case SOCKET_ADDRESS_TYPE_INET:
|
found = (strcmp(status, target) == 0);
|
||||||
qdict_put_str(dict, "type", "inet");
|
qobject_unref(response);
|
||||||
qdict_put_str(dict, "host", addr->u.inet.host);
|
} while (!found);
|
||||||
qdict_put_str(dict, "port", addr->u.inet.port);
|
|
||||||
break;
|
|
||||||
case SOCKET_ADDRESS_TYPE_UNIX:
|
|
||||||
qdict_put_str(dict, "type", "unix");
|
|
||||||
qdict_put_str(dict, "path", addr->u.q_unix.path);
|
|
||||||
break;
|
|
||||||
case SOCKET_ADDRESS_TYPE_FD:
|
|
||||||
qdict_put_str(dict, "type", "fd");
|
|
||||||
qdict_put_str(dict, "str", addr->u.fd.str);
|
|
||||||
break;
|
|
||||||
case SOCKET_ADDRESS_TYPE_VSOCK:
|
|
||||||
qdict_put_str(dict, "type", "vsock");
|
|
||||||
qdict_put_str(dict, "cid", addr->u.vsock.cid);
|
|
||||||
qdict_put_str(dict, "port", addr->u.vsock.port);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
g_assert_not_reached();
|
|
||||||
}
|
|
||||||
|
|
||||||
return dict;
|
|
||||||
}
|
|
||||||
|
|
||||||
static SocketAddressList *migrate_get_socket_address(QTestState *who)
|
|
||||||
{
|
|
||||||
QDict *rsp;
|
|
||||||
SocketAddressList *addrs;
|
|
||||||
Visitor *iv = NULL;
|
|
||||||
QObject *object;
|
|
||||||
|
|
||||||
rsp = migrate_query(who);
|
|
||||||
object = qdict_get(rsp, "socket-address");
|
|
||||||
|
|
||||||
iv = qobject_input_visitor_new(object);
|
|
||||||
visit_type_SocketAddressList(iv, NULL, &addrs, &error_abort);
|
|
||||||
visit_free(iv);
|
|
||||||
|
|
||||||
qobject_unref(rsp);
|
|
||||||
return addrs;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char *
|
|
||||||
migrate_get_connect_uri(QTestState *who)
|
|
||||||
{
|
|
||||||
SocketAddressList *addrs;
|
|
||||||
char *connect_uri;
|
|
||||||
|
|
||||||
addrs = migrate_get_socket_address(who);
|
|
||||||
connect_uri = SocketAddress_to_str(addrs->value);
|
|
||||||
|
|
||||||
qapi_free_SocketAddressList(addrs);
|
|
||||||
return connect_uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
static QDict *
|
|
||||||
migrate_get_connect_qdict(QTestState *who)
|
|
||||||
{
|
|
||||||
SocketAddressList *addrs;
|
|
||||||
QDict *connect_qdict;
|
|
||||||
|
|
||||||
addrs = migrate_get_socket_address(who);
|
|
||||||
connect_qdict = SocketAddress_to_qdict(addrs->value);
|
|
||||||
|
|
||||||
qapi_free_SocketAddressList(addrs);
|
|
||||||
return connect_qdict;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void migrate_set_ports(QTestState *to, QList *channel_list)
|
|
||||||
{
|
|
||||||
QDict *addr;
|
|
||||||
QListEntry *entry;
|
|
||||||
const char *addr_port = NULL;
|
|
||||||
|
|
||||||
addr = migrate_get_connect_qdict(to);
|
|
||||||
|
|
||||||
QLIST_FOREACH_ENTRY(channel_list, entry) {
|
|
||||||
QDict *channel = qobject_to(QDict, qlist_entry_obj(entry));
|
|
||||||
QDict *addrdict = qdict_get_qdict(channel, "addr");
|
|
||||||
|
|
||||||
if (qdict_haskey(addrdict, "port") &&
|
|
||||||
qdict_haskey(addr, "port") &&
|
|
||||||
(strcmp(qdict_get_str(addrdict, "port"), "0") == 0)) {
|
|
||||||
addr_port = qdict_get_str(addr, "port");
|
|
||||||
qdict_put_str(addrdict, "port", addr_port);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
qobject_unref(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool migrate_watch_for_events(QTestState *who, const char *name,
|
|
||||||
QDict *event, void *opaque)
|
|
||||||
{
|
|
||||||
QTestMigrationState *state = opaque;
|
|
||||||
|
|
||||||
if (g_str_equal(name, "STOP")) {
|
|
||||||
state->stop_seen = true;
|
|
||||||
return true;
|
|
||||||
} else if (g_str_equal(name, "SUSPEND")) {
|
|
||||||
state->suspend_seen = true;
|
|
||||||
return true;
|
|
||||||
} else if (g_str_equal(name, "RESUME")) {
|
|
||||||
state->resume_seen = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void migrate_qmp_fail(QTestState *who, const char *uri,
|
void migrate_qmp_fail(QTestState *who, const char *uri,
|
||||||
|
@ -272,43 +152,6 @@ void migrate_incoming_qmp(QTestState *to, const char *uri, const char *fmt, ...)
|
||||||
migration_event_wait(to, "setup");
|
migration_event_wait(to, "setup");
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Note: caller is responsible to free the returned object via
|
|
||||||
* qobject_unref() after use
|
|
||||||
*/
|
|
||||||
QDict *migrate_query(QTestState *who)
|
|
||||||
{
|
|
||||||
return qtest_qmp_assert_success_ref(who, "{ 'execute': 'query-migrate' }");
|
|
||||||
}
|
|
||||||
|
|
||||||
QDict *migrate_query_not_failed(QTestState *who)
|
|
||||||
{
|
|
||||||
const char *status;
|
|
||||||
QDict *rsp = migrate_query(who);
|
|
||||||
status = qdict_get_str(rsp, "status");
|
|
||||||
if (g_str_equal(status, "failed")) {
|
|
||||||
g_printerr("query-migrate shows failed migration: %s\n",
|
|
||||||
qdict_get_str(rsp, "error-desc"));
|
|
||||||
}
|
|
||||||
g_assert(!g_str_equal(status, "failed"));
|
|
||||||
return rsp;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Note: caller is responsible to free the returned object via
|
|
||||||
* g_free() after use
|
|
||||||
*/
|
|
||||||
static gchar *migrate_query_status(QTestState *who)
|
|
||||||
{
|
|
||||||
QDict *rsp_return = migrate_query(who);
|
|
||||||
gchar *status = g_strdup(qdict_get_str(rsp_return, "status"));
|
|
||||||
|
|
||||||
g_assert(status);
|
|
||||||
qobject_unref(rsp_return);
|
|
||||||
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool check_migration_status(QTestState *who, const char *goal,
|
static bool check_migration_status(QTestState *who, const char *goal,
|
||||||
const char **ungoals)
|
const char **ungoals)
|
||||||
{
|
{
|
||||||
|
@ -383,148 +226,260 @@ void wait_for_migration_fail(QTestState *from, bool allow_active)
|
||||||
qobject_unref(rsp_return);
|
qobject_unref(rsp_return);
|
||||||
}
|
}
|
||||||
|
|
||||||
char *find_common_machine_version(const char *mtype, const char *var1,
|
void wait_for_stop(QTestState *who, QTestMigrationState *state)
|
||||||
const char *var2)
|
|
||||||
{
|
{
|
||||||
g_autofree char *type1 = qtest_resolve_machine_alias(var1, mtype);
|
if (!state->stop_seen) {
|
||||||
g_autofree char *type2 = qtest_resolve_machine_alias(var2, mtype);
|
qtest_qmp_eventwait(who, "STOP");
|
||||||
|
}
|
||||||
g_assert(type1 && type2);
|
|
||||||
|
|
||||||
if (g_str_equal(type1, type2)) {
|
|
||||||
/* either can be used */
|
|
||||||
return g_strdup(type1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (qtest_has_machine_with_env(var2, type1)) {
|
void wait_for_resume(QTestState *who, QTestMigrationState *state)
|
||||||
return g_strdup(type1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (qtest_has_machine_with_env(var1, type2)) {
|
|
||||||
return g_strdup(type2);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_test_message("No common machine version for machine type '%s' between "
|
|
||||||
"binaries %s and %s", mtype, getenv(var1), getenv(var2));
|
|
||||||
g_assert_not_reached();
|
|
||||||
}
|
|
||||||
|
|
||||||
char *resolve_machine_version(const char *alias, const char *var1,
|
|
||||||
const char *var2)
|
|
||||||
{
|
{
|
||||||
const char *mname = g_getenv("QTEST_QEMU_MACHINE_TYPE");
|
if (!state->resume_seen) {
|
||||||
g_autofree char *machine_name = NULL;
|
qtest_qmp_eventwait(who, "RESUME");
|
||||||
|
|
||||||
if (mname) {
|
|
||||||
const char *dash = strrchr(mname, '-');
|
|
||||||
const char *dot = strrchr(mname, '.');
|
|
||||||
|
|
||||||
machine_name = g_strdup(mname);
|
|
||||||
|
|
||||||
if (dash && dot) {
|
|
||||||
assert(qtest_has_machine(machine_name));
|
|
||||||
return g_steal_pointer(&machine_name);
|
|
||||||
}
|
}
|
||||||
/* else: probably an alias, let it be resolved below */
|
|
||||||
} else {
|
|
||||||
/* use the hardcoded alias */
|
|
||||||
machine_name = g_strdup(alias);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return find_common_machine_version(machine_name, var1, var2);
|
void wait_for_suspend(QTestState *who, QTestMigrationState *state)
|
||||||
}
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
char *name;
|
|
||||||
void (*func)(void);
|
|
||||||
} MigrationTest;
|
|
||||||
|
|
||||||
static void migration_test_destroy(gpointer data)
|
|
||||||
{
|
{
|
||||||
MigrationTest *test = (MigrationTest *)data;
|
if (state->suspend_me && !state->suspend_seen) {
|
||||||
|
qtest_qmp_eventwait(who, "SUSPEND");
|
||||||
g_free(test->name);
|
|
||||||
g_free(test);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void migration_test_wrapper(const void *data)
|
|
||||||
{
|
|
||||||
MigrationTest *test = (MigrationTest *)data;
|
|
||||||
|
|
||||||
g_test_message("Running /%s%s", qtest_get_arch(), test->name);
|
|
||||||
test->func();
|
|
||||||
}
|
|
||||||
|
|
||||||
void migration_test_add(const char *path, void (*fn)(void))
|
|
||||||
{
|
|
||||||
MigrationTest *test = g_new0(MigrationTest, 1);
|
|
||||||
|
|
||||||
test->func = fn;
|
|
||||||
test->name = g_strdup(path);
|
|
||||||
|
|
||||||
qtest_add_data_func_full(path, test, migration_test_wrapper,
|
|
||||||
migration_test_destroy);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef O_DIRECT
|
|
||||||
/*
|
|
||||||
* Probe for O_DIRECT support on the filesystem. Since this is used
|
|
||||||
* for tests, be conservative, if anything fails, assume it's
|
|
||||||
* unsupported.
|
|
||||||
*/
|
|
||||||
bool probe_o_direct_support(const char *tmpfs)
|
|
||||||
{
|
|
||||||
g_autofree char *filename = g_strdup_printf("%s/probe-o-direct", tmpfs);
|
|
||||||
int fd, flags = O_CREAT | O_RDWR | O_TRUNC | O_DIRECT;
|
|
||||||
void *buf;
|
|
||||||
ssize_t ret, len;
|
|
||||||
uint64_t offset;
|
|
||||||
|
|
||||||
fd = open(filename, flags, 0660);
|
|
||||||
if (fd < 0) {
|
|
||||||
unlink(filename);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Using 1MB alignment as conservative choice to satisfy any
|
* Note: caller is responsible to free the returned object via
|
||||||
* plausible architecture default page size, and/or filesystem
|
* qobject_unref() after use
|
||||||
* alignment restrictions.
|
|
||||||
*/
|
*/
|
||||||
len = 0x100000;
|
QDict *migrate_query(QTestState *who)
|
||||||
offset = 0x100000;
|
{
|
||||||
|
return qtest_qmp_assert_success_ref(who, "{ 'execute': 'query-migrate' }");
|
||||||
buf = qemu_try_memalign(len, len);
|
}
|
||||||
g_assert(buf);
|
|
||||||
|
QDict *migrate_query_not_failed(QTestState *who)
|
||||||
ret = pwrite(fd, buf, len, offset);
|
|
||||||
unlink(filename);
|
|
||||||
g_free(buf);
|
|
||||||
|
|
||||||
if (ret < 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Wait for a "MIGRATION" event. This is what Libvirt uses to track
|
|
||||||
* migration status changes.
|
|
||||||
*/
|
|
||||||
void migration_event_wait(QTestState *s, const char *target)
|
|
||||||
{
|
{
|
||||||
QDict *response, *data;
|
|
||||||
const char *status;
|
const char *status;
|
||||||
bool found;
|
QDict *rsp = migrate_query(who);
|
||||||
|
status = qdict_get_str(rsp, "status");
|
||||||
do {
|
if (g_str_equal(status, "failed")) {
|
||||||
response = qtest_qmp_eventwait_ref(s, "MIGRATION");
|
g_printerr("query-migrate shows failed migration: %s\n",
|
||||||
data = qdict_get_qdict(response, "data");
|
qdict_get_str(rsp, "error-desc"));
|
||||||
g_assert(data);
|
}
|
||||||
status = qdict_get_str(data, "status");
|
g_assert(!g_str_equal(status, "failed"));
|
||||||
found = (strcmp(status, target) == 0);
|
return rsp;
|
||||||
qobject_unref(response);
|
}
|
||||||
} while (!found);
|
|
||||||
|
/*
|
||||||
|
* Note: caller is responsible to free the returned object via
|
||||||
|
* g_free() after use
|
||||||
|
*/
|
||||||
|
gchar *migrate_query_status(QTestState *who)
|
||||||
|
{
|
||||||
|
QDict *rsp_return = migrate_query(who);
|
||||||
|
gchar *status = g_strdup(qdict_get_str(rsp_return, "status"));
|
||||||
|
|
||||||
|
g_assert(status);
|
||||||
|
qobject_unref(rsp_return);
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t read_ram_property_int(QTestState *who, const char *property)
|
||||||
|
{
|
||||||
|
QDict *rsp_return, *rsp_ram;
|
||||||
|
int64_t result;
|
||||||
|
|
||||||
|
rsp_return = migrate_query_not_failed(who);
|
||||||
|
if (!qdict_haskey(rsp_return, "ram")) {
|
||||||
|
/* Still in setup */
|
||||||
|
result = 0;
|
||||||
|
} else {
|
||||||
|
rsp_ram = qdict_get_qdict(rsp_return, "ram");
|
||||||
|
result = qdict_get_try_int(rsp_ram, property, 0);
|
||||||
|
}
|
||||||
|
qobject_unref(rsp_return);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t read_migrate_property_int(QTestState *who, const char *property)
|
||||||
|
{
|
||||||
|
QDict *rsp_return;
|
||||||
|
int64_t result;
|
||||||
|
|
||||||
|
rsp_return = migrate_query_not_failed(who);
|
||||||
|
result = qdict_get_try_int(rsp_return, property, 0);
|
||||||
|
qobject_unref(rsp_return);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t get_migration_pass(QTestState *who)
|
||||||
|
{
|
||||||
|
return read_ram_property_int(who, "dirty-sync-count");
|
||||||
|
}
|
||||||
|
|
||||||
|
void read_blocktime(QTestState *who)
|
||||||
|
{
|
||||||
|
QDict *rsp_return;
|
||||||
|
|
||||||
|
rsp_return = migrate_query_not_failed(who);
|
||||||
|
g_assert(qdict_haskey(rsp_return, "postcopy-blocktime"));
|
||||||
|
qobject_unref(rsp_return);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wait for two changes in the migration pass count, but bail if we stop.
|
||||||
|
*/
|
||||||
|
void wait_for_migration_pass(QTestState *who, QTestMigrationState *src_state)
|
||||||
|
{
|
||||||
|
uint64_t pass, prev_pass = 0, changes = 0;
|
||||||
|
|
||||||
|
while (changes < 2 && !src_state->stop_seen && !src_state->suspend_seen) {
|
||||||
|
usleep(1000);
|
||||||
|
pass = get_migration_pass(who);
|
||||||
|
changes += (pass != prev_pass);
|
||||||
|
prev_pass = pass;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static long long migrate_get_parameter_int(QTestState *who,
|
||||||
|
const char *parameter)
|
||||||
|
{
|
||||||
|
QDict *rsp;
|
||||||
|
long long result;
|
||||||
|
|
||||||
|
rsp = qtest_qmp_assert_success_ref(
|
||||||
|
who, "{ 'execute': 'query-migrate-parameters' }");
|
||||||
|
result = qdict_get_int(rsp, parameter);
|
||||||
|
qobject_unref(rsp);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void migrate_check_parameter_int(QTestState *who, const char *parameter,
|
||||||
|
long long value)
|
||||||
|
{
|
||||||
|
long long result;
|
||||||
|
|
||||||
|
result = migrate_get_parameter_int(who, parameter);
|
||||||
|
g_assert_cmpint(result, ==, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void migrate_set_parameter_int(QTestState *who, const char *parameter,
|
||||||
|
long long value)
|
||||||
|
{
|
||||||
|
qtest_qmp_assert_success(who,
|
||||||
|
"{ 'execute': 'migrate-set-parameters',"
|
||||||
|
"'arguments': { %s: %lld } }",
|
||||||
|
parameter, value);
|
||||||
|
migrate_check_parameter_int(who, parameter, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *migrate_get_parameter_str(QTestState *who, const char *parameter)
|
||||||
|
{
|
||||||
|
QDict *rsp;
|
||||||
|
char *result;
|
||||||
|
|
||||||
|
rsp = qtest_qmp_assert_success_ref(
|
||||||
|
who, "{ 'execute': 'query-migrate-parameters' }");
|
||||||
|
result = g_strdup(qdict_get_str(rsp, parameter));
|
||||||
|
qobject_unref(rsp);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void migrate_check_parameter_str(QTestState *who, const char *parameter,
|
||||||
|
const char *value)
|
||||||
|
{
|
||||||
|
g_autofree char *result = migrate_get_parameter_str(who, parameter);
|
||||||
|
g_assert_cmpstr(result, ==, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void migrate_set_parameter_str(QTestState *who, const char *parameter,
|
||||||
|
const char *value)
|
||||||
|
{
|
||||||
|
qtest_qmp_assert_success(who,
|
||||||
|
"{ 'execute': 'migrate-set-parameters',"
|
||||||
|
"'arguments': { %s: %s } }",
|
||||||
|
parameter, value);
|
||||||
|
migrate_check_parameter_str(who, parameter, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static long long migrate_get_parameter_bool(QTestState *who,
|
||||||
|
const char *parameter)
|
||||||
|
{
|
||||||
|
QDict *rsp;
|
||||||
|
int result;
|
||||||
|
|
||||||
|
rsp = qtest_qmp_assert_success_ref(
|
||||||
|
who, "{ 'execute': 'query-migrate-parameters' }");
|
||||||
|
result = qdict_get_bool(rsp, parameter);
|
||||||
|
qobject_unref(rsp);
|
||||||
|
return !!result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void migrate_check_parameter_bool(QTestState *who, const char *parameter,
|
||||||
|
int value)
|
||||||
|
{
|
||||||
|
int result;
|
||||||
|
|
||||||
|
result = migrate_get_parameter_bool(who, parameter);
|
||||||
|
g_assert_cmpint(result, ==, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void migrate_set_parameter_bool(QTestState *who, const char *parameter,
|
||||||
|
int value)
|
||||||
|
{
|
||||||
|
qtest_qmp_assert_success(who,
|
||||||
|
"{ 'execute': 'migrate-set-parameters',"
|
||||||
|
"'arguments': { %s: %i } }",
|
||||||
|
parameter, value);
|
||||||
|
migrate_check_parameter_bool(who, parameter, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void migrate_ensure_non_converge(QTestState *who)
|
||||||
|
{
|
||||||
|
/* Can't converge with 1ms downtime + 3 mbs bandwidth limit */
|
||||||
|
migrate_set_parameter_int(who, "max-bandwidth", 3 * 1000 * 1000);
|
||||||
|
migrate_set_parameter_int(who, "downtime-limit", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void migrate_ensure_converge(QTestState *who)
|
||||||
|
{
|
||||||
|
/* Should converge with 30s downtime + 1 gbs bandwidth limit */
|
||||||
|
migrate_set_parameter_int(who, "max-bandwidth", 1 * 1000 * 1000 * 1000);
|
||||||
|
migrate_set_parameter_int(who, "downtime-limit", 30 * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void migrate_pause(QTestState *who)
|
||||||
|
{
|
||||||
|
qtest_qmp_assert_success(who, "{ 'execute': 'migrate-pause' }");
|
||||||
|
}
|
||||||
|
|
||||||
|
void migrate_continue(QTestState *who, const char *state)
|
||||||
|
{
|
||||||
|
qtest_qmp_assert_success(who,
|
||||||
|
"{ 'execute': 'migrate-continue',"
|
||||||
|
" 'arguments': { 'state': %s } }",
|
||||||
|
state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void migrate_recover(QTestState *who, const char *uri)
|
||||||
|
{
|
||||||
|
qtest_qmp_assert_success(who,
|
||||||
|
"{ 'execute': 'migrate-recover', "
|
||||||
|
" 'id': 'recover-cmd', "
|
||||||
|
" 'arguments': { 'uri': %s } }",
|
||||||
|
uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
void migrate_cancel(QTestState *who)
|
||||||
|
{
|
||||||
|
qtest_qmp_assert_success(who, "{ 'execute': 'migrate_cancel' }");
|
||||||
|
}
|
||||||
|
|
||||||
|
void migrate_postcopy_start(QTestState *from, QTestState *to,
|
||||||
|
QTestMigrationState *src_state)
|
||||||
|
{
|
||||||
|
qtest_qmp_assert_success(from, "{ 'execute': 'migrate-start-postcopy' }");
|
||||||
|
|
||||||
|
wait_for_stop(from, src_state);
|
||||||
|
qtest_qmp_eventwait(to, "RESUME");
|
||||||
}
|
}
|
46
tests/qtest/migration/migration-qmp.h
Normal file
46
tests/qtest/migration/migration-qmp.h
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
#ifndef MIGRATION_QMP_H
|
||||||
|
#define MIGRATION_QMP_H
|
||||||
|
|
||||||
|
#include "migration-util.h"
|
||||||
|
|
||||||
|
G_GNUC_PRINTF(4, 5)
|
||||||
|
void migrate_qmp_fail(QTestState *who, const char *uri,
|
||||||
|
const char *channels, const char *fmt, ...);
|
||||||
|
|
||||||
|
G_GNUC_PRINTF(5, 6)
|
||||||
|
void migrate_qmp(QTestState *who, QTestState *to, const char *uri,
|
||||||
|
const char *channels, const char *fmt, ...);
|
||||||
|
|
||||||
|
G_GNUC_PRINTF(3, 4)
|
||||||
|
void migrate_incoming_qmp(QTestState *who, const char *uri,
|
||||||
|
const char *fmt, ...);
|
||||||
|
|
||||||
|
void migration_event_wait(QTestState *s, const char *target);
|
||||||
|
void migrate_set_capability(QTestState *who, const char *capability,
|
||||||
|
bool value);
|
||||||
|
int64_t read_ram_property_int(QTestState *who, const char *property);
|
||||||
|
void migrate_set_parameter_int(QTestState *who, const char *parameter,
|
||||||
|
long long value);
|
||||||
|
void wait_for_stop(QTestState *who, QTestMigrationState *state);
|
||||||
|
void wait_for_resume(QTestState *who, QTestMigrationState *state);
|
||||||
|
void wait_for_suspend(QTestState *who, QTestMigrationState *state);
|
||||||
|
gchar *migrate_query_status(QTestState *who);
|
||||||
|
int64_t read_migrate_property_int(QTestState *who, const char *property);
|
||||||
|
uint64_t get_migration_pass(QTestState *who);
|
||||||
|
void read_blocktime(QTestState *who);
|
||||||
|
void wait_for_migration_pass(QTestState *who, QTestMigrationState *src_state);
|
||||||
|
void migrate_set_parameter_str(QTestState *who, const char *parameter,
|
||||||
|
const char *value);
|
||||||
|
void migrate_set_parameter_bool(QTestState *who, const char *parameter,
|
||||||
|
int value);
|
||||||
|
void migrate_ensure_non_converge(QTestState *who);
|
||||||
|
void migrate_ensure_converge(QTestState *who);
|
||||||
|
void migrate_pause(QTestState *who);
|
||||||
|
void migrate_continue(QTestState *who, const char *state);
|
||||||
|
void migrate_recover(QTestState *who, const char *uri);
|
||||||
|
void migrate_cancel(QTestState *who);
|
||||||
|
void migrate_postcopy_start(QTestState *from, QTestState *to,
|
||||||
|
QTestMigrationState *src_state);
|
||||||
|
|
||||||
|
#endif /* MIGRATION_QMP_H */
|
362
tests/qtest/migration/migration-util.c
Normal file
362
tests/qtest/migration/migration-util.c
Normal file
|
@ -0,0 +1,362 @@
|
||||||
|
/*
|
||||||
|
* QTest migration utilities
|
||||||
|
*
|
||||||
|
* Copyright (c) 2016-2018 Red Hat, Inc. and/or its affiliates
|
||||||
|
* based on the vhost-user-test.c that is:
|
||||||
|
* Copyright (c) 2014 Virtual Open Systems Sarl.
|
||||||
|
*
|
||||||
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||||
|
* See the COPYING file in the top-level directory.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "qemu/osdep.h"
|
||||||
|
#include "qemu/ctype.h"
|
||||||
|
#include "qapi/qapi-visit-sockets.h"
|
||||||
|
#include "qapi/qobject-input-visitor.h"
|
||||||
|
#include "qapi/error.h"
|
||||||
|
#include "qapi/qmp/qlist.h"
|
||||||
|
#include "qemu/cutils.h"
|
||||||
|
#include "qemu/memalign.h"
|
||||||
|
|
||||||
|
#include "migration/bootfile.h"
|
||||||
|
#include "migration/migration-util.h"
|
||||||
|
|
||||||
|
/* for uffd_version_check() */
|
||||||
|
#if defined(__linux__) && defined(__NR_userfaultfd) && defined(CONFIG_EVENTFD)
|
||||||
|
#include <sys/eventfd.h>
|
||||||
|
#include "qemu/userfaultfd.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* For dirty ring test; so far only x86_64 is supported */
|
||||||
|
#if defined(__linux__) && defined(HOST_X86_64)
|
||||||
|
#include "linux/kvm.h"
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
static char *SocketAddress_to_str(SocketAddress *addr)
|
||||||
|
{
|
||||||
|
switch (addr->type) {
|
||||||
|
case SOCKET_ADDRESS_TYPE_INET:
|
||||||
|
return g_strdup_printf("tcp:%s:%s",
|
||||||
|
addr->u.inet.host,
|
||||||
|
addr->u.inet.port);
|
||||||
|
case SOCKET_ADDRESS_TYPE_UNIX:
|
||||||
|
return g_strdup_printf("unix:%s",
|
||||||
|
addr->u.q_unix.path);
|
||||||
|
case SOCKET_ADDRESS_TYPE_FD:
|
||||||
|
return g_strdup_printf("fd:%s", addr->u.fd.str);
|
||||||
|
case SOCKET_ADDRESS_TYPE_VSOCK:
|
||||||
|
return g_strdup_printf("vsock:%s:%s",
|
||||||
|
addr->u.vsock.cid,
|
||||||
|
addr->u.vsock.port);
|
||||||
|
default:
|
||||||
|
return g_strdup("unknown address type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static QDict *SocketAddress_to_qdict(SocketAddress *addr)
|
||||||
|
{
|
||||||
|
QDict *dict = qdict_new();
|
||||||
|
|
||||||
|
switch (addr->type) {
|
||||||
|
case SOCKET_ADDRESS_TYPE_INET:
|
||||||
|
qdict_put_str(dict, "type", "inet");
|
||||||
|
qdict_put_str(dict, "host", addr->u.inet.host);
|
||||||
|
qdict_put_str(dict, "port", addr->u.inet.port);
|
||||||
|
break;
|
||||||
|
case SOCKET_ADDRESS_TYPE_UNIX:
|
||||||
|
qdict_put_str(dict, "type", "unix");
|
||||||
|
qdict_put_str(dict, "path", addr->u.q_unix.path);
|
||||||
|
break;
|
||||||
|
case SOCKET_ADDRESS_TYPE_FD:
|
||||||
|
qdict_put_str(dict, "type", "fd");
|
||||||
|
qdict_put_str(dict, "str", addr->u.fd.str);
|
||||||
|
break;
|
||||||
|
case SOCKET_ADDRESS_TYPE_VSOCK:
|
||||||
|
qdict_put_str(dict, "type", "vsock");
|
||||||
|
qdict_put_str(dict, "cid", addr->u.vsock.cid);
|
||||||
|
qdict_put_str(dict, "port", addr->u.vsock.port);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
g_assert_not_reached();
|
||||||
|
}
|
||||||
|
|
||||||
|
return dict;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SocketAddressList *migrate_get_socket_address(QTestState *who)
|
||||||
|
{
|
||||||
|
QDict *rsp;
|
||||||
|
SocketAddressList *addrs;
|
||||||
|
Visitor *iv = NULL;
|
||||||
|
QObject *object;
|
||||||
|
|
||||||
|
rsp = migrate_query(who);
|
||||||
|
object = qdict_get(rsp, "socket-address");
|
||||||
|
|
||||||
|
iv = qobject_input_visitor_new(object);
|
||||||
|
visit_type_SocketAddressList(iv, NULL, &addrs, &error_abort);
|
||||||
|
visit_free(iv);
|
||||||
|
|
||||||
|
qobject_unref(rsp);
|
||||||
|
return addrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *migrate_get_connect_uri(QTestState *who)
|
||||||
|
{
|
||||||
|
SocketAddressList *addrs;
|
||||||
|
char *connect_uri;
|
||||||
|
|
||||||
|
addrs = migrate_get_socket_address(who);
|
||||||
|
connect_uri = SocketAddress_to_str(addrs->value);
|
||||||
|
|
||||||
|
qapi_free_SocketAddressList(addrs);
|
||||||
|
return connect_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
static QDict *
|
||||||
|
migrate_get_connect_qdict(QTestState *who)
|
||||||
|
{
|
||||||
|
SocketAddressList *addrs;
|
||||||
|
QDict *connect_qdict;
|
||||||
|
|
||||||
|
addrs = migrate_get_socket_address(who);
|
||||||
|
connect_qdict = SocketAddress_to_qdict(addrs->value);
|
||||||
|
|
||||||
|
qapi_free_SocketAddressList(addrs);
|
||||||
|
return connect_qdict;
|
||||||
|
}
|
||||||
|
|
||||||
|
void migrate_set_ports(QTestState *to, QList *channel_list)
|
||||||
|
{
|
||||||
|
QDict *addr;
|
||||||
|
QListEntry *entry;
|
||||||
|
const char *addr_port = NULL;
|
||||||
|
|
||||||
|
addr = migrate_get_connect_qdict(to);
|
||||||
|
|
||||||
|
QLIST_FOREACH_ENTRY(channel_list, entry) {
|
||||||
|
QDict *channel = qobject_to(QDict, qlist_entry_obj(entry));
|
||||||
|
QDict *addrdict = qdict_get_qdict(channel, "addr");
|
||||||
|
|
||||||
|
if (qdict_haskey(addrdict, "port") &&
|
||||||
|
qdict_haskey(addr, "port") &&
|
||||||
|
(strcmp(qdict_get_str(addrdict, "port"), "0") == 0)) {
|
||||||
|
addr_port = qdict_get_str(addr, "port");
|
||||||
|
qdict_put_str(addrdict, "port", addr_port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qobject_unref(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool migrate_watch_for_events(QTestState *who, const char *name,
|
||||||
|
QDict *event, void *opaque)
|
||||||
|
{
|
||||||
|
QTestMigrationState *state = opaque;
|
||||||
|
|
||||||
|
if (g_str_equal(name, "STOP")) {
|
||||||
|
state->stop_seen = true;
|
||||||
|
return true;
|
||||||
|
} else if (g_str_equal(name, "SUSPEND")) {
|
||||||
|
state->suspend_seen = true;
|
||||||
|
return true;
|
||||||
|
} else if (g_str_equal(name, "RESUME")) {
|
||||||
|
state->resume_seen = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *find_common_machine_version(const char *mtype, const char *var1,
|
||||||
|
const char *var2)
|
||||||
|
{
|
||||||
|
g_autofree char *type1 = qtest_resolve_machine_alias(var1, mtype);
|
||||||
|
g_autofree char *type2 = qtest_resolve_machine_alias(var2, mtype);
|
||||||
|
|
||||||
|
g_assert(type1 && type2);
|
||||||
|
|
||||||
|
if (g_str_equal(type1, type2)) {
|
||||||
|
/* either can be used */
|
||||||
|
return g_strdup(type1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qtest_has_machine_with_env(var2, type1)) {
|
||||||
|
return g_strdup(type1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qtest_has_machine_with_env(var1, type2)) {
|
||||||
|
return g_strdup(type2);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_test_message("No common machine version for machine type '%s' between "
|
||||||
|
"binaries %s and %s", mtype, getenv(var1), getenv(var2));
|
||||||
|
g_assert_not_reached();
|
||||||
|
}
|
||||||
|
|
||||||
|
char *resolve_machine_version(const char *alias, const char *var1,
|
||||||
|
const char *var2)
|
||||||
|
{
|
||||||
|
const char *mname = g_getenv("QTEST_QEMU_MACHINE_TYPE");
|
||||||
|
g_autofree char *machine_name = NULL;
|
||||||
|
|
||||||
|
if (mname) {
|
||||||
|
const char *dash = strrchr(mname, '-');
|
||||||
|
const char *dot = strrchr(mname, '.');
|
||||||
|
|
||||||
|
machine_name = g_strdup(mname);
|
||||||
|
|
||||||
|
if (dash && dot) {
|
||||||
|
assert(qtest_has_machine(machine_name));
|
||||||
|
return g_steal_pointer(&machine_name);
|
||||||
|
}
|
||||||
|
/* else: probably an alias, let it be resolved below */
|
||||||
|
} else {
|
||||||
|
/* use the hardcoded alias */
|
||||||
|
machine_name = g_strdup(alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
return find_common_machine_version(machine_name, var1, var2);
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *name;
|
||||||
|
void (*func)(void);
|
||||||
|
} MigrationTest;
|
||||||
|
|
||||||
|
static void migration_test_destroy(gpointer data)
|
||||||
|
{
|
||||||
|
MigrationTest *test = (MigrationTest *)data;
|
||||||
|
|
||||||
|
g_free(test->name);
|
||||||
|
g_free(test);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void migration_test_wrapper(const void *data)
|
||||||
|
{
|
||||||
|
MigrationTest *test = (MigrationTest *)data;
|
||||||
|
|
||||||
|
g_test_message("Running /%s%s", qtest_get_arch(), test->name);
|
||||||
|
test->func();
|
||||||
|
}
|
||||||
|
|
||||||
|
void migration_test_add(const char *path, void (*fn)(void))
|
||||||
|
{
|
||||||
|
MigrationTest *test = g_new0(MigrationTest, 1);
|
||||||
|
|
||||||
|
test->func = fn;
|
||||||
|
test->name = g_strdup(path);
|
||||||
|
|
||||||
|
qtest_add_data_func_full(path, test, migration_test_wrapper,
|
||||||
|
migration_test_destroy);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef O_DIRECT
|
||||||
|
/*
|
||||||
|
* Probe for O_DIRECT support on the filesystem. Since this is used
|
||||||
|
* for tests, be conservative, if anything fails, assume it's
|
||||||
|
* unsupported.
|
||||||
|
*/
|
||||||
|
bool probe_o_direct_support(const char *tmpfs)
|
||||||
|
{
|
||||||
|
g_autofree char *filename = g_strdup_printf("%s/probe-o-direct", tmpfs);
|
||||||
|
int fd, flags = O_CREAT | O_RDWR | O_TRUNC | O_DIRECT;
|
||||||
|
void *buf;
|
||||||
|
ssize_t ret, len;
|
||||||
|
uint64_t offset;
|
||||||
|
|
||||||
|
fd = open(filename, flags, 0660);
|
||||||
|
if (fd < 0) {
|
||||||
|
unlink(filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Using 1MB alignment as conservative choice to satisfy any
|
||||||
|
* plausible architecture default page size, and/or filesystem
|
||||||
|
* alignment restrictions.
|
||||||
|
*/
|
||||||
|
len = 0x100000;
|
||||||
|
offset = 0x100000;
|
||||||
|
|
||||||
|
buf = qemu_try_memalign(len, len);
|
||||||
|
g_assert(buf);
|
||||||
|
|
||||||
|
ret = pwrite(fd, buf, len, offset);
|
||||||
|
unlink(filename);
|
||||||
|
g_free(buf);
|
||||||
|
|
||||||
|
if (ret < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__linux__) && defined(__NR_userfaultfd) && defined(CONFIG_EVENTFD)
|
||||||
|
bool ufd_version_check(bool *uffd_feature_thread_id)
|
||||||
|
{
|
||||||
|
struct uffdio_api api_struct;
|
||||||
|
uint64_t ioctl_mask;
|
||||||
|
|
||||||
|
int ufd = uffd_open(O_CLOEXEC);
|
||||||
|
|
||||||
|
if (ufd == -1) {
|
||||||
|
g_test_message("Skipping test: userfaultfd not available");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
api_struct.api = UFFD_API;
|
||||||
|
api_struct.features = 0;
|
||||||
|
if (ioctl(ufd, UFFDIO_API, &api_struct)) {
|
||||||
|
g_test_message("Skipping test: UFFDIO_API failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uffd_feature_thread_id) {
|
||||||
|
*uffd_feature_thread_id = api_struct.features & UFFD_FEATURE_THREAD_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
ioctl_mask = (1ULL << _UFFDIO_REGISTER |
|
||||||
|
1ULL << _UFFDIO_UNREGISTER);
|
||||||
|
if ((api_struct.ioctls & ioctl_mask) != ioctl_mask) {
|
||||||
|
g_test_message("Skipping test: Missing userfault feature");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
bool ufd_version_check(bool *uffd_feature_thread_id)
|
||||||
|
{
|
||||||
|
g_test_message("Skipping test: Userfault not available (builtdtime)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool kvm_dirty_ring_supported(void)
|
||||||
|
{
|
||||||
|
#if defined(__linux__) && defined(HOST_X86_64)
|
||||||
|
int ret, kvm_fd = open("/dev/kvm", O_RDONLY);
|
||||||
|
|
||||||
|
if (kvm_fd < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = ioctl(kvm_fd, KVM_CHECK_EXTENSION, KVM_CAP_DIRTY_LOG_RING);
|
||||||
|
close(kvm_fd);
|
||||||
|
|
||||||
|
/* We test with 4096 slots */
|
||||||
|
if (ret < 4096) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
|
@ -10,8 +10,8 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef MIGRATION_HELPERS_H
|
#ifndef MIGRATION_UTIL_H
|
||||||
#define MIGRATION_HELPERS_H
|
#define MIGRATION_UTIL_H
|
||||||
|
|
||||||
#include "libqtest.h"
|
#include "libqtest.h"
|
||||||
|
|
||||||
|
@ -25,21 +25,6 @@ typedef struct QTestMigrationState {
|
||||||
bool migrate_watch_for_events(QTestState *who, const char *name,
|
bool migrate_watch_for_events(QTestState *who, const char *name,
|
||||||
QDict *event, void *opaque);
|
QDict *event, void *opaque);
|
||||||
|
|
||||||
G_GNUC_PRINTF(5, 6)
|
|
||||||
void migrate_qmp(QTestState *who, QTestState *to, const char *uri,
|
|
||||||
const char *channels, const char *fmt, ...);
|
|
||||||
|
|
||||||
G_GNUC_PRINTF(3, 4)
|
|
||||||
void migrate_incoming_qmp(QTestState *who, const char *uri,
|
|
||||||
const char *fmt, ...);
|
|
||||||
|
|
||||||
G_GNUC_PRINTF(4, 5)
|
|
||||||
void migrate_qmp_fail(QTestState *who, const char *uri,
|
|
||||||
const char *channels, const char *fmt, ...);
|
|
||||||
|
|
||||||
void migrate_set_capability(QTestState *who, const char *capability,
|
|
||||||
bool value);
|
|
||||||
|
|
||||||
QDict *migrate_query(QTestState *who);
|
QDict *migrate_query(QTestState *who);
|
||||||
QDict *migrate_query_not_failed(QTestState *who);
|
QDict *migrate_query_not_failed(QTestState *who);
|
||||||
|
|
||||||
|
@ -62,7 +47,11 @@ static inline bool probe_o_direct_support(const char *tmpfs)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
void migration_test_add(const char *path, void (*fn)(void));
|
|
||||||
void migration_event_wait(QTestState *s, const char *target);
|
|
||||||
|
|
||||||
#endif /* MIGRATION_HELPERS_H */
|
bool ufd_version_check(bool *uffd_feature_thread_id);
|
||||||
|
bool kvm_dirty_ring_supported(void);
|
||||||
|
void migration_test_add(const char *path, void (*fn)(void));
|
||||||
|
char *migrate_get_connect_uri(QTestState *who);
|
||||||
|
void migrate_set_ports(QTestState *to, QList *channel_list);
|
||||||
|
|
||||||
|
#endif /* MIGRATION_UTIL_H */
|
282
tests/qtest/migration/misc-tests.c
Normal file
282
tests/qtest/migration/misc-tests.c
Normal file
|
@ -0,0 +1,282 @@
|
||||||
|
/*
|
||||||
|
* QTest testcases for migration
|
||||||
|
*
|
||||||
|
* Copyright (c) 2016-2018 Red Hat, Inc. and/or its affiliates
|
||||||
|
* based on the vhost-user-test.c that is:
|
||||||
|
* Copyright (c) 2014 Virtual Open Systems Sarl.
|
||||||
|
*
|
||||||
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||||
|
* See the COPYING file in the top-level directory.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "qemu/osdep.h"
|
||||||
|
#include "libqtest.h"
|
||||||
|
#include "migration/framework.h"
|
||||||
|
#include "migration/migration-qmp.h"
|
||||||
|
#include "migration/migration-util.h"
|
||||||
|
|
||||||
|
#define ANALYZE_SCRIPT "scripts/analyze-migration.py"
|
||||||
|
|
||||||
|
static char *tmpfs;
|
||||||
|
|
||||||
|
static void test_baddest(void)
|
||||||
|
{
|
||||||
|
MigrateStart args = {
|
||||||
|
.hide_stderr = true
|
||||||
|
};
|
||||||
|
QTestState *from, *to;
|
||||||
|
|
||||||
|
if (migrate_start(&from, &to, "tcp:127.0.0.1:0", &args)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
migrate_qmp(from, to, "tcp:127.0.0.1:0", NULL, "{}");
|
||||||
|
wait_for_migration_fail(from, false);
|
||||||
|
migrate_end(from, to, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
static void test_analyze_script(void)
|
||||||
|
{
|
||||||
|
MigrateStart args = {
|
||||||
|
.opts_source = "-uuid 11111111-1111-1111-1111-111111111111",
|
||||||
|
};
|
||||||
|
QTestState *from, *to;
|
||||||
|
g_autofree char *uri = NULL;
|
||||||
|
g_autofree char *file = NULL;
|
||||||
|
int pid, wstatus;
|
||||||
|
const char *python = g_getenv("PYTHON");
|
||||||
|
|
||||||
|
if (!python) {
|
||||||
|
g_test_skip("PYTHON variable not set");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* dummy url */
|
||||||
|
if (migrate_start(&from, &to, "tcp:127.0.0.1:0", &args)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Setting these two capabilities causes the "configuration"
|
||||||
|
* vmstate to include subsections for them. The script needs to
|
||||||
|
* parse those subsections properly.
|
||||||
|
*/
|
||||||
|
migrate_set_capability(from, "validate-uuid", true);
|
||||||
|
migrate_set_capability(from, "x-ignore-shared", true);
|
||||||
|
|
||||||
|
file = g_strdup_printf("%s/migfile", tmpfs);
|
||||||
|
uri = g_strdup_printf("exec:cat > %s", file);
|
||||||
|
|
||||||
|
migrate_ensure_converge(from);
|
||||||
|
migrate_qmp(from, to, uri, NULL, "{}");
|
||||||
|
wait_for_migration_complete(from);
|
||||||
|
|
||||||
|
pid = fork();
|
||||||
|
if (!pid) {
|
||||||
|
close(1);
|
||||||
|
open("/dev/null", O_WRONLY);
|
||||||
|
execl(python, python, ANALYZE_SCRIPT, "-f", file, NULL);
|
||||||
|
g_assert_not_reached();
|
||||||
|
}
|
||||||
|
|
||||||
|
g_assert(waitpid(pid, &wstatus, 0) == pid);
|
||||||
|
if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) {
|
||||||
|
g_test_message("Failed to analyze the migration stream");
|
||||||
|
g_test_fail();
|
||||||
|
}
|
||||||
|
migrate_end(from, to, false);
|
||||||
|
unlink(file);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void test_ignore_shared(void)
|
||||||
|
{
|
||||||
|
g_autofree char *uri = g_strdup_printf("unix:%s/migsocket", tmpfs);
|
||||||
|
QTestState *from, *to;
|
||||||
|
MigrateStart args = {
|
||||||
|
.use_shmem = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (migrate_start(&from, &to, uri, &args)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
migrate_ensure_non_converge(from);
|
||||||
|
migrate_prepare_for_dirty_mem(from);
|
||||||
|
|
||||||
|
migrate_set_capability(from, "x-ignore-shared", true);
|
||||||
|
migrate_set_capability(to, "x-ignore-shared", true);
|
||||||
|
|
||||||
|
/* Wait for the first serial output from the source */
|
||||||
|
wait_for_serial("src_serial");
|
||||||
|
|
||||||
|
migrate_qmp(from, to, uri, NULL, "{}");
|
||||||
|
|
||||||
|
migrate_wait_for_dirty_mem(from, to);
|
||||||
|
|
||||||
|
wait_for_stop(from, get_src());
|
||||||
|
|
||||||
|
qtest_qmp_eventwait(to, "RESUME");
|
||||||
|
|
||||||
|
wait_for_serial("dest_serial");
|
||||||
|
wait_for_migration_complete(from);
|
||||||
|
|
||||||
|
/* Check whether shared RAM has been really skipped */
|
||||||
|
g_assert_cmpint(
|
||||||
|
read_ram_property_int(from, "transferred"), <, 4 * 1024 * 1024);
|
||||||
|
|
||||||
|
migrate_end(from, to, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void do_test_validate_uuid(MigrateStart *args, bool should_fail)
|
||||||
|
{
|
||||||
|
g_autofree char *uri = g_strdup_printf("unix:%s/migsocket", tmpfs);
|
||||||
|
QTestState *from, *to;
|
||||||
|
|
||||||
|
if (migrate_start(&from, &to, uri, args)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* UUID validation is at the begin of migration. So, the main process of
|
||||||
|
* migration is not interesting for us here. Thus, set huge downtime for
|
||||||
|
* very fast migration.
|
||||||
|
*/
|
||||||
|
migrate_set_parameter_int(from, "downtime-limit", 1000000);
|
||||||
|
migrate_set_capability(from, "validate-uuid", true);
|
||||||
|
|
||||||
|
/* Wait for the first serial output from the source */
|
||||||
|
wait_for_serial("src_serial");
|
||||||
|
|
||||||
|
migrate_qmp(from, to, uri, NULL, "{}");
|
||||||
|
|
||||||
|
if (should_fail) {
|
||||||
|
qtest_set_expected_status(to, EXIT_FAILURE);
|
||||||
|
wait_for_migration_fail(from, true);
|
||||||
|
} else {
|
||||||
|
wait_for_migration_complete(from);
|
||||||
|
}
|
||||||
|
|
||||||
|
migrate_end(from, to, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_validate_uuid(void)
|
||||||
|
{
|
||||||
|
MigrateStart args = {
|
||||||
|
.opts_source = "-uuid 11111111-1111-1111-1111-111111111111",
|
||||||
|
.opts_target = "-uuid 11111111-1111-1111-1111-111111111111",
|
||||||
|
};
|
||||||
|
|
||||||
|
do_test_validate_uuid(&args, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_validate_uuid_error(void)
|
||||||
|
{
|
||||||
|
MigrateStart args = {
|
||||||
|
.opts_source = "-uuid 11111111-1111-1111-1111-111111111111",
|
||||||
|
.opts_target = "-uuid 22222222-2222-2222-2222-222222222222",
|
||||||
|
.hide_stderr = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
do_test_validate_uuid(&args, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_validate_uuid_src_not_set(void)
|
||||||
|
{
|
||||||
|
MigrateStart args = {
|
||||||
|
.opts_target = "-uuid 22222222-2222-2222-2222-222222222222",
|
||||||
|
.hide_stderr = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
do_test_validate_uuid(&args, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_validate_uuid_dst_not_set(void)
|
||||||
|
{
|
||||||
|
MigrateStart args = {
|
||||||
|
.opts_source = "-uuid 11111111-1111-1111-1111-111111111111",
|
||||||
|
.hide_stderr = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
do_test_validate_uuid(&args, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void do_test_validate_uri_channel(MigrateCommon *args)
|
||||||
|
{
|
||||||
|
QTestState *from, *to;
|
||||||
|
|
||||||
|
if (migrate_start(&from, &to, args->listen_uri, &args->start)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wait for the first serial output from the source */
|
||||||
|
wait_for_serial("src_serial");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 'uri' and 'channels' validation is checked even before the migration
|
||||||
|
* starts.
|
||||||
|
*/
|
||||||
|
migrate_qmp_fail(from, args->connect_uri, args->connect_channels, "{}");
|
||||||
|
migrate_end(from, to, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_validate_uri_channels_both_set(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.start = {
|
||||||
|
.hide_stderr = true,
|
||||||
|
},
|
||||||
|
.listen_uri = "defer",
|
||||||
|
.connect_uri = "tcp:127.0.0.1:0",
|
||||||
|
.connect_channels = ("[ { ""'channel-type': 'main',"
|
||||||
|
" 'addr': { 'transport': 'socket',"
|
||||||
|
" 'type': 'inet',"
|
||||||
|
" 'host': '127.0.0.1',"
|
||||||
|
" 'port': '0' } } ]"),
|
||||||
|
};
|
||||||
|
|
||||||
|
do_test_validate_uri_channel(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_validate_uri_channels_none_set(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.start = {
|
||||||
|
.hide_stderr = true,
|
||||||
|
},
|
||||||
|
.listen_uri = "defer",
|
||||||
|
};
|
||||||
|
|
||||||
|
do_test_validate_uri_channel(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void migration_test_add_misc(MigrationTestEnv *env)
|
||||||
|
{
|
||||||
|
tmpfs = env->tmpfs;
|
||||||
|
|
||||||
|
migration_test_add("/migration/bad_dest", test_baddest);
|
||||||
|
#ifndef _WIN32
|
||||||
|
migration_test_add("/migration/analyze-script", test_analyze_script);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Our CI system has problems with shared memory.
|
||||||
|
* Don't run this test until we find a workaround.
|
||||||
|
*/
|
||||||
|
if (getenv("QEMU_TEST_FLAKY_TESTS")) {
|
||||||
|
migration_test_add("/migration/ignore-shared", test_ignore_shared);
|
||||||
|
}
|
||||||
|
|
||||||
|
migration_test_add("/migration/validate_uuid", test_validate_uuid);
|
||||||
|
migration_test_add("/migration/validate_uuid_error",
|
||||||
|
test_validate_uuid_error);
|
||||||
|
migration_test_add("/migration/validate_uuid_src_not_set",
|
||||||
|
test_validate_uuid_src_not_set);
|
||||||
|
migration_test_add("/migration/validate_uuid_dst_not_set",
|
||||||
|
test_validate_uuid_dst_not_set);
|
||||||
|
migration_test_add("/migration/validate_uri/channels/both_set",
|
||||||
|
test_validate_uri_channels_both_set);
|
||||||
|
migration_test_add("/migration/validate_uri/channels/none_set",
|
||||||
|
test_validate_uri_channels_none_set);
|
||||||
|
}
|
106
tests/qtest/migration/postcopy-tests.c
Normal file
106
tests/qtest/migration/postcopy-tests.c
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
/*
|
||||||
|
* QTest testcases for postcopy migration
|
||||||
|
*
|
||||||
|
* Copyright (c) 2016-2018 Red Hat, Inc. and/or its affiliates
|
||||||
|
* based on the vhost-user-test.c that is:
|
||||||
|
* Copyright (c) 2014 Virtual Open Systems Sarl.
|
||||||
|
*
|
||||||
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||||
|
* See the COPYING file in the top-level directory.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "qemu/osdep.h"
|
||||||
|
#include "libqtest.h"
|
||||||
|
#include "migration/framework.h"
|
||||||
|
#include "migration/migration-util.h"
|
||||||
|
#include "qapi/qmp/qlist.h"
|
||||||
|
#include "qemu/module.h"
|
||||||
|
#include "qemu/option.h"
|
||||||
|
#include "qemu/range.h"
|
||||||
|
#include "qemu/sockets.h"
|
||||||
|
|
||||||
|
static void test_postcopy(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = { };
|
||||||
|
|
||||||
|
test_postcopy_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_postcopy_suspend(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.start.suspend_me = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_postcopy_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_postcopy_preempt(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.postcopy_preempt = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_postcopy_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_postcopy_recovery(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = { };
|
||||||
|
|
||||||
|
test_postcopy_recovery_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_postcopy_recovery_fail_handshake(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.postcopy_recovery_fail_stage = POSTCOPY_FAIL_RECOVERY,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_postcopy_recovery_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_postcopy_recovery_fail_reconnect(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.postcopy_recovery_fail_stage = POSTCOPY_FAIL_CHANNEL_ESTABLISH,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_postcopy_recovery_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_postcopy_preempt_recovery(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.postcopy_preempt = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_postcopy_recovery_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void migration_test_add_postcopy(MigrationTestEnv *env)
|
||||||
|
{
|
||||||
|
if (env->has_uffd) {
|
||||||
|
migration_test_add("/migration/postcopy/plain", test_postcopy);
|
||||||
|
migration_test_add("/migration/postcopy/recovery/plain",
|
||||||
|
test_postcopy_recovery);
|
||||||
|
migration_test_add("/migration/postcopy/preempt/plain",
|
||||||
|
test_postcopy_preempt);
|
||||||
|
migration_test_add("/migration/postcopy/preempt/recovery/plain",
|
||||||
|
test_postcopy_preempt_recovery);
|
||||||
|
|
||||||
|
migration_test_add(
|
||||||
|
"/migration/postcopy/recovery/double-failures/handshake",
|
||||||
|
test_postcopy_recovery_fail_handshake);
|
||||||
|
|
||||||
|
migration_test_add(
|
||||||
|
"/migration/postcopy/recovery/double-failures/reconnect",
|
||||||
|
test_postcopy_recovery_fail_reconnect);
|
||||||
|
|
||||||
|
if (env->is_x86) {
|
||||||
|
migration_test_add("/migration/postcopy/suspend",
|
||||||
|
test_postcopy_suspend);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1007
tests/qtest/migration/precopy-tests.c
Normal file
1007
tests/qtest/migration/precopy-tests.c
Normal file
File diff suppressed because it is too large
Load diff
791
tests/qtest/migration/tls-tests.c
Normal file
791
tests/qtest/migration/tls-tests.c
Normal file
|
@ -0,0 +1,791 @@
|
||||||
|
/*
|
||||||
|
* QTest testcases for TLS migration
|
||||||
|
*
|
||||||
|
* Copyright (c) 2016-2018 Red Hat, Inc. and/or its affiliates
|
||||||
|
* based on the vhost-user-test.c that is:
|
||||||
|
* Copyright (c) 2014 Virtual Open Systems Sarl.
|
||||||
|
*
|
||||||
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||||
|
* See the COPYING file in the top-level directory.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "qemu/osdep.h"
|
||||||
|
#include "crypto/tlscredspsk.h"
|
||||||
|
#include "libqtest.h"
|
||||||
|
#include "migration/framework.h"
|
||||||
|
#include "migration/migration-qmp.h"
|
||||||
|
#include "migration/migration-util.h"
|
||||||
|
|
||||||
|
#include "tests/unit/crypto-tls-psk-helpers.h"
|
||||||
|
#ifdef CONFIG_TASN1
|
||||||
|
# include "tests/unit/crypto-tls-x509-helpers.h"
|
||||||
|
#endif /* CONFIG_TASN1 */
|
||||||
|
|
||||||
|
|
||||||
|
struct TestMigrateTLSPSKData {
|
||||||
|
char *workdir;
|
||||||
|
char *workdiralt;
|
||||||
|
char *pskfile;
|
||||||
|
char *pskfilealt;
|
||||||
|
};
|
||||||
|
|
||||||
|
static char *tmpfs;
|
||||||
|
|
||||||
|
static void *
|
||||||
|
migrate_hook_start_tls_psk_common(QTestState *from,
|
||||||
|
QTestState *to,
|
||||||
|
bool mismatch)
|
||||||
|
{
|
||||||
|
struct TestMigrateTLSPSKData *data =
|
||||||
|
g_new0(struct TestMigrateTLSPSKData, 1);
|
||||||
|
|
||||||
|
data->workdir = g_strdup_printf("%s/tlscredspsk0", tmpfs);
|
||||||
|
data->pskfile = g_strdup_printf("%s/%s", data->workdir,
|
||||||
|
QCRYPTO_TLS_CREDS_PSKFILE);
|
||||||
|
g_mkdir_with_parents(data->workdir, 0700);
|
||||||
|
test_tls_psk_init(data->pskfile);
|
||||||
|
|
||||||
|
if (mismatch) {
|
||||||
|
data->workdiralt = g_strdup_printf("%s/tlscredspskalt0", tmpfs);
|
||||||
|
data->pskfilealt = g_strdup_printf("%s/%s", data->workdiralt,
|
||||||
|
QCRYPTO_TLS_CREDS_PSKFILE);
|
||||||
|
g_mkdir_with_parents(data->workdiralt, 0700);
|
||||||
|
test_tls_psk_init_alt(data->pskfilealt);
|
||||||
|
}
|
||||||
|
|
||||||
|
qtest_qmp_assert_success(from,
|
||||||
|
"{ 'execute': 'object-add',"
|
||||||
|
" 'arguments': { 'qom-type': 'tls-creds-psk',"
|
||||||
|
" 'id': 'tlscredspsk0',"
|
||||||
|
" 'endpoint': 'client',"
|
||||||
|
" 'dir': %s,"
|
||||||
|
" 'username': 'qemu'} }",
|
||||||
|
data->workdir);
|
||||||
|
|
||||||
|
qtest_qmp_assert_success(to,
|
||||||
|
"{ 'execute': 'object-add',"
|
||||||
|
" 'arguments': { 'qom-type': 'tls-creds-psk',"
|
||||||
|
" 'id': 'tlscredspsk0',"
|
||||||
|
" 'endpoint': 'server',"
|
||||||
|
" 'dir': %s } }",
|
||||||
|
mismatch ? data->workdiralt : data->workdir);
|
||||||
|
|
||||||
|
migrate_set_parameter_str(from, "tls-creds", "tlscredspsk0");
|
||||||
|
migrate_set_parameter_str(to, "tls-creds", "tlscredspsk0");
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *
|
||||||
|
migrate_hook_start_tls_psk_match(QTestState *from,
|
||||||
|
QTestState *to)
|
||||||
|
{
|
||||||
|
return migrate_hook_start_tls_psk_common(from, to, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *
|
||||||
|
migrate_hook_start_tls_psk_mismatch(QTestState *from,
|
||||||
|
QTestState *to)
|
||||||
|
{
|
||||||
|
return migrate_hook_start_tls_psk_common(from, to, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
migrate_hook_end_tls_psk(QTestState *from,
|
||||||
|
QTestState *to,
|
||||||
|
void *opaque)
|
||||||
|
{
|
||||||
|
struct TestMigrateTLSPSKData *data = opaque;
|
||||||
|
|
||||||
|
test_tls_psk_cleanup(data->pskfile);
|
||||||
|
if (data->pskfilealt) {
|
||||||
|
test_tls_psk_cleanup(data->pskfilealt);
|
||||||
|
}
|
||||||
|
rmdir(data->workdir);
|
||||||
|
if (data->workdiralt) {
|
||||||
|
rmdir(data->workdiralt);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free(data->workdiralt);
|
||||||
|
g_free(data->pskfilealt);
|
||||||
|
g_free(data->workdir);
|
||||||
|
g_free(data->pskfile);
|
||||||
|
g_free(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_TASN1
|
||||||
|
typedef struct {
|
||||||
|
char *workdir;
|
||||||
|
char *keyfile;
|
||||||
|
char *cacert;
|
||||||
|
char *servercert;
|
||||||
|
char *serverkey;
|
||||||
|
char *clientcert;
|
||||||
|
char *clientkey;
|
||||||
|
} TestMigrateTLSX509Data;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool verifyclient;
|
||||||
|
bool clientcert;
|
||||||
|
bool hostileclient;
|
||||||
|
bool authzclient;
|
||||||
|
const char *certhostname;
|
||||||
|
const char *certipaddr;
|
||||||
|
} TestMigrateTLSX509;
|
||||||
|
|
||||||
|
static void *
|
||||||
|
migrate_hook_start_tls_x509_common(QTestState *from,
|
||||||
|
QTestState *to,
|
||||||
|
TestMigrateTLSX509 *args)
|
||||||
|
{
|
||||||
|
TestMigrateTLSX509Data *data = g_new0(TestMigrateTLSX509Data, 1);
|
||||||
|
|
||||||
|
data->workdir = g_strdup_printf("%s/tlscredsx5090", tmpfs);
|
||||||
|
data->keyfile = g_strdup_printf("%s/key.pem", data->workdir);
|
||||||
|
|
||||||
|
data->cacert = g_strdup_printf("%s/ca-cert.pem", data->workdir);
|
||||||
|
data->serverkey = g_strdup_printf("%s/server-key.pem", data->workdir);
|
||||||
|
data->servercert = g_strdup_printf("%s/server-cert.pem", data->workdir);
|
||||||
|
if (args->clientcert) {
|
||||||
|
data->clientkey = g_strdup_printf("%s/client-key.pem", data->workdir);
|
||||||
|
data->clientcert = g_strdup_printf("%s/client-cert.pem", data->workdir);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_mkdir_with_parents(data->workdir, 0700);
|
||||||
|
|
||||||
|
test_tls_init(data->keyfile);
|
||||||
|
#ifndef _WIN32
|
||||||
|
g_assert(link(data->keyfile, data->serverkey) == 0);
|
||||||
|
#else
|
||||||
|
g_assert(CreateHardLink(data->serverkey, data->keyfile, NULL) != 0);
|
||||||
|
#endif
|
||||||
|
if (args->clientcert) {
|
||||||
|
#ifndef _WIN32
|
||||||
|
g_assert(link(data->keyfile, data->clientkey) == 0);
|
||||||
|
#else
|
||||||
|
g_assert(CreateHardLink(data->clientkey, data->keyfile, NULL) != 0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
TLS_ROOT_REQ_SIMPLE(cacertreq, data->cacert);
|
||||||
|
if (args->clientcert) {
|
||||||
|
TLS_CERT_REQ_SIMPLE_CLIENT(servercertreq, cacertreq,
|
||||||
|
args->hostileclient ?
|
||||||
|
QCRYPTO_TLS_TEST_CLIENT_HOSTILE_NAME :
|
||||||
|
QCRYPTO_TLS_TEST_CLIENT_NAME,
|
||||||
|
data->clientcert);
|
||||||
|
test_tls_deinit_cert(&servercertreq);
|
||||||
|
}
|
||||||
|
|
||||||
|
TLS_CERT_REQ_SIMPLE_SERVER(clientcertreq, cacertreq,
|
||||||
|
data->servercert,
|
||||||
|
args->certhostname,
|
||||||
|
args->certipaddr);
|
||||||
|
test_tls_deinit_cert(&clientcertreq);
|
||||||
|
test_tls_deinit_cert(&cacertreq);
|
||||||
|
|
||||||
|
qtest_qmp_assert_success(from,
|
||||||
|
"{ 'execute': 'object-add',"
|
||||||
|
" 'arguments': { 'qom-type': 'tls-creds-x509',"
|
||||||
|
" 'id': 'tlscredsx509client0',"
|
||||||
|
" 'endpoint': 'client',"
|
||||||
|
" 'dir': %s,"
|
||||||
|
" 'sanity-check': true,"
|
||||||
|
" 'verify-peer': true} }",
|
||||||
|
data->workdir);
|
||||||
|
migrate_set_parameter_str(from, "tls-creds", "tlscredsx509client0");
|
||||||
|
if (args->certhostname) {
|
||||||
|
migrate_set_parameter_str(from, "tls-hostname", args->certhostname);
|
||||||
|
}
|
||||||
|
|
||||||
|
qtest_qmp_assert_success(to,
|
||||||
|
"{ 'execute': 'object-add',"
|
||||||
|
" 'arguments': { 'qom-type': 'tls-creds-x509',"
|
||||||
|
" 'id': 'tlscredsx509server0',"
|
||||||
|
" 'endpoint': 'server',"
|
||||||
|
" 'dir': %s,"
|
||||||
|
" 'sanity-check': true,"
|
||||||
|
" 'verify-peer': %i} }",
|
||||||
|
data->workdir, args->verifyclient);
|
||||||
|
migrate_set_parameter_str(to, "tls-creds", "tlscredsx509server0");
|
||||||
|
|
||||||
|
if (args->authzclient) {
|
||||||
|
qtest_qmp_assert_success(to,
|
||||||
|
"{ 'execute': 'object-add',"
|
||||||
|
" 'arguments': { 'qom-type': 'authz-simple',"
|
||||||
|
" 'id': 'tlsauthz0',"
|
||||||
|
" 'identity': %s} }",
|
||||||
|
"CN=" QCRYPTO_TLS_TEST_CLIENT_NAME);
|
||||||
|
migrate_set_parameter_str(to, "tls-authz", "tlsauthz0");
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The normal case: match server's cert hostname against
|
||||||
|
* whatever host we were telling QEMU to connect to (if any)
|
||||||
|
*/
|
||||||
|
static void *
|
||||||
|
migrate_hook_start_tls_x509_default_host(QTestState *from,
|
||||||
|
QTestState *to)
|
||||||
|
{
|
||||||
|
TestMigrateTLSX509 args = {
|
||||||
|
.verifyclient = true,
|
||||||
|
.clientcert = true,
|
||||||
|
.certipaddr = "127.0.0.1"
|
||||||
|
};
|
||||||
|
return migrate_hook_start_tls_x509_common(from, to, &args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The unusual case: the server's cert is different from
|
||||||
|
* the address we're telling QEMU to connect to (if any),
|
||||||
|
* so we must give QEMU an explicit hostname to validate
|
||||||
|
*/
|
||||||
|
static void *
|
||||||
|
migrate_hook_start_tls_x509_override_host(QTestState *from,
|
||||||
|
QTestState *to)
|
||||||
|
{
|
||||||
|
TestMigrateTLSX509 args = {
|
||||||
|
.verifyclient = true,
|
||||||
|
.clientcert = true,
|
||||||
|
.certhostname = "qemu.org",
|
||||||
|
};
|
||||||
|
return migrate_hook_start_tls_x509_common(from, to, &args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The unusual case: the server's cert is different from
|
||||||
|
* the address we're telling QEMU to connect to, and so we
|
||||||
|
* expect the client to reject the server
|
||||||
|
*/
|
||||||
|
static void *
|
||||||
|
migrate_hook_start_tls_x509_mismatch_host(QTestState *from,
|
||||||
|
QTestState *to)
|
||||||
|
{
|
||||||
|
TestMigrateTLSX509 args = {
|
||||||
|
.verifyclient = true,
|
||||||
|
.clientcert = true,
|
||||||
|
.certipaddr = "10.0.0.1",
|
||||||
|
};
|
||||||
|
return migrate_hook_start_tls_x509_common(from, to, &args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *
|
||||||
|
migrate_hook_start_tls_x509_friendly_client(QTestState *from,
|
||||||
|
QTestState *to)
|
||||||
|
{
|
||||||
|
TestMigrateTLSX509 args = {
|
||||||
|
.verifyclient = true,
|
||||||
|
.clientcert = true,
|
||||||
|
.authzclient = true,
|
||||||
|
.certipaddr = "127.0.0.1",
|
||||||
|
};
|
||||||
|
return migrate_hook_start_tls_x509_common(from, to, &args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *
|
||||||
|
migrate_hook_start_tls_x509_hostile_client(QTestState *from,
|
||||||
|
QTestState *to)
|
||||||
|
{
|
||||||
|
TestMigrateTLSX509 args = {
|
||||||
|
.verifyclient = true,
|
||||||
|
.clientcert = true,
|
||||||
|
.hostileclient = true,
|
||||||
|
.authzclient = true,
|
||||||
|
.certipaddr = "127.0.0.1",
|
||||||
|
};
|
||||||
|
return migrate_hook_start_tls_x509_common(from, to, &args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The case with no client certificate presented,
|
||||||
|
* and no server verification
|
||||||
|
*/
|
||||||
|
static void *
|
||||||
|
migrate_hook_start_tls_x509_allow_anon_client(QTestState *from,
|
||||||
|
QTestState *to)
|
||||||
|
{
|
||||||
|
TestMigrateTLSX509 args = {
|
||||||
|
.certipaddr = "127.0.0.1",
|
||||||
|
};
|
||||||
|
return migrate_hook_start_tls_x509_common(from, to, &args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The case with no client certificate presented,
|
||||||
|
* and server verification rejecting
|
||||||
|
*/
|
||||||
|
static void *
|
||||||
|
migrate_hook_start_tls_x509_reject_anon_client(QTestState *from,
|
||||||
|
QTestState *to)
|
||||||
|
{
|
||||||
|
TestMigrateTLSX509 args = {
|
||||||
|
.verifyclient = true,
|
||||||
|
.certipaddr = "127.0.0.1",
|
||||||
|
};
|
||||||
|
return migrate_hook_start_tls_x509_common(from, to, &args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
migrate_hook_end_tls_x509(QTestState *from,
|
||||||
|
QTestState *to,
|
||||||
|
void *opaque)
|
||||||
|
{
|
||||||
|
TestMigrateTLSX509Data *data = opaque;
|
||||||
|
|
||||||
|
test_tls_cleanup(data->keyfile);
|
||||||
|
g_free(data->keyfile);
|
||||||
|
|
||||||
|
unlink(data->cacert);
|
||||||
|
g_free(data->cacert);
|
||||||
|
unlink(data->servercert);
|
||||||
|
g_free(data->servercert);
|
||||||
|
unlink(data->serverkey);
|
||||||
|
g_free(data->serverkey);
|
||||||
|
|
||||||
|
if (data->clientcert) {
|
||||||
|
unlink(data->clientcert);
|
||||||
|
g_free(data->clientcert);
|
||||||
|
}
|
||||||
|
if (data->clientkey) {
|
||||||
|
unlink(data->clientkey);
|
||||||
|
g_free(data->clientkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
rmdir(data->workdir);
|
||||||
|
g_free(data->workdir);
|
||||||
|
|
||||||
|
g_free(data);
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_TASN1 */
|
||||||
|
|
||||||
|
static void test_postcopy_tls_psk(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.start_hook = migrate_hook_start_tls_psk_match,
|
||||||
|
.end_hook = migrate_hook_end_tls_psk,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_postcopy_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_postcopy_preempt_tls_psk(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.postcopy_preempt = true,
|
||||||
|
.start_hook = migrate_hook_start_tls_psk_match,
|
||||||
|
.end_hook = migrate_hook_end_tls_psk,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_postcopy_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_postcopy_recovery_tls_psk(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.start_hook = migrate_hook_start_tls_psk_match,
|
||||||
|
.end_hook = migrate_hook_end_tls_psk,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_postcopy_recovery_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This contains preempt+recovery+tls test altogether */
|
||||||
|
static void test_postcopy_preempt_all(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.postcopy_preempt = true,
|
||||||
|
.start_hook = migrate_hook_start_tls_psk_match,
|
||||||
|
.end_hook = migrate_hook_end_tls_psk,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_postcopy_recovery_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_precopy_unix_tls_psk(void)
|
||||||
|
{
|
||||||
|
g_autofree char *uri = g_strdup_printf("unix:%s/migsocket", tmpfs);
|
||||||
|
MigrateCommon args = {
|
||||||
|
.connect_uri = uri,
|
||||||
|
.listen_uri = uri,
|
||||||
|
.start_hook = migrate_hook_start_tls_psk_match,
|
||||||
|
.end_hook = migrate_hook_end_tls_psk,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_precopy_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_TASN1
|
||||||
|
static void test_precopy_unix_tls_x509_default_host(void)
|
||||||
|
{
|
||||||
|
g_autofree char *uri = g_strdup_printf("unix:%s/migsocket", tmpfs);
|
||||||
|
MigrateCommon args = {
|
||||||
|
.start = {
|
||||||
|
.hide_stderr = true,
|
||||||
|
},
|
||||||
|
.connect_uri = uri,
|
||||||
|
.listen_uri = uri,
|
||||||
|
.start_hook = migrate_hook_start_tls_x509_default_host,
|
||||||
|
.end_hook = migrate_hook_end_tls_x509,
|
||||||
|
.result = MIG_TEST_FAIL_DEST_QUIT_ERR,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_precopy_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_precopy_unix_tls_x509_override_host(void)
|
||||||
|
{
|
||||||
|
g_autofree char *uri = g_strdup_printf("unix:%s/migsocket", tmpfs);
|
||||||
|
MigrateCommon args = {
|
||||||
|
.connect_uri = uri,
|
||||||
|
.listen_uri = uri,
|
||||||
|
.start_hook = migrate_hook_start_tls_x509_override_host,
|
||||||
|
.end_hook = migrate_hook_end_tls_x509,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_precopy_common(&args);
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_TASN1 */
|
||||||
|
|
||||||
|
static void test_precopy_tcp_tls_psk_match(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.listen_uri = "tcp:127.0.0.1:0",
|
||||||
|
.start_hook = migrate_hook_start_tls_psk_match,
|
||||||
|
.end_hook = migrate_hook_end_tls_psk,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_precopy_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_precopy_tcp_tls_psk_mismatch(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.start = {
|
||||||
|
.hide_stderr = true,
|
||||||
|
},
|
||||||
|
.listen_uri = "tcp:127.0.0.1:0",
|
||||||
|
.start_hook = migrate_hook_start_tls_psk_mismatch,
|
||||||
|
.end_hook = migrate_hook_end_tls_psk,
|
||||||
|
.result = MIG_TEST_FAIL,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_precopy_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_TASN1
|
||||||
|
static void test_precopy_tcp_tls_x509_default_host(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.listen_uri = "tcp:127.0.0.1:0",
|
||||||
|
.start_hook = migrate_hook_start_tls_x509_default_host,
|
||||||
|
.end_hook = migrate_hook_end_tls_x509,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_precopy_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_precopy_tcp_tls_x509_override_host(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.listen_uri = "tcp:127.0.0.1:0",
|
||||||
|
.start_hook = migrate_hook_start_tls_x509_override_host,
|
||||||
|
.end_hook = migrate_hook_end_tls_x509,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_precopy_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_precopy_tcp_tls_x509_mismatch_host(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.start = {
|
||||||
|
.hide_stderr = true,
|
||||||
|
},
|
||||||
|
.listen_uri = "tcp:127.0.0.1:0",
|
||||||
|
.start_hook = migrate_hook_start_tls_x509_mismatch_host,
|
||||||
|
.end_hook = migrate_hook_end_tls_x509,
|
||||||
|
.result = MIG_TEST_FAIL_DEST_QUIT_ERR,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_precopy_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_precopy_tcp_tls_x509_friendly_client(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.listen_uri = "tcp:127.0.0.1:0",
|
||||||
|
.start_hook = migrate_hook_start_tls_x509_friendly_client,
|
||||||
|
.end_hook = migrate_hook_end_tls_x509,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_precopy_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_precopy_tcp_tls_x509_hostile_client(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.start = {
|
||||||
|
.hide_stderr = true,
|
||||||
|
},
|
||||||
|
.listen_uri = "tcp:127.0.0.1:0",
|
||||||
|
.start_hook = migrate_hook_start_tls_x509_hostile_client,
|
||||||
|
.end_hook = migrate_hook_end_tls_x509,
|
||||||
|
.result = MIG_TEST_FAIL,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_precopy_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_precopy_tcp_tls_x509_allow_anon_client(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.listen_uri = "tcp:127.0.0.1:0",
|
||||||
|
.start_hook = migrate_hook_start_tls_x509_allow_anon_client,
|
||||||
|
.end_hook = migrate_hook_end_tls_x509,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_precopy_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_precopy_tcp_tls_x509_reject_anon_client(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.start = {
|
||||||
|
.hide_stderr = true,
|
||||||
|
},
|
||||||
|
.listen_uri = "tcp:127.0.0.1:0",
|
||||||
|
.start_hook = migrate_hook_start_tls_x509_reject_anon_client,
|
||||||
|
.end_hook = migrate_hook_end_tls_x509,
|
||||||
|
.result = MIG_TEST_FAIL,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_precopy_common(&args);
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_TASN1 */
|
||||||
|
|
||||||
|
static void *
|
||||||
|
migrate_hook_start_multifd_tcp_tls_psk_match(QTestState *from,
|
||||||
|
QTestState *to)
|
||||||
|
{
|
||||||
|
migrate_hook_start_precopy_tcp_multifd_common(from, to, "none");
|
||||||
|
return migrate_hook_start_tls_psk_match(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *
|
||||||
|
migrate_hook_start_multifd_tcp_tls_psk_mismatch(QTestState *from,
|
||||||
|
QTestState *to)
|
||||||
|
{
|
||||||
|
migrate_hook_start_precopy_tcp_multifd_common(from, to, "none");
|
||||||
|
return migrate_hook_start_tls_psk_mismatch(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_TASN1
|
||||||
|
static void *
|
||||||
|
migrate_hook_start_multifd_tls_x509_default_host(QTestState *from,
|
||||||
|
QTestState *to)
|
||||||
|
{
|
||||||
|
migrate_hook_start_precopy_tcp_multifd_common(from, to, "none");
|
||||||
|
return migrate_hook_start_tls_x509_default_host(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *
|
||||||
|
migrate_hook_start_multifd_tls_x509_override_host(QTestState *from,
|
||||||
|
QTestState *to)
|
||||||
|
{
|
||||||
|
migrate_hook_start_precopy_tcp_multifd_common(from, to, "none");
|
||||||
|
return migrate_hook_start_tls_x509_override_host(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *
|
||||||
|
migrate_hook_start_multifd_tls_x509_mismatch_host(QTestState *from,
|
||||||
|
QTestState *to)
|
||||||
|
{
|
||||||
|
migrate_hook_start_precopy_tcp_multifd_common(from, to, "none");
|
||||||
|
return migrate_hook_start_tls_x509_mismatch_host(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *
|
||||||
|
migrate_hook_start_multifd_tls_x509_allow_anon_client(QTestState *from,
|
||||||
|
QTestState *to)
|
||||||
|
{
|
||||||
|
migrate_hook_start_precopy_tcp_multifd_common(from, to, "none");
|
||||||
|
return migrate_hook_start_tls_x509_allow_anon_client(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *
|
||||||
|
migrate_hook_start_multifd_tls_x509_reject_anon_client(QTestState *from,
|
||||||
|
QTestState *to)
|
||||||
|
{
|
||||||
|
migrate_hook_start_precopy_tcp_multifd_common(from, to, "none");
|
||||||
|
return migrate_hook_start_tls_x509_reject_anon_client(from, to);
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_TASN1 */
|
||||||
|
|
||||||
|
static void test_multifd_tcp_tls_psk_match(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.listen_uri = "defer",
|
||||||
|
.start_hook = migrate_hook_start_multifd_tcp_tls_psk_match,
|
||||||
|
.end_hook = migrate_hook_end_tls_psk,
|
||||||
|
};
|
||||||
|
test_precopy_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_multifd_tcp_tls_psk_mismatch(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.start = {
|
||||||
|
.hide_stderr = true,
|
||||||
|
},
|
||||||
|
.listen_uri = "defer",
|
||||||
|
.start_hook = migrate_hook_start_multifd_tcp_tls_psk_mismatch,
|
||||||
|
.end_hook = migrate_hook_end_tls_psk,
|
||||||
|
.result = MIG_TEST_FAIL,
|
||||||
|
};
|
||||||
|
test_precopy_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_TASN1
|
||||||
|
static void test_multifd_tcp_tls_x509_default_host(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.listen_uri = "defer",
|
||||||
|
.start_hook = migrate_hook_start_multifd_tls_x509_default_host,
|
||||||
|
.end_hook = migrate_hook_end_tls_x509,
|
||||||
|
};
|
||||||
|
test_precopy_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_multifd_tcp_tls_x509_override_host(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.listen_uri = "defer",
|
||||||
|
.start_hook = migrate_hook_start_multifd_tls_x509_override_host,
|
||||||
|
.end_hook = migrate_hook_end_tls_x509,
|
||||||
|
};
|
||||||
|
test_precopy_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_multifd_tcp_tls_x509_mismatch_host(void)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This has different behaviour to the non-multifd case.
|
||||||
|
*
|
||||||
|
* In non-multifd case when client aborts due to mismatched
|
||||||
|
* cert host, the server has already started trying to load
|
||||||
|
* migration state, and so it exits with I/O failure.
|
||||||
|
*
|
||||||
|
* In multifd case when client aborts due to mismatched
|
||||||
|
* cert host, the server is still waiting for the other
|
||||||
|
* multifd connections to arrive so hasn't started trying
|
||||||
|
* to load migration state, and thus just aborts the migration
|
||||||
|
* without exiting.
|
||||||
|
*/
|
||||||
|
MigrateCommon args = {
|
||||||
|
.start = {
|
||||||
|
.hide_stderr = true,
|
||||||
|
},
|
||||||
|
.listen_uri = "defer",
|
||||||
|
.start_hook = migrate_hook_start_multifd_tls_x509_mismatch_host,
|
||||||
|
.end_hook = migrate_hook_end_tls_x509,
|
||||||
|
.result = MIG_TEST_FAIL,
|
||||||
|
};
|
||||||
|
test_precopy_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_multifd_tcp_tls_x509_allow_anon_client(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.listen_uri = "defer",
|
||||||
|
.start_hook = migrate_hook_start_multifd_tls_x509_allow_anon_client,
|
||||||
|
.end_hook = migrate_hook_end_tls_x509,
|
||||||
|
};
|
||||||
|
test_precopy_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_multifd_tcp_tls_x509_reject_anon_client(void)
|
||||||
|
{
|
||||||
|
MigrateCommon args = {
|
||||||
|
.start = {
|
||||||
|
.hide_stderr = true,
|
||||||
|
},
|
||||||
|
.listen_uri = "defer",
|
||||||
|
.start_hook = migrate_hook_start_multifd_tls_x509_reject_anon_client,
|
||||||
|
.end_hook = migrate_hook_end_tls_x509,
|
||||||
|
.result = MIG_TEST_FAIL,
|
||||||
|
};
|
||||||
|
test_precopy_common(&args);
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_TASN1 */
|
||||||
|
|
||||||
|
void migration_test_add_tls(MigrationTestEnv *env)
|
||||||
|
{
|
||||||
|
tmpfs = env->tmpfs;
|
||||||
|
|
||||||
|
migration_test_add("/migration/precopy/unix/tls/psk",
|
||||||
|
test_precopy_unix_tls_psk);
|
||||||
|
|
||||||
|
if (env->has_uffd) {
|
||||||
|
/*
|
||||||
|
* NOTE: psk test is enough for postcopy, as other types of TLS
|
||||||
|
* channels are tested under precopy. Here what we want to test is the
|
||||||
|
* general postcopy path that has TLS channel enabled.
|
||||||
|
*/
|
||||||
|
migration_test_add("/migration/postcopy/tls/psk",
|
||||||
|
test_postcopy_tls_psk);
|
||||||
|
migration_test_add("/migration/postcopy/recovery/tls/psk",
|
||||||
|
test_postcopy_recovery_tls_psk);
|
||||||
|
migration_test_add("/migration/postcopy/preempt/tls/psk",
|
||||||
|
test_postcopy_preempt_tls_psk);
|
||||||
|
migration_test_add("/migration/postcopy/preempt/recovery/tls/psk",
|
||||||
|
test_postcopy_preempt_all);
|
||||||
|
}
|
||||||
|
#ifdef CONFIG_TASN1
|
||||||
|
migration_test_add("/migration/precopy/unix/tls/x509/default-host",
|
||||||
|
test_precopy_unix_tls_x509_default_host);
|
||||||
|
migration_test_add("/migration/precopy/unix/tls/x509/override-host",
|
||||||
|
test_precopy_unix_tls_x509_override_host);
|
||||||
|
#endif /* CONFIG_TASN1 */
|
||||||
|
|
||||||
|
migration_test_add("/migration/precopy/tcp/tls/psk/match",
|
||||||
|
test_precopy_tcp_tls_psk_match);
|
||||||
|
migration_test_add("/migration/precopy/tcp/tls/psk/mismatch",
|
||||||
|
test_precopy_tcp_tls_psk_mismatch);
|
||||||
|
#ifdef CONFIG_TASN1
|
||||||
|
migration_test_add("/migration/precopy/tcp/tls/x509/default-host",
|
||||||
|
test_precopy_tcp_tls_x509_default_host);
|
||||||
|
migration_test_add("/migration/precopy/tcp/tls/x509/override-host",
|
||||||
|
test_precopy_tcp_tls_x509_override_host);
|
||||||
|
migration_test_add("/migration/precopy/tcp/tls/x509/mismatch-host",
|
||||||
|
test_precopy_tcp_tls_x509_mismatch_host);
|
||||||
|
migration_test_add("/migration/precopy/tcp/tls/x509/friendly-client",
|
||||||
|
test_precopy_tcp_tls_x509_friendly_client);
|
||||||
|
migration_test_add("/migration/precopy/tcp/tls/x509/hostile-client",
|
||||||
|
test_precopy_tcp_tls_x509_hostile_client);
|
||||||
|
migration_test_add("/migration/precopy/tcp/tls/x509/allow-anon-client",
|
||||||
|
test_precopy_tcp_tls_x509_allow_anon_client);
|
||||||
|
migration_test_add("/migration/precopy/tcp/tls/x509/reject-anon-client",
|
||||||
|
test_precopy_tcp_tls_x509_reject_anon_client);
|
||||||
|
#endif /* CONFIG_TASN1 */
|
||||||
|
|
||||||
|
migration_test_add("/migration/multifd/tcp/tls/psk/match",
|
||||||
|
test_multifd_tcp_tls_psk_match);
|
||||||
|
migration_test_add("/migration/multifd/tcp/tls/psk/mismatch",
|
||||||
|
test_multifd_tcp_tls_psk_mismatch);
|
||||||
|
#ifdef CONFIG_TASN1
|
||||||
|
migration_test_add("/migration/multifd/tcp/tls/x509/default-host",
|
||||||
|
test_multifd_tcp_tls_x509_default_host);
|
||||||
|
migration_test_add("/migration/multifd/tcp/tls/x509/override-host",
|
||||||
|
test_multifd_tcp_tls_x509_override_host);
|
||||||
|
migration_test_add("/migration/multifd/tcp/tls/x509/mismatch-host",
|
||||||
|
test_multifd_tcp_tls_x509_mismatch_host);
|
||||||
|
migration_test_add("/migration/multifd/tcp/tls/x509/allow-anon-client",
|
||||||
|
test_multifd_tcp_tls_x509_allow_anon_client);
|
||||||
|
migration_test_add("/migration/multifd/tcp/tls/x509/reject-anon-client",
|
||||||
|
test_multifd_tcp_tls_x509_reject_anon_client);
|
||||||
|
#endif /* CONFIG_TASN1 */
|
||||||
|
}
|
|
@ -83,7 +83,6 @@ static void test_smram_lock(void)
|
||||||
{
|
{
|
||||||
QPCIBus *pcibus;
|
QPCIBus *pcibus;
|
||||||
QPCIDevice *pcidev;
|
QPCIDevice *pcidev;
|
||||||
QDict *response;
|
|
||||||
QTestState *qts;
|
QTestState *qts;
|
||||||
|
|
||||||
qts = qtest_init("-M q35");
|
qts = qtest_init("-M q35");
|
||||||
|
@ -107,10 +106,7 @@ static void test_smram_lock(void)
|
||||||
g_assert(smram_test_bit(pcidev, MCH_HOST_BRIDGE_SMRAM_D_OPEN) == false);
|
g_assert(smram_test_bit(pcidev, MCH_HOST_BRIDGE_SMRAM_D_OPEN) == false);
|
||||||
|
|
||||||
/* reset */
|
/* reset */
|
||||||
response = qtest_qmp(qts, "{'execute': 'system_reset', 'arguments': {} }");
|
qtest_system_reset(qts);
|
||||||
g_assert(response);
|
|
||||||
g_assert(!qdict_haskey(response, "error"));
|
|
||||||
qobject_unref(response);
|
|
||||||
|
|
||||||
/* check open is settable again */
|
/* check open is settable again */
|
||||||
smram_set_bit(pcidev, MCH_HOST_BRIDGE_SMRAM_D_OPEN, false);
|
smram_set_bit(pcidev, MCH_HOST_BRIDGE_SMRAM_D_OPEN, false);
|
||||||
|
@ -194,7 +190,6 @@ static void test_smram_smbase_lock(void)
|
||||||
{
|
{
|
||||||
QPCIBus *pcibus;
|
QPCIBus *pcibus;
|
||||||
QPCIDevice *pcidev;
|
QPCIDevice *pcidev;
|
||||||
QDict *response;
|
|
||||||
QTestState *qts;
|
QTestState *qts;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
@ -237,10 +232,7 @@ static void test_smram_smbase_lock(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* reset */
|
/* reset */
|
||||||
response = qtest_qmp(qts, "{'execute': 'system_reset', 'arguments': {} }");
|
qtest_system_reset(qts);
|
||||||
g_assert(response);
|
|
||||||
g_assert(!qdict_haskey(response, "error"));
|
|
||||||
qobject_unref(response);
|
|
||||||
|
|
||||||
/* check RAM at SMBASE is available after reset */
|
/* check RAM at SMBASE is available after reset */
|
||||||
g_assert_cmpint(qtest_readb(qts, SMBASE), ==, SMRAM_TEST_PATTERN);
|
g_assert_cmpint(qtest_readb(qts, SMBASE), ==, SMRAM_TEST_PATTERN);
|
||||||
|
|
|
@ -103,8 +103,7 @@ static void restart_qemu_or_continue(char *path)
|
||||||
old_path = g_strdup(path);
|
old_path = g_strdup(path);
|
||||||
qtest_start(path);
|
qtest_start(path);
|
||||||
} else { /* if cmd line is the same, reset the guest */
|
} else { /* if cmd line is the same, reset the guest */
|
||||||
qobject_unref(qmp("{ 'execute': 'system_reset' }"));
|
qtest_system_reset(global_qtest);
|
||||||
qmp_eventwait("RESET");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -169,14 +169,6 @@ static uint32_t reset(uint32_t gpio, unsigned int offset)
|
||||||
return 0x0;
|
return 0x0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void system_reset(void)
|
|
||||||
{
|
|
||||||
QDict *r;
|
|
||||||
r = qtest_qmp(global_qtest, "{'execute': 'system_reset'}");
|
|
||||||
g_assert_false(qdict_haskey(r, "error"));
|
|
||||||
qobject_unref(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_idr_reset_value(void)
|
static void test_idr_reset_value(void)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
|
@ -214,7 +206,7 @@ static void test_idr_reset_value(void)
|
||||||
gpio_writel(GPIO_H, OTYPER, 0xDEADBEEF);
|
gpio_writel(GPIO_H, OTYPER, 0xDEADBEEF);
|
||||||
gpio_writel(GPIO_H, PUPDR, 0xDEADBEEF);
|
gpio_writel(GPIO_H, PUPDR, 0xDEADBEEF);
|
||||||
|
|
||||||
system_reset();
|
qtest_system_reset(global_qtest);
|
||||||
|
|
||||||
uint32_t moder = gpio_readl(GPIO_A, MODER);
|
uint32_t moder = gpio_readl(GPIO_A, MODER);
|
||||||
uint32_t odr = gpio_readl(GPIO_A, ODR);
|
uint32_t odr = gpio_readl(GPIO_A, ODR);
|
||||||
|
|
|
@ -47,14 +47,6 @@ static void syscfg_set_irq(int num, int level)
|
||||||
qtest_set_irq_in(global_qtest, SOC, NULL, num, level);
|
qtest_set_irq_in(global_qtest, SOC, NULL, num, level);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void system_reset(void)
|
|
||||||
{
|
|
||||||
QDict *response;
|
|
||||||
response = qtest_qmp(global_qtest, "{'execute': 'system_reset'}");
|
|
||||||
g_assert(qdict_haskey(response, "return"));
|
|
||||||
qobject_unref(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_reset(void)
|
static void test_reset(void)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
|
@ -182,7 +174,7 @@ static void test_set_only_bits(void)
|
||||||
syscfg_writel(SYSCFG_SWPR2, 0x00000000);
|
syscfg_writel(SYSCFG_SWPR2, 0x00000000);
|
||||||
g_assert_cmphex(syscfg_readl(SYSCFG_SWPR2), ==, 0xFFFFFFFF);
|
g_assert_cmphex(syscfg_readl(SYSCFG_SWPR2), ==, 0xFFFFFFFF);
|
||||||
|
|
||||||
system_reset();
|
qtest_system_reset(global_qtest);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_clear_only_bits(void)
|
static void test_clear_only_bits(void)
|
||||||
|
@ -194,7 +186,7 @@ static void test_clear_only_bits(void)
|
||||||
syscfg_writel(SYSCFG_CFGR1, 0x00000001);
|
syscfg_writel(SYSCFG_CFGR1, 0x00000001);
|
||||||
g_assert_cmphex(syscfg_readl(SYSCFG_CFGR1), ==, 0x00000000);
|
g_assert_cmphex(syscfg_readl(SYSCFG_CFGR1), ==, 0x00000000);
|
||||||
|
|
||||||
system_reset();
|
qtest_system_reset(global_qtest);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_interrupt(void)
|
static void test_interrupt(void)
|
||||||
|
|
|
@ -11,7 +11,8 @@
|
||||||
#include "libqtest.h"
|
#include "libqtest.h"
|
||||||
#include "libqos/pci.h"
|
#include "libqos/pci.h"
|
||||||
#include "libqos/pci-pc.h"
|
#include "libqos/pci-pc.h"
|
||||||
#include "migration-helpers.h"
|
#include "migration/migration-qmp.h"
|
||||||
|
#include "migration/migration-util.h"
|
||||||
#include "qapi/qmp/qdict.h"
|
#include "qapi/qmp/qdict.h"
|
||||||
#include "qapi/qmp/qlist.h"
|
#include "qapi/qmp/qlist.h"
|
||||||
#include "qapi/qmp/qjson.h"
|
#include "qapi/qmp/qjson.h"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue