migration 1st pull for 9.0
- We lost Juan and Leo in the maintainers file - Steven's suspend state fix - Steven's fix for coverity on migrate_mode - Avihai's migration cleanup series -----BEGIN PGP SIGNATURE----- iIgEABYKADAWIQS5GE3CDMRX2s990ak7X8zN86vXBgUCZZY0TxIccGV0ZXJ4QHJl ZGhhdC5jb20ACgkQO1/MzfOr1wbSxgEAoM5g3wkc22lpAlRpU+hJUqT9NVOVQSK+ Fk7XJYTdSgABAKzykA6hAmU5Kj+yVI6jI874SVZbs2FWpFs4osvsKk4D =sfuM -----END PGP SIGNATURE----- Merge tag 'migration-20240104-pull-request' of https://gitlab.com/peterx/qemu into staging migration 1st pull for 9.0 - We lost Juan and Leo in the maintainers file - Steven's suspend state fix - Steven's fix for coverity on migrate_mode - Avihai's migration cleanup series # -----BEGIN PGP SIGNATURE----- # # iIgEABYKADAWIQS5GE3CDMRX2s990ak7X8zN86vXBgUCZZY0TxIccGV0ZXJ4QHJl # ZGhhdC5jb20ACgkQO1/MzfOr1wbSxgEAoM5g3wkc22lpAlRpU+hJUqT9NVOVQSK+ # Fk7XJYTdSgABAKzykA6hAmU5Kj+yVI6jI874SVZbs2FWpFs4osvsKk4D # =sfuM # -----END PGP SIGNATURE----- # gpg: Signature made Thu 04 Jan 2024 04:30:07 GMT # gpg: using EDDSA key B9184DC20CC457DACF7DD1A93B5FCCCDF3ABD706 # gpg: issuer "peterx@redhat.com" # gpg: Good signature from "Peter Xu <xzpeter@gmail.com>" [unknown] # gpg: aka "Peter Xu <peterx@redhat.com>" [unknown] # gpg: WARNING: This key is not certified with a trusted signature! # gpg: There is no indication that the signature belongs to the owner. # Primary key fingerprint: B918 4DC2 0CC4 57DA CF7D D1A9 3B5F CCCD F3AB D706 * tag 'migration-20240104-pull-request' of https://gitlab.com/peterx/qemu: (26 commits) migration: fix coverity migrate_mode finding migration/multifd: Remove unnecessary usage of local Error migration: Remove unnecessary usage of local Error migration: Fix migration_channel_read_peek() error path migration/multifd: Remove error_setg() in migration_ioc_process_incoming() migration/multifd: Fix leaking of Error in TLS error flow migration/multifd: Simplify multifd_channel_connect() if else statement migration/multifd: Fix error message in multifd_recv_initial_packet() migration: Remove errp parameter in migration_fd_process_incoming() migration: Refactor migration_incoming_setup() migration: Remove nulling of hostname in migrate_init() migration: Remove migrate_max_downtime() declaration tests/qtest: postcopy migration with suspend tests/qtest: precopy migration with suspend tests/qtest: option to suspend during migration tests/qtest: migration events migration: preserve suspended for bg_migration migration: preserve suspended for snapshot migration: preserve suspended runstate migration: propagate suspended runstate ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
c8193acc07
1
.mailmap
1
.mailmap
@ -81,6 +81,7 @@ Greg Kurz <groug@kaod.org> <gkurz@linux.vnet.ibm.com>
|
|||||||
Huacai Chen <chenhuacai@kernel.org> <chenhc@lemote.com>
|
Huacai Chen <chenhuacai@kernel.org> <chenhc@lemote.com>
|
||||||
Huacai Chen <chenhuacai@kernel.org> <chenhuacai@loongson.cn>
|
Huacai Chen <chenhuacai@kernel.org> <chenhuacai@loongson.cn>
|
||||||
James Hogan <jhogan@kernel.org> <james.hogan@imgtec.com>
|
James Hogan <jhogan@kernel.org> <james.hogan@imgtec.com>
|
||||||
|
Juan Quintela <quintela@trasno.org> <quintela@redhat.com>
|
||||||
Leif Lindholm <quic_llindhol@quicinc.com> <leif.lindholm@linaro.org>
|
Leif Lindholm <quic_llindhol@quicinc.com> <leif.lindholm@linaro.org>
|
||||||
Leif Lindholm <quic_llindhol@quicinc.com> <leif@nuviainc.com>
|
Leif Lindholm <quic_llindhol@quicinc.com> <leif@nuviainc.com>
|
||||||
Luc Michel <luc@lmichel.fr> <luc.michel@git.antfield.fr>
|
Luc Michel <luc@lmichel.fr> <luc.michel@git.antfield.fr>
|
||||||
|
@ -70,7 +70,6 @@ R: Daniel P. Berrangé <berrange@redhat.com>
|
|||||||
R: Thomas Huth <thuth@redhat.com>
|
R: Thomas Huth <thuth@redhat.com>
|
||||||
R: Markus Armbruster <armbru@redhat.com>
|
R: Markus Armbruster <armbru@redhat.com>
|
||||||
R: Philippe Mathieu-Daudé <philmd@linaro.org>
|
R: Philippe Mathieu-Daudé <philmd@linaro.org>
|
||||||
R: Juan Quintela <quintela@redhat.com>
|
|
||||||
W: https://www.qemu.org/docs/master/devel/index.html
|
W: https://www.qemu.org/docs/master/devel/index.html
|
||||||
S: Odd Fixes
|
S: Odd Fixes
|
||||||
F: docs/devel/style.rst
|
F: docs/devel/style.rst
|
||||||
@ -3355,10 +3354,8 @@ S: Odd Fixes
|
|||||||
F: scripts/checkpatch.pl
|
F: scripts/checkpatch.pl
|
||||||
|
|
||||||
Migration
|
Migration
|
||||||
M: Juan Quintela <quintela@redhat.com>
|
|
||||||
M: Peter Xu <peterx@redhat.com>
|
M: Peter Xu <peterx@redhat.com>
|
||||||
M: Fabiano Rosas <farosas@suse.de>
|
M: Fabiano Rosas <farosas@suse.de>
|
||||||
R: Leonardo Bras <leobras@redhat.com>
|
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/core/vmstate-if.c
|
F: hw/core/vmstate-if.c
|
||||||
F: include/hw/vmstate-if.h
|
F: include/hw/vmstate-if.h
|
||||||
@ -3375,10 +3372,8 @@ F: util/userfaultfd.c
|
|||||||
X: migration/rdma*
|
X: migration/rdma*
|
||||||
|
|
||||||
RDMA Migration
|
RDMA Migration
|
||||||
M: Juan Quintela <quintela@redhat.com>
|
|
||||||
R: Li Zhijian <lizhijian@fujitsu.com>
|
R: Li Zhijian <lizhijian@fujitsu.com>
|
||||||
R: Peter Xu <peterx@redhat.com>
|
R: Peter Xu <peterx@redhat.com>
|
||||||
R: Leonardo Bras <leobras@redhat.com>
|
|
||||||
S: Odd Fixes
|
S: Odd Fixes
|
||||||
F: migration/rdma*
|
F: migration/rdma*
|
||||||
|
|
||||||
|
@ -904,7 +904,7 @@ static void tpm_emulator_vm_state_change(void *opaque, bool running,
|
|||||||
|
|
||||||
trace_tpm_emulator_vm_state_change(running, state);
|
trace_tpm_emulator_vm_state_change(running, state);
|
||||||
|
|
||||||
if (!running || state != RUN_STATE_RUNNING || !tpm_emu->relock_storage) {
|
if (!running || !tpm_emu->relock_storage) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2451,7 +2451,7 @@ static void usb_ehci_vm_state_change(void *opaque, bool running, RunState state)
|
|||||||
* USB-devices which have async handled packages have a packet in the
|
* USB-devices which have async handled packages have a packet in the
|
||||||
* ep queue to match the completion with.
|
* ep queue to match the completion with.
|
||||||
*/
|
*/
|
||||||
if (state == RUN_STATE_RUNNING) {
|
if (running) {
|
||||||
ehci_advance_async_state(ehci);
|
ehci_advance_async_state(ehci);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1403,7 +1403,7 @@ static void usbredir_vm_state_change(void *priv, bool running, RunState state)
|
|||||||
{
|
{
|
||||||
USBRedirDevice *dev = priv;
|
USBRedirDevice *dev = priv;
|
||||||
|
|
||||||
if (state == RUN_STATE_RUNNING && dev->parser != NULL) {
|
if (running && dev->parser != NULL) {
|
||||||
usbredirparser_do_write(dev->parser); /* Flush any pending writes */
|
usbredirparser_do_write(dev->parser); /* Flush any pending writes */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -623,7 +623,7 @@ void xen_hvm_change_state_handler(void *opaque, bool running,
|
|||||||
|
|
||||||
xen_set_ioreq_server_state(xen_domid,
|
xen_set_ioreq_server_state(xen_domid,
|
||||||
state->ioservid,
|
state->ioservid,
|
||||||
(rstate == RUN_STATE_RUNNING));
|
running);
|
||||||
}
|
}
|
||||||
|
|
||||||
void xen_exit_notifier(Notifier *n, void *data)
|
void xen_exit_notifier(Notifier *n, void *data)
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
#define QEMU_MIGRATION_SNAPSHOT_H
|
#define QEMU_MIGRATION_SNAPSHOT_H
|
||||||
|
|
||||||
#include "qapi/qapi-builtin-types.h"
|
#include "qapi/qapi-builtin-types.h"
|
||||||
|
#include "qapi/qapi-types-run-state.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* save_snapshot: Save an internal snapshot.
|
* save_snapshot: Save an internal snapshot.
|
||||||
@ -61,4 +62,10 @@ bool delete_snapshot(const char *name,
|
|||||||
bool has_devices, strList *devices,
|
bool has_devices, strList *devices,
|
||||||
Error **errp);
|
Error **errp);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* load_snapshot_resume: Restore runstate after loading snapshot.
|
||||||
|
* @state: state to restore
|
||||||
|
*/
|
||||||
|
void load_snapshot_resume(RunState state);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -40,6 +40,15 @@ static inline bool shutdown_caused_by_guest(ShutdownCause cause)
|
|||||||
return cause >= SHUTDOWN_CAUSE_GUEST_SHUTDOWN;
|
return cause >= SHUTDOWN_CAUSE_GUEST_SHUTDOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* In a "live" state, the vcpu clock is ticking, and the runstate notifiers
|
||||||
|
* think we are running.
|
||||||
|
*/
|
||||||
|
static inline bool runstate_is_live(RunState state)
|
||||||
|
{
|
||||||
|
return state == RUN_STATE_RUNNING || state == RUN_STATE_SUSPENDED;
|
||||||
|
}
|
||||||
|
|
||||||
void vm_start(void);
|
void vm_start(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -48,9 +57,20 @@ void vm_start(void);
|
|||||||
* @step_pending: whether any of the CPUs is about to be single-stepped by gdb
|
* @step_pending: whether any of the CPUs is about to be single-stepped by gdb
|
||||||
*/
|
*/
|
||||||
int vm_prepare_start(bool step_pending);
|
int vm_prepare_start(bool step_pending);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* vm_resume: If @state is a live state, start the vm and set the state,
|
||||||
|
* else just set the state.
|
||||||
|
*
|
||||||
|
* @state: the state to restore
|
||||||
|
*/
|
||||||
|
void vm_resume(RunState state);
|
||||||
|
|
||||||
int vm_stop(RunState state);
|
int vm_stop(RunState state);
|
||||||
int vm_stop_force_state(RunState state);
|
int vm_stop_force_state(RunState state);
|
||||||
int vm_shutdown(void);
|
int vm_shutdown(void);
|
||||||
|
void vm_set_suspended(bool suspended);
|
||||||
|
bool vm_get_suspended(void);
|
||||||
|
|
||||||
typedef enum WakeupReason {
|
typedef enum WakeupReason {
|
||||||
/* Always keep QEMU_WAKEUP_REASON_NONE = 0 */
|
/* Always keep QEMU_WAKEUP_REASON_NONE = 0 */
|
||||||
|
@ -117,9 +117,12 @@ int migration_channel_read_peek(QIOChannel *ioc,
|
|||||||
len = qio_channel_readv_full(ioc, &iov, 1, NULL, NULL,
|
len = qio_channel_readv_full(ioc, &iov, 1, NULL, NULL,
|
||||||
QIO_CHANNEL_READ_FLAG_MSG_PEEK, errp);
|
QIO_CHANNEL_READ_FLAG_MSG_PEEK, errp);
|
||||||
|
|
||||||
if (len <= 0 && len != QIO_CHANNEL_ERR_BLOCK) {
|
if (len < 0 && len != QIO_CHANNEL_ERR_BLOCK) {
|
||||||
error_setg(errp,
|
return -1;
|
||||||
"Failed to peek at channel");
|
}
|
||||||
|
|
||||||
|
if (len == 0) {
|
||||||
|
error_setg(errp, "Failed to peek at channel");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,16 @@
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t size;
|
uint32_t size;
|
||||||
uint8_t runstate[100];
|
|
||||||
|
/*
|
||||||
|
* runstate was 100 bytes, zero padded, but we trimmed it to add a
|
||||||
|
* few fields and maintain backwards compatibility.
|
||||||
|
*/
|
||||||
|
uint8_t runstate[32];
|
||||||
|
uint8_t has_vm_was_suspended;
|
||||||
|
uint8_t vm_was_suspended;
|
||||||
|
uint8_t unused[66];
|
||||||
|
|
||||||
RunState state;
|
RunState state;
|
||||||
bool received;
|
bool received;
|
||||||
} GlobalState;
|
} GlobalState;
|
||||||
@ -35,6 +44,10 @@ static void global_state_do_store(RunState state)
|
|||||||
assert(strlen(state_str) < sizeof(global_state.runstate));
|
assert(strlen(state_str) < sizeof(global_state.runstate));
|
||||||
strpadcpy((char *)global_state.runstate, sizeof(global_state.runstate),
|
strpadcpy((char *)global_state.runstate, sizeof(global_state.runstate),
|
||||||
state_str, '\0');
|
state_str, '\0');
|
||||||
|
global_state.has_vm_was_suspended = true;
|
||||||
|
global_state.vm_was_suspended = vm_get_suspended();
|
||||||
|
|
||||||
|
memset(global_state.unused, 0, sizeof(global_state.unused));
|
||||||
}
|
}
|
||||||
|
|
||||||
void global_state_store(void)
|
void global_state_store(void)
|
||||||
@ -59,24 +72,7 @@ RunState global_state_get_runstate(void)
|
|||||||
|
|
||||||
static bool global_state_needed(void *opaque)
|
static bool global_state_needed(void *opaque)
|
||||||
{
|
{
|
||||||
GlobalState *s = opaque;
|
return migrate_get_current()->store_global_state;
|
||||||
char *runstate = (char *)s->runstate;
|
|
||||||
|
|
||||||
/* If it is not optional, it is mandatory */
|
|
||||||
|
|
||||||
if (migrate_get_current()->store_global_state) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If state is running or paused, it is not needed */
|
|
||||||
|
|
||||||
if (strcmp(runstate, "running") == 0 ||
|
|
||||||
strcmp(runstate, "paused") == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* for any other state it is needed */
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int global_state_post_load(void *opaque, int version_id)
|
static int global_state_post_load(void *opaque, int version_id)
|
||||||
@ -93,7 +89,7 @@ static int global_state_post_load(void *opaque, int version_id)
|
|||||||
sizeof(s->runstate)) == sizeof(s->runstate)) {
|
sizeof(s->runstate)) == sizeof(s->runstate)) {
|
||||||
/*
|
/*
|
||||||
* This condition should never happen during migration, because
|
* This condition should never happen during migration, because
|
||||||
* all runstate names are shorter than 100 bytes (the size of
|
* all runstate names are shorter than 32 bytes (the size of
|
||||||
* s->runstate). However, a malicious stream could overflow
|
* s->runstate). However, a malicious stream could overflow
|
||||||
* the qapi_enum_parse() call, so we force the last character
|
* the qapi_enum_parse() call, so we force the last character
|
||||||
* to a NUL byte.
|
* to a NUL byte.
|
||||||
@ -110,6 +106,14 @@ static int global_state_post_load(void *opaque, int version_id)
|
|||||||
}
|
}
|
||||||
s->state = r;
|
s->state = r;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* global_state is saved on the outgoing side before forcing a stopped
|
||||||
|
* state, so it may have saved state=suspended and vm_was_suspended=0.
|
||||||
|
* Now we are in a paused state, and when we later call vm_start, it must
|
||||||
|
* restore the suspended state, so we must set vm_was_suspended=1 here.
|
||||||
|
*/
|
||||||
|
vm_set_suspended(s->vm_was_suspended || r == RUN_STATE_SUSPENDED);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,6 +138,9 @@ static const VMStateDescription vmstate_globalstate = {
|
|||||||
.fields = (const VMStateField[]) {
|
.fields = (const VMStateField[]) {
|
||||||
VMSTATE_UINT32(size, GlobalState),
|
VMSTATE_UINT32(size, GlobalState),
|
||||||
VMSTATE_BUFFER(runstate, GlobalState),
|
VMSTATE_BUFFER(runstate, GlobalState),
|
||||||
|
VMSTATE_UINT8(has_vm_was_suspended, GlobalState),
|
||||||
|
VMSTATE_UINT8(vm_was_suspended, GlobalState),
|
||||||
|
VMSTATE_BUFFER(unused, GlobalState),
|
||||||
VMSTATE_END_OF_LIST()
|
VMSTATE_END_OF_LIST()
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -399,15 +399,17 @@ void hmp_info_migrate_parameters(Monitor *mon, const QDict *qdict)
|
|||||||
|
|
||||||
void hmp_loadvm(Monitor *mon, const QDict *qdict)
|
void hmp_loadvm(Monitor *mon, const QDict *qdict)
|
||||||
{
|
{
|
||||||
int saved_vm_running = runstate_is_running();
|
RunState saved_state = runstate_get();
|
||||||
|
|
||||||
const char *name = qdict_get_str(qdict, "name");
|
const char *name = qdict_get_str(qdict, "name");
|
||||||
Error *err = NULL;
|
Error *err = NULL;
|
||||||
|
|
||||||
vm_stop(RUN_STATE_RESTORE_VM);
|
vm_stop(RUN_STATE_RESTORE_VM);
|
||||||
|
|
||||||
if (load_snapshot(name, NULL, false, NULL, &err) && saved_vm_running) {
|
if (load_snapshot(name, NULL, false, NULL, &err)) {
|
||||||
vm_start();
|
load_snapshot_resume(saved_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
hmp_handle_error(mon, err);
|
hmp_handle_error(mon, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -604,7 +604,7 @@ static void process_incoming_migration_bh(void *opaque)
|
|||||||
*/
|
*/
|
||||||
if (!migrate_late_block_activate() ||
|
if (!migrate_late_block_activate() ||
|
||||||
(autostart && (!global_state_received() ||
|
(autostart && (!global_state_received() ||
|
||||||
global_state_get_runstate() == RUN_STATE_RUNNING))) {
|
runstate_is_live(global_state_get_runstate())))) {
|
||||||
/* Make sure all file formats throw away their mutable metadata.
|
/* Make sure all file formats throw away their mutable metadata.
|
||||||
* If we get an error here, just don't restart the VM yet. */
|
* If we get an error here, just don't restart the VM yet. */
|
||||||
bdrv_activate_all(&local_err);
|
bdrv_activate_all(&local_err);
|
||||||
@ -628,7 +628,7 @@ static void process_incoming_migration_bh(void *opaque)
|
|||||||
dirty_bitmap_mig_before_vm_start();
|
dirty_bitmap_mig_before_vm_start();
|
||||||
|
|
||||||
if (!global_state_received() ||
|
if (!global_state_received() ||
|
||||||
global_state_get_runstate() == RUN_STATE_RUNNING) {
|
runstate_is_live(global_state_get_runstate())) {
|
||||||
if (autostart) {
|
if (autostart) {
|
||||||
vm_start();
|
vm_start();
|
||||||
} else {
|
} else {
|
||||||
@ -724,11 +724,8 @@ fail:
|
|||||||
/**
|
/**
|
||||||
* migration_incoming_setup: Setup incoming migration
|
* migration_incoming_setup: Setup incoming migration
|
||||||
* @f: file for main migration channel
|
* @f: file for main migration channel
|
||||||
* @errp: where to put errors
|
|
||||||
*
|
|
||||||
* Returns: %true on success, %false on error.
|
|
||||||
*/
|
*/
|
||||||
static bool migration_incoming_setup(QEMUFile *f, Error **errp)
|
static void migration_incoming_setup(QEMUFile *f)
|
||||||
{
|
{
|
||||||
MigrationIncomingState *mis = migration_incoming_get_current();
|
MigrationIncomingState *mis = migration_incoming_get_current();
|
||||||
|
|
||||||
@ -736,7 +733,6 @@ static bool migration_incoming_setup(QEMUFile *f, Error **errp)
|
|||||||
mis->from_src_file = f;
|
mis->from_src_file = f;
|
||||||
}
|
}
|
||||||
qemu_file_set_blocking(f, false);
|
qemu_file_set_blocking(f, false);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void migration_incoming_process(void)
|
void migration_incoming_process(void)
|
||||||
@ -778,11 +774,9 @@ static bool postcopy_try_recover(void)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void migration_fd_process_incoming(QEMUFile *f, Error **errp)
|
void migration_fd_process_incoming(QEMUFile *f)
|
||||||
{
|
{
|
||||||
if (!migration_incoming_setup(f, errp)) {
|
migration_incoming_setup(f);
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (postcopy_try_recover()) {
|
if (postcopy_try_recover()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -836,10 +830,9 @@ void migration_ioc_process_incoming(QIOChannel *ioc, Error **errp)
|
|||||||
* issue is not possible.
|
* issue is not possible.
|
||||||
*/
|
*/
|
||||||
ret = migration_channel_read_peek(ioc, (void *)&channel_magic,
|
ret = migration_channel_read_peek(ioc, (void *)&channel_magic,
|
||||||
sizeof(channel_magic), &local_err);
|
sizeof(channel_magic), errp);
|
||||||
|
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
error_propagate(errp, local_err);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -849,16 +842,12 @@ void migration_ioc_process_incoming(QIOChannel *ioc, Error **errp)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (multifd_load_setup(errp) != 0) {
|
if (multifd_load_setup(errp) != 0) {
|
||||||
error_setg(errp, "Failed to setup multifd channels");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (default_channel) {
|
if (default_channel) {
|
||||||
f = qemu_file_new_input(ioc);
|
f = qemu_file_new_input(ioc);
|
||||||
|
migration_incoming_setup(f);
|
||||||
if (!migration_incoming_setup(f, errp)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
/* Multiple connections */
|
/* Multiple connections */
|
||||||
assert(migration_needs_multiple_sockets());
|
assert(migration_needs_multiple_sockets());
|
||||||
@ -1588,7 +1577,6 @@ int migrate_init(MigrationState *s, Error **errp)
|
|||||||
s->migration_thread_running = false;
|
s->migration_thread_running = false;
|
||||||
error_free(s->error);
|
error_free(s->error);
|
||||||
s->error = NULL;
|
s->error = NULL;
|
||||||
s->hostname = NULL;
|
|
||||||
s->vmdesc = NULL;
|
s->vmdesc = NULL;
|
||||||
|
|
||||||
migrate_set_state(&s->state, MIGRATION_STATUS_NONE, MIGRATION_STATUS_SETUP);
|
migrate_set_state(&s->state, MIGRATION_STATUS_NONE, MIGRATION_STATUS_SETUP);
|
||||||
@ -1836,8 +1824,6 @@ bool migration_is_blocked(Error **errp)
|
|||||||
static bool migrate_prepare(MigrationState *s, bool blk, bool blk_inc,
|
static bool migrate_prepare(MigrationState *s, bool blk, bool blk_inc,
|
||||||
bool resume, Error **errp)
|
bool resume, Error **errp)
|
||||||
{
|
{
|
||||||
Error *local_err = NULL;
|
|
||||||
|
|
||||||
if (blk_inc) {
|
if (blk_inc) {
|
||||||
warn_report("parameter 'inc' is deprecated;"
|
warn_report("parameter 'inc' is deprecated;"
|
||||||
" use blockdev-mirror with NBD instead");
|
" use blockdev-mirror with NBD instead");
|
||||||
@ -1907,8 +1893,7 @@ static bool migrate_prepare(MigrationState *s, bool blk, bool blk_inc,
|
|||||||
"current migration capabilities");
|
"current migration capabilities");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!migrate_cap_set(MIGRATION_CAPABILITY_BLOCK, true, &local_err)) {
|
if (!migrate_cap_set(MIGRATION_CAPABILITY_BLOCK, true, errp)) {
|
||||||
error_propagate(errp, local_err);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
s->must_remove_block_options = true;
|
s->must_remove_block_options = true;
|
||||||
@ -2416,7 +2401,6 @@ static int postcopy_start(MigrationState *ms, Error **errp)
|
|||||||
|
|
||||||
migration_downtime_start(ms);
|
migration_downtime_start(ms);
|
||||||
|
|
||||||
qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER, NULL);
|
|
||||||
global_state_store();
|
global_state_store();
|
||||||
ret = migration_stop_vm(RUN_STATE_FINISH_MIGRATE);
|
ret = migration_stop_vm(RUN_STATE_FINISH_MIGRATE);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
@ -2615,7 +2599,6 @@ static int migration_completion_precopy(MigrationState *s,
|
|||||||
|
|
||||||
qemu_mutex_lock_iothread();
|
qemu_mutex_lock_iothread();
|
||||||
migration_downtime_start(s);
|
migration_downtime_start(s);
|
||||||
qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER, NULL);
|
|
||||||
|
|
||||||
s->vm_old_state = runstate_get();
|
s->vm_old_state = runstate_get();
|
||||||
global_state_store();
|
global_state_store();
|
||||||
@ -3136,7 +3119,7 @@ static void migration_iteration_finish(MigrationState *s)
|
|||||||
case MIGRATION_STATUS_FAILED:
|
case MIGRATION_STATUS_FAILED:
|
||||||
case MIGRATION_STATUS_CANCELLED:
|
case MIGRATION_STATUS_CANCELLED:
|
||||||
case MIGRATION_STATUS_CANCELLING:
|
case MIGRATION_STATUS_CANCELLING:
|
||||||
if (s->vm_old_state == RUN_STATE_RUNNING) {
|
if (runstate_is_live(s->vm_old_state)) {
|
||||||
if (!runstate_check(RUN_STATE_SHUTDOWN)) {
|
if (!runstate_check(RUN_STATE_SHUTDOWN)) {
|
||||||
vm_start();
|
vm_start();
|
||||||
}
|
}
|
||||||
@ -3392,7 +3375,7 @@ static void bg_migration_vm_start_bh(void *opaque)
|
|||||||
qemu_bh_delete(s->vm_start_bh);
|
qemu_bh_delete(s->vm_start_bh);
|
||||||
s->vm_start_bh = NULL;
|
s->vm_start_bh = NULL;
|
||||||
|
|
||||||
vm_start();
|
vm_resume(s->vm_old_state);
|
||||||
migration_downtime_end(s);
|
migration_downtime_end(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3464,11 +3447,6 @@ static void *bg_migration_thread(void *opaque)
|
|||||||
|
|
||||||
qemu_mutex_lock_iothread();
|
qemu_mutex_lock_iothread();
|
||||||
|
|
||||||
/*
|
|
||||||
* If VM is currently in suspended state, then, to make a valid runstate
|
|
||||||
* transition in vm_stop_force_state() we need to wakeup it up.
|
|
||||||
*/
|
|
||||||
qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER, NULL);
|
|
||||||
s->vm_old_state = runstate_get();
|
s->vm_old_state = runstate_get();
|
||||||
|
|
||||||
global_state_store();
|
global_state_store();
|
||||||
|
@ -474,14 +474,12 @@ struct MigrationState {
|
|||||||
|
|
||||||
void migrate_set_state(int *state, int old_state, int new_state);
|
void migrate_set_state(int *state, int old_state, int new_state);
|
||||||
|
|
||||||
void migration_fd_process_incoming(QEMUFile *f, Error **errp);
|
void migration_fd_process_incoming(QEMUFile *f);
|
||||||
void migration_ioc_process_incoming(QIOChannel *ioc, Error **errp);
|
void migration_ioc_process_incoming(QIOChannel *ioc, Error **errp);
|
||||||
void migration_incoming_process(void);
|
void migration_incoming_process(void);
|
||||||
|
|
||||||
bool migration_has_all_channels(void);
|
bool migration_has_all_channels(void);
|
||||||
|
|
||||||
uint64_t migrate_max_downtime(void);
|
|
||||||
|
|
||||||
void migrate_set_error(MigrationState *s, const Error *error);
|
void migrate_set_error(MigrationState *s, const Error *error);
|
||||||
bool migrate_has_error(MigrationState *s);
|
bool migrate_has_error(MigrationState *s);
|
||||||
|
|
||||||
|
@ -228,8 +228,8 @@ static int multifd_recv_initial_packet(QIOChannel *c, Error **errp)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (msg.id > migrate_multifd_channels()) {
|
if (msg.id > migrate_multifd_channels()) {
|
||||||
error_setg(errp, "multifd: received channel version %u "
|
error_setg(errp, "multifd: received channel id %u is greater than "
|
||||||
"expected %u", msg.version, MULTIFD_VERSION);
|
"number of channels %u", msg.id, migrate_multifd_channels());
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -787,6 +787,7 @@ static void multifd_tls_outgoing_handshake(QIOTask *task,
|
|||||||
|
|
||||||
trace_multifd_tls_outgoing_handshake_error(ioc, error_get_pretty(err));
|
trace_multifd_tls_outgoing_handshake_error(ioc, error_get_pretty(err));
|
||||||
|
|
||||||
|
migrate_set_error(migrate_get_current(), err);
|
||||||
/*
|
/*
|
||||||
* Error happen, mark multifd_send_thread status as 'quit' although it
|
* Error happen, mark multifd_send_thread status as 'quit' although it
|
||||||
* is not created, and then tell who pay attention to me.
|
* is not created, and then tell who pay attention to me.
|
||||||
@ -794,6 +795,7 @@ static void multifd_tls_outgoing_handshake(QIOTask *task,
|
|||||||
p->quit = true;
|
p->quit = true;
|
||||||
qemu_sem_post(&multifd_send_state->channels_ready);
|
qemu_sem_post(&multifd_send_state->channels_ready);
|
||||||
qemu_sem_post(&p->sem_sync);
|
qemu_sem_post(&p->sem_sync);
|
||||||
|
error_free(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *multifd_tls_handshake_thread(void *opaque)
|
static void *multifd_tls_handshake_thread(void *opaque)
|
||||||
@ -847,14 +849,13 @@ static bool multifd_channel_connect(MultiFDSendParams *p,
|
|||||||
* so we mustn't call multifd_send_thread until then
|
* so we mustn't call multifd_send_thread until then
|
||||||
*/
|
*/
|
||||||
return multifd_tls_channel_connect(p, ioc, errp);
|
return multifd_tls_channel_connect(p, ioc, errp);
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
|
||||||
migration_ioc_register_yank(ioc);
|
migration_ioc_register_yank(ioc);
|
||||||
p->registered_yank = true;
|
p->registered_yank = true;
|
||||||
p->c = ioc;
|
p->c = ioc;
|
||||||
qemu_thread_create(&p->thread, p->name, multifd_send_thread, p,
|
qemu_thread_create(&p->thread, p->name, multifd_send_thread, p,
|
||||||
QEMU_THREAD_JOINABLE);
|
QEMU_THREAD_JOINABLE);
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -950,12 +951,10 @@ int multifd_save_setup(Error **errp)
|
|||||||
|
|
||||||
for (i = 0; i < thread_count; i++) {
|
for (i = 0; i < thread_count; i++) {
|
||||||
MultiFDSendParams *p = &multifd_send_state->params[i];
|
MultiFDSendParams *p = &multifd_send_state->params[i];
|
||||||
Error *local_err = NULL;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
ret = multifd_send_state->ops->send_setup(p, &local_err);
|
ret = multifd_send_state->ops->send_setup(p, errp);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
error_propagate(errp, local_err);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1194,12 +1193,10 @@ int multifd_load_setup(Error **errp)
|
|||||||
|
|
||||||
for (i = 0; i < thread_count; i++) {
|
for (i = 0; i < thread_count; i++) {
|
||||||
MultiFDRecvParams *p = &multifd_recv_state->params[i];
|
MultiFDRecvParams *p = &multifd_recv_state->params[i];
|
||||||
Error *local_err = NULL;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
ret = multifd_recv_state->ops->recv_setup(p, &local_err);
|
ret = multifd_recv_state->ops->recv_setup(p, errp);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
error_propagate(errp, local_err);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -833,8 +833,10 @@ uint64_t migrate_max_postcopy_bandwidth(void)
|
|||||||
MigMode migrate_mode(void)
|
MigMode migrate_mode(void)
|
||||||
{
|
{
|
||||||
MigrationState *s = migrate_get_current();
|
MigrationState *s = migrate_get_current();
|
||||||
|
MigMode mode = s->parameters.mode;
|
||||||
|
|
||||||
return s->parameters.mode;
|
assert(mode >= 0 && mode < MIG_MODE__MAX);
|
||||||
|
return mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
int migrate_multifd_channels(void)
|
int migrate_multifd_channels(void)
|
||||||
|
@ -4035,7 +4035,6 @@ static void rdma_accept_incoming_migration(void *opaque)
|
|||||||
{
|
{
|
||||||
RDMAContext *rdma = opaque;
|
RDMAContext *rdma = opaque;
|
||||||
QEMUFile *f;
|
QEMUFile *f;
|
||||||
Error *local_err = NULL;
|
|
||||||
|
|
||||||
trace_qemu_rdma_accept_incoming_migration();
|
trace_qemu_rdma_accept_incoming_migration();
|
||||||
if (qemu_rdma_accept(rdma) < 0) {
|
if (qemu_rdma_accept(rdma) < 0) {
|
||||||
@ -4057,10 +4056,7 @@ static void rdma_accept_incoming_migration(void *opaque)
|
|||||||
}
|
}
|
||||||
|
|
||||||
rdma->migration_started_on_destination = 1;
|
rdma->migration_started_on_destination = 1;
|
||||||
migration_fd_process_incoming(f, &local_err);
|
migration_fd_process_incoming(f);
|
||||||
if (local_err) {
|
|
||||||
error_reportf_err(local_err, "RDMA ERROR:");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void rdma_start_incoming_migration(InetSocketAddress *host_port,
|
void rdma_start_incoming_migration(InetSocketAddress *host_port,
|
||||||
|
@ -3046,7 +3046,7 @@ bool save_snapshot(const char *name, bool overwrite, const char *vmstate,
|
|||||||
QEMUSnapshotInfo sn1, *sn = &sn1;
|
QEMUSnapshotInfo sn1, *sn = &sn1;
|
||||||
int ret = -1, ret2;
|
int ret = -1, ret2;
|
||||||
QEMUFile *f;
|
QEMUFile *f;
|
||||||
int saved_vm_running;
|
RunState saved_state = runstate_get();
|
||||||
uint64_t vm_state_size;
|
uint64_t vm_state_size;
|
||||||
g_autoptr(GDateTime) now = g_date_time_new_now_local();
|
g_autoptr(GDateTime) now = g_date_time_new_now_local();
|
||||||
|
|
||||||
@ -3092,8 +3092,6 @@ bool save_snapshot(const char *name, bool overwrite, const char *vmstate,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
saved_vm_running = runstate_is_running();
|
|
||||||
|
|
||||||
global_state_store();
|
global_state_store();
|
||||||
vm_stop(RUN_STATE_SAVE_VM);
|
vm_stop(RUN_STATE_SAVE_VM);
|
||||||
|
|
||||||
@ -3147,9 +3145,7 @@ bool save_snapshot(const char *name, bool overwrite, const char *vmstate,
|
|||||||
the_end:
|
the_end:
|
||||||
bdrv_drain_all_end();
|
bdrv_drain_all_end();
|
||||||
|
|
||||||
if (saved_vm_running) {
|
vm_resume(saved_state);
|
||||||
vm_start();
|
|
||||||
}
|
|
||||||
return ret == 0;
|
return ret == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3317,6 +3313,14 @@ err_drain:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void load_snapshot_resume(RunState state)
|
||||||
|
{
|
||||||
|
vm_resume(state);
|
||||||
|
if (state == RUN_STATE_RUNNING && runstate_get() == RUN_STATE_SUSPENDED) {
|
||||||
|
qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER, &error_abort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool delete_snapshot(const char *name, bool has_devices,
|
bool delete_snapshot(const char *name, bool has_devices,
|
||||||
strList *devices, Error **errp)
|
strList *devices, Error **errp)
|
||||||
{
|
{
|
||||||
@ -3381,16 +3385,15 @@ static void snapshot_load_job_bh(void *opaque)
|
|||||||
{
|
{
|
||||||
Job *job = opaque;
|
Job *job = opaque;
|
||||||
SnapshotJob *s = container_of(job, SnapshotJob, common);
|
SnapshotJob *s = container_of(job, SnapshotJob, common);
|
||||||
int orig_vm_running;
|
RunState orig_state = runstate_get();
|
||||||
|
|
||||||
job_progress_set_remaining(&s->common, 1);
|
job_progress_set_remaining(&s->common, 1);
|
||||||
|
|
||||||
orig_vm_running = runstate_is_running();
|
|
||||||
vm_stop(RUN_STATE_RESTORE_VM);
|
vm_stop(RUN_STATE_RESTORE_VM);
|
||||||
|
|
||||||
s->ret = load_snapshot(s->tag, s->vmstate, true, s->devices, s->errp);
|
s->ret = load_snapshot(s->tag, s->vmstate, true, s->devices, s->errp);
|
||||||
if (s->ret && orig_vm_running) {
|
if (s->ret) {
|
||||||
vm_start();
|
load_snapshot_resume(orig_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
job_progress_update(&s->common, 1);
|
job_progress_update(&s->common, 1);
|
||||||
|
@ -134,7 +134,7 @@
|
|||||||
##
|
##
|
||||||
# @stop:
|
# @stop:
|
||||||
#
|
#
|
||||||
# Stop all guest VCPU execution.
|
# Stop guest VM execution.
|
||||||
#
|
#
|
||||||
# Since: 0.14
|
# Since: 0.14
|
||||||
#
|
#
|
||||||
@ -143,6 +143,9 @@
|
|||||||
# the guest remains paused once migration finishes, as if the -S
|
# the guest remains paused once migration finishes, as if the -S
|
||||||
# option was passed on the command line.
|
# option was passed on the command line.
|
||||||
#
|
#
|
||||||
|
# In the "suspended" state, it will completely stop the VM and
|
||||||
|
# cause a transition to the "paused" state. (Since 9.0)
|
||||||
|
#
|
||||||
# Example:
|
# Example:
|
||||||
#
|
#
|
||||||
# -> { "execute": "stop" }
|
# -> { "execute": "stop" }
|
||||||
@ -153,7 +156,7 @@
|
|||||||
##
|
##
|
||||||
# @cont:
|
# @cont:
|
||||||
#
|
#
|
||||||
# Resume guest VCPU execution.
|
# Resume guest VM execution.
|
||||||
#
|
#
|
||||||
# Since: 0.14
|
# Since: 0.14
|
||||||
#
|
#
|
||||||
@ -165,6 +168,10 @@
|
|||||||
# guest starts once migration finishes, removing the effect of the
|
# guest starts once migration finishes, removing the effect of the
|
||||||
# -S command line option if it was passed.
|
# -S command line option if it was passed.
|
||||||
#
|
#
|
||||||
|
# If the VM was previously suspended, and not been reset or woken,
|
||||||
|
# this command will transition back to the "suspended" state.
|
||||||
|
# (Since 9.0)
|
||||||
|
#
|
||||||
# Example:
|
# Example:
|
||||||
#
|
#
|
||||||
# -> { "execute": "cont" }
|
# -> { "execute": "cont" }
|
||||||
|
@ -102,7 +102,7 @@
|
|||||||
##
|
##
|
||||||
# @StatusInfo:
|
# @StatusInfo:
|
||||||
#
|
#
|
||||||
# Information about VCPU run state
|
# Information about VM run state
|
||||||
#
|
#
|
||||||
# @running: true if all VCPUs are runnable, false if not runnable
|
# @running: true if all VCPUs are runnable, false if not runnable
|
||||||
#
|
#
|
||||||
@ -130,9 +130,9 @@
|
|||||||
##
|
##
|
||||||
# @query-status:
|
# @query-status:
|
||||||
#
|
#
|
||||||
# Query the run status of all VCPUs
|
# Query the run status of the VM
|
||||||
#
|
#
|
||||||
# Returns: @StatusInfo reflecting all VCPUs
|
# Returns: @StatusInfo reflecting the VM
|
||||||
#
|
#
|
||||||
# Since: 0.14
|
# Since: 0.14
|
||||||
#
|
#
|
||||||
|
@ -259,14 +259,33 @@ void cpu_interrupt(CPUState *cpu, int mask)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* True if the vm was previously suspended, and has not been woken or reset.
|
||||||
|
*/
|
||||||
|
static int vm_was_suspended;
|
||||||
|
|
||||||
|
void vm_set_suspended(bool suspended)
|
||||||
|
{
|
||||||
|
vm_was_suspended = suspended;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool vm_get_suspended(void)
|
||||||
|
{
|
||||||
|
return vm_was_suspended;
|
||||||
|
}
|
||||||
|
|
||||||
static int do_vm_stop(RunState state, bool send_stop)
|
static int do_vm_stop(RunState state, bool send_stop)
|
||||||
{
|
{
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
RunState oldstate = runstate_get();
|
||||||
|
|
||||||
if (runstate_is_running()) {
|
if (runstate_is_live(oldstate)) {
|
||||||
|
vm_was_suspended = (oldstate == RUN_STATE_SUSPENDED);
|
||||||
runstate_set(state);
|
runstate_set(state);
|
||||||
cpu_disable_ticks();
|
cpu_disable_ticks();
|
||||||
|
if (oldstate == RUN_STATE_RUNNING) {
|
||||||
pause_all_vcpus();
|
pause_all_vcpus();
|
||||||
|
}
|
||||||
vm_state_notify(0, state);
|
vm_state_notify(0, state);
|
||||||
if (send_stop) {
|
if (send_stop) {
|
||||||
qapi_event_send_stop();
|
qapi_event_send_stop();
|
||||||
@ -679,11 +698,13 @@ int vm_stop(RunState state)
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare for (re)starting the VM.
|
* Prepare for (re)starting the VM.
|
||||||
* Returns -1 if the vCPUs are not to be restarted (e.g. if they are already
|
* Returns 0 if the vCPUs should be restarted, -1 on an error condition,
|
||||||
* running or in case of an error condition), 0 otherwise.
|
* and 1 otherwise.
|
||||||
*/
|
*/
|
||||||
int vm_prepare_start(bool step_pending)
|
int vm_prepare_start(bool step_pending)
|
||||||
{
|
{
|
||||||
|
int ret = vm_was_suspended ? 1 : 0;
|
||||||
|
RunState state = vm_was_suspended ? RUN_STATE_SUSPENDED : RUN_STATE_RUNNING;
|
||||||
RunState requested;
|
RunState requested;
|
||||||
|
|
||||||
qemu_vmstop_requested(&requested);
|
qemu_vmstop_requested(&requested);
|
||||||
@ -714,9 +735,10 @@ int vm_prepare_start(bool step_pending)
|
|||||||
qapi_event_send_resume();
|
qapi_event_send_resume();
|
||||||
|
|
||||||
cpu_enable_ticks();
|
cpu_enable_ticks();
|
||||||
runstate_set(RUN_STATE_RUNNING);
|
runstate_set(state);
|
||||||
vm_state_notify(1, RUN_STATE_RUNNING);
|
vm_state_notify(1, state);
|
||||||
return 0;
|
vm_was_suspended = false;
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void vm_start(void)
|
void vm_start(void)
|
||||||
@ -726,11 +748,20 @@ void vm_start(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void vm_resume(RunState state)
|
||||||
|
{
|
||||||
|
if (runstate_is_live(state)) {
|
||||||
|
vm_start();
|
||||||
|
} else {
|
||||||
|
runstate_set(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* does a state transition even if the VM is already stopped,
|
/* does a state transition even if the VM is already stopped,
|
||||||
current state is forgotten forever */
|
current state is forgotten forever */
|
||||||
int vm_stop_force_state(RunState state)
|
int vm_stop_force_state(RunState state)
|
||||||
{
|
{
|
||||||
if (runstate_is_running()) {
|
if (runstate_is_live(runstate_get())) {
|
||||||
return vm_stop(state);
|
return vm_stop(state);
|
||||||
} else {
|
} else {
|
||||||
int ret;
|
int ret;
|
||||||
|
@ -77,6 +77,7 @@ typedef struct {
|
|||||||
|
|
||||||
static const RunStateTransition runstate_transitions_def[] = {
|
static const RunStateTransition runstate_transitions_def[] = {
|
||||||
{ RUN_STATE_PRELAUNCH, RUN_STATE_INMIGRATE },
|
{ RUN_STATE_PRELAUNCH, RUN_STATE_INMIGRATE },
|
||||||
|
{ RUN_STATE_PRELAUNCH, RUN_STATE_SUSPENDED },
|
||||||
|
|
||||||
{ RUN_STATE_DEBUG, RUN_STATE_RUNNING },
|
{ RUN_STATE_DEBUG, RUN_STATE_RUNNING },
|
||||||
{ RUN_STATE_DEBUG, RUN_STATE_FINISH_MIGRATE },
|
{ RUN_STATE_DEBUG, RUN_STATE_FINISH_MIGRATE },
|
||||||
@ -108,6 +109,7 @@ static const RunStateTransition runstate_transitions_def[] = {
|
|||||||
{ RUN_STATE_PAUSED, RUN_STATE_POSTMIGRATE },
|
{ RUN_STATE_PAUSED, RUN_STATE_POSTMIGRATE },
|
||||||
{ RUN_STATE_PAUSED, RUN_STATE_PRELAUNCH },
|
{ RUN_STATE_PAUSED, RUN_STATE_PRELAUNCH },
|
||||||
{ RUN_STATE_PAUSED, RUN_STATE_COLO},
|
{ RUN_STATE_PAUSED, RUN_STATE_COLO},
|
||||||
|
{ RUN_STATE_PAUSED, RUN_STATE_SUSPENDED},
|
||||||
|
|
||||||
{ RUN_STATE_POSTMIGRATE, RUN_STATE_RUNNING },
|
{ RUN_STATE_POSTMIGRATE, RUN_STATE_RUNNING },
|
||||||
{ RUN_STATE_POSTMIGRATE, RUN_STATE_FINISH_MIGRATE },
|
{ RUN_STATE_POSTMIGRATE, RUN_STATE_FINISH_MIGRATE },
|
||||||
@ -131,6 +133,7 @@ static const RunStateTransition runstate_transitions_def[] = {
|
|||||||
|
|
||||||
{ RUN_STATE_RESTORE_VM, RUN_STATE_RUNNING },
|
{ RUN_STATE_RESTORE_VM, RUN_STATE_RUNNING },
|
||||||
{ RUN_STATE_RESTORE_VM, RUN_STATE_PRELAUNCH },
|
{ RUN_STATE_RESTORE_VM, RUN_STATE_PRELAUNCH },
|
||||||
|
{ RUN_STATE_RESTORE_VM, RUN_STATE_SUSPENDED },
|
||||||
|
|
||||||
{ RUN_STATE_COLO, RUN_STATE_RUNNING },
|
{ RUN_STATE_COLO, RUN_STATE_RUNNING },
|
||||||
{ RUN_STATE_COLO, RUN_STATE_PRELAUNCH },
|
{ RUN_STATE_COLO, RUN_STATE_PRELAUNCH },
|
||||||
@ -149,6 +152,7 @@ static const RunStateTransition runstate_transitions_def[] = {
|
|||||||
{ RUN_STATE_RUNNING, RUN_STATE_COLO},
|
{ RUN_STATE_RUNNING, RUN_STATE_COLO},
|
||||||
|
|
||||||
{ RUN_STATE_SAVE_VM, RUN_STATE_RUNNING },
|
{ RUN_STATE_SAVE_VM, RUN_STATE_RUNNING },
|
||||||
|
{ RUN_STATE_SAVE_VM, RUN_STATE_SUSPENDED },
|
||||||
|
|
||||||
{ RUN_STATE_SHUTDOWN, RUN_STATE_PAUSED },
|
{ RUN_STATE_SHUTDOWN, RUN_STATE_PAUSED },
|
||||||
{ RUN_STATE_SHUTDOWN, RUN_STATE_FINISH_MIGRATE },
|
{ RUN_STATE_SHUTDOWN, RUN_STATE_FINISH_MIGRATE },
|
||||||
@ -161,6 +165,10 @@ static const RunStateTransition runstate_transitions_def[] = {
|
|||||||
{ RUN_STATE_SUSPENDED, RUN_STATE_FINISH_MIGRATE },
|
{ RUN_STATE_SUSPENDED, RUN_STATE_FINISH_MIGRATE },
|
||||||
{ RUN_STATE_SUSPENDED, RUN_STATE_PRELAUNCH },
|
{ RUN_STATE_SUSPENDED, RUN_STATE_PRELAUNCH },
|
||||||
{ RUN_STATE_SUSPENDED, RUN_STATE_COLO},
|
{ RUN_STATE_SUSPENDED, RUN_STATE_COLO},
|
||||||
|
{ RUN_STATE_SUSPENDED, RUN_STATE_PAUSED},
|
||||||
|
{ RUN_STATE_SUSPENDED, RUN_STATE_SAVE_VM },
|
||||||
|
{ RUN_STATE_SUSPENDED, RUN_STATE_RESTORE_VM },
|
||||||
|
{ RUN_STATE_SUSPENDED, RUN_STATE_SHUTDOWN },
|
||||||
|
|
||||||
{ RUN_STATE_WATCHDOG, RUN_STATE_RUNNING },
|
{ RUN_STATE_WATCHDOG, RUN_STATE_RUNNING },
|
||||||
{ RUN_STATE_WATCHDOG, RUN_STATE_FINISH_MIGRATE },
|
{ RUN_STATE_WATCHDOG, RUN_STATE_FINISH_MIGRATE },
|
||||||
@ -502,6 +510,7 @@ void qemu_system_reset(ShutdownCause reason)
|
|||||||
qapi_event_send_reset(shutdown_caused_by_guest(reason), reason);
|
qapi_event_send_reset(shutdown_caused_by_guest(reason), reason);
|
||||||
}
|
}
|
||||||
cpu_synchronize_all_post_reset();
|
cpu_synchronize_all_post_reset();
|
||||||
|
vm_set_suspended(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -2710,7 +2710,9 @@ void qmp_x_exit_preconfig(Error **errp)
|
|||||||
qemu_machine_creation_done();
|
qemu_machine_creation_done();
|
||||||
|
|
||||||
if (loadvm) {
|
if (loadvm) {
|
||||||
|
RunState state = autostart ? RUN_STATE_RUNNING : runstate_get();
|
||||||
load_snapshot(loadvm, NULL, false, NULL, &error_fatal);
|
load_snapshot(loadvm, NULL, false, NULL, &error_fatal);
|
||||||
|
load_snapshot_resume(state);
|
||||||
}
|
}
|
||||||
if (replay_mode != REPLAY_MODE_NONE) {
|
if (replay_mode != REPLAY_MODE_NONE) {
|
||||||
replay_vmstate_init();
|
replay_vmstate_init();
|
||||||
|
@ -4,9 +4,10 @@
|
|||||||
.PHONY: all clean
|
.PHONY: all clean
|
||||||
all: a-b-bootblock.h
|
all: a-b-bootblock.h
|
||||||
|
|
||||||
a-b-bootblock.h: x86.bootsect
|
a-b-bootblock.h: x86.bootsect x86.o
|
||||||
echo "$$__note" > header.tmp
|
echo "$$__note" > header.tmp
|
||||||
xxd -i $< | sed -e 's/.*int.*//' >> header.tmp
|
xxd -i $< | sed -e 's/.*int.*//' >> header.tmp
|
||||||
|
nm x86.o | awk '{print "#define SYM_"$$3" 0x"$$1}' >> header.tmp
|
||||||
mv header.tmp $@
|
mv header.tmp $@
|
||||||
|
|
||||||
x86.bootsect: x86.boot
|
x86.bootsect: x86.boot
|
||||||
@ -16,7 +17,7 @@ x86.boot: x86.o
|
|||||||
$(CROSS_PREFIX)objcopy -O binary $< $@
|
$(CROSS_PREFIX)objcopy -O binary $< $@
|
||||||
|
|
||||||
x86.o: a-b-bootblock.S
|
x86.o: a-b-bootblock.S
|
||||||
$(CROSS_PREFIX)gcc -m32 -march=i486 -c $< -o $@
|
$(CROSS_PREFIX)gcc -I.. -m32 -march=i486 -c $< -o $@
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
@rm -rf *.boot *.o *.bootsect
|
@rm -rf *.boot *.o *.bootsect
|
||||||
|
@ -9,6 +9,23 @@
|
|||||||
#
|
#
|
||||||
# Author: dgilbert@redhat.com
|
# Author: dgilbert@redhat.com
|
||||||
|
|
||||||
|
#include "migration-test.h"
|
||||||
|
|
||||||
|
#define ACPI_ENABLE 0xf1
|
||||||
|
#define ACPI_PORT_SMI_CMD 0xb2
|
||||||
|
#define ACPI_PM_BASE 0x600
|
||||||
|
#define PM1A_CNT_OFFSET 4
|
||||||
|
|
||||||
|
#define ACPI_SCI_ENABLE 0x0001
|
||||||
|
#define ACPI_SLEEP_TYPE 0x0400
|
||||||
|
#define ACPI_SLEEP_ENABLE 0x2000
|
||||||
|
#define SLEEP (ACPI_SCI_ENABLE + ACPI_SLEEP_TYPE + ACPI_SLEEP_ENABLE)
|
||||||
|
|
||||||
|
#define LOW_ADDR X86_TEST_MEM_START
|
||||||
|
#define HIGH_ADDR X86_TEST_MEM_END
|
||||||
|
|
||||||
|
/* Save the suspended status at an address that is not written in the loop. */
|
||||||
|
#define suspended (X86_TEST_MEM_START + 4)
|
||||||
|
|
||||||
.code16
|
.code16
|
||||||
.org 0x7c00
|
.org 0x7c00
|
||||||
@ -35,8 +52,8 @@ start: # at 0x7c00 ?
|
|||||||
mov %eax,%ds
|
mov %eax,%ds
|
||||||
|
|
||||||
# Start from 1MB
|
# Start from 1MB
|
||||||
.set TEST_MEM_START, (1024*1024)
|
.set TEST_MEM_START, X86_TEST_MEM_START
|
||||||
.set TEST_MEM_END, (100*1024*1024)
|
.set TEST_MEM_END, X86_TEST_MEM_END
|
||||||
|
|
||||||
mov $65,%ax
|
mov $65,%ax
|
||||||
mov $0x3f8,%dx
|
mov $0x3f8,%dx
|
||||||
@ -69,7 +86,30 @@ innerloop:
|
|||||||
mov $0x3f8,%dx
|
mov $0x3f8,%dx
|
||||||
outb %al,%dx
|
outb %al,%dx
|
||||||
|
|
||||||
jmp mainloop
|
# should this test suspend?
|
||||||
|
mov (suspend_me),%eax
|
||||||
|
cmp $0,%eax
|
||||||
|
je mainloop
|
||||||
|
|
||||||
|
# are we waking after suspend? do not suspend again.
|
||||||
|
mov $suspended,%eax
|
||||||
|
mov (%eax),%eax
|
||||||
|
cmp $1,%eax
|
||||||
|
je mainloop
|
||||||
|
|
||||||
|
# enable acpi
|
||||||
|
mov $ACPI_ENABLE,%al
|
||||||
|
outb %al,$ACPI_PORT_SMI_CMD
|
||||||
|
|
||||||
|
# suspend to ram
|
||||||
|
mov $suspended,%eax
|
||||||
|
movl $1,(%eax)
|
||||||
|
mov $SLEEP,%ax
|
||||||
|
mov $(ACPI_PM_BASE + PM1A_CNT_OFFSET),%dx
|
||||||
|
outw %ax,%dx
|
||||||
|
# not reached. The wakeup causes reset and restart at 0x7c00, and we
|
||||||
|
# do not save and restore registers as a real kernel would do.
|
||||||
|
|
||||||
|
|
||||||
# GDT magic from old (GPLv2) Grub startup.S
|
# GDT magic from old (GPLv2) Grub startup.S
|
||||||
.p2align 2 /* force 4-byte alignment */
|
.p2align 2 /* force 4-byte alignment */
|
||||||
@ -95,6 +135,10 @@ gdtdesc:
|
|||||||
.word 0x27 /* limit */
|
.word 0x27 /* limit */
|
||||||
.long gdt /* addr */
|
.long gdt /* addr */
|
||||||
|
|
||||||
|
/* test launcher can poke a 1 here to exercise suspend */
|
||||||
|
suspend_me:
|
||||||
|
.int 0
|
||||||
|
|
||||||
/* I'm a bootable disk */
|
/* I'm a bootable disk */
|
||||||
.org 0x7dfe
|
.org 0x7dfe
|
||||||
.byte 0x55
|
.byte 0x55
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
* the header and the assembler differences in your patch submission.
|
* the header and the assembler differences in your patch submission.
|
||||||
*/
|
*/
|
||||||
unsigned char x86_bootsect[] = {
|
unsigned char x86_bootsect[] = {
|
||||||
0xfa, 0x0f, 0x01, 0x16, 0x8c, 0x7c, 0x66, 0xb8, 0x01, 0x00, 0x00, 0x00,
|
0xfa, 0x0f, 0x01, 0x16, 0xb8, 0x7c, 0x66, 0xb8, 0x01, 0x00, 0x00, 0x00,
|
||||||
0x0f, 0x22, 0xc0, 0x66, 0xea, 0x20, 0x7c, 0x00, 0x00, 0x08, 0x00, 0x00,
|
0x0f, 0x22, 0xc0, 0x66, 0xea, 0x20, 0x7c, 0x00, 0x00, 0x08, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe4, 0x92, 0x0c, 0x02,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe4, 0x92, 0x0c, 0x02,
|
||||||
0xe6, 0x92, 0xb8, 0x10, 0x00, 0x00, 0x00, 0x8e, 0xd8, 0x66, 0xb8, 0x41,
|
0xe6, 0x92, 0xb8, 0x10, 0x00, 0x00, 0x00, 0x8e, 0xd8, 0x66, 0xb8, 0x41,
|
||||||
@ -13,13 +13,13 @@ unsigned char x86_bootsect[] = {
|
|||||||
0x40, 0x06, 0x7c, 0xf1, 0xb8, 0x00, 0x00, 0x10, 0x00, 0xfe, 0x00, 0x05,
|
0x40, 0x06, 0x7c, 0xf1, 0xb8, 0x00, 0x00, 0x10, 0x00, 0xfe, 0x00, 0x05,
|
||||||
0x00, 0x10, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x40, 0x06, 0x7c, 0xf2, 0xfe,
|
0x00, 0x10, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x40, 0x06, 0x7c, 0xf2, 0xfe,
|
||||||
0xc3, 0x80, 0xe3, 0x3f, 0x75, 0xe6, 0x66, 0xb8, 0x42, 0x00, 0x66, 0xba,
|
0xc3, 0x80, 0xe3, 0x3f, 0x75, 0xe6, 0x66, 0xb8, 0x42, 0x00, 0x66, 0xba,
|
||||||
0xf8, 0x03, 0xee, 0xeb, 0xdb, 0x8d, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0xf8, 0x03, 0xee, 0xa1, 0xbe, 0x7c, 0x00, 0x00, 0x83, 0xf8, 0x00, 0x74,
|
||||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x9a, 0xcf, 0x00,
|
0xd3, 0xb8, 0x04, 0x00, 0x10, 0x00, 0x8b, 0x00, 0x83, 0xf8, 0x01, 0x74,
|
||||||
0xff, 0xff, 0x00, 0x00, 0x00, 0x92, 0xcf, 0x00, 0x27, 0x00, 0x74, 0x7c,
|
0xc7, 0xb0, 0xf1, 0xe6, 0xb2, 0xb8, 0x04, 0x00, 0x10, 0x00, 0xc7, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x01, 0x00, 0x00, 0x00, 0x66, 0xb8, 0x01, 0x24, 0x66, 0xba, 0x04, 0x06,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x66, 0xef, 0x66, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0xff, 0xff, 0x00, 0x00, 0x00, 0x9a, 0xcf, 0x00, 0xff, 0xff, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x92, 0xcf, 0x00, 0x27, 0x00, 0xa0, 0x7c, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
@ -49,3 +49,13 @@ unsigned char x86_bootsect[] = {
|
|||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xaa
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xaa
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define SYM_do_zero 0x00007c3d
|
||||||
|
#define SYM_gdt 0x00007ca0
|
||||||
|
#define SYM_gdtdesc 0x00007cb8
|
||||||
|
#define SYM_innerloop 0x00007c51
|
||||||
|
#define SYM_mainloop 0x00007c4c
|
||||||
|
#define SYM_pre_zero 0x00007c38
|
||||||
|
#define SYM_start 0x00007c00
|
||||||
|
#define SYM_suspend_me 0x00007cbe
|
||||||
|
#define SYM_TEST_MEM_END 0x06400000
|
||||||
|
#define SYM_TEST_MEM_START 0x00100000
|
||||||
|
@ -24,26 +24,19 @@
|
|||||||
*/
|
*/
|
||||||
#define MIGRATION_STATUS_WAIT_TIMEOUT 120
|
#define MIGRATION_STATUS_WAIT_TIMEOUT 120
|
||||||
|
|
||||||
bool migrate_watch_for_stop(QTestState *who, const char *name,
|
bool migrate_watch_for_events(QTestState *who, const char *name,
|
||||||
QDict *event, void *opaque)
|
QDict *event, void *opaque)
|
||||||
{
|
{
|
||||||
bool *seen = opaque;
|
QTestMigrationState *state = opaque;
|
||||||
|
|
||||||
if (g_str_equal(name, "STOP")) {
|
if (g_str_equal(name, "STOP")) {
|
||||||
*seen = true;
|
state->stop_seen = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
} else if (g_str_equal(name, "SUSPEND")) {
|
||||||
|
state->suspend_seen = true;
|
||||||
return false;
|
return true;
|
||||||
}
|
} else if (g_str_equal(name, "RESUME")) {
|
||||||
|
state->resume_seen = true;
|
||||||
bool migrate_watch_for_resume(QTestState *who, const char *name,
|
|
||||||
QDict *event, void *opaque)
|
|
||||||
{
|
|
||||||
bool *seen = opaque;
|
|
||||||
|
|
||||||
if (g_str_equal(name, "RESUME")) {
|
|
||||||
*seen = true;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,9 +15,14 @@
|
|||||||
|
|
||||||
#include "libqtest.h"
|
#include "libqtest.h"
|
||||||
|
|
||||||
bool migrate_watch_for_stop(QTestState *who, const char *name,
|
typedef struct QTestMigrationState {
|
||||||
QDict *event, void *opaque);
|
bool stop_seen;
|
||||||
bool migrate_watch_for_resume(QTestState *who, const char *name,
|
bool resume_seen;
|
||||||
|
bool suspend_seen;
|
||||||
|
bool suspend_me;
|
||||||
|
} QTestMigrationState;
|
||||||
|
|
||||||
|
bool migrate_watch_for_events(QTestState *who, const char *name,
|
||||||
QDict *event, void *opaque);
|
QDict *event, void *opaque);
|
||||||
|
|
||||||
G_GNUC_PRINTF(3, 4)
|
G_GNUC_PRINTF(3, 4)
|
||||||
|
@ -43,8 +43,8 @@
|
|||||||
unsigned start_address;
|
unsigned start_address;
|
||||||
unsigned end_address;
|
unsigned end_address;
|
||||||
static bool uffd_feature_thread_id;
|
static bool uffd_feature_thread_id;
|
||||||
static bool got_src_stop;
|
static QTestMigrationState src_state;
|
||||||
static bool got_dst_resume;
|
static QTestMigrationState dst_state;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* An initial 3 MB offset is used as that corresponds
|
* An initial 3 MB offset is used as that corresponds
|
||||||
@ -133,7 +133,7 @@ static char *bootpath;
|
|||||||
#include "tests/migration/aarch64/a-b-kernel.h"
|
#include "tests/migration/aarch64/a-b-kernel.h"
|
||||||
#include "tests/migration/s390x/a-b-bios.h"
|
#include "tests/migration/s390x/a-b-bios.h"
|
||||||
|
|
||||||
static void bootfile_create(char *dir)
|
static void bootfile_create(char *dir, bool suspend_me)
|
||||||
{
|
{
|
||||||
const char *arch = qtest_get_arch();
|
const char *arch = qtest_get_arch();
|
||||||
unsigned char *content;
|
unsigned char *content;
|
||||||
@ -143,6 +143,7 @@ static void bootfile_create(char *dir)
|
|||||||
if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) {
|
if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) {
|
||||||
/* the assembled x86 boot sector should be exactly one sector large */
|
/* the assembled x86 boot sector should be exactly one sector large */
|
||||||
g_assert(sizeof(x86_bootsect) == 512);
|
g_assert(sizeof(x86_bootsect) == 512);
|
||||||
|
x86_bootsect[SYM_suspend_me - SYM_start] = suspend_me;
|
||||||
content = x86_bootsect;
|
content = x86_bootsect;
|
||||||
len = sizeof(x86_bootsect);
|
len = sizeof(x86_bootsect);
|
||||||
} else if (g_str_equal(arch, "s390x")) {
|
} else if (g_str_equal(arch, "s390x")) {
|
||||||
@ -177,7 +178,7 @@ static void bootfile_delete(void)
|
|||||||
/*
|
/*
|
||||||
* Wait for some output in the serial output file,
|
* Wait for some output in the serial output file,
|
||||||
* we get an 'A' followed by an endless string of 'B's
|
* we get an 'A' followed by an endless string of 'B's
|
||||||
* but on the destination we won't have the A.
|
* but on the destination we won't have the A (unless we enabled suspend/resume)
|
||||||
*/
|
*/
|
||||||
static void wait_for_serial(const char *side)
|
static void wait_for_serial(const char *side)
|
||||||
{
|
{
|
||||||
@ -230,6 +231,27 @@ static void wait_for_serial(const char *side)
|
|||||||
} while (true);
|
} while (true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void wait_for_stop(QTestState *who, QTestMigrationState *state)
|
||||||
|
{
|
||||||
|
if (!state->stop_seen) {
|
||||||
|
qtest_qmp_eventwait(who, "STOP");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void wait_for_resume(QTestState *who, QTestMigrationState *state)
|
||||||
|
{
|
||||||
|
if (!state->resume_seen) {
|
||||||
|
qtest_qmp_eventwait(who, "RESUME");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void wait_for_suspend(QTestState *who, QTestMigrationState *state)
|
||||||
|
{
|
||||||
|
if (state->suspend_me && !state->suspend_seen) {
|
||||||
|
qtest_qmp_eventwait(who, "SUSPEND");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* It's tricky to use qemu's migration event capability with qtest,
|
* It's tricky to use qemu's migration event capability with qtest,
|
||||||
* events suddenly appearing confuse the qmp()/hmp() responses.
|
* events suddenly appearing confuse the qmp()/hmp() responses.
|
||||||
@ -277,21 +299,19 @@ static void read_blocktime(QTestState *who)
|
|||||||
qobject_unref(rsp_return);
|
qobject_unref(rsp_return);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wait for two changes in the migration pass count, but bail if we stop.
|
||||||
|
*/
|
||||||
static void wait_for_migration_pass(QTestState *who)
|
static void wait_for_migration_pass(QTestState *who)
|
||||||
{
|
{
|
||||||
uint64_t initial_pass = get_migration_pass(who);
|
uint64_t pass, prev_pass = 0, changes = 0;
|
||||||
uint64_t pass;
|
|
||||||
|
|
||||||
/* Wait for the 1st sync */
|
while (changes < 2 && !src_state.stop_seen && !src_state.suspend_seen) {
|
||||||
while (!got_src_stop && !initial_pass) {
|
|
||||||
usleep(1000);
|
|
||||||
initial_pass = get_migration_pass(who);
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
usleep(1000);
|
usleep(1000);
|
||||||
pass = get_migration_pass(who);
|
pass = get_migration_pass(who);
|
||||||
} while (pass == initial_pass && !got_src_stop);
|
changes += (pass != prev_pass);
|
||||||
|
prev_pass = pass;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void check_guests_ram(QTestState *who)
|
static void check_guests_ram(QTestState *who)
|
||||||
@ -571,6 +591,12 @@ static void migrate_wait_for_dirty_mem(QTestState *from,
|
|||||||
usleep(1000 * 10);
|
usleep(1000 * 10);
|
||||||
} while (qtest_readq(to, marker_address) != MAGIC_MARKER);
|
} 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
|
* Now ensure that already transferred bytes are
|
||||||
* dirty again from the guest workload. Note the
|
* dirty again from the guest workload. Note the
|
||||||
@ -617,10 +643,7 @@ static void migrate_postcopy_start(QTestState *from, QTestState *to)
|
|||||||
{
|
{
|
||||||
qtest_qmp_assert_success(from, "{ 'execute': 'migrate-start-postcopy' }");
|
qtest_qmp_assert_success(from, "{ 'execute': 'migrate-start-postcopy' }");
|
||||||
|
|
||||||
if (!got_src_stop) {
|
wait_for_stop(from, &src_state);
|
||||||
qtest_qmp_eventwait(from, "STOP");
|
|
||||||
}
|
|
||||||
|
|
||||||
qtest_qmp_eventwait(to, "RESUME");
|
qtest_qmp_eventwait(to, "RESUME");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -637,6 +660,8 @@ typedef struct {
|
|||||||
bool use_dirty_ring;
|
bool use_dirty_ring;
|
||||||
const char *opts_source;
|
const char *opts_source;
|
||||||
const char *opts_target;
|
const char *opts_target;
|
||||||
|
/* suspend the src before migrating to dest. */
|
||||||
|
bool suspend_me;
|
||||||
} MigrateStart;
|
} MigrateStart;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -756,8 +781,11 @@ static int test_migrate_start(QTestState **from, QTestState **to,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
got_src_stop = false;
|
dst_state = (QTestMigrationState) { };
|
||||||
got_dst_resume = false;
|
src_state = (QTestMigrationState) { };
|
||||||
|
bootfile_create(tmpfs, args->suspend_me);
|
||||||
|
src_state.suspend_me = args->suspend_me;
|
||||||
|
|
||||||
if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) {
|
if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) {
|
||||||
memory_size = "150M";
|
memory_size = "150M";
|
||||||
|
|
||||||
@ -848,8 +876,8 @@ static int test_migrate_start(QTestState **from, QTestState **to,
|
|||||||
if (!args->only_target) {
|
if (!args->only_target) {
|
||||||
*from = qtest_init_with_env(QEMU_ENV_SRC, cmd_source);
|
*from = qtest_init_with_env(QEMU_ENV_SRC, cmd_source);
|
||||||
qtest_qmp_set_event_callback(*from,
|
qtest_qmp_set_event_callback(*from,
|
||||||
migrate_watch_for_stop,
|
migrate_watch_for_events,
|
||||||
&got_src_stop);
|
&src_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_target = g_strdup_printf("-accel kvm%s -accel tcg "
|
cmd_target = g_strdup_printf("-accel kvm%s -accel tcg "
|
||||||
@ -869,8 +897,8 @@ static int test_migrate_start(QTestState **from, QTestState **to,
|
|||||||
ignore_stderr);
|
ignore_stderr);
|
||||||
*to = qtest_init_with_env(QEMU_ENV_DST, cmd_target);
|
*to = qtest_init_with_env(QEMU_ENV_DST, cmd_target);
|
||||||
qtest_qmp_set_event_callback(*to,
|
qtest_qmp_set_event_callback(*to,
|
||||||
migrate_watch_for_resume,
|
migrate_watch_for_events,
|
||||||
&got_dst_resume);
|
&dst_state);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Remove shmem file immediately to avoid memory leak in test failed case.
|
* Remove shmem file immediately to avoid memory leak in test failed case.
|
||||||
@ -1319,6 +1347,7 @@ static int migrate_postcopy_prepare(QTestState **from_ptr,
|
|||||||
|
|
||||||
/* Wait for the first serial output from the source */
|
/* Wait for the first serial output from the source */
|
||||||
wait_for_serial("src_serial");
|
wait_for_serial("src_serial");
|
||||||
|
wait_for_suspend(from, &src_state);
|
||||||
|
|
||||||
g_autofree char *uri = migrate_get_socket_address(to, "socket-address");
|
g_autofree char *uri = migrate_get_socket_address(to, "socket-address");
|
||||||
migrate_qmp(from, uri, "{}");
|
migrate_qmp(from, uri, "{}");
|
||||||
@ -1336,6 +1365,11 @@ static void migrate_postcopy_complete(QTestState *from, QTestState *to,
|
|||||||
{
|
{
|
||||||
wait_for_migration_complete(from);
|
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 */
|
/* Make sure we get at least one "B" on destination */
|
||||||
wait_for_serial("dest_serial");
|
wait_for_serial("dest_serial");
|
||||||
|
|
||||||
@ -1369,6 +1403,15 @@ static void test_postcopy(void)
|
|||||||
test_postcopy_common(&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_compress(void)
|
static void test_postcopy_compress(void)
|
||||||
{
|
{
|
||||||
MigrateCommon args = {
|
MigrateCommon args = {
|
||||||
@ -1703,6 +1746,7 @@ static void test_precopy_common(MigrateCommon *args)
|
|||||||
/* Wait for the first serial output from the source */
|
/* Wait for the first serial output from the source */
|
||||||
if (args->result == MIG_TEST_SUCCEED) {
|
if (args->result == MIG_TEST_SUCCEED) {
|
||||||
wait_for_serial("src_serial");
|
wait_for_serial("src_serial");
|
||||||
|
wait_for_suspend(from, &src_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args->live) {
|
if (args->live) {
|
||||||
@ -1717,9 +1761,7 @@ static void test_precopy_common(MigrateCommon *args)
|
|||||||
*/
|
*/
|
||||||
if (args->result == MIG_TEST_SUCCEED) {
|
if (args->result == MIG_TEST_SUCCEED) {
|
||||||
qtest_qmp_assert_success(from, "{ 'execute' : 'stop'}");
|
qtest_qmp_assert_success(from, "{ 'execute' : 'stop'}");
|
||||||
if (!got_src_stop) {
|
wait_for_stop(from, &src_state);
|
||||||
qtest_qmp_eventwait(from, "STOP");
|
|
||||||
}
|
|
||||||
migrate_ensure_converge(from);
|
migrate_ensure_converge(from);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1765,9 +1807,8 @@ static void test_precopy_common(MigrateCommon *args)
|
|||||||
*/
|
*/
|
||||||
wait_for_migration_complete(from);
|
wait_for_migration_complete(from);
|
||||||
|
|
||||||
if (!got_src_stop) {
|
wait_for_stop(from, &src_state);
|
||||||
qtest_qmp_eventwait(from, "STOP");
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
wait_for_migration_complete(from);
|
wait_for_migration_complete(from);
|
||||||
/*
|
/*
|
||||||
@ -1780,8 +1821,11 @@ static void test_precopy_common(MigrateCommon *args)
|
|||||||
qtest_qmp_assert_success(to, "{ 'execute' : 'cont'}");
|
qtest_qmp_assert_success(to, "{ 'execute' : 'cont'}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!got_dst_resume) {
|
wait_for_resume(to, &dst_state);
|
||||||
qtest_qmp_eventwait(to, "RESUME");
|
|
||||||
|
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");
|
wait_for_serial("dest_serial");
|
||||||
@ -1821,9 +1865,7 @@ static void test_file_common(MigrateCommon *args, bool stop_src)
|
|||||||
|
|
||||||
if (stop_src) {
|
if (stop_src) {
|
||||||
qtest_qmp_assert_success(from, "{ 'execute' : 'stop'}");
|
qtest_qmp_assert_success(from, "{ 'execute' : 'stop'}");
|
||||||
if (!got_src_stop) {
|
wait_for_stop(from, &src_state);
|
||||||
qtest_qmp_eventwait(from, "STOP");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args->result == MIG_TEST_QMP_ERROR) {
|
if (args->result == MIG_TEST_QMP_ERROR) {
|
||||||
@ -1844,10 +1886,7 @@ static void test_file_common(MigrateCommon *args, bool stop_src)
|
|||||||
if (stop_src) {
|
if (stop_src) {
|
||||||
qtest_qmp_assert_success(to, "{ 'execute' : 'cont'}");
|
qtest_qmp_assert_success(to, "{ 'execute' : 'cont'}");
|
||||||
}
|
}
|
||||||
|
wait_for_resume(to, &dst_state);
|
||||||
if (!got_dst_resume) {
|
|
||||||
qtest_qmp_eventwait(to, "RESUME");
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_for_serial("dest_serial");
|
wait_for_serial("dest_serial");
|
||||||
|
|
||||||
@ -1875,6 +1914,34 @@ static void test_precopy_unix_plain(void)
|
|||||||
test_precopy_common(&args);
|
test_precopy_common(&args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_precopy_unix_suspend_live(void)
|
||||||
|
{
|
||||||
|
g_autofree char *uri = g_strdup_printf("unix:%s/migsocket", tmpfs);
|
||||||
|
MigrateCommon args = {
|
||||||
|
.listen_uri = uri,
|
||||||
|
.connect_uri = uri,
|
||||||
|
/*
|
||||||
|
* despite being live, the test is fast because the src
|
||||||
|
* suspends immediately.
|
||||||
|
*/
|
||||||
|
.live = true,
|
||||||
|
.start.suspend_me = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_precopy_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_precopy_unix_suspend_notlive(void)
|
||||||
|
{
|
||||||
|
g_autofree char *uri = g_strdup_printf("unix:%s/migsocket", tmpfs);
|
||||||
|
MigrateCommon args = {
|
||||||
|
.listen_uri = uri,
|
||||||
|
.connect_uri = uri,
|
||||||
|
.start.suspend_me = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
test_precopy_common(&args);
|
||||||
|
}
|
||||||
|
|
||||||
static void test_precopy_unix_dirty_ring(void)
|
static void test_precopy_unix_dirty_ring(void)
|
||||||
{
|
{
|
||||||
@ -1966,9 +2033,7 @@ static void test_ignore_shared(void)
|
|||||||
|
|
||||||
migrate_wait_for_dirty_mem(from, to);
|
migrate_wait_for_dirty_mem(from, to);
|
||||||
|
|
||||||
if (!got_src_stop) {
|
wait_for_stop(from, &src_state);
|
||||||
qtest_qmp_eventwait(from, "STOP");
|
|
||||||
}
|
|
||||||
|
|
||||||
qtest_qmp_eventwait(to, "RESUME");
|
qtest_qmp_eventwait(to, "RESUME");
|
||||||
|
|
||||||
@ -2503,7 +2568,7 @@ static void test_migrate_auto_converge(void)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
usleep(20);
|
usleep(20);
|
||||||
g_assert_false(got_src_stop);
|
g_assert_false(src_state.stop_seen);
|
||||||
} while (true);
|
} while (true);
|
||||||
/* The first percentage of throttling should be at least init_pct */
|
/* The first percentage of throttling should be at least init_pct */
|
||||||
g_assert_cmpint(percentage, >=, init_pct);
|
g_assert_cmpint(percentage, >=, init_pct);
|
||||||
@ -2842,9 +2907,7 @@ static void test_multifd_tcp_cancel(void)
|
|||||||
|
|
||||||
migrate_ensure_converge(from);
|
migrate_ensure_converge(from);
|
||||||
|
|
||||||
if (!got_src_stop) {
|
wait_for_stop(from, &src_state);
|
||||||
qtest_qmp_eventwait(from, "STOP");
|
|
||||||
}
|
|
||||||
qtest_qmp_eventwait(to2, "RESUME");
|
qtest_qmp_eventwait(to2, "RESUME");
|
||||||
|
|
||||||
wait_for_serial("dest_serial");
|
wait_for_serial("dest_serial");
|
||||||
@ -2985,7 +3048,9 @@ static int64_t get_limit_rate(QTestState *who)
|
|||||||
static QTestState *dirtylimit_start_vm(void)
|
static QTestState *dirtylimit_start_vm(void)
|
||||||
{
|
{
|
||||||
QTestState *vm = NULL;
|
QTestState *vm = NULL;
|
||||||
g_autofree gchar *
|
g_autofree gchar *cmd = NULL;
|
||||||
|
|
||||||
|
bootfile_create(tmpfs, false);
|
||||||
cmd = g_strdup_printf("-accel kvm,dirty-ring-size=4096 "
|
cmd = g_strdup_printf("-accel kvm,dirty-ring-size=4096 "
|
||||||
"-name dirtylimit-test,debug-threads=on "
|
"-name dirtylimit-test,debug-threads=on "
|
||||||
"-m 150M -smp 1 "
|
"-m 150M -smp 1 "
|
||||||
@ -3177,7 +3242,7 @@ static void test_migrate_dirty_limit(void)
|
|||||||
throttle_us_per_full =
|
throttle_us_per_full =
|
||||||
read_migrate_property_int(from, "dirty-limit-throttle-time-per-round");
|
read_migrate_property_int(from, "dirty-limit-throttle-time-per-round");
|
||||||
usleep(100);
|
usleep(100);
|
||||||
g_assert_false(got_src_stop);
|
g_assert_false(src_state.stop_seen);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Now cancel migrate and wait for dirty limit throttle switch off */
|
/* Now cancel migrate and wait for dirty limit throttle switch off */
|
||||||
@ -3189,7 +3254,7 @@ static void test_migrate_dirty_limit(void)
|
|||||||
throttle_us_per_full =
|
throttle_us_per_full =
|
||||||
read_migrate_property_int(from, "dirty-limit-throttle-time-per-round");
|
read_migrate_property_int(from, "dirty-limit-throttle-time-per-round");
|
||||||
usleep(100);
|
usleep(100);
|
||||||
g_assert_false(got_src_stop);
|
g_assert_false(src_state.stop_seen);
|
||||||
} while (throttle_us_per_full != 0 && --max_try_count);
|
} while (throttle_us_per_full != 0 && --max_try_count);
|
||||||
|
|
||||||
/* Assert dirty limit is not in service */
|
/* Assert dirty limit is not in service */
|
||||||
@ -3218,7 +3283,7 @@ static void test_migrate_dirty_limit(void)
|
|||||||
throttle_us_per_full =
|
throttle_us_per_full =
|
||||||
read_migrate_property_int(from, "dirty-limit-throttle-time-per-round");
|
read_migrate_property_int(from, "dirty-limit-throttle-time-per-round");
|
||||||
usleep(100);
|
usleep(100);
|
||||||
g_assert_false(got_src_stop);
|
g_assert_false(src_state.stop_seen);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -3277,7 +3342,7 @@ static bool kvm_dirty_ring_supported(void)
|
|||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
bool has_kvm, has_tcg;
|
bool has_kvm, has_tcg;
|
||||||
bool has_uffd;
|
bool has_uffd, is_x86;
|
||||||
const char *arch;
|
const char *arch;
|
||||||
g_autoptr(GError) err = NULL;
|
g_autoptr(GError) err = NULL;
|
||||||
const char *qemu_src = getenv(QEMU_ENV_SRC);
|
const char *qemu_src = getenv(QEMU_ENV_SRC);
|
||||||
@ -3307,6 +3372,7 @@ int main(int argc, char **argv)
|
|||||||
|
|
||||||
has_uffd = ufd_version_check();
|
has_uffd = ufd_version_check();
|
||||||
arch = qtest_get_arch();
|
arch = qtest_get_arch();
|
||||||
|
is_x86 = !strcmp(arch, "i386") || !strcmp(arch, "x86_64");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* On ppc64, the test only works with kvm-hv, but not with kvm-pr and TCG
|
* On ppc64, the test only works with kvm-hv, but not with kvm-pr and TCG
|
||||||
@ -3334,10 +3400,16 @@ int main(int argc, char **argv)
|
|||||||
g_get_tmp_dir(), err->message);
|
g_get_tmp_dir(), err->message);
|
||||||
}
|
}
|
||||||
g_assert(tmpfs);
|
g_assert(tmpfs);
|
||||||
bootfile_create(tmpfs);
|
|
||||||
|
|
||||||
module_call_init(MODULE_INIT_QOM);
|
module_call_init(MODULE_INIT_QOM);
|
||||||
|
|
||||||
|
if (is_x86) {
|
||||||
|
qtest_add_func("/migration/precopy/unix/suspend/live",
|
||||||
|
test_precopy_unix_suspend_live);
|
||||||
|
qtest_add_func("/migration/precopy/unix/suspend/notlive",
|
||||||
|
test_precopy_unix_suspend_notlive);
|
||||||
|
}
|
||||||
|
|
||||||
if (has_uffd) {
|
if (has_uffd) {
|
||||||
qtest_add_func("/migration/postcopy/plain", test_postcopy);
|
qtest_add_func("/migration/postcopy/plain", test_postcopy);
|
||||||
qtest_add_func("/migration/postcopy/recovery/plain",
|
qtest_add_func("/migration/postcopy/recovery/plain",
|
||||||
@ -3355,7 +3427,10 @@ int main(int argc, char **argv)
|
|||||||
qtest_add_func("/migration/postcopy/recovery/double-failures",
|
qtest_add_func("/migration/postcopy/recovery/double-failures",
|
||||||
test_postcopy_recovery_double_fail);
|
test_postcopy_recovery_double_fail);
|
||||||
#endif /* _WIN32 */
|
#endif /* _WIN32 */
|
||||||
|
if (is_x86) {
|
||||||
|
qtest_add_func("/migration/postcopy/suspend",
|
||||||
|
test_postcopy_suspend);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
qtest_add_func("/migration/bad_dest", test_baddest);
|
qtest_add_func("/migration/bad_dest", test_baddest);
|
||||||
|
Loading…
Reference in New Issue
Block a user