1 /* 2 * QTest testcases for CPR 3 * 4 * Copyright (c) 2016-2018 Red Hat, Inc. and/or its affiliates 5 * based on the vhost-user-test.c that is: 6 * Copyright (c) 2014 Virtual Open Systems Sarl. 7 * 8 * This work is licensed under the terms of the GNU GPL, version 2 or later. 9 * See the COPYING file in the top-level directory. 10 * 11 */ 12 13 #include "qemu/osdep.h" 14 #include "libqtest.h" 15 #include "migration/framework.h" 16 #include "migration/migration-qmp.h" 17 #include "migration/migration-util.h" 18 19 20 static char *tmpfs; 21 22 static void *migrate_hook_start_mode_reboot(QTestState *from, QTestState *to) 23 { 24 migrate_set_parameter_str(from, "mode", "cpr-reboot"); 25 migrate_set_parameter_str(to, "mode", "cpr-reboot"); 26 27 return NULL; 28 } 29 30 static void test_mode_reboot(void) 31 { 32 g_autofree char *uri = g_strdup_printf("file:%s/%s", tmpfs, 33 FILE_TEST_FILENAME); 34 MigrateCommon args = { 35 .start.mem_type = MEM_TYPE_SHMEM, 36 .connect_uri = uri, 37 .listen_uri = "defer", 38 .start_hook = migrate_hook_start_mode_reboot, 39 .start = { 40 .caps[MIGRATION_CAPABILITY_X_IGNORE_SHARED] = true, 41 }, 42 }; 43 44 test_file_common(&args, true); 45 } 46 47 static void *test_mode_transfer_start(QTestState *from, QTestState *to) 48 { 49 migrate_set_parameter_str(from, "mode", "cpr-transfer"); 50 return NULL; 51 } 52 53 /* 54 * cpr-transfer mode cannot use the target monitor prior to starting the 55 * migration, and cannot connect synchronously to the monitor, so defer 56 * the target connection. 57 */ 58 static void test_mode_transfer_common(bool incoming_defer) 59 { 60 g_autofree char *cpr_path = g_strdup_printf("%s/cpr.sock", tmpfs); 61 g_autofree char *mig_path = g_strdup_printf("%s/migsocket", tmpfs); 62 g_autofree char *uri = g_strdup_printf("unix:%s", mig_path); 63 g_autofree char *opts_target = NULL; 64 65 const char *opts = "-machine aux-ram-share=on -nodefaults"; 66 g_autofree const char *cpr_channel = g_strdup_printf( 67 "cpr,addr.transport=socket,addr.type=unix,addr.path=%s", 68 cpr_path); 69 70 g_autofree char *connect_channels = g_strdup_printf( 71 "[ { 'channel-type': 'main'," 72 " 'addr': { 'transport': 'socket'," 73 " 'type': 'unix'," 74 " 'path': '%s' } } ]", 75 mig_path); 76 77 /* 78 * Set up a UNIX domain socket for the CPR channel before 79 * launching the destination VM, to avoid timing issues 80 * during connection setup. 81 */ 82 int cpr_sockfd = qtest_socket_server(cpr_path); 83 g_assert(cpr_sockfd >= 0); 84 85 opts_target = g_strdup_printf("-incoming cpr,addr.transport=socket," 86 "addr.type=fd,addr.str=%d %s", 87 cpr_sockfd, opts); 88 MigrateCommon args = { 89 .start.opts_source = opts, 90 .start.opts_target = opts_target, 91 .start.defer_target_connect = true, 92 .start.mem_type = MEM_TYPE_MEMFD, 93 .listen_uri = incoming_defer ? "defer" : uri, 94 .connect_channels = connect_channels, 95 .cpr_channel = cpr_channel, 96 .start_hook = test_mode_transfer_start, 97 }; 98 99 if (test_precopy_common(&args) < 0) { 100 close(cpr_sockfd); 101 unlink(cpr_path); 102 } 103 } 104 105 static void test_mode_transfer(void) 106 { 107 test_mode_transfer_common(NULL); 108 } 109 110 static void test_mode_transfer_defer(void) 111 { 112 test_mode_transfer_common(true); 113 } 114 115 static void set_cpr_exec_args(QTestState *who, MigrateCommon *args) 116 { 117 g_autofree char *qtest_from_args = NULL; 118 g_autofree char *from_args = NULL; 119 g_autofree char *to_args = NULL; 120 g_autofree char *exec_args = NULL; 121 g_auto(GStrv) argv = NULL; 122 char *from_str, *src, *dst; 123 int ret; 124 125 /* 126 * hide_stderr appends "2>/dev/null" to the command line, but cpr-exec 127 * passes the command-line words to execv, not to the shell, so suppress it 128 * here. fd 2 was already bound in the source VM, and execv preserves it. 129 */ 130 g_assert(args->start.hide_stderr == false); 131 132 ret = migrate_args(&from_args, &to_args, args->listen_uri, &args->start); 133 g_assert(!ret); 134 qtest_from_args = qtest_qemu_args(from_args); 135 136 /* 137 * The generated args may have been formatted using "%s %s" with empty 138 * strings, which can produce consecutive spaces, which g_strsplit would 139 * convert into empty strings. Ditto for leading and trailing space. 140 * De-dup spaces to avoid that. 141 */ 142 143 from_str = src = dst = g_strstrip(qtest_from_args); 144 do { 145 if (*src != ' ' || src[-1] != ' ') { 146 *dst++ = *src; 147 } 148 } while (*src++); 149 150 exec_args = g_strconcat(qtest_qemu_binary(migration_get_env()->qemu_dst), 151 " -incoming defer ", from_str, NULL); 152 argv = g_strsplit(exec_args, " ", -1); 153 migrate_set_parameter_strv(who, "cpr-exec-command", argv); 154 } 155 156 static void wait_for_migration_event(QTestState *who, const char *waitfor) 157 { 158 QDict *rsp, *data; 159 char *status; 160 bool done = false; 161 162 while (!done) { 163 rsp = qtest_qmp_eventwait_ref(who, "MIGRATION"); 164 g_assert(qdict_haskey(rsp, "data")); 165 data = qdict_get_qdict(rsp, "data"); 166 g_assert(qdict_haskey(data, "status")); 167 status = g_strdup(qdict_get_str(data, "status")); 168 g_assert(strcmp(status, "failed")); 169 done = !strcmp(status, waitfor); 170 qobject_unref(rsp); 171 } 172 } 173 174 static void test_cpr_exec(MigrateCommon *args) 175 { 176 QTestState *from, *to; 177 void *data_hook = NULL; 178 g_autofree char *connect_uri = g_strdup(args->connect_uri); 179 g_autofree char *filename = g_strdup_printf("%s/%s", tmpfs, 180 FILE_TEST_FILENAME); 181 182 if (migrate_start(&from, NULL, args->listen_uri, &args->start)) { 183 return; 184 } 185 186 /* Source and dest never run concurrently */ 187 g_assert_false(args->live); 188 189 if (args->start_hook) { 190 data_hook = args->start_hook(from, NULL); 191 } 192 193 wait_for_serial("src_serial"); 194 set_cpr_exec_args(from, args); 195 migrate_set_capability(from, "events", true); 196 migrate_qmp(from, NULL, connect_uri, NULL, "{}"); 197 wait_for_migration_event(from, "completed"); 198 199 to = qtest_init_after_exec(from); 200 201 qtest_qmp_assert_success(to, "{ 'execute': 'migrate-incoming'," 202 " 'arguments': { " 203 " 'channels': [ { 'channel-type': 'main'," 204 " 'addr': { 'transport': 'file'," 205 " 'filename': %s," 206 " 'offset': 0 } } ] } }", 207 filename); 208 wait_for_migration_complete(to); 209 210 wait_for_resume(to, get_dst()); 211 /* Device on target is still named src_serial because args do not change */ 212 wait_for_serial("src_serial"); 213 214 if (args->end_hook) { 215 args->end_hook(from, to, data_hook); 216 } 217 218 migrate_end(from, to, args->result == MIG_TEST_SUCCEED); 219 } 220 221 static void *test_mode_exec_start(QTestState *from, QTestState *to) 222 { 223 assert(!to); 224 migrate_set_parameter_str(from, "mode", "cpr-exec"); 225 return NULL; 226 } 227 228 static void test_mode_exec(void) 229 { 230 g_autofree char *uri = g_strdup_printf("file:%s/%s", tmpfs, 231 FILE_TEST_FILENAME); 232 g_autofree char *listen_uri = g_strdup_printf("defer"); 233 234 MigrateCommon args = { 235 .start.only_source = true, 236 .start.opts_source = "-machine aux-ram-share=on -nodefaults", 237 .start.mem_type = MEM_TYPE_MEMFD, 238 .connect_uri = uri, 239 .listen_uri = listen_uri, 240 .start_hook = test_mode_exec_start, 241 }; 242 243 test_cpr_exec(&args); 244 } 245 246 void migration_test_add_cpr(MigrationTestEnv *env) 247 { 248 tmpfs = env->tmpfs; 249 250 /* no tests in the smoke set for now */ 251 252 if (!env->full_set) { 253 return; 254 } 255 256 /* 257 * Our CI system has problems with shared memory. 258 * Don't run this test until we find a workaround. 259 */ 260 if (getenv("QEMU_TEST_FLAKY_TESTS")) { 261 migration_test_add("/migration/mode/reboot", test_mode_reboot); 262 } 263 264 if (env->has_kvm) { 265 migration_test_add("/migration/mode/transfer", test_mode_transfer); 266 migration_test_add("/migration/mode/transfer/defer", 267 test_mode_transfer_defer); 268 migration_test_add("/migration/mode/exec", test_mode_exec); 269 } 270 } 271