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