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