aboutsummaryrefslogtreecommitdiff
path: root/tests/boot
blob: 2f778eb6c9a3d7865c282a22b72160e9b60ff280 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# A real guest, booted for real. Skipped without a usable /dev/kvm, so the
# suite still exits 0 on a machine with no virtualisation.

if [ ! -r /dev/kvm ] || [ ! -w /dev/kvm ]; then
	skip 1 "no usable /dev/kvm, skipping the boot tests"
	return 0
fi

"$VMM" create bt1 64M >/dev/null 2>&1
cfg bt1 <<EOF
MEM=256
CPUS=1
EOF

exits 0 "start boots a guest" start bt1
PID=$(cat "$VMMDIR/bt1/pid" 2>/dev/null || echo)
if [ -n "$PID" ] && kill -0 "$PID" 2>/dev/null; then
	ok "qemu is running"
else
	notok "qemu is running" "$(tail -n 5 "$VMMDIR/bt1/stderr" 2>/dev/null)"
fi

holds "qemu's stdout lands in a file" test -f "$VMMDIR/bt1/stdout"
holds "qemu's stderr lands in another" test -f "$VMMDIR/bt1/stderr"
outputs '==> .*stderr <==' "logs shows every stream" logs bt1

exits 0 "status of a running VM exits 0" status bt1
outputs 'STATE=running' "status reports running" status bt1
outputs 'running' "list reports running" list

# The lock must be free after start, or every later verb reports busy.
holds "start releases its lock" flock -n "$VMMDIR/bt1" true

exits 0 "start is idempotent" start bt1
outputs 'already running' "a second start says so" start bt1

# restart swallows exit 5 from a stop that had to force the guest, and
# nothing else. A guest with no OS ignores ACPI, so this is that path,
# and it must come back as a different process.
old=$(cat "$VMMDIR/bt1/pid" 2>/dev/null || echo)
exits 0 "restart survives a stop that had to force" restart bt1
new=$(cat "$VMMDIR/bt1/pid" 2>/dev/null || echo)
if [ -n "$new" ] && [ "$new" != "$old" ]; then
	ok "restart comes back as a new process"
else
	notok "restart comes back as a new process" "was [$old] now [$new]"
fi
outputs 'STATE=running' "the restarted guest is running" status bt1

# A restart that cannot start again must not report success: the config
# is broken under the guest, so stop works and start refuses.
cp "$VMMDIR/bt1/config" "$WORK/bt1.config"
cfg bt1 <<EOF
NOSUCHKEY=1
EOF
refuses 'unknown key' "restart fails loudly when it cannot start again" restart bt1
exits 3 "and leaves the guest stopped, not half restarted" status bt1
cp "$WORK/bt1.config" "$VMMDIR/bt1/config"
exits 0 "start again for the tests below" start bt1

outputs 'running' "the monitor answers" monitor bt1 info status

# Commands read from stdin: the count says every one was sent.
out=$(printf 'info status\ninfo version\n' | "$VMM" monitor bt1 2>&1) && got=0 || got=$?
n=$(printf '%s\n' "$out" | grep -c '"return"' || :)
if [ "$got" = 0 ] && [ "$n" = 2 ]; then
	ok "monitor answers every command it reads from stdin"
else
	notok "monitor answers every command it reads from stdin" \
		"exit $got, $n replies" "$out"
fi

# A final line with no newline is a command too: dropped, it would be
# exit 0 and nothing done.
out=$(printf 'info status' | "$VMM" monitor bt1 2>&1) && got=0 || got=$?
if [ "$got" = 0 ] && printf '%s\n' "$out" | grep -q '"return"'; then
	ok "monitor sends a final line with no newline"
else
	notok "monitor sends a final line with no newline" "exit $got: $out"
fi

# The command is interpolated into a JSON string. This payload closes
# that string and the object around it, so an unescaped one reaches
# QEMU's parser as a second complete command and stops the guest. An
# unescaped backslash instead makes the object unparseable, and QEMU
# refuses it with an error carrying no id, so no reply arrives at all.
outputs 'unknown command' "a quote cannot close the monitor's own object" \
	monitor bt1 'x"}},{"execute":"quit'
