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