Merge lp:~wesley-wiedenmeier/curtin/mkfs into lp:~curtin-dev/curtin/trunk
- mkfs
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 343 |
Proposed branch: | lp:~wesley-wiedenmeier/curtin/mkfs |
Merge into: | lp:~curtin-dev/curtin/trunk |
Diff against target: |
588 lines (+363/-131) 5 files modified
curtin/block/mkfs.py (+197/-0) curtin/commands/block_meta.py (+3/-49) curtin/commands/mkfs.py (+20/-81) curtin/util.py (+9/-1) tests/unittests/test_block_mkfs.py (+134/-0) |
To merge this branch: | bzr merge lp:~wesley-wiedenmeier/curtin/mkfs |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ryan Harper (community) | Needs Fixing | ||
Review via email: mp+281529@code.launchpad.net |
Commit message
Description of the change
Move mkfs logic into block.mkfs to allow for more flexible support for different format types and unit tests
- 336. By Wesley Wiedenmeier
-
Comment typo fixes
- 337. By Wesley Wiedenmeier
-
Verify that path for curtin.
block.mkfs. mkfs is a valid block dev - 338. By Wesley Wiedenmeier
-
General cleanup, and comment addition
- 339. By Wesley Wiedenmeier
-
Renamed curtin.
block.mkfs. mkfs to curtin. block.mkfs. run_mkfs and changed import
in curtin.commands. mkfs to avoid name conflict between block.mkfs and
commands.mkfs - 340. By Wesley Wiedenmeier
-
Add check for precise + btrfs edge case in mkfs unit tests
- 341. By Wesley Wiedenmeier
-
Added negative unit tests for all errors that mkfs can raise
Scott Moser (smoser) wrote : | # |
This looks really good. Some comments in line.
One request is to make curtin/
So we'd then have:
curtin mkfs --label=FOO /dev/vdb1 ext4
I think I like that better than :
curtin mkfs /dev/vdb1 ext4 label=FOO uuid=BAR
but I'm not opposed to arguments for either.
- 342. By Wesley Wiedenmeier
-
Rename block.mkfs.run_mkfs to block.mkfs.mkfs, and use import as in
commands.mkfs to avoid conflict - 343. By Wesley Wiedenmeier
-
Switched to long form flags where available
- 344. By Wesley Wiedenmeier
-
Improve docstring
- 345. By Wesley Wiedenmeier
-
Check that mkfs command exists on system before trying to run it
- 346. By Wesley Wiedenmeier
-
Moved negative unit tests for block.mkfs into separate functions for better
readibility/more informative errors - 347. By Wesley Wiedenmeier
-
Do not add quiet flag by default, as command output is captured
- 348. By Wesley Wiedenmeier
-
Removed -q from expected flags
- 349. By Wesley Wiedenmeier
-
Added strict flag to control mkfs behavior when an invalid format flag is
specified or when label is too long- With strict=True, raise error if flag specified that is not supported by
current fs family, raise error if filesystem label is too long
- With strict=False, silently ignore flag if not supported by current fs
family (default behavior of previous code in block_meta) and truncate
filesystem label if it is too long (many of the mkfs commands do this
anyway)
Scott Moser (smoser) wrote : | # |
Looking again at this, it looks like you've addressed most of my concerns.
The remaining things I see are:
1.)
| please improve the docstring here to cover what is in 'flags' at least.
|
| Would it work to give this a signature like:
| run_mkfs(path, fstype, label=None, uuid=None, ...)
|
| then you could call run_mkfs from your 'mkfs_from_config' via:
| flags = {i: info.get(i) for i in info if i in family_
| run_mkfs(dev, fstype, **flags)
2.) fix mkfs command to be user usable wrapper around mkfs()
curtin mkfs --label=FOO /dev/vdb1 ext4
I think I like that better than :
curtin mkfs /dev/vdb1 ext4 label=FOO uuid=BAR
- 350. By Wesley Wiedenmeier
-
Remove config parsing from curtin.
commands. mkfs and add flags to pass in label
and uuid - 351. By Wesley Wiedenmeier
-
Added uuid and label kwargs to mkfs.mkfs to simplify usage
- 352. By Wesley Wiedenmeier
-
Added force flag as kwarg in block.mkfs
- 353. By Wesley Wiedenmeier
-
Removed extra_flags parameter from block.mkfs and rewrote logic to append flags
- 354. By Wesley Wiedenmeier
-
Updated docstring for mkfs to remove explaination of extra_flags
- 355. By Wesley Wiedenmeier
-
Added strict flag to mkfs command to enforce filesystem label length limits and
flags not supported by some some filesystems - 356. By Wesley Wiedenmeier
-
Move check for btrfs+precise into mkfs
- 357. By Wesley Wiedenmeier
-
Sorted command tables in block.mkfs
Scott Moser (smoser) wrote : | # |
Hey.
One more thing on this. I just gave it a try.
$ rm -f /tmp/my.img; truncate --size 1G /tmp/my.img ; ./bin/curtin mkfs /tmp/my.img
invalid block dev path '/tmp/my.img'
I'd like that fixed, so I changed it to just verify that the path exists.
Then:
$ rm -f /tmp/my.img; truncate --size 1G /tmp/my.img ; ./bin/curtin mkfs /tmp/my.img
<hang>
I hit enter and I see:
Unexpected error while running command.
Command: ['mkfs.ext4', '/tmp/my.img']
Exit code: 1
Reason: -
Stdout: '/tmp/my.img is not a block special device.\nProceed anyway? (y,n) '
Stderr: 'mke2fs 1.42.9 (4-Feb-2014)\n'
It was expecting input from me but was hiding/capturing output.
We need to change the subp implementation to not use 'subprocess.pipe'.
Below is the patch for that. Then we get the same output as above, without the hang.. which is good.
=== modified file 'curtin/
--- curtin/
+++ curtin/
@@ -22,6 +22,7 @@
from curtin import block
import string
+import os
mkfs_commands = {
"btrfs": "mkfs.btrfs",
@@ -119,8 +120,10 @@
finds old data or filesystems on the partition.
"""
- if path is None or not block.is_
+ if path is None:
raise ValueError("invalid block dev path '%s'" % path)
+ if not os.path.
+ raise ValueError("'%s': no such file or directory" % path)
fs_family = specific_
mkfs_cmd = mkfs_commands.
=== modified file 'curtin/util.py'
--- curtin/util.py 2015-12-18 19:47:48 +0000
+++ curtin/util.py 2016-01-15 21:51:07 +0000
@@ -39,6 +39,7 @@
if rcs is None:
rcs = [0]
+ devnull_fp = None
try:
if not logstring:
@@ -52,7 +53,10 @@
if capture:
stdout = subprocess.PIPE
stderr = subprocess.PIPE
- if data is not None:
+ if data is None:
+ devnull_fp = open(os.devnull)
+ stdin = devnull_fp
+ else:
stdin = subprocess.PIPE
sp = subprocess.
@@ -74,6 +78,10 @@
err = ldecode(err)
except OSError as e:
raise ProcessExecutio
+ finally:
+ if devnull_fp:
+ devnull_fp.close()
+
rc = sp.returncode # pylint: disable=E1101
if rc not in rcs:
raise ProcessExecutio
Scott Moser (smoser) wrote : | # |
So that fix in pastebin is http://
The result then we can actually:
$ rm -f /tmp/my.img; truncate --size 1G /tmp/my.img ; ./bin/curtin mkfs --force /tmp/my.img
$ blkid /tmp/my.img
/tmp/my.img: UUID="3268e5cc-
- 358. By Wesley Wiedenmeier
-
Applied patch from http://
paste.ubuntu. com/14511520/ - will operate on non block devices such as disk images
- will not hang silently if waiting for confirmation - 359. By Wesley Wiedenmeier
-
Updated unit tests to mock os.path.exists instead of block.is_
valid_device - 360. By Wesley Wiedenmeier
-
block.mkfs returns uuid of created filesystem
- if no uuid is passed into command uuid.uuid1() is used to generate one
- if the mkfs command for the filesystem type does not support passing in uuid
then blkid will be used to determine uuid after fs is created
- if blkid fails no exceptions will be thrown as mkfs command did still
succeed - 361. By Wesley Wiedenmeier
-
Updated unit tests to verify that mkfs is generating a uuid and to avoid
potential errors from not mocking block - 362. By Wesley Wiedenmeier
-
Print message after creating filesystem in commands.mkfs
- 363. By Wesley Wiedenmeier
-
Added block.mkfs.
valid_fstypes( ) to list fstypes we know how to support - 364. By Wesley Wiedenmeier
-
Use 'choices': valid_fstypes() in commands.mkfs arguments so that help message
lists valid fstypes
Preview Diff
1 | === added file 'curtin/block/mkfs.py' |
2 | --- curtin/block/mkfs.py 1970-01-01 00:00:00 +0000 |
3 | +++ curtin/block/mkfs.py 2016-01-15 23:10:42 +0000 |
4 | @@ -0,0 +1,197 @@ |
5 | +# Copyright (C) 2016 Canonical Ltd. |
6 | +# |
7 | +# Author: Wesley Wiedenmeier <wesley.wiedenmeier@canonical.com> |
8 | +# |
9 | +# Curtin is free software: you can redistribute it and/or modify it under |
10 | +# the terms of the GNU Affero General Public License as published by the |
11 | +# Free Software Foundation, either version 3 of the License, or (at your |
12 | +# option) any later version. |
13 | +# |
14 | +# Curtin is distributed in the hope that it will be useful, but WITHOUT ANY |
15 | +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
16 | +# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for |
17 | +# more details. |
18 | +# |
19 | +# You should have received a copy of the GNU Affero General Public License |
20 | +# along with Curtin. If not, see <http://www.gnu.org/licenses/>. |
21 | + |
22 | +# This module wraps calls to mkfs.<fstype> and determines the appropriate flags |
23 | +# for each filesystem type |
24 | + |
25 | +from curtin import util |
26 | +from curtin import block |
27 | + |
28 | +import string |
29 | +import os |
30 | +from uuid import uuid1 |
31 | + |
32 | +mkfs_commands = { |
33 | + "btrfs": "mkfs.btrfs", |
34 | + "ext2": "mkfs.ext2", |
35 | + "ext3": "mkfs.ext3", |
36 | + "ext4": "mkfs.ext4", |
37 | + "fat": "mkfs.fat", |
38 | + "fat12": "mkfs.fat", |
39 | + "fat16": "mkfs.fat", |
40 | + "fat32": "mkfs.fat", |
41 | + "jfs": "jfs_mkfs", |
42 | + "ntfs": "mkntfs", |
43 | + "reiserfs": "mkfs.reiserfs", |
44 | + "swap": "mkswap", |
45 | + "xfs": "mkfs.xfs" |
46 | + } |
47 | + |
48 | +specific_to_family = { |
49 | + "ext2": "ext", |
50 | + "ext3": "ext", |
51 | + "ext4": "ext", |
52 | + "fat12": "fat", |
53 | + "fat16": "fat", |
54 | + "fat32": "fat", |
55 | + } |
56 | + |
57 | +label_length_limits = { |
58 | + "btrfs": 256, |
59 | + "ext": 16, |
60 | + "fat": 11, |
61 | + "jfs": 16, # see jfs_tune manpage |
62 | + "ntfs": 32, |
63 | + "reiserfs": 16, |
64 | + "swap": 15, # not in manpages, found experimentally |
65 | + "xfs": 12 |
66 | + } |
67 | + |
68 | +family_flag_mappings = { |
69 | + "label": {"btrfs": "--label", |
70 | + "ext": "-L", |
71 | + "fat": "-n", |
72 | + "jfs": "-L", |
73 | + "ntfs": "--label", |
74 | + "reiserfs": "--label", |
75 | + "swap": "--label", |
76 | + "xfs": "-L"}, |
77 | + "uuid": {"btrfs": "--uuid", |
78 | + "ext": "-U", |
79 | + "reiserfs": "--uuid", |
80 | + "swap": "--uuid"}, |
81 | + "force": {"btrfs": "--force", |
82 | + "ext": "-F", |
83 | + "ntfs": "--force", |
84 | + "reiserfs": "-f", |
85 | + "swap": "--force", |
86 | + "xfs": "-f"}, |
87 | + "fatsize": {"fat": "-F"}, |
88 | + "quiet": {"ext": "-q", |
89 | + "ntfs": "-q", |
90 | + "reiserfs": "-q", |
91 | + "xfs": "--quiet"} |
92 | + } |
93 | + |
94 | + |
95 | +def valid_fstypes(): |
96 | + return list(mkfs_commands.keys()) |
97 | + |
98 | + |
99 | +def get_flag_mapping(flag_name, fs_family, param=None, strict=False): |
100 | + ret = [] |
101 | + flag_sym_families = family_flag_mappings.get(flag_name) |
102 | + if flag_sym_families is None: |
103 | + raise ValueError("unsupported flag '%s'" % flag_name) |
104 | + flag_sym = flag_sym_families.get(fs_family) |
105 | + if flag_sym is None: |
106 | + if strict: |
107 | + raise ValueError("flag '%s' not supported by fs family '%s'" % |
108 | + flag_name, fs_family) |
109 | + else: |
110 | + ret = [flag_sym] |
111 | + if param is not None: |
112 | + ret.append(param) |
113 | + return ret |
114 | + |
115 | + |
116 | +def mkfs(path, fstype, strict=False, label=None, uuid=None, force=False): |
117 | + """Make filesystem on block device with given path using given fstype and |
118 | + appropriate flags for filesystem family. |
119 | + |
120 | + Filesystem uuid and label can be passed in as kwargs. By default no |
121 | + label or uuid will be used. If a filesystem label is too long curtin |
122 | + will raise a ValueError if the strict flag is true or will truncate |
123 | + it to the maximum possible length. |
124 | + |
125 | + If a flag is not supported by a filesystem family mkfs will raise a |
126 | + ValueError if the strict flag is true or silently ignore it otherwise. |
127 | + |
128 | + Force can be specified to force the mkfs command to continue even if it |
129 | + finds old data or filesystems on the partition. |
130 | + """ |
131 | + |
132 | + if path is None: |
133 | + raise ValueError("invalid block dev path '%s'" % path) |
134 | + if not os.path.exists(path): |
135 | + raise ValueError("'%s': no such file or directory" % path) |
136 | + |
137 | + fs_family = specific_to_family.get(fstype, fstype) |
138 | + mkfs_cmd = mkfs_commands.get(fstype) |
139 | + if not mkfs_cmd: |
140 | + raise ValueError("unsupported fs type '%s'" % fstype) |
141 | + |
142 | + if util.which(mkfs_cmd) is None: |
143 | + raise ValueError("need '%s' but it could not be found" % mkfs_cmd) |
144 | + |
145 | + cmd = [mkfs_cmd] |
146 | + |
147 | + if force: |
148 | + # On precise mkfs.btrfs does not have a force flag, though it does in |
149 | + # later releases |
150 | + if not (util.lsb_release()['codename'] == "precise" and fstype == |
151 | + "btrfs"): |
152 | + cmd.extend(get_flag_mapping("force", fs_family, strict=strict)) |
153 | + if label is not None: |
154 | + limit = label_length_limits.get(fs_family) |
155 | + if len(label) > limit: |
156 | + if strict: |
157 | + raise ValueError("length of fs label for '%s' exceeds max \ |
158 | + allowed for fstype '%s'. max is '%s'" |
159 | + % (path, fstype, limit)) |
160 | + else: |
161 | + label = label[:limit] |
162 | + cmd.extend(get_flag_mapping("label", fs_family, param=label, |
163 | + strict=strict)) |
164 | + |
165 | + # If uuid is not specified, generate one and try to use it |
166 | + if uuid is None: |
167 | + uuid = str(uuid1()) |
168 | + cmd.extend(get_flag_mapping("uuid", fs_family, param=uuid, strict=strict)) |
169 | + |
170 | + if fs_family == "fat": |
171 | + fat_size = fstype.strip(string.ascii_letters) |
172 | + if fat_size in ["12", "16", "32"]: |
173 | + cmd.extend(get_flag_mapping("fatsize", fs_family, param=fat_size, |
174 | + strict=strict)) |
175 | + |
176 | + cmd.append(path) |
177 | + util.subp(cmd, capture=True) |
178 | + |
179 | + # if fs_family does not support specifying uuid then use blkid to find it |
180 | + # if blkid is unable to then just return None for uuid |
181 | + if fs_family not in family_flag_mappings['uuid']: |
182 | + try: |
183 | + uuid = block.blkid()[path]['UUID'] |
184 | + except: |
185 | + pass |
186 | + |
187 | + # return uuid, may be none if it could not be specified and blkid could not |
188 | + # find it |
189 | + return uuid |
190 | + |
191 | + |
192 | +def mkfs_from_config(path, info, strict=False): |
193 | + """Make filesystem on block device with given path according to storage |
194 | + config given""" |
195 | + fstype = info.get('fstype') |
196 | + if fstype is None: |
197 | + raise ValueError("fstype must be specified") |
198 | + # NOTE: Since old metadata on partitions that have not been wiped can cause |
199 | + # some mkfs commands to refuse to work, it's best to use force=True |
200 | + mkfs(path, fstype, strict=strict, force=True, uuid=info.get('uuid'), |
201 | + label=info.get('label')) |
202 | |
203 | === modified file 'curtin/commands/block_meta.py' |
204 | --- curtin/commands/block_meta.py 2016-01-07 18:53:54 +0000 |
205 | +++ curtin/commands/block_meta.py 2016-01-15 23:10:42 +0000 |
206 | @@ -19,6 +19,7 @@ |
207 | from curtin import (block, config, util) |
208 | from curtin.block import mdadm |
209 | from curtin.log import LOG |
210 | +from curtin.block import mkfs |
211 | |
212 | from . import populate_one_subcmd |
213 | from curtin.udev import compose_udev_equality |
214 | @@ -26,7 +27,6 @@ |
215 | import glob |
216 | import os |
217 | import platform |
218 | -import string |
219 | import sys |
220 | import tempfile |
221 | import time |
222 | @@ -635,10 +635,7 @@ |
223 | |
224 | |
225 | def format_handler(info, storage_config): |
226 | - fstype = info.get('fstype') |
227 | volume = info.get('volume') |
228 | - part_label = info.get('label') |
229 | - uuid = info.get('uuid') |
230 | if not volume: |
231 | raise ValueError("volume must be specified for partition '%s'" % |
232 | info.get('id')) |
233 | @@ -651,51 +648,8 @@ |
234 | # Volume marked to be preserved, not formatting |
235 | return |
236 | |
237 | - # Generate mkfs command and run |
238 | - if fstype in ["ext4", "ext3"]: |
239 | - cmd = ['mkfs.%s' % fstype, '-F', '-q'] |
240 | - if part_label: |
241 | - if len(part_label) > 16: |
242 | - raise ValueError( |
243 | - "ext3/4 partition labels cannot be longer than " |
244 | - "16 characters") |
245 | - else: |
246 | - cmd.extend(["-L", part_label]) |
247 | - if uuid: |
248 | - cmd.extend(["-U", uuid]) |
249 | - cmd.append(volume_path) |
250 | - elif fstype in ["btrfs"]: |
251 | - cmd = ['mkfs.%s' % fstype] |
252 | - if util.lsb_release()['codename'] != "precise": |
253 | - cmd.extend(['-f']) |
254 | - if part_label: |
255 | - cmd.extend(["-L", part_label]) |
256 | - if uuid: |
257 | - cmd.extend(["-U", uuid]) |
258 | - cmd.append(volume_path) |
259 | - elif fstype in ["fat12", "fat16", "fat32", "fat"]: |
260 | - cmd = ["mkfs.fat"] |
261 | - fat_size = fstype.strip(string.ascii_letters) |
262 | - if fat_size in ["12", "16", "32"]: |
263 | - cmd.extend(["-F", fat_size]) |
264 | - if part_label: |
265 | - if len(part_label) > 11: |
266 | - raise ValueError( |
267 | - "fat partition names cannot be longer than " |
268 | - "11 characters") |
269 | - cmd.extend(["-n", part_label]) |
270 | - cmd.append(volume_path) |
271 | - elif fstype == "swap": |
272 | - cmd = ["mkswap", volume_path] |
273 | - else: |
274 | - # See if mkfs.<fstype> exists. If so try to run it. |
275 | - try: |
276 | - util.subp(["which", "mkfs.%s" % fstype]) |
277 | - cmd = ["mkfs.%s" % fstype, volume_path] |
278 | - except util.ProcessExecutionError: |
279 | - raise ValueError("fstype '%s' not supported" % fstype) |
280 | - LOG.info("formatting volume '%s' with format '%s'" % (volume_path, fstype)) |
281 | - logtime(' '.join(cmd), util.subp, cmd) |
282 | + # Make filesystem using block library |
283 | + mkfs.mkfs_from_config(volume_path, info) |
284 | |
285 | |
286 | def mount_handler(info, storage_config): |
287 | |
288 | === modified file 'curtin/commands/mkfs.py' |
289 | --- curtin/commands/mkfs.py 2015-11-18 17:15:08 +0000 |
290 | +++ curtin/commands/mkfs.py 2016-01-15 23:10:42 +0000 |
291 | @@ -15,18 +15,11 @@ |
292 | # You should have received a copy of the GNU Affero General Public License |
293 | # along with Curtin. If not, see <http://www.gnu.org/licenses/>. |
294 | |
295 | -import curtin.config |
296 | -from curtin.log import LOG |
297 | -from curtin import config |
298 | -from curtin import util |
299 | from . import populate_one_subcmd |
300 | -from curtin.commands.block_meta import get_path_to_storage_volume |
301 | - |
302 | -from collections import OrderedDict |
303 | - |
304 | -import os |
305 | +from curtin.block.mkfs import mkfs as run_mkfs |
306 | +from curtin.block.mkfs import valid_fstypes |
307 | + |
308 | import sys |
309 | -import string |
310 | |
311 | CMD_ARGUMENTS = ( |
312 | (('devices', |
313 | @@ -35,84 +28,30 @@ |
314 | 'metavar': 'DEVICE', 'action': 'store', 'nargs': '+'}), |
315 | (('-f', '--fstype'), |
316 | {'help': 'filesystem type to use. default is ext4', |
317 | + 'choices': valid_fstypes(), |
318 | 'default': 'ext4', 'action': 'store'}), |
319 | - (('-c', '--config'), |
320 | - {'help': 'read configuration from cfg', 'action': 'append', |
321 | - 'metavar': 'CONFIG', 'dest': 'cfgopts', 'default': []}) |
322 | + (('-l', '--label'), |
323 | + {'help': 'label to use for filesystem', 'action': 'store'}), |
324 | + (('-u', '--uuid'), |
325 | + {'help': 'uuid to use for filesystem', 'action': 'store'}), |
326 | + (('-s', '--strict'), |
327 | + {'help': 'exit if mkfs cannot do exactly what is specified', |
328 | + 'action': 'store_true', 'default': False}), |
329 | + (('-F', '--force'), |
330 | + {'help': 'continue if some data already exists on device', |
331 | + 'action': 'store_true', 'default': False}) |
332 | ) |
333 | ) |
334 | |
335 | |
336 | -def format_blockdev(volume_path, fstype, part_id=None): |
337 | - if not part_id: |
338 | - part_id = volume_path.split("/")[-1] |
339 | - |
340 | - # Generate mkfs command and run |
341 | - if fstype in ["ext4", "ext3"]: |
342 | - if len(part_id) > 16: |
343 | - raise ValueError("ext3/4 partition labels cannot be longer than \ |
344 | - 16 characters") |
345 | - cmd = ['mkfs.%s' % fstype, '-F', '-q', '-L', part_id, volume_path] |
346 | - elif fstype in ["fat12", "fat16", "fat32", "fat"]: |
347 | - cmd = ["mkfs.fat"] |
348 | - fat_size = fstype.strip(string.ascii_letters) |
349 | - if fat_size in ["12", "16", "32"]: |
350 | - cmd.extend(["-F", fat_size]) |
351 | - if len(part_id) > 11: |
352 | - raise ValueError("fat partition names cannot be longer than \ |
353 | - 11 characters") |
354 | - cmd.extend(["-n", part_id, volume_path]) |
355 | - else: |
356 | - # See if mkfs.<fstype> exists. If so try to run it. |
357 | - try: |
358 | - util.subp(["which", "mkfs.%s" % fstype]) |
359 | - cmd = ["mkfs.%s" % fstype, volume_path] |
360 | - except util.ProcessExecutionError: |
361 | - raise ValueError("fstype '%s' not supported" % fstype) |
362 | - LOG.info("formatting volume '%s' with format '%s'" % (volume_path, fstype)) |
363 | - util.subp(cmd) |
364 | - |
365 | - |
366 | -def format_storage_item(info, storage_config): |
367 | - fstype = info.get('fstype') |
368 | - volume = info.get('volume') |
369 | - part_id = info.get('id') |
370 | - if not volume: |
371 | - raise ValueError("volume must be specified for partition '%s'" % |
372 | - info.get('id')) |
373 | - |
374 | - # Get path to volume |
375 | - volume_path = get_path_to_storage_volume(volume, storage_config) |
376 | - |
377 | - # Call format_blockdev |
378 | - format_blockdev(volume_path, fstype, part_id=part_id) |
379 | - |
380 | - |
381 | def mkfs(args): |
382 | - state = util.load_command_environment() |
383 | - cfg = config.load_command_config(args, state) |
384 | - |
385 | - if args.cfgopts: |
386 | - for filename in args.cfgopts: |
387 | - curtin.config.merge_config_fp(cfg, open(filename)) |
388 | - |
389 | - if "storage" in cfg: |
390 | - storage_config = OrderedDict((d["id"], d) for (i, d) in |
391 | - enumerate(cfg.get("storage"))) |
392 | - else: |
393 | - storage_config = {} |
394 | - |
395 | for device in args.devices: |
396 | - if device in storage_config: |
397 | - # Device is in storage config |
398 | - format_storage_item(storage_config.get(device), storage_config) |
399 | - elif "/dev/" in device and os.path.exists(device): |
400 | - # Device is path to block dev |
401 | - format_blockdev(device, args.fstype) |
402 | - else: |
403 | - # Bad argument |
404 | - raise ValueError("device '%s' is neither an item in storage " |
405 | - "config nor a block device" % device) |
406 | + uuid = run_mkfs(device, args.fstype, strict=args.strict, |
407 | + uuid=args.uuid, label=args.label, |
408 | + force=args.force) |
409 | + |
410 | + print("Created '%s' filesystem in '%s' with uuid '%s' and label '%s'" % |
411 | + (args.fstype, device, uuid, args.label)) |
412 | |
413 | sys.exit(0) |
414 | |
415 | |
416 | === modified file 'curtin/util.py' |
417 | --- curtin/util.py 2015-12-18 19:47:48 +0000 |
418 | +++ curtin/util.py 2016-01-15 23:10:42 +0000 |
419 | @@ -39,6 +39,7 @@ |
420 | if rcs is None: |
421 | rcs = [0] |
422 | |
423 | + devnull_fp = None |
424 | try: |
425 | if not logstring: |
426 | LOG.debug(("Running command %s with allowed return codes %s" |
427 | @@ -52,7 +53,10 @@ |
428 | if capture: |
429 | stdout = subprocess.PIPE |
430 | stderr = subprocess.PIPE |
431 | - if data is not None: |
432 | + if data is None: |
433 | + devnull_fp = open(os.devnull) |
434 | + stdin = devnull_fp |
435 | + else: |
436 | stdin = subprocess.PIPE |
437 | sp = subprocess.Popen(args, stdout=stdout, |
438 | stderr=stderr, stdin=stdin, |
439 | @@ -74,6 +78,10 @@ |
440 | err = ldecode(err) |
441 | except OSError as e: |
442 | raise ProcessExecutionError(cmd=args, reason=e) |
443 | + finally: |
444 | + if devnull_fp: |
445 | + devnull_fp.close() |
446 | + |
447 | rc = sp.returncode # pylint: disable=E1101 |
448 | if rc not in rcs: |
449 | raise ProcessExecutionError(stdout=out, stderr=err, |
450 | |
451 | === added file 'tests/unittests/test_block_mkfs.py' |
452 | --- tests/unittests/test_block_mkfs.py 1970-01-01 00:00:00 +0000 |
453 | +++ tests/unittests/test_block_mkfs.py 2016-01-15 23:10:42 +0000 |
454 | @@ -0,0 +1,134 @@ |
455 | +from curtin.block import mkfs |
456 | + |
457 | +from unittest import TestCase |
458 | +import mock |
459 | + |
460 | + |
461 | +class TestBlockMkfs(TestCase): |
462 | + test_uuid = "fb26cc6c-ae73-11e5-9e38-2fb63f0c3155" |
463 | + |
464 | + def _get_config(self, fstype): |
465 | + return {"fstype": fstype, "type": "format", "id": "testfmt", |
466 | + "volume": "null", "label": "format1", |
467 | + "uuid": self.test_uuid} |
468 | + |
469 | + def _assert_same_flags(self, call, expected): |
470 | + for flag in expected: |
471 | + if type(flag) == list: |
472 | + flag_name = flag[0] |
473 | + flag_val = flag[1] |
474 | + self.assertIn(flag_name, call) |
475 | + flag_index = call.index(flag_name) |
476 | + self.assertTrue(len(call) > flag_index) |
477 | + self.assertEquals(call[flag_index + 1], flag_val) |
478 | + call.remove(flag_name) |
479 | + call.remove(flag_val) |
480 | + else: |
481 | + self.assertIn(flag, call) |
482 | + call.remove(flag) |
483 | + # Only remaining vals in call should be mkfs.fstype and dev path |
484 | + self.assertEquals(len(call), 2) |
485 | + |
486 | + @mock.patch("curtin.block.mkfs.block") |
487 | + @mock.patch("curtin.block.mkfs.os") |
488 | + @mock.patch("curtin.block.mkfs.util") |
489 | + def _run_mkfs_with_config(self, config, expected_cmd, expected_flags, |
490 | + mock_util, mock_os, mock_block, |
491 | + release="wily", strict=False): |
492 | + # Pretend we are on wily as there are no known edge cases for it |
493 | + mock_util.lsb_release.return_value = {"codename": release} |
494 | + mock_os.path.exists.return_value = True |
495 | + |
496 | + mkfs.mkfs_from_config("/dev/null", config, strict=strict) |
497 | + self.assertTrue(mock_util.subp.called) |
498 | + calls = mock_util.subp.call_args_list |
499 | + self.assertEquals(len(calls), 1) |
500 | + |
501 | + # Get first function call, tuple of first positional arg and its |
502 | + # (nonexistant) keyword arg, and unpack to get cmd |
503 | + call = calls[0][0][0] |
504 | + self.assertEquals(call[0], expected_cmd) |
505 | + self._assert_same_flags(call, expected_flags) |
506 | + |
507 | + def test_mkfs_ext(self): |
508 | + conf = self._get_config("ext4") |
509 | + expected_flags = [["-L", "format1"], "-F", |
510 | + ["-U", self.test_uuid]] |
511 | + self._run_mkfs_with_config(conf, "mkfs.ext4", expected_flags) |
512 | + |
513 | + def test_mkfs_btrfs(self): |
514 | + conf = self._get_config("btrfs") |
515 | + expected_flags = [["--label", "format1"], "--force", |
516 | + ["--uuid", self.test_uuid]] |
517 | + self._run_mkfs_with_config(conf, "mkfs.btrfs", expected_flags) |
518 | + |
519 | + # Test precise+btrfs edge case, force should not be used |
520 | + expected_flags.remove("--force") |
521 | + self._run_mkfs_with_config(conf, "mkfs.btrfs", expected_flags, |
522 | + release="precise") |
523 | + |
524 | + def test_mkfs_fat(self): |
525 | + conf = self._get_config("fat32") |
526 | + expected_flags = [["-n", "format1"], ["-F", "32"]] |
527 | + self._run_mkfs_with_config(conf, "mkfs.fat", expected_flags) |
528 | + |
529 | + def test_mkfs_invalid_fstype(self): |
530 | + """Do not proceed if fstype is None or invalid""" |
531 | + with self.assertRaises(ValueError): |
532 | + conf = self._get_config(None) |
533 | + self._run_mkfs_with_config(conf, "mkfs.ext4", []) |
534 | + with self.assertRaises(ValueError): |
535 | + conf = self._get_config("fakefilesystemtype") |
536 | + self._run_mkfs_with_config(conf, "mkfs.ext3", []) |
537 | + |
538 | + def test_mkfs_invalid_label(self): |
539 | + """Do not proceed if filesystem label is too long""" |
540 | + with self.assertRaises(ValueError): |
541 | + conf = self._get_config("ext4") |
542 | + conf['label'] = "thislabelislongerthan16chars" |
543 | + self._run_mkfs_with_config(conf, "mkfs.ext4", [], strict=True) |
544 | + |
545 | + conf = self._get_config("swap") |
546 | + expected_flags = ["--force", ["--label", "abcdefghijklmno"], |
547 | + ["--uuid", conf['uuid']]] |
548 | + conf['label'] = "abcdefghijklmnop" # 16 chars, 15 is max |
549 | + |
550 | + # Raise error, do not truncate with strict = True |
551 | + with self.assertRaises(ValueError): |
552 | + self._run_mkfs_with_config(conf, "mkswap", expected_flags, |
553 | + strict=True) |
554 | + |
555 | + # Do not raise with strict = False |
556 | + self._run_mkfs_with_config(conf, "mkswap", expected_flags) |
557 | + |
558 | + @mock.patch("curtin.block.mkfs.block") |
559 | + @mock.patch("curtin.block.mkfs.util") |
560 | + @mock.patch("curtin.block.mkfs.os") |
561 | + def test_mkfs_kwargs(self, mock_os, mock_util, mock_block): |
562 | + """Ensure that kwargs are being followed""" |
563 | + mkfs.mkfs("/dev/null", "ext4", [], uuid=self.test_uuid, |
564 | + label="testlabel", force=True) |
565 | + expected_flags = ["-F", ["-L", "testlabel"], ["-U", self.test_uuid]] |
566 | + calls = mock_util.subp.call_args_list |
567 | + self.assertEquals(len(calls), 1) |
568 | + call = calls[0][0][0] |
569 | + self.assertEquals(call[0], "mkfs.ext4") |
570 | + self._assert_same_flags(call, expected_flags) |
571 | + |
572 | + @mock.patch("curtin.block.mkfs.os") |
573 | + def test_mkfs_invalid_block_device(self, mock_os): |
574 | + """Do not proceed if block device is none or is not valid block dev""" |
575 | + with self.assertRaises(ValueError): |
576 | + mock_os.path.exists.return_value = False |
577 | + mkfs.mkfs("/dev/null", "ext4") |
578 | + with self.assertRaises(ValueError): |
579 | + mock_os.path.exists.return_value = True |
580 | + mkfs.mkfs(None, "ext4") |
581 | + |
582 | + @mock.patch("curtin.block.mkfs.util") |
583 | + @mock.patch("curtin.block.mkfs.os") |
584 | + def test_mkfs_generates_uuid(self, mock_os, mock_util): |
585 | + """Ensure that block.mkfs generates and returns a uuid if None is |
586 | + provided""" |
587 | + uuid = mkfs.mkfs("/dev/null", "ext4") |
588 | + self.assertIsNotNone(uuid) |
Thanks for this cleanup. In general looks good. A few comments below. es(ValueError, fn) etc.
I'd also like to see some negative unit tests for each place you raise a ValueError exception, you should provide bogus data and self.assertRais
Also, we know of at least one edge case (btrfs on precise), we should have a test-case specifically for that one. You'll need to mock out the lsb_release call for that. I used:
@patch( 'curtin. block.mkfs. util') lsb_release. return_ value = {'codename': 'precise'}
mock_util.