xref: /kvm-unit-tests/scripts/arch-run.bash (revision db9c4e1c8c2ab14af6a61d62604b7754b808833f) !
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 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		return 2
382	fi
383
384	command -v $kvmtool
385}
386
387initrd_cleanup ()
388{
389	rm -f $KVM_UNIT_TESTS_ENV
390	if [ "$KVM_UNIT_TESTS_ENV_OLD" ]; then
391		export KVM_UNIT_TESTS_ENV
392		KVM_UNIT_TESTS_ENV="$KVM_UNIT_TESTS_ENV_OLD"
393	else
394		unset KVM_UNIT_TESTS_ENV
395	fi
396	unset KVM_UNIT_TESTS_ENV_OLD
397}
398
399initrd_create ()
400{
401	if [ "$ENVIRON_DEFAULT" = "yes" ]; then
402		trap_exit_push 'initrd_cleanup'
403		[ -f "$KVM_UNIT_TESTS_ENV" ] && export KVM_UNIT_TESTS_ENV_OLD="$KVM_UNIT_TESTS_ENV"
404		export KVM_UNIT_TESTS_ENV
405		KVM_UNIT_TESTS_ENV=$(mktemp)
406		env_params
407		env_file
408		env_errata || return $?
409	fi
410
411	unset INITRD
412	[ -f "$KVM_UNIT_TESTS_ENV" ] && INITRD="$(vmm_optname_initrd) $KVM_UNIT_TESTS_ENV"
413
414	return 0
415}
416
417env_add_params ()
418{
419	local p
420
421	for p in "$@"; do
422		if eval test -v $p; then
423			eval export "$p"
424		else
425			eval export "$p="
426		fi
427		grep "^$p=" <(env) >>$KVM_UNIT_TESTS_ENV
428	done
429}
430
431env_params ()
432{
433	local qemu have_qemu
434	local _ rest
435
436	env_add_params TARGET
437
438	# kvmtool's versioning has been broken since it was split from the
439	# kernel source.
440	if [ "$(vmm_get_target)" = "qemu" ]; then
441		qemu=$(search_qemu_binary) && have_qemu=1
442		if [ "$have_qemu" ]; then
443			if [ -n "$ACCEL" ] || [ -n "$QEMU_ACCEL" ]; then
444				[ -n "$ACCEL" ] && QEMU_ACCEL=$ACCEL
445			fi
446			QEMU_VERSION_STRING="$($qemu -h | head -1)"
447			# Shellcheck does not see QEMU_MAJOR|MINOR|MICRO are used
448			# shellcheck disable=SC2034
449			IFS='[ .]' read -r _ _ _ QEMU_MAJOR QEMU_MINOR QEMU_MICRO rest <<<"$QEMU_VERSION_STRING"
450		fi
451		env_add_params QEMU_ACCEL QEMU_VERSION_STRING QEMU_MAJOR QEMU_MINOR QEMU_MICRO
452	fi
453
454	KERNEL_VERSION_STRING=$(uname -r)
455	IFS=. read -r KERNEL_VERSION KERNEL_PATCHLEVEL rest <<<"$KERNEL_VERSION_STRING"
456	IFS=- read -r KERNEL_SUBLEVEL KERNEL_EXTRAVERSION <<<"$rest"
457	KERNEL_SUBLEVEL=${KERNEL_SUBLEVEL%%[!0-9]*}
458	KERNEL_EXTRAVERSION=${KERNEL_EXTRAVERSION%%[!0-9]*}
459	! [[ $KERNEL_SUBLEVEL =~ ^[0-9]+$ ]] && unset $KERNEL_SUBLEVEL
460	! [[ $KERNEL_EXTRAVERSION =~ ^[0-9]+$ ]] && unset $KERNEL_EXTRAVERSION
461	env_add_params KERNEL_VERSION_STRING KERNEL_VERSION KERNEL_PATCHLEVEL KERNEL_SUBLEVEL KERNEL_EXTRAVERSION
462}
463
464env_file ()
465{
466	local line var
467
468	[ ! -f "$KVM_UNIT_TESTS_ENV_OLD" ] && return
469
470	grep -E '^[[:blank:]]*[[:alpha:]_][[:alnum:]_]*=' "$KVM_UNIT_TESTS_ENV_OLD" | while IFS= read -r line ; do
471		var=${line%%=*}
472		if ! grep -q "^$var=" $KVM_UNIT_TESTS_ENV; then
473			eval export "$line"
474			grep "^$var=" <(env) >>$KVM_UNIT_TESTS_ENV
475		fi
476	done
477}
478
479env_errata ()
480{
481	local new_env
482
483	if [ "$ACCEL" = "tcg" ]; then
484		export "ERRATA_FORCE=y"
485	elif [ "$ERRATATXT" ] && [ ! -f "$ERRATATXT" ]; then
486		echo "$ERRATATXT not found. (ERRATATXT=$ERRATATXT)" >&2
487		return 2
488	elif [ "$ERRATATXT" ]; then
489		env_generate_errata
490	fi
491	new_env=$(sort <(env | grep '^ERRATA_') <(grep '^ERRATA_' $KVM_UNIT_TESTS_ENV) | uniq -u)
492	echo "$new_env" >>$KVM_UNIT_TESTS_ENV
493}
494
495env_generate_errata ()
496{
497	local line commit minver errata rest v p s x have
498
499	for line in $(grep -v '^#' "$ERRATATXT" | tr -d '[:blank:]' | cut -d: -f1,2); do
500		commit=${line%:*}
501		minver=${line#*:}
502
503		test -z "$commit" && continue
504		errata="ERRATA_$commit"
505		[ -n "${!errata}" ] && continue
506
507		IFS=. read -r v p rest <<<"$minver"
508		IFS=- read -r s x <<<"$rest"
509		s=${s%%[!0-9]*}
510		x=${x%%[!0-9]*}
511
512		if ! [[ $v =~ ^[0-9]+$ ]] || ! [[ $p =~ ^[0-9]+$ ]]; then
513			echo "Bad minimum kernel version in $ERRATATXT, $minver"
514			return 2
515		fi
516		! [[ $s =~ ^[0-9]+$ ]] && unset $s
517		! [[ $x =~ ^[0-9]+$ ]] && unset $x
518
519		if (( $KERNEL_VERSION > $v ||
520		      ($KERNEL_VERSION == $v && $KERNEL_PATCHLEVEL > $p) )); then
521			have=y
522		elif (( $KERNEL_VERSION == $v && $KERNEL_PATCHLEVEL == $p )); then
523			if [ "$KERNEL_SUBLEVEL" ] && [ "$s" ]; then
524				if (( $KERNEL_SUBLEVEL > $s )); then
525					have=y
526				elif (( $KERNEL_SUBLEVEL == $s )); then
527					if [ "$KERNEL_EXTRAVERSION" ] && [ "$x" ]; then
528						if (( $KERNEL_EXTRAVERSION >= $x )); then
529							have=y
530						else
531							have=n
532						fi
533					elif [ "$x" ] && (( $x != 0 )); then
534						have=n
535					else
536						have=y
537					fi
538				else
539					have=n
540				fi
541			elif [ "$s" ] && (( $s != 0 )); then
542				have=n
543			else
544				have=y
545			fi
546		else
547			have=n
548		fi
549		eval export "$errata=$have"
550	done
551}
552
553trap_exit_push ()
554{
555	local old_exit
556
557	old_exit=$(trap -p EXIT | sed "s/^[^']*'//;s/'[^']*$//")
558	trap -- "$1; $old_exit" EXIT
559}
560
561kvm_available ()
562{
563	[ -c /dev/kvm ] ||
564		return 1
565
566	[ "$HOST" = "$ARCH_NAME" ] ||
567		( [ "$HOST" = aarch64 ] && [ "$ARCH" = arm ] ) ||
568		( [ "$HOST" = x86_64 ] && [ "$ARCH" = i386 ] )
569}
570
571hvf_available ()
572{
573	[ "$(sysctl -n kern.hv_support 2>/dev/null)" = "1" ] || return 1
574	[ "$HOST" = "$ARCH_NAME" ] ||
575		( [ "$HOST" = x86_64 ] && [ "$ARCH" = i386 ] )
576}
577
578set_qemu_accelerator ()
579{
580	# Shellcheck does not see ACCEL_PROPS is used
581	# shellcheck disable=SC2034
582	ACCEL_PROPS=${ACCEL#"${ACCEL%%,*}"}
583	ACCEL=${ACCEL%%,*}
584
585	if [ "$ACCEL" = "kvm" ] && ! kvm_available; then
586		echo "KVM is needed, but not available on this host" >&2
587		return 2
588	fi
589	if [ "$ACCEL" = "hvf" ] && ! hvf_available; then
590		echo "HVF is needed, but not available on this host" >&2
591		return 2
592	fi
593
594	if [ -z "$ACCEL" ]; then
595		if kvm_available; then
596			ACCEL="kvm"
597		elif hvf_available; then
598			ACCEL="hvf"
599		else
600			ACCEL="tcg"
601		fi
602	fi
603
604	return 0
605}
606