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 | 1051 | 'ext2': ['e2fsprogs'], | 1051 | 'ext2': ['e2fsprogs'], |
7 | 1052 | 'ext3': ['e2fsprogs'], | 1052 | 'ext3': ['e2fsprogs'], |
8 | 1053 | 'ext4': ['e2fsprogs'], | 1053 | 'ext4': ['e2fsprogs'], |
9 | 1054 | 'jfs': ['jfsutils'], | ||
10 | 1054 | 'lvm_partition': ['lvm2'], | 1055 | 'lvm_partition': ['lvm2'], |
11 | 1055 | 'lvm_volgroup': ['lvm2'], | 1056 | 'lvm_volgroup': ['lvm2'], |
12 | 1057 | 'ntfs': ['ntfs-3g'], | ||
13 | 1056 | 'raid': ['mdadm'], | 1058 | 'raid': ['mdadm'], |
14 | 1059 | 'reiserfs': ['reiserfsprogs'], | ||
15 | 1057 | 'xfs': ['xfsprogs'], | 1060 | 'xfs': ['xfsprogs'], |
16 | 1061 | 'zfsroot': ['zfsutils-linux', 'zfs-initramfs'], | ||
17 | 1058 | 'zfs': ['zfsutils-linux', 'zfs-initramfs'], | 1062 | 'zfs': ['zfsutils-linux', 'zfs-initramfs'], |
18 | 1059 | 'zpool': ['zfsutils-linux', 'zfs-initramfs'], | 1063 | 'zpool': ['zfsutils-linux', 'zfs-initramfs'], |
19 | 1060 | 'zfsroot': ['zfsutils-linux', 'zfs-initramfs'], | ||
20 | 1061 | }, | 1064 | }, |
21 | 1062 | }, | 1065 | }, |
22 | 1063 | } | 1066 | } |
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 | 54 | "force": {"btrfs": "--force", | 54 | "force": {"btrfs": "--force", |
29 | 55 | "ext": "-F", | 55 | "ext": "-F", |
30 | 56 | "fat": "-I", | 56 | "fat": "-I", |
31 | 57 | "jfs": "-q", | ||
32 | 57 | "ntfs": "--force", | 58 | "ntfs": "--force", |
33 | 58 | "reiserfs": "-f", | 59 | "reiserfs": "-f", |
34 | 59 | "swap": "--force", | 60 | "swap": "--force", |
35 | diff --git a/examples/tests/filesystem_battery.yaml b/examples/tests/filesystem_battery.yaml | |||
36 | 60 | new file mode 100644 | 61 | new file mode 100644 |
37 | index 0000000..ba4fcac | |||
38 | --- /dev/null | |||
39 | +++ b/examples/tests/filesystem_battery.yaml | |||
40 | @@ -0,0 +1,101 @@ | |||
41 | 1 | showtrace: true | ||
42 | 2 | early_commands: | ||
43 | 3 | "00": [apt-get, update, -qy] | ||
44 | 4 | "01": [apt-get, install, -qy, --no-install-recommends, | ||
45 | 5 | ntfs-3g, jfsutils, reiserfsprogs] | ||
46 | 6 | storage: | ||
47 | 7 | version: 1 | ||
48 | 8 | config: | ||
49 | 9 | - id: disk1 | ||
50 | 10 | type: disk | ||
51 | 11 | ptable: msdos | ||
52 | 12 | model: QEMU HARDDISK | ||
53 | 13 | serial: disk-a | ||
54 | 14 | wipe: superblock | ||
55 | 15 | grub_device: true | ||
56 | 16 | - id: disk1p1 | ||
57 | 17 | type: partition | ||
58 | 18 | number: 1 | ||
59 | 19 | size: 3GB | ||
60 | 20 | device: disk1 | ||
61 | 21 | flag: boot | ||
62 | 22 | - id: disk1p1_fs | ||
63 | 23 | type: format | ||
64 | 24 | fstype: ext4 | ||
65 | 25 | volume: disk1p1 | ||
66 | 26 | label: 'cloudimg-rootfs' | ||
67 | 27 | - id: disk1p1_mount | ||
68 | 28 | type: mount | ||
69 | 29 | path: / | ||
70 | 30 | device: disk1p1_fs | ||
71 | 31 | - id: disk2 | ||
72 | 32 | type: disk | ||
73 | 33 | serial: fsbattery | ||
74 | 34 | wipe: superblock | ||
75 | 35 | ptable: gpt | ||
76 | 36 | - {id: d2p01, number: 1, device: disk2, type: partition, size: 500M} | ||
77 | 37 | - {id: d2p02, number: 2, device: disk2, type: partition, size: 500M} | ||
78 | 38 | - {id: d2p03, number: 3, device: disk2, type: partition, size: 500M} | ||
79 | 39 | - {id: d2p04, number: 4, device: disk2, type: partition, size: 500M} | ||
80 | 40 | - {id: d2p05, number: 5, device: disk2, type: partition, size: 500M} | ||
81 | 41 | - {id: d2p06, number: 6, device: disk2, type: partition, size: 500M} | ||
82 | 42 | - {id: d2p07, number: 7, device: disk2, type: partition, size: 500M} | ||
83 | 43 | - {id: d2p08, number: 8, device: disk2, type: partition, size: 500M} | ||
84 | 44 | - {id: d2p09, number: 9, device: disk2, type: partition, size: 500M} | ||
85 | 45 | - {id: d2p10, number: 10, device: disk2, type: partition, size: 500M} | ||
86 | 46 | - id: fs01 | ||
87 | 47 | type: format | ||
88 | 48 | fstype: btrfs | ||
89 | 49 | label: mybtrfs | ||
90 | 50 | volume: d2p01 | ||
91 | 51 | uuid: 8946d6ad-1e5f-4609-924c-4a39b6b561c9 | ||
92 | 52 | - id: fs02 | ||
93 | 53 | type: format | ||
94 | 54 | fstype: ext2 | ||
95 | 55 | label: myext2 | ||
96 | 56 | volume: d2p02 | ||
97 | 57 | uuid: 5d60e5e8-0c41-11e8-a664-525400123456 | ||
98 | 58 | - id: fs03 | ||
99 | 59 | type: format | ||
100 | 60 | fstype: ext3 | ||
101 | 61 | label: myext3 | ||
102 | 62 | volume: d2p03 | ||
103 | 63 | uuid: 5d7f4d30-0c41-11e8-a664-525400123456 | ||
104 | 64 | - id: fs04 | ||
105 | 65 | type: format | ||
106 | 66 | fstype: ext4 | ||
107 | 67 | label: myext4 | ||
108 | 68 | volume: d2p04 | ||
109 | 69 | uuid: 5da136b6-0c41-11e8-a664-525400123456 | ||
110 | 70 | - id: fs05 | ||
111 | 71 | type: format | ||
112 | 72 | fstype: fat16 | ||
113 | 73 | label: myvfat16 | ||
114 | 74 | volume: d2p05 | ||
115 | 75 | - id: fs06 | ||
116 | 76 | type: format | ||
117 | 77 | fstype: fat32 | ||
118 | 78 | label: myvfat32 | ||
119 | 79 | volume: d2p06 | ||
120 | 80 | - id: fs07 | ||
121 | 81 | type: format | ||
122 | 82 | fstype: jfs | ||
123 | 83 | label: myjfs | ||
124 | 84 | volume: d2p07 | ||
125 | 85 | - id: fs08 | ||
126 | 86 | type: format | ||
127 | 87 | fstype: ntfs | ||
128 | 88 | label: myntfs | ||
129 | 89 | volume: d2p08 | ||
130 | 90 | - id: fs09 | ||
131 | 91 | type: format | ||
132 | 92 | fstype: reiserfs | ||
133 | 93 | label: myreiserfs | ||
134 | 94 | volume: d2p09 | ||
135 | 95 | uuid: 5ed8308e-0c41-11e8-a664-525400123456 | ||
136 | 96 | - id: fs10 | ||
137 | 97 | type: format | ||
138 | 98 | fstype: xfs | ||
139 | 99 | label: myxfs | ||
140 | 100 | volume: d2p10 | ||
141 | 101 | uuid: 9c537621-f2f4-4e24-a071-e05012a1a997 | ||
142 | diff --git a/tests/vmtests/test_fs_battery.py b/tests/vmtests/test_fs_battery.py | |||
143 | 0 | new file mode 100644 | 102 | 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 | 1 | # This file is part of curtin. See LICENSE file for copyright and license info. | ||
149 | 2 | |||
150 | 3 | from . import VMBaseClass | ||
151 | 4 | from .releases import base_vm_classes as relbase | ||
152 | 5 | |||
153 | 6 | from curtin import config | ||
154 | 7 | |||
155 | 8 | import os | ||
156 | 9 | import textwrap | ||
157 | 10 | |||
158 | 11 | |||
159 | 12 | def _parse_blkid_output(content): | ||
160 | 13 | """Parse the output of the 'blkid' calls in collect_script. | ||
161 | 14 | |||
162 | 15 | Input is groups of lines. Each line is key=value. Each group | ||
163 | 16 | has the first line with key DEVNAME and last line key RESULT. | ||
164 | 17 | |||
165 | 18 | returned value is a dictionary by shortened devname like:. | ||
166 | 19 | {'part1': {'devname': 'part1', 'label': '...'}}""" | ||
167 | 20 | def _record(lines): | ||
168 | 21 | record = {} | ||
169 | 22 | for line in lines: | ||
170 | 23 | key, _, val = line.partition("=") | ||
171 | 24 | if key == 'DEVNAME': | ||
172 | 25 | bname = os.path.basename(val) | ||
173 | 26 | # bname is 'virtio-fsbattery-partX'. get just 'partX' | ||
174 | 27 | record[key.lower()] = bname.rpartition("-")[2] | ||
175 | 28 | elif key in ('RESULT', 'LABEL', 'UUID', 'TYPE'): | ||
176 | 29 | record[key.lower()] = val | ||
177 | 30 | return record | ||
178 | 31 | |||
179 | 32 | lines = [] | ||
180 | 33 | records = {} | ||
181 | 34 | for line in content.splitlines(): | ||
182 | 35 | lines.append(line) | ||
183 | 36 | if line.startswith("RESULT"): | ||
184 | 37 | r = _record(lines) | ||
185 | 38 | records[r['devname']] = r | ||
186 | 39 | lines = [] | ||
187 | 40 | |||
188 | 41 | return records | ||
189 | 42 | |||
190 | 43 | |||
191 | 44 | class TestFsBattery(VMBaseClass): | ||
192 | 45 | interactive = False | ||
193 | 46 | conf_file = "examples/tests/filesystem_battery.yaml" | ||
194 | 47 | extra_disks = ['20G'] | ||
195 | 48 | collect_scripts = VMBaseClass.collect_scripts + [textwrap.dedent(""" | ||
196 | 49 | cd OUTPUT_COLLECT_D | ||
197 | 50 | blkid -o export > blkid.out | ||
198 | 51 | cat /proc/mounts > proc_mounts | ||
199 | 52 | cat /proc/partitions > proc_partitions | ||
200 | 53 | find /etc/network/interfaces.d > find_interfacesd | ||
201 | 54 | cat /proc/cmdline > cmdline | ||
202 | 55 | |||
203 | 56 | set +x | ||
204 | 57 | serial="fsbattery" | ||
205 | 58 | disk=$(echo /dev/disk/by-id/*-$serial) | ||
206 | 59 | [ -b "$disk" ] || { echo "No disk with serial $serial." exit 1; } | ||
207 | 60 | |||
208 | 61 | # not all blkid versions output DEVNAME, so do it ourselves. | ||
209 | 62 | blkid -o export "$disk" | grep -q DEVNAME= && | ||
210 | 63 | hasdev=true || hasdev=false | ||
211 | 64 | for d in $disk-part*; do | ||
212 | 65 | $hasdev || echo DEVNAME=$d | ||
213 | 66 | blkid -o export "$d" | ||
214 | 67 | echo RESULT=$? | ||
215 | 68 | done > battery-blkid | ||
216 | 69 | |||
217 | 70 | mpbase=/tmp/mp; | ||
218 | 71 | mkdir -p /tmp/mp | ||
219 | 72 | for d in $disk-part*; do | ||
220 | 73 | fstype=$(blkid -o export "$d" | | ||
221 | 74 | awk -F= '$1 == "TYPE" { print $2 }') | ||
222 | 75 | if [ -z "$fstype" ]; then | ||
223 | 76 | msg="FAIL: blkid did not identify fstype" | ||
224 | 77 | else | ||
225 | 78 | mp="$mpbase/${d##*-}" | ||
226 | 79 | mkdir "$mp" | ||
227 | 80 | echo "${d##*-} $fstype" > "$mp.info" | ||
228 | 81 | if out=$(mount -t "$fstype" "$d" "$mp" 2>&1); then | ||
229 | 82 | msg="PASS" | ||
230 | 83 | else | ||
231 | 84 | rm -Rf "$mp.info" "$mp" | ||
232 | 85 | msg="FAIL: mount $fstype failed $?: $out" | ||
233 | 86 | fi | ||
234 | 87 | fi | ||
235 | 88 | echo "${d##*-} mount: $msg" | ||
236 | 89 | done > battery-mount-umount | ||
237 | 90 | |||
238 | 91 | awk '$5 ~ mp { print $0 }' "mp=$mpbase/" \ | ||
239 | 92 | /proc/1/mountinfo > battery-mountinfo | ||
240 | 93 | |||
241 | 94 | for info in $mpbase/*.info; do | ||
242 | 95 | read part fstype < "$info" | ||
243 | 96 | mp="${info%.info}" | ||
244 | 97 | out=$(umount "$mp" 2>&1) && | ||
245 | 98 | echo "$part umount: PASS" || | ||
246 | 99 | echo "$part umount: FAIL: $out" | ||
247 | 100 | done >> battery-mount-umount | ||
248 | 101 | """)] | ||
249 | 102 | |||
250 | 103 | def get_fs_entries(self): | ||
251 | 104 | """Return a dictionary of fs entires in config by 'partX'.""" | ||
252 | 105 | stgcfg = config.load_config(self.conf_file)['storage']['config'] | ||
253 | 106 | fs_entries = {} | ||
254 | 107 | for entry in stgcfg: | ||
255 | 108 | if not entry['id'].startswith("fs"): | ||
256 | 109 | continue | ||
257 | 110 | part = "part%d" % int(entry['id'][2:]) | ||
258 | 111 | fs_entries[part] = entry.copy() | ||
259 | 112 | return fs_entries | ||
260 | 113 | |||
261 | 114 | def test_blkid_output(self): | ||
262 | 115 | """Check the recorded output of 'blkid -o export' on each partition. | ||
263 | 116 | |||
264 | 117 | parse parse the 'battery-blkid' collected file, and compare it | ||
265 | 118 | to expected output from reading the storage config.""" | ||
266 | 119 | results = _parse_blkid_output(self.load_collect_file("battery-blkid")) | ||
267 | 120 | |||
268 | 121 | # tools for these types do not support providing uuid. | ||
269 | 122 | no_uuid_types = ['vfat', 'jfs', 'fat16', 'fat32', 'ntfs'] | ||
270 | 123 | if self.release in ('trusty'): | ||
271 | 124 | no_uuid_types += ['btrfs', 'xfs'] | ||
272 | 125 | |||
273 | 126 | for k, v in results.items(): | ||
274 | 127 | if v['type'] in no_uuid_types: | ||
275 | 128 | del v['uuid'] | ||
276 | 129 | |||
277 | 130 | # these curtin "types" show in blkid output differently. | ||
278 | 131 | type2blkid = {'fat32': 'vfat', 'fat16': 'vfat'} | ||
279 | 132 | expected = {} | ||
280 | 133 | for part, entry in self.get_fs_entries().items(): | ||
281 | 134 | record = { | ||
282 | 135 | 'devname': part, | ||
283 | 136 | 'label': entry['label'], | ||
284 | 137 | 'type': type2blkid.get(entry['fstype'], entry['fstype']), | ||
285 | 138 | 'result': "0", | ||
286 | 139 | } | ||
287 | 140 | if 'uuid' in entry and record['type'] not in no_uuid_types: | ||
288 | 141 | record['uuid'] = entry['uuid'] | ||
289 | 142 | expected[record['devname']] = record | ||
290 | 143 | |||
291 | 144 | self.assertEqual(expected, results) | ||
292 | 145 | |||
293 | 146 | def test_mount_umount(self): | ||
294 | 147 | """Check output of mount and unmount operations for each fs.""" | ||
295 | 148 | results = self.load_collect_file("battery-mount-umount").splitlines() | ||
296 | 149 | entries = self.get_fs_entries() | ||
297 | 150 | expected = (["%s mount: PASS" % k for k in entries] + | ||
298 | 151 | ["%s umount: PASS" % k for k in entries]) | ||
299 | 152 | self.assertEqual(sorted(expected), sorted(results)) | ||
300 | 153 | |||
301 | 154 | |||
302 | 155 | class TrustyTestFsBattery(relbase.trusty, TestFsBattery): | ||
303 | 156 | __test__ = True | ||
304 | 157 | |||
305 | 158 | |||
306 | 159 | class TrustyHWEXTestFsBattery(relbase.trusty_hwe_x, TestFsBattery): | ||
307 | 160 | __test__ = True | ||
308 | 161 | |||
309 | 162 | |||
310 | 163 | class XenialGATestFsBattery(relbase.xenial_ga, TestFsBattery): | ||
311 | 164 | __test__ = True | ||
312 | 165 | |||
313 | 166 | |||
314 | 167 | class XenialHWETestFsBattery(relbase.xenial_hwe, TestFsBattery): | ||
315 | 168 | __test__ = True | ||
316 | 169 | |||
317 | 170 | |||
318 | 171 | class XenialEdgeTestFsBattery(relbase.xenial_edge, TestFsBattery): | ||
319 | 172 | __test__ = True | ||
320 | 173 | |||
321 | 174 | |||
322 | 175 | class BionicTestFsBattery(relbase.bionic, TestFsBattery): | ||
323 | 176 | __test__ = True | ||
324 | 177 | |||
325 | 178 | |||
326 | 179 | # 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.