exits 0 "and the quit it carried never ran" status bt1
outputs '"return"' "a backslash still leaves the monitor a parseable object" \
	monitor bt1 'info \ block'

exits 5 "stop escalates and exits 5 on a guest with no OS" stop bt1
holds "stop removes the pidfile once dead" test ! -e "$VMMDIR/bt1/pid"
exits 0 "stop is idempotent" stop bt1

# SNAPSHOT=yes must build a real overlay, never rely on -snapshot.
cp "$VMMDIR/bt1/disk.qcow2" "$WORK/bt1-before-snapshot.qcow2"
cfg bt1 <<EOF
MEM=256
CPUS=1
SNAPSHOT=yes
EOF
exits 0 "start with SNAPSHOT=yes" start bt1
holds "the overlay is unlinked once qemu holds it" \
	test ! -e "$VMMDIR/bt1/ephemeral.qcow2"
# Unlinked, but open: qemu still serves the guest from it, and says so.
outputs 'ephemeral.qcow2' "the guest runs on the overlay, not the disk" \
	monitor bt1 info block
outputs '"return": ""' "a snapshot accepts guest disk writes" monitor bt1 \
	'qemu-io -d /machine/peripheral/blk0/virtio-backend "write -P 0xa5 1048576 4096"'
exits 0 "the guest survives losing the overlay's name" status bt1

"$VMM" kill bt1 >/dev/null 2>&1 || :
holds "snapshot writes do not change the durable disk" qemu-img compare -q \
	"$WORK/bt1-before-snapshot.qcow2" "$VMMDIR/bt1/disk.qcow2"
exits 0 "kill is idempotent" kill bt1

# The same write without SNAPSHOT must survive a complete QEMU restart.
cfg bt1 <<EOF
MEM=256
CPUS=1
EOF
exits 0 "start without snapshot mode" start bt1
outputs '"return": ""' "an ordinary guest accepts a disk write" monitor bt1 \
	'qemu-io -d /machine/peripheral/blk0/virtio-backend "write -P 0x5a 2097152 4096"'
"$VMM" kill bt1 >/dev/null 2>&1 || :
exits 0 "restart after a durable disk write" start bt1
outputs '"return": ""' "an ordinary disk write survives restart" monitor bt1 \
	'qemu-io -d /machine/peripheral/blk0/virtio-backend "read -P 0x5a 2097152 4096"'
"$VMM" kill bt1 >/dev/null 2>&1 || :

# A running VM must be undeletable and unclonable.
cfg bt1 <<EOF
MEM=256
CPUS=1
EOF
"$VMM" start bt1 >/dev/null 2>&1 || :
exits 4 "delete refuses a running VM" delete -f bt1
exits 4 "clone refuses a running source" clone bt1 bt9
"$VMM" kill bt1 >/dev/null 2>&1 || :
exits 0 "delete -f removes it once stopped" delete -f bt1

# status answers about the process, so an unparseable config must not stop
# it, and a config edited under a live guest must not make it lie.
"$VMM" create bt2 64M >/dev/null 2>&1
cfg bt2 <<EOF
MEM=256
CPUS=1
EOF
"$VMM" start bt2 >/dev/null 2>&1 || :
cfg bt2 <<EOF
THIS IS NOT A CONFIG
EOF
outputs 'STATE=running' "status ignores a broken config" status bt2
omits '^MEM=' "status reports no config values at all" status bt2

# A live guest must be visible under every spelling of VMMDIR, or the
# verbs that clean up delete the state of a running guest.
state=$(VMMDIR="$VMMDIR/" "$VMM" status bt2 2>/dev/null | sed -n 's/^STATE=//p')
if [ "$state" = running ]; then
	ok "a running guest is visible through a trailing slash"
else
	notok "a running guest is visible through a trailing slash" "got [$state]"
fi

