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