1run_test () 2{ 3 local stdout errors ret 4 5 initrd_create || return $? 6 echo -n "$@" 7 [ "$ENVIRON_DEFAULT" = "yes" ] && echo -n " #" 8 echo " $INITRD" 9 10 # stdout to {stdout}, stderr to $errors and stderr 11 exec {stdout}>&1 12 errors=$("${@}" $INITRD </dev/null 2> >(tee /dev/stderr) > /dev/fd/$stdout) 13 ret=$? 14 exec {stdout}>&- 15 16 ret=$(vmm_fixup_return_code $ret $errors) 17 18 return $ret 19} 20 21run_test_status () 22{ 23 local stdout ret 24 25 exec {stdout}>&1 26 lines=$(run_test "$@" > >(tee /dev/fd/$stdout)) 27 ret=$? 28 exec {stdout}>&- 29 30 if [ $ret -eq 1 ]; then 31 testret=$(grep '^EXIT: ' <<<"$lines" | head -n1 | sed 's/.*STATUS=\([0-9][0-9]*\).*/\1/') 32 if [ "$testret" ]; then 33 if [ $testret -eq 1 ]; then 34 ret=0 35 else 36 ret=$testret 37 fi 38 fi 39 fi 40 41 return $ret 42} 43 44timeout_cmd () 45{ 46 local s 47 48 if [ "$TIMEOUT" ] && [ "$TIMEOUT" != "0" ]; then 49 if [ "$CONFIG_EFI" = 'y' ]; then 50 s=${TIMEOUT: -1} 51 if [ "$s" = 's' ]; then 52 TIMEOUT=${TIMEOUT:0:-1} 53 ((TIMEOUT += 10)) # Add 10 seconds for booting UEFI 54 TIMEOUT="${TIMEOUT}s" 55 fi 56 fi 57 echo "timeout -k 1s --foreground $TIMEOUT" 58 fi 59} 60 61qmp () 62{ 63 echo '{ "execute": "qmp_capabilities" }{ "execute":' "$2" '}' | ncat -U $1 64} 65 66filter_quiet_msgs () 67{ 68 grep -v "Now migrate the VM (quiet)" | 69 grep -v "Begin continuous migration (quiet)" | 70 grep -v "End continuous migration (quiet)" | 71 grep -v "Skipped VM migration (quiet)" 72} 73 74seen_migrate_msg () 75{ 76 if [ $skip_migration -eq 1 ]; then 77 grep -q -e "Now migrate the VM" -e "Begin continuous migration" < $1 78 else 79 grep -q -e "Now migrate the VM" -e "Begin continuous migration" -e "Skipped VM migration" < $1 80 fi 81} 82 83run_migration () 84{ 85 if ! command -v ncat >/dev/null 2>&1; then 86 echo "${FUNCNAME[0]} needs ncat (netcat)" >&2 87 return 77 88 fi 89 90 migcmdline=("$@") 91 92 trap 'trap - TERM ; kill 0 ; exit 2' INT TERM 93 trap 'rm -f ${src_out} ${dst_out} ${src_outfifo} ${dst_outfifo} ${dst_incoming} ${src_qmp} ${dst_qmp} ${src_infifo} ${dst_infifo}' RETURN EXIT 94 95 dst_incoming=$(mktemp -u -t mig-helper-socket-incoming.XXXXXXXXXX) 96 src_out=$(mktemp -t mig-helper-stdout1.XXXXXXXXXX) 97 src_outfifo=$(mktemp -u -t mig-helper-fifo-stdout1.XXXXXXXXXX) 98 dst_out=$(mktemp -t mig-helper-stdout2.XXXXXXXXXX) 99 dst_outfifo=$(mktemp -u -t mig-helper-fifo-stdout2.XXXXXXXXXX) 100 src_qmp=$(mktemp -u -t mig-helper-qmp1.XXXXXXXXXX) 101 dst_qmp=$(mktemp -u -t mig-helper-qmp2.XXXXXXXXXX) 102 src_infifo=$(mktemp -u -t mig-helper-fifo-stdin1.XXXXXXXXXX) 103 dst_infifo=$(mktemp -u -t mig-helper-fifo-stdin2.XXXXXXXXXX) 104 src_qmpout=/dev/null 105 dst_qmpout=/dev/null 106 skip_migration=0 107 continuous_migration=0 108 109 mkfifo ${src_outfifo} 110 mkfifo ${dst_outfifo} 111 112 # Holding both ends of the input fifo open prevents opens from 113 # blocking and readers getting EOF when a writer closes it. 114 # These fds appear to be unused to shellcheck so quieten the warning. 115 mkfifo ${src_infifo} 116 mkfifo ${dst_infifo} 117 # shellcheck disable=SC2034 118 exec {src_infifo_fd}<>${src_infifo} 119 # shellcheck disable=SC2034 120 exec {dst_infifo_fd}<>${dst_infifo} 121 122 "${migcmdline[@]}" \ 123 -chardev socket,id=mon,path=${src_qmp},server=on,wait=off \ 124 -mon chardev=mon,mode=control \ 125 < ${src_infifo} > ${src_outfifo} & 126 live_pid=$! 127 # Shellcheck complains about useless cat but it is clearer than a 128 # redirect in this case. 129 # shellcheck disable=SC2002 130 cat ${src_outfifo} | tee ${src_out} | filter_quiet_msgs & 131 132 # Start the first destination QEMU machine in advance of the test 133 # reaching the migration point, since we expect at least one migration. 134 # Then destination machines are started after the test outputs 135 # subsequent "Now migrate the VM" messages. 136 do_migration || return $? 137 138 while ps -p ${live_pid} > /dev/null ; do 139 if [ ${continuous_migration} -eq 1 ] ; then 140 do_migration || return $? 141 elif ! seen_migrate_msg ${src_out} ; then 142 sleep 0.1 143 elif grep -q "Begin continuous migration" < ${src_out} ; then 144 do_migration || return $? 145 elif grep -q "Now migrate the VM" < ${src_out} ; then 146 do_migration || return $? 147 elif [ $skip_migration -eq 0 ] && grep -q "Skipped VM migration" < ${src_out} ; then 148 echo > ${src_infifo} # Resume src and carry on. 149 break; 150 fi 151 done 152 153 wait ${live_pid} 154 ret=$? 155 156 while (( $(jobs -r | wc -l) > 0 )); do 157 sleep 0.1 158 done 159 160 return $ret 161} 162 163do_migration () 164{ 165 "${migcmdline[@]}" \ 166 -chardev socket,id=mon,path=${dst_qmp},server=on,wait=off \ 167 -mon chardev=mon,mode=control -incoming unix:${dst_incoming} \ 168 < ${dst_infifo} > ${dst_outfifo} & 169 incoming_pid=$! 170 # Shellcheck complains about useless cat but it is clearer than a 171 # redirect in this case. 172 # shellcheck disable=SC2002 173 cat ${dst_outfifo} | tee ${dst_out} | filter_quiet_msgs & 174 175 # The test must prompt the user to migrate, so wait for the 176 # "Now migrate VM" or similar console message. 177 while [ ${continuous_migration} -eq 0 ] && ! seen_migrate_msg ${src_out} ; do 178 if ! ps -p ${live_pid} > /dev/null ; then 179 echo > ${dst_infifo} 180 qmp ${dst_qmp} '"quit"'> ${dst_qmpout} 2>/dev/null 181 echo "ERROR: Test exit before migration point." >&2 182 qmp ${src_qmp} '"quit"'> ${src_qmpout} 2>/dev/null 183 return 3 184 fi 185 sleep 0.1 186 done 187 188 if grep -q "Begin continuous migration" < ${src_out} ; then 189 if [ ${continuous_migration} -eq 1 ] ; then 190 echo > ${dst_infifo} 191 qmp ${dst_qmp} '"quit"'> ${dst_qmpout} 2>/dev/null 192 echo "ERROR: Continuous migration already begun." >&2 193 qmp ${src_qmp} '"quit"'> ${src_qmpout} 2>/dev/null 194 return 3 195 fi 196 continuous_migration=1 197 echo > ${src_infifo} 198 fi 199 200 # Wait until the destination has created the incoming and qmp sockets 201 while ! [ -S ${dst_incoming} ] ; do sleep 0.1 ; done 202 while ! [ -S ${dst_qmp} ] ; do sleep 0.1 ; done 203 204 if [ $skip_migration -eq 0 ] && grep -q "Skipped VM migration" < ${src_out} ; then 205 # May not get any migrations, exit to main loop for now... 206 # No migrations today, shut down dst in an orderly manner... 207 if [ ${continuous_migration} -eq 1 ] ; then 208 echo > ${dst_infifo} 209 qmp ${dst_qmp} '"quit"'> ${dst_qmpout} 2>/dev/null 210 echo "ERROR: Can't skip in continuous migration." >&2 211 qmp ${src_qmp} '"quit"'> ${src_qmpout} 2>/dev/null 212 return 3 213 fi 214 echo > ${dst_infifo} 215 qmp ${dst_qmp} '"quit"'> ${dst_qmpout} 2>/dev/null 216 echo > ${src_infifo} # Resume src and carry on. 217 skip_migration=1 218 return 0 219 fi 220 221 qmp ${src_qmp} '"migrate", "arguments": { "uri": "unix:'${dst_incoming}'" }' > ${src_qmpout} 222 223 # Wait for the migration to complete 224 migstatus=$(qmp ${src_qmp} '"query-migrate"' | grep return) 225 while ! grep -q '"completed"' <<<"$migstatus" ; do 226 sleep 0.1 227 if ! migstatus=$(qmp ${src_qmp} '"query-migrate"'); then 228 echo "ERROR: Querying migration state failed." >&2 229 echo > ${dst_infifo} 230 qmp ${dst_qmp} '"quit"'> ${dst_qmpout} 2>/dev/null 231 return 2 232 fi 233 migstatus=$(grep return <<<"$migstatus") 234 if grep -q '"failed"' <<<"$migstatus"; then 235 echo "ERROR: Migration failed." >&2 236 echo > ${dst_infifo} 237 qmp ${src_qmp} '"quit"'> ${src_qmpout} 2>/dev/null 238 qmp ${dst_qmp} '"quit"'> ${dst_qmpout} 2>/dev/null 239 return 2 240 fi 241 done 242 243 qmp ${src_qmp} '"quit"'> ${src_qmpout} 2>/dev/null 244 245 # Should we end continuous migration? 246 if grep -q "End continuous migration" < ${src_out} ; then 247 if [ ${continuous_migration} -eq 0 ] ; then 248 echo "ERROR: Can't end continuous migration when not started." >&2 249 echo > ${dst_infifo} 250 qmp ${dst_qmp} '"quit"'> ${dst_qmpout} 2>/dev/null 251 qmp ${src_qmp} '"quit"'> ${src_qmpout} 2>/dev/null 252 return 3 253 fi 254 continuous_migration=0 255 echo > ${src_infifo} 256 fi 257 258 if [ ${continuous_migration} -eq 0 ]; then 259 # keypress to dst so getchar completes and test continues 260 echo > ${dst_infifo} 261 fi 262 263 # Wait for the incoming socket being removed, ready for next destination 264 while [ -S ${dst_incoming} ] ; do sleep 0.1 ; done 265 266 wait ${live_pid} 267 ret=$? 268 269 # Now flip the variables because destination machine becomes source 270 # for the next migration. 271 live_pid=${incoming_pid} 272 tmp=${src_out} 273 src_out=${dst_out} 274 dst_out=${tmp} 275 tmp=${src_infifo} 276 src_infifo=${dst_infifo} 277 dst_infifo=${tmp} 278 tmp=${src_outfifo} 279 src_outfifo=${dst_outfifo} 280 dst_outfifo=${tmp} 281 tmp=${src_qmp} 282 src_qmp=${dst_qmp} 283 dst_qmp=${tmp} 284 285 return $ret 286} 287 288run_panic () 289{ 290 if ! command -v jq >/dev/null 2>&1; then 291 echo "${FUNCNAME[0]} needs jq" >&2 292 return 77 293 fi 294 295 trap 'trap - TERM ; kill 0 ; exit 2' INT TERM 296 trap 'rm -f ${qmp}.in ${qmp}.out' RETURN EXIT 297 298 qmp=$(mktemp -u -t panic-qmp.XXXXXXXXXX) 299 mkfifo ${qmp}.in ${qmp}.out 300 301 # start VM stopped so we don't miss any events 302 "$@" -chardev pipe,id=mon,path=${qmp} \ 303 -mon chardev=mon,mode=control -S & 304 echo '{ "execute": "qmp_capabilities" }{ "execute": "cont" }' > ${qmp}.in 305 306 panic_event_count=$(jq -c 'select(.event == "GUEST_PANICKED")' < ${qmp}.out | wc -l) 307 if [ "$panic_event_count" -lt 1 ]; then 308 echo "FAIL: guest did not panic" 309 ret=3 310 else 311 # some QEMU versions report multiple panic events 312 echo "PASS: guest panicked" 313 ret=1 314 fi 315 316 return $ret 317} 318 319migration_cmd () 320{ 321 if [ "$MIGRATION" = "yes" ]; then 322 echo "run_migration" 323 fi 324} 325 326panic_cmd () 327{ 328 if [ "$PANIC" = "yes" ]; then 329 echo "run_panic" 330 fi 331} 332 333search_qemu_binary () 334{ 335 local save_path=$PATH 336 local qemucmd qemu 337 338 : "${QEMU_ARCH:=$ARCH_NAME}" 339 340 export PATH=$PATH:/usr/libexec 341 for qemucmd in ${QEMU:-qemu-system-$QEMU_ARCH qemu-kvm}; do 342 if $qemucmd --help 2>/dev/null | grep -q 'QEMU'; then 343 qemu="$qemucmd" 344 break 345 fi 346 done 347 348 if [ -z "$qemu" ]; then 349 echo "A QEMU binary was not found." >&2 350 echo "You can set a custom location by using the QEMU=<path> environment variable." >&2 351 return 2 352 fi 353 command -v $qemu 354 export PATH=$save_path 355} 356 357search_kvmtool_binary () 358{ 359 local kvmtoolcmd kvmtool 360 361 for kvmtoolcmd in ${KVMTOOL:-lkvm vm lkvm-static}; do 362 if "$kvmtoolcmd" --help 2>/dev/null| grep -q 'The most commonly used'; then 363 kvmtool="$kvmtoolcmd" 364 break 365 fi 366 done 367 368 if [ -z "$kvmtool" ]; then 369 echo "A kvmtool binary was not found." >&2 370 echo "You can set a custom location by using the KVMTOOL=<path> environment variable." >&2 371 return 2 372 fi 373 374 command -v $kvmtool 375} 376 377initrd_cleanup () 378{ 379 rm -f $KVM_UNIT_TESTS_ENV 380 if [ "$KVM_UNIT_TESTS_ENV_OLD" ]; then 381 export KVM_UNIT_TESTS_ENV 382 KVM_UNIT_TESTS_ENV="$KVM_UNIT_TESTS_ENV_OLD" 383 else 384 unset KVM_UNIT_TESTS_ENV 385 fi 386 unset KVM_UNIT_TESTS_ENV_OLD 387} 388 389initrd_create () 390{ 391 if [ "$ENVIRON_DEFAULT" = "yes" ]; then 392 trap_exit_push 'initrd_cleanup' 393 [ -f "$KVM_UNIT_TESTS_ENV" ] && export KVM_UNIT_TESTS_ENV_OLD="$KVM_UNIT_TESTS_ENV" 394 export KVM_UNIT_TESTS_ENV 395 KVM_UNIT_TESTS_ENV=$(mktemp) 396 env_params 397 env_file 398 env_errata || return $? 399 fi 400 401 unset INITRD 402 [ -f "$KVM_UNIT_TESTS_ENV" ] && INITRD="$(vmm_optname_initrd) $KVM_UNIT_TESTS_ENV" 403 404 return 0 405} 406 407env_add_params () 408{ 409 local p 410 411 for p in "$@"; do 412 if eval test -v $p; then 413 eval export "$p" 414 else 415 eval export "$p=" 416 fi 417 grep "^$p=" <(env) >>$KVM_UNIT_TESTS_ENV 418 done 419} 420 421env_params () 422{ 423 local qemu have_qemu 424 local _ rest 425 426 env_add_params TARGET 427 428 # kvmtool's versioning has been broken since it was split from the 429 # kernel source. 430 if [ "$(vmm_get_target)" = "qemu" ]; then 431 qemu=$(search_qemu_binary) && have_qemu=1 432 if [ "$have_qemu" ]; then 433 if [ -n "$ACCEL" ] || [ -n "$QEMU_ACCEL" ]; then 434 [ -n "$ACCEL" ] && QEMU_ACCEL=$ACCEL 435 fi 436 QEMU_VERSION_STRING="$($qemu -h | head -1)" 437 # Shellcheck does not see QEMU_MAJOR|MINOR|MICRO are used 438 # shellcheck disable=SC2034 439 IFS='[ .]' read -r _ _ _ QEMU_MAJOR QEMU_MINOR QEMU_MICRO rest <<<"$QEMU_VERSION_STRING" 440 fi 441 env_add_params QEMU_ACCEL QEMU_VERSION_STRING QEMU_MAJOR QEMU_MINOR QEMU_MICRO 442 fi 443 444 KERNEL_VERSION_STRING=$(uname -r) 445 IFS=. read -r KERNEL_VERSION KERNEL_PATCHLEVEL rest <<<"$KERNEL_VERSION_STRING" 446 IFS=- read -r KERNEL_SUBLEVEL KERNEL_EXTRAVERSION <<<"$rest" 447 KERNEL_SUBLEVEL=${KERNEL_SUBLEVEL%%[!0-9]*} 448 KERNEL_EXTRAVERSION=${KERNEL_EXTRAVERSION%%[!0-9]*} 449 ! [[ $KERNEL_SUBLEVEL =~ ^[0-9]+$ ]] && unset $KERNEL_SUBLEVEL 450 ! [[ $KERNEL_EXTRAVERSION =~ ^[0-9]+$ ]] && unset $KERNEL_EXTRAVERSION 451 env_add_params KERNEL_VERSION_STRING KERNEL_VERSION KERNEL_PATCHLEVEL KERNEL_SUBLEVEL KERNEL_EXTRAVERSION 452} 453 454env_file () 455{ 456 local line var 457 458 [ ! -f "$KVM_UNIT_TESTS_ENV_OLD" ] && return 459 460 grep -E '^[[:blank:]]*[[:alpha:]_][[:alnum:]_]*=' "$KVM_UNIT_TESTS_ENV_OLD" | while IFS= read -r line ; do 461 var=${line%%=*} 462 if ! grep -q "^$var=" $KVM_UNIT_TESTS_ENV; then 463 eval export "$line" 464 grep "^$var=" <(env) >>$KVM_UNIT_TESTS_ENV 465 fi 466 done 467} 468 469env_errata () 470{ 471 local new_env 472 473 if [ "$ACCEL" = "tcg" ]; then 474 export "ERRATA_FORCE=y" 475 elif [ "$ERRATATXT" ] && [ ! -f "$ERRATATXT" ]; then 476 echo "$ERRATATXT not found. (ERRATATXT=$ERRATATXT)" >&2 477 return 2 478 elif [ "$ERRATATXT" ]; then 479 env_generate_errata 480 fi 481 new_env=$(sort <(env | grep '^ERRATA_') <(grep '^ERRATA_' $KVM_UNIT_TESTS_ENV) | uniq -u) 482 echo "$new_env" >>$KVM_UNIT_TESTS_ENV 483} 484 485env_generate_errata () 486{ 487 local line commit minver errata rest v p s x have 488 489 for line in $(grep -v '^#' "$ERRATATXT" | tr -d '[:blank:]' | cut -d: -f1,2); do 490 commit=${line%:*} 491 minver=${line#*:} 492 493 test -z "$commit" && continue 494 errata="ERRATA_$commit" 495 [ -n "${!errata}" ] && continue 496 497 IFS=. read -r v p rest <<<"$minver" 498 IFS=- read -r s x <<<"$rest" 499 s=${s%%[!0-9]*} 500 x=${x%%[!0-9]*} 501 502 if ! [[ $v =~ ^[0-9]+$ ]] || ! [[ $p =~ ^[0-9]+$ ]]; then 503 echo "Bad minimum kernel version in $ERRATATXT, $minver" 504 return 2 505 fi 506 ! [[ $s =~ ^[0-9]+$ ]] && unset $s 507 ! [[ $x =~ ^[0-9]+$ ]] && unset $x 508 509 if (( $KERNEL_VERSION > $v || 510 ($KERNEL_VERSION == $v && $KERNEL_PATCHLEVEL > $p) )); then 511 have=y 512 elif (( $KERNEL_VERSION == $v && $KERNEL_PATCHLEVEL == $p )); then 513 if [ "$KERNEL_SUBLEVEL" ] && [ "$s" ]; then 514 if (( $KERNEL_SUBLEVEL > $s )); then 515 have=y 516 elif (( $KERNEL_SUBLEVEL == $s )); then 517 if [ "$KERNEL_EXTRAVERSION" ] && [ "$x" ]; then 518 if (( $KERNEL_EXTRAVERSION >= $x )); then 519 have=y 520 else 521 have=n 522 fi 523 elif [ "$x" ] && (( $x != 0 )); then 524 have=n 525 else 526 have=y 527 fi 528 else 529 have=n 530 fi 531 elif [ "$s" ] && (( $s != 0 )); then 532 have=n 533 else 534 have=y 535 fi 536 else 537 have=n 538 fi 539 eval export "$errata=$have" 540 done 541} 542 543trap_exit_push () 544{ 545 local old_exit 546 547 old_exit=$(trap -p EXIT | sed "s/^[^']*'//;s/'[^']*$//") 548 trap -- "$1; $old_exit" EXIT 549} 550 551kvm_available () 552{ 553 [ -c /dev/kvm ] || 554 return 1 555 556 [ "$HOST" = "$ARCH_NAME" ] || 557 ( [ "$HOST" = aarch64 ] && [ "$ARCH" = arm ] ) || 558 ( [ "$HOST" = x86_64 ] && [ "$ARCH" = i386 ] ) 559} 560 561hvf_available () 562{ 563 [ "$(sysctl -n kern.hv_support 2>/dev/null)" = "1" ] || return 1 564 [ "$HOST" = "$ARCH_NAME" ] || 565 ( [ "$HOST" = x86_64 ] && [ "$ARCH" = i386 ] ) 566} 567 568set_qemu_accelerator () 569{ 570 # Shellcheck does not see ACCEL_PROPS is used 571 # shellcheck disable=SC2034 572 ACCEL_PROPS=${ACCEL#"${ACCEL%%,*}"} 573 ACCEL=${ACCEL%%,*} 574 575 if [ "$ACCEL" = "kvm" ] && ! kvm_available; then 576 echo "KVM is needed, but not available on this host" >&2 577 return 2 578 fi 579 if [ "$ACCEL" = "hvf" ] && ! hvf_available; then 580 echo "HVF is needed, but not available on this host" >&2 581 return 2 582 fi 583 584 if [ -z "$ACCEL" ]; then 585 if kvm_available; then 586 ACCEL="kvm" 587 elif hvf_available; then 588 ACCEL="hvf" 589 else 590 ACCEL="tcg" 591 fi 592 fi 593 594 return 0 595} 596