Merge ~smoser/ubuntu/+source/open-iscsi:bug/1785108-xenial-net-interface-handler-runs-always into ubuntu/+source/open-iscsi:ubuntu/xenial-devel
- Git
- lp:~smoser/ubuntu/+source/open-iscsi
- bug/1785108-xenial-net-interface-handler-runs-always
- Merge into ubuntu/xenial-devel
Status: | Merged |
---|---|
Merged at revision: | ebcb7919e134ee84ce8049788e483b468adb5af1 |
Proposed branch: | ~smoser/ubuntu/+source/open-iscsi:bug/1785108-xenial-net-interface-handler-runs-always |
Merge into: | ubuntu/+source/open-iscsi:ubuntu/xenial-devel |
Diff against target: |
2137 lines (+1518/-298) 9 files modified
debian/changelog (+8/-0) debian/net-interface-handler (+18/-4) debian/tests/README-boot-test.md (+139/-0) debian/tests/control (+6/-2) debian/tests/get-image (+123/-31) debian/tests/patch-image (+288/-0) debian/tests/test-open-iscsi.py (+191/-75) debian/tests/tgt-boot-test (+494/-159) debian/tests/xkvm (+251/-27) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Francis Ginther | Pending | ||
git-ubuntu developers | Pending | ||
Review via email: mp+352216@code.launchpad.net |
Commit message
net-interface-
net-interface-
interface for all network device adds and it "down" for all the removes.
The quotes are used above because the design point of the script is to
basically fake ifupdown into believing the event already occurred.
The change here is to only operate when the INTERFACE provided by
udev is the iscsi root device.
I've also pulled back the functioning dep8 tests from cosmic.
This allows us to actually have a test that shows the failure and
passes with the changes.
LP: #1785108
Description of the change
see commit message
- 03dc4a4... by Scott Moser
-
releasing package open-iscsi version 2.0.873+
git0.3b4b4500- 14ubuntu3. 5 - f5cfc6b... by Scott Moser
-
copy debian/tests cloud image test back from cosmic.
The open-iscsi boot test has been quite problematic in the past.
There are a number of fixes since it was last touched in 16.04 and
it is now running consistently in cosmic.So bring back the tests and tools from cosmic.
- beeb3a5... by Scott Moser
-
debian/
tests/test- open-iscsi. py: attempt to trigger 1785108 Without the fix applied, this would cause test failure.
- 710ebad... by Scott Moser
-
update changelog pull test back
- 7a7fad1... by Scott Moser
-
ammend tests to have depends open-iscsi
Preview Diff
1 | diff --git a/debian/changelog b/debian/changelog |
2 | index b23a821..e6dbe18 100644 |
3 | --- a/debian/changelog |
4 | +++ b/debian/changelog |
5 | @@ -1,3 +1,11 @@ |
6 | +open-iscsi (2.0.873+git0.3b4b4500-14ubuntu3.5) xenial; urgency=medium |
7 | + |
8 | + * d/tests: pull back cloud image test updates from cosmic. |
9 | + * d/net-interface-handler: Apply changes only for the iscsi-root |
10 | + (LP: #1785108) |
11 | + |
12 | + -- Scott Moser <smoser@ubuntu.com> Thu, 02 Aug 2018 15:09:53 -0400 |
13 | + |
14 | open-iscsi (2.0.873+git0.3b4b4500-14ubuntu3.4) xenial; urgency=medium |
15 | |
16 | * d/p/08_Parse-origin-value-from-iBFT.patch |
17 | diff --git a/debian/net-interface-handler b/debian/net-interface-handler |
18 | index 7717f61..15d35c6 100755 |
19 | --- a/debian/net-interface-handler |
20 | +++ b/debian/net-interface-handler |
21 | @@ -10,12 +10,23 @@ |
22 | # ifupdown appears to have no way to do this without also running |
23 | # /etc/network/*.d/ scripts. |
24 | |
25 | +assert_interface() { |
26 | + # udev sets INTERFACE to the name of the currently-processed nic. |
27 | + [ -n "$INTERFACE" ] && return 0 |
28 | + echo "environment variable INTERFACE not set." 1>&2; |
29 | + return 1 |
30 | +} |
31 | + |
32 | start() { |
33 | CR=" |
34 | " |
35 | + assert_interface || return |
36 | ifile=/run/initramfs/open-iscsi.interface |
37 | - if [ -f "$ifile" ] && read iface < "$ifile" && |
38 | - ! grep -qs "^$iface=" /run/network/ifstate; then |
39 | + |
40 | + [ -f "$ifile" ] && read iface < "$ifile" || return 0 |
41 | + [ "$INTERFACE" = "$iface" ] || return |
42 | + |
43 | + if ! grep -qs "^$iface=" /run/network/ifstate; then |
44 | mkdir -p /run/network |
45 | echo "$iface=$iface" >>/run/network/ifstate |
46 | |
47 | @@ -51,9 +62,12 @@ EOF |
48 | } |
49 | |
50 | stop() { |
51 | + assert_interface || return |
52 | ifile=/run/initramfs/open-iscsi.interface |
53 | - if [ -f "$ifile" ] && read iface < "$ifile" && |
54 | - grep -qs "^$iface=" /run/network/ifstate; then |
55 | + [ -f "$ifile" ] && read iface < "$ifile" || return 0 |
56 | + [ "$INTERFACE" = "$iface" ] || return |
57 | + |
58 | + if grep -qs "^$iface=" /run/network/ifstate; then |
59 | grep -v "^$iface=" /run/network/ifstate >/run/network/.ifstate.tmp || true |
60 | mv /run/network/.ifstate.tmp /run/network/ifstate |
61 | |
62 | diff --git a/debian/tests/README-boot-test.md b/debian/tests/README-boot-test.md |
63 | new file mode 100644 |
64 | index 0000000..e45bbf0 |
65 | --- /dev/null |
66 | +++ b/debian/tests/README-boot-test.md |
67 | @@ -0,0 +1,139 @@ |
68 | +## open-iscsi boot test |
69 | +The purpose of this test (`CloudImageTest`) is to test the boot of a system |
70 | +using an iscsi root target. In order to accomplish that, the tests does |
71 | + |
72 | + 1. Download Ubuntu cloud image |
73 | + 2. installs the open-iscsi deb inside |
74 | + 3. collect kernel and initramfs from inside |
75 | + 4. register the image as a read-only iscsi target served by tgt |
76 | + 5. boot kernel and initramfs with a command line to use the image as root. |
77 | + And additionally attach a local disk for collecting output. |
78 | + 6. provide user-data that executes commands, colects files and writes them |
79 | + to the output disk and then shuts the system down. |
80 | + 7. extract the collected files from the output disk and inspect them. |
81 | + |
82 | +The `CloudImageTest` uses qemu user networking. |
83 | + |
84 | + |
85 | +## Caveats |
86 | + |
87 | + 1. It depends on a cloud-image being present. |
88 | + |
89 | + Cloud-images are often not available for the first few weeks of a cycle. |
90 | + If no cloud-image of 'REL' is available, then boot-test will skip. |
91 | + If 'REL' is not known in distro-info-data, then test will fail. |
92 | + |
93 | + This means that uploads of open-iscsi (or its dependencies) will not |
94 | + be properly tested until a cloud-image is available, and will fail |
95 | + until distro-info-data is uploaded. |
96 | + |
97 | + 2. Installation of large packages via patch-image may fail. |
98 | + An ubuntu image downloaded has only a small amount of extra space. |
99 | + Installation of a new kernel into the target would probably fail. |
100 | + |
101 | + If this becomes a problem, we could grow the disk like done at |
102 | + https://gist.github.com/smoser/6a048a0e2795b48221fc44962202fa14 |
103 | + |
104 | + |
105 | +### testing manually ### |
106 | +The test case in `debian/tests/test-open-iscsi.py` uses some helper tools. |
107 | + |
108 | + * **patch-image**: this is used to install packages into the pristine image |
109 | + and collect the kernel and initramfs out of the image. This allows us to |
110 | + test the portions of open-iscsi that update the initramfs. Without |
111 | + using the updated initramfs we wouldn't really be testing the new |
112 | + open-iscsi. |
113 | + |
114 | + It will upgrade any packages inside that are mentioned in |
115 | + ADT_TEST_TRIGGERS environment. It will also install open-iscsi if |
116 | + it is not in that list. |
117 | + |
118 | + It installs packages into the target by copying the host system's |
119 | + /etc/apt content in, and also includes copying local (file://) apt |
120 | + repos into the target. This is necessary for the autopackage test |
121 | + environment that adds local package repositories to sources.list.d. |
122 | + |
123 | + It may also make other changes to the image to workaround bugs. |
124 | + See --help output for list of bugs. |
125 | + |
126 | + * **get-image**: This downloads an image from cloud-images.ubuntu.com. See |
127 | + its Usage for more information. it downloads via stream data and verifies |
128 | + download. One thing to note is that it does not overwrite existing files. |
129 | + |
130 | + * **tgt-boot-test**: this registers an image in tgt locally, and then boots |
131 | + kernel and initramfs to mount that. It knows how to build iscsi kernel |
132 | + command lines. |
133 | + |
134 | + By default, the xkvm process that is started will be allowed `60m` to |
135 | + complete. This can be adjusted by setting `BOOT_TIMEOUT` in the |
136 | + environment. |
137 | + |
138 | + * **xkvm**: this is a helper/wrapper around qemu. It is taken from the curtin |
139 | + projects tools/ directory. It allows some simplified command lines, and |
140 | + most usefully, the '--netdev=<bridge>' argument will create a tun/tap |
141 | + device and attach it to the bridge. |
142 | + |
143 | + If the host system is not bare metal, kvm will not be |
144 | + enabled in the guest that is lauched. To force use of kvm, set |
145 | + _USE_KVM=1 in the environment. See 'should_try_kvm' in xkvm for details. |
146 | + |
147 | +Testing manually looks like this: |
148 | + |
149 | + ## set up path to include debian/tests directory. |
150 | + $ PATH=$PWD/debian/tests:$PATH |
151 | + |
152 | + ## Get the image you want. This creates out.d/disk.img and disk.img.dist |
153 | + $ get-image xenial.d xenial |
154 | + |
155 | + ## patch the image with an open-iscsi, which creates xenial.d/kernel |
156 | + ## and xenial.d/initrd from the kernel and initramfs inside the image. |
157 | + $ apt-get download open-iscsi |
158 | + $ deb=$(ls open-iscsi_*.deb | tail -n 1) |
159 | + $ sudo ./debian/tests/patch-image \ |
160 | + --kernel=xenial.d/kernel --initrd=xenial.d/initrd |
161 | + xenial.d/disk.img "$deb" |
162 | + |
163 | + ## Boot the system, log in, look around. |
164 | + $ tgt-boot-test -v xenial.d/disk.img xenial.d/kernel xenial.d/initrd |
165 | + |
166 | + |
167 | +### Features of tgt-boot-test ### |
168 | + |
169 | +tgt-boot-test does a number of useful things. |
170 | + |
171 | + * determines the host address that the guest will use. |
172 | + This should support ipv6 and ipv4 addresses on bridges, and |
173 | + knows values that qemu's user networking uses. Flags passed to `--netdev` |
174 | + are read intelligently. This can be overriden with `--host-addr`, but |
175 | + it does a good job of determining what the right values are. |
176 | + |
177 | + * provides a nocloud metadata service with a python web server that |
178 | + supports ipv4 and ipv6. |
179 | + |
180 | + * provides the ability to provide additional kernel command line options |
181 | + or to provide a 'template' that references variables it knows such as |
182 | + {iserver} (iscsi server) or {seed_url}. |
183 | + |
184 | + * Sets ubuntu (passw0rd) and root password (root) and imports users |
185 | + ssh keys to the ubuntu user. |
186 | + |
187 | +One thing to note is that yakkety's version of qemu does not run an ipv6 |
188 | +dhcp server on its user-network, so a stateful dhclient request will not |
189 | +work. |
190 | + |
191 | +In order to create a bridge easily with a ipv6 dhcp server, you can use |
192 | +lxd at sufficent version (https://github.com/lxc/lxd/issues/2481). |
193 | +Assuming that bug is fixed, to create an ipv6 only bridge: |
194 | + |
195 | + $ netname="ipv6-only" |
196 | + $ lxc network create $netname |
197 | + $ lxc network unset $netname ipv4.address |
198 | + $ lxc network unset $netname ipv4.nat |
199 | + $ lxc network set $netname ipv6.dhcp.stateful true |
200 | + |
201 | +Then, you can use tgt-boot-test with that: |
202 | + |
203 | + $ PATH=$PWD/debian/tests:$PATH |
204 | + $ ./debian/tests/tgt-boot-test -vv --netdev=ipv6-only \ |
205 | + --cmdline-ip="ip=off ip6=dhcp" \ |
206 | + disk.img kernel initramfs |
207 | diff --git a/debian/tests/control b/debian/tests/control |
208 | index 320757e..3662819 100644 |
209 | --- a/debian/tests/control |
210 | +++ b/debian/tests/control |
211 | @@ -1,3 +1,7 @@ |
212 | -Tests: install sysvinit-install daemon testsuite |
213 | +Tests: install sysvinit-install daemon |
214 | Restrictions: needs-root isolation-machine breaks-testbed |
215 | -Depends: open-iscsi, python, tgt, qemu-system, ubuntu-cloudimage-keyring, simplestreams, python-netifaces, distro-info, cloud-image-utils |
216 | +Depends: open-iscsi |
217 | + |
218 | +Tests: testsuite |
219 | +Restrictions: needs-root isolation-machine breaks-testbed |
220 | +Depends: open-iscsi, python, tgt, qemu-system, ubuntu-cloudimage-keyring, simplestreams, python-netifaces, distro-info, cloud-image-utils, dctrl-tools, rsync |
221 | diff --git a/debian/tests/get-maas-eph b/debian/tests/get-image |
222 | index 660a72d..41b903c 100755 |
223 | --- a/debian/tests/get-maas-eph |
224 | +++ b/debian/tests/get-image |
225 | @@ -2,23 +2,38 @@ |
226 | Usage() { |
227 | cat <<EOF |
228 | ${0##*/} out-directory release |
229 | + |
230 | +By default, the cloud image for 'release' is downloaded from the |
231 | +daily cloud image stream. |
232 | + |
233 | +Additional arguments can change behavior of what is downloaded. |
234 | +Some supported arguments: |
235 | + * cloud-daily: use daily stream [default] |
236 | + * cloud-release: use release stream rather than daily stream. |
237 | + * maas-release, maas-daily: download maas images instead of cloud-image |
238 | + * hwe-N: download the hwe kernel from release 'N' instead of default |
239 | + this is only respected if maas stream is used. |
240 | + * YYYYMMDD[.X] : download the specific build version |
241 | + |
242 | example: |
243 | - ${0##*/} trusty_dir trusty daily |
244 | + ${0##*/} xenial_dir xenial cloud-daily |
245 | EOF |
246 | } |
247 | TEMP_D="" |
248 | |
249 | +NO_IMAGE_AVAILABLE_RC=3 |
250 | + |
251 | cleanup() { |
252 | [ ! -d "${TEMP_D}" ] || rm -Rf "${TEMP_D}" |
253 | } |
254 | -VERBOSITY=0 |
255 | +VERBOSITY=1 |
256 | inargs() { |
257 | local n=$1 x=""; |
258 | shift; |
259 | for x in "$@"; do [ "$n" = "$x" ] && return 0; done; |
260 | return 1 |
261 | } |
262 | -debug() { [ $1 -lt $VERBOSITY ] || return 0; shift; echo "$@" 1>&2; } |
263 | +debug() { [ $1 -ge $VERBOSITY ] || return 0; shift; echo "$@" 1>&2; } |
264 | error() { echo "$@" 1>&2; } |
265 | fail() { [ $# -eq 0 ] || error "$@"; exit 1; } |
266 | |
267 | @@ -29,8 +44,11 @@ trap cleanup EXIT |
268 | arches=( ppc64el i386 amd64 ) |
269 | releases=( $(ubuntu-distro-info --all) ) |
270 | mbase="http://maas.ubuntu.com/images/ephemeral-v2" |
271 | -def_stream="${mbase}/daily" |
272 | -def_rel="trusty" |
273 | +cbase="http://cloud-images.ubuntu.com" |
274 | +def_stream_type="cloud" |
275 | +stream_src="" |
276 | +curbase="$mbase" |
277 | +def_rel="xenial" |
278 | vername="" |
279 | |
280 | [ "$1" = "-h" -o "$1" = "--help" ] && { Usage; exit 0; } |
281 | @@ -50,28 +68,52 @@ pt=( ) |
282 | for x in "$@"; do |
283 | inargs "$x" "${arches[@]}" && pt[${#pt[@]}]=arch=$x && arch="$x" && continue |
284 | inargs "$x" "${releases[@]}" && pt[${#pt[@]}]="release=$x" && release=$x && continue |
285 | + [ "$x" = "released" -o "$x" = "release" ] && x="releases" |
286 | case "$x" in |
287 | - daily) stream="${mbase}/daily";; |
288 | - released|release|releases) stream="${mbase}/releases";; |
289 | + cloud-released|cloud-release) x="cloud-releases";; |
290 | + maas-released|maas-release) x="maas-releases";; |
291 | + esac |
292 | + |
293 | + case "$x" in |
294 | + maas|cloud) stream_type="$x";; |
295 | + cloud-daily|maas-daily|cloud-releases|maas-releases) |
296 | + stream_type="${x%-*}" |
297 | + stream_sub=${x#*-};; |
298 | http://*) stream=$x;; |
299 | - hwe-*) subarch="$x"; pt[${#pt[@]}]="subarch=$x";; |
300 | - subarch=*) pt[${#pt[@]}]="$x"; subarch=${x#*=};; |
301 | + hwe-*) subarch="$x";; |
302 | + subarch=*) subarch=${x#*=};; |
303 | *=*) pt[${#pt[@]}]="$x";; |
304 | *~*) pt[${#pt[@]}]="$x";; |
305 | [0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]|[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9].[0-9]) |
306 | pt[${#pt[@]}]="version_name=$x" |
307 | vername="$x";; |
308 | + *) fail "Confused by input token '$x'";; |
309 | esac |
310 | done |
311 | |
312 | -[ -n "$stream" ] || stream="$def_stream" |
313 | +[ -n "$stream_type" ] || stream_type="${def_stream_type}" |
314 | +if [ -z "$stream" ]; then |
315 | + stream_sub=${stream_sub:-daily} |
316 | + case "$stream_type" in |
317 | + maas) stream="$mbase/$stream_sub";; |
318 | + cloud) stream="$cbase/$stream_sub";; |
319 | + *) fail "unknown stream_type '$stream_type'";; |
320 | + esac |
321 | +fi |
322 | + |
323 | [ -n "$arch" ] || pt[${#pt[@]}]="arch=$def_arch" |
324 | -if [ -z "$subarch" ]; then |
325 | - t=${release#?} |
326 | - first_letter=${release%${t}} |
327 | - subarch="hwe-${first_letter}" |
328 | - pt[${#pt[@]}]="subarch=$subarch" |
329 | - error "selected subarch=${subarch} for $release" |
330 | + |
331 | +if [ "$stream_type" = "maas" ]; then |
332 | + if [ -z "$subarch" ]; then |
333 | + t=${release#?} |
334 | + first_letter=${release%${t}} |
335 | + subarch="hwe-${first_letter}" |
336 | + error "selected subarch=${subarch} for $release" |
337 | + fi |
338 | + pt[${#pt[@]}]="subarch=$subarch" |
339 | + ofmt="%(sha256)s %(ftype)s %(subarch)s %(item_url)s" |
340 | +else |
341 | + ofmt="%(sha256)s %(ftype)s %(item_url)s" |
342 | fi |
343 | |
344 | OIFS="$IFS" |
345 | @@ -97,39 +139,89 @@ if [ -n "${needs# }" ]; then |
346 | fi |
347 | |
348 | qcmd=( sstream-query ${keyring:+"--keyring=$keyring"} ${vername:---max=1} |
349 | - --output-format="%(sha256)s %(subarch)s %(ftype)s %(item_url)s" "${stream}" "${pt[@]}" ) |
350 | + --output-format="$ofmt" "${stream}" datatype="image-downloads" |
351 | + "${pt[@]}" ) |
352 | echo "${qcmd[@]}" |
353 | "${qcmd[@]}" > "${TEMP_D}/qout" |
354 | -roots=$(awk '$3 == "root-image.gz" { print $2 }' "${TEMP_D}/qout" | sort -u | wc -l) |
355 | -[ "$roots" = "1" ] || { |
356 | +roots=$(awk '$2 == "root-image.gz" || $2 == "disk1.img" { print $2 }' \ |
357 | + "${TEMP_D}/qout" | sort -u | wc -l) |
358 | +if [ "$roots" = "0" ]; then |
359 | + error "No image available for $release in $stream${pt:+ (${pt[*]})}" |
360 | + exit ${NO_IMAGE_AVAILABLE_RC} |
361 | +elif [ "$roots" != "1" ]; then |
362 | error "query resulted in '$roots' root images. expect 1 and only 1" |
363 | + error "cmd was ${qcmd[*]}" |
364 | cat "${TEMP_D}/qout" |
365 | fail |
366 | -} |
367 | -image="$out_d/root-image" |
368 | +fi |
369 | + |
370 | set -f |
371 | while read line; do |
372 | set -- $line |
373 | - sha256="$1"; subarch="$2"; ftype="$3"; url="$4" |
374 | - [ "$ftype" = "di-kernel" -o "$ftype" = "di-initrd" ] && continue |
375 | + if [ "$stream_type" = "maas" ]; then |
376 | + sha256="$1"; ftype="$2"; subarch="$3"; url="$4" |
377 | + else |
378 | + sha256="$1"; ftype="$2"; url="$3" |
379 | + fi |
380 | + case "$ftype" in |
381 | + boot-kernel|boot-initrd|root-image.gz|disk1.img) :;; |
382 | + *) continue |
383 | + esac |
384 | if [ "$ftype" = "root-image.gz" ]; then |
385 | fname="$ftype" |
386 | - [ -f "$image" ] && continue |
387 | + image_src="$out_d/$fname" |
388 | + image="$out_d/root-image" |
389 | + elif [ "$ftype" = "disk1.img" ]; then |
390 | + fname="disk.img.dist" |
391 | + image_src="$out_d/$fname" |
392 | + image="$out_d/disk.img" |
393 | else |
394 | fname="$subarch/$ftype" |
395 | fi |
396 | out_f="${out_d}/$fname" |
397 | - [ -f "$out_f" ] && continue |
398 | + if [ -f "$out_f" ]; then |
399 | + debug 1 "reusing existing '$out_f' rather than downloading $url." |
400 | + continue |
401 | + fi |
402 | [ -d "${out_f%/*}" ] || mkdir -p "${out_f%/*}" |
403 | - wget -c "$url" -O "$out_f.tmp" && mv "$out_f.tmp" "$out_f" || { rm -f "$out_f.tmp"; exit 3; } |
404 | - echo "$out_d/$fname" "$url" >> "$out_d/contents" |
405 | + tmp="${out_f}.tmp" |
406 | + wget --progress=dot:mega -O "$tmp" "$url" || { |
407 | + error "Failed to download $url to $tmp." |
408 | + rm -f "$tmp" |
409 | + exit 3 |
410 | + } |
411 | + debug 1 "checksumming $tmp from $url" |
412 | + out=$(sha256sum "$tmp") || { |
413 | + error "Failed to checksum $tmp" |
414 | + rm -f "$tmp" |
415 | + exit 3; |
416 | + } |
417 | + found=${out% *} |
418 | + if [ "$found" != "$sha256" ]; then |
419 | + error "$tmp from $url found sha256 of $found. expected $sha256." |
420 | + ls -l "$tmp" 1>&2 |
421 | + rm -f "$tmp" |
422 | + exit 3 |
423 | + fi |
424 | + mv "$tmp" "$out_f" || fail "failed renaming $tmp to $out_f" |
425 | + echo "$fname" "$url" >> "$out_d/contents" |
426 | done < "${TEMP_D}/qout" |
427 | |
428 | if [ ! -f "$image" ]; then |
429 | - debug 1 "decompressing $image.gz" |
430 | - zcat "$image.gz" > "$image.tmp" && |
431 | - mv "$image.tmp" "$image" || |
432 | - { rm -f "$image.tmp"; exit 1; } |
433 | + case "$image_src" in |
434 | + */root-image.gz) |
435 | + debug 1 "decompressing $image_src" |
436 | + zcat "$image_src" > "$image.tmp" && |
437 | + mv "$image.tmp" "$image" || |
438 | + { rm -f "$image.tmp"; exit 1; } |
439 | + ;; |
440 | + */disk.img.dist) |
441 | + debug 1 "converting qcow2 in $image_src -> raw in $image" |
442 | + qemu-img convert -O raw "$image_src" "$image.tmp" && |
443 | + mv "$image.tmp" "$image" || |
444 | + { rm -f "$image.tmp"; exit 1; } |
445 | + ;; |
446 | + esac |
447 | fi |
448 | |
449 | # vi: ts=4 expandtab |
450 | diff --git a/debian/tests/patch-image b/debian/tests/patch-image |
451 | new file mode 100755 |
452 | index 0000000..b9b34e3 |
453 | --- /dev/null |
454 | +++ b/debian/tests/patch-image |
455 | @@ -0,0 +1,288 @@ |
456 | +#!/bin/bash |
457 | + |
458 | +VERBOSITY=1 |
459 | +TEMP_D="" |
460 | + |
461 | +error() { echo "$@" 1>&2; } |
462 | +fail() { local r=$?; [ $r -eq 0 ] && r=1; failrc "$r" "$@"; } |
463 | +failrc() { local r=$1; shift; [ $# -eq 0 ] || error "$@"; exit $r; } |
464 | + |
465 | +Usage() { |
466 | + cat <<EOF |
467 | +Usage: ${0##*/} [options] image [packages] |
468 | + Patch image, upgrading [packages]. |
469 | + --kernel FILE copy kernel out to FILE |
470 | + --initrd FILE copy initrd out to FILE |
471 | + |
472 | + --krd-only only copy out kernel/initrd do not change image. |
473 | + this is incompatible with 'packages' |
474 | + it is a short-cut to specifying all the '--no-*' |
475 | + options below. |
476 | + |
477 | + --no-copy-apt do not copy host's apt repos in. |
478 | + |
479 | + image modifications: |
480 | + --no-update-fstab do not modify fstab (LP: #1732028) |
481 | + |
482 | + if no packages are provided, and ADT_TEST_TRIGGERS is set |
483 | + in environment, then it will be read for the list of packages. |
484 | + to override that behavior pass package 'none'. |
485 | +EOF |
486 | + |
487 | +} |
488 | + |
489 | +bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || error "$@"; return 1; } |
490 | +cleanup() { |
491 | + [ -z "${TEMP_D}" -o ! -d "${TEMP_D}" ] || rm -Rf "${TEMP_D}" |
492 | +} |
493 | + |
494 | +debug() { |
495 | + local level=${1}; shift; |
496 | + [ "${level}" -gt "${VERBOSITY}" ] && return |
497 | + error "${@}" |
498 | +} |
499 | + |
500 | +adt_test_triggers_to_bin_pkgs() { |
501 | + # ADT_TEST_TRIGGERS is space delimited <src>/version |
502 | + # returns a scalar '_RET', caller has to expand |
503 | + local tok src="" ver="" binpkgs="" |
504 | + for tok in "$@"; do |
505 | + src=${tok%/*} |
506 | + ver=${tok#*/} |
507 | + bin_packages_from_source_pkg "$src" "$ver" || return |
508 | + if [ -z "${_RET}" ]; then |
509 | + error "$tok: (source=$src ver=$ver) No packages available!" |
510 | + return 1 |
511 | + fi |
512 | + binpkgs="${binpkgs} ${_RET}" |
513 | + done |
514 | + _RET=${binpkgs# } |
515 | +} |
516 | + |
517 | +bin_packages_from_source_pkg() { |
518 | + local pkg=$1 ver=${2} cmd="" out="" p="" ret="" |
519 | + if ! command -v grep-aptavail >/dev/null; then |
520 | + error "No command grep-aptavail: apt-get install dctrl-tools" |
521 | + return 1 |
522 | + fi |
523 | + cmd=( grep-aptavail --show-field=Package |
524 | + --exact-match --field=Source:Package "$pkg" ) |
525 | + if [ -n "$ver" ]; then |
526 | + cmd=( "${cmd[@]}" --and --field=Version "$ver" ) |
527 | + fi |
528 | + out=$("${cmd[@]}" | sed 's,Package: ,,') |
529 | + ret="" |
530 | + for p in $out; do |
531 | + ret="$ret ${p#Package: }${ver:+=${ver}}" |
532 | + done |
533 | + # if no version was provided, we could have multiple results |
534 | + _RET=$(set -f; for i in ${ret}; do echo "$i"; done | sort -u) |
535 | +} |
536 | + |
537 | +update_fstab() { |
538 | + # update_fstab(path) modify the fstab at path |
539 | + # to remove problematic entries (LP: #1732028) |
540 | + local fstab="$1" |
541 | + if [ ! -e "$fstab.patch-image-dist" ]; then |
542 | + cp "$fstab" "$fstab.patch-image-dist" || |
543 | + { error "failed backing up $fstab to $fstab.dist"; return 1; } |
544 | + fi |
545 | + sed -i '/^LABEL=UEFI/s/^/#/' "$fstab" |
546 | +} |
547 | + |
548 | +main() { |
549 | + local short_opts="hv" |
550 | + local long_opts="help,no-copy-apt,initrd:,kernel:,verbose" |
551 | + local getopt_out="" orig_args="" |
552 | + orig_args=( "$@" ) |
553 | + getopt_out=$(getopt --name "${0##*/}" \ |
554 | + --options "${short_opts}" --long "${long_opts}" -- "$@") && |
555 | + eval set -- "${getopt_out}" || |
556 | + { bad_Usage; return; } |
557 | + |
558 | + local cur="" next="" |
559 | + local kernel="" initrd="" copy_apt=true krd_only=false |
560 | + local update_fstab=true |
561 | + |
562 | + while [ $# -ne 0 ]; do |
563 | + cur="$1"; next="$2"; |
564 | + case "$cur" in |
565 | + -h|--help) Usage ; exit 0;; |
566 | + --krd-only) krd_only=true; shift;; |
567 | + --no-copy-apt) copy_apt=false; shift;; |
568 | + --no-update-fstab) update_fstab=false; shift;; |
569 | + --kernel) kernel=$next; shift;; |
570 | + --initrd) initrd=$next; shift;; |
571 | + -v|--verbose) VERBOSITY=$((${VERBOSITY}+1));; |
572 | + --) shift; break;; |
573 | + esac |
574 | + shift; |
575 | + done |
576 | + |
577 | + [ $# -ne 0 ] || { bad_Usage "must provide at least image"; return; } |
578 | + |
579 | + [ "$(id -u)" = "0" ] || fail "not root" |
580 | + target="$1" |
581 | + shift |
582 | + if [ -f "$target" ]; then |
583 | + local nargs="" |
584 | + nargs=( "${orig_args[@]}" ) |
585 | + # replace the first occurance of target in orig args with _MOUNTPOINT_ |
586 | + for((i=0;i<${#nargs[@]};i++)); do |
587 | + [ "${nargs[$i]}" = "$target" ] && nargs[$i]=_MOUNTPOINT_ && break |
588 | + done |
589 | + debug 1 "mic $target -- $0 ${nargs[*]}" |
590 | + IMAGE="$target" exec mount-image-callback \ |
591 | + --system-resolvconf "$target" -- "$0" "${nargs[@]}" |
592 | + fi |
593 | + |
594 | + if [ ! -d "$target" ]; then |
595 | + fail "$target: not a directory or file" |
596 | + fi |
597 | + |
598 | + local tsrc=${IMAGE:-${target}} |
599 | + local packages=( ) |
600 | + packages=( "$@" ) |
601 | + |
602 | + if $krd_only; then |
603 | + if [ "${#packages[@]}" != 0 -a "${packages[*]}" != "none" ]; then |
604 | + error "--krd-only is incompatible with packages." |
605 | + return 1 |
606 | + fi |
607 | + debug 1 "--krd-only provided no changes will be done." |
608 | + copy_apt=false |
609 | + update_fstab=false |
610 | + packages=( "none" ) |
611 | + fi |
612 | + |
613 | + if [ "${#packages[@]}" = "1" -a "${packages[0]}" = "none" ]; then |
614 | + packages=( ) |
615 | + elif [ "${#packages[@]}" -eq 0 -a -n "${ADT_TEST_TRIGGERS}" ]; then |
616 | + adt_test_triggers_to_bin_pkgs ${ADT_TEST_TRIGGERS} || |
617 | + fail "failed to find binary packages " \ |
618 | + "from ADT_TEST_TRIGGERS=$ADT_TEST_TRIGGERS" |
619 | + packages=( $_RET ) |
620 | + debug 1 "read ADT_TEST_TRIGGERS=$ADT_TEST_TRIGGERS to set" \ |
621 | + "packages=${packages[*]}" |
622 | + else |
623 | + local opackages="" debs="" pkg="" |
624 | + opackages=( "${packages[@]}" ) |
625 | + packages=( ) |
626 | + debs=( ) |
627 | + for pkg in "${opackages[@]}"; do |
628 | + [ -f "${pkg}" ] && debs[${#debs[@]}]="$pkg" || |
629 | + packages[${packages[@]}]="$pkg" |
630 | + done |
631 | + fi |
632 | + |
633 | + # if open-iscsi is not in the packages list above, we handle it specifically |
634 | + # so that even if the image did not have open-iscsi, it will get it. |
635 | + local pkg="" mypkg="open-iscsi" |
636 | + for pkg in "${packages[@]}"; do |
637 | + case "$pkg" in |
638 | + open-iscsi|open-iscsi/*) mypkg="none";; |
639 | + esac |
640 | + done |
641 | + |
642 | + TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX") || |
643 | + fail "failed to make tempdir" |
644 | + trap cleanup EXIT |
645 | + |
646 | + if $copy_apt; then |
647 | + local distdir="${target}/etc/apt.dist.${0##*/}" |
648 | + if [ ! -d "$distdir" ]; then |
649 | + mv "$target/etc/apt" "$distdir" || |
650 | + fail "failed to backup /etc/apt in $tsrc" |
651 | + fi |
652 | + cp -a /etc/apt "$target/etc/" || |
653 | + fail "failed to replace /etc/apt in $tsrc" |
654 | + |
655 | + # find file:// repos in apt sources and copy them in |
656 | + local file_repos="" |
657 | + file_repos=$( |
658 | + fm='s,.*[[:space:]]file:///*\([^[[:space:]]*\).*,/\1,p' |
659 | + shopt -s nullglob |
660 | + for f in /etc/apt/sources.list /etc/apt/sources.list.d/*.list; do |
661 | + sed -n -e 's/#.*//' -e "$fm" "$f" |
662 | + done |
663 | + ) |
664 | + local dir="" bdir="" tdir="" |
665 | + for dir in ${file_repos}; do |
666 | + tdir="${target}$(dirname $dir)/$(basename ${dir})" |
667 | + if [ -d "$dir" ]; then |
668 | + debug 1 "copying $dir -> $tdir" |
669 | + mkdir -p "$tdir" || |
670 | + fail "failed to create $tdir" |
671 | + rsync --delete -a "$dir/" "$tdir" || |
672 | + fail "failed copy from $dir/ to $tdir" |
673 | + fi |
674 | + done |
675 | + fi |
676 | + |
677 | + if $update_fstab; then |
678 | + update_fstab "$target/etc/fstab" || |
679 | + { error "failed updating /etc/fstab in target"; return 1; } |
680 | + fi |
681 | + |
682 | + mount -o bind /sys "$target"/sys || : |
683 | + mount -o bind /proc "$target"/proc || : |
684 | + mount -o bind /dev "$target"/dev || : |
685 | + mount -o bind /dev/pts "$target"/dev/pts || : |
686 | + |
687 | + if [ "${#packages[@]}" != "0" ]; then |
688 | + DEBIAN_FRONTEND=noninteractive chroot "$target" sh -exc ' |
689 | + mypkg=$1 |
690 | + shift |
691 | + apt-get update -q |
692 | + if [ "$mypkg" != "none" ]; then |
693 | + apt-get install -qy "$mypkg" |
694 | + fi |
695 | + apt-get install -qy --only-upgrade "$@"' \ |
696 | + chroot-inst "$mypkg" "${packages[@]}" || |
697 | + fail "failed to install ${packages[*]} in $target" |
698 | + fi |
699 | + |
700 | + if [ "${#debs[@]}" != "0" ]; then |
701 | + local tmpd="" |
702 | + tmpd=$(mktemp -d "${target}/tmp/${0##*/}.XXXXXX") |
703 | + cp "${debs[@]}" "$tmpd" |
704 | + DEBIAN_FRONTEND=noninteractive chroot "$target" sh -exc \ |
705 | + 'cd "$1"; shift; dpkg -i *.deb' debinstalls "${tmpd#${target}}" |
706 | + rm -Rf "${tmpd}" |
707 | + fi |
708 | + |
709 | + umount "$target"/dev/pts || : |
710 | + umount "$target"/dev || : |
711 | + umount "$target"/proc || : |
712 | + umount "$target"/sys || : |
713 | + |
714 | + local kpath="" ipath="" |
715 | + for f in "$target/boot/"*; do |
716 | + case "${f##*/}" in |
717 | + *.signed) continue;; |
718 | + vmlinu?-*) kpath="$f";; |
719 | + initrd.img-*) ipath="$f";; |
720 | + esac |
721 | + done |
722 | + |
723 | + if [ -n "$kernel" ]; then |
724 | + [ -n "$kpath" ] || fail "unable to find kernel in $tsrc" |
725 | + debug 1 "using kernel ${kpath#${target}}" |
726 | + cp "$kpath" "$kernel" || |
727 | + fail "failed copy $kpath to $kernel" |
728 | + [ -z "$IMAGE" ] || chown "--reference=$IMAGE" "$kernel" |
729 | + fi |
730 | + |
731 | + if [ -n "$initrd" ]; then |
732 | + [ -n "$ipath" ] || fail "unable to find initrd in $tsrc" |
733 | + debug 1 "using initrd ${ipath#${target}}" |
734 | + cp "$ipath" "$initrd" || |
735 | + fail "failed copy $ipath to $initrd" |
736 | + [ -z "$IMAGE" ] || chown "--reference=$IMAGE" "$initrd" |
737 | + fi |
738 | + |
739 | +} |
740 | + |
741 | +main "$@" |
742 | + |
743 | +# vi: ts=4 expandtab |
744 | diff --git a/debian/tests/test-open-iscsi.py b/debian/tests/test-open-iscsi.py |
745 | index 5358a9d..4da193e 100644 |
746 | --- a/debian/tests/test-open-iscsi.py |
747 | +++ b/debian/tests/test-open-iscsi.py |
748 | @@ -44,8 +44,10 @@ |
749 | |
750 | |
751 | from netifaces import gateways, AF_INET |
752 | -import unittest, subprocess, sys, os |
753 | +import unittest, subprocess, sys, os, glob |
754 | +import shutil |
755 | import testlib |
756 | +import textwrap |
757 | from tempfile import mkdtemp |
758 | import time |
759 | |
760 | @@ -57,6 +59,46 @@ username_in = 'ubuntu' |
761 | password_in = 'ubuntupasswd' |
762 | initiatorname = 'iqn.2009-10.com.example.hardy-multi:iscsi-01' |
763 | |
764 | +COLLECT_USER_DATA = """\ |
765 | +#cloud-config |
766 | +bukket: |
767 | + - &get_resolved_status | |
768 | + if [ "${1:--}" != "-" ]; then |
769 | + exec >"$1" 2>&1 |
770 | + fi |
771 | + if ! command -v systemd-resolve >/dev/null 2>&1; then |
772 | + echo "systemd-resolve: not available." |
773 | + exit |
774 | + fi |
775 | + if ! ( systemd-resolve --help | grep -q -- --status ); then |
776 | + echo "systemd-resolve: no --status." |
777 | + exit |
778 | + fi |
779 | + systemd-resolve --status --no-pager |
780 | + - &add_and_remove_tuntap | |
781 | + #!/bin/sh |
782 | + # LP: #1785108 would break dns when any device was removed. |
783 | + tapdev="mytap0" |
784 | + echo ==== Adding $tapdev ==== |
785 | + ip tuntap add mode tap user root $tapdev |
786 | + udevadm settle |
787 | + echo ==== Removing $tapdev ==== |
788 | + ip tuntap del mode tap $tapdev |
789 | + udevadm settle |
790 | + |
791 | +runcmd: |
792 | + - [ sh, -c, *add_and_remove_tuntap ] |
793 | + - [ mkdir, -p, /output ] |
794 | + - [ cp, /etc/resolv.conf, /output] |
795 | + - [ sh, -c, *get_resolved_status, --, /output/systemd-resolve-status.txt] |
796 | + - [ tar, -C, /output, -cf, /dev/disk/by-id/virtio-output-disk, . ] |
797 | + |
798 | +power_state: |
799 | + mode: poweroff |
800 | + message: cloud-init finished. Shutting down. |
801 | + timeout: 60 |
802 | +""" |
803 | + |
804 | try: |
805 | from private.qrt.OpenIscsi import PrivateOpenIscsiTest |
806 | except ImportError: |
807 | @@ -158,89 +200,163 @@ discovery.sendtargets.auth.password_in = %s |
808 | nih_path = '/lib/open-iscsi/net-interface-handler' |
809 | self.assertTrue(os.access(nih_path, os.X_OK)) |
810 | |
811 | -reason = ( |
812 | - "Skipped MAAS ephemeral test in ppc64el because it lacks nested " |
813 | - "virtualization support.") |
814 | -@unittest.skipIf(testlib.manager.dpkg_arch == "ppc64el", reason) |
815 | -class MAASEphemeralTest(testlib.TestlibCase, PrivateOpenIscsiTest): |
816 | - '''Test MAAS ephemeral image can boot. |
817 | - |
818 | - Downloads MAAS ephemeral image, patches the image to use dep8's built |
819 | - open-iscsi package, sets the image to be served by tgt and then boot that |
820 | - image under (nested) kvm. It runs cloud-init in the booted VM to collect |
821 | - some data and verify the image worked as expected. |
822 | +class CloudImageTest(testlib.TestlibCase, PrivateOpenIscsiTest): |
823 | + '''Test the cloud image can boot on iscsi root. |
824 | + |
825 | + See README-boot-test.md for more information. |
826 | ''' |
827 | |
828 | - def setUp(self): |
829 | - self.here = os.path.dirname(os.path.abspath(__file__)) |
830 | - self.release = testlib.ubuntu_release() |
831 | - self.subarch = 'hwe-{}'.format(self.release[0]) |
832 | + @classmethod |
833 | + def setUpClass(cls): |
834 | + reason = ( |
835 | + "Skipped Cloud Image test on {arch}. whitelisted are: {whitelist}") |
836 | + whitelisted = os.environ.get('WHITELIST_ARCHES', 'amd64').split(",") |
837 | + curarch = testlib.manager.dpkg_arch |
838 | + if testlib.manager.dpkg_arch not in whitelisted: |
839 | + raise unittest.SkipTest( |
840 | + reason.format(arch=curarch, whitelist=whitelisted)) |
841 | + |
842 | + here = os.path.dirname(os.path.abspath(__file__)) |
843 | + os.environ['PATH'] = "%s:%s" % (here, os.environ['PATH']) |
844 | + release = os.environ.get( |
845 | + "ISCSI_TEST_RELEASE", testlib.ubuntu_release()) |
846 | + image_d = os.path.join(here, '{}.d'.format(release)) |
847 | # Download MAAS ephemeral image. |
848 | - self.get_maas_ephemeral() |
849 | - # Patch the image to use the dep8 built open-iscsi. |
850 | - self.patch_image() |
851 | - self.output_disk_path = self.create_output_disk(self.here) |
852 | - |
853 | - def create_output_disk(self, base_path): |
854 | - output_disk_path = os.path.join(base_path, 'output_disk.img') |
855 | - subprocess.call([ |
856 | - 'qemu-img', 'create', '-f', 'raw', output_disk_path, '10M']) |
857 | - return output_disk_path |
858 | - |
859 | - def get_maas_ephemeral(self): |
860 | - get_eph_cmd = os.path.join(self.here, 'get-maas-eph') |
861 | - subprocess.call([ |
862 | - get_eph_cmd, os.path.join(self.here, '{}.d'.format(self.release)), |
863 | - self.release, self.subarch]) |
864 | - |
865 | - def patch_image(self): |
866 | - '''Patch root-image with dep8 built open-iscsi package.''' |
867 | - root_image_path = os.path.join( |
868 | - self.here, '{}.d'.format(self.release), 'root-image') |
869 | - open_iscsi_deb_path = os.path.join( |
870 | - # XXX: matsubara fix this path hackery. |
871 | - # os.environ['ADTTMP'], 'binaries', 'open-iscsi.deb') |
872 | - self.here, '..', '..', '..', '..', 'binaries', 'open-iscsi.deb') |
873 | - open_iscsi_deb = open(open_iscsi_deb_path) |
874 | - install_cmd = ( |
875 | - 'cat >/tmp/my.deb && dpkg -i /tmp/my.deb; ret=$?;' |
876 | - 'rm -f /tmp/my.deb; exit $ret') |
877 | - subprocess.check_output([ |
878 | - 'mount-image-callback', '--system-mounts', |
879 | - root_image_path, '--', 'chroot', '_MOUNTPOINT_', '/bin/sh', '-c', |
880 | - install_cmd], stdin=open_iscsi_deb) |
881 | + info = {'release': release, |
882 | + 'image_d': image_d, |
883 | + 'root_image': os.path.join(image_d, 'disk.img'), |
884 | + 'kernel': os.path.join(image_d, 'kernel'), |
885 | + 'initrd': os.path.join(image_d, 'initrd')} |
886 | + try: |
887 | + get_image(info['image_d'], release) |
888 | + except CalledProcessError as e: |
889 | + if e.return_code != 3: |
890 | + raise e |
891 | + raise unittest.SkipTest( |
892 | + "Cloud Image not available for release '%s'." % release) |
893 | + if os.environ.get("NO_PATCH_IMAGE", "0") == "0": |
894 | + patch_image(info['root_image'], |
895 | + kernel=info['kernel'], initrd=info['initrd']) |
896 | + cls.info = info |
897 | + cls.here = here |
898 | + |
899 | + def tearDown(self): |
900 | + super(CloudImageTest, self).tearDown() |
901 | + if os.path.exists(self.tmpdir): |
902 | + shutil.rmtree(self.tmpdir) |
903 | + |
904 | + def setUp(self): |
905 | + super(CloudImageTest, self).setUp() |
906 | + self.tmpdir = mkdtemp() |
907 | + udp = os.path.join(self.tmpdir, 'user-data') |
908 | + with open(udp, "w") as fp: |
909 | + fp.write(COLLECT_USER_DATA) |
910 | + self.info['user-data'] = udp |
911 | + |
912 | + def create_output_disk(self): |
913 | + path = os.path.join(self.tmpdir, 'output-disk.img') |
914 | + subprocess.check_call([ |
915 | + 'qemu-img', 'create', '-f', 'raw', path, '10M']) |
916 | + return path |
917 | |
918 | def extract_files(self, path): |
919 | + # get contents in a dictionary of tarball at 'path' |
920 | tmpdir = mkdtemp() |
921 | - subprocess.call(['tar', '-C', tmpdir, '-xf', path]) |
922 | - return [os.path.join(tmpdir, f) for f in os.listdir(tmpdir)] |
923 | - |
924 | - def get_default_route_ip(self): |
925 | - gws = gateways() |
926 | - return gws['default'][AF_INET][0] |
927 | + try: |
928 | + subprocess.check_call(['tar', '-C', tmpdir, '-xf', path]) |
929 | + flist = {} |
930 | + prefix = len(tmpdir) + 1 |
931 | + for root, dirs, files in os.walk(tmpdir): |
932 | + for fname in files: |
933 | + fpath = os.path.join(root, fname) |
934 | + key = fpath[prefix:] |
935 | + with open(fpath, "r") as fp: |
936 | + flist[key] = fp.read() |
937 | + return flist |
938 | + finally: |
939 | + shutil.rmtree(tmpdir) |
940 | |
941 | def test_tgt_boot(self): |
942 | tgt_boot_cmd = os.path.join(self.here, 'tgt-boot-test') |
943 | - env = os.environ |
944 | - env['HOST_IP'] = self.get_default_route_ip() |
945 | # Add self.here to PATH so xkvm will be available to tgt-boot-test |
946 | - env['PATH'] = env['PATH'] + ":%s" % self.here |
947 | - env['OUTPUT_DISK'] = self.output_disk_path |
948 | - rel_dir = '{}.d'.format(self.release) |
949 | - subprocess.call([ |
950 | - tgt_boot_cmd, |
951 | - os.path.join(self.here, rel_dir, 'root-image'), |
952 | - os.path.join(self.here, rel_dir, self.subarch, 'boot-kernel'), |
953 | - os.path.join(self.here, rel_dir, self.subarch, 'boot-initrd')], |
954 | - env=env) |
955 | - files = self.extract_files(self.output_disk_path) |
956 | - for f in files: |
957 | - if f.endswith('resolv.conf'): |
958 | - resolvconf_contents = open(f).read() |
959 | - self.assertIn( |
960 | - '10.0.2.3', resolvconf_contents, |
961 | - msg="10.0.2.3 not in resolvconf contents: \n{}".format( |
962 | - resolvconf_contents)) |
963 | + dns_addr = '10.1.1.4' |
964 | + rel_dir = '{}.d'.format(self.info['release']) |
965 | + dns_search = 'example.com' |
966 | + info = {'host': '10.1.1.2', 'dns': dns_addr, |
967 | + 'dnssearch': dns_search, 'network': '10.1.1.0/24'} |
968 | + netdev = ("--netdev=user,net={network},host={host},dns={dns}," |
969 | + "dnssearch={dnssearch}").format(**info) |
970 | + |
971 | + output_disk = self.create_output_disk() |
972 | + cmd = [ |
973 | + tgt_boot_cmd, '-v', netdev, |
974 | + '--disk=%s,serial=output-disk' % output_disk, |
975 | + '--user-data-add=%s' % self.info['user-data'], |
976 | + self.info['root_image'], self.info['kernel'], |
977 | + self.info['initrd']] |
978 | + sys.stderr.write(' '.join(cmd) + "\n") |
979 | + |
980 | + env = os.environ.copy() |
981 | + env['BOOT_TIMEOUT'] = env.get('BOOT_TIMEOUT', '60m') |
982 | + subprocess.check_call(cmd, env=env) |
983 | + |
984 | + files = self.extract_files(output_disk) |
985 | + print("files: %s" % files) |
986 | + resolvconf = files.get('resolv.conf', "NO_RESOLVCONF_FOUND") |
987 | + resolve_status = files.get('systemd-resolve-status.txt') |
988 | + |
989 | + resolvconf_id = 'generated by resolvconf' |
990 | + resolved_addr = "127.0.0.53" |
991 | + if resolvconf_id in resolvconf: |
992 | + print("resolvconf manages resolvconf.\n") |
993 | + self.assertIn( |
994 | + dns_addr, resolvconf, |
995 | + msg = ("%s not in resolvconf contents: \n%s" % |
996 | + (dns_addr, resolvconf))) |
997 | + if dns_search in resolvconf: |
998 | + print("%s was found in resolv.conf." % dns_search) |
999 | + elif resolved_addr in resolvconf and dns_search in resolve_status: |
1000 | + # zesty has resolvconf and systemd-resolved. |
1001 | + print("%s was in resolve_status and %s in resolv.conf" % |
1002 | + (resolved_addr, dns_search)) |
1003 | + else: |
1004 | + raise AssertionError( |
1005 | + "%s domain is not being searched." % dns_search) |
1006 | + |
1007 | + else: |
1008 | + print("systemd-resolved managing resolve.conf\n") |
1009 | + self.assertIn( |
1010 | + resolved_addr, resolvconf, |
1011 | + msg="%s not in resolved resolv.conf: %s" % (resolved_addr, |
1012 | + resolvconf)) |
1013 | + self.assertIn(dns_addr, resolve_status, |
1014 | + msg=("%s not in systemd-resolve status: %s" % |
1015 | + (dns_addr, resolve_status))) |
1016 | + self.assertIn(dns_search, resolve_status, |
1017 | + msg=("search addr '%s' not in systemd-resolve status: %s" % |
1018 | + (dns_search, resolve_status))) |
1019 | + |
1020 | + |
1021 | +def get_image(top_d, release): |
1022 | + cmd = ['get-image', top_d, 'cloud-daily', release] |
1023 | + subprocess.check_call(cmd) |
1024 | + |
1025 | + |
1026 | +def patch_image(image, packages=None, kernel=None, initrd=None): |
1027 | + '''Patch root-image with dep8 built open-iscsi package.''' |
1028 | + |
1029 | + if packages is None: |
1030 | + # an empty 'packages' to patch-image still installs open-iscsi |
1031 | + packages = [] |
1032 | + |
1033 | + cmd = ['patch-image', image] |
1034 | + if kernel: |
1035 | + cmd.append('--kernel=%s' % kernel) |
1036 | + if initrd: |
1037 | + cmd.append('--initrd=%s' % initrd) |
1038 | + cmd.extend(packages) |
1039 | + |
1040 | + subprocess.check_call(cmd) |
1041 | |
1042 | |
1043 | if __name__ == '__main__': |
1044 | @@ -283,7 +399,7 @@ if __name__ == '__main__': |
1045 | suite = unittest.TestSuite() |
1046 | suite.addTest(unittest.TestLoader().loadTestsFromTestCase(OpenIscsiTest)) |
1047 | suite.addTest(unittest.TestLoader().loadTestsFromTestCase( |
1048 | - MAASEphemeralTest)) |
1049 | + CloudImageTest)) |
1050 | rc = unittest.TextTestRunner(verbosity=2).run(suite) |
1051 | if not rc.wasSuccessful(): |
1052 | sys.exit(1) |
1053 | diff --git a/debian/tests/tgt-boot-test b/debian/tests/tgt-boot-test |
1054 | index 538477c..6ed7a9b 100755 |
1055 | --- a/debian/tests/tgt-boot-test |
1056 | +++ b/debian/tests/tgt-boot-test |
1057 | @@ -1,27 +1,27 @@ |
1058 | #!/bin/bash |
1059 | |
1060 | -## we run a python simple web server on this port. |
1061 | -WPORT=${WPORT:-32600} |
1062 | -## HOST_IP has to be an IP address that the vm will be able to access |
1063 | -## ie, it can't be "127.0.0.1". If empty (default) then the ip from |
1064 | -## the device HOST_IP_NIC (default=eth0) will be used. |
1065 | -HOST_IP=${HOST_IP:-""} |
1066 | -HOST_IP_NIC="eth0" |
1067 | -## set OVERLAY_DISK in environment to get this. |
1068 | -## set to 'temp' to just get a temp file to excercise that path |
1069 | -## set to some other path and it will be created if non-existant |
1070 | -## the overlay will then be put on this drive and read from there |
1071 | -## instead of a tmpfs |
1072 | -OVERLAY_DISK=${OVERLAY_DISK:-""} |
1073 | -## |
1074 | -## XKVM_BRIDGE: set to 'user' to use qemu user networking |
1075 | -## set to name of a bridge such as virbr0 to use tap on that bridge |
1076 | -XKVM_BRIDGE=${XKVM_BRIDGE:-"user"} |
1077 | +VERBOSITY=0 |
1078 | +TEMP_D="" |
1079 | +DEF_WPORT=32600 |
1080 | +IPV4_NET="10.0.12" |
1081 | +DEF_NETDEV="user,net=${IPV4_NET}.0/24,host=${IPV4_NET}.2" |
1082 | |
1083 | +ISCSI_PORT=3260 |
1084 | TGT_CONF="" |
1085 | HTTP_PID="" |
1086 | TGT_NAME="" |
1087 | |
1088 | +_DEF_CMDLINE_TMPL=( |
1089 | + nomodeset {icmdline_parms} {cmdline_ip} ro |
1090 | + net.ifnames=0 BOOTIF_DEFAULT=eth0 |
1091 | + root={root} {overlay_drive} |
1092 | + console=ttyS0 {cmdline_ds} |
1093 | +) |
1094 | +DEF_CMDLINE_TMPL="${_DEF_CMDLINE_TMPL[*]}" |
1095 | + |
1096 | +error() { echo "$@" 1>&2; } |
1097 | +fail() { [ $# -eq 0 ] || error "$@"; exit 1; } |
1098 | + |
1099 | cleanup() { |
1100 | [ ! -d "$TEMP_D" ] || rm -Rf "$TEMP_D" |
1101 | if [ -f "$TGT_CONF" ]; then |
1102 | @@ -33,165 +33,500 @@ cleanup() { |
1103 | kill $HTTP_PID |
1104 | fi |
1105 | } |
1106 | + |
1107 | Usage() { |
1108 | - cat <<EOF |
1109 | -${0##*/} image kernel initrd |
1110 | + cat <<EOF |
1111 | +Usage: ${0##*/} [ options ] image kernel initrd |
1112 | + |
1113 | + put 'image' into tgt, boot 'kernel' and 'initrd' in a kvm |
1114 | + guest pointing to that iscsi target. |
1115 | |
1116 | -puts 'image' into tgt, then boots kernel and initrd in kvm to use it as root. |
1117 | + options: |
1118 | + -p | --webserv-port PORT serve cloud-init seed on port PORT |
1119 | + default: ${DEF_WPORT} |
1120 | + -O | --overlay-disk DISK attach DISK to guest and use it for the overlayroot |
1121 | + target. Use 'temp' to use a temp file. |
1122 | + use an existing file to use that. Use a |
1123 | + non-existing file to create. |
1124 | + -n | --netdev DEV passed through to xkvm. |
1125 | + if none passed, default is: |
1126 | + $DEF_NETDEV |
1127 | + -a | --host-addr ADDR will be used for iscsi host address and |
1128 | + cloud-init seed address. Default is read |
1129 | + from 'host=' in --netdev arg. |
1130 | + -l | --serial-log FILE write serial log to FILE default is to let output |
1131 | + go to console. |
1132 | + --cmdline TMPL template for kernel command line. |
1133 | + default: ${DEF_CMDLINE_TMPL} |
1134 | + --cmdline-add ARGS add the args to the kernel command line. default: "" |
1135 | + --user-data FILE user-data to provide to instance. |
1136 | + default: builtin |
1137 | + --user-data-add FILE append FILE to default user-data. |
1138 | EOF |
1139 | } |
1140 | -error() { echo "$@" 1>&2; } |
1141 | -fail() { [ $# -eq 0 ] || error "$@"; exit 1; } |
1142 | |
1143 | +bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || error "$@"; return 1; } |
1144 | + |
1145 | +debug() { |
1146 | + local level=${1}; shift; |
1147 | + [ "${level}" -gt "${VERBOSITY}" ] && return |
1148 | + error "${@}" |
1149 | +} |
1150 | |
1151 | -[ "$1" = "-h" -o "$1" = "--help" ] && { Usage ; exit 0; } |
1152 | -image_in=$1 |
1153 | -kernel_in=$2 |
1154 | -initrd_in=$3 |
1155 | -[ $# -ge 3 ] || { Usage 1>&2; fail "must give image, kernel, initrd"; } |
1156 | - |
1157 | -image=$(readlink -f "$image_in") || |
1158 | - fail "cannot get full path to $image_in" |
1159 | -kernel=$(readlink -f "$kernel_in") || |
1160 | - fail "cannot get full path to $kernel_in" |
1161 | -initrd=$(readlink -f "$initrd_in") || |
1162 | - fail "cannot get full path to $initrd_in" |
1163 | - |
1164 | -TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}-XXXXXX") || exit 1 |
1165 | -TEMP_D=$(cd "$TEMP_D" && pwd) |
1166 | -trap cleanup EXIT |
1167 | - |
1168 | -t_image="${TEMP_D}/image" |
1169 | -t_kernel="${TEMP_D}/kernel" |
1170 | -t_initrd="${TEMP_D}/initrd" |
1171 | - |
1172 | -# this is the IP address of the tgtd |
1173 | -if [ -n "$HOST_IP" ]; then |
1174 | - ipaddr=${HOST_IP} |
1175 | -else |
1176 | - ipaddr=$(ifconfig ${HOST_IP_NIC} | |
1177 | - sed -n '/inet addr/s/\([^:]*:\)\([^ ]*\)\( .*\)/\2/p') |
1178 | - [ $? -eq 0 -a -n "$ipaddr" ] || |
1179 | - fail "failed to get ipaddr from '$HOST_IP_NIC'. set HOST_IP or HOST_IP_NIC" |
1180 | -fi |
1181 | - |
1182 | -## set up a user-data and meta-data for cloud-init |
1183 | -## and we'll seed that through the kernel command line |
1184 | -## |
1185 | -## The user data we provide will allow us to log in |
1186 | -## as the 'ubuntu' user with 'passw0rd'. |
1187 | -keys=$( (ssh-add -L 2>/dev/null ; |
1188 | - [ -f ~/.ssh/id_rsa.pub ] && cat ~/.ssh/id_rsa.pub ) | sort -u ) |
1189 | -if [ -n "$keys" ]; then |
1190 | - d=$(IFS=$'\n'; for i in ${keys}; do echo "'$i',"; done) |
1191 | - keys="[${d%,}]" |
1192 | -else |
1193 | - keys="[]" |
1194 | -fi |
1195 | -mkdir "${TEMP_D}/ci-seed" |
1196 | -cat > "${TEMP_D}/ci-seed/user-data" <<EOF |
1197 | +write_userdata() { |
1198 | + ## set up a user-data and meta-data for cloud-init |
1199 | + ## and we'll seed that through the kernel command line |
1200 | + ## |
1201 | + ## The user data we provide will allow us to log in |
1202 | + ## as the 'ubuntu' user with 'passw0rd'. |
1203 | + local out="$1" keys="${2:-[]}" |
1204 | + cat > "$out" <<"EOF" |
1205 | #cloud-config |
1206 | -password: passw0rd |
1207 | -chpasswd: { expire: False } |
1208 | +chpasswd: |
1209 | + expire: False |
1210 | + list: | |
1211 | + ubuntu:passw0rd |
1212 | + root:root |
1213 | ssh_pwauth: True |
1214 | -ssh_authorized_keys: $keys |
1215 | EOF |
1216 | +} |
1217 | |
1218 | -### XXX: matsubara fix user data to collect stuff |
1219 | -## just an example of what you could do here. |
1220 | -## added when debugging resolvconf LP: #1501033 |
1221 | -if true; then |
1222 | - cat >> "${TEMP_D}/ci-seed/user-data" <<EOF |
1223 | -runcmd: |
1224 | - - [ mkdir, -p, /mnt/output ] |
1225 | - - [ cp, /etc/resolv.conf, /mnt/output] |
1226 | - - [ tar, -C, /mnt/output, -cf, /dev/vda, . ] |
1227 | -power_state: |
1228 | - mode: poweroff |
1229 | - message: cloud-init finished. Shutting down. |
1230 | - timeout: 60 |
1231 | -EOF |
1232 | -fi |
1233 | +get_ssh_pubkeys() { |
1234 | + # get users ssh pubkeys in a yaml list format |
1235 | + local keys="" d="" |
1236 | + keys=$( (ssh-add -L 2>/dev/null ; |
1237 | + [ -f ~/.ssh/id_rsa.pub ] && cat ~/.ssh/id_rsa.pub ) | sort -u ) |
1238 | + if [ -n "$keys" ]; then |
1239 | + d=$(IFS=$'\n'; for i in ${keys}; do echo "'$i',"; done) |
1240 | + keys="[${d%,}]" |
1241 | + else |
1242 | + keys="[]" |
1243 | + fi |
1244 | + _RET="$keys" |
1245 | +} |
1246 | |
1247 | -iid=$(uuidgen 2>/dev/null || echo i-abcdefg) |
1248 | -cat > "${TEMP_D}/ci-seed/meta-data" <<EOF |
1249 | -instance-id: $iid |
1250 | +write_metadata() { |
1251 | + local iid="" out="$1" pubkeys="$2" |
1252 | + iid=$(uuidgen 2>/dev/null || echo i-abcdefg) |
1253 | + { |
1254 | + echo "instance-id: $iid" |
1255 | + if [ -n "$pubkeys" ]; then |
1256 | + echo "public-keys: $pubkeys" |
1257 | + fi |
1258 | + } > "$out" |
1259 | +} |
1260 | + |
1261 | +register_image() { |
1262 | + local conf="$1" name="$2" image="$3" |
1263 | + sudo tee "$conf" >/dev/null <<EOF |
1264 | +<target ${name}> |
1265 | + readonly 1 |
1266 | + backing-store "$image" |
1267 | + allow-in-use yes |
1268 | +</target> |
1269 | EOF |
1270 | + [ $? -eq 0 ] || { error "failed to write '$conf'"; return 1; } |
1271 | |
1272 | -## run a python web server to seed this |
1273 | -( cd "${TEMP_D}/ci-seed" && exec python -m SimpleHTTPServer $WPORT ) & |
1274 | -HTTP_PID=$! |
1275 | + sudo tgt-admin "--update=$name" || |
1276 | + { error "failed tgt-admin update=$name"; return 1; } |
1277 | +} |
1278 | |
1279 | -if [ "${image%.gz}" != "$image" ]; then |
1280 | - error "uncompressing $image_in -> temp-dir" |
1281 | - gzcat "$image" > "${TEMP_D}/image" || |
1282 | - fail "failed uncompress of $image_in" |
1283 | -else |
1284 | - ln -s "$image" "$t_image" || fail "symlink to $image failed" |
1285 | -fi |
1286 | +address_on_dev() { |
1287 | + local dev="$1" atype="${2:-inet}" out="" addr="" |
1288 | + out=$(ip address show dev "$dev" 2>/dev/null) || |
1289 | + { error "could not get address on $1"; return 1; } |
1290 | + addr=$(echo "$out" | |
1291 | + awk '$1 == atype && $4 == "global" { |
1292 | + sub(/\/.*/, "", $2); print $2; exit(0); }' "atype=$atype" ) |
1293 | + [ -n "$addr" ] || return |
1294 | + _RET="$addr" |
1295 | +} |
1296 | |
1297 | -ln -s "$kernel" "$t_kernel" && |
1298 | - ln -s "$initrd" "$t_initrd" || |
1299 | - fail "failed link to kernel or initrd" |
1300 | +find_host_addr() { |
1301 | + # run through --netdev args and fine 'host=' or make one up base |
1302 | + # on there being a net= and assume .2 for host. |
1303 | + # example: type=user,id=net00,net=10.0.12.0/24,host=10.0.12.2 |
1304 | + # ipv6-net=fec0::/64,ipv6-host=fec0::2 |
1305 | + # lxcbr0 |
1306 | + local nd="" atype="" bridge="" |
1307 | + local oifs="$IFS" |
1308 | + for nd in "$@"; do |
1309 | + local host="" netaddr="" cur="" ipv4="x" ipv6="x" ntype="" |
1310 | + bridge=${nd%%,*} |
1311 | + if [ "$bridge" != "user" -a -e "/sys/class/net/$bridge" ]; then |
1312 | + for atype in inet inet6; do |
1313 | + address_on_dev "$bridge" "$atype" || continue |
1314 | + debug 2 "took $atype address on bridge '$bridge': $_RET"; |
1315 | + return 0; |
1316 | + done |
1317 | + fi |
1318 | + cur=${nd} |
1319 | + while :; do |
1320 | + first=${cur%%,*} |
1321 | + rest=${cur#*,} |
1322 | + case "$first" in |
1323 | + user) ntype=user;; |
1324 | + ipv4=on|ipv4) ipv4=on;; |
1325 | + ipv4=off) ipv4=off;; |
1326 | + ipv6=on|ipv6) ipv6=on;; |
1327 | + ipv6=off) ipv6=off;; |
1328 | + ipv6-net=*) tmp=${first#*=}; |
1329 | + tmp=${tmp%/*} |
1330 | + netaddr="${tmp%.*}:2" |
1331 | + ;; |
1332 | + net=*) tmp=${first#*=}; |
1333 | + tmp=${tmp%/*} |
1334 | + netaddr="${tmp%.*}.2" |
1335 | + ;; |
1336 | + host=*|ipv6-host=) host=${first#*=};; |
1337 | + esac |
1338 | + if [ "$first" = "$rest" ]; then |
1339 | + # there was no ',' |
1340 | + break |
1341 | + fi |
1342 | + cur=$rest |
1343 | + done |
1344 | + local found="" msg="" |
1345 | + if [ -n "$host" ]; then |
1346 | + found="$host" |
1347 | + msg="host= in '$nd'" |
1348 | + elif [ -n "$netaddr" ]; then |
1349 | + found="$netaddr" |
1350 | + msg="second address on net= in '$nd'"; |
1351 | + elif [ "$ntype" = "user" ]; then |
1352 | + case "$ipv4/$ipv6" in |
1353 | + */on) |
1354 | + msg="known user net default host in ipv6 in '$nd'" |
1355 | + found="fec0::2";; |
1356 | + x/x|on/*) |
1357 | + msg="known user net default host in ipv4 in '$nd'" |
1358 | + found="10.0.2.2";; |
1359 | + esac |
1360 | + fi |
1361 | + if [ -n "$found" ]; then |
1362 | + debug 2 "found host address '$found' via $msg" |
1363 | + _RET="$found" |
1364 | + return 0 |
1365 | + fi |
1366 | + done |
1367 | + return 1 |
1368 | +} |
1369 | |
1370 | |
1371 | -TGT_NAME=${TEMP_D##*/} |
1372 | -TGT_CONF="/etc/tgt/conf.d/$TGT_NAME.conf" |
1373 | +get_rfc4173() { |
1374 | + # protocol would normally be '6' (tcp but empty is fine) |
1375 | + # https://tools.ietf.org/html/rfc4173 |
1376 | + # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=804162 |
1377 | + local server="$1" name="$2" port="$3" lun="$4" user="$5" pass="$6" proto="$7" |
1378 | + local rfc="" auth="${user:+${user}${pass:+:${pass}}}" |
1379 | + rfc="iscsi:" |
1380 | + if [ -n "${user}" ]; then |
1381 | + rfc="$rfc:${user}${pass:+:${pass}}" |
1382 | + fi |
1383 | + case "$server" in |
1384 | + \[*\]) :;; |
1385 | + *:*) server="[$server]";; |
1386 | + esac |
1387 | + rfc="${server}:$proto:${port}:${lun}:${name}" |
1388 | + _RET="$rfc" |
1389 | +} |
1390 | |
1391 | -sudo tee "$TGT_CONF" >/dev/null <<EOF |
1392 | -<target ${TGT_NAME}> |
1393 | - readonly 1 |
1394 | - backing-store "$t_image" |
1395 | - allow-in-use yes |
1396 | -</target> |
1397 | +get_root_parm() { |
1398 | + local server="$1" name="$2" port="${3:-$ISCSI_PORT}" |
1399 | + local lun="$4" user="$5" pass="$6" proto="$7" part="$8" |
1400 | + local ret="" |
1401 | + # for ipv6 addresses, path just contains : for the server |
1402 | + _RET="/dev/disk/by-path/ip-$server:$port-iscsi-${name}-lun-${lun}${part:+-part$part}" |
1403 | +} |
1404 | + |
1405 | +get_cmdline_params() { |
1406 | + local server="$1" name="$2" port="$3" lun="$4" user="$5" pass="$6" |
1407 | + local proto="$7" initiator="$8" ret="" |
1408 | + ret="${initiator:+iscsi_initiator=${initiator} }" |
1409 | + ret="${ret}iscsi_target_name=$name " |
1410 | + ret="${ret}iscsi_target_ip=$server " |
1411 | + ret="${ret}${port:+iscsi_target_port=$port }" |
1412 | + ret="${ret}${initiator:+iscsi_initiator=$initiator }" |
1413 | + _RET=${ret% } |
1414 | +} |
1415 | + |
1416 | +get_iscsi_cmd() { |
1417 | + local server="$1" name="$2" port="$3" lun="$4" user="$5" pass="$6" |
1418 | + local proto="$7" initiator="$8" ret="" group=${9:-1} |
1419 | + _RET=( iscsistart -i "$initiator" |
1420 | + -t "$name" -g "$group" -a "$server" -p "$port" |
1421 | + ${user:+-u "$user"} ${pass:+-w "${pass}"} ) |
1422 | +} |
1423 | + |
1424 | +serv_http() { |
1425 | + local dir="$1" port="$2" check="$3" |
1426 | + local python="" kid="" |
1427 | + command -v python3 >/dev/null 2>&1 && python=python3 || python=python |
1428 | + cat > "$TEMP_D/webserv" <<EOF |
1429 | +import os, socket, sys |
1430 | +try: |
1431 | + from BaseHTTPServer import HTTPServer |
1432 | + from SimpleHTTPServer import SimpleHTTPRequestHandler |
1433 | +except ImportError: |
1434 | + from http.server import HTTPServer, SimpleHTTPRequestHandler |
1435 | + |
1436 | +class HTTPServerV6(HTTPServer): |
1437 | + address_family = socket.AF_INET6 |
1438 | + |
1439 | +if __name__ == "__main__": |
1440 | + dir = sys.argv[1] |
1441 | + port = int(sys.argv[2]) |
1442 | + os.chdir(dir) |
1443 | + server = HTTPServerV6(("::", port), SimpleHTTPRequestHandler) |
1444 | + server.serve_forever() |
1445 | EOF |
1446 | -[ $? -eq 0 ] || fail "failed to write '$TGT_CONF'" |
1447 | - |
1448 | -sudo tgt-admin "--update=${TGT_NAME}" || fail "failed tgt-admin update=$TGT_NAME" |
1449 | - |
1450 | -overlay_disk=${OVERLAY_DISK} |
1451 | -overlay_drive_kernel="overlayroot=tmpfs" |
1452 | -overlay_drive_xkvm="" |
1453 | -if [ -n "$overlay_disk" ]; then |
1454 | - if [ "${overlay_disk}" = "temp" ]; then |
1455 | - overlay_disk="${TEMP_D}/overlay.img" |
1456 | - fi |
1457 | - if [ ! -f "$overlay_disk" ]; then |
1458 | - qemu-img create -f raw "$overlay_disk" 1G |
1459 | - mkfs.ext2 -L "delta" -F "$overlay_disk" >/dev/null 2>&1 || |
1460 | - fail "failed mkfs on $overlay_disk" |
1461 | - overlay_drive_kernel="overlayroot=device:dev=LABEL=delta" |
1462 | - overlay_drive_xkvm="--disk=${overlay_disk},format=raw" |
1463 | - fi |
1464 | -fi |
1465 | - |
1466 | -## Now boot a kvm from it |
1467 | -iport="3260" |
1468 | -root=LABEL=cloudimg-rootfs |
1469 | -root=/dev/disk/by-path/ip-$ipaddr:$iport-iscsi-${TGT_NAME}-lun-1 |
1470 | -parms=( |
1471 | - nomodeset |
1472 | - iscsi_target_name=${TGT_NAME} iscsi_target_ip=$ipaddr |
1473 | - iscsi_target_port=3260 |
1474 | - iscsi_initiator=maas-enlist |
1475 | - ip=::::maas-enlist:BOOTIF ro |
1476 | - net.ifnames=0 |
1477 | - BOOTIF_DEFAULT=eth0 |
1478 | - "root=${root}" |
1479 | - ${overlay_drive_kernel} |
1480 | - console=ttyS0 console=tty1 |
1481 | - "ds=nocloud-net;seedfrom=http://$ipaddr:$WPORT/" |
1482 | -) |
1483 | + $python "$TEMP_D/webserv" "$dir" "$port" & |
1484 | + HTTP_PID=$! |
1485 | + local i=0 site="127.0.0.1" |
1486 | + local url="http://$site:$port/$check" |
1487 | + while [ -d /proc/$HTTP_PID -a "$i" -lt 10 ] && i=$(($i+1)); do |
1488 | + no_proxy=$site wget -q -O /dev/null "$url" && return 0 |
1489 | + sleep .5 |
1490 | + done |
1491 | + return 1 |
1492 | +} |
1493 | + |
1494 | +render_templ() { |
1495 | + local tmpl="$1" |
1496 | + shift |
1497 | + local key val i="" cur="$tmpl" |
1498 | + for i in "$@"; do |
1499 | + key=${i%%=*} |
1500 | + val=${i#*=} |
1501 | + cur=${cur//\{${key}\}/${val}} |
1502 | + done |
1503 | + _RET="$cur" |
1504 | +} |
1505 | + |
1506 | +get_partition() { |
1507 | + # return in _RET the 'auto' partition for a image. |
1508 | + # return partition number for a partitioned image |
1509 | + # return 0 for unpartitioned |
1510 | + # return 0 if image is partitioned, 1 if not |
1511 | + local img="$1" |
1512 | + out=$(LANG=C sfdisk --list -uS "$img" 2>&1) || { |
1513 | + error "failed determining if partitioned: $out"; |
1514 | + return 1; |
1515 | + } |
1516 | + if echo "$out" | grep -q 'Device.*Start.*End'; then |
1517 | + _RET=1 |
1518 | + else |
1519 | + _RET=0 |
1520 | + fi |
1521 | +} |
1522 | + |
1523 | +main() { |
1524 | + local short_opts="a:hn:O:p:l:v" |
1525 | + local long_opts="help,cmdline:,cmdline-add:,cmdline-ip:,disk:,host-addr:,overlay-disk:,netdev:,serial-log:,user-data:,user-data-add:,verbose,webserv-port:" |
1526 | + local getopt_out="" |
1527 | + |
1528 | + getopt_out=$(getopt --name "${0##*/}" \ |
1529 | + --options "${short_opts}" --long "${long_opts}" -- "$@") && |
1530 | + eval set -- "${getopt_out}" || |
1531 | + { bad_Usage; return; } |
1532 | + |
1533 | + local cur="" next="" |
1534 | + local overlay_disk="" webserv_port="${DEF_WPORT}" netdevs="" host_addr="" |
1535 | + local serial_log="" cmdline_tmpl="${DEF_CMDLINE_TMPL}" user_data_file="" |
1536 | + local cmdline_add="" user_data_add="" pt="" |
1537 | + # The string 'BOOTIF' is replaced at runtime with the interface |
1538 | + # name by cloud-initramfs-dyn-netconf. |
1539 | + local cmdline_ip="ip=::::{iinitiator}:BOOTIF" |
1540 | + netdevs=( ) |
1541 | + pt=( ) |
1542 | + |
1543 | + while [ $# -ne 0 ]; do |
1544 | + cur="$1"; next="$2"; |
1545 | + case "$cur" in |
1546 | + -h|--help) Usage ; exit 0;; |
1547 | + --cmdline) cmdline_tmpl=$next; shift;; |
1548 | + --cmdline-add) cmdline_add=$next; shift;; |
1549 | + --cmdline-ip) cmdline_ip=$next; shift;; |
1550 | + --disk) pt[${#pt[@]}]="$1=$2"; shift;; |
1551 | + -a|--host-addr) host_addr=$next; shift;; |
1552 | + -O|--overlay-disk) overlay_disk=$next; shift;; |
1553 | + -n|--netdev) netdevs[${#netdevs[@]}]="$next"; shift;; |
1554 | + -v|--verbose) VERBOSITY=$((${VERBOSITY}+1));; |
1555 | + -l|--serial-log) serial_log="$next"; shift;; |
1556 | + --user-data-file) user_data_file="$next"; shift;; |
1557 | + --user-data-add) user_data_add="$next"; shift;; |
1558 | + -p|--webserv-port) webserv_port=$port; shift;; |
1559 | + --) shift; break;; |
1560 | + esac |
1561 | + shift; |
1562 | + done |
1563 | + |
1564 | + [ $# -eq 3 ] || { bad_Usage "expected 3 args, got $# ($*)"; return 1; } |
1565 | + local image_in=$1 kernel_in=$2 initrd_in=$3 |
1566 | + for f in "$image_in" "$kernel_in" "$initrd_in"; do |
1567 | + [ -f "$f" ] || fail "$f: not a file" |
1568 | + done |
1569 | + |
1570 | + local image="" kernel="" initrd="" |
1571 | + image=$(readlink -f "$image_in") || |
1572 | + fail "cannot get full path to $image_in" |
1573 | + kernel=$(readlink -f "$kernel_in") || |
1574 | + fail "cannot get full path to $kernel_in" |
1575 | + initrd=$(readlink -f "$initrd_in") || |
1576 | + fail "cannot get full path to $initrd_in" |
1577 | + |
1578 | + TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX") || |
1579 | + fail "failed to make tempdir" |
1580 | + TEMP_D=$(cd "$TEMP_D" && pwd) |
1581 | + trap cleanup EXIT |
1582 | + |
1583 | + local t_image t_kernel t_initrd |
1584 | + t_image="${TEMP_D}/image" |
1585 | + t_kernel="${TEMP_D}/kernel" |
1586 | + t_initrd="${TEMP_D}/initrd" |
1587 | + |
1588 | + if [ "${#netdevs[@]}" -eq 0 ]; then |
1589 | + netdevs=( "$DEF_NETDEV" ) |
1590 | + fi |
1591 | + |
1592 | + local keys="" |
1593 | + mkdir -p "$TEMP_D/ci-seed" |
1594 | + if [ -n "${user_data_file}" ]; then |
1595 | + cat "${user_data_file}" > "$TEMP_D/ci-seed/user-data" || |
1596 | + fail "failed to copy '$user_data_file' to ci-seed/user-data" |
1597 | + else |
1598 | + write_userdata "$TEMP_D/ci-seed/user-data" || |
1599 | + fail "failed to write user-data" |
1600 | + fi |
1601 | + if [ -n "$user_data_add" ]; then |
1602 | + cat "$user_data_add" >> "$TEMP_D/ci-seed/user-data" || |
1603 | + fail "failed to write additional user-data" |
1604 | + fi |
1605 | + |
1606 | + get_ssh_pubkeys || fail "failed getting pubkeys" |
1607 | + keys="$_RET" |
1608 | + write_metadata "$TEMP_D/ci-seed/meta-data" "$keys" || |
1609 | + fail "failed writing metadata" |
1610 | + |
1611 | + if [ -z "$host_addr" ]; then |
1612 | + find_host_addr "${netdevs[@]}" || |
1613 | + fail "failed to find host address. try --host-addr." |
1614 | + host_addr="$_RET" |
1615 | + fi |
1616 | + debug 1 "using host address: '${host_addr}'" |
1617 | + |
1618 | + ## run a python web server to seed this |
1619 | + serv_http "$TEMP_D/ci-seed" "$webserv_port" "user-data" || |
1620 | + fail "failed to web serv $TEMP_D/ci-seed on $webserv_port" |
1621 | + debug 1 "web server in pid $HTTP_PID serving ${TEMP_D}/ci-seed/ on port $webserv_port" |
1622 | + local seed_url="http://$host_addr:$webserv_port/" |
1623 | + if [ "${host_addr#*:}" != "$host_addr" ]; then |
1624 | + seed_url="http://[$host_addr]:$webserv_port/" |
1625 | + fi |
1626 | + debug 1 " ${seed_url}meta-data" |
1627 | + |
1628 | + if [ "${image%.gz}" != "$image" ]; then |
1629 | + error "uncompressing $image_in -> temp-dir" |
1630 | + gzcat "$image" > "${TEMP_D}/image" || |
1631 | + fail "failed uncompress of $image_in" |
1632 | + else |
1633 | + ln -s "$image" "$t_image" || fail "symlink to $image failed" |
1634 | + fi |
1635 | + |
1636 | + ln -s "$kernel" "$t_kernel" && |
1637 | + ln -s "$initrd" "$t_initrd" || |
1638 | + fail "failed link to kernel or initrd" |
1639 | + |
1640 | + local partition="" |
1641 | + get_partition "$t_image" || |
1642 | + fail "failed to read partition from $image" |
1643 | + partition=${_RET} |
1644 | + [ "$part" = "0" ] && partition="" |
1645 | + |
1646 | + TGT_NAME=${TEMP_D##*/} |
1647 | + TGT_NAME=${TGT_NAME//./-} |
1648 | + TGT_CONF="/etc/tgt/conf.d/$TGT_NAME.conf" |
1649 | + register_image "$TGT_CONF" "$TGT_NAME" "$t_image" || |
1650 | + fail "failed to register image $t_image in tgt" |
1651 | + |
1652 | + overlay_drive_kernel="overlayroot=tmpfs" |
1653 | + overlay_drive_xkvm="" |
1654 | + if [ -n "${overlay_disk}" ]; then |
1655 | + if [ "${overlay_disk}" = "temp" ]; then |
1656 | + overlay_disk="${TEMP_D}/overlay.img" |
1657 | + fi |
1658 | + if [ ! -f "$overlay_disk" ]; then |
1659 | + qemu-img create -f raw "$overlay_disk" 1G |
1660 | + mkfs.ext2 -L "delta" -F "$overlay_disk" >/dev/null 2>&1 || |
1661 | + fail "failed mkfs on $overlay_disk" |
1662 | + overlay_drive_kernel="overlayroot=device:dev=LABEL=delta" |
1663 | + overlay_drive_xkvm="--disk=${overlay_disk},format=raw" |
1664 | + fi |
1665 | + fi |
1666 | + |
1667 | + local iserver="${host_addr}" iname="$TGT_NAME" iport="$ISCSI_PORT" |
1668 | + local ilun="1" iuser="" ipass="" iproto="" iinitiator="maas-enlist" |
1669 | + local rfc="" root="" iscsi_cmdline_parms="" iscsi_start_cmd="" |
1670 | + local cmdline="" |
1671 | + get_rfc4173 "${iserver}" "$iname" "$iport" "$ilun" \ |
1672 | + "$iuser" "$ipass" "$iproto" |
1673 | + rfc="$_RET" |
1674 | + |
1675 | + get_cmdline_params "${iserver}" "$iname" "$iport" "$ilun" \ |
1676 | + "$iuser" "$ipass" "$iproto" "$iinitiator" |
1677 | + iscsi_cmdline_parms="$_RET" |
1678 | + |
1679 | + get_root_parm "$iserver" "$iname" "$iport" "$ilun" \ |
1680 | + "$iuser" "$ipass" "$iproto" "$partition" |
1681 | + root="$_RET" |
1682 | + |
1683 | + get_iscsi_cmd "$iserver" "$iname" "$iport" "$ilun" \ |
1684 | + "$iuser" "$ipass" "$iproto" "$iinitiator" |
1685 | + iscsi_start_cmd=( "${_RET[@]}" ) |
1686 | + |
1687 | + render_templ "${cmdline_ip}" iinitiator="$iinitiator" || |
1688 | + { error "failed rendering cmdline ip: ${cmdline_ip}"; return 1; } |
1689 | + cmdline_ip="$_RET" |
1690 | + |
1691 | + render_templ "$cmdline_tmpl${cmdline_add:+ ${cmdline_add}}" \ |
1692 | + iserver="$host_addr" iname="$TGT_NAME" iport="$ISCSI_PORT" \ |
1693 | + ilun="$ilun" iuser="$iuser" ipass="$ipass" iproto="$iproto" \ |
1694 | + iinitiator="$iinitiator" "rfc4173=$rfc" "root=$root" \ |
1695 | + "seed_url=$seed_url" icmdline_parms="$iscsi_cmdline_parms" \ |
1696 | + "overlay_drive=${overlay_drive_kernel}" \ |
1697 | + "cmdline_ds=ds=nocloud-net;seedfrom=${seed_url}" \ |
1698 | + "cmdline_ip=${cmdline_ip}" || |
1699 | + { error "failed rendering cmdline"; return 1; } |
1700 | + cmdline="$_RET" |
1701 | + |
1702 | + debug 1 "rfc4173: $rfc" |
1703 | + debug 1 "iscsi_cmdline_parms: $iscsi_cmdline_parms" |
1704 | + debug 1 "root: $root" |
1705 | + debug 1 "iscsi_start_cmd: ${iscsi_start_cmd[*]}" |
1706 | + debug 1 "cmdline=$cmdline" |
1707 | + |
1708 | + local netdev_args="" |
1709 | + netdev_args=( ) |
1710 | + for i in "${netdevs[@]}"; do |
1711 | + netdev_args[${#netdev_args[@]}]="--netdev=$i" |
1712 | + done |
1713 | + |
1714 | + local mem="512" start="$SECONDS" ret="" |
1715 | + local cmd="" |
1716 | + cmd=( |
1717 | + ${BOOT_TIMEOUT:+timeout --kill-after=1m --signal=TERM $BOOT_TIMEOUT} |
1718 | + xkvm -v -v "${netdev_args[@]}" ${overlay_drive_xkvm} "${pt[@]}" -- |
1719 | + -echr 0x5 |
1720 | + -m $mem ${serial_log:+-serial "file:$serial_log"} -nographic |
1721 | + -kernel "${t_kernel}" -initrd "${t_initrd}" |
1722 | + -append "${cmdline}" |
1723 | + ) |
1724 | + debug 1 "executing: ${cmd[*]}" |
1725 | + "${cmd[@]}" |
1726 | + ret=$? |
1727 | + debug 1 "xkvm returned $ret in $((SECONDS-start))s" |
1728 | + return $ret |
1729 | +} |
1730 | |
1731 | -## The console is set to ttyS0, and serial output is |
1732 | -## logged to the file 'serial.log' |
1733 | -## in the ncurses path, you'll eventually get a login prompt |
1734 | -## but you can also ssh in. |
1735 | -[ "${NO_LOG:-0}" = "0" ] && serial_log="serial" || serial_log="" |
1736 | -mem="1024" |
1737 | -xkvm --netdev ${XKVM_BRIDGE} \ |
1738 | - ${overlay_drive_xkvm} \ |
1739 | - ${OUTPUT_DISK:+--disk "$OUTPUT_DISK"} --\ |
1740 | - -m 1024 ${serial_log:+-serial "file:$serial_log"} -nographic \ |
1741 | - -kernel "${t_kernel}" -initrd "${t_initrd}" \ |
1742 | - -append "${parms[*]}" |
1743 | +main "$@" |
1744 | +# vi: ts=4 expandtab |
1745 | diff --git a/debian/tests/xkvm b/debian/tests/xkvm |
1746 | index e260988..9513723 100755 |
1747 | --- a/debian/tests/xkvm |
1748 | +++ b/debian/tests/xkvm |
1749 | @@ -11,6 +11,7 @@ TAPDEVS=( ) |
1750 | # OVS_CLEANUP gets populated with bridge:devname pairs used with ovs |
1751 | OVS_CLEANUP=( ) |
1752 | MAC_PREFIX="52:54:00:12:34" |
1753 | +KVM="kvm" |
1754 | declare -A KVM_DEVOPTS |
1755 | |
1756 | error() { echo "$@" 1>&2; } |
1757 | @@ -27,7 +28,10 @@ randmac() { |
1758 | |
1759 | cleanup() { |
1760 | [ -z "${TEMP_D}" -o ! -d "${TEMP_D}" ] || rm -Rf "${TEMP_D}" |
1761 | - [ -z "${KVM_PID}" ] || kill "$KVM_PID" |
1762 | + if [ -n "${KVM_PID}" ]; then |
1763 | + debug 1 "${0##*/} cleanup killing kvm pid '$KVM_PID'" |
1764 | + kill "$KVM_PID" |
1765 | + fi |
1766 | if [ ${#TAPDEVS[@]} -ne 0 ]; then |
1767 | local name item |
1768 | for item in "${TAPDEVS[@]}"; do |
1769 | @@ -74,6 +78,10 @@ Usage: ${0##*/} [ options ] -- kvm-args [ ... ] |
1770 | -d | --disk DISK.img attach DISK.img as a disk (via virtio) |
1771 | --dry-run only report what would be done |
1772 | |
1773 | + --uefi boot with efi |
1774 | + --uefi-nvram=FILE boot with efi, using nvram settings in FILE |
1775 | + if FILE not present, copy from defaults. |
1776 | + |
1777 | NETDEV: |
1778 | Above, 'NETDEV' is a comma delimited string |
1779 | The first field must be |
1780 | @@ -102,13 +110,13 @@ isdevopt() { |
1781 | local model="$1" input="${2%%=*}" |
1782 | local out="" opt="" opts=() |
1783 | if [ -z "${KVM_DEVOPTS[$model]}" ]; then |
1784 | - out=$(kvm -device "$model,?" 2>&1) && |
1785 | - out=$(echo "$out" | sed -e "s,${model}[.],," -e 's,=.*,,') && |
1786 | + out=$($KVM -device "$model,?" 2>&1) && |
1787 | + out=$(echo "$out" | sed -e "s,[^.]*[.],," -e 's,=.*,,') && |
1788 | KVM_DEVOPTS[$model]="$out" || |
1789 | { error "bad device model $model?"; exit 1; } |
1790 | fi |
1791 | opts=( ${KVM_DEVOPTS[$model]} ) |
1792 | - for opt in "${KVM_DEVOPTS[@]}"; do |
1793 | + for opt in "${opts[@]}"; do |
1794 | [ "$input" = "$opt" ] && return 0 |
1795 | done |
1796 | return 1 |
1797 | @@ -223,19 +231,126 @@ ovs_cleanup() { |
1798 | return $errors |
1799 | } |
1800 | |
1801 | +quote_cmd() { |
1802 | + local quote='"' x="" vline="" |
1803 | + for x in "$@"; do |
1804 | + if [ "${x#* }" != "${x}" ]; then |
1805 | + if [ "${x#*$quote}" = "${x}" ]; then |
1806 | + x="\"$x\"" |
1807 | + else |
1808 | + x="'$x'" |
1809 | + fi |
1810 | + fi |
1811 | + vline="${vline} $x" |
1812 | + done |
1813 | + echo "$vline" |
1814 | +} |
1815 | + |
1816 | +get_bios_opts() { |
1817 | + # get_bios_opts(bios, uefi, nvram) |
1818 | + # bios is a explicit bios to boot. |
1819 | + # uefi is boolean indicating uefi |
1820 | + # nvram is optional and indicates that ovmf vars should be copied |
1821 | + # to that file if it does not exist. if it exists, use it. |
1822 | + local bios="$1" uefi="${2:-false}" nvram="$3" |
1823 | + local ovmf_dir="/usr/share/OVMF" |
1824 | + local bios_opts="" pflash_common="if=pflash,format=raw" |
1825 | + unset _RET |
1826 | + _RET=( ) |
1827 | + if [ -n "$bios" ]; then |
1828 | + _RET=( -drive "${pflash_common},file=$bios" ) |
1829 | + return 0 |
1830 | + elif ! $uefi; then |
1831 | + return 0 |
1832 | + fi |
1833 | + |
1834 | + # ovmf in older releases (14.04) shipped only a single file |
1835 | + # /usr/share/ovmf/OVMF.fd |
1836 | + # newer ovmf ships split files |
1837 | + # /usr/share/OVMF/OVMF_CODE.fd |
1838 | + # /usr/share/OVMF/OVMF_VARS.fd |
1839 | + # with single file, pass only one file and read-write |
1840 | + # with split, pass code as readonly and vars as read-write |
1841 | + local joined="/usr/share/ovmf/OVMF.fd" |
1842 | + local code="/usr/share/OVMF/OVMF_CODE.fd" |
1843 | + local vars="/usr/share/OVMF/OVMF_VARS.fd" |
1844 | + local split="" nvram_src="" |
1845 | + if [ -e "$code" -o -e "$vars" ]; then |
1846 | + split=true |
1847 | + nvram_src="$vars" |
1848 | + elif [ -e "$joined" ]; then |
1849 | + split=false |
1850 | + nvram_src="$joined" |
1851 | + elif [ -n "$nvram" -a -e "$nvram" ]; then |
1852 | + error "WARN: nvram given, but did not find expected ovmf files." |
1853 | + error " assuming this is code and vars (OVMF.fd)" |
1854 | + split=false |
1855 | + else |
1856 | + error "uefi support requires ovmf bios: apt-get install -qy ovmf" |
1857 | + return 1 |
1858 | + fi |
1859 | + |
1860 | + if [ -n "$nvram" ]; then |
1861 | + if [ ! -f "$nvram" ]; then |
1862 | + cp "$nvram_src" "$nvram" || |
1863 | + { error "failed copy $nvram_src to $nvram"; return 1; } |
1864 | + debug 1 "copied $nvram_src to $nvram" |
1865 | + fi |
1866 | + else |
1867 | + debug 1 "uefi without --uefi-nvram storage." \ |
1868 | + "nvram settings likely will not persist." |
1869 | + nvram="${nvram_src}" |
1870 | + fi |
1871 | + |
1872 | + if [ ! -w "$nvram" ]; then |
1873 | + debug 1 "nvram file ${nvram} is readonly" |
1874 | + nvram_ro="readonly" |
1875 | + fi |
1876 | + |
1877 | + if $split; then |
1878 | + # to ensure bootability firmware must be first, then variables |
1879 | + _RET=( -drive "${pflash_common},file=$code,readonly" ) |
1880 | + fi |
1881 | + _RET=( "${_RET[@]}" |
1882 | + -drive "${pflash_common},file=$nvram${nvram_ro:+,${nvram_ro}}" ) |
1883 | +} |
1884 | + |
1885 | +should_try_kvm() { |
1886 | + local name="should_try_kvm" msg="set _USE_KVM=1 to force." |
1887 | + case "${_USE_KVM-notset}" in |
1888 | + 0) debug 1 "$name = no. _USE_KVM=${_USE_KVM}"; return 1;; |
1889 | + 1) debug 1 "$name = yes. _USE_KVM=${_USE_KVM}"; return 0;; |
1890 | + notset) :;; |
1891 | + *) debug 1 "$name = no. unknown _USE_KVM=${_USE_KVM}"; return 1;; |
1892 | + esac |
1893 | + local virt="no-detect-virt" |
1894 | + if command -v systemd-detect-virt >/dev/null; then |
1895 | + virt="$(systemd-detect-virt --vm)" |
1896 | + fi |
1897 | + case "$virt" in |
1898 | + none) :;; |
1899 | + kvm) debug 1 "$name = no. virt=$virt (nested kvm is finicky). $msg"; |
1900 | + return 1;; |
1901 | + *) debug 1 "$name = no. virt=$virt. $msg"; return 1;; |
1902 | + esac |
1903 | + debug 1 "$name = yes." |
1904 | + return 0 |
1905 | +} |
1906 | + |
1907 | main() { |
1908 | local short_opts="hd:n:v" |
1909 | - local long_opts="help,dowait,disk:,dry-run,kvm:,no-dowait,netdev:,verbose" |
1910 | + local long_opts="bios:,help,dowait,disk:,dry-run,kvm:,no-dowait,netdev:,uefi,uefi-nvram:,verbose" |
1911 | local getopt_out="" |
1912 | getopt_out=$(getopt --name "${0##*/}" \ |
1913 | --options "${short_opts}" --long "${long_opts}" -- "$@") && |
1914 | eval set -- "${getopt_out}" || { bad_Usage; return 1; } |
1915 | |
1916 | - local bridge="$DEF_BRIDGE" |
1917 | + local bridge="$DEF_BRIDGE" oifs="$IFS" |
1918 | local netdevs="" need_tap="" ret="" p="" i="" pt="" cur="" conn="" |
1919 | local kvm="" kvmcmd="" archopts="" |
1920 | - local def_diskif=${DEF_DISKIF:-"virtio"} |
1921 | + local def_disk_driver=${DEF_DISK_DRIVER:-"virtio-blk"} |
1922 | local def_netmodel=${DEF_NETMODEL:-"virtio-net-pci"} |
1923 | + local bios="" uefi=false uefi_nvram="" |
1924 | |
1925 | archopts=( ) |
1926 | kvmcmd=( ) |
1927 | @@ -259,13 +374,16 @@ main() { |
1928 | -h|--help) Usage; exit 0;; |
1929 | -d|--disk) |
1930 | diskdevs[${#diskdevs[@]}]="$next"; shift;; |
1931 | - --dry-run) DRY_RUN=true;; |
1932 | - --kvm) kvm="$next"; shift;; |
1933 | + --dry-run) DRY_RUN=true;; |
1934 | + --kvm) kvm="$next"; shift;; |
1935 | -n|--netdev) |
1936 | netdevs[${#netdevs[@]}]=$next; shift;; |
1937 | -v|--verbose) VERBOSITY=$((${VERBOSITY}+1));; |
1938 | - --dowait) dowait=true;; |
1939 | - --no-dowait) dowait=false;; |
1940 | + --dowait) dowait=true;; |
1941 | + --no-dowait) dowait=false;; |
1942 | + --bios) bios="$next"; shift;; |
1943 | + --uefi) uefi=true;; |
1944 | + --uefi-nvram) uefi=true; uefi_nvram="$next"; shift;; |
1945 | --) shift; break;; |
1946 | esac |
1947 | shift; |
1948 | @@ -274,7 +392,7 @@ main() { |
1949 | [ ${#netdevs[@]} -eq 0 ] && netdevs=( "${DEF_BRIDGE}" ) |
1950 | pt=( "$@" ) |
1951 | |
1952 | - local kvm_pkg="" |
1953 | + local kvm_pkg="" virtio_scsi_bus="virtio-scsi-pci" |
1954 | [ -n "$kvm" ] && kvm_pkg="none" |
1955 | case $(uname -m) in |
1956 | i?86) |
1957 | @@ -285,28 +403,119 @@ main() { |
1958 | [ -n "$kvm" ] || |
1959 | { kvm="qemu-system-x86_64"; kvm_pkg="qemu-system-x86"; } |
1960 | ;; |
1961 | + s390x) |
1962 | + [ -n "$kvm" ] || |
1963 | + { kvm="qemu-system-s390x"; kvm_pkg="qemu-system-misc"; } |
1964 | + def_netmodel=${DEF_NETMODEL:-"virtio-net-ccw"} |
1965 | + virtio_scsi_bus="virtio-scsi-ccw" |
1966 | + ;; |
1967 | ppc64*) |
1968 | [ -n "$kvm" ] || |
1969 | { kvm="qemu-system-ppc64"; kvm_pkg="qemu-system-ppc"; } |
1970 | - def_netmodel="spapr-vlan" |
1971 | + def_netmodel="virtio-net-pci" |
1972 | # virtio seems functional on in 14.10, but might want scsi here |
1973 | #def_diskif="scsi" |
1974 | archopts=( "${archopts[@]}" -machine pseries,usb=off ) |
1975 | archopts=( "${archopts[@]}" -device spapr-vscsi ) |
1976 | ;; |
1977 | - *) kvm=qemu-system-$(uname -r);; |
1978 | + *) kvm=qemu-system-$(uname -m);; |
1979 | esac |
1980 | - kvmcmd=( $kvm -enable-kvm ) |
1981 | + KVM="$kvm" |
1982 | + kvmcmd=( $kvm ) |
1983 | + if should_try_kvm; then |
1984 | + if [ ! -e /dev/kvm ]; then |
1985 | + # if /dev/kvm didn't exist, then try to make it so. |
1986 | + local mod="" |
1987 | + for mod in kvm-intel kvm-amd kvm-hv kvm-pv; do |
1988 | + modprobe --quiet $mod && [ -w /dev/kvm ] && |
1989 | + debug 1 "loaded module $mod and got kvm" && break |
1990 | + done |
1991 | + fi |
1992 | + if [ -w /dev/kvm ]; then |
1993 | + kvmcmd[${#kvmcmd[@]}]="-enable-kvm" |
1994 | + else |
1995 | + error "WARNING: no kvm support it seems." |
1996 | + fi |
1997 | + fi |
1998 | |
1999 | - local out="" fmt="" |
2000 | - for i in "${diskdevs[@]}"; do |
2001 | - if [ -f "$i" ]; then |
2002 | - out=$(LANG=C qemu-img info "$i") && |
2003 | + local bios_opts="" |
2004 | + if [ -n "$bios" ] && $uefi; then |
2005 | + error "--uefi (or --uefi-nvram) is incompatible with --bios" |
2006 | + return 1 |
2007 | + fi |
2008 | + get_bios_opts "$bios" "$uefi" "$uefi_nvram" || |
2009 | + { error "failed to get bios opts"; return 1; } |
2010 | + bios_opts=( "${_RET[@]}" ) |
2011 | + |
2012 | + local out="" fmt="" bus="" unit="" index="" serial="" driver="" devopts="" |
2013 | + local busorindex="" driveopts="" cur="" val="" file="" |
2014 | + for((i=0;i<${#diskdevs[@]};i++)); do |
2015 | + cur=${diskdevs[$i]} |
2016 | + IFS=","; set -- $cur; IFS="$oifs" |
2017 | + driver="" |
2018 | + id=$(printf "disk%02d" "$i") |
2019 | + file="" |
2020 | + fmt="" |
2021 | + bus="" |
2022 | + unit="" |
2023 | + index="" |
2024 | + serial="" |
2025 | + for tok in "$@"; do |
2026 | + [ "${tok#*=}" = "${tok}" -a -f "${tok}" -a -z "$file" ] && file="$tok" |
2027 | + val=${tok#*=} |
2028 | + case "$tok" in |
2029 | + driver=*) driver=$val;; |
2030 | + if=virtio) driver=virtio-blk;; |
2031 | + if=scsi) driver=scsi-hd;; |
2032 | + if=pflash) driver=;; |
2033 | + if=sd|if=mtd|floppy) fail "do not know what to do with $tok on $cur";; |
2034 | + id=*) id=$val;; |
2035 | + file=*) file=$val;; |
2036 | + fmt=*|format=*) fmt=$val;; |
2037 | + serial=*) serial=$val;; |
2038 | + bus=*) bus=$val;; |
2039 | + unit=*) unit=$val;; |
2040 | + index=*) index=$val;; |
2041 | + esac |
2042 | + done |
2043 | + [ -z "$file" ] && fail "did not read a file from $cur" |
2044 | + if [ -f "$file" -a -z "$fmt" ]; then |
2045 | + out=$(LANG=C qemu-img info "$file") && |
2046 | fmt=$(echo "$out" | awk '$0 ~ /^file format:/ { print $3 }') || |
2047 | - { error "failed to determine format of $i"; return 1; } |
2048 | + { error "failed to determine format of $file"; return 1; } |
2049 | + else |
2050 | + fmt=raw |
2051 | fi |
2052 | - diskargs[${#diskargs[@]}]="-drive"; |
2053 | - diskargs[${#diskargs[@]}]="if=${def_diskif},file=$i,format=$fmt"; |
2054 | + if [ -z "$driver" ]; then |
2055 | + driver="$def_disk_driver" |
2056 | + fi |
2057 | + if [ -z "$serial" ]; then |
2058 | + serial="${file##*/}" |
2059 | + fi |
2060 | + |
2061 | + # make sure we add either bus= or index= |
2062 | + if [ -n "$bus" -o "$unit" ] && [ -n "$index" ]; then |
2063 | + fail "bus and index cant be specified together: $cur" |
2064 | + elif [ -z "$bus" -a -z "$unit" -a -z "$index" ]; then |
2065 | + index=$i |
2066 | + elif [ -n "$bus" -a -z "$unit" ]; then |
2067 | + unit=$i |
2068 | + fi |
2069 | + |
2070 | + busorindex="${bus:+bus=$bus,unit=$unit}${index:+index=${index}}" |
2071 | + diskopts="file=${file},id=$id,if=none,format=$fmt,$busorindex" |
2072 | + devopts="$driver,drive=$id${serial:+,serial=${serial}}" |
2073 | + for tok in "$@"; do |
2074 | + case "$tok" in |
2075 | + id=*|if=*|driver=*|$file|file=*) continue;; |
2076 | + fmt=*|format=*) continue;; |
2077 | + serial=*|bus=*|unit=*|index=*) continue;; |
2078 | + esac |
2079 | + isdevopt "$driver" "$tok" && devopts="${devopts},$tok" || |
2080 | + diskopts="${diskopts},${tok}" |
2081 | + done |
2082 | + |
2083 | + diskargs=( "${diskargs[@]}" -drive "$diskopts" -device "$devopts" ) |
2084 | done |
2085 | |
2086 | local mnics_vflag="" |
2087 | @@ -316,7 +525,7 @@ main() { |
2088 | # now go through and split out options |
2089 | # -device virtio-net-pci,netdev=virtnet0,mac=52:54:31:15:63:02 |
2090 | # -netdev type=tap,id=virtnet0,vhost=on,script=/etc/kvm/kvm-ifup.br0,downscript=no |
2091 | - local oifs="$IFS" netopts="" devopts="" id="" need_taps=0 model="" |
2092 | + local netopts="" devopts="" id="" need_taps=0 model="" |
2093 | local device_args netdev_args |
2094 | device_args=( ) |
2095 | netdev_args=( ) |
2096 | @@ -403,6 +612,15 @@ main() { |
2097 | |
2098 | if [ $need_taps -ne 0 ]; then |
2099 | local missing="" missing_pkgs="" reqs="" req="" pkgs="" pkg="" |
2100 | + for i in "${connections[@]}"; do |
2101 | + [ "$i" = "user" -o -e "/sys/class/net/$i" ] || |
2102 | + missing="${missing} $i" |
2103 | + done |
2104 | + [ -z "$missing" ] || { |
2105 | + error "cannot create connection on ${missing# }." |
2106 | + error "bridges do not exist."; |
2107 | + return 1; |
2108 | + } |
2109 | error "creating tap devices: ${connections[*]}" |
2110 | if $DRY_RUN; then |
2111 | error "sudo $0 tap-control make-nics" \ |
2112 | @@ -444,15 +662,21 @@ main() { |
2113 | -netdev "${netdev_args[$i]}") |
2114 | done |
2115 | |
2116 | - cmd=( "${kvmcmd[@]}" "${archopts[@]}" "${netargs[@]}" |
2117 | - "${diskargs[@]}" "${pt[@]}" ) |
2118 | - error "${cmd[@]}" |
2119 | + local bus_devices |
2120 | + bus_devices=( -device "$virtio_scsi_bus,id=virtio-scsi-xkvm" ) |
2121 | + cmd=( "${kvmcmd[@]}" "${archopts[@]}" |
2122 | + "${bios_opts[@]}" |
2123 | + "${bus_devices[@]}" |
2124 | + "${netargs[@]}" |
2125 | + "${diskargs[@]}" "${pt[@]}" ) |
2126 | + local pcmd=$(quote_cmd "${cmd[@]}") |
2127 | + error "$pcmd" |
2128 | ${DRY_RUN} && return 0 |
2129 | |
2130 | if $dowait; then |
2131 | "${cmd[@]}" & |
2132 | KVM_PID=$! |
2133 | - debug 1 "kvm pid=$KVM_PID. my pid=$$" |
2134 | + debug 1 "kvm pid=$KVM_PID. my pid=$$. ppid=$PPID." |
2135 | wait |
2136 | ret=$? |
2137 | KVM_PID="" |