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
=== modified file 'bin/curtin'
--- bin/curtin 2015-03-09 16:35:00 +0000
+++ bin/curtin 2015-11-18 18:13:59 +0000
@@ -1,8 +1,7 @@
1#!/bin/sh1#!/bin/sh
2PY3OR2_MAIN="curtin.commands.main"2PY3OR2_MAIN="curtin.commands.main"
3PY3OR2_MCHECK="curtin.deps.check"3PY3OR2_MCHECK="curtin.deps.check"
4PY3OR2_MINSTALL="curtin.deps.install"4PY3OR2_PYTHONS=${PY3OR2_PYTHONS:-"python3:python"}
5PY3OR2_PYTHONS=${PY3OR2_PYTHONS:-"python3:python2:python"}
6PYTHON=${PY3OR2_PYTHON}5PYTHON=${PY3OR2_PYTHON}
76
8debug() {7debug() {
@@ -27,25 +26,28 @@
27if [ ! -n "$PYTHON" ]; then26if [ ! -n "$PYTHON" ]; then
28 first_exe=""27 first_exe=""
29 oifs="$IFS"; IFS=":"28 oifs="$IFS"; IFS=":"
29 best=0
30 best_exe=""
30 for p in $PY3OR2_PYTHONS; do31 for p in $PY3OR2_PYTHONS; do
31 command -v "$p" >/dev/null 2>&1 ||32 command -v "$p" >/dev/null 2>&1 ||
32 { debug "$p: not in path"; continue; }33 { debug "$p: not in path"; continue; }
34 [ -z "$PY3OR2_MCHECK" ] && PYTHON=$p && break
35 out=$($p -m "$PY3OR2_MCHECK" "$@" 2>&1) && PYTHON="$p" &&
36 { debug "$p passed check [$p -m $PY3OR2_MCHECK $*]"; break; }
37 ret=$?
38 debug "$p [$ret]: $out"
39 # exit code of 1 is unuseable
40 [ $ret -eq 1 ] && continue
33 [ -n "$first_exe" ] || first_exe="$p"41 [ -n "$first_exe" ] || first_exe="$p"
34 [ -z "$PY3OR2_MCHECK" ] && PYTHON=$p && break42 # higher non-zero exit values indicate more plausible usability
35 out=$($p -m "$PY3OR2_MCHECK" 2>&1) && PYTHON="$p" && break43 [ $best -lt $ret ] && best_exe="$p" && best=$ret &&
36 debug "$p: $out"44 debug "current best: $best_exe"
37 done45 done
38 IFS="$oifs"46 IFS="$oifs"
39 if [ -z "$PYTHON" ]; then47 [ -z "$best_exe" -a -n "$first_exe" ] && best_exe="$first_exe"
40 if [ -n "$first_exe" -a -n "$PY3OR2_MINSTALL" ]; then48 [ -n "$PYTHON" ] || PYTHON="$best_exe"
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 [ -n "$PYTHON" ] ||49 [ -n "$PYTHON" ] ||
48 fail "no availble python? [PY3OR2_DEBUG=1 for more info]"50 fail "no availble python? [PY3OR2_DEBUG=1 for more info]"
49fi51fi
50debug "executing: $PYTHON -m "$PY3OR2_MAIN" $*"52debug "executing: $PYTHON -m \"$PY3OR2_MAIN\" $*"
51exec $PYTHON -m "$PY3OR2_MAIN" "$@"53exec $PYTHON -m "$PY3OR2_MAIN" "$@"
5254
=== modified file 'curtin/commands/block_meta.py'
--- curtin/commands/block_meta.py 2015-11-17 22:03:23 +0000
+++ curtin/commands/block_meta.py 2015-11-18 18:13:59 +0000
@@ -31,14 +31,12 @@
31import sys31import sys
32import tempfile32import tempfile
33import time33import time
34import re
3435
35SIMPLE = 'simple'36SIMPLE = 'simple'
36SIMPLE_BOOT = 'simple-boot'37SIMPLE_BOOT = 'simple-boot'
37CUSTOM = 'custom'38CUSTOM = 'custom'
3839
39CUSTOM_REQUIRED_PACKAGES = ['mdadm', 'lvm2', 'bcache-tools',
40 'btrfs-tools', 'xfsprogs']
41
42CMD_ARGUMENTS = (40CMD_ARGUMENTS = (
43 ((('-D', '--devices'),41 ((('-D', '--devices'),
44 {'help': 'which devices to operate on', 'action': 'append',42 {'help': 'which devices to operate on', 'action': 'append',
@@ -155,20 +153,43 @@
155 util.subp(cmd, rcs=[0, 1, 2, 5], capture=True)153 util.subp(cmd, rcs=[0, 1, 2, 5], capture=True)
156154
157155
158def get_holders(devname):156def block_find_sysfs_path(devname):
157 # Look up any block device holders. Handle devices and partitions
158 # as devnames (vdb, md0, vdb7)
159 if not devname:159 if not devname:
160 return []160 return []
161161
162 devname_sysfs = \162 sys_class_block = '/sys/class/block/'
163 '/sys/class/block/{}/holders'.format(os.path.basename(devname))163 basename = os.path.basename(devname)
164 if not os.path.exists(devname_sysfs):164 # try without parent blockdevice, then prepend parent
165 err = ('No sysfs path to device holders:'165 paths = [
166 os.path.join(sys_class_block, basename),
167 os.path.join(sys_class_block,
168 re.split('[\d+]', basename)[0], basename),
169 ]
170
171 # find path to devname directory in sysfs
172 devname_sysfs = None
173 for path in paths:
174 if os.path.exists(path):
175 devname_sysfs = path
176
177 if devname_sysfs is None:
178 err = ('No sysfs path to device:'
166 ' {}'.format(devname_sysfs))179 ' {}'.format(devname_sysfs))
167 LOG.error(err)180 LOG.error(err)
168 raise ValueError(err)181 raise ValueError(err)
169182
170 LOG.debug('Getting blockdev holders: {}'.format(devname_sysfs))183 return devname_sysfs
171 return os.listdir(devname_sysfs)184
185
186def get_holders(devname):
187 devname_sysfs = block_find_sysfs_path(devname)
188 if devname_sysfs:
189 LOG.debug('Getting blockdev holders: {}'.format(devname_sysfs))
190 return os.listdir(os.path.join(devname_sysfs, 'holders'))
191
192 return []
172193
173194
174def clear_holders(sys_block_path):195def clear_holders(sys_block_path):
@@ -892,7 +913,7 @@
892 if mdname:913 if mdname:
893 mdnameparm = "--name=%s" % info.get('mdname')914 mdnameparm = "--name=%s" % info.get('mdname')
894915
895 cmd = ["yes", "|", "mdadm", "--create", "/dev/%s" % info.get('name'),916 cmd = ["mdadm", "--create", "/dev/%s" % info.get('name'), "--run",
896 "--level=%s" % raidlevel, "--raid-devices=%s" % len(device_paths),917 "--level=%s" % raidlevel, "--raid-devices=%s" % len(device_paths),
897 mdnameparm]918 mdnameparm]
898919
@@ -911,7 +932,11 @@
911 cmd.append(device)932 cmd.append(device)
912933
913 # Create the raid device934 # Create the raid device
935 util.subp(["udevadm", "settle"])
936 util.subp(["udevadm", "control", "--stop-exec-queue"])
914 util.subp(" ".join(cmd), shell=True)937 util.subp(" ".join(cmd), shell=True)
938 util.subp(["udevadm", "control", "--start-exec-queue"])
939 util.subp(["udevadm", "settle"])
915940
916 # Make dname rule for this dev941 # Make dname rule for this dev
917 make_dname(info.get('id'), storage_config)942 make_dname(info.get('id'), storage_config)
@@ -945,17 +970,37 @@
945 cache_mode = info.get('cache_mode', None)970 cache_mode = info.get('cache_mode', None)
946971
947 if not backing_device or not cache_device:972 if not backing_device or not cache_device:
948 raise ValueError("backing device and cache device for bcache must be \973 raise ValueError("backing device and cache device for bcache"
949 specified")974 " must be specified")
950975
951 # The bcache module is not loaded when bcache is installed by apt-get, so976 # The bcache module is not loaded when bcache is installed by apt-get, so
952 # we will load it now977 # we will load it now
953 util.subp(["modprobe", "bcache"])978 util.subp(["modprobe", "bcache"])
954979
955 # If both the backing device and cache device are specified at the same980 if cache_device:
956 # time than it is not necessary to attach the cache device manually, as981 # /sys/class/block/XXX/YYY/
957 # bcache will do this automatically.982 cache_device_sysfs = block_find_sysfs_path(cache_device)
958 util.subp(["make-bcache", "-B", backing_device, "-C", cache_device])983
984 if os.path.exists(os.path.join(cache_device_sysfs, "bcache")):
985 # read in cset uuid from cache device
986 (out, err) = util.subp(["bcache-super-show", cache_device],
987 capture=True)
988 LOG.debug('out=[{}]'.format(out))
989 [cset_uuid] = [line.split()[-1] for line in out.split("\n")
990 if line.startswith('cset.uuid')]
991
992 else:
993 # make the cache device, extracting cacheset uuid
994 (out, err) = util.subp(["make-bcache", "-C", cache_device],
995 capture=True)
996 LOG.debug('out=[{}]'.format(out))
997 [cset_uuid] = [line.split()[-1] for line in out.split("\n")
998 if line.startswith('Set UUID:')]
999
1000 if backing_device:
1001 backing_device_sysfs = block_find_sysfs_path(backing_device)
1002 if not os.path.exists(os.path.join(backing_device_sysfs, "bcache")):
1003 util.subp(["make-bcache", "-B", backing_device])
9591004
960 # Some versions of bcache-tools will register the bcache device as soon as1005 # Some versions of bcache-tools will register the bcache device as soon as
961 # we run make-bcache using udev rules, so wait for udev to settle, then try1006 # we run make-bcache using udev rules, so wait for udev to settle, then try
@@ -971,6 +1016,22 @@
971 fp.write(path)1016 fp.write(path)
972 fp.close()1017 fp.close()
9731018
1019 # if we specify both then we need to attach backing to cache
1020 if cache_device and backing_device:
1021 if cset_uuid:
1022 LOG.info("Attaching backing device to cacheset: "
1023 "{} -> {} cset.uuid: {}".format(backing_device,
1024 cache_device,
1025 cset_uuid))
1026 attach = os.path.join(backing_device_sysfs,
1027 "bcache",
1028 "attach")
1029 with open(attach, "w") as fp:
1030 fp.write(cset_uuid)
1031 else:
1032 LOG.error("Invalid cset_uuid: {}".format(cset_uuid))
1033 raise
1034
974 if cache_mode:1035 if cache_mode:
975 # find the actual bcache device name via sysfs using the1036 # find the actual bcache device name via sysfs using the
976 # backing device's holders directory.1037 # backing device's holders directory.
@@ -999,19 +1060,6 @@
999 not supported")1060 not supported")
10001061
10011062
1002def install_missing_packages_for_meta_custom():
1003 """Install all the missing package that `meta_custom` requires to
1004 function properly."""
1005 missing_packages = [
1006 package
1007 for package in CUSTOM_REQUIRED_PACKAGES
1008 if not util.has_pkg_installed(package)
1009 ]
1010 if len(missing_packages) > 0:
1011 util.apt_update()
1012 util.install_packages(missing_packages)
1013
1014
1015def meta_custom(args):1063def meta_custom(args):
1016 """Does custom partitioning based on the layout provided in the config1064 """Does custom partitioning based on the layout provided in the config
1017 file. Section with the name storage contains information on which1065 file. Section with the name storage contains information on which
@@ -1034,9 +1082,6 @@
1034 state = util.load_command_environment()1082 state = util.load_command_environment()
1035 cfg = config.load_command_config(args, state)1083 cfg = config.load_command_config(args, state)
10361084
1037 # make sure the required packages are installed
1038 install_missing_packages_for_meta_custom()
1039
1040 storage_config = cfg.get('storage', {})1085 storage_config = cfg.get('storage', {})
1041 if not storage_config:1086 if not storage_config:
1042 raise Exception("storage configuration is required by mode '%s' "1087 raise Exception("storage configuration is required by mode '%s' "
10431088
=== modified file 'curtin/commands/curthooks.py'
--- curtin/commands/curthooks.py 2015-10-02 19:59:11 +0000
+++ curtin/commands/curthooks.py 2015-11-18 18:13:59 +0000
@@ -591,6 +591,12 @@
591 'bridge-utils': ['bridge']},591 'bridge-utils': ['bridge']},
592 }592 }
593593
594 format_configs = {
595 'xfsprogs': ['xfs'],
596 'e2fsprogs': ['ext2', 'ext3', 'ext4'],
597 'brtfs-tools': ['btrfs'],
598 }
599
594 needed_packages = []600 needed_packages = []
595 installed_packages = get_installed_packages(target)601 installed_packages = get_installed_packages(target)
596 for cust_cfg, pkg_reqs in custom_configs.items():602 for cust_cfg, pkg_reqs in custom_configs.items():
@@ -606,6 +612,15 @@
606 pkg not in installed_packages:612 pkg not in installed_packages:
607 needed_packages.append(pkg)613 needed_packages.append(pkg)
608614
615 format_types = set(
616 [operation['fstype']
617 for operation in cfg[cust_cfg]['config']
618 if operation['type'] == 'format'])
619 for pkg, fstypes in format_configs.items():
620 if set(fstypes).intersection(format_types) and \
621 pkg not in installed_packages:
622 needed_packages.append(pkg)
623
609 if needed_packages:624 if needed_packages:
610 util.install_packages(needed_packages, target=target)625 util.install_packages(needed_packages, target=target)
611626
@@ -687,6 +702,10 @@
687 "mdadm.conf")702 "mdadm.conf")
688 if os.path.exists(mdadm_location):703 if os.path.exists(mdadm_location):
689 copy_mdadm_conf(mdadm_location, target)704 copy_mdadm_conf(mdadm_location, target)
705 # as per https://bugs.launchpad.net/ubuntu/+source/mdadm/+bug/964052
706 # reconfigure mdadm
707 util.subp(['chroot', target, 'dpkg-reconfigure',
708 '--frontend=noninteractive', 'mdadm'], data=None)
690709
691 # If udev dname rules were created, copy them to target710 # If udev dname rules were created, copy them to target
692 udev_rules_d = os.path.join(state['scratch'], "rules.d")711 udev_rules_d = os.path.join(state['scratch'], "rules.d")
693712
=== modified file 'curtin/commands/main.py'
--- curtin/commands/main.py 2015-10-20 16:56:30 +0000
+++ curtin/commands/main.py 2015-11-18 18:13:59 +0000
@@ -23,8 +23,7 @@
2323
24from .. import log24from .. import log
25from .. import util25from .. import util
26from .. import config26from ..deps import install_deps
27from ..reporter import (events, update_configuration)
2827
29SUB_COMMAND_MODULES = ['apply_net', 'block-meta', 'curthooks', 'extract',28SUB_COMMAND_MODULES = ['apply_net', 'block-meta', 'curthooks', 'extract',
30 'hook', 'in-target', 'install', 'mkfs', 'net-meta',29 'hook', 'in-target', 'install', 'mkfs', 'net-meta',
@@ -43,20 +42,20 @@
43 popfunc(subparser.add_parser(subcmd))42 popfunc(subparser.add_parser(subcmd))
4443
4544
46def main(args=None):45class NoHelpParser(argparse.ArgumentParser):
47 if args is None:46 # ArgumentParser with forced 'add_help=False'
48 args = sys.argv47 def __init__(self, *args, **kwargs):
4948 kwargs.update({'add_help': False})
50 parser = argparse.ArgumentParser()49 super(NoHelpParser, self).__init__(*args, **kwargs)
5150
52 stacktrace = (os.environ.get('CURTIN_STACKTRACE', "0").lower()51 def error(self, message):
53 not in ("0", "false", ""))52 # without overriding this, argparse exits with bad usage
5453 raise ValueError("failed parsing arguments: %s" % message)
55 try:54
56 verbosity = int(os.environ.get('CURTIN_VERBOSITY', "0"))55
57 except ValueError:56def get_main_parser(stacktrace=False, verbosity=0,
58 verbosity = 157 parser_class=argparse.ArgumentParser):
5958 parser = parser_class(prog='curtin')
60 parser.add_argument('--showtrace', action='store_true', default=stacktrace)59 parser.add_argument('--showtrace', action='store_true', default=stacktrace)
61 parser.add_argument('-v', '--verbose', action='count', default=verbosity,60 parser.add_argument('-v', '--verbose', action='count', default=verbosity,
62 dest='verbosity')61 dest='verbosity')
@@ -66,6 +65,9 @@
66 help='read configuration from cfg',65 help='read configuration from cfg',
67 metavar='FILE', type=argparse.FileType("rb"),66 metavar='FILE', type=argparse.FileType("rb"),
68 dest='main_cfgopts', default=[])67 dest='main_cfgopts', default=[])
68 parser.add_argument('--install-deps', action='store_true',
69 help='install dependencies as necessary',
70 default=False)
69 parser.add_argument('--set', action=util.MergedCmdAppend,71 parser.add_argument('--set', action=util.MergedCmdAppend,
70 help=('define a config variable. key can be a "/" '72 help=('define a config variable. key can be a "/" '
71 'delimited path ("early_commands/cmd1=a"). if '73 'delimited path ("early_commands/cmd1=a"). if '
@@ -75,6 +77,66 @@
75 parser.set_defaults(config={})77 parser.set_defaults(config={})
76 parser.set_defaults(reportstack=None)78 parser.set_defaults(reportstack=None)
7779
80 return parser
81
82
83def maybe_install_deps(args, stacktrace=True, verbosity=0):
84 parser = get_main_parser(stacktrace=stacktrace, verbosity=verbosity,
85 parser_class=NoHelpParser)
86 subps = parser.add_subparsers(dest="subcmd", parser_class=NoHelpParser)
87 for subcmd in SUB_COMMAND_MODULES:
88 subps.add_parser(subcmd)
89
90 install_only_args = [
91 ['-v', '--install-deps'],
92 ['-vv', '--install-deps'],
93 ['--install-deps', '-v'],
94 ['--install-deps', '-vv'],
95 ['--install-deps'],
96 ]
97
98 install_only = args in install_only_args
99
100 if install_only:
101 verbosity = 1
102 else:
103 try:
104 ns, unknown = parser.parse_known_args(args)
105 verbosity = ns.verbosity
106 if not ns.install_deps:
107 return
108 except ValueError as e:
109 # bad usage will be reported by the real reporter
110 return
111
112 ret = install_deps(verbosity=verbosity)
113
114 if ret != 0 or install_only:
115 sys.exit(ret)
116
117 return
118
119
120def main(args=None):
121 if args is None:
122 args = sys.argv
123
124 stacktrace = (os.environ.get('CURTIN_STACKTRACE', "0").lower()
125 not in ("0", "false", ""))
126
127 try:
128 verbosity = int(os.environ.get('CURTIN_VERBOSITY', "0"))
129 except ValueError:
130 verbosity = 1
131
132 maybe_install_deps(sys.argv[1:], stacktrace=stacktrace,
133 verbosity=verbosity)
134
135 # Above here, only standard library modules can be assumed.
136 from .. import config
137 from ..reporter import (events, update_configuration)
138
139 parser = get_main_parser(stacktrace=stacktrace, verbosity=verbosity)
78 subps = parser.add_subparsers(dest="subcmd")140 subps = parser.add_subparsers(dest="subcmd")
79 for subcmd in SUB_COMMAND_MODULES:141 for subcmd in SUB_COMMAND_MODULES:
80 add_subcmd(subps, subcmd)142 add_subcmd(subps, subcmd)
81143
=== modified file 'curtin/deps/__init__.py'
--- curtin/deps/__init__.py 2015-03-06 22:06:40 +0000
+++ curtin/deps/__init__.py 2015-11-18 18:13:59 +0000
@@ -1,1 +1,163 @@
1# Copyright (C) 2015 Canonical Ltd.
2#
3# Author: Scott Moser <scott.moser@canonical.com>
4#
5# Curtin is free software: you can redistribute it and/or modify it under
6# the terms of the GNU Affero General Public License as published by the
7# Free Software Foundation, either version 3 of the License, or (at your
8# option) any later version.
9#
10# Curtin is distributed in the hope that it will be useful, but WITHOUT ANY
11# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
13# more details.
14#
15# You should have received a copy of the GNU Affero General Public License
16# along with Curtin. If not, see <http://www.gnu.org/licenses/>.
17import os
18import sys
19
20from curtin.util import (which, install_packages, lsb_release,
21 ProcessExecutionError)
22
23REQUIRED_IMPORTS = [
24 # import string to execute, python2 package, python3 package
25 ('import yaml', 'python-yaml', 'python3-yaml'),
26]
27
28REQUIRED_EXECUTABLES = [
29 # executable in PATH, package
30 ('lvcreate', 'lvm2'),
31 ('mdadm', 'mdadm'),
32 ('mkfs.btrfs', 'btrfs-tools'),
33 ('mkfs.ext4', 'e2fsprogs'),
34 ('mkfs.xfs', 'xfsprogs'),
35 ('partprobe', 'parted'),
36 ('sgdisk', 'gdisk'),
37 ('udevadm', 'udev'),
38]
39
40if lsb_release()['codename'] == "precise":
41 REQUIRED_IMPORTS.append(
42 ('import oauth.oauth', 'python-oauth', None),)
43else:
44 REQUIRED_EXECUTABLES.append(('make-bcache', 'bcache-tools',))
45 REQUIRED_IMPORTS.append(
46 ('import oauthlib.oauth1', 'python-oauthlib', 'python3-oauthlib'),)
47
48
49class MissingDeps(Exception):
50 def __init__(self, message, deps):
51 self.message = message
52 if isinstance(deps, str) or deps is None:
53 deps = [deps]
54 self.deps = [d for d in deps if d is not None]
55 self.fatal = None in deps
56
57 def __str__(self):
58 if self.fatal:
59 if not len(self.deps):
60 return self.message + " Unresolvable."
61 return (self.message +
62 " Unresolvable. Partially resolvable with packages: %s" %
63 ' '.join(self.deps))
64 else:
65 return self.message + " Install packages: %s" % ' '.join(self.deps)
66
67
68def check_import(imports, py2pkgs, py3pkgs, message=None):
69 import_group = imports
70 if isinstance(import_group, str):
71 import_group = [import_group]
72
73 for istr in import_group:
74 try:
75 exec(istr)
76 return
77 except ImportError:
78 pass
79
80 if not message:
81 if isinstance(imports, str):
82 message = "Failed '%s'." % imports
83 else:
84 message = "Unable to do any of %s." % import_group
85
86 if sys.version_info[0] == 2:
87 pkgs = py2pkgs
88 else:
89 pkgs = py3pkgs
90
91 raise MissingDeps(message, pkgs)
92
93
94def check_executable(cmdname, pkg):
95 if not which(cmdname):
96 raise MissingDeps("Missing program '%s'." % cmdname, pkg)
97
98
99def check_executables(executables=None):
100 if executables is None:
101 executables = REQUIRED_EXECUTABLES
102 mdeps = []
103 for exe, pkg in executables:
104 try:
105 check_executable(exe, pkg)
106 except MissingDeps as e:
107 mdeps.append(e)
108 return mdeps
109
110
111def check_imports(imports=None):
112 if imports is None:
113 imports = REQUIRED_IMPORTS
114
115 mdeps = []
116 for import_str, py2pkg, py3pkg in imports:
117 try:
118 check_import(import_str, py2pkg, py3pkg)
119 except MissingDeps as e:
120 mdeps.append(e)
121 return mdeps
122
123
124def find_missing_deps():
125 return check_executables() + check_imports()
126
127
128def install_deps(verbosity=False, dry_run=False, allow_daemons=True):
129 errors = find_missing_deps()
130 if len(errors) == 0:
131 if verbosity:
132 sys.stderr.write("No missing dependencies\n")
133 return 0
134
135 missing_pkgs = []
136 for e in errors:
137 missing_pkgs += e.deps
138
139 deps_string = ' '.join(sorted(missing_pkgs))
140
141 if dry_run:
142 sys.stderr.write("Missing dependencies: %s\n" % deps_string)
143 return 0
144
145 if os.geteuid() != 0:
146 sys.stderr.write("Missing dependencies: %s\n" % deps_string)
147 sys.stderr.write("Package installation is not possible as non-root.\n")
148 return 2
149
150 if verbosity:
151 sys.stderr.write("Installing %s\n" % deps_string)
152
153 ret = 0
154 try:
155 install_packages(missing_pkgs, allow_daemons=allow_daemons)
156 except ProcessExecutionError as e:
157 sys.stderr.write("%s\n" % e)
158 ret = e.exit_code
159
160 return ret
161
162
1# vi: ts=4 expandtab syntax=python163# vi: ts=4 expandtab syntax=python
2164
=== modified file 'curtin/deps/check.py'
--- curtin/deps/check.py 2015-08-10 14:33:03 +0000
+++ curtin/deps/check.py 2015-11-18 18:13:59 +0000
@@ -20,29 +20,57 @@
20and exit success or fail, indicating that deps should be there.20and exit success or fail, indicating that deps should be there.
21 python -m curtin.deps.check [-v]21 python -m curtin.deps.check [-v]
22"""22"""
23_imports = (23import argparse
24 "from ..commands import main",24import sys
25 "import yaml",25
26)26from . import find_missing_deps
2727
2828
29def _check_imports(imports=_imports):29def debug(level, msg_level, msg):
30 errors = []30 if level >= msg_level:
31 for istr in _imports:31 if msg[-1] != "\n":
32 try:32 msg += "\n"
33 exec(istr)33 sys.stderr.write(msg)
34 except ImportError as e:34
35 errors.append("failed '%s': %s" % (istr, e))35
3636def main():
37 return errors37 parser = argparse.ArgumentParser(
38 prog='curtin-check-deps',
39 description='check dependencies for curtin.')
40 parser.add_argument('-v', '--verbose', action='count', default=0,
41 dest='verbosity')
42 args, extra = parser.parse_known_args(sys.argv[1:])
43
44 errors = find_missing_deps()
45
46 if len(errors) == 0:
47 # exit 0 means all dependencies are available.
48 debug(args.verbosity, 1, "No missing dependencies")
49 sys.exit(0)
50
51 missing_pkgs = []
52 fatal = []
53 for e in errors:
54 if e.fatal:
55 fatal.append(e)
56 debug(args.verbosity, 2, str(e))
57 missing_pkgs += e.deps
58
59 if len(fatal):
60 for e in fatal:
61 debug(args.verbosity, 1, str(e))
62 sys.exit(1)
63
64 debug(args.verbosity, 1,
65 "Fix with:\n apt-get -qy install %s\n" %
66 ' '.join(sorted(missing_pkgs)))
67
68 # we exit higher with less deps needed.
69 # exiting 99 means just 1 dep needed.
70 sys.exit(100-len(missing_pkgs))
71
3872
39if __name__ == '__main__':73if __name__ == '__main__':
40 import sys74 main()
41 verbose = False75
42 if len(sys.argv) > 1 and sys.argv[1] in ("-v", "--verbose"):76# vi: ts=4 expandtab syntax=python
43 verbose = True
44 errors = _check_imports()
45 if verbose:
46 for emsg in errors:
47 sys.stderr.write("%s\n" % emsg)
48 sys.exit(len(errors))
4977
=== modified file 'curtin/deps/install.py'
--- curtin/deps/install.py 2015-08-10 14:33:03 +0000
+++ curtin/deps/install.py 2015-11-18 18:13:59 +0000
@@ -20,33 +20,29 @@
20 python -m curtin.deps.install [-v]20 python -m curtin.deps.install [-v]
21"""21"""
2222
23import subprocess23import argparse
24import sys24import sys
25import time25
2626from . import install_deps
2727
28def runcmd(cmd, retries=[]):28
29 for wait in retries:29def main():
30 try:30 parser = argparse.ArgumentParser(
31 subprocess.check_call(cmd)31 prog='curtin-install-deps',
32 return 032 description='install dependencies for curtin.')
33 except subprocess.CalledProcessError as e:33 parser.add_argument('-v', '--verbose', action='count', default=0,
34 sys.stderr.write("%s failed. sleeping %s\n" % (cmd, wait))34 dest='verbosity')
35 time.sleep(wait)35 parser.add_argument('--dry-run', action='store_true', default=False)
36 try:36 parser.add_argument('--no-allow-daemons', action='store_false',
37 subprocess.check_call(cmd)37 default=True)
38 except subprocess.CalledProcessError as e:38 args = parser.parse_args(sys.argv[1:])
39 return e.returncode39
40 ret = install_deps(verbose=args.verbosity, dry_run=args.dry_run,
41 allow_daemons=True)
42 sys.exit(ret)
43
4044
41if __name__ == '__main__':45if __name__ == '__main__':
42 if sys.version_info[0] == 2:46 main()
43 pkgs = ['python-yaml']47
44 else:48# vi: ts=4 expandtab syntax=python
45 pkgs = ['python3-yaml']
46
47 ret = runcmd(['apt-get', '--quiet', 'update'], retries=[2, 4])
48 if ret != 0:
49 sys.exit(ret)
50
51 ret = runcmd(['apt-get', 'install', '--quiet', '--assume-yes'] + pkgs)
52 sys.exit(ret)
5349
=== modified file 'curtin/pack.py'
--- curtin/pack.py 2015-03-10 17:17:55 +0000
+++ curtin/pack.py 2015-11-18 18:13:59 +0000
@@ -26,7 +26,6 @@
26#!/bin/sh26#!/bin/sh
27PY3OR2_MAIN="%(ep_main)s"27PY3OR2_MAIN="%(ep_main)s"
28PY3OR2_MCHECK="%(ep_mcheck)s"28PY3OR2_MCHECK="%(ep_mcheck)s"
29PY3OR2_MINSTALL="%(ep_minstall)s"
30PY3OR2_PYTHONS=${PY3OR2_PYTHONS:-"%(python_exe_list)s"}29PY3OR2_PYTHONS=${PY3OR2_PYTHONS:-"%(python_exe_list)s"}
31PYTHON=${PY3OR2_PYTHON}30PYTHON=${PY3OR2_PYTHON}
32""".strip()31""".strip()
@@ -54,41 +53,42 @@
54if [ ! -n "$PYTHON" ]; then53if [ ! -n "$PYTHON" ]; then
55 first_exe=""54 first_exe=""
56 oifs="$IFS"; IFS=":"55 oifs="$IFS"; IFS=":"
56 best=0
57 best_exe=""
57 for p in $PY3OR2_PYTHONS; do58 for p in $PY3OR2_PYTHONS; do
58 command -v "$p" >/dev/null 2>&1 ||59 command -v "$p" >/dev/null 2>&1 ||
59 { debug "$p: not in path"; continue; }60 { debug "$p: not in path"; continue; }
61 [ -z "$PY3OR2_MCHECK" ] && PYTHON=$p && break
62 out=$($p -m "$PY3OR2_MCHECK" "$@" 2>&1) && PYTHON="$p" &&
63 { debug "$p passed check [$p -m $PY3OR2_MCHECK $*]"; break; }
64 ret=$?
65 debug "$p [$ret]: $out"
66 # exit code of 1 is unuseable
67 [ $ret -eq 1 ] && continue
60 [ -n "$first_exe" ] || first_exe="$p"68 [ -n "$first_exe" ] || first_exe="$p"
61 [ -z "$PY3OR2_MCHECK" ] && PYTHON=$p && break69 # higher non-zero exit values indicate more plausible usability
62 out=$($p -m "$PY3OR2_MCHECK" 2>&1) && PYTHON="$p" && break70 [ $best -lt $ret ] && best_exe="$p" && best=$ret &&
63 debug "$p: $out"71 debug "current best: $best_exe"
64 done72 done
65 IFS="$oifs"73 IFS="$oifs"
66 if [ -z "$PYTHON" ]; then74 [ -z "$best_exe" -a -n "$first_exe" ] && best_exe="$first_exe"
67 if [ -n "$first_exe" -a -n "$PY3OR2_MINSTALL" ]; then75 [ -n "$PYTHON" ] || PYTHON="$best_exe"
68 debug "attempting deps install with $first_exe $PY3OR2_MINSTALL"
69 "$first_exe" -m "$PY3OR2_MINSTALL" ||
70 fail "failed to install deps!"
71 PYTHON="$first_exe"
72 fi
73 fi
74 [ -n "$PYTHON" ] ||76 [ -n "$PYTHON" ] ||
75 fail "no availble python? [PY3OR2_DEBUG=1 for more info]"77 fail "no availble python? [PY3OR2_DEBUG=1 for more info]"
76fi78fi
77debug "executing: $PYTHON -m \"$PY3OR2_MAIN\" $*"79debug "executing: $PYTHON -m \\"$PY3OR2_MAIN\\" $*"
78exec $PYTHON -m "$PY3OR2_MAIN" "$@"80exec $PYTHON -m "$PY3OR2_MAIN" "$@"
79"""81"""
8082
8183
82def write_exe_wrapper(entrypoint, path=None, interpreter=None,84def write_exe_wrapper(entrypoint, path=None, interpreter=None,
83 deps_check_entry=None, deps_install_entry=None,85 deps_check_entry=None, mode=0o755):
84 mode=0o755):
85 if not interpreter:86 if not interpreter:
86 interpreter = "python3:python2:python"87 interpreter = "python3:python"
8788
88 subs = {89 subs = {
89 'ep_main': entrypoint,90 'ep_main': entrypoint,
90 'ep_mcheck': deps_check_entry if deps_check_entry else "",91 'ep_mcheck': deps_check_entry if deps_check_entry else "",
91 'ep_minstall': deps_install_entry if deps_install_entry else "",
92 'python_exe_list': interpreter,92 'python_exe_list': interpreter,
93 }93 }
9494
@@ -140,9 +140,7 @@
140 ignore=not_dot_py)140 ignore=not_dot_py)
141 write_exe_wrapper(entrypoint='curtin.commands.main',141 write_exe_wrapper(entrypoint='curtin.commands.main',
142 path=os.path.join(bindir, 'curtin'),142 path=os.path.join(bindir, 'curtin'),
143 deps_check_entry="curtin.deps.check",143 deps_check_entry="curtin.deps.check")
144 deps_install_entry="curtin.deps.install"
145 )
146144
147 for archpath, filepath in copy_files:145 for archpath, filepath in copy_files:
148 target = os.path.abspath(os.path.join(exdir, archpath))146 target = os.path.abspath(os.path.join(exdir, archpath))
@@ -208,7 +206,8 @@
208206
209207
210def pack_install(fdout=None, configs=None, paths=None,208def pack_install(fdout=None, configs=None, paths=None,
211 add_files=None, copy_files=None, args=None):209 add_files=None, copy_files=None, args=None,
210 install_deps=True):
212211
213 if configs is None:212 if configs is None:
214 configs = []213 configs = []
@@ -219,7 +218,12 @@
219 if args is None:218 if args is None:
220 args = []219 args = []
221220
222 command = ["curtin", "install"]221 if install_deps:
222 dep_flags = ["--install-deps"]
223 else:
224 dep_flags = []
225
226 command = ["curtin", dep_flags, "install"]
223227
224 my_files = []228 my_files = []
225 for n, config in enumerate(configs):229 for n, config in enumerate(configs):
226230
=== modified file 'curtin/util.py'
--- curtin/util.py 2015-10-26 18:03:20 +0000
+++ curtin/util.py 2015-11-18 18:13:59 +0000
@@ -30,6 +30,8 @@
30_INSTALLED_HELPERS_PATH = '/usr/lib/curtin/helpers'30_INSTALLED_HELPERS_PATH = '/usr/lib/curtin/helpers'
31_INSTALLED_MAIN = '/usr/bin/curtin'31_INSTALLED_MAIN = '/usr/bin/curtin'
3232
33_LSB_RELEASE = {}
34
3335
34def _subp(args, data=None, rcs=None, env=None, capture=False, shell=False,36def _subp(args, data=None, rcs=None, env=None, capture=False, shell=False,
35 logstring=False):37 logstring=False):
@@ -691,6 +693,31 @@
691 return (isinstance(exc, IOError) and exc.errno == errno.ENOENT)693 return (isinstance(exc, IOError) and exc.errno == errno.ENOENT)
692694
693695
696def lsb_release():
697 fmap = {'Codename': 'codename', 'Description': 'description',
698 'Distributor ID': 'id', 'Release': 'release'}
699 global _LSB_RELEASE
700 if not _LSB_RELEASE:
701 data = {}
702 try:
703 out, err = subp(['lsb_release', '--all'], capture=True)
704 for line in out.splitlines():
705 fname, tok, val = line.partition(":")
706 if fname in fmap:
707 data[fmap[fname]] = val.strip()
708 missing = [k for k in fmap.values() if k not in data]
709 if len(missing):
710 LOG.warn("Missing fields in lsb_release --all output: %s",
711 ','.join(missing))
712
713 except ProcessExecutionError as e:
714 LOG.warn("Unable to get lsb_release --all: %s", e)
715 data = {v: "UNAVAILABLE" for v in fmap.values()}
716
717 _LSB_RELEASE.update(data)
718 return _LSB_RELEASE
719
720
694class MergedCmdAppend(argparse.Action):721class MergedCmdAppend(argparse.Action):
695 """This appends to a list in order of appearence both the option string722 """This appends to a list in order of appearence both the option string
696 and the value"""723 and the value"""
697724
=== modified file 'debian/control'
--- debian/control 2015-10-07 13:44:24 +0000
+++ debian/control 2015-11-18 18:13:59 +0000
@@ -25,7 +25,15 @@
25Package: curtin25Package: curtin
26Architecture: all26Architecture: all
27Priority: extra27Priority: extra
28Depends: python3-curtin (= ${binary:Version}),28Depends: btrfs-tools,
29 e2fsprogs,
30 gdisk,
31 lvm2,
32 mdadm,
33 parted,
34 python3-curtin (= ${binary:Version}),
35 udev,
36 xfsprogs,
29 ${misc:Depends}37 ${misc:Depends}
30Description: Library and tools for the curtin installer38Description: Library and tools for the curtin installer
31 This package provides the curtin installer.39 This package provides the curtin installer.
3240
=== modified file 'examples/tests/mdadm_bcache.yaml'
--- examples/tests/mdadm_bcache.yaml 2015-10-29 19:13:25 +0000
+++ examples/tests/mdadm_bcache.yaml 2015-11-18 18:13:59 +0000
@@ -32,6 +32,10 @@
32 type: partition32 type: partition
33 size: 1GB33 size: 1GB
34 device: sda34 device: sda
35 - id: sda6
36 type: partition
37 size: 1GB
38 device: sda
35 - id: mddevice39 - id: mddevice
36 name: md040 name: md0
37 type: raid41 type: raid
@@ -47,6 +51,12 @@
47 backing_device: mddevice51 backing_device: mddevice
48 cache_device: sda552 cache_device: sda5
49 cache_mode: writeback53 cache_mode: writeback
54 - id: bcache1
55 type: bcache
56 name: cached_array_2
57 backing_device: sda6
58 cache_device: sda5
59 cache_mode: writeback
50 - id: sda1_root60 - id: sda1_root
51 type: format61 type: format
52 fstype: ext462 fstype: ext4
@@ -55,6 +65,10 @@
55 type: format65 type: format
56 fstype: ext466 fstype: ext4
57 volume: bcache067 volume: bcache0
68 - id: bcache_storage
69 type: format
70 fstype: ext4
71 volume: bcache1
58 - id: sda1_mount72 - id: sda1_mount
59 type: mount73 type: mount
60 path: /74 path: /
@@ -63,3 +77,7 @@
63 type: mount77 type: mount
64 path: /media/data78 path: /media/data
65 device: raid_storage79 device: raid_storage
80 - id: bcache1_mount
81 type: mount
82 path: /media/bcache1
83 device: bcache_storage
6684
=== added file 'examples/tests/raid5bcache.yaml'
--- examples/tests/raid5bcache.yaml 1970-01-01 00:00:00 +0000
+++ examples/tests/raid5bcache.yaml 2015-11-18 18:13:59 +0000
@@ -0,0 +1,97 @@
1storage:
2 config:
3 - grub_device: true
4 id: sda
5 model: QEMU HARDDISK
6 name: sda
7 ptable: msdos
8 path: /dev/vdb
9 type: disk
10 wipe: superblock
11 - id: sdb
12 model: QEMU HARDDISK
13 name: sdb
14 path: /dev/vdc
15 type: disk
16 wipe: superblock
17 - id: sdc
18 model: QEMU HARDDISK
19 name: sdc
20 path: /dev/vdd
21 type: disk
22 wipe: superblock
23 - id: sdd
24 model: QEMU HARDDISK
25 name: sdd
26 path: /dev/vde
27 type: disk
28 wipe: superblock
29 - id: sde
30 model: QEMU HARDDISK
31 name: sde
32 path: /dev/vdf
33 type: disk
34 wipe: superblock
35 - devices:
36 - sdc
37 - sdd
38 - sde
39 id: md0
40 name: md0
41 raidlevel: 5
42 spare_devices: []
43 type: raid
44 - device: sda
45 id: sda-part1
46 name: sda-part1
47 number: 1
48 offset: 2097152B
49 size: 1000001536B
50 type: partition
51 uuid: 3a38820c-d675-4069-b060-509a3d9d13cc
52 wipe: superblock
53 - device: sda
54 id: sda-part2
55 name: sda-part2
56 number: 2
57 size: 7586787328B
58 type: partition
59 uuid: 17747faa-4b9e-4411-97e5-12fd3d199fb8
60 wipe: superblock
61 - backing_device: sda-part2
62 cache_device: sdb
63 cache_mode: writeback
64 id: bcache0
65 name: bcache0
66 type: bcache
67 - fstype: ext4
68 id: sda-part1_format
69 label: ''
70 type: format
71 uuid: 71b1ef6f-5cab-4a77-b4c8-5a209ec11d7c
72 volume: sda-part1
73 - fstype: ext4
74 id: md0_format
75 label: ''
76 type: format
77 uuid: b031f0a0-adb3-43be-bb43-ce0fc8a224a4
78 volume: md0
79 - fstype: ext4
80 id: bcache0_format
81 label: ''
82 type: format
83 uuid: ce45bbaf-5a44-4487-b89e-035c2dd40657
84 volume: bcache0
85 - device: bcache0_format
86 id: bcache0_mount
87 path: /
88 type: mount
89 - device: sda-part1_format
90 id: sda-part1_mount
91 path: /boot
92 type: mount
93 - device: md0_format
94 id: md0_mount
95 path: /srv/data
96 type: mount
97 version: 1
098
=== modified file 'tests/unittests/test_util.py'
--- tests/unittests/test_util.py 2014-09-18 01:05:55 +0000
+++ tests/unittests/test_util.py 2015-11-18 18:13:59 +0000
@@ -1,4 +1,5 @@
1from unittest import TestCase1from unittest import TestCase
2import mock
2import os3import os
3import shutil4import shutil
4import tempfile5import tempfile
@@ -99,6 +100,45 @@
99 self.assertEqual(found, "/usr/bin2/fuzz")100 self.assertEqual(found, "/usr/bin2/fuzz")
100101
101102
103class TestLsbRelease(TestCase):
104 def setUp(self):
105 self._reset_cache()
106
107 def _reset_cache(self):
108 keys = [k for k in util._LSB_RELEASE.keys()]
109 for d in keys:
110 del util._LSB_RELEASE[d]
111
112 @mock.patch("curtin.util.subp")
113 def test_lsb_release_functional(self, mock_subp):
114 output = '\n'.join([
115 "Distributor ID: Ubuntu",
116 "Description: Ubuntu 14.04.2 LTS",
117 "Release: 14.04",
118 "Codename: trusty",
119 ])
120 rdata = {'id': 'Ubuntu', 'description': 'Ubuntu 14.04.2 LTS',
121 'codename': 'trusty', 'release': '14.04'}
122
123 def fake_subp(cmd, capture=False):
124 return output, 'No LSB modules are available.'
125
126 mock_subp.side_effect = fake_subp
127 found = util.lsb_release()
128 mock_subp.assert_called_with(['lsb_release', '--all'], capture=True)
129 self.assertEqual(found, rdata)
130
131 @mock.patch("curtin.util.subp")
132 def test_lsb_release_unavailable(self, mock_subp):
133 def doraise(*args, **kwargs):
134 raise util.ProcessExecutionError("foo")
135 mock_subp.side_effect = doraise
136
137 expected = {k: "UNAVAILABLE" for k in
138 ('id', 'description', 'codename', 'release')}
139 self.assertEqual(util.lsb_release(), expected)
140
141
102class TestSubp(TestCase):142class TestSubp(TestCase):
103143
104 def test_subp_handles_utf8(self):144 def test_subp_handles_utf8(self):
105145
=== modified file 'tests/vmtests/__init__.py'
--- tests/vmtests/__init__.py 2015-11-18 16:24:35 +0000
+++ tests/vmtests/__init__.py 2015-11-18 18:13:59 +0000
@@ -483,7 +483,15 @@
483 collect_post = textwrap.dedent(483 collect_post = textwrap.dedent(
484 'tar -C "%s" -cf "%s" .' % (output_dir, output_device))484 'tar -C "%s" -cf "%s" .' % (output_dir, output_device))
485485
486 scripts = [collect_prep] + collect_scripts + [collect_post]486 # failsafe poweroff runs on precise only, where power_state does
487 # not exist.
488 precise_poweroff = textwrap.dedent("""#!/bin/sh
489 [ "$(lsb_release -sc)" = "precise" ] || exit 0;
490 shutdown -P now "Shutting down on precise"
491 """)
492
493 scripts = ([collect_prep] + collect_scripts + [collect_post] +
494 [precise_poweroff])
487495
488 for part in scripts:496 for part in scripts:
489 if not part.startswith("#!"):497 if not part.startswith("#!"):
490498
=== modified file 'tests/vmtests/test_basic.py'
--- tests/vmtests/test_basic.py 2015-10-27 11:35:10 +0000
+++ tests/vmtests/test_basic.py 2015-11-18 18:13:59 +0000
@@ -23,7 +23,10 @@
23 cat /etc/fstab > fstab23 cat /etc/fstab > fstab
24 ls /dev/disk/by-dname/ > ls_dname24 ls /dev/disk/by-dname/ > ls_dname
2525
26 apt-config dump Acquire::HTTP::Proxy > apt-proxy26 v=""
27 out=$(apt-config shell v Acquire::HTTP::Proxy)
28 eval "$out"
29 echo "$v" > apt-proxy
27 """)]30 """)]
2831
29 def test_output_files_exist(self):32 def test_output_files_exist(self):
@@ -67,7 +70,7 @@
67 def test_proxy_set(self):70 def test_proxy_set(self):
68 expected = get_apt_proxy()71 expected = get_apt_proxy()
69 with open(os.path.join(self.td.mnt, "apt-proxy")) as fp:72 with open(os.path.join(self.td.mnt, "apt-proxy")) as fp:
70 apt_proxy_found = fp.read()73 apt_proxy_found = fp.read().rstrip()
71 if expected:74 if expected:
72 # the proxy should have gotten set through75 # the proxy should have gotten set through
73 self.assertIn(expected, apt_proxy_found)76 self.assertIn(expected, apt_proxy_found)
@@ -88,3 +91,16 @@
88 repo = "maas-daily"91 repo = "maas-daily"
89 release = "vivid"92 release = "vivid"
90 arch = "amd64"93 arch = "amd64"
94
95
96class PreciseTestBasic(TestBasicAbs, TestCase):
97 __test__ = True
98 repo = "maas-daily"
99 release = "precise"
100 arch = "amd64"
101
102 def test_ptable(self):
103 print("test_ptable does not work for Precise")
104
105 def test_dname(self):
106 print("test_dname does not work for Precise")
91107
=== modified file 'tests/vmtests/test_mdadm_bcache.py'
--- tests/vmtests/test_mdadm_bcache.py 2015-10-30 13:38:36 +0000
+++ tests/vmtests/test_mdadm_bcache.py 2015-11-18 18:13:59 +0000
@@ -44,31 +44,60 @@
44 'main_disk': 5,44 'main_disk': 5,
45 'main_disk': 6,45 'main_disk': 6,
46 'md0': 0,46 'md0': 0,
47 'cached_array': 0}47 'cached_array': 0,
48 'cached_array_2': 0}
4849
49 collect_scripts = TestMdadmAbs.collect_scripts + [textwrap.dedent("""50 collect_scripts = TestMdadmAbs.collect_scripts + [textwrap.dedent("""
50 cd OUTPUT_COLLECT_D51 cd OUTPUT_COLLECT_D
51 bcache-super-show /dev/vda6 > bcache_super_vda652 bcache-super-show /dev/vda6 > bcache_super_vda6
53 bcache-super-show /dev/vda7 > bcache_super_vda7
54 bcache-super-show /dev/md0 > bcache_super_md0
52 ls /sys/fs/bcache > bcache_ls55 ls /sys/fs/bcache > bcache_ls
53 cat /sys/block/bcache0/bcache/cache_mode > bcache_cache_mode56 cat /sys/block/bcache0/bcache/cache_mode > bcache_cache_mode
57 cat /proc/mounts > proc_mounts
54 """)]58 """)]
55 fstab_expected = {59 fstab_expected = {
56 '/dev/bcache0': '/media/data'60 '/dev/bcache0': '/media/data',
61 '/dev/bcache1': '/media/bcache1',
57 }62 }
5863
59 def test_bcache_output_files_exist(self):64 def test_bcache_output_files_exist(self):
60 self.output_files_exist(["bcache_super_vda6", "bcache_ls",65 self.output_files_exist(["bcache_super_vda6",
66 "bcache_super_vda7",
67 "bcache_super_md0",
68 "bcache_ls",
61 "bcache_cache_mode"])69 "bcache_cache_mode"])
6270
63 def test_bcache_status(self):71 def test_bcache_status(self):
72 bcache_supers = [
73 "bcache_super_vda6",
74 "bcache_super_vda7",
75 "bcache_super_md0",
76 ]
64 bcache_cset_uuid = None77 bcache_cset_uuid = None
65 with open(os.path.join(self.td.mnt, "bcache_super_vda6"), "r") as fp:78 found = {}
66 for line in fp.read().splitlines():79 for bcache_super in bcache_supers:
67 if line != "" and line.split()[0] == "cset.uuid":80 with open(os.path.join(self.td.mnt, bcache_super), "r") as fp:
68 bcache_cset_uuid = line.split()[-1].rstrip()81 for line in fp.read().splitlines():
69 self.assertIsNotNone(bcache_cset_uuid)82 if line != "" and line.split()[0] == "cset.uuid":
70 with open(os.path.join(self.td.mnt, "bcache_ls"), "r") as fp:83 bcache_cset_uuid = line.split()[-1].rstrip()
71 self.assertTrue(bcache_cset_uuid in fp.read().splitlines())84 if bcache_cset_uuid in found:
85 found[bcache_cset_uuid].append(bcache_super)
86 else:
87 found[bcache_cset_uuid] = [bcache_super]
88 self.assertIsNotNone(bcache_cset_uuid)
89 with open(os.path.join(self.td.mnt, "bcache_ls"), "r") as fp:
90 self.assertTrue(bcache_cset_uuid in fp.read().splitlines())
91
92 # one cset.uuid for all devices
93 self.assertEqual(len(found), 1)
94
95 # three devices with same cset.uuid
96 self.assertEqual(len(found[bcache_cset_uuid]), 3)
97
98 # check the cset.uuid in the dict
99 self.assertEqual(list(found.keys()).pop(),
100 bcache_cset_uuid)
72101
73 def test_bcache_cachemode(self):102 def test_bcache_cachemode(self):
74 self.check_file_regex("bcache_cache_mode", r"\[writeback\]")103 self.check_file_regex("bcache_cache_mode", r"\[writeback\]")
75104
=== added file 'tests/vmtests/test_raid5_bcache.py'
--- tests/vmtests/test_raid5_bcache.py 1970-01-01 00:00:00 +0000
+++ tests/vmtests/test_raid5_bcache.py 2015-11-18 18:13:59 +0000
@@ -0,0 +1,85 @@
1from . import VMBaseClass
2from unittest import TestCase
3
4import textwrap
5import os
6
7
8class TestMdadmAbs(VMBaseClass, TestCase):
9 __test__ = False
10 repo = "maas-daily"
11 arch = "amd64"
12 install_timeout = 600
13 boot_timeout = 100
14 interactive = False
15 extra_disks = ['10G', '10G', '10G', '10G']
16 active_mdadm = "1"
17 collect_scripts = [textwrap.dedent("""
18 cd OUTPUT_COLLECT_D
19 cat /etc/fstab > fstab
20 mdadm --detail --scan > mdadm_status
21 mdadm --detail --scan | grep -c ubuntu > mdadm_active1
22 grep -c active /proc/mdstat > mdadm_active2
23 ls /dev/disk/by-dname > ls_dname
24 """)]
25
26 def test_mdadm_output_files_exist(self):
27 self.output_files_exist(
28 ["fstab", "mdadm_status", "mdadm_active1", "mdadm_active2",
29 "ls_dname"])
30
31 def test_mdadm_status(self):
32 # ubuntu:<ID> is the name assigned to the md array
33 self.check_file_regex("mdadm_status", r"ubuntu:[0-9]*")
34 self.check_file_strippedline("mdadm_active1", self.active_mdadm)
35 self.check_file_strippedline("mdadm_active2", self.active_mdadm)
36
37
38class TestMdadmBcacheAbs(TestMdadmAbs):
39 conf_file = "examples/tests/raid5bcache.yaml"
40 disk_to_check = {'sda': 2,
41 'md0': 0}
42
43 collect_scripts = TestMdadmAbs.collect_scripts + [textwrap.dedent("""
44 cd OUTPUT_COLLECT_D
45 bcache-super-show /dev/vda2 > bcache_super_vda2
46 ls /sys/fs/bcache > bcache_ls
47 cat /sys/block/bcache0/bcache/cache_mode > bcache_cache_mode
48 cat /proc/mounts > proc_mounts
49 cat /proc/partitions > proc_partitions
50 """)]
51 fstab_expected = {
52 '/dev/bcache0': '/',
53 '/dev/md0': '/srv/data',
54 }
55
56 def test_bcache_output_files_exist(self):
57 self.output_files_exist(["bcache_super_vda2", "bcache_ls",
58 "bcache_cache_mode"])
59
60 def test_bcache_status(self):
61 bcache_cset_uuid = None
62 with open(os.path.join(self.td.mnt, "bcache_super_vda2"), "r") as fp:
63 for line in fp.read().splitlines():
64 if line != "" and line.split()[0] == "cset.uuid":
65 bcache_cset_uuid = line.split()[-1].rstrip()
66 self.assertIsNotNone(bcache_cset_uuid)
67 with open(os.path.join(self.td.mnt, "bcache_ls"), "r") as fp:
68 self.assertTrue(bcache_cset_uuid in fp.read().splitlines())
69
70 def test_bcache_cachemode(self):
71 self.check_file_regex("bcache_cache_mode", r"\[writeback\]")
72
73
74class WilyTestRaid5Bcache(TestMdadmBcacheAbs):
75 __test__ = True
76 release = "wily"
77
78
79class VividTestRaid5Bcache(TestMdadmBcacheAbs):
80 __test__ = True
81 release = "vivid"
82
83class TrustTestRaid5Bcache(TestMdadmBcacheAbs):
84 __test__ = True
85 release = "trusty"
086
=== modified file 'tools/launch'
--- tools/launch 2015-11-13 20:04:48 +0000
+++ tools/launch 2015-11-18 18:13:59 +0000
@@ -35,6 +35,8 @@
35 Note, qemu adds 5900 to port numbers. (:0 = port 5900)35 Note, qemu adds 5900 to port numbers. (:0 = port 5900)
36 --serial-log F : log to F (default 'serial.log')36 --serial-log F : log to F (default 'serial.log')
37 -v | --verbose be more verbose37 -v | --verbose be more verbose
38 --no-install-deps do not install insert '--install-deps'
39 on curtin command invocations
3840
39 use of --kernel/--initrd will seed cloud-init via cmdline41 use of --kernel/--initrd will seed cloud-init via cmdline
40 rather than the local datasource42 rather than the local datasource
@@ -99,6 +101,36 @@
99EOF101EOF
100}102}
101103
104write_pstate_config() {
105 local pstate="$1" config1="$2" config2="$3"
106 cat > "$config1" <<EOF
107#cloud-config
108power_state:
109 mode: "$pstate"
110EOF
111 sed -e "s,PSTATE,$pstate," > "$config2" <<"EOF"
112#upstart-job
113# precise does not do cloud-config poweroff
114description "power-state for precise"
115start on stopped cloud-final
116console output
117task
118script
119 [ "$(lsb_release -sc)" = "precise" ] || exit 0
120 target="PSTATE"
121 msg="precise-powerstate: $target"
122 case "$target" in
123 on) exit 0;;
124 off|poweroff) shutdown -P now "$msg";;
125 reboot) shutdown -r now "$msg";;
126 *) echo "$msg : unknown target"; exit 1;;
127 esac
128 echo "$msg"
129 exit 0
130end script
131EOF
132}
133
102write_userdata() {134write_userdata() {
103 local x135 local x
104 cat <<EOF136 cat <<EOF
@@ -219,7 +251,7 @@
219 local initrd="" kernel="" uappend="" iargs="" disk_args=""251 local initrd="" kernel="" uappend="" iargs="" disk_args=""
220 local pubs="" disks="" pstate="null"252 local pubs="" disks="" pstate="null"
221 local uefi=0253 local uefi=0
222 local netdevs=""254 local netdevs="" install_deps="--install-deps"
223 local video="-curses -vga std" serial_log="file:serial.log"255 local video="-curses -vga std" serial_log="file:serial.log"
224 # dowait: run xkvm with a '&' and then 'wait' on the pid.256 # dowait: run xkvm with a '&' and then 'wait' on the pid.
225 # the reason to do this or not do this has to do with interactivity257 # the reason to do this or not do this has to do with interactivity
@@ -246,6 +278,7 @@
246 -d|--disk) disks[${#disks[@]}]="$next"; shift;;278 -d|--disk) disks[${#disks[@]}]="$next"; shift;;
247 -h|--help) Usage ; exit 0;;279 -h|--help) Usage ; exit 0;;
248 -i|--initrd) initrd="$next"; shift;;280 -i|--initrd) initrd="$next"; shift;;
281 --no-install-deps) install_deps="";;
249 -k|--kernel) kernel="$next"; shift;;282 -k|--kernel) kernel="$next"; shift;;
250 --mem) mem="$next"; shift;;283 --mem) mem="$next"; shift;;
251 --power)284 --power)
@@ -400,6 +433,12 @@
400 addargs[${#addargs[@]}]="--add=config/${fpath##*/}:$fpath"433 addargs[${#addargs[@]}]="--add=config/${fpath##*/}:$fpath"
401 done434 done
402435
436 if [ "${cmdargs[0]}" = "curtin" -a -n "$install_deps" ]; then
437 # if this is a 'curtin' command, then also insert --install-deps
438 debug 1 "adding --install-deps to command"
439 cmdargs=( curtin $install_deps "${cmdargs[@]:1}" )
440 fi
441
403 PYTHONPATH="${top_d}${PYTHONPATH:+${PYTHONPATH}}" \442 PYTHONPATH="${top_d}${PYTHONPATH:+${PYTHONPATH}}" \
404 "${top_d}/bin/curtin" pack "${addargs[@]}" -- \443 "${top_d}/bin/curtin" pack "${addargs[@]}" -- \
405 "${cmdargs[@]}" > "${TEMP_D}/install-cmd" ||444 "${cmdargs[@]}" > "${TEMP_D}/install-cmd" ||
@@ -411,9 +450,9 @@
411 local ccfiles=""450 local ccfiles=""
412 ccfiles=( )451 ccfiles=( )
413 if [ -n "${pstate}" ]; then452 if [ -n "${pstate}" ]; then
414 printf "#cloud-config\npower_state:\n mode: %s\n" "$pstate" \453 write_pstate_config "$pstate" "${TEMP_D}/pstate.1" "${TEMP_D}/pstate.2"
415 > "${TEMP_D}/pstate"454 ccfiles[${#ccfiles[@]}]="${TEMP_D}/pstate.1"
416 ccfiles[${#ccfiles[@]}]="${TEMP_D}/pstate"455 ccfiles[${#ccfiles[@]}]="${TEMP_D}/pstate.2"
417 fi456 fi
418457
419 if tmp=$(find_apt_proxy); then458 if tmp=$(find_apt_proxy); then
420459
=== modified file 'tools/write-curtin'
--- tools/write-curtin 2015-03-09 16:35:00 +0000
+++ tools/write-curtin 2015-11-18 18:13:59 +0000
@@ -16,8 +16,7 @@
16 parg = path16 parg = path
1717
18ret = write_exe_wrapper(entrypoint="curtin.commands.main", path=parg,18ret = write_exe_wrapper(entrypoint="curtin.commands.main", path=parg,
19 deps_check_entry="curtin.deps.check",19 deps_check_entry="curtin.deps.check")
20 deps_install_entry="curtin.deps.install")
2120
22if path == "-":21if path == "-":
23 sys.stdout.write(ret)22 sys.stdout.write(ret)

Subscribers

People subscribed via source and target branches