xref: /kvm-unit-tests/scripts/arch-run.bash (revision d50fa5a856a9ac1508cac9a923aad21abb593322)
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 [ -z "$(echo "$errors" | grep -vi warning)" ]; then
65			ret=0
66		fi
67	fi
68
69	return $ret
70}
71
72run_qemu_status ()
73{
74	local stdout ret
75
76	exec {stdout}>&1
77	lines=$(run_qemu "$@" > >(tee /dev/fd/$stdout))
78	ret=$?
79	exec {stdout}>&-
80
81	if [ $ret -eq 1 ]; then
82		testret=$(grep '^EXIT: ' <<<"$lines" | head -n1 | sed 's/.*STATUS=\([0-9][0-9]*\).*/\1/')
83		if [ "$testret" ]; then
84			if [ $testret -eq 1 ]; then
85				ret=0
86			else
87				ret=$testret
88			fi
89		fi
90	fi
91
92	return $ret
93}
94
95timeout_cmd ()
96{
97	local s
98
99	if [ "$TIMEOUT" ] && [ "$TIMEOUT" != "0" ]; then
100		if [ "$CONFIG_EFI" = 'y' ]; then
101			s=${TIMEOUT: -1}
102			if [ "$s" = 's' ]; then
103				TIMEOUT=${TIMEOUT:0:-1}
104				((TIMEOUT += 10)) # Add 10 seconds for booting UEFI
105				TIMEOUT="${TIMEOUT}s"
106			fi
107		fi
108		echo "timeout -k 1s --foreground $TIMEOUT"
109	fi
110}
111
112qmp ()
113{
114	echo '{ "execute": "qmp_capabilities" }{ "execute":' "$2" '}' | ncat -U $1
115}
116
117qmp_events ()
118{
119	while ! test -S "$1"; do sleep 0.1; done
120	echo '{ "execute": "qmp_capabilities" }{ "execute": "cont" }' |
121		ncat --no-shutdown -U $1 |
122		jq -c 'select(has("event"))'
123}
124
125run_migration ()
126{
127	if ! command -v ncat >/dev/null 2>&1; then
128		echo "${FUNCNAME[0]} needs ncat (netcat)" >&2
129		return 77
130	fi
131
132	migcmdline=$@
133
134	trap 'trap - TERM ; kill 0 ; exit 2' INT TERM
135	trap 'rm -f ${src_out} ${dst_out} ${src_outfifo} ${dst_outfifo} ${dst_incoming} ${src_qmp} ${dst_qmp} ${dst_infifo}' RETURN EXIT
136
137	dst_incoming=$(mktemp -u -t mig-helper-socket-incoming.XXXXXXXXXX)
138	src_out=$(mktemp -t mig-helper-stdout1.XXXXXXXXXX)
139	src_outfifo=$(mktemp -u -t mig-helper-fifo-stdout1.XXXXXXXXXX)
140	dst_out=$(mktemp -t mig-helper-stdout2.XXXXXXXXXX)
141	dst_outfifo=$(mktemp -u -t mig-helper-fifo-stdout2.XXXXXXXXXX)
142	src_qmp=$(mktemp -u -t mig-helper-qmp1.XXXXXXXXXX)
143	dst_qmp=$(mktemp -u -t mig-helper-qmp2.XXXXXXXXXX)
144	dst_infifo=$(mktemp -u -t mig-helper-fifo-stdin.XXXXXXXXXX)
145	src_qmpout=/dev/null
146	dst_qmpout=/dev/null
147
148	mkfifo ${src_outfifo}
149	mkfifo ${dst_outfifo}
150
151	eval "$migcmdline" \
152		-chardev socket,id=mon,path=${src_qmp},server=on,wait=off \
153		-mon chardev=mon,mode=control > ${src_outfifo} &
154	live_pid=$!
155	cat ${src_outfifo} | tee ${src_out} | grep -v "Now migrate the VM (quiet)" &
156
157	# Start the first destination QEMU machine in advance of the test
158	# reaching the migration point, since we expect at least one migration.
159	# Then destination machines are started after the test outputs
160	# subsequent "Now migrate the VM" messages.
161	do_migration || return $?
162
163	while ps -p ${live_pid} > /dev/null ; do
164		# Wait for test exit or further migration messages.
165		if ! grep -q -i "Now migrate the VM" < ${src_out} ; then
166			sleep 0.1
167		else
168			do_migration || return $?
169		fi
170	done
171
172	wait ${live_pid}
173	ret=$?
174
175	while (( $(jobs -r | wc -l) > 0 )); do
176		sleep 0.1
177	done
178
179	return $ret
180}
181
182do_migration ()
183{
184	# We have to use cat to open the named FIFO, because named FIFO's,
185	# unlike pipes, will block on open() until the other end is also
186	# opened, and that totally breaks QEMU...
187	mkfifo ${dst_infifo}
188	eval "$migcmdline" \
189		-chardev socket,id=mon,path=${dst_qmp},server=on,wait=off \
190		-mon chardev=mon,mode=control -incoming unix:${dst_incoming} \
191		< <(cat ${dst_infifo}) > ${dst_outfifo} &
192	incoming_pid=$!
193	cat ${dst_outfifo} | tee ${dst_out} | grep -v "Now migrate the VM (quiet)" &
194
195	# The test must prompt the user to migrate, so wait for the
196	# "Now migrate VM" console message.
197	while ! grep -q -i "Now migrate the VM" < ${src_out} ; do
198		if ! ps -p ${live_pid} > /dev/null ; then
199			echo "ERROR: Test exit before migration point." >&2
200			echo > ${dst_infifo}
201			qmp ${src_qmp} '"quit"'> ${src_qmpout} 2>/dev/null
202			qmp ${dst_qmp} '"quit"'> ${dst_qmpout} 2>/dev/null
203			return 3
204		fi
205		sleep 0.1
206	done
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	qmp ${src_qmp} '"migrate", "arguments": { "uri": "unix:'${dst_incoming}'" }' > ${src_qmpout}
213
214	# Wait for the migration to complete
215	migstatus=`qmp ${src_qmp} '"query-migrate"' | grep return`
216	while ! grep -q '"completed"' <<<"$migstatus" ; do
217		sleep 0.1
218		if ! migstatus=`qmp ${src_qmp} '"query-migrate"'`; then
219			echo "ERROR: Querying migration state failed." >&2
220			echo > ${dst_infifo}
221			qmp ${dst_qmp} '"quit"'> ${dst_qmpout} 2>/dev/null
222			return 2
223		fi
224		migstatus=`grep return <<<"$migstatus"`
225		if grep -q '"failed"' <<<"$migstatus"; then
226			echo "ERROR: Migration failed." >&2
227			echo > ${dst_infifo}
228			qmp ${src_qmp} '"quit"'> ${src_qmpout} 2>/dev/null
229			qmp ${dst_qmp} '"quit"'> ${dst_qmpout} 2>/dev/null
230			return 2
231		fi
232	done
233
234	qmp ${src_qmp} '"quit"'> ${src_qmpout} 2>/dev/null
235
236	# keypress to dst so getchar completes and test continues
237	echo > ${dst_infifo}
238	rm ${dst_infifo}
239
240	# Wait for the incoming socket being removed, ready for next destination
241	while [ -S ${dst_incoming} ] ; do sleep 0.1 ; done
242
243	wait ${live_pid}
244	ret=$?
245
246	# Now flip the variables because destination machine becomes source
247	# for the next migration.
248	live_pid=${incoming_pid}
249	tmp=${src_out}
250	src_out=${dst_out}
251	dst_out=${tmp}
252	tmp=${src_outfifo}
253	src_outfifo=${dst_outfifo}
254	dst_outfifo=${tmp}
255	tmp=${src_qmp}
256	src_qmp=${dst_qmp}
257	dst_qmp=${tmp}
258
259	return $ret
260}
261
262run_panic ()
263{
264	if ! command -v ncat >/dev/null 2>&1; then
265		echo "${FUNCNAME[0]} needs ncat (netcat)" >&2
266		return 77
267	fi
268
269	if ! command -v jq >/dev/null 2>&1; then
270		echo "${FUNCNAME[0]} needs jq" >&2
271		return 77
272	fi
273
274	trap 'trap - TERM ; kill 0 ; exit 2' INT TERM
275	trap 'rm -f ${qmp}' RETURN EXIT
276
277	qmp=$(mktemp -u -t panic-qmp.XXXXXXXXXX)
278
279	# start VM stopped so we don't miss any events
280	eval "$@" -chardev socket,id=mon,path=${qmp},server=on,wait=off \
281		-mon chardev=mon,mode=control -S &
282
283	panic_event_count=$(qmp_events ${qmp} | jq -c 'select(.event == "GUEST_PANICKED")' | wc -l)
284	if [ "$panic_event_count" -lt 1 ]; then
285		echo "FAIL: guest did not panic"
286		ret=3
287	else
288		# some QEMU versions report multiple panic events
289		echo "PASS: guest panicked"
290		ret=1
291	fi
292
293	return $ret
294}
295
296migration_cmd ()
297{
298	if [ "$MIGRATION" = "yes" ]; then
299		echo "run_migration"
300	fi
301}
302
303panic_cmd ()
304{
305	if [ "$PANIC" = "yes" ]; then
306		echo "run_panic"
307	fi
308}
309
310search_qemu_binary ()
311{
312	local save_path=$PATH
313	local qemucmd qemu
314
315	: "${QEMU_ARCH:=$ARCH_NAME}"
316
317	export PATH=$PATH:/usr/libexec
318	for qemucmd in ${QEMU:-qemu-system-$QEMU_ARCH qemu-kvm}; do
319		if $qemucmd --help 2>/dev/null | grep -q 'QEMU'; then
320			qemu="$qemucmd"
321			break
322		fi
323	done
324
325	if [ -z "$qemu" ]; then
326		echo "A QEMU binary was not found." >&2
327		echo "You can set a custom location by using the QEMU=<path> environment variable." >&2
328		return 2
329	fi
330	command -v $qemu
331	export PATH=$save_path
332}
333
334initrd_cleanup ()
335{
336	rm -f $KVM_UNIT_TESTS_ENV
337	if [ "$KVM_UNIT_TESTS_ENV_OLD" ]; then
338		export KVM_UNIT_TESTS_ENV="$KVM_UNIT_TESTS_ENV_OLD"
339	else
340		unset KVM_UNIT_TESTS_ENV
341	fi
342	unset KVM_UNIT_TESTS_ENV_OLD
343}
344
345initrd_create ()
346{
347	if [ "$ENVIRON_DEFAULT" = "yes" ]; then
348		trap_exit_push 'initrd_cleanup'
349		[ -f "$KVM_UNIT_TESTS_ENV" ] && export KVM_UNIT_TESTS_ENV_OLD="$KVM_UNIT_TESTS_ENV"
350		export KVM_UNIT_TESTS_ENV=$(mktemp)
351		env_params
352		env_file
353		env_errata || return $?
354	fi
355
356	unset INITRD
357	[ -f "$KVM_UNIT_TESTS_ENV" ] && INITRD="-initrd $KVM_UNIT_TESTS_ENV"
358
359	return 0
360}
361
362env_add_params ()
363{
364	local p
365
366	for p in "$@"; do
367		if eval test -v $p; then
368			eval export "$p"
369		else
370			eval export "$p="
371		fi
372		grep "^$p=" <(env) >>$KVM_UNIT_TESTS_ENV
373	done
374}
375
376env_params ()
377{
378	local qemu have_qemu
379	local _ rest
380
381	qemu=$(search_qemu_binary) && have_qemu=1
382
383	if [ "$have_qemu" ]; then
384		if [ -n "$ACCEL" ] || [ -n "$QEMU_ACCEL" ]; then
385			[ -n "$ACCEL" ] && QEMU_ACCEL=$ACCEL
386		fi
387		QEMU_VERSION_STRING="$($qemu -h | head -1)"
388		IFS='[ .]' read -r _ _ _ QEMU_MAJOR QEMU_MINOR QEMU_MICRO rest <<<"$QEMU_VERSION_STRING"
389	fi
390	env_add_params QEMU_ACCEL QEMU_VERSION_STRING QEMU_MAJOR QEMU_MINOR QEMU_MICRO
391
392	KERNEL_VERSION_STRING=$(uname -r)
393	IFS=. read -r KERNEL_VERSION KERNEL_PATCHLEVEL rest <<<"$KERNEL_VERSION_STRING"
394	IFS=- read -r KERNEL_SUBLEVEL KERNEL_EXTRAVERSION <<<"$rest"
395	KERNEL_SUBLEVEL=${KERNEL_SUBLEVEL%%[!0-9]*}
396	KERNEL_EXTRAVERSION=${KERNEL_EXTRAVERSION%%[!0-9]*}
397	! [[ $KERNEL_SUBLEVEL =~ ^[0-9]+$ ]] && unset $KERNEL_SUBLEVEL
398	! [[ $KERNEL_EXTRAVERSION =~ ^[0-9]+$ ]] && unset $KERNEL_EXTRAVERSION
399	env_add_params KERNEL_VERSION_STRING KERNEL_VERSION KERNEL_PATCHLEVEL KERNEL_SUBLEVEL KERNEL_EXTRAVERSION
400}
401
402env_file ()
403{
404	local line var
405
406	[ ! -f "$KVM_UNIT_TESTS_ENV_OLD" ] && return
407
408	for line in $(grep -E '^[[:blank:]]*[[:alpha:]_][[:alnum:]_]*=' "$KVM_UNIT_TESTS_ENV_OLD"); do
409		var=${line%%=*}
410		if ! grep -q "^$var=" $KVM_UNIT_TESTS_ENV; then
411			eval export "$line"
412			grep "^$var=" <(env) >>$KVM_UNIT_TESTS_ENV
413		fi
414	done
415}
416
417env_errata ()
418{
419	local new_env
420
421	if [ "$ACCEL" = "tcg" ]; then
422		export "ERRATA_FORCE=y"
423	elif [ "$ERRATATXT" ] && [ ! -f "$ERRATATXT" ]; then
424		echo "$ERRATATXT not found. (ERRATATXT=$ERRATATXT)" >&2
425		return 2
426	elif [ "$ERRATATXT" ]; then
427		env_generate_errata
428	fi
429	new_env=$(sort <(env | grep '^ERRATA_') <(grep '^ERRATA_' $KVM_UNIT_TESTS_ENV) | uniq -u)
430	echo "$new_env" >>$KVM_UNIT_TESTS_ENV
431}
432
433env_generate_errata ()
434{
435	local line commit minver errata rest v p s x have
436
437	for line in $(grep -v '^#' "$ERRATATXT" | tr -d '[:blank:]' | cut -d: -f1,2); do
438		commit=${line%:*}
439		minver=${line#*:}
440
441		test -z "$commit" && continue
442		errata="ERRATA_$commit"
443		[ -n "${!errata}" ] && continue
444
445		IFS=. read -r v p rest <<<"$minver"
446		IFS=- read -r s x <<<"$rest"
447		s=${s%%[!0-9]*}
448		x=${x%%[!0-9]*}
449
450		if ! [[ $v =~ ^[0-9]+$ ]] || ! [[ $p =~ ^[0-9]+$ ]]; then
451			echo "Bad minimum kernel version in $ERRATATXT, $minver"
452			return 2
453		fi
454		! [[ $s =~ ^[0-9]+$ ]] && unset $s
455		! [[ $x =~ ^[0-9]+$ ]] && unset $x
456
457		if (( $KERNEL_VERSION > $v ||
458		      ($KERNEL_VERSION == $v && $KERNEL_PATCHLEVEL > $p) )); then
459			have=y
460		elif (( $KERNEL_VERSION == $v && $KERNEL_PATCHLEVEL == $p )); then
461			if [ "$KERNEL_SUBLEVEL" ] && [ "$s" ]; then
462				if (( $KERNEL_SUBLEVEL > $s )); then
463					have=y
464				elif (( $KERNEL_SUBLEVEL == $s )); then
465					if [ "$KERNEL_EXTRAVERSION" ] && [ "$x" ]; then
466						if (( $KERNEL_EXTRAVERSION >= $x )); then
467							have=y
468						else
469							have=n
470						fi
471					elif [ "$x" ] && (( $x != 0 )); then
472						have=n
473					else
474						have=y
475					fi
476				else
477					have=n
478				fi
479			elif [ "$s" ] && (( $s != 0 )); then
480				have=n
481			else
482				have=y
483			fi
484		else
485			have=n
486		fi
487		eval export "$errata=$have"
488	done
489}
490
491trap_exit_push ()
492{
493	local old_exit=$(trap -p EXIT | sed "s/^[^']*'//;s/'[^']*$//")
494	trap -- "$1; $old_exit" EXIT
495}
496
497kvm_available ()
498{
499	[ -c /dev/kvm ] ||
500		return 1
501
502	[ "$HOST" = "$ARCH_NAME" ] ||
503		( [ "$HOST" = aarch64 ] && [ "$ARCH" = arm ] ) ||
504		( [ "$HOST" = x86_64 ] && [ "$ARCH" = i386 ] )
505}
506
507hvf_available ()
508{
509	[ "$(sysctl -n kern.hv_support 2>/dev/null)" = "1" ] || return 1
510	[ "$HOST" = "$ARCH_NAME" ] ||
511		( [ "$HOST" = x86_64 ] && [ "$ARCH" = i386 ] )
512}
513
514set_qemu_accelerator ()
515{
516	ACCEL_PROPS=${ACCEL#"${ACCEL%%,*}"}
517	ACCEL=${ACCEL%%,*}
518
519	if [ "$ACCEL" = "kvm" ] && ! kvm_available; then
520		echo "KVM is needed, but not available on this host" >&2
521		return 2
522	fi
523	if [ "$ACCEL" = "hvf" ] && ! hvf_available; then
524		echo "HVF is needed, but not available on this host" >&2
525		return 2
526	fi
527
528	if [ -z "$ACCEL" ]; then
529		if kvm_available; then
530			ACCEL="kvm"
531		elif hvf_available; then
532			ACCEL="hvf"
533		else
534			ACCEL="tcg"
535		fi
536	fi
537
538	return 0
539}
540