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