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