Merge ~smoser/curtin:feature/add-fstest-battery into curtin:master
- Git
- lp:~smoser/curtin
- feature/add-fstest-battery
- Merge into master
Status: | Merged |
---|---|
Approved by: | Scott Moser |
Approved revision: | ece93d5d40c56f5e07c3575f134de0e9ae915330 |
Merge reported by: | Scott Moser |
Merged at revision: | 5b7ca31e32eb47eedf2862e7b323dbb9ecb2a781 |
Proposed branch: | ~smoser/curtin:feature/add-fstest-battery |
Merge into: | curtin:master |
Diff against target: |
326 lines (+285/-1) 4 files modified
curtin/block/__init__.py (+4/-1) curtin/block/mkfs.py (+1/-0) examples/tests/filesystem_battery.yaml (+101/-0) tests/vmtests/test_fs_battery.py (+179/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Server Team CI bot | continuous-integration | Approve | |
Ryan Harper (community) | Approve | ||
Review via email: mp+337244@code.launchpad.net |
Commit message
vmtest: Add Filesystem Battery test.
This test exercises filesystem creation, mount and unmount of all
supported filesystem types.
Also here is a fix for jfs filesystem creation. mkfs_jfs requires a '-q'
argument to force/quiet. Otherwise it will prompt to ask if you
really want to do that.
Description of the change
see commit message
Scott Moser (smoser) wrote : | # |
Ryan Harper (raharper) : | # |
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:a6d0ba0f13d
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
FAILURE: https:/
Click here to trigger a rebuild:
https:/
Scott Moser (smoser) : | # |
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:a6d0ba0f13d
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:22bef4f07c3
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Ryan Harper (raharper) wrote : | # |
And it all passes? Nice work. One comment on where to store the blkid output parser.
Scott Moser (smoser) wrote : | # |
its not completely generic blkid parser as it is.
but yes, i thought about putting that into the base class and just always collecting that info.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:d4f0600d637
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Ryan Harper (raharper) wrote : | # |
Let's confirm this passed on Jenkins and I'm +1
Scott Moser (smoser) wrote : | # |
On diglet, I did:
git
$ git rev-parse HEAD
ece93d5d40c56f5
# full output: http://
$ ./tools/
CURTIN_
CURTIN_
CURTIN_
CURTIN_
CURTIN_
CURTIN_
CURTIN_
CURTIN_
CURTIN_
TGT_IPC_
TGT_LOG_
TGT_PID=17545
TGT_PORTAL=
http_proxy=
https_proxy=
no_proxy=
Quering synced ephemeral images/kernels in /srv/images
=======
Release Codename ImageDate Arch/SubArch Path
-------
12.04 precise 20170424 amd64/hwe-t precise/
14.04 trusty 20180302 amd64/hwe-t trusty/
14.04 trusty 20180302 amd64/hwe-x trusty/
16.04 xenial 20180306 amd64/ga-16.04 xenial/
16.04 xenial 20180306 amd64/hwe-16.04 xenial/
16.04 xenial 20180306 amd64/hwe-
17.04 zesty 20171219 amd64/ga-17.04 zesty/amd64/
17.10 artful 20180303 amd64/ga-17.10 artful/
18.04 bionic 20180224 amd64/ga-18.04 bionic/
=======
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:ece93d5d40c
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Preview Diff
1 | diff --git a/curtin/block/__init__.py b/curtin/block/__init__.py |
2 | index a82ed57..50e953e 100644 |
3 | --- a/curtin/block/__init__.py |
4 | +++ b/curtin/block/__init__.py |
5 | @@ -1051,13 +1051,16 @@ def detect_required_packages_mapping(): |
6 | 'ext2': ['e2fsprogs'], |
7 | 'ext3': ['e2fsprogs'], |
8 | 'ext4': ['e2fsprogs'], |
9 | + 'jfs': ['jfsutils'], |
10 | 'lvm_partition': ['lvm2'], |
11 | 'lvm_volgroup': ['lvm2'], |
12 | + 'ntfs': ['ntfs-3g'], |
13 | 'raid': ['mdadm'], |
14 | + 'reiserfs': ['reiserfsprogs'], |
15 | 'xfs': ['xfsprogs'], |
16 | + 'zfsroot': ['zfsutils-linux', 'zfs-initramfs'], |
17 | 'zfs': ['zfsutils-linux', 'zfs-initramfs'], |
18 | 'zpool': ['zfsutils-linux', 'zfs-initramfs'], |
19 | - 'zfsroot': ['zfsutils-linux', 'zfs-initramfs'], |
20 | }, |
21 | }, |
22 | } |
23 | diff --git a/curtin/block/mkfs.py b/curtin/block/mkfs.py |
24 | index 5cdb87c..a199d05 100644 |
25 | --- a/curtin/block/mkfs.py |
26 | +++ b/curtin/block/mkfs.py |
27 | @@ -54,6 +54,7 @@ family_flag_mappings = { |
28 | "force": {"btrfs": "--force", |
29 | "ext": "-F", |
30 | "fat": "-I", |
31 | + "jfs": "-q", |
32 | "ntfs": "--force", |
33 | "reiserfs": "-f", |
34 | "swap": "--force", |
35 | diff --git a/examples/tests/filesystem_battery.yaml b/examples/tests/filesystem_battery.yaml |
36 | new file mode 100644 |
37 | index 0000000..ba4fcac |
38 | --- /dev/null |
39 | +++ b/examples/tests/filesystem_battery.yaml |
40 | @@ -0,0 +1,101 @@ |
41 | +showtrace: true |
42 | +early_commands: |
43 | + "00": [apt-get, update, -qy] |
44 | + "01": [apt-get, install, -qy, --no-install-recommends, |
45 | + ntfs-3g, jfsutils, reiserfsprogs] |
46 | +storage: |
47 | + version: 1 |
48 | + config: |
49 | + - id: disk1 |
50 | + type: disk |
51 | + ptable: msdos |
52 | + model: QEMU HARDDISK |
53 | + serial: disk-a |
54 | + wipe: superblock |
55 | + grub_device: true |
56 | + - id: disk1p1 |
57 | + type: partition |
58 | + number: 1 |
59 | + size: 3GB |
60 | + device: disk1 |
61 | + flag: boot |
62 | + - id: disk1p1_fs |
63 | + type: format |
64 | + fstype: ext4 |
65 | + volume: disk1p1 |
66 | + label: 'cloudimg-rootfs' |
67 | + - id: disk1p1_mount |
68 | + type: mount |
69 | + path: / |
70 | + device: disk1p1_fs |
71 | + - id: disk2 |
72 | + type: disk |
73 | + serial: fsbattery |
74 | + wipe: superblock |
75 | + ptable: gpt |
76 | + - {id: d2p01, number: 1, device: disk2, type: partition, size: 500M} |
77 | + - {id: d2p02, number: 2, device: disk2, type: partition, size: 500M} |
78 | + - {id: d2p03, number: 3, device: disk2, type: partition, size: 500M} |
79 | + - {id: d2p04, number: 4, device: disk2, type: partition, size: 500M} |
80 | + - {id: d2p05, number: 5, device: disk2, type: partition, size: 500M} |
81 | + - {id: d2p06, number: 6, device: disk2, type: partition, size: 500M} |
82 | + - {id: d2p07, number: 7, device: disk2, type: partition, size: 500M} |
83 | + - {id: d2p08, number: 8, device: disk2, type: partition, size: 500M} |
84 | + - {id: d2p09, number: 9, device: disk2, type: partition, size: 500M} |
85 | + - {id: d2p10, number: 10, device: disk2, type: partition, size: 500M} |
86 | + - id: fs01 |
87 | + type: format |
88 | + fstype: btrfs |
89 | + label: mybtrfs |
90 | + volume: d2p01 |
91 | + uuid: 8946d6ad-1e5f-4609-924c-4a39b6b561c9 |
92 | + - id: fs02 |
93 | + type: format |
94 | + fstype: ext2 |
95 | + label: myext2 |
96 | + volume: d2p02 |
97 | + uuid: 5d60e5e8-0c41-11e8-a664-525400123456 |
98 | + - id: fs03 |
99 | + type: format |
100 | + fstype: ext3 |
101 | + label: myext3 |
102 | + volume: d2p03 |
103 | + uuid: 5d7f4d30-0c41-11e8-a664-525400123456 |
104 | + - id: fs04 |
105 | + type: format |
106 | + fstype: ext4 |
107 | + label: myext4 |
108 | + volume: d2p04 |
109 | + uuid: 5da136b6-0c41-11e8-a664-525400123456 |
110 | + - id: fs05 |
111 | + type: format |
112 | + fstype: fat16 |
113 | + label: myvfat16 |
114 | + volume: d2p05 |
115 | + - id: fs06 |
116 | + type: format |
117 | + fstype: fat32 |
118 | + label: myvfat32 |
119 | + volume: d2p06 |
120 | + - id: fs07 |
121 | + type: format |
122 | + fstype: jfs |
123 | + label: myjfs |
124 | + volume: d2p07 |
125 | + - id: fs08 |
126 | + type: format |
127 | + fstype: ntfs |
128 | + label: myntfs |
129 | + volume: d2p08 |
130 | + - id: fs09 |
131 | + type: format |
132 | + fstype: reiserfs |
133 | + label: myreiserfs |
134 | + volume: d2p09 |
135 | + uuid: 5ed8308e-0c41-11e8-a664-525400123456 |
136 | + - id: fs10 |
137 | + type: format |
138 | + fstype: xfs |
139 | + label: myxfs |
140 | + volume: d2p10 |
141 | + uuid: 9c537621-f2f4-4e24-a071-e05012a1a997 |
142 | diff --git a/tests/vmtests/test_fs_battery.py b/tests/vmtests/test_fs_battery.py |
143 | new file mode 100644 |
144 | index 0000000..5798d48 |
145 | --- /dev/null |
146 | +++ b/tests/vmtests/test_fs_battery.py |
147 | @@ -0,0 +1,179 @@ |
148 | +# This file is part of curtin. See LICENSE file for copyright and license info. |
149 | + |
150 | +from . import VMBaseClass |
151 | +from .releases import base_vm_classes as relbase |
152 | + |
153 | +from curtin import config |
154 | + |
155 | +import os |
156 | +import textwrap |
157 | + |
158 | + |
159 | +def _parse_blkid_output(content): |
160 | + """Parse the output of the 'blkid' calls in collect_script. |
161 | + |
162 | + Input is groups of lines. Each line is key=value. Each group |
163 | + has the first line with key DEVNAME and last line key RESULT. |
164 | + |
165 | + returned value is a dictionary by shortened devname like:. |
166 | + {'part1': {'devname': 'part1', 'label': '...'}}""" |
167 | + def _record(lines): |
168 | + record = {} |
169 | + for line in lines: |
170 | + key, _, val = line.partition("=") |
171 | + if key == 'DEVNAME': |
172 | + bname = os.path.basename(val) |
173 | + # bname is 'virtio-fsbattery-partX'. get just 'partX' |
174 | + record[key.lower()] = bname.rpartition("-")[2] |
175 | + elif key in ('RESULT', 'LABEL', 'UUID', 'TYPE'): |
176 | + record[key.lower()] = val |
177 | + return record |
178 | + |
179 | + lines = [] |
180 | + records = {} |
181 | + for line in content.splitlines(): |
182 | + lines.append(line) |
183 | + if line.startswith("RESULT"): |
184 | + r = _record(lines) |
185 | + records[r['devname']] = r |
186 | + lines = [] |
187 | + |
188 | + return records |
189 | + |
190 | + |
191 | +class TestFsBattery(VMBaseClass): |
192 | + interactive = False |
193 | + conf_file = "examples/tests/filesystem_battery.yaml" |
194 | + extra_disks = ['20G'] |
195 | + collect_scripts = VMBaseClass.collect_scripts + [textwrap.dedent(""" |
196 | + cd OUTPUT_COLLECT_D |
197 | + blkid -o export > blkid.out |
198 | + cat /proc/mounts > proc_mounts |
199 | + cat /proc/partitions > proc_partitions |
200 | + find /etc/network/interfaces.d > find_interfacesd |
201 | + cat /proc/cmdline > cmdline |
202 | + |
203 | + set +x |
204 | + serial="fsbattery" |
205 | + disk=$(echo /dev/disk/by-id/*-$serial) |
206 | + [ -b "$disk" ] || { echo "No disk with serial $serial." exit 1; } |
207 | + |
208 | + # not all blkid versions output DEVNAME, so do it ourselves. |
209 | + blkid -o export "$disk" | grep -q DEVNAME= && |
210 | + hasdev=true || hasdev=false |
211 | + for d in $disk-part*; do |
212 | + $hasdev || echo DEVNAME=$d |
213 | + blkid -o export "$d" |
214 | + echo RESULT=$? |
215 | + done > battery-blkid |
216 | + |
217 | + mpbase=/tmp/mp; |
218 | + mkdir -p /tmp/mp |
219 | + for d in $disk-part*; do |
220 | + fstype=$(blkid -o export "$d" | |
221 | + awk -F= '$1 == "TYPE" { print $2 }') |
222 | + if [ -z "$fstype" ]; then |
223 | + msg="FAIL: blkid did not identify fstype" |
224 | + else |
225 | + mp="$mpbase/${d##*-}" |
226 | + mkdir "$mp" |
227 | + echo "${d##*-} $fstype" > "$mp.info" |
228 | + if out=$(mount -t "$fstype" "$d" "$mp" 2>&1); then |
229 | + msg="PASS" |
230 | + else |
231 | + rm -Rf "$mp.info" "$mp" |
232 | + msg="FAIL: mount $fstype failed $?: $out" |
233 | + fi |
234 | + fi |
235 | + echo "${d##*-} mount: $msg" |
236 | + done > battery-mount-umount |
237 | + |
238 | + awk '$5 ~ mp { print $0 }' "mp=$mpbase/" \ |
239 | + /proc/1/mountinfo > battery-mountinfo |
240 | + |
241 | + for info in $mpbase/*.info; do |
242 | + read part fstype < "$info" |
243 | + mp="${info%.info}" |
244 | + out=$(umount "$mp" 2>&1) && |
245 | + echo "$part umount: PASS" || |
246 | + echo "$part umount: FAIL: $out" |
247 | + done >> battery-mount-umount |
248 | + """)] |
249 | + |
250 | + def get_fs_entries(self): |
251 | + """Return a dictionary of fs entires in config by 'partX'.""" |
252 | + stgcfg = config.load_config(self.conf_file)['storage']['config'] |
253 | + fs_entries = {} |
254 | + for entry in stgcfg: |
255 | + if not entry['id'].startswith("fs"): |
256 | + continue |
257 | + part = "part%d" % int(entry['id'][2:]) |
258 | + fs_entries[part] = entry.copy() |
259 | + return fs_entries |
260 | + |
261 | + def test_blkid_output(self): |
262 | + """Check the recorded output of 'blkid -o export' on each partition. |
263 | + |
264 | + parse parse the 'battery-blkid' collected file, and compare it |
265 | + to expected output from reading the storage config.""" |
266 | + results = _parse_blkid_output(self.load_collect_file("battery-blkid")) |
267 | + |
268 | + # tools for these types do not support providing uuid. |
269 | + no_uuid_types = ['vfat', 'jfs', 'fat16', 'fat32', 'ntfs'] |
270 | + if self.release in ('trusty'): |
271 | + no_uuid_types += ['btrfs', 'xfs'] |
272 | + |
273 | + for k, v in results.items(): |
274 | + if v['type'] in no_uuid_types: |
275 | + del v['uuid'] |
276 | + |
277 | + # these curtin "types" show in blkid output differently. |
278 | + type2blkid = {'fat32': 'vfat', 'fat16': 'vfat'} |
279 | + expected = {} |
280 | + for part, entry in self.get_fs_entries().items(): |
281 | + record = { |
282 | + 'devname': part, |
283 | + 'label': entry['label'], |
284 | + 'type': type2blkid.get(entry['fstype'], entry['fstype']), |
285 | + 'result': "0", |
286 | + } |
287 | + if 'uuid' in entry and record['type'] not in no_uuid_types: |
288 | + record['uuid'] = entry['uuid'] |
289 | + expected[record['devname']] = record |
290 | + |
291 | + self.assertEqual(expected, results) |
292 | + |
293 | + def test_mount_umount(self): |
294 | + """Check output of mount and unmount operations for each fs.""" |
295 | + results = self.load_collect_file("battery-mount-umount").splitlines() |
296 | + entries = self.get_fs_entries() |
297 | + expected = (["%s mount: PASS" % k for k in entries] + |
298 | + ["%s umount: PASS" % k for k in entries]) |
299 | + self.assertEqual(sorted(expected), sorted(results)) |
300 | + |
301 | + |
302 | +class TrustyTestFsBattery(relbase.trusty, TestFsBattery): |
303 | + __test__ = True |
304 | + |
305 | + |
306 | +class TrustyHWEXTestFsBattery(relbase.trusty_hwe_x, TestFsBattery): |
307 | + __test__ = True |
308 | + |
309 | + |
310 | +class XenialGATestFsBattery(relbase.xenial_ga, TestFsBattery): |
311 | + __test__ = True |
312 | + |
313 | + |
314 | +class XenialHWETestFsBattery(relbase.xenial_hwe, TestFsBattery): |
315 | + __test__ = True |
316 | + |
317 | + |
318 | +class XenialEdgeTestFsBattery(relbase.xenial_edge, TestFsBattery): |
319 | + __test__ = True |
320 | + |
321 | + |
322 | +class BionicTestFsBattery(relbase.bionic, TestFsBattery): |
323 | + __test__ = True |
324 | + |
325 | + |
326 | +# vi: ts=4 expandtab syntax=python |
I still need to test that the things are mountable. So far I've only been through the installation of Bionic.