# vm_pid coming back empty is not proof of death. When some other live
# qemu owns the pidfile, the cleanup paths must leave the directory alone.
P2=$(cat "$VMMDIR/bt2/pid" 2>/dev/null || echo)
mkdir -p "$VMMDIR/bt3"
cp "$VMMDIR/bt2/config" "$VMMDIR/bt3/config"
printf '%s\n' "$P2" > "$VMMDIR/bt3/pid"
exits 4 "a different live qemu is reported as unknown state" stop bt3
holds "state owned by an unrecognised live qemu is left alone" \
	test -f "$VMMDIR/bt3/pid"
state=$("$VMM" list | awk '$1 == "bt3" { print $2 }')
if [ "$state" = unknown ]; then
	ok "list exposes unrecognised live qemu state"
else
	notok "list exposes unrecognised live qemu state" "got [$state]"
fi
rm -rf "$VMMDIR/bt3"

# A wedged monitor must be reported, not silently reported as success.
P=$(cat "$VMMDIR/bt2/pid" 2>/dev/null || echo)
if [ -n "$P" ]; then
	kill -STOP "$P" 2>/dev/null || :
	out=$(MONITOR_TIMEOUT=1 "$VMM" monitor bt2 info status 2>&1) && got=0 || got=$?
	kill -CONT "$P" 2>/dev/null || :
	if [ "$got" != 0 ] && printf '%s' "$out" | grep -q 'no reply'; then
		ok "a silent monitor is an error, not exit 0"
	else
		notok "a silent monitor is an error, not exit 0" "exit $got: $out"
	fi
	# The shell reports the job timeout kills unless it is reaped where
	# nobody can see it, and that report is not vmm's to print.
	if printf '%s' "$out" | grep -q 'Terminated'; then
		notok "a timed out call prints nothing but vmm's own error" "$out"
	else
		ok "a timed out call prints nothing but vmm's own error"
	fi
else
	skip 2 "bt2 did not start, cannot test the wedged monitor"
fi

"$VMM" kill bt2 >/dev/null 2>&1 || :
exits 3 "status of the killed guest exits 3" status bt2

# GRAPHICS=vnc listens on a unix socket in the VM directory, and status
# reports the socket that is really there rather than what the config says.
"$VMM" create bt4 64M >/dev/null 2>&1
cfg bt4 <<EOF
MEM=256
CPUS=1
GRAPHICS=vnc
EOF
if "$VMM" start bt4 >/dev/null 2>&1; then
	holds "GRAPHICS=vnc creates the socket" test -S "$VMMDIR/bt4/vnc.sock"
	outputs "VNC=$VMMDIR/bt4/vnc.sock" "status names the VNC socket" status bt4
	"$VMM" kill bt4 >/dev/null 2>&1 || :
	holds "the socket is removed once stopped" test ! -e "$VMMDIR/bt4/vnc.sock"
	out=$("$VMM" status bt4 2>&1) && got=0 || got=$?
	if [ "$got" = 3 ] && ! printf '%s\n' "$out" | grep -q '^VNC='; then
		ok "a stopped VM has no VNC line"
	else
		notok "a stopped VM has no VNC line" "exit $got: $out"
	fi
else
	skip 4 "this qemu cannot do GRAPHICS=vnc"
fi

# One directory per VM means two guests are independent of each other:
# each names its own process, and killing one leaves the other alone.
"$VMM" create bt5 64M >/dev/null 2>&1
"$VMM" create bt6 64M >/dev/null 2>&1
for v in bt5 bt6; do
	cfg "$v" <<CFG
MEM=256
CPUS=1
CFG
	"$VMM" start "$v" >/dev/null 2>&1 || :
done
p5=$(cat "$VMMDIR/bt5/pid" 2>/dev/null || echo)
p6=$(cat "$VMMDIR/bt6/pid" 2>/dev/null || echo)
holds "two guests run at once, as two processes" \
	test -n "$p5" -a -n "$p6" -a "$p5" != "$p6"
"$VMM" kill bt5 >/dev/null 2>&1 || :
exits 3 "killing one stops that one" status bt5
exits 0 "and leaves the other running" status bt6
"$VMM" kill bt6 >/dev/null 2>&1 || :