xref: /kvm-unit-tests/scripts/arch-run.bash (revision b7e5dfd367f4a79fa9f71c259f2a01eca7b08432)
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