Merge lp:~smoser/curtin/merge-critical into lp:~curtin-dev/curtin/trunk

Proposed by Scott Moser
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
Reviewer Review Type Date Requested Status
Ryan Harper (community) Needs Fixing
Review via email: mp+277744@code.launchpad.net

Description of the change

this is a pull-ready merge of 3 branches.
hoping to test, see all magic pixies and commit.

To post a comment you must log in.
Revision history for this message
Ryan Harper (raharper) wrote :

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
which is available in the underlying util.install_packages, so in curthooks where
we really want to re-use deps.install_deps(), we want to pass it in target and
the custom config.

But the following allows test_mdadm_bcache.py to pass now.

% bzr diff curtin/commands/curthooks.py
=== modified file 'curtin/commands/curthooks.py'
--- curtin/commands/curthooks.py 2015-11-17 19:10:43 +0000
+++ curtin/commands/curthooks.py 2015-11-18 03:30:19 +0000
@@ -591,6 +591,12 @@
             'bridge-utils': ['bridge']},
     }

+ format_configs = {
+ 'xfsprogs': ['xfs'],
+ 'e2fsprogs': ['ext2', 'ext3', 'ext4'],
+ 'brtfs-tools': ['btrfs'],
+ }
+
     needed_packages = []
     installed_packages = get_installed_packages(target)
     for cust_cfg, pkg_reqs in custom_configs.items():
@@ -606,6 +612,15 @@
                pkg not in installed_packages:
                 needed_packages.append(pkg)

+ format_types = set(
+ [operation['fstype']
+ for operation in cfg[cust_cfg]['config']
+ if operation['type'] == 'format'])
+ for pkg, fstypes in format_configs.items():
+ if set(fstypes).intersection(format_types) and \
+ pkg not in installed_packages:
+ needed_packages.append(pkg)
+
     if needed_packages:
         util.install_packages(needed_packages, target=target)

review: Needs Fixing
Revision history for this message
Ryan Harper (raharper) wrote :

test_network.py:TrustyTestBasic fails due to missing python?-oauth package not installed.

Fixed with:

% bzr diff curtin/deps/__init__.py
=== modified file 'curtin/deps/__init__.py'
--- curtin/deps/__init__.py 2015-11-17 19:10:04 +0000
+++ curtin/deps/__init__.py 2015-11-18 06:09:41 +0000
@@ -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-oauthlib'),
 ]

 REQUIRED_EXECUTABLES = [

review: Needs Fixing
Revision history for this message
Ryan Harper (raharper) wrote :

% bzr diff curtin/deps/__init__.py
=== modified file 'curtin/deps/__init__.py'
--- curtin/deps/__init__.py 2015-11-17 19:10:04 +0000
+++ curtin/deps/__init__.py 2015-11-18 12:22:35 +0000
@@ -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-oauthlib'),
 ]

 REQUIRED_EXECUTABLES = [
     # 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(field='codename') == "precise":
    REQUIRED_EXECUTABLES.append(('make-bcache', 'bcache-utils',))

lp:~smoser/curtin/merge-critical updated
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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)

Subscribers

People subscribed via source and target branches