Merge lp:~smoser/curtin/merge-critical into lp:~curtin-dev/curtin/trunk
- merge-critical
- Merge into trunk
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Merged at revision: | 306 | ||||||||
Proposed branch: | lp:~smoser/curtin/merge-critical | ||||||||
Merge into: | lp:~curtin-dev/curtin/trunk | ||||||||
Diff against target: |
1440 lines (+840/-156) 19 files modified
bin/curtin (+16/-14) curtin/commands/block_meta.py (+78/-33) curtin/commands/curthooks.py (+19/-0) curtin/commands/main.py (+78/-16) curtin/deps/__init__.py (+162/-0) curtin/deps/check.py (+52/-24) curtin/deps/install.py (+23/-27) curtin/pack.py (+26/-22) curtin/util.py (+27/-0) debian/control (+9/-1) examples/tests/mdadm_bcache.yaml (+18/-0) examples/tests/raid5bcache.yaml (+97/-0) tests/unittests/test_util.py (+40/-0) tests/vmtests/__init__.py (+9/-1) tests/vmtests/test_basic.py (+18/-2) tests/vmtests/test_mdadm_bcache.py (+39/-10) tests/vmtests/test_raid5_bcache.py (+85/-0) tools/launch (+43/-4) tools/write-curtin (+1/-2) |
||||||||
To merge this branch: | bzr merge lp:~smoser/curtin/merge-critical | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ryan Harper (community) | Needs Fixing | ||
Review via email: mp+277744@code.launchpad.net |
Commit message
Description of the change
this is a pull-ready merge of 3 branches.
hoping to test, see all magic pixies and commit.
Ryan Harper (raharper) wrote : | # |
test_network.
Fixed with:
% bzr diff curtin/
=== modified file 'curtin/
--- curtin/
+++ curtin/
@@ -23,6 +23,7 @@
REQUIRED_IMPORTS = [
# import string to execute, python2 package, python3 package
('import yaml', 'python-yaml', 'python3-yaml'),
+ ('import oauth', 'python-oauthlib', 'python3-
]
REQUIRED_
Ryan Harper (raharper) wrote : | # |
% bzr diff curtin/
=== modified file 'curtin/
--- curtin/
+++ curtin/
@@ -23,11 +23,13 @@
REQUIRED_IMPORTS = [
# import string to execute, python2 package, python3 package
('import yaml', 'python-yaml', 'python3-yaml'),
+ ('import oauth', 'python-oauthlib', 'python3-
]
REQUIRED_
# executable in PATH, package
('lvcreate', 'lvm2'),
+ ('make-bcache', 'bcache-tools'),
('mdadm', 'mdadm'),
('mkfs.btrfs', 'btrfs-tools'),
('mkfs.ext4', 'e2fsprogs'),
And, I think this should have bcache-tools , not bcache-utils:
if lsb_release(
REQUIRED_
- 302. By Scott Moser
-
mkfs.ext3 or mkfs.ext4 should be called with -F
There are times when mkfs.ext3 believes it knows better than you,
and will refuse to create a filesystem on a device.I've not discovered the complete heuristics, but it seems
it might be related to the device name (vdb versus sdb), the contents
(filesystem or partition table or blank).Since we are intending to mkfs, we should be passing -F, but
I am afraid that we're possibly masking some other issues here.
In that any device we're wiping we should be wiping any signatures
from the start and end of disk and partitions.
------------- This line and the following will be ignored --------------modified:
curtin/commands/ block_meta. py
curtin/commands/ mkfs.py
pending merges:
Scott Moser 2015-11-17 mkfs.ext3 or mkfs.ext4 should be called with -F - 303. By Scott Moser
-
tests/vmtests/: do not remove tempdir on TempDir __del__
this was thwarting the attempts of CURTIN_
VMTEST_ KEEP_DATA
to keep data by deleting it anyway. - 304. By Scott Moser
-
Allow re-use of bcache cache devices with separate backing devices
Decouple creation of bcache caches and backing devices by dropping
use of make-bcache -B <> -C <>, in favor of single use -B and -C
and combining backing devices with caches via sysfs attach attributes.
Update the mdadm/bcache vmtest to include a multi-bcache backing device
cached by single cachedevice test.While validating results, fix lurking issue with mdadm.conf not getting
updated post raid configuration in target triggering LP: #964052. - 305. By Scott Moser
-
Reduce chance udev race with mdadm opening /dev/mdX
In some cases mdadm races with udevd with the creation of /dev/mdX and
fails to open the newly created raid array. Reduce the window by stopping
the udev exec queue while creating the raid device and resuming it
afterwards. The attached testcase no longer reproduces the issue found
only on Trusty but may still lurk in Trusty mdadm (or older). - 306. By Scott Moser
-
Improve dependency checking and installation
This makes several changes all toward the goal of fixing
curtin running on precise and better handing dependency installation.* change launcher to stop calling 'install-deps' itself, but rather just
invoke the command it needs with the best python available
the curtin main now handles installing its own dependencies if
the user provides --install-deps.* pack_install now calls 'curtin pack-install --install-deps'
* curtin/util and portions of curtin/
commands/ main.py are expected
to be runnable with any python with only standard library modules.Lastly, in order to verify all this is functional we add a Precise test.
That required some work to the test suite to handle powering the
system off because cloud-init in 12.04 does not have power_state module.
Preview Diff
1 | === modified file 'bin/curtin' |
2 | --- bin/curtin 2015-03-09 16:35:00 +0000 |
3 | +++ bin/curtin 2015-11-18 18:13:59 +0000 |
4 | @@ -1,8 +1,7 @@ |
5 | #!/bin/sh |
6 | PY3OR2_MAIN="curtin.commands.main" |
7 | PY3OR2_MCHECK="curtin.deps.check" |
8 | -PY3OR2_MINSTALL="curtin.deps.install" |
9 | -PY3OR2_PYTHONS=${PY3OR2_PYTHONS:-"python3:python2:python"} |
10 | +PY3OR2_PYTHONS=${PY3OR2_PYTHONS:-"python3:python"} |
11 | PYTHON=${PY3OR2_PYTHON} |
12 | |
13 | debug() { |
14 | @@ -27,25 +26,28 @@ |
15 | if [ ! -n "$PYTHON" ]; then |
16 | first_exe="" |
17 | oifs="$IFS"; IFS=":" |
18 | + best=0 |
19 | + best_exe="" |
20 | for p in $PY3OR2_PYTHONS; do |
21 | command -v "$p" >/dev/null 2>&1 || |
22 | { debug "$p: not in path"; continue; } |
23 | + [ -z "$PY3OR2_MCHECK" ] && PYTHON=$p && break |
24 | + out=$($p -m "$PY3OR2_MCHECK" "$@" 2>&1) && PYTHON="$p" && |
25 | + { debug "$p passed check [$p -m $PY3OR2_MCHECK $*]"; break; } |
26 | + ret=$? |
27 | + debug "$p [$ret]: $out" |
28 | + # exit code of 1 is unuseable |
29 | + [ $ret -eq 1 ] && continue |
30 | [ -n "$first_exe" ] || first_exe="$p" |
31 | - [ -z "$PY3OR2_MCHECK" ] && PYTHON=$p && break |
32 | - out=$($p -m "$PY3OR2_MCHECK" 2>&1) && PYTHON="$p" && break |
33 | - debug "$p: $out" |
34 | + # higher non-zero exit values indicate more plausible usability |
35 | + [ $best -lt $ret ] && best_exe="$p" && best=$ret && |
36 | + debug "current best: $best_exe" |
37 | done |
38 | IFS="$oifs" |
39 | - if [ -z "$PYTHON" ]; then |
40 | - if [ -n "$first_exe" -a -n "$PY3OR2_MINSTALL" ]; then |
41 | - debug "attempting deps install with $first_exe $PY3OR2_MINSTALL" |
42 | - "$first_exe" -m "$PY3OR2_MINSTALL" || |
43 | - fail "failed to install deps!" |
44 | - PYTHON="$first_exe" |
45 | - fi |
46 | - fi |
47 | + [ -z "$best_exe" -a -n "$first_exe" ] && best_exe="$first_exe" |
48 | + [ -n "$PYTHON" ] || PYTHON="$best_exe" |
49 | [ -n "$PYTHON" ] || |
50 | fail "no availble python? [PY3OR2_DEBUG=1 for more info]" |
51 | fi |
52 | -debug "executing: $PYTHON -m "$PY3OR2_MAIN" $*" |
53 | +debug "executing: $PYTHON -m \"$PY3OR2_MAIN\" $*" |
54 | exec $PYTHON -m "$PY3OR2_MAIN" "$@" |
55 | |
56 | === modified file 'curtin/commands/block_meta.py' |
57 | --- curtin/commands/block_meta.py 2015-11-17 22:03:23 +0000 |
58 | +++ curtin/commands/block_meta.py 2015-11-18 18:13:59 +0000 |
59 | @@ -31,14 +31,12 @@ |
60 | import sys |
61 | import tempfile |
62 | import time |
63 | +import re |
64 | |
65 | SIMPLE = 'simple' |
66 | SIMPLE_BOOT = 'simple-boot' |
67 | CUSTOM = 'custom' |
68 | |
69 | -CUSTOM_REQUIRED_PACKAGES = ['mdadm', 'lvm2', 'bcache-tools', |
70 | - 'btrfs-tools', 'xfsprogs'] |
71 | - |
72 | CMD_ARGUMENTS = ( |
73 | ((('-D', '--devices'), |
74 | {'help': 'which devices to operate on', 'action': 'append', |
75 | @@ -155,20 +153,43 @@ |
76 | util.subp(cmd, rcs=[0, 1, 2, 5], capture=True) |
77 | |
78 | |
79 | -def get_holders(devname): |
80 | +def block_find_sysfs_path(devname): |
81 | + # Look up any block device holders. Handle devices and partitions |
82 | + # as devnames (vdb, md0, vdb7) |
83 | if not devname: |
84 | return [] |
85 | |
86 | - devname_sysfs = \ |
87 | - '/sys/class/block/{}/holders'.format(os.path.basename(devname)) |
88 | - if not os.path.exists(devname_sysfs): |
89 | - err = ('No sysfs path to device holders:' |
90 | + sys_class_block = '/sys/class/block/' |
91 | + basename = os.path.basename(devname) |
92 | + # try without parent blockdevice, then prepend parent |
93 | + paths = [ |
94 | + os.path.join(sys_class_block, basename), |
95 | + os.path.join(sys_class_block, |
96 | + re.split('[\d+]', basename)[0], basename), |
97 | + ] |
98 | + |
99 | + # find path to devname directory in sysfs |
100 | + devname_sysfs = None |
101 | + for path in paths: |
102 | + if os.path.exists(path): |
103 | + devname_sysfs = path |
104 | + |
105 | + if devname_sysfs is None: |
106 | + err = ('No sysfs path to device:' |
107 | ' {}'.format(devname_sysfs)) |
108 | LOG.error(err) |
109 | raise ValueError(err) |
110 | |
111 | - LOG.debug('Getting blockdev holders: {}'.format(devname_sysfs)) |
112 | - return os.listdir(devname_sysfs) |
113 | + return devname_sysfs |
114 | + |
115 | + |
116 | +def get_holders(devname): |
117 | + devname_sysfs = block_find_sysfs_path(devname) |
118 | + if devname_sysfs: |
119 | + LOG.debug('Getting blockdev holders: {}'.format(devname_sysfs)) |
120 | + return os.listdir(os.path.join(devname_sysfs, 'holders')) |
121 | + |
122 | + return [] |
123 | |
124 | |
125 | def clear_holders(sys_block_path): |
126 | @@ -892,7 +913,7 @@ |
127 | if mdname: |
128 | mdnameparm = "--name=%s" % info.get('mdname') |
129 | |
130 | - cmd = ["yes", "|", "mdadm", "--create", "/dev/%s" % info.get('name'), |
131 | + cmd = ["mdadm", "--create", "/dev/%s" % info.get('name'), "--run", |
132 | "--level=%s" % raidlevel, "--raid-devices=%s" % len(device_paths), |
133 | mdnameparm] |
134 | |
135 | @@ -911,7 +932,11 @@ |
136 | cmd.append(device) |
137 | |
138 | # Create the raid device |
139 | + util.subp(["udevadm", "settle"]) |
140 | + util.subp(["udevadm", "control", "--stop-exec-queue"]) |
141 | util.subp(" ".join(cmd), shell=True) |
142 | + util.subp(["udevadm", "control", "--start-exec-queue"]) |
143 | + util.subp(["udevadm", "settle"]) |
144 | |
145 | # Make dname rule for this dev |
146 | make_dname(info.get('id'), storage_config) |
147 | @@ -945,17 +970,37 @@ |
148 | cache_mode = info.get('cache_mode', None) |
149 | |
150 | if not backing_device or not cache_device: |
151 | - raise ValueError("backing device and cache device for bcache must be \ |
152 | - specified") |
153 | + raise ValueError("backing device and cache device for bcache" |
154 | + " must be specified") |
155 | |
156 | # The bcache module is not loaded when bcache is installed by apt-get, so |
157 | # we will load it now |
158 | util.subp(["modprobe", "bcache"]) |
159 | |
160 | - # If both the backing device and cache device are specified at the same |
161 | - # time than it is not necessary to attach the cache device manually, as |
162 | - # bcache will do this automatically. |
163 | - util.subp(["make-bcache", "-B", backing_device, "-C", cache_device]) |
164 | + if cache_device: |
165 | + # /sys/class/block/XXX/YYY/ |
166 | + cache_device_sysfs = block_find_sysfs_path(cache_device) |
167 | + |
168 | + if os.path.exists(os.path.join(cache_device_sysfs, "bcache")): |
169 | + # read in cset uuid from cache device |
170 | + (out, err) = util.subp(["bcache-super-show", cache_device], |
171 | + capture=True) |
172 | + LOG.debug('out=[{}]'.format(out)) |
173 | + [cset_uuid] = [line.split()[-1] for line in out.split("\n") |
174 | + if line.startswith('cset.uuid')] |
175 | + |
176 | + else: |
177 | + # make the cache device, extracting cacheset uuid |
178 | + (out, err) = util.subp(["make-bcache", "-C", cache_device], |
179 | + capture=True) |
180 | + LOG.debug('out=[{}]'.format(out)) |
181 | + [cset_uuid] = [line.split()[-1] for line in out.split("\n") |
182 | + if line.startswith('Set UUID:')] |
183 | + |
184 | + if backing_device: |
185 | + backing_device_sysfs = block_find_sysfs_path(backing_device) |
186 | + if not os.path.exists(os.path.join(backing_device_sysfs, "bcache")): |
187 | + util.subp(["make-bcache", "-B", backing_device]) |
188 | |
189 | # Some versions of bcache-tools will register the bcache device as soon as |
190 | # we run make-bcache using udev rules, so wait for udev to settle, then try |
191 | @@ -971,6 +1016,22 @@ |
192 | fp.write(path) |
193 | fp.close() |
194 | |
195 | + # if we specify both then we need to attach backing to cache |
196 | + if cache_device and backing_device: |
197 | + if cset_uuid: |
198 | + LOG.info("Attaching backing device to cacheset: " |
199 | + "{} -> {} cset.uuid: {}".format(backing_device, |
200 | + cache_device, |
201 | + cset_uuid)) |
202 | + attach = os.path.join(backing_device_sysfs, |
203 | + "bcache", |
204 | + "attach") |
205 | + with open(attach, "w") as fp: |
206 | + fp.write(cset_uuid) |
207 | + else: |
208 | + LOG.error("Invalid cset_uuid: {}".format(cset_uuid)) |
209 | + raise |
210 | + |
211 | if cache_mode: |
212 | # find the actual bcache device name via sysfs using the |
213 | # backing device's holders directory. |
214 | @@ -999,19 +1060,6 @@ |
215 | not supported") |
216 | |
217 | |
218 | -def install_missing_packages_for_meta_custom(): |
219 | - """Install all the missing package that `meta_custom` requires to |
220 | - function properly.""" |
221 | - missing_packages = [ |
222 | - package |
223 | - for package in CUSTOM_REQUIRED_PACKAGES |
224 | - if not util.has_pkg_installed(package) |
225 | - ] |
226 | - if len(missing_packages) > 0: |
227 | - util.apt_update() |
228 | - util.install_packages(missing_packages) |
229 | - |
230 | - |
231 | def meta_custom(args): |
232 | """Does custom partitioning based on the layout provided in the config |
233 | file. Section with the name storage contains information on which |
234 | @@ -1034,9 +1082,6 @@ |
235 | state = util.load_command_environment() |
236 | cfg = config.load_command_config(args, state) |
237 | |
238 | - # make sure the required packages are installed |
239 | - install_missing_packages_for_meta_custom() |
240 | - |
241 | storage_config = cfg.get('storage', {}) |
242 | if not storage_config: |
243 | raise Exception("storage configuration is required by mode '%s' " |
244 | |
245 | === modified file 'curtin/commands/curthooks.py' |
246 | --- curtin/commands/curthooks.py 2015-10-02 19:59:11 +0000 |
247 | +++ curtin/commands/curthooks.py 2015-11-18 18:13:59 +0000 |
248 | @@ -591,6 +591,12 @@ |
249 | 'bridge-utils': ['bridge']}, |
250 | } |
251 | |
252 | + format_configs = { |
253 | + 'xfsprogs': ['xfs'], |
254 | + 'e2fsprogs': ['ext2', 'ext3', 'ext4'], |
255 | + 'brtfs-tools': ['btrfs'], |
256 | + } |
257 | + |
258 | needed_packages = [] |
259 | installed_packages = get_installed_packages(target) |
260 | for cust_cfg, pkg_reqs in custom_configs.items(): |
261 | @@ -606,6 +612,15 @@ |
262 | pkg not in installed_packages: |
263 | needed_packages.append(pkg) |
264 | |
265 | + format_types = set( |
266 | + [operation['fstype'] |
267 | + for operation in cfg[cust_cfg]['config'] |
268 | + if operation['type'] == 'format']) |
269 | + for pkg, fstypes in format_configs.items(): |
270 | + if set(fstypes).intersection(format_types) and \ |
271 | + pkg not in installed_packages: |
272 | + needed_packages.append(pkg) |
273 | + |
274 | if needed_packages: |
275 | util.install_packages(needed_packages, target=target) |
276 | |
277 | @@ -687,6 +702,10 @@ |
278 | "mdadm.conf") |
279 | if os.path.exists(mdadm_location): |
280 | copy_mdadm_conf(mdadm_location, target) |
281 | + # as per https://bugs.launchpad.net/ubuntu/+source/mdadm/+bug/964052 |
282 | + # reconfigure mdadm |
283 | + util.subp(['chroot', target, 'dpkg-reconfigure', |
284 | + '--frontend=noninteractive', 'mdadm'], data=None) |
285 | |
286 | # If udev dname rules were created, copy them to target |
287 | udev_rules_d = os.path.join(state['scratch'], "rules.d") |
288 | |
289 | === modified file 'curtin/commands/main.py' |
290 | --- curtin/commands/main.py 2015-10-20 16:56:30 +0000 |
291 | +++ curtin/commands/main.py 2015-11-18 18:13:59 +0000 |
292 | @@ -23,8 +23,7 @@ |
293 | |
294 | from .. import log |
295 | from .. import util |
296 | -from .. import config |
297 | -from ..reporter import (events, update_configuration) |
298 | +from ..deps import install_deps |
299 | |
300 | SUB_COMMAND_MODULES = ['apply_net', 'block-meta', 'curthooks', 'extract', |
301 | 'hook', 'in-target', 'install', 'mkfs', 'net-meta', |
302 | @@ -43,20 +42,20 @@ |
303 | popfunc(subparser.add_parser(subcmd)) |
304 | |
305 | |
306 | -def main(args=None): |
307 | - if args is None: |
308 | - args = sys.argv |
309 | - |
310 | - parser = argparse.ArgumentParser() |
311 | - |
312 | - stacktrace = (os.environ.get('CURTIN_STACKTRACE', "0").lower() |
313 | - not in ("0", "false", "")) |
314 | - |
315 | - try: |
316 | - verbosity = int(os.environ.get('CURTIN_VERBOSITY', "0")) |
317 | - except ValueError: |
318 | - verbosity = 1 |
319 | - |
320 | +class NoHelpParser(argparse.ArgumentParser): |
321 | + # ArgumentParser with forced 'add_help=False' |
322 | + def __init__(self, *args, **kwargs): |
323 | + kwargs.update({'add_help': False}) |
324 | + super(NoHelpParser, self).__init__(*args, **kwargs) |
325 | + |
326 | + def error(self, message): |
327 | + # without overriding this, argparse exits with bad usage |
328 | + raise ValueError("failed parsing arguments: %s" % message) |
329 | + |
330 | + |
331 | +def get_main_parser(stacktrace=False, verbosity=0, |
332 | + parser_class=argparse.ArgumentParser): |
333 | + parser = parser_class(prog='curtin') |
334 | parser.add_argument('--showtrace', action='store_true', default=stacktrace) |
335 | parser.add_argument('-v', '--verbose', action='count', default=verbosity, |
336 | dest='verbosity') |
337 | @@ -66,6 +65,9 @@ |
338 | help='read configuration from cfg', |
339 | metavar='FILE', type=argparse.FileType("rb"), |
340 | dest='main_cfgopts', default=[]) |
341 | + parser.add_argument('--install-deps', action='store_true', |
342 | + help='install dependencies as necessary', |
343 | + default=False) |
344 | parser.add_argument('--set', action=util.MergedCmdAppend, |
345 | help=('define a config variable. key can be a "/" ' |
346 | 'delimited path ("early_commands/cmd1=a"). if ' |
347 | @@ -75,6 +77,66 @@ |
348 | parser.set_defaults(config={}) |
349 | parser.set_defaults(reportstack=None) |
350 | |
351 | + return parser |
352 | + |
353 | + |
354 | +def maybe_install_deps(args, stacktrace=True, verbosity=0): |
355 | + parser = get_main_parser(stacktrace=stacktrace, verbosity=verbosity, |
356 | + parser_class=NoHelpParser) |
357 | + subps = parser.add_subparsers(dest="subcmd", parser_class=NoHelpParser) |
358 | + for subcmd in SUB_COMMAND_MODULES: |
359 | + subps.add_parser(subcmd) |
360 | + |
361 | + install_only_args = [ |
362 | + ['-v', '--install-deps'], |
363 | + ['-vv', '--install-deps'], |
364 | + ['--install-deps', '-v'], |
365 | + ['--install-deps', '-vv'], |
366 | + ['--install-deps'], |
367 | + ] |
368 | + |
369 | + install_only = args in install_only_args |
370 | + |
371 | + if install_only: |
372 | + verbosity = 1 |
373 | + else: |
374 | + try: |
375 | + ns, unknown = parser.parse_known_args(args) |
376 | + verbosity = ns.verbosity |
377 | + if not ns.install_deps: |
378 | + return |
379 | + except ValueError as e: |
380 | + # bad usage will be reported by the real reporter |
381 | + return |
382 | + |
383 | + ret = install_deps(verbosity=verbosity) |
384 | + |
385 | + if ret != 0 or install_only: |
386 | + sys.exit(ret) |
387 | + |
388 | + return |
389 | + |
390 | + |
391 | +def main(args=None): |
392 | + if args is None: |
393 | + args = sys.argv |
394 | + |
395 | + stacktrace = (os.environ.get('CURTIN_STACKTRACE', "0").lower() |
396 | + not in ("0", "false", "")) |
397 | + |
398 | + try: |
399 | + verbosity = int(os.environ.get('CURTIN_VERBOSITY', "0")) |
400 | + except ValueError: |
401 | + verbosity = 1 |
402 | + |
403 | + maybe_install_deps(sys.argv[1:], stacktrace=stacktrace, |
404 | + verbosity=verbosity) |
405 | + |
406 | + # Above here, only standard library modules can be assumed. |
407 | + from .. import config |
408 | + from ..reporter import (events, update_configuration) |
409 | + |
410 | + parser = get_main_parser(stacktrace=stacktrace, verbosity=verbosity) |
411 | subps = parser.add_subparsers(dest="subcmd") |
412 | for subcmd in SUB_COMMAND_MODULES: |
413 | add_subcmd(subps, subcmd) |
414 | |
415 | === modified file 'curtin/deps/__init__.py' |
416 | --- curtin/deps/__init__.py 2015-03-06 22:06:40 +0000 |
417 | +++ curtin/deps/__init__.py 2015-11-18 18:13:59 +0000 |
418 | @@ -1,1 +1,163 @@ |
419 | +# Copyright (C) 2015 Canonical Ltd. |
420 | +# |
421 | +# Author: Scott Moser <scott.moser@canonical.com> |
422 | +# |
423 | +# Curtin is free software: you can redistribute it and/or modify it under |
424 | +# the terms of the GNU Affero General Public License as published by the |
425 | +# Free Software Foundation, either version 3 of the License, or (at your |
426 | +# option) any later version. |
427 | +# |
428 | +# Curtin is distributed in the hope that it will be useful, but WITHOUT ANY |
429 | +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
430 | +# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for |
431 | +# more details. |
432 | +# |
433 | +# You should have received a copy of the GNU Affero General Public License |
434 | +# along with Curtin. If not, see <http://www.gnu.org/licenses/>. |
435 | +import os |
436 | +import sys |
437 | + |
438 | +from curtin.util import (which, install_packages, lsb_release, |
439 | + ProcessExecutionError) |
440 | + |
441 | +REQUIRED_IMPORTS = [ |
442 | + # import string to execute, python2 package, python3 package |
443 | + ('import yaml', 'python-yaml', 'python3-yaml'), |
444 | +] |
445 | + |
446 | +REQUIRED_EXECUTABLES = [ |
447 | + # executable in PATH, package |
448 | + ('lvcreate', 'lvm2'), |
449 | + ('mdadm', 'mdadm'), |
450 | + ('mkfs.btrfs', 'btrfs-tools'), |
451 | + ('mkfs.ext4', 'e2fsprogs'), |
452 | + ('mkfs.xfs', 'xfsprogs'), |
453 | + ('partprobe', 'parted'), |
454 | + ('sgdisk', 'gdisk'), |
455 | + ('udevadm', 'udev'), |
456 | +] |
457 | + |
458 | +if lsb_release()['codename'] == "precise": |
459 | + REQUIRED_IMPORTS.append( |
460 | + ('import oauth.oauth', 'python-oauth', None),) |
461 | +else: |
462 | + REQUIRED_EXECUTABLES.append(('make-bcache', 'bcache-tools',)) |
463 | + REQUIRED_IMPORTS.append( |
464 | + ('import oauthlib.oauth1', 'python-oauthlib', 'python3-oauthlib'),) |
465 | + |
466 | + |
467 | +class MissingDeps(Exception): |
468 | + def __init__(self, message, deps): |
469 | + self.message = message |
470 | + if isinstance(deps, str) or deps is None: |
471 | + deps = [deps] |
472 | + self.deps = [d for d in deps if d is not None] |
473 | + self.fatal = None in deps |
474 | + |
475 | + def __str__(self): |
476 | + if self.fatal: |
477 | + if not len(self.deps): |
478 | + return self.message + " Unresolvable." |
479 | + return (self.message + |
480 | + " Unresolvable. Partially resolvable with packages: %s" % |
481 | + ' '.join(self.deps)) |
482 | + else: |
483 | + return self.message + " Install packages: %s" % ' '.join(self.deps) |
484 | + |
485 | + |
486 | +def check_import(imports, py2pkgs, py3pkgs, message=None): |
487 | + import_group = imports |
488 | + if isinstance(import_group, str): |
489 | + import_group = [import_group] |
490 | + |
491 | + for istr in import_group: |
492 | + try: |
493 | + exec(istr) |
494 | + return |
495 | + except ImportError: |
496 | + pass |
497 | + |
498 | + if not message: |
499 | + if isinstance(imports, str): |
500 | + message = "Failed '%s'." % imports |
501 | + else: |
502 | + message = "Unable to do any of %s." % import_group |
503 | + |
504 | + if sys.version_info[0] == 2: |
505 | + pkgs = py2pkgs |
506 | + else: |
507 | + pkgs = py3pkgs |
508 | + |
509 | + raise MissingDeps(message, pkgs) |
510 | + |
511 | + |
512 | +def check_executable(cmdname, pkg): |
513 | + if not which(cmdname): |
514 | + raise MissingDeps("Missing program '%s'." % cmdname, pkg) |
515 | + |
516 | + |
517 | +def check_executables(executables=None): |
518 | + if executables is None: |
519 | + executables = REQUIRED_EXECUTABLES |
520 | + mdeps = [] |
521 | + for exe, pkg in executables: |
522 | + try: |
523 | + check_executable(exe, pkg) |
524 | + except MissingDeps as e: |
525 | + mdeps.append(e) |
526 | + return mdeps |
527 | + |
528 | + |
529 | +def check_imports(imports=None): |
530 | + if imports is None: |
531 | + imports = REQUIRED_IMPORTS |
532 | + |
533 | + mdeps = [] |
534 | + for import_str, py2pkg, py3pkg in imports: |
535 | + try: |
536 | + check_import(import_str, py2pkg, py3pkg) |
537 | + except MissingDeps as e: |
538 | + mdeps.append(e) |
539 | + return mdeps |
540 | + |
541 | + |
542 | +def find_missing_deps(): |
543 | + return check_executables() + check_imports() |
544 | + |
545 | + |
546 | +def install_deps(verbosity=False, dry_run=False, allow_daemons=True): |
547 | + errors = find_missing_deps() |
548 | + if len(errors) == 0: |
549 | + if verbosity: |
550 | + sys.stderr.write("No missing dependencies\n") |
551 | + return 0 |
552 | + |
553 | + missing_pkgs = [] |
554 | + for e in errors: |
555 | + missing_pkgs += e.deps |
556 | + |
557 | + deps_string = ' '.join(sorted(missing_pkgs)) |
558 | + |
559 | + if dry_run: |
560 | + sys.stderr.write("Missing dependencies: %s\n" % deps_string) |
561 | + return 0 |
562 | + |
563 | + if os.geteuid() != 0: |
564 | + sys.stderr.write("Missing dependencies: %s\n" % deps_string) |
565 | + sys.stderr.write("Package installation is not possible as non-root.\n") |
566 | + return 2 |
567 | + |
568 | + if verbosity: |
569 | + sys.stderr.write("Installing %s\n" % deps_string) |
570 | + |
571 | + ret = 0 |
572 | + try: |
573 | + install_packages(missing_pkgs, allow_daemons=allow_daemons) |
574 | + except ProcessExecutionError as e: |
575 | + sys.stderr.write("%s\n" % e) |
576 | + ret = e.exit_code |
577 | + |
578 | + return ret |
579 | + |
580 | + |
581 | # vi: ts=4 expandtab syntax=python |
582 | |
583 | === modified file 'curtin/deps/check.py' |
584 | --- curtin/deps/check.py 2015-08-10 14:33:03 +0000 |
585 | +++ curtin/deps/check.py 2015-11-18 18:13:59 +0000 |
586 | @@ -20,29 +20,57 @@ |
587 | and exit success or fail, indicating that deps should be there. |
588 | python -m curtin.deps.check [-v] |
589 | """ |
590 | -_imports = ( |
591 | - "from ..commands import main", |
592 | - "import yaml", |
593 | -) |
594 | - |
595 | - |
596 | -def _check_imports(imports=_imports): |
597 | - errors = [] |
598 | - for istr in _imports: |
599 | - try: |
600 | - exec(istr) |
601 | - except ImportError as e: |
602 | - errors.append("failed '%s': %s" % (istr, e)) |
603 | - |
604 | - return errors |
605 | +import argparse |
606 | +import sys |
607 | + |
608 | +from . import find_missing_deps |
609 | + |
610 | + |
611 | +def debug(level, msg_level, msg): |
612 | + if level >= msg_level: |
613 | + if msg[-1] != "\n": |
614 | + msg += "\n" |
615 | + sys.stderr.write(msg) |
616 | + |
617 | + |
618 | +def main(): |
619 | + parser = argparse.ArgumentParser( |
620 | + prog='curtin-check-deps', |
621 | + description='check dependencies for curtin.') |
622 | + parser.add_argument('-v', '--verbose', action='count', default=0, |
623 | + dest='verbosity') |
624 | + args, extra = parser.parse_known_args(sys.argv[1:]) |
625 | + |
626 | + errors = find_missing_deps() |
627 | + |
628 | + if len(errors) == 0: |
629 | + # exit 0 means all dependencies are available. |
630 | + debug(args.verbosity, 1, "No missing dependencies") |
631 | + sys.exit(0) |
632 | + |
633 | + missing_pkgs = [] |
634 | + fatal = [] |
635 | + for e in errors: |
636 | + if e.fatal: |
637 | + fatal.append(e) |
638 | + debug(args.verbosity, 2, str(e)) |
639 | + missing_pkgs += e.deps |
640 | + |
641 | + if len(fatal): |
642 | + for e in fatal: |
643 | + debug(args.verbosity, 1, str(e)) |
644 | + sys.exit(1) |
645 | + |
646 | + debug(args.verbosity, 1, |
647 | + "Fix with:\n apt-get -qy install %s\n" % |
648 | + ' '.join(sorted(missing_pkgs))) |
649 | + |
650 | + # we exit higher with less deps needed. |
651 | + # exiting 99 means just 1 dep needed. |
652 | + sys.exit(100-len(missing_pkgs)) |
653 | + |
654 | |
655 | if __name__ == '__main__': |
656 | - import sys |
657 | - verbose = False |
658 | - if len(sys.argv) > 1 and sys.argv[1] in ("-v", "--verbose"): |
659 | - verbose = True |
660 | - errors = _check_imports() |
661 | - if verbose: |
662 | - for emsg in errors: |
663 | - sys.stderr.write("%s\n" % emsg) |
664 | - sys.exit(len(errors)) |
665 | + main() |
666 | + |
667 | +# vi: ts=4 expandtab syntax=python |
668 | |
669 | === modified file 'curtin/deps/install.py' |
670 | --- curtin/deps/install.py 2015-08-10 14:33:03 +0000 |
671 | +++ curtin/deps/install.py 2015-11-18 18:13:59 +0000 |
672 | @@ -20,33 +20,29 @@ |
673 | python -m curtin.deps.install [-v] |
674 | """ |
675 | |
676 | -import subprocess |
677 | +import argparse |
678 | import sys |
679 | -import time |
680 | - |
681 | - |
682 | -def runcmd(cmd, retries=[]): |
683 | - for wait in retries: |
684 | - try: |
685 | - subprocess.check_call(cmd) |
686 | - return 0 |
687 | - except subprocess.CalledProcessError as e: |
688 | - sys.stderr.write("%s failed. sleeping %s\n" % (cmd, wait)) |
689 | - time.sleep(wait) |
690 | - try: |
691 | - subprocess.check_call(cmd) |
692 | - except subprocess.CalledProcessError as e: |
693 | - return e.returncode |
694 | + |
695 | +from . import install_deps |
696 | + |
697 | + |
698 | +def main(): |
699 | + parser = argparse.ArgumentParser( |
700 | + prog='curtin-install-deps', |
701 | + description='install dependencies for curtin.') |
702 | + parser.add_argument('-v', '--verbose', action='count', default=0, |
703 | + dest='verbosity') |
704 | + parser.add_argument('--dry-run', action='store_true', default=False) |
705 | + parser.add_argument('--no-allow-daemons', action='store_false', |
706 | + default=True) |
707 | + args = parser.parse_args(sys.argv[1:]) |
708 | + |
709 | + ret = install_deps(verbose=args.verbosity, dry_run=args.dry_run, |
710 | + allow_daemons=True) |
711 | + sys.exit(ret) |
712 | + |
713 | |
714 | if __name__ == '__main__': |
715 | - if sys.version_info[0] == 2: |
716 | - pkgs = ['python-yaml'] |
717 | - else: |
718 | - pkgs = ['python3-yaml'] |
719 | - |
720 | - ret = runcmd(['apt-get', '--quiet', 'update'], retries=[2, 4]) |
721 | - if ret != 0: |
722 | - sys.exit(ret) |
723 | - |
724 | - ret = runcmd(['apt-get', 'install', '--quiet', '--assume-yes'] + pkgs) |
725 | - sys.exit(ret) |
726 | + main() |
727 | + |
728 | +# vi: ts=4 expandtab syntax=python |
729 | |
730 | === modified file 'curtin/pack.py' |
731 | --- curtin/pack.py 2015-03-10 17:17:55 +0000 |
732 | +++ curtin/pack.py 2015-11-18 18:13:59 +0000 |
733 | @@ -26,7 +26,6 @@ |
734 | #!/bin/sh |
735 | PY3OR2_MAIN="%(ep_main)s" |
736 | PY3OR2_MCHECK="%(ep_mcheck)s" |
737 | -PY3OR2_MINSTALL="%(ep_minstall)s" |
738 | PY3OR2_PYTHONS=${PY3OR2_PYTHONS:-"%(python_exe_list)s"} |
739 | PYTHON=${PY3OR2_PYTHON} |
740 | """.strip() |
741 | @@ -54,41 +53,42 @@ |
742 | if [ ! -n "$PYTHON" ]; then |
743 | first_exe="" |
744 | oifs="$IFS"; IFS=":" |
745 | + best=0 |
746 | + best_exe="" |
747 | for p in $PY3OR2_PYTHONS; do |
748 | command -v "$p" >/dev/null 2>&1 || |
749 | { debug "$p: not in path"; continue; } |
750 | + [ -z "$PY3OR2_MCHECK" ] && PYTHON=$p && break |
751 | + out=$($p -m "$PY3OR2_MCHECK" "$@" 2>&1) && PYTHON="$p" && |
752 | + { debug "$p passed check [$p -m $PY3OR2_MCHECK $*]"; break; } |
753 | + ret=$? |
754 | + debug "$p [$ret]: $out" |
755 | + # exit code of 1 is unuseable |
756 | + [ $ret -eq 1 ] && continue |
757 | [ -n "$first_exe" ] || first_exe="$p" |
758 | - [ -z "$PY3OR2_MCHECK" ] && PYTHON=$p && break |
759 | - out=$($p -m "$PY3OR2_MCHECK" 2>&1) && PYTHON="$p" && break |
760 | - debug "$p: $out" |
761 | + # higher non-zero exit values indicate more plausible usability |
762 | + [ $best -lt $ret ] && best_exe="$p" && best=$ret && |
763 | + debug "current best: $best_exe" |
764 | done |
765 | IFS="$oifs" |
766 | - if [ -z "$PYTHON" ]; then |
767 | - if [ -n "$first_exe" -a -n "$PY3OR2_MINSTALL" ]; then |
768 | - debug "attempting deps install with $first_exe $PY3OR2_MINSTALL" |
769 | - "$first_exe" -m "$PY3OR2_MINSTALL" || |
770 | - fail "failed to install deps!" |
771 | - PYTHON="$first_exe" |
772 | - fi |
773 | - fi |
774 | + [ -z "$best_exe" -a -n "$first_exe" ] && best_exe="$first_exe" |
775 | + [ -n "$PYTHON" ] || PYTHON="$best_exe" |
776 | [ -n "$PYTHON" ] || |
777 | fail "no availble python? [PY3OR2_DEBUG=1 for more info]" |
778 | fi |
779 | -debug "executing: $PYTHON -m \"$PY3OR2_MAIN\" $*" |
780 | +debug "executing: $PYTHON -m \\"$PY3OR2_MAIN\\" $*" |
781 | exec $PYTHON -m "$PY3OR2_MAIN" "$@" |
782 | """ |
783 | |
784 | |
785 | def write_exe_wrapper(entrypoint, path=None, interpreter=None, |
786 | - deps_check_entry=None, deps_install_entry=None, |
787 | - mode=0o755): |
788 | + deps_check_entry=None, mode=0o755): |
789 | if not interpreter: |
790 | - interpreter = "python3:python2:python" |
791 | + interpreter = "python3:python" |
792 | |
793 | subs = { |
794 | 'ep_main': entrypoint, |
795 | 'ep_mcheck': deps_check_entry if deps_check_entry else "", |
796 | - 'ep_minstall': deps_install_entry if deps_install_entry else "", |
797 | 'python_exe_list': interpreter, |
798 | } |
799 | |
800 | @@ -140,9 +140,7 @@ |
801 | ignore=not_dot_py) |
802 | write_exe_wrapper(entrypoint='curtin.commands.main', |
803 | path=os.path.join(bindir, 'curtin'), |
804 | - deps_check_entry="curtin.deps.check", |
805 | - deps_install_entry="curtin.deps.install" |
806 | - ) |
807 | + deps_check_entry="curtin.deps.check") |
808 | |
809 | for archpath, filepath in copy_files: |
810 | target = os.path.abspath(os.path.join(exdir, archpath)) |
811 | @@ -208,7 +206,8 @@ |
812 | |
813 | |
814 | def pack_install(fdout=None, configs=None, paths=None, |
815 | - add_files=None, copy_files=None, args=None): |
816 | + add_files=None, copy_files=None, args=None, |
817 | + install_deps=True): |
818 | |
819 | if configs is None: |
820 | configs = [] |
821 | @@ -219,7 +218,12 @@ |
822 | if args is None: |
823 | args = [] |
824 | |
825 | - command = ["curtin", "install"] |
826 | + if install_deps: |
827 | + dep_flags = ["--install-deps"] |
828 | + else: |
829 | + dep_flags = [] |
830 | + |
831 | + command = ["curtin", dep_flags, "install"] |
832 | |
833 | my_files = [] |
834 | for n, config in enumerate(configs): |
835 | |
836 | === modified file 'curtin/util.py' |
837 | --- curtin/util.py 2015-10-26 18:03:20 +0000 |
838 | +++ curtin/util.py 2015-11-18 18:13:59 +0000 |
839 | @@ -30,6 +30,8 @@ |
840 | _INSTALLED_HELPERS_PATH = '/usr/lib/curtin/helpers' |
841 | _INSTALLED_MAIN = '/usr/bin/curtin' |
842 | |
843 | +_LSB_RELEASE = {} |
844 | + |
845 | |
846 | def _subp(args, data=None, rcs=None, env=None, capture=False, shell=False, |
847 | logstring=False): |
848 | @@ -691,6 +693,31 @@ |
849 | return (isinstance(exc, IOError) and exc.errno == errno.ENOENT) |
850 | |
851 | |
852 | +def lsb_release(): |
853 | + fmap = {'Codename': 'codename', 'Description': 'description', |
854 | + 'Distributor ID': 'id', 'Release': 'release'} |
855 | + global _LSB_RELEASE |
856 | + if not _LSB_RELEASE: |
857 | + data = {} |
858 | + try: |
859 | + out, err = subp(['lsb_release', '--all'], capture=True) |
860 | + for line in out.splitlines(): |
861 | + fname, tok, val = line.partition(":") |
862 | + if fname in fmap: |
863 | + data[fmap[fname]] = val.strip() |
864 | + missing = [k for k in fmap.values() if k not in data] |
865 | + if len(missing): |
866 | + LOG.warn("Missing fields in lsb_release --all output: %s", |
867 | + ','.join(missing)) |
868 | + |
869 | + except ProcessExecutionError as e: |
870 | + LOG.warn("Unable to get lsb_release --all: %s", e) |
871 | + data = {v: "UNAVAILABLE" for v in fmap.values()} |
872 | + |
873 | + _LSB_RELEASE.update(data) |
874 | + return _LSB_RELEASE |
875 | + |
876 | + |
877 | class MergedCmdAppend(argparse.Action): |
878 | """This appends to a list in order of appearence both the option string |
879 | and the value""" |
880 | |
881 | === modified file 'debian/control' |
882 | --- debian/control 2015-10-07 13:44:24 +0000 |
883 | +++ debian/control 2015-11-18 18:13:59 +0000 |
884 | @@ -25,7 +25,15 @@ |
885 | Package: curtin |
886 | Architecture: all |
887 | Priority: extra |
888 | -Depends: python3-curtin (= ${binary:Version}), |
889 | +Depends: btrfs-tools, |
890 | + e2fsprogs, |
891 | + gdisk, |
892 | + lvm2, |
893 | + mdadm, |
894 | + parted, |
895 | + python3-curtin (= ${binary:Version}), |
896 | + udev, |
897 | + xfsprogs, |
898 | ${misc:Depends} |
899 | Description: Library and tools for the curtin installer |
900 | This package provides the curtin installer. |
901 | |
902 | === modified file 'examples/tests/mdadm_bcache.yaml' |
903 | --- examples/tests/mdadm_bcache.yaml 2015-10-29 19:13:25 +0000 |
904 | +++ examples/tests/mdadm_bcache.yaml 2015-11-18 18:13:59 +0000 |
905 | @@ -32,6 +32,10 @@ |
906 | type: partition |
907 | size: 1GB |
908 | device: sda |
909 | + - id: sda6 |
910 | + type: partition |
911 | + size: 1GB |
912 | + device: sda |
913 | - id: mddevice |
914 | name: md0 |
915 | type: raid |
916 | @@ -47,6 +51,12 @@ |
917 | backing_device: mddevice |
918 | cache_device: sda5 |
919 | cache_mode: writeback |
920 | + - id: bcache1 |
921 | + type: bcache |
922 | + name: cached_array_2 |
923 | + backing_device: sda6 |
924 | + cache_device: sda5 |
925 | + cache_mode: writeback |
926 | - id: sda1_root |
927 | type: format |
928 | fstype: ext4 |
929 | @@ -55,6 +65,10 @@ |
930 | type: format |
931 | fstype: ext4 |
932 | volume: bcache0 |
933 | + - id: bcache_storage |
934 | + type: format |
935 | + fstype: ext4 |
936 | + volume: bcache1 |
937 | - id: sda1_mount |
938 | type: mount |
939 | path: / |
940 | @@ -63,3 +77,7 @@ |
941 | type: mount |
942 | path: /media/data |
943 | device: raid_storage |
944 | + - id: bcache1_mount |
945 | + type: mount |
946 | + path: /media/bcache1 |
947 | + device: bcache_storage |
948 | |
949 | === added file 'examples/tests/raid5bcache.yaml' |
950 | --- examples/tests/raid5bcache.yaml 1970-01-01 00:00:00 +0000 |
951 | +++ examples/tests/raid5bcache.yaml 2015-11-18 18:13:59 +0000 |
952 | @@ -0,0 +1,97 @@ |
953 | +storage: |
954 | + config: |
955 | + - grub_device: true |
956 | + id: sda |
957 | + model: QEMU HARDDISK |
958 | + name: sda |
959 | + ptable: msdos |
960 | + path: /dev/vdb |
961 | + type: disk |
962 | + wipe: superblock |
963 | + - id: sdb |
964 | + model: QEMU HARDDISK |
965 | + name: sdb |
966 | + path: /dev/vdc |
967 | + type: disk |
968 | + wipe: superblock |
969 | + - id: sdc |
970 | + model: QEMU HARDDISK |
971 | + name: sdc |
972 | + path: /dev/vdd |
973 | + type: disk |
974 | + wipe: superblock |
975 | + - id: sdd |
976 | + model: QEMU HARDDISK |
977 | + name: sdd |
978 | + path: /dev/vde |
979 | + type: disk |
980 | + wipe: superblock |
981 | + - id: sde |
982 | + model: QEMU HARDDISK |
983 | + name: sde |
984 | + path: /dev/vdf |
985 | + type: disk |
986 | + wipe: superblock |
987 | + - devices: |
988 | + - sdc |
989 | + - sdd |
990 | + - sde |
991 | + id: md0 |
992 | + name: md0 |
993 | + raidlevel: 5 |
994 | + spare_devices: [] |
995 | + type: raid |
996 | + - device: sda |
997 | + id: sda-part1 |
998 | + name: sda-part1 |
999 | + number: 1 |
1000 | + offset: 2097152B |
1001 | + size: 1000001536B |
1002 | + type: partition |
1003 | + uuid: 3a38820c-d675-4069-b060-509a3d9d13cc |
1004 | + wipe: superblock |
1005 | + - device: sda |
1006 | + id: sda-part2 |
1007 | + name: sda-part2 |
1008 | + number: 2 |
1009 | + size: 7586787328B |
1010 | + type: partition |
1011 | + uuid: 17747faa-4b9e-4411-97e5-12fd3d199fb8 |
1012 | + wipe: superblock |
1013 | + - backing_device: sda-part2 |
1014 | + cache_device: sdb |
1015 | + cache_mode: writeback |
1016 | + id: bcache0 |
1017 | + name: bcache0 |
1018 | + type: bcache |
1019 | + - fstype: ext4 |
1020 | + id: sda-part1_format |
1021 | + label: '' |
1022 | + type: format |
1023 | + uuid: 71b1ef6f-5cab-4a77-b4c8-5a209ec11d7c |
1024 | + volume: sda-part1 |
1025 | + - fstype: ext4 |
1026 | + id: md0_format |
1027 | + label: '' |
1028 | + type: format |
1029 | + uuid: b031f0a0-adb3-43be-bb43-ce0fc8a224a4 |
1030 | + volume: md0 |
1031 | + - fstype: ext4 |
1032 | + id: bcache0_format |
1033 | + label: '' |
1034 | + type: format |
1035 | + uuid: ce45bbaf-5a44-4487-b89e-035c2dd40657 |
1036 | + volume: bcache0 |
1037 | + - device: bcache0_format |
1038 | + id: bcache0_mount |
1039 | + path: / |
1040 | + type: mount |
1041 | + - device: sda-part1_format |
1042 | + id: sda-part1_mount |
1043 | + path: /boot |
1044 | + type: mount |
1045 | + - device: md0_format |
1046 | + id: md0_mount |
1047 | + path: /srv/data |
1048 | + type: mount |
1049 | + version: 1 |
1050 | |
1051 | === modified file 'tests/unittests/test_util.py' |
1052 | --- tests/unittests/test_util.py 2014-09-18 01:05:55 +0000 |
1053 | +++ tests/unittests/test_util.py 2015-11-18 18:13:59 +0000 |
1054 | @@ -1,4 +1,5 @@ |
1055 | from unittest import TestCase |
1056 | +import mock |
1057 | import os |
1058 | import shutil |
1059 | import tempfile |
1060 | @@ -99,6 +100,45 @@ |
1061 | self.assertEqual(found, "/usr/bin2/fuzz") |
1062 | |
1063 | |
1064 | +class TestLsbRelease(TestCase): |
1065 | + def setUp(self): |
1066 | + self._reset_cache() |
1067 | + |
1068 | + def _reset_cache(self): |
1069 | + keys = [k for k in util._LSB_RELEASE.keys()] |
1070 | + for d in keys: |
1071 | + del util._LSB_RELEASE[d] |
1072 | + |
1073 | + @mock.patch("curtin.util.subp") |
1074 | + def test_lsb_release_functional(self, mock_subp): |
1075 | + output = '\n'.join([ |
1076 | + "Distributor ID: Ubuntu", |
1077 | + "Description: Ubuntu 14.04.2 LTS", |
1078 | + "Release: 14.04", |
1079 | + "Codename: trusty", |
1080 | + ]) |
1081 | + rdata = {'id': 'Ubuntu', 'description': 'Ubuntu 14.04.2 LTS', |
1082 | + 'codename': 'trusty', 'release': '14.04'} |
1083 | + |
1084 | + def fake_subp(cmd, capture=False): |
1085 | + return output, 'No LSB modules are available.' |
1086 | + |
1087 | + mock_subp.side_effect = fake_subp |
1088 | + found = util.lsb_release() |
1089 | + mock_subp.assert_called_with(['lsb_release', '--all'], capture=True) |
1090 | + self.assertEqual(found, rdata) |
1091 | + |
1092 | + @mock.patch("curtin.util.subp") |
1093 | + def test_lsb_release_unavailable(self, mock_subp): |
1094 | + def doraise(*args, **kwargs): |
1095 | + raise util.ProcessExecutionError("foo") |
1096 | + mock_subp.side_effect = doraise |
1097 | + |
1098 | + expected = {k: "UNAVAILABLE" for k in |
1099 | + ('id', 'description', 'codename', 'release')} |
1100 | + self.assertEqual(util.lsb_release(), expected) |
1101 | + |
1102 | + |
1103 | class TestSubp(TestCase): |
1104 | |
1105 | def test_subp_handles_utf8(self): |
1106 | |
1107 | === modified file 'tests/vmtests/__init__.py' |
1108 | --- tests/vmtests/__init__.py 2015-11-18 16:24:35 +0000 |
1109 | +++ tests/vmtests/__init__.py 2015-11-18 18:13:59 +0000 |
1110 | @@ -483,7 +483,15 @@ |
1111 | collect_post = textwrap.dedent( |
1112 | 'tar -C "%s" -cf "%s" .' % (output_dir, output_device)) |
1113 | |
1114 | - scripts = [collect_prep] + collect_scripts + [collect_post] |
1115 | + # failsafe poweroff runs on precise only, where power_state does |
1116 | + # not exist. |
1117 | + precise_poweroff = textwrap.dedent("""#!/bin/sh |
1118 | + [ "$(lsb_release -sc)" = "precise" ] || exit 0; |
1119 | + shutdown -P now "Shutting down on precise" |
1120 | + """) |
1121 | + |
1122 | + scripts = ([collect_prep] + collect_scripts + [collect_post] + |
1123 | + [precise_poweroff]) |
1124 | |
1125 | for part in scripts: |
1126 | if not part.startswith("#!"): |
1127 | |
1128 | === modified file 'tests/vmtests/test_basic.py' |
1129 | --- tests/vmtests/test_basic.py 2015-10-27 11:35:10 +0000 |
1130 | +++ tests/vmtests/test_basic.py 2015-11-18 18:13:59 +0000 |
1131 | @@ -23,7 +23,10 @@ |
1132 | cat /etc/fstab > fstab |
1133 | ls /dev/disk/by-dname/ > ls_dname |
1134 | |
1135 | - apt-config dump Acquire::HTTP::Proxy > apt-proxy |
1136 | + v="" |
1137 | + out=$(apt-config shell v Acquire::HTTP::Proxy) |
1138 | + eval "$out" |
1139 | + echo "$v" > apt-proxy |
1140 | """)] |
1141 | |
1142 | def test_output_files_exist(self): |
1143 | @@ -67,7 +70,7 @@ |
1144 | def test_proxy_set(self): |
1145 | expected = get_apt_proxy() |
1146 | with open(os.path.join(self.td.mnt, "apt-proxy")) as fp: |
1147 | - apt_proxy_found = fp.read() |
1148 | + apt_proxy_found = fp.read().rstrip() |
1149 | if expected: |
1150 | # the proxy should have gotten set through |
1151 | self.assertIn(expected, apt_proxy_found) |
1152 | @@ -88,3 +91,16 @@ |
1153 | repo = "maas-daily" |
1154 | release = "vivid" |
1155 | arch = "amd64" |
1156 | + |
1157 | + |
1158 | +class PreciseTestBasic(TestBasicAbs, TestCase): |
1159 | + __test__ = True |
1160 | + repo = "maas-daily" |
1161 | + release = "precise" |
1162 | + arch = "amd64" |
1163 | + |
1164 | + def test_ptable(self): |
1165 | + print("test_ptable does not work for Precise") |
1166 | + |
1167 | + def test_dname(self): |
1168 | + print("test_dname does not work for Precise") |
1169 | |
1170 | === modified file 'tests/vmtests/test_mdadm_bcache.py' |
1171 | --- tests/vmtests/test_mdadm_bcache.py 2015-10-30 13:38:36 +0000 |
1172 | +++ tests/vmtests/test_mdadm_bcache.py 2015-11-18 18:13:59 +0000 |
1173 | @@ -44,31 +44,60 @@ |
1174 | 'main_disk': 5, |
1175 | 'main_disk': 6, |
1176 | 'md0': 0, |
1177 | - 'cached_array': 0} |
1178 | + 'cached_array': 0, |
1179 | + 'cached_array_2': 0} |
1180 | |
1181 | collect_scripts = TestMdadmAbs.collect_scripts + [textwrap.dedent(""" |
1182 | cd OUTPUT_COLLECT_D |
1183 | bcache-super-show /dev/vda6 > bcache_super_vda6 |
1184 | + bcache-super-show /dev/vda7 > bcache_super_vda7 |
1185 | + bcache-super-show /dev/md0 > bcache_super_md0 |
1186 | ls /sys/fs/bcache > bcache_ls |
1187 | cat /sys/block/bcache0/bcache/cache_mode > bcache_cache_mode |
1188 | + cat /proc/mounts > proc_mounts |
1189 | """)] |
1190 | fstab_expected = { |
1191 | - '/dev/bcache0': '/media/data' |
1192 | + '/dev/bcache0': '/media/data', |
1193 | + '/dev/bcache1': '/media/bcache1', |
1194 | } |
1195 | |
1196 | def test_bcache_output_files_exist(self): |
1197 | - self.output_files_exist(["bcache_super_vda6", "bcache_ls", |
1198 | + self.output_files_exist(["bcache_super_vda6", |
1199 | + "bcache_super_vda7", |
1200 | + "bcache_super_md0", |
1201 | + "bcache_ls", |
1202 | "bcache_cache_mode"]) |
1203 | |
1204 | def test_bcache_status(self): |
1205 | + bcache_supers = [ |
1206 | + "bcache_super_vda6", |
1207 | + "bcache_super_vda7", |
1208 | + "bcache_super_md0", |
1209 | + ] |
1210 | bcache_cset_uuid = None |
1211 | - with open(os.path.join(self.td.mnt, "bcache_super_vda6"), "r") as fp: |
1212 | - for line in fp.read().splitlines(): |
1213 | - if line != "" and line.split()[0] == "cset.uuid": |
1214 | - bcache_cset_uuid = line.split()[-1].rstrip() |
1215 | - self.assertIsNotNone(bcache_cset_uuid) |
1216 | - with open(os.path.join(self.td.mnt, "bcache_ls"), "r") as fp: |
1217 | - self.assertTrue(bcache_cset_uuid in fp.read().splitlines()) |
1218 | + found = {} |
1219 | + for bcache_super in bcache_supers: |
1220 | + with open(os.path.join(self.td.mnt, bcache_super), "r") as fp: |
1221 | + for line in fp.read().splitlines(): |
1222 | + if line != "" and line.split()[0] == "cset.uuid": |
1223 | + bcache_cset_uuid = line.split()[-1].rstrip() |
1224 | + if bcache_cset_uuid in found: |
1225 | + found[bcache_cset_uuid].append(bcache_super) |
1226 | + else: |
1227 | + found[bcache_cset_uuid] = [bcache_super] |
1228 | + self.assertIsNotNone(bcache_cset_uuid) |
1229 | + with open(os.path.join(self.td.mnt, "bcache_ls"), "r") as fp: |
1230 | + self.assertTrue(bcache_cset_uuid in fp.read().splitlines()) |
1231 | + |
1232 | + # one cset.uuid for all devices |
1233 | + self.assertEqual(len(found), 1) |
1234 | + |
1235 | + # three devices with same cset.uuid |
1236 | + self.assertEqual(len(found[bcache_cset_uuid]), 3) |
1237 | + |
1238 | + # check the cset.uuid in the dict |
1239 | + self.assertEqual(list(found.keys()).pop(), |
1240 | + bcache_cset_uuid) |
1241 | |
1242 | def test_bcache_cachemode(self): |
1243 | self.check_file_regex("bcache_cache_mode", r"\[writeback\]") |
1244 | |
1245 | === added file 'tests/vmtests/test_raid5_bcache.py' |
1246 | --- tests/vmtests/test_raid5_bcache.py 1970-01-01 00:00:00 +0000 |
1247 | +++ tests/vmtests/test_raid5_bcache.py 2015-11-18 18:13:59 +0000 |
1248 | @@ -0,0 +1,85 @@ |
1249 | +from . import VMBaseClass |
1250 | +from unittest import TestCase |
1251 | + |
1252 | +import textwrap |
1253 | +import os |
1254 | + |
1255 | + |
1256 | +class TestMdadmAbs(VMBaseClass, TestCase): |
1257 | + __test__ = False |
1258 | + repo = "maas-daily" |
1259 | + arch = "amd64" |
1260 | + install_timeout = 600 |
1261 | + boot_timeout = 100 |
1262 | + interactive = False |
1263 | + extra_disks = ['10G', '10G', '10G', '10G'] |
1264 | + active_mdadm = "1" |
1265 | + collect_scripts = [textwrap.dedent(""" |
1266 | + cd OUTPUT_COLLECT_D |
1267 | + cat /etc/fstab > fstab |
1268 | + mdadm --detail --scan > mdadm_status |
1269 | + mdadm --detail --scan | grep -c ubuntu > mdadm_active1 |
1270 | + grep -c active /proc/mdstat > mdadm_active2 |
1271 | + ls /dev/disk/by-dname > ls_dname |
1272 | + """)] |
1273 | + |
1274 | + def test_mdadm_output_files_exist(self): |
1275 | + self.output_files_exist( |
1276 | + ["fstab", "mdadm_status", "mdadm_active1", "mdadm_active2", |
1277 | + "ls_dname"]) |
1278 | + |
1279 | + def test_mdadm_status(self): |
1280 | + # ubuntu:<ID> is the name assigned to the md array |
1281 | + self.check_file_regex("mdadm_status", r"ubuntu:[0-9]*") |
1282 | + self.check_file_strippedline("mdadm_active1", self.active_mdadm) |
1283 | + self.check_file_strippedline("mdadm_active2", self.active_mdadm) |
1284 | + |
1285 | + |
1286 | +class TestMdadmBcacheAbs(TestMdadmAbs): |
1287 | + conf_file = "examples/tests/raid5bcache.yaml" |
1288 | + disk_to_check = {'sda': 2, |
1289 | + 'md0': 0} |
1290 | + |
1291 | + collect_scripts = TestMdadmAbs.collect_scripts + [textwrap.dedent(""" |
1292 | + cd OUTPUT_COLLECT_D |
1293 | + bcache-super-show /dev/vda2 > bcache_super_vda2 |
1294 | + ls /sys/fs/bcache > bcache_ls |
1295 | + cat /sys/block/bcache0/bcache/cache_mode > bcache_cache_mode |
1296 | + cat /proc/mounts > proc_mounts |
1297 | + cat /proc/partitions > proc_partitions |
1298 | + """)] |
1299 | + fstab_expected = { |
1300 | + '/dev/bcache0': '/', |
1301 | + '/dev/md0': '/srv/data', |
1302 | + } |
1303 | + |
1304 | + def test_bcache_output_files_exist(self): |
1305 | + self.output_files_exist(["bcache_super_vda2", "bcache_ls", |
1306 | + "bcache_cache_mode"]) |
1307 | + |
1308 | + def test_bcache_status(self): |
1309 | + bcache_cset_uuid = None |
1310 | + with open(os.path.join(self.td.mnt, "bcache_super_vda2"), "r") as fp: |
1311 | + for line in fp.read().splitlines(): |
1312 | + if line != "" and line.split()[0] == "cset.uuid": |
1313 | + bcache_cset_uuid = line.split()[-1].rstrip() |
1314 | + self.assertIsNotNone(bcache_cset_uuid) |
1315 | + with open(os.path.join(self.td.mnt, "bcache_ls"), "r") as fp: |
1316 | + self.assertTrue(bcache_cset_uuid in fp.read().splitlines()) |
1317 | + |
1318 | + def test_bcache_cachemode(self): |
1319 | + self.check_file_regex("bcache_cache_mode", r"\[writeback\]") |
1320 | + |
1321 | + |
1322 | +class WilyTestRaid5Bcache(TestMdadmBcacheAbs): |
1323 | + __test__ = True |
1324 | + release = "wily" |
1325 | + |
1326 | + |
1327 | +class VividTestRaid5Bcache(TestMdadmBcacheAbs): |
1328 | + __test__ = True |
1329 | + release = "vivid" |
1330 | + |
1331 | +class TrustTestRaid5Bcache(TestMdadmBcacheAbs): |
1332 | + __test__ = True |
1333 | + release = "trusty" |
1334 | |
1335 | === modified file 'tools/launch' |
1336 | --- tools/launch 2015-11-13 20:04:48 +0000 |
1337 | +++ tools/launch 2015-11-18 18:13:59 +0000 |
1338 | @@ -35,6 +35,8 @@ |
1339 | Note, qemu adds 5900 to port numbers. (:0 = port 5900) |
1340 | --serial-log F : log to F (default 'serial.log') |
1341 | -v | --verbose be more verbose |
1342 | + --no-install-deps do not install insert '--install-deps' |
1343 | + on curtin command invocations |
1344 | |
1345 | use of --kernel/--initrd will seed cloud-init via cmdline |
1346 | rather than the local datasource |
1347 | @@ -99,6 +101,36 @@ |
1348 | EOF |
1349 | } |
1350 | |
1351 | +write_pstate_config() { |
1352 | + local pstate="$1" config1="$2" config2="$3" |
1353 | + cat > "$config1" <<EOF |
1354 | +#cloud-config |
1355 | +power_state: |
1356 | + mode: "$pstate" |
1357 | +EOF |
1358 | + sed -e "s,PSTATE,$pstate," > "$config2" <<"EOF" |
1359 | +#upstart-job |
1360 | +# precise does not do cloud-config poweroff |
1361 | +description "power-state for precise" |
1362 | +start on stopped cloud-final |
1363 | +console output |
1364 | +task |
1365 | +script |
1366 | + [ "$(lsb_release -sc)" = "precise" ] || exit 0 |
1367 | + target="PSTATE" |
1368 | + msg="precise-powerstate: $target" |
1369 | + case "$target" in |
1370 | + on) exit 0;; |
1371 | + off|poweroff) shutdown -P now "$msg";; |
1372 | + reboot) shutdown -r now "$msg";; |
1373 | + *) echo "$msg : unknown target"; exit 1;; |
1374 | + esac |
1375 | + echo "$msg" |
1376 | + exit 0 |
1377 | +end script |
1378 | +EOF |
1379 | +} |
1380 | + |
1381 | write_userdata() { |
1382 | local x |
1383 | cat <<EOF |
1384 | @@ -219,7 +251,7 @@ |
1385 | local initrd="" kernel="" uappend="" iargs="" disk_args="" |
1386 | local pubs="" disks="" pstate="null" |
1387 | local uefi=0 |
1388 | - local netdevs="" |
1389 | + local netdevs="" install_deps="--install-deps" |
1390 | local video="-curses -vga std" serial_log="file:serial.log" |
1391 | # dowait: run xkvm with a '&' and then 'wait' on the pid. |
1392 | # the reason to do this or not do this has to do with interactivity |
1393 | @@ -246,6 +278,7 @@ |
1394 | -d|--disk) disks[${#disks[@]}]="$next"; shift;; |
1395 | -h|--help) Usage ; exit 0;; |
1396 | -i|--initrd) initrd="$next"; shift;; |
1397 | + --no-install-deps) install_deps="";; |
1398 | -k|--kernel) kernel="$next"; shift;; |
1399 | --mem) mem="$next"; shift;; |
1400 | --power) |
1401 | @@ -400,6 +433,12 @@ |
1402 | addargs[${#addargs[@]}]="--add=config/${fpath##*/}:$fpath" |
1403 | done |
1404 | |
1405 | + if [ "${cmdargs[0]}" = "curtin" -a -n "$install_deps" ]; then |
1406 | + # if this is a 'curtin' command, then also insert --install-deps |
1407 | + debug 1 "adding --install-deps to command" |
1408 | + cmdargs=( curtin $install_deps "${cmdargs[@]:1}" ) |
1409 | + fi |
1410 | + |
1411 | PYTHONPATH="${top_d}${PYTHONPATH:+${PYTHONPATH}}" \ |
1412 | "${top_d}/bin/curtin" pack "${addargs[@]}" -- \ |
1413 | "${cmdargs[@]}" > "${TEMP_D}/install-cmd" || |
1414 | @@ -411,9 +450,9 @@ |
1415 | local ccfiles="" |
1416 | ccfiles=( ) |
1417 | if [ -n "${pstate}" ]; then |
1418 | - printf "#cloud-config\npower_state:\n mode: %s\n" "$pstate" \ |
1419 | - > "${TEMP_D}/pstate" |
1420 | - ccfiles[${#ccfiles[@]}]="${TEMP_D}/pstate" |
1421 | + write_pstate_config "$pstate" "${TEMP_D}/pstate.1" "${TEMP_D}/pstate.2" |
1422 | + ccfiles[${#ccfiles[@]}]="${TEMP_D}/pstate.1" |
1423 | + ccfiles[${#ccfiles[@]}]="${TEMP_D}/pstate.2" |
1424 | fi |
1425 | |
1426 | if tmp=$(find_apt_proxy); then |
1427 | |
1428 | === modified file 'tools/write-curtin' |
1429 | --- tools/write-curtin 2015-03-09 16:35:00 +0000 |
1430 | +++ tools/write-curtin 2015-11-18 18:13:59 +0000 |
1431 | @@ -16,8 +16,7 @@ |
1432 | parg = path |
1433 | |
1434 | ret = write_exe_wrapper(entrypoint="curtin.commands.main", path=parg, |
1435 | - deps_check_entry="curtin.deps.check", |
1436 | - deps_install_entry="curtin.deps.install") |
1437 | + deps_check_entry="curtin.deps.check") |
1438 | |
1439 | if path == "-": |
1440 | sys.stdout.write(ret) |
We're not installing filesystem deps in-target anymore (xfs_info not found):
Ideally the per-config dict of package mapping to config types is
what the deps/ stuff should be using; the fstype needs to check a second value
in the config to determine which filesystem was used.
Additionally, the current deps/__ init__. py:install_ deps doesn't expose target packages, so in curthooks where deps(), we want to pass it in target and
which is available in the underlying util.install_
we really want to re-use deps.install_
the custom config.
But the following allows test_mdadm_ bcache. py to pass now.
% bzr diff curtin/ commands/ curthooks. py commands/ curthooks. py' commands/ curthooks. py 2015-11-17 19:10:43 +0000 commands/ curthooks. py 2015-11-18 03:30:19 +0000
' bridge- utils': ['bridge']},
=== modified file 'curtin/
--- curtin/
+++ curtin/
@@ -591,6 +591,12 @@
}
+ format_configs = { packages = [] packages = get_installed_ packages( target) configs. items() :
needed_ packages. append( pkg)
+ 'xfsprogs': ['xfs'],
+ 'e2fsprogs': ['ext2', 'ext3', 'ext4'],
+ 'brtfs-tools': ['btrfs'],
+ }
+
needed_
installed_
for cust_cfg, pkg_reqs in custom_
@@ -606,6 +612,15 @@
pkg not in installed_packages:
+ format_types = set( 'fstype' ] cfg]['config' ] configs. items() : .intersection( format_ types) and \ packages. append( pkg)
util. install_ packages( needed_ packages, target=target)
+ [operation[
+ for operation in cfg[cust_
+ if operation['type'] == 'format'])
+ for pkg, fstypes in format_
+ if set(fstypes)
+ pkg not in installed_packages:
+ needed_
+
if needed_packages: