Merge ~raharper/curtin:ubuntu/devel/release-18.2 into curtin:ubuntu/devel

Proposed by Ryan Harper on 2018-12-06
Status: Merged
Merged at revision: ce1e3c1d2e9cc1927b07a2dad105820ac80c8948
Proposed branch: ~raharper/curtin:ubuntu/devel/release-18.2
Merge into: curtin:ubuntu/devel
Diff against target: 3184 lines (+1417/-240)
59 files modified
curtin/__init__.py (+1/-1)
curtin/commands/apt_config.py (+22/-6)
curtin/commands/block_meta.py (+157/-49)
curtin/swap.py (+9/-3)
curtin/udev.py (+37/-0)
debian/changelog (+27/-0)
doc/topics/apt_source.rst (+57/-0)
doc/topics/storage.rst (+45/-2)
examples/tests/basic.yaml (+9/-0)
examples/tests/basic_scsi.yaml (+27/-0)
examples/tests/multipath.yaml (+1/-0)
examples/tests/nvme.yaml (+2/-2)
examples/tests/nvme_bcache.yaml (+1/-1)
examples/tests/simple-storage.yaml (+48/-0)
helpers/common (+50/-5)
tests/unittests/helpers.py (+7/-0)
tests/unittests/test_apt_custom_sources_list.py (+61/-29)
tests/unittests/test_commands_block_meta.py (+4/-3)
tests/unittests/test_commands_extract.py (+2/-1)
tests/unittests/test_make_dname.py (+172/-30)
tests/unittests/test_swap.py (+42/-28)
tests/unittests/test_udev.py (+68/-0)
tests/vmtests/__init__.py (+124/-20)
tests/vmtests/releases.py (+6/-0)
tests/vmtests/test_apt_config_cmd.py (+11/-2)
tests/vmtests/test_apt_source.py (+7/-2)
tests/vmtests/test_basic.py (+61/-8)
tests/vmtests/test_bcache_basic.py (+6/-0)
tests/vmtests/test_bcache_bug1718699.py (+4/-0)
tests/vmtests/test_fs_battery.py (+5/-0)
tests/vmtests/test_iscsi.py (+6/-0)
tests/vmtests/test_journald_reporter.py (+4/-0)
tests/vmtests/test_lvm.py (+5/-0)
tests/vmtests/test_lvm_iscsi.py (+13/-7)
tests/vmtests/test_lvm_raid.py (+16/-8)
tests/vmtests/test_lvm_root.py (+2/-0)
tests/vmtests/test_mdadm_bcache.py (+42/-0)
tests/vmtests/test_mdadm_iscsi.py (+13/-6)
tests/vmtests/test_multipath.py (+31/-0)
tests/vmtests/test_network.py (+6/-0)
tests/vmtests/test_network_alias.py (+6/-0)
tests/vmtests/test_network_bonding.py (+6/-0)
tests/vmtests/test_network_bridging.py (+7/-0)
tests/vmtests/test_network_ipv6.py (+8/-0)
tests/vmtests/test_network_ipv6_static.py (+4/-0)
tests/vmtests/test_network_ipv6_vlan.py (+4/-0)
tests/vmtests/test_network_mtu.py (+9/-0)
tests/vmtests/test_network_static.py (+4/-0)
tests/vmtests/test_network_static_routes.py (+5/-0)
tests/vmtests/test_network_vlan.py (+6/-0)
tests/vmtests/test_nvme.py (+17/-4)
tests/vmtests/test_old_apt_features.py (+7/-2)
tests/vmtests/test_pollinate_useragent.py (+6/-0)
tests/vmtests/test_raid5_bcache.py (+15/-7)
tests/vmtests/test_simple.py (+67/-1)
tests/vmtests/test_ubuntu_core.py (+2/-0)
tests/vmtests/test_uefi_basic.py (+17/-7)
tests/vmtests/test_zfsroot.py (+10/-0)
tools/jenkins-runner (+6/-6)
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve on 2018-12-06
curtin developers 2018-12-06 Pending
Review via email: mp+360217@code.launchpad.net

Commit message

curtin (18.2-0ubuntu1) disco; urgency=medium

  * New upstream release (18.2).
    - Release 18.2
    - Adjust helpers/common to edit GRUB_CMDLINE_LINUX_DEFAULT in place.
      (LP: #1527664)
    - dname: persistent names based on serial or wwn (LP: #1735839)
    - Fix bug in is_swap_device if a device was smaller than page_size.
      (LP: #1803672)
    - vmtest: add disco tests [Joshua Powers]
    - unittest: change directory to tmpdir for testing relative files.
    - Add clear-holders to meta-simple (LP: #1786736)
    - vmtests: check install log for Out of memory kernel messages and fail
    - unittest: correctly use tmpdir for my.img [Joshua Powers] (LP: #1803611)
    - block_meta: use wipe config when clearing partitions (LP: #1800153)
    - tests: fix vmtests for apt perserve_source_list changes
    - apt: Use new format apt config when writing preserve_sources_list.
      (LP: #1735950)
    - vmtests: multipath mount /home with nofail and validate in unittest
    - vmtests: fix common collect scripts to not exit failure.
    - vmtest: handle collect disk unpack failure
    - vmtests: dont use multiple subclasses in uefi 4k tests
    - vmtests: disable snapd/seeding to avoid boot hang
    - jenkins-runner: fix when using --filter only

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/curtin/__init__.py b/curtin/__init__.py
2index ee35ca3..a307e84 100644
3--- a/curtin/__init__.py
4+++ b/curtin/__init__.py
5@@ -30,6 +30,6 @@ FEATURES = [
6 'HAS_VERSION_MODULE',
7 ]
8
9-__version__ = "18.1"
10+__version__ = "18.2"
11
12 # vi: ts=4 expandtab syntax=python
13diff --git a/curtin/commands/apt_config.py b/curtin/commands/apt_config.py
14index 9ce25b3..8bd6e79 100644
15--- a/curtin/commands/apt_config.py
16+++ b/curtin/commands/apt_config.py
17@@ -10,7 +10,6 @@ import glob
18 import os
19 import re
20 import sys
21-import yaml
22
23 from curtin.log import LOG
24 from curtin import (config, distro, gpg, paths, util)
25@@ -71,6 +70,7 @@ def handle_apt(cfg, target=None):
26 if not config.value_as_boolean(cfg.get('preserve_sources_list',
27 True)):
28 generate_sources_list(cfg, release, mirrors, target)
29+ apply_preserve_sources_list(target)
30 rename_apt_lists(mirrors, target)
31
32 try:
33@@ -318,16 +318,32 @@ def generate_sources_list(cfg, release, mirrors, target=None):
34 disabled = disable_suites(cfg.get('disable_suites'), rendered, release)
35 util.write_file(paths.target_path(target, aptsrc), disabled, mode=0o644)
36
37+
38+def apply_preserve_sources_list(target):
39 # protect the just generated sources.list from cloud-init
40 cloudfile = "/etc/cloud/cloud.cfg.d/curtin-preserve-sources.cfg"
41- # this has to work with older cloud-init as well, so use old key
42- cloudconf = yaml.dump({'apt_preserve_sources_list': True}, indent=1)
43+
44+ target_ver = distro.get_package_version('cloud-init', target=target)
45+ if not target_ver:
46+ LOG.info("Attempt to read cloud-init version from target returned "
47+ "'%s', not writing preserve_sources_list config.",
48+ target_ver)
49+ return
50+
51+ cfg = {'apt': {'preserve_sources_list': True}}
52+ if target_ver['major'] < 1:
53+ # anything cloud-init 0.X.X will get the old config key.
54+ cfg = {'apt_preserve_sources_list': True}
55+
56 try:
57 util.write_file(paths.target_path(target, cloudfile),
58- cloudconf, mode=0o644)
59+ config.dump_config(cfg), mode=0o644)
60+ LOG.debug("Set preserve_sources_list to True in %s with: %s",
61+ cloudfile, cfg)
62 except IOError:
63- LOG.exception("Failed to protect source.list from cloud-init in (%s)",
64- paths.target_path(target, cloudfile))
65+ LOG.exception(
66+ "Failed to protect /etc/apt/sources.list from cloud-init in '%s'",
67+ cloudfile)
68 raise
69
70
71diff --git a/curtin/commands/block_meta.py b/curtin/commands/block_meta.py
72index 197c1fd..8decab5 100644
73--- a/curtin/commands/block_meta.py
74+++ b/curtin/commands/block_meta.py
75@@ -8,7 +8,8 @@ from curtin.log import LOG, logged_time
76 from curtin.reporter import events
77
78 from . import populate_one_subcmd
79-from curtin.udev import compose_udev_equality, udevadm_settle, udevadm_trigger
80+from curtin.udev import (compose_udev_equality, udevadm_settle,
81+ udevadm_trigger, udevadm_info)
82
83 import glob
84 import os
85@@ -35,6 +36,8 @@ CMD_ARGUMENTS = (
86 'metavar': 'DEVICE', 'default': None, }),
87 ('--fstype', {'help': 'root partition filesystem type',
88 'choices': ['ext4', 'ext3'], 'default': 'ext4'}),
89+ ('--force-mode', {'help': 'force mode, disable mode detection',
90+ 'action': 'store_true', 'default': False}),
91 (('-t', '--target'),
92 {'help': 'chroot to target. default is env[TARGET_MOUNT_POINT]',
93 'action': 'store', 'metavar': 'TARGET',
94@@ -55,11 +58,35 @@ def block_meta(args):
95 state = util.load_command_environment()
96 cfg = config.load_command_config(args, state)
97 dd_images = util.get_dd_images(cfg.get('sources', {}))
98- if ((args.mode == CUSTOM or cfg.get("storage") is not None) and
99- len(dd_images) == 0):
100- meta_custom(args)
101- elif args.mode in (SIMPLE, SIMPLE_BOOT) or len(dd_images) > 0:
102- meta_simple(args)
103+
104+ # run clear holders on potential devices
105+ devices = args.devices
106+ if devices is None:
107+ devices = []
108+ if 'storage' in cfg:
109+ devices = get_disk_paths_from_storage_config(
110+ extract_storage_ordered_dict(cfg))
111+ if len(devices) == 0:
112+ devices = cfg.get('block-meta', {}).get('devices', [])
113+ LOG.debug('Declared block devices: %s', devices)
114+ args.devices = devices
115+
116+ meta_clear(devices, state.get('report_stack_prefix', ''))
117+
118+ # dd-images requires use of meta_simple
119+ if len(dd_images) > 0 and args.force_mode is False:
120+ LOG.info('blockmeta: detected dd-images, using mode=simple')
121+ return meta_simple(args)
122+
123+ if cfg.get("storage") and args.force_mode is False:
124+ LOG.info('blockmeta: detected storage config, using mode=custom')
125+ return meta_custom(args)
126+
127+ LOG.info('blockmeta: mode=%s force=%s', args.mode, args.force_mode)
128+ if args.mode == CUSTOM:
129+ return meta_custom(args)
130+ elif args.mode in (SIMPLE, SIMPLE_BOOT):
131+ return meta_simple(args)
132 else:
133 raise NotImplementedError("mode=%s is not implemented" % args.mode)
134
135@@ -195,12 +222,43 @@ def sanitize_dname(dname):
136 return ''.join(c if c in valid else '-' for c in dname)
137
138
139+def make_dname_byid(path, error_msg=None, info=None):
140+ """ Returns a list of udev equalities for a given disk path
141+
142+ :param path: string of a kernel device path to a block device
143+ :param error_msg: more information about path for log/errors
144+ :param info: dict of udevadm info key, value pairs of device specified by
145+ path.
146+ :returns: list of udev equalities (lists)
147+ :raises: ValueError if path is not a disk.
148+ :raises: RuntimeError if there is no serial or wwn.
149+ """
150+ error_msg = str(path) + ("" if not error_msg else " [%s]" % error_msg)
151+ if info is None:
152+ info = udevadm_info(path=path)
153+ devtype = info.get('DEVTYPE')
154+ if devtype != "disk":
155+ raise ValueError(
156+ "Disk tag udev rules are only for disks, %s has devtype=%s" %
157+ (error_msg, devtype))
158+
159+ byid_keys = ['ID_SERIAL', 'ID_WWN_WITH_EXTENSION']
160+ present = [k for k in byid_keys if info.get(k)]
161+ if not present:
162+ raise RuntimeError(
163+ "Cannot create disk tag udev rule for %s, "
164+ "missing 'serial' or 'wwn' value" % error_msg)
165+
166+ return [[compose_udev_equality('ENV{%s}' % k, info[k]) for k in present]]
167+
168+
169 def make_dname(volume, storage_config):
170 state = util.load_command_environment()
171 rules_dir = os.path.join(state['scratch'], "rules.d")
172 vol = storage_config.get(volume)
173 path = get_path_to_storage_volume(volume, storage_config)
174 ptuuid = None
175+ byid = None
176 dname = vol.get('name')
177 if vol.get('type') in ["partition", "disk"]:
178 (out, _err) = util.subp(["blkid", "-o", "export", path], capture=True,
179@@ -209,28 +267,41 @@ def make_dname(volume, storage_config):
180 if "PTUUID" in line or "PARTUUID" in line:
181 ptuuid = line.split('=')[-1]
182 break
183+ if vol.get('type') == 'disk':
184+ byid = make_dname_byid(path, error_msg="id=%s" % vol.get('id'))
185 # we may not always be able to find a uniq identifier on devices with names
186- if not ptuuid and vol.get('type') in ["disk", "partition"]:
187+ if (not ptuuid and not byid) and vol.get('type') in ["disk", "partition"]:
188 LOG.warning("Can't find a uuid for volume: {}. Skipping dname.".format(
189 volume))
190 return
191
192- rule = [
193+ matches = []
194+ base_rule = [
195 compose_udev_equality("SUBSYSTEM", "block"),
196 compose_udev_equality("ACTION", "add|change"),
197 ]
198 if vol.get('type') == "disk":
199- rule.append(compose_udev_equality('ENV{DEVTYPE}', "disk"))
200- rule.append(compose_udev_equality('ENV{ID_PART_TABLE_UUID}', ptuuid))
201+ if ptuuid:
202+ matches += [[compose_udev_equality('ENV{DEVTYPE}', "disk"),
203+ compose_udev_equality('ENV{ID_PART_TABLE_UUID}',
204+ ptuuid)]]
205+ for rule in byid:
206+ matches += [
207+ [compose_udev_equality('ENV{DEVTYPE}', "disk")] + rule]
208 elif vol.get('type') == "partition":
209- rule.append(compose_udev_equality('ENV{DEVTYPE}', "partition"))
210- dname = storage_config.get(vol.get('device')).get('name') + \
211- "-part%s" % determine_partition_number(volume, storage_config)
212- rule.append(compose_udev_equality('ENV{ID_PART_ENTRY_UUID}', ptuuid))
213+ # if partition has its own name, bind that to the existing PTUUID
214+ if dname:
215+ matches += [[compose_udev_equality('ENV{DEVTYPE}', "partition"),
216+ compose_udev_equality('ENV{ID_PART_ENTRY_UUID}',
217+ ptuuid)]]
218+ else:
219+ # disks generate dname-part%n rules automatically
220+ LOG.debug('No partition-specific dname')
221+ return
222 elif vol.get('type') == "raid":
223 md_data = mdadm.mdadm_query_detail(path)
224 md_uuid = md_data.get('MD_UUID')
225- rule.append(compose_udev_equality("ENV{MD_UUID}", md_uuid))
226+ matches += [[compose_udev_equality("ENV{MD_UUID}", md_uuid)]]
227 elif vol.get('type') == "bcache":
228 # bind dname to bcache backing device's dev.uuid as the bcache minor
229 # device numbers are not stable across reboots.
230@@ -239,12 +310,12 @@ def make_dname(volume, storage_config):
231 bcache_super = bcache.superblock_asdict(device=backing_dev)
232 if bcache_super and bcache_super['sb.version'].startswith('1'):
233 bdev_uuid = bcache_super['dev.uuid']
234- rule.append(compose_udev_equality("ENV{CACHED_UUID}", bdev_uuid))
235+ matches += [[compose_udev_equality("ENV{CACHED_UUID}", bdev_uuid)]]
236 bcache.write_label(sanitize_dname(dname), backing_dev)
237 elif vol.get('type') == "lvm_partition":
238 volgroup_name = storage_config.get(vol.get('volgroup')).get('name')
239 dname = "%s-%s" % (volgroup_name, dname)
240- rule.append(compose_udev_equality("ENV{DM_NAME}", dname))
241+ matches += [[compose_udev_equality("ENV{DM_NAME}", dname)]]
242 else:
243 raise ValueError('cannot make dname for device with type: {}'
244 .format(vol.get('type')))
245@@ -257,11 +328,25 @@ def make_dname(volume, storage_config):
246 LOG.warning(
247 "dname modified to remove invalid chars. old: '{}' new: '{}'"
248 .format(dname, sanitized))
249- rule.append("SYMLINK+=\"disk/by-dname/%s\"\n" % sanitized)
250- LOG.debug("Writing dname udev rule '{}'".format(str(rule)))
251+ content = ['# Written by curtin']
252+ for match in matches:
253+ rule = (base_rule + match +
254+ ["SYMLINK+=\"disk/by-dname/%s\"\n" % sanitized])
255+ LOG.debug("Creating dname udev rule '{}'".format(str(rule)))
256+ content.append(', '.join(rule))
257+
258+ if vol.get('type') == 'disk':
259+ for brule in byid:
260+ rule = (base_rule +
261+ [compose_udev_equality('ENV{DEVTYPE}', 'partition')] +
262+ brule +
263+ ['SYMLINK+="disk/by-dname/%s-part%%n"\n' % sanitized])
264+ LOG.debug("Creating dname udev rule '{}'".format(str(rule)))
265+ content.append(', '.join(rule))
266+
267 util.ensure_dir(rules_dir)
268 rule_file = os.path.join(rules_dir, '{}.rules'.format(sanitized))
269- util.write_file(rule_file, ', '.join(rule))
270+ util.write_file(rule_file, '\n'.join(content))
271
272
273 def get_poolname(info, storage_config):
274@@ -584,6 +669,9 @@ def partition_handler(info, storage_config):
275 block.zero_file_at_offsets(disk, [wipe_offset], exclusive=False)
276
277 if disk_ptable == "msdos":
278+ if flag and flag == 'prep':
279+ raise ValueError('PReP partitions require a GPT partition table')
280+
281 if flag in ["extended", "logical", "primary"]:
282 partition_type = flag
283 else:
284@@ -604,6 +692,16 @@ def partition_handler(info, storage_config):
285 else:
286 raise ValueError("parent partition has invalid partition table")
287
288+ # wipe the created partition if needed, superblocks have already been wiped
289+ wipe_mode = info.get('wipe', 'superblock')
290+ if wipe_mode != 'superblock':
291+ part_path = block.dev_path(block.partition_kname(disk_kname,
292+ partnumber))
293+ block.rescan_block_devices([disk])
294+ udevadm_settle(exists=part_path)
295+ LOG.debug('Wiping partition %s mode=%s', part_path, wipe_mode)
296+ block.wipe_volume(part_path, mode=wipe_mode, exclusive=False)
297+
298 # Make the name if needed
299 if storage_config.get(device).get('name') and partition_type != 'extended':
300 make_dname(info.get('id'), storage_config)
301@@ -1335,6 +1433,19 @@ def extract_storage_ordered_dict(config):
302 return OrderedDict((d["id"], d) for (i, d) in enumerate(scfg))
303
304
305+def get_disk_paths_from_storage_config(storage_config):
306+ """Returns a list of disk paths in a storage config filtering out
307+ preserved or disks which do not have wipe configuration.
308+
309+ :param: storage_config: Ordered dict of storage configation
310+ """
311+ return [get_path_to_storage_volume(k, storage_config)
312+ for (k, v) in storage_config.items()
313+ if v.get('type') == 'disk' and
314+ config.value_as_boolean(v.get('wipe')) and
315+ not config.value_as_boolean(v.get('preserve'))]
316+
317+
318 def zfsroot_update_storage_config(storage_config):
319 """Return an OrderedDict that has 'zfsroot' format expanded into
320 zpool and zfs commands to enable ZFS on rootfs.
321@@ -1429,6 +1540,24 @@ def zfsroot_update_storage_config(storage_config):
322 return ret
323
324
325+def meta_clear(devices, report_prefix=''):
326+ """ Run clear_holders on specified list of devices.
327+
328+ :param: devices: a list of block devices (/dev/XXX) to be cleared
329+ :param: report_prefix: a string to pass to the ReportEventStack
330+ """
331+ # shut down any already existing storage layers above any disks used in
332+ # config that have 'wipe' set
333+ with events.ReportEventStack(
334+ name=report_prefix + '/clear-holders',
335+ reporting_enabled=True, level='INFO',
336+ description="removing previous storage devices"):
337+ clear_holders.start_clear_holders_deps()
338+ clear_holders.clear_holders(devices)
339+ # if anything was not properly shut down, stop installation
340+ clear_holders.assert_clear(devices)
341+
342+
343 def meta_custom(args):
344 """Does custom partitioning based on the layout provided in the config
345 file. Section with the name storage contains information on which
346@@ -1460,21 +1589,6 @@ def meta_custom(args):
347 # set up reportstack
348 stack_prefix = state.get('report_stack_prefix', '')
349
350- # shut down any already existing storage layers above any disks used in
351- # config that have 'wipe' set
352- with events.ReportEventStack(
353- name=stack_prefix, reporting_enabled=True, level='INFO',
354- description="removing previous storage devices"):
355- clear_holders.start_clear_holders_deps()
356- disk_paths = [get_path_to_storage_volume(k, storage_config_dict)
357- for (k, v) in storage_config_dict.items()
358- if v.get('type') == 'disk' and
359- config.value_as_boolean(v.get('wipe')) and
360- not config.value_as_boolean(v.get('preserve'))]
361- clear_holders.clear_holders(disk_paths)
362- # if anything was not properly shut down, stop installation
363- clear_holders.assert_clear(disk_paths)
364-
365 for item_id, command in storage_config_dict.items():
366 handler = command_handlers.get(command['type'])
367 if not handler:
368@@ -1500,8 +1614,15 @@ def meta_simple(args):
369 create a separate /boot partition.
370 """
371 state = util.load_command_environment()
372-
373 cfg = config.load_command_config(args, state)
374+ if args.target is not None:
375+ state['target'] = args.target
376+
377+ if state['target'] is None:
378+ sys.stderr.write("Unable to find target. "
379+ "Use --target or set TARGET_MOUNT_POINT\n")
380+ sys.exit(2)
381+
382 devpath = None
383 if cfg.get("storage") is not None:
384 for i in cfg["storage"]["config"]:
385@@ -1512,21 +1633,8 @@ def meta_simple(args):
386 diskPath = block.lookup_disk(serial)
387 if grub is True:
388 devpath = diskPath
389- if config.value_as_boolean(i.get('wipe')):
390- block.wipe_volume(diskPath, mode=i.get('wipe'))
391-
392- if args.target is not None:
393- state['target'] = args.target
394-
395- if state['target'] is None:
396- sys.stderr.write("Unable to find target. "
397- "Use --target or set TARGET_MOUNT_POINT\n")
398- sys.exit(2)
399
400 devices = args.devices
401- if devices is None:
402- devices = cfg.get('block-meta', {}).get('devices', [])
403-
404 bootpt = get_bootpt_cfg(
405 cfg.get('block-meta', {}).get('boot-partition', {}),
406 enabled=args.mode == SIMPLE_BOOT, fstype=args.boot_fstype,
407diff --git a/curtin/swap.py b/curtin/swap.py
408index 5f77b03..d3f29dc 100644
409--- a/curtin/swap.py
410+++ b/curtin/swap.py
411@@ -103,9 +103,15 @@ def is_swap_device(path):
412 https://github.com/torvalds/linux/blob/master/include/linux/swap.h#L111
413 """
414 LOG.debug('Checking if %s is a swap device', path)
415- swap_magic_offset = resource.getpagesize() - 10
416- magic = util.load_file(path, read_len=10, offset=swap_magic_offset,
417- decode=False)
418+ pagesize = resource.getpagesize()
419+ magic_offset = pagesize - 10
420+ size = util.file_size(path)
421+ if size < magic_offset:
422+ LOG.debug("%s is to small for swap (size=%d < pagesize=%d)",
423+ path, size, pagesize)
424+ return False
425+ magic = util.load_file(
426+ path, read_len=10, offset=magic_offset, decode=False)
427 LOG.debug('Found swap magic: %s' % magic)
428 return magic in [b'SWAPSPACE2', b'SWAP-SPACE']
429 # vi: ts=4 expandtab syntax=python
430diff --git a/curtin/udev.py b/curtin/udev.py
431index 13d9cc5..106a7e7 100644
432--- a/curtin/udev.py
433+++ b/curtin/udev.py
434@@ -61,4 +61,41 @@ def udevadm_trigger(devices):
435 util.subp(['udevadm', 'trigger'] + list(devices))
436 udevadm_settle()
437
438+
439+def udevadm_info(path=None):
440+ """ Return a dictionary populated by properties of the device specified
441+ in the `path` variable via querying udev 'property' database.
442+
443+ :params: path: path to device, either /dev or /sys
444+ :returns: dictionary of key=value pairs as exported from the udev database
445+ :raises: ValueError path is None, ProcessExecutionError on exec error.
446+ """
447+ if not path:
448+ raise ValueError('Invalid path: "%s"' % path)
449+
450+ info_cmd = ['udevadm', 'info', '--query=property', path]
451+ output, _ = util.subp(info_cmd, capture=True)
452+
453+ # strip for trailing empty line
454+ info = {}
455+ for line in output.splitlines():
456+ if not line:
457+ continue
458+ # maxsplit=2 gives us key and remaininng part of line is value
459+ # py2.7 on Trusty doesn't have keyword, pass as argument
460+ key, value = line.split('=', 2)
461+ if not value:
462+ value = None
463+ if value:
464+ # devlinks is a list of paths separated by space
465+ # convert to a list for easy use
466+ if key == 'DEVLINKS':
467+ info[key] = value.split()
468+ else:
469+ # preserve spaces in values, to match udev database
470+ info[key] = value
471+
472+ return info
473+
474+
475 # vi: ts=4 expandtab syntax=python
476diff --git a/debian/changelog b/debian/changelog
477index 89bb20f..ef377fe 100644
478--- a/debian/changelog
479+++ b/debian/changelog
480@@ -1,3 +1,30 @@
481+curtin (18.2-0ubuntu1) disco; urgency=medium
482+
483+ * New upstream release (18.2).
484+ - Release 18.2
485+ - Adjust helpers/common to edit GRUB_CMDLINE_LINUX_DEFAULT in place.
486+ (LP: #1527664)
487+ - dname: persistent names based on serial or wwn (LP: #1735839)
488+ - Fix bug in is_swap_device if a device was smaller than page_size.
489+ (LP: #1803672)
490+ - vmtest: add disco tests [Joshua Powers]
491+ - unittest: change directory to tmpdir for testing relative files.
492+ - Add clear-holders to meta-simple (LP: #1786736)
493+ - vmtests: check install log for Out of memory kernel messages and fail
494+ - unittest: correctly use tmpdir for my.img [Joshua Powers] (LP: #1803611)
495+ - block_meta: use wipe config when clearing partitions (LP: #1800153)
496+ - tests: fix vmtests for apt perserve_source_list changes
497+ - apt: Use new format apt config when writing preserve_sources_list.
498+ (LP: #1735950)
499+ - vmtests: multipath mount /home with nofail and validate in unittest
500+ - vmtests: fix common collect scripts to not exit failure.
501+ - vmtest: handle collect disk unpack failure
502+ - vmtests: dont use multiple subclasses in uefi 4k tests
503+ - vmtests: disable snapd/seeding to avoid boot hang
504+ - jenkins-runner: fix when using --filter only
505+
506+ -- Ryan Harper <ryan.harper@canonical.com> Thu, 06 Dec 2018 12:11:01 -0600
507+
508 curtin (18.1-59-g0f993084-0ubuntu1) cosmic; urgency=medium
509
510 * New upstream snapshot.
511diff --git a/doc/topics/apt_source.rst b/doc/topics/apt_source.rst
512index 9795361..f996c53 100644
513--- a/doc/topics/apt_source.rst
514+++ b/doc/topics/apt_source.rst
515@@ -165,3 +165,60 @@ Dependencies
516 ~~~~~~~~~~~~
517 Cloud-init might need to resolve dependencies and install packages in the ephemeral environment to run curtin.
518 Therefore it is recommended to not only provide an apt configuration to curtin for the target, but also one to the install environment via cloud-init.
519+
520+
521+apt preserve_sources_list setting
522+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
523+cloud-init and curtin treat the ``preserve_sources_list`` setting slightly differently, and thus this setting deserves its own section.
524+
525+Interpretation / Meaning
526+------------------------
527+curtin reads ``preserve_sources_list`` to indicate whether or not it should update the target systems' ``/etc/apt/sources.list``. This includes replacing the mirrors used (apt/primary...).
528+
529+cloud-init reads ``preserve_sources_list`` to indicate whether or not it should *render* ``/etc/apt/sources.list`` from its built-in template.
530+
531+defaults
532+--------
533+Just for reference, the ``preserve_sources_list`` defaults in curtin and cloud-init are:
534+
535+ * curtin: **true**
536+ By default curtin will not modify ``/etc/apt/sources.list`` in the installed OS. It is assumed that this file is intentionally as it is.
537+ * cloud-init: **false**
538+ * cloud-init in ephemeral environment: **false**
539+ * cloud-init system installed by curtin: **true**
540+ (curtin writes this to a file ``/etc/cloud/cloud.cfg.d/curtin-preserve-sources.cfg`` in the target). It does this because we have already written the sources.list that is desired in the installer. We do not want cloud-init to overwrite it when it boots.
541+
542+preserve_sources_list in MAAS
543+-----------------------------
544+Curtin and cloud-init use the same ``apt`` configuration language.
545+MAAS provides apt config in three different scenarios.
546+
547+ 1. To cloud-init in ephemeral environment (rescue, install or commissioning)
548+ Here MAAS **should not send a value**. If it wants to be explicit it should send ``preserve_sources_list: false``.
549+
550+ 2. To curtin in curtin config
551+ MAAS **should send ``preserve_sources_list: false``**. curtin will correctly read and update mirrors in official Ubuntu images, so setting this to 'false' is correct. In some cases for custom images, the user might want to be able to have their /etc/apt/sources.list left untouched entirely. In such cases they may want to override this value.
552+
553+ 3. To cloud-init via curtin config in debconf_selections.
554+ MAAS should **not send a value**. Curtin will handle telling cloud-init to not update /etc/apt/sources.list. MAAS does not need to do this.
555+
556+ 4. To installed system via vendor-data or user-data.
557+ MAAS should **not send a value**. MAAS does not currently send a value. The user could send one in user-data, but then if they did presumably they did that for a reason.
558+
559+Legacy format
560+-------------
561+
562+Versions of cloud-init in 14.04 and older only support:
563+
564+.. code-block:: yaml
565+
566+ apt_preserve_sources_list: VALUE
567+
568+Versions of cloud-init present 16.04+ read the "new" style apt configuration, but support the old style configuration also. The new style configuration is:
569+
570+.. code-block:: yaml
571+
572+ apt:
573+ preserve_sources_list: VALUE
574+
575+**Note**: If versions of cloud-init that support the new style config receive conflicting values in old style and new style, cloud-init will raise exception and exit failure. It simplly doesn't know what behavior is desired.
576diff --git a/doc/topics/storage.rst b/doc/topics/storage.rst
577index b28964b..f9a9fe8 100644
578--- a/doc/topics/storage.rst
579+++ b/doc/topics/storage.rst
580@@ -175,7 +175,15 @@ not need to be preserved.
581 If the ``name`` key is present, curtin will create a udev rule that makes a
582 symbolic link to the disk with the given name value. This makes it easy to find
583 disks on an installed system. The links are created in
584-``/dev/disk/by-dname/<name>``.
585+``/dev/disk/by-dname/<name>``. The udev rules will utilize two types of disk
586+metadata to construct the link. For disks with ``serial`` and/or ``wwn`` values
587+these will be used to ensure the name persists even if the contents of the disk
588+change. For legacy purposes, curtin also emits a rule utilizing metadata on
589+the disk contents, typically a partition UUID value, this also preserves these
590+links for disks which lack persistent attributes such as a ``serial`` or
591+``wwn``, typically found on virtualized environments where such values are left
592+unset.
593+
594 A link to each partition on the disk will also be created at
595 ``/dev/disk/by-dname/<name>-part<number>``, so if ``name: maindisk`` is set,
596 the disk will be at ``/dev/disk/by-dname/maindisk`` and the first partition on
597@@ -237,6 +245,14 @@ After the partition is added to the disk's partition table, curtin can run a
598 wipe command on the partition. The wipe command values are the sames as for
599 disks.
600
601+.. note::
602+
603+ Curtin will automatically wipe 1MB at the starting location of the partition
604+ prior to creating the partition to ensure that other block layers or devices
605+ do not enable themselves and prevent accessing the partition. Wipe
606+ and other destructive operations only occur if the ``preserve`` value
607+ is not set to ``True``.
608+
609 **flag**: *logical, extended, boot, bios_grub, swap, lvm, raid, home, prep*
610
611 If the ``flag`` key is present, curtin will set the specified flag on the
612@@ -268,6 +284,17 @@ filesystem or be mounted anywhere on the system.
613 If the preserve flag is set to true, curtin will verify that the partition
614 exists and will not modify the partition.
615
616+**name**: *<name>*
617+
618+If the ``name`` key is present, curtin will create a udev rule that makes a
619+symbolic link to the partition with the given name value. The links are created
620+in ``/dev/disk/by-dname/<name>``.
621+
622+For partitions, the udev rule created relies upon disk contents, in this case
623+the partition entry UUID. This will remain in effect unless the underlying disk
624+on which the partition resides has the partition table modified or wiped.
625+
626+
627 **Config Example**::
628
629 - id: disk0-part1
630@@ -276,6 +303,7 @@ exists and will not modify the partition.
631 size: 8GB
632 device: disk0
633 flag: boot
634+ name: boot_partition
635
636 .. _format:
637
638@@ -496,6 +524,12 @@ scheme for Logical Volumes follows the pattern
639 with ``name`` *lv1* on a ``lvm_volgroup`` named *vg1* would have the path
640 ``/dev/disk/by-dname/vg1-lv1``.
641
642+.. note::
643+
644+ dname values for contructed devices (such as lvm) only remain persistent
645+ as long as the device metadata does not change. If users modify the device
646+ such that device metadata is changed then the udev rule may no longer apply.
647+
648 **volgroup**: *<volgroup id>*
649
650 The ``volgroup`` key specifies the ``id`` of the Volume Group in which to
651@@ -592,7 +626,9 @@ The ``name`` key specifies the name of the md device.
652 .. note::
653
654 Curtin creates a udev rule to create a link to the md device in
655- ``/dev/disk/by-dname/<name>`` using the specified name.
656+ ``/dev/disk/by-dname/<name>`` using the specified name. The dname
657+ symbolic link is only persistent as long as the raid metadata is
658+ not modifed or destroyed.
659
660 **raidlevel**: *0, 1, 5, 6, 10*
661
662@@ -669,6 +705,13 @@ reads/writes from the cache. None effectively disables bcache.
663 If the ``name`` key is present, curtin will create a link to the device at
664 ``/dev/disk/by-dname/<name>``.
665
666+.. note::
667+
668+ dname values for contructed devices (such as bcache) only remain persistent
669+ as long as the device metadata does not change. If users modify the device
670+ such that device metadata is changed then the udev rule may no longer apply.
671+
672+
673 **Config Example**::
674
675 - id: bcache0
676diff --git a/examples/tests/basic.yaml b/examples/tests/basic.yaml
677index 089e776..71730c0 100644
678--- a/examples/tests/basic.yaml
679+++ b/examples/tests/basic.yaml
680@@ -26,6 +26,7 @@ storage:
681 number: 3
682 size: 1GB
683 device: sda
684+ name: swap
685 - id: sda1_root
686 type: format
687 fstype: ext4
688@@ -83,6 +84,14 @@ storage:
689 device: pnum_disk
690 - id: pnum_disk_p2
691 type: partition
692+ number: 2
693+ size: 8MB
694+ device: pnum_disk
695+ flag: prep
696+ wipe: zero
697+ name: prep
698+ - id: pnum_disk_p3
699+ type: partition
700 number: 10
701 size: 1GB
702 device: pnum_disk
703diff --git a/examples/tests/basic_scsi.yaml b/examples/tests/basic_scsi.yaml
704index 5f60459..aa62137 100644
705--- a/examples/tests/basic_scsi.yaml
706+++ b/examples/tests/basic_scsi.yaml
707@@ -20,14 +20,25 @@ storage:
708 number: 2
709 size: 1GB
710 device: sda
711+ - id: sda3
712+ type: partition
713+ number: 3
714+ size: 1GB
715+ device: sda
716+ name: swap
717 - id: sda1_root
718 type: format
719 fstype: ext4
720 volume: sda1
721+ label: 'cloudimg-rootfs'
722 - id: sda2_home
723 type: format
724 fstype: ext4
725 volume: sda2
726+ - id: sda3_swap
727+ type: format
728+ fstype: swap
729+ volume: sda3
730 - id: sda1_mount
731 type: mount
732 path: /
733@@ -41,6 +52,10 @@ storage:
734 wwn: '0x080258d13ea95ae5'
735 name: sparedisk
736 wipe: superblock
737+ - id: sparedisk_fat_fmt_id
738+ type: format
739+ fstype: fat32
740+ volume: sparedisk_id
741 - id: btrfs_disk_id
742 type: disk
743 wwn: '0x22dc58dc023c7008'
744@@ -68,6 +83,18 @@ storage:
745 device: pnum_disk
746 - id: pnum_disk_p2
747 type: partition
748+ number: 2
749+ size: 8MB
750+ device: pnum_disk
751+ flag: prep
752+ wipe: zero
753+ name: prep
754+ - id: pnum_disk_p3
755+ type: partition
756 number: 10
757 size: 1GB
758 device: pnum_disk
759+ - id: swap_mnt
760+ type: mount
761+ path: "none"
762+ device: sda3_swap
763diff --git a/examples/tests/multipath.yaml b/examples/tests/multipath.yaml
764index d4b928c..8447d55 100644
765--- a/examples/tests/multipath.yaml
766+++ b/examples/tests/multipath.yaml
767@@ -36,3 +36,4 @@ storage:
768 type: mount
769 path: /home
770 device: sda2_home
771+ options: 'defaults,nofail'
772diff --git a/examples/tests/nvme.yaml b/examples/tests/nvme.yaml
773index b2a1276..4cf7735 100644
774--- a/examples/tests/nvme.yaml
775+++ b/examples/tests/nvme.yaml
776@@ -44,7 +44,7 @@ storage:
777 device: main_disk_home
778 - id: nvme_disk
779 type: disk
780- path: /dev/nvme0n1
781+ serial: nvme-a
782 name: nvme_disk
783 wipe: superblock
784 ptable: gpt
785@@ -63,7 +63,7 @@ storage:
786 device: nvme_disk
787 - id: nvme_disk2
788 type: disk
789- path: /dev/nvme1n1
790+ serial: nvme-b
791 wipe: superblock
792 ptable: msdos
793 name: second_nvme
794diff --git a/examples/tests/nvme_bcache.yaml b/examples/tests/nvme_bcache.yaml
795index 2ee0ad3..4fefd94 100644
796--- a/examples/tests/nvme_bcache.yaml
797+++ b/examples/tests/nvme_bcache.yaml
798@@ -19,7 +19,7 @@ storage:
799 model: INTEL SSDPEDME400G4
800 name: nvme0n1
801 ptable: gpt
802- path: /dev/nvme0n1
803+ serial: nvme-a
804 type: disk
805 wipe: superblock
806 - device: sda
807diff --git a/examples/tests/simple-storage.yaml b/examples/tests/simple-storage.yaml
808new file mode 100644
809index 0000000..644c5aa
810--- /dev/null
811+++ b/examples/tests/simple-storage.yaml
812@@ -0,0 +1,48 @@
813+# MAAS will send storage config to dd and windows to help pick boot device
814+# this test forces curtin down a block-meta simple path along with storage cfg
815+partitioning_commands:
816+ builtin: [curtin, block-meta, simple, --force-mode]
817+showtrace: true
818+storage:
819+ version: 1
820+ config:
821+ - id: sda
822+ type: disk
823+ wipe: superblock
824+ ptable: msdos
825+ model: QEMU HARDDISK
826+ serial: disk-a
827+ grub_device: true
828+ - id: sdb
829+ type: disk
830+ wipe: superblock
831+ ptable: msdos
832+ model: QEMU HARDDISK
833+ serial: disk-b
834+ wipe: superblock
835+ - id: sdc
836+ type: disk
837+ wipe: superblock
838+ ptable: msdos
839+ model: QEMU HARDDISK
840+ serial: disk-c
841+ wipe: superblock
842+# This partition config is here to "dirty" the disk
843+ - id: sda-part1
844+ type: partition
845+ device: sda
846+ name: sda-part1
847+ number: 1
848+ size: 3G
849+ uuid: ecc1ec63-e8d2-4719-8cee-dd7f4e2b390e
850+ wipe: superblock
851+ - id: sda-part1_format
852+ type: format
853+ fstype: ext4
854+ label: root
855+ uuid: f793b242-e812-44df-91c0-c245a55ffd59
856+ volume: sda-part1
857+ - id: sda-part1_mount
858+ type: mount
859+ path: /
860+ device: sda-part1_format
861diff --git a/helpers/common b/helpers/common
862index f9217b7..34f0870 100644
863--- a/helpers/common
864+++ b/helpers/common
865@@ -537,7 +537,44 @@ get_carryover_params() {
866 done
867 echo "${c# }"
868 )
869- _RET="${carry_lead:+${carry_lead} }${carry_extra}"
870+ [ -n "${carry_lead}" -a -n "${carry_extra}" ] &&
871+ carry_lead="${carry_lead} "
872+ _RET="${carry_lead}${carry_extra}"
873+}
874+
875+shell_config_update() {
876+ # shell_config_update(file, name, value)
877+ # update variable 'name' setting value to 'val' in shell syntax 'file'.
878+ # if 'name' is not present, then append declaration.
879+ local file="$1" name="$2" val="$3"
880+ if ! [ -f "$file" ] || ! grep -q "^$name=" "$file"; then
881+ debug 2 "appending to $file shell $name=\"$val\""
882+ echo "$name=\"$val\"" >> "$file"
883+ return
884+ fi
885+ local cand="" del=""
886+ for cand in "|" "," "/"; do
887+ [ "${val#*${del}}" = "${val}" ] && del="$cand" && break
888+ done
889+ [ -n "$del" ] || {
890+ error "Couldn't find a sed delimiter for '$val'";
891+ return 1;
892+ }
893+
894+ sed -i -e "s${del}^$name=.*${del}$name=\"$val\"${del}" "$file" ||
895+ { error "Failed editing '$file' to set $name=$val"; return 1; }
896+ debug 2 "updated $file to set $name=\"$val\""
897+ return 0
898+}
899+
900+apply_grub_cmdline_linux_default() {
901+ local mp="$1" newargs="$2" edg="${3:-etc/default/grub}"
902+ local gcld="GRUB_CMDLINE_LINUX_DEFAULT"
903+ debug 1 "setting $gcld to '$newargs' in $edg"
904+ shell_config_update "$mp/$edg" "$gcld" "$newargs" || {
905+ error "Failed to set '$gcld=$newargs' in $edg"
906+ return 1
907+ }
908 }
909
910 install_grub() {
911@@ -709,6 +746,8 @@ install_grub() {
912 esac
913
914 local grub_d="etc/default/grub.d"
915+ # ubuntu writes to /etc/default/grub.d/50-curtin-settings.cfg
916+ # to avoid tripping prompts on upgrade LP: #564853
917 local mygrub_cfg="$grub_d/50-curtin-settings.cfg"
918 case $os_family in
919 redhat)
920@@ -736,9 +775,9 @@ install_grub() {
921 # always append rd.auto=1 for centos
922 case $os_family in
923 redhat)
924- newargs="$newargs rd.auto=1";;
925+ newargs="${newargs:+${newargs} }rd.auto=1";;
926 esac
927- debug 1 "carryover command line params: $newargs"
928+ debug 1 "carryover command line params '$newargs'"
929
930 case $os_family in
931 debian)
932@@ -746,9 +785,15 @@ install_grub() {
933 { error "Failed to write '$mygrub_cfg'"; return 1; }
934 ;;
935 esac
936+
937+ if [ "${REPLACE_GRUB_LINUX_DEFAULT:-1}" != "0" ]; then
938+ apply_grub_cmdline_linux_default "$mp" "$newargs" || {
939+ error "Failed to apply grub cmdline."
940+ return 1
941+ }
942+ fi
943+
944 {
945- [ "${REPLACE_GRUB_LINUX_DEFAULT:-1}" = "0" ] ||
946- echo "GRUB_CMDLINE_LINUX_DEFAULT=\"$newargs\""
947 echo "# Curtin disable grub os prober that might find other OS installs."
948 echo "GRUB_DISABLE_OS_PROBER=true"
949 echo "GRUB_TERMINAL=console"
950diff --git a/tests/unittests/helpers.py b/tests/unittests/helpers.py
951index 58e068b..1268880 100644
952--- a/tests/unittests/helpers.py
953+++ b/tests/unittests/helpers.py
954@@ -5,7 +5,9 @@ import imp
955 import importlib
956 import mock
957 import os
958+import random
959 import shutil
960+import string
961 import tempfile
962 from unittest import TestCase
963
964@@ -67,6 +69,11 @@ class CiTestCase(TestCase):
965 return os.path.normpath(
966 os.path.abspath(os.path.sep.join((_dir, path))))
967
968+ def random_string(self, length=8):
969+ """ return a random lowercase string with default length of 8"""
970+ return ''.join(
971+ random.choice(string.ascii_lowercase) for _ in range(length))
972+
973
974 def dir2dict(startdir, prefix=None):
975 flist = {}
976diff --git a/tests/unittests/test_apt_custom_sources_list.py b/tests/unittests/test_apt_custom_sources_list.py
977index a427ae9..d77c750 100644
978--- a/tests/unittests/test_apt_custom_sources_list.py
979+++ b/tests/unittests/test_apt_custom_sources_list.py
980@@ -94,36 +94,30 @@ class TestAptSourceConfigSourceList(CiTestCase):
981 self.new_root = self.tmp_dir()
982 # self.patchUtils(self.new_root)
983
984- @staticmethod
985- def _apt_source_list(cfg, expected):
986+ def _apt_source_list(self, cfg, expected):
987 "_apt_source_list - Test rendering from template (generic)"
988
989 arch = util.get_architecture()
990 # would fail inside the unittest context
991- with mock.patch.object(util, 'get_architecture',
992- return_value=arch) as mockga:
993- with mock.patch.object(util, 'write_file') as mockwrite:
994- # keep it side effect free and avoid permission errors
995- with mock.patch.object(os, 'rename'):
996- # make test independent to executing system
997- with mock.patch.object(util, 'load_file',
998- return_value=MOCKED_APT_SRC_LIST):
999- with mock.patch.object(distro, 'lsb_release',
1000- return_value={'codename':
1001- 'fakerel'}):
1002- apt_config.handle_apt(cfg, TARGET)
1003-
1004- mockga.assert_called_with("/")
1005-
1006- cloudfile = '/etc/cloud/cloud.cfg.d/curtin-preserve-sources.cfg'
1007- cloudconf = yaml.dump({'apt_preserve_sources_list': True}, indent=1)
1008+ bpath = "curtin.commands.apt_config."
1009+ upath = bpath + "util."
1010+ self.add_patch(upath + "get_architecture", "mockga", return_value=arch)
1011+ self.add_patch(upath + "write_file", "mockwrite")
1012+ self.add_patch(bpath + "os.rename", "mockrename")
1013+ self.add_patch(upath + "load_file", "mockload_file",
1014+ return_value=MOCKED_APT_SRC_LIST)
1015+ self.add_patch(bpath + "distro.lsb_release", "mock_lsb_release",
1016+ return_value={'codename': 'fakerel'})
1017+ self.add_patch(bpath + "apply_preserve_sources_list",
1018+ "mock_apply_preserve_sources_list")
1019+
1020+ apt_config.handle_apt(cfg, TARGET)
1021+
1022+ self.mockga.assert_called_with(TARGET)
1023+ self.mock_apply_preserve_sources_list.assert_called_with(TARGET)
1024 calls = [call(paths.target_path(TARGET, '/etc/apt/sources.list'),
1025- expected,
1026- mode=0o644),
1027- call(paths.target_path(TARGET, cloudfile),
1028- cloudconf,
1029- mode=0o644)]
1030- mockwrite.assert_has_calls(calls)
1031+ expected, mode=0o644)]
1032+ self.mockwrite.assert_has_calls(calls)
1033
1034 def test_apt_source_list(self):
1035 """test_apt_source_list - Test with neither custom sources nor parms"""
1036@@ -156,10 +150,6 @@ class TestAptSourceConfigSourceList(CiTestCase):
1037 self.assertEqual(
1038 EXPECTED_CONVERTED_CONTENT,
1039 util.load_file(paths.target_path(target, "/etc/apt/sources.list")))
1040- cloudfile = paths.target_path(
1041- target, '/etc/cloud/cloud.cfg.d/curtin-preserve-sources.cfg')
1042- self.assertEqual({'apt_preserve_sources_list': True},
1043- yaml.load(util.load_file(cloudfile)))
1044
1045 @mock.patch("curtin.distro.lsb_release")
1046 @mock.patch("curtin.util.get_architecture", return_value="amd64")
1047@@ -224,4 +214,46 @@ class TestAptSourceConfigSourceList(CiTestCase):
1048 apt_config.handle_apt(cfg, target)
1049 self.assertEqual(expected, util.load_file(easl))
1050
1051+
1052+class TestApplyPreserveSourcesList(CiTestCase):
1053+ """Test apply_preserve_sources_list."""
1054+
1055+ cloudfile = "/etc/cloud/cloud.cfg.d/curtin-preserve-sources.cfg"
1056+
1057+ def setUp(self):
1058+ super(TestApplyPreserveSourcesList, self).setUp()
1059+ self.tmp = self.tmp_dir()
1060+ self.tmp_cfg = self.tmp_path(self.cloudfile, self.tmp)
1061+
1062+ @mock.patch("curtin.commands.apt_config.distro.get_package_version")
1063+ def test_old_cloudinit_version(self, m_get_pkg_ver):
1064+ """Test installed old cloud-init version."""
1065+ m_get_pkg_ver.return_value = distro.parse_dpkg_version('0.7.7-0')
1066+ apt_config.apply_preserve_sources_list(self.tmp)
1067+ m_get_pkg_ver.assert_has_calls(
1068+ [mock.call('cloud-init', target=self.tmp)])
1069+ self.assertEqual(
1070+ yaml.load(util.load_file(self.tmp_cfg)),
1071+ {'apt_preserve_sources_list': True})
1072+
1073+ @mock.patch("curtin.commands.apt_config.distro.get_package_version")
1074+ def test_no_cloudinit(self, m_get_pkg_ver):
1075+ """Test where cloud-init is not installed."""
1076+ m_get_pkg_ver.return_value = None
1077+ apt_config.apply_preserve_sources_list(self.tmp)
1078+ m_get_pkg_ver.assert_has_calls(
1079+ [mock.call('cloud-init', target=self.tmp)])
1080+ self.assertFalse(os.path.exists(self.tmp_cfg))
1081+
1082+ @mock.patch("curtin.commands.apt_config.distro.get_package_version")
1083+ def test_new_cloudinit_version(self, m_get_pkg_ver):
1084+ """Test cloud-init > 1.0 with new apt format."""
1085+ m_get_pkg_ver.return_value = distro.parse_dpkg_version('17.1-0ubuntu1')
1086+ apt_config.apply_preserve_sources_list(self.tmp)
1087+ m_get_pkg_ver.assert_has_calls(
1088+ [mock.call('cloud-init', target=self.tmp)])
1089+ self.assertEqual(
1090+ yaml.load(util.load_file(self.tmp_cfg)),
1091+ {'apt': {'preserve_sources_list': True}})
1092+
1093 # vi: ts=4 expandtab syntax=python
1094diff --git a/tests/unittests/test_commands_block_meta.py b/tests/unittests/test_commands_block_meta.py
1095index e70d6ed..45b9906 100644
1096--- a/tests/unittests/test_commands_block_meta.py
1097+++ b/tests/unittests/test_commands_block_meta.py
1098@@ -85,8 +85,9 @@ class TestBlockMetaSimple(CiTestCase):
1099 self.mock_block_get_root_device.assert_called_with([devname],
1100 paths=paths)
1101
1102+ @patch('curtin.commands.block_meta.meta_clear')
1103 @patch('curtin.commands.block_meta.write_image_to_disk')
1104- def test_meta_simple_calls_write_img(self, mock_write_image):
1105+ def test_meta_simple_calls_write_img(self, mock_write_image, mock_clear):
1106 devname = "fakedisk1p1"
1107 devnode = "/dev/" + devname
1108 sources = {
1109@@ -104,9 +105,9 @@ class TestBlockMetaSimple(CiTestCase):
1110 mock_write_image.return_value = devname
1111
1112 args = Namespace(target=self.target, devices=None, mode=None,
1113- boot_fstype=None, fstype=None)
1114+ boot_fstype=None, fstype=None, force_mode=False)
1115
1116- block_meta.meta_simple(args)
1117+ block_meta.block_meta(args)
1118
1119 mock_write_image.assert_called_with(sources.get('unittest'), devname)
1120 self.mock_subp.assert_has_calls(
1121diff --git a/tests/unittests/test_commands_extract.py b/tests/unittests/test_commands_extract.py
1122index e604d7f..cc117bb 100644
1123--- a/tests/unittests/test_commands_extract.py
1124+++ b/tests/unittests/test_commands_extract.py
1125@@ -27,8 +27,9 @@ class TestExtractRootFsImageUrl(CiTestCase):
1126 tmpd = self.tmp_dir()
1127 target = self.tmp_path("target_d", tmpd)
1128 startdir = os.getcwd()
1129+ fname = "my.img"
1130 try:
1131- fname = "my.img"
1132+ os.chdir(tmpd)
1133 util.write_file(fname, fname + " data\n")
1134 extract_root_fsimage_url("file://" + fname, target)
1135 finally:
1136diff --git a/tests/unittests/test_make_dname.py b/tests/unittests/test_make_dname.py
1137index 2b92a88..76c7b28 100644
1138--- a/tests/unittests/test_make_dname.py
1139+++ b/tests/unittests/test_make_dname.py
1140@@ -13,10 +13,16 @@ class TestMakeDname(CiTestCase):
1141 state = {'scratch': '/tmp/null'}
1142 rules_d = '/tmp/null/rules.d'
1143 rule_file = '/tmp/null/rules.d/{}.rules'
1144+ disk_serial = 'abcdefg'
1145+ disk_wwn = '0x1234567890'
1146 storage_config = {
1147- 'disk1': {'type': 'disk', 'id': 'disk1', 'name': 'main_disk'},
1148+ 'disk1': {'type': 'disk', 'id': 'disk1', 'name': 'main_disk',
1149+ 'serial': disk_serial},
1150+ 'disk_noid': {'type': 'disk', 'id': 'disk_noid', 'name': 'main_disk'},
1151 'disk1p1': {'type': 'partition', 'id': 'disk1p1', 'device': 'disk1'},
1152- 'disk2': {'type': 'disk', 'id': 'disk2',
1153+ 'disk1p2': {'type': 'partition', 'id': 'disk1p2', 'device': 'disk1',
1154+ 'name': 'custom-partname'},
1155+ 'disk2': {'type': 'disk', 'id': 'disk2', 'wwn': disk_wwn,
1156 'name': 'in_valid/name!@#$% &*(+disk'},
1157 'disk2p1': {'type': 'partition', 'id': 'disk2p1', 'device': 'disk2'},
1158 'md_id': {'type': 'raid', 'id': 'md_id', 'name': 'mdadm_name'},
1159@@ -27,7 +33,8 @@ class TestMakeDname(CiTestCase):
1160 'lpart2_id': {'type': 'lvm_partition', 'id': 'lpart2_id',
1161 'name': 'lvm part/2', 'volgroup': 'lvol_id'},
1162 'bcache1_id': {'type': 'bcache', 'id': 'bcache1_id',
1163- 'name': 'my-cached-data'}
1164+ 'name': 'my-cached-data'},
1165+ 'iscsi1': {'type': 'disk', 'id': 'iscsi1', 'name': 'iscsi_disk1'}
1166 }
1167 bcache_super_show = {
1168 'sb.version': '1 [backing device]',
1169@@ -57,20 +64,37 @@ class TestMakeDname(CiTestCase):
1170 rule.append('SYMLINK+="disk/by-dname/{}"\n'.format(target))
1171 return ', '.join(rule)
1172
1173+ def _content(self, rules=[]):
1174+ return "\n".join(['# Written by curtin'] + rules)
1175+
1176+ @mock.patch('curtin.commands.block_meta.udevadm_info')
1177 @mock.patch('curtin.commands.block_meta.LOG')
1178 @mock.patch('curtin.commands.block_meta.get_path_to_storage_volume')
1179 @mock.patch('curtin.commands.block_meta.util')
1180- def test_make_dname_disk(self, mock_util, mock_get_path, mock_log):
1181+ def test_make_dname_disk(self, mock_util, mock_get_path, mock_log,
1182+ mock_udev):
1183 disk_ptuuid = str(uuid.uuid1())
1184 mock_util.subp.side_effect = self._make_mock_subp_blkid(
1185 disk_ptuuid, self.disk_blkid)
1186 mock_util.load_command_environment.return_value = self.state
1187- rule_identifiers = [
1188- ('DEVTYPE', 'disk'),
1189- ('ID_PART_TABLE_UUID', disk_ptuuid)
1190- ]
1191+
1192+ rule_identifiers = [('ID_PART_TABLE_UUID', disk_ptuuid)]
1193+ id_rule_identifiers = [('ID_SERIAL', self.disk_serial)]
1194+ wwn_rule_identifiers = [('ID_WWN_WITH_EXTENSION', self.disk_wwn)]
1195+
1196+ def _drule(devtype, match):
1197+ return [('DEVTYPE', devtype)] + [m for m in match]
1198+
1199+ def drule(match):
1200+ return _drule('disk', match)
1201+
1202+ def prule(match):
1203+ return _drule('partition', match)
1204
1205 # simple run
1206+ mock_udev.side_effect = (
1207+ [{'DEVTYPE': 'disk', 'ID_SERIAL': self.disk_serial},
1208+ {'DEVTYPE': 'disk', 'ID_WWN_WITH_EXTENSION': self.disk_wwn}])
1209 res_dname = 'main_disk'
1210 block_meta.make_dname('disk1', self.storage_config)
1211 mock_util.ensure_dir.assert_called_with(self.rules_d)
1212@@ -78,7 +102,13 @@ class TestMakeDname(CiTestCase):
1213 self.assertFalse(mock_log.warning.called)
1214 mock_util.write_file.assert_called_with(
1215 self.rule_file.format(res_dname),
1216- self._formatted_rule(rule_identifiers, res_dname))
1217+ self._content(
1218+ [self._formatted_rule(drule(rule_identifiers),
1219+ res_dname),
1220+ self._formatted_rule(drule(id_rule_identifiers),
1221+ res_dname),
1222+ self._formatted_rule(prule(id_rule_identifiers),
1223+ "%s-part%%n" % res_dname)]))
1224
1225 # run invalid dname
1226 res_dname = 'in_valid-name----------disk'
1227@@ -86,12 +116,39 @@ class TestMakeDname(CiTestCase):
1228 self.assertTrue(mock_log.warning.called)
1229 mock_util.write_file.assert_called_with(
1230 self.rule_file.format(res_dname),
1231- self._formatted_rule(rule_identifiers, res_dname))
1232+ self._content(
1233+ [self._formatted_rule(drule(rule_identifiers),
1234+ res_dname),
1235+ self._formatted_rule(drule(wwn_rule_identifiers),
1236+ res_dname),
1237+ self._formatted_rule(prule(wwn_rule_identifiers),
1238+ "%s-part%%n" % res_dname)]))
1239
1240+ # iscsi disk with no config, but info returns serial and wwn
1241+ mock_udev.side_effect = (
1242+ [{'DEVTYPE': 'disk', 'ID_SERIAL': self.disk_serial,
1243+ 'DEVTYPE': 'disk', 'ID_WWN_WITH_EXTENSION': self.disk_wwn}])
1244+ res_dname = 'iscsi_disk1'
1245+ block_meta.make_dname('iscsi1', self.storage_config)
1246+ mock_util.ensure_dir.assert_called_with(self.rules_d)
1247+ self.assertTrue(mock_log.debug.called)
1248+ both_rules = (id_rule_identifiers + wwn_rule_identifiers)
1249+ mock_util.write_file.assert_called_with(
1250+ self.rule_file.format(res_dname),
1251+ self._content(
1252+ [self._formatted_rule(drule(rule_identifiers), res_dname),
1253+ self._formatted_rule(drule(both_rules), res_dname),
1254+ self._formatted_rule(prule(both_rules),
1255+ "%s-part%%n" % res_dname)]))
1256+
1257+ @mock.patch('curtin.commands.block_meta.udevadm_info')
1258 @mock.patch('curtin.commands.block_meta.LOG')
1259 @mock.patch('curtin.commands.block_meta.get_path_to_storage_volume')
1260 @mock.patch('curtin.commands.block_meta.util')
1261- def test_make_dname_failures(self, mock_util, mock_get_path, mock_log):
1262+ def test_make_dname_failures(self, mock_util, mock_get_path, mock_log,
1263+ mock_udev):
1264+ mock_udev.side_effect = ([{'DEVTYPE': 'disk'}, {'DEVTYPE': 'disk'}])
1265+
1266 mock_util.subp.side_effect = self._make_mock_subp_blkid(
1267 '', self.trusty_blkid)
1268 mock_util.load_command_environment.return_value = self.state
1269@@ -99,10 +156,14 @@ class TestMakeDname(CiTestCase):
1270 warning_msg = "Can't find a uuid for volume: {}. Skipping dname."
1271
1272 # disk with no PT_UUID
1273- block_meta.make_dname('disk1', self.storage_config)
1274- mock_log.warning.assert_called_with(warning_msg.format('disk1'))
1275- self.assertFalse(mock_util.write_file.called)
1276+ disk = 'disk_noid'
1277+ with self.assertRaises(RuntimeError):
1278+ block_meta.make_dname(disk, self.storage_config)
1279+ mock_log.warning.assert_called_with(warning_msg.format(disk))
1280+ self.assertFalse(mock_util.write_file.called)
1281
1282+ mock_util.subp.side_effect = self._make_mock_subp_blkid(
1283+ '', self.trusty_blkid)
1284 # partition with no PART_UUID
1285 block_meta.make_dname('disk1p1', self.storage_config)
1286 mock_log.warning.assert_called_with(warning_msg.format('disk1p1'))
1287@@ -123,22 +184,15 @@ class TestMakeDname(CiTestCase):
1288 ]
1289
1290 # simple run
1291- res_dname = 'main_disk-part1'
1292- block_meta.make_dname('disk1p1', self.storage_config)
1293+ res_dname = 'custom-partname'
1294+ block_meta.make_dname('disk1p2', self.storage_config)
1295 mock_util.ensure_dir.assert_called_with(self.rules_d)
1296 self.assertTrue(mock_log.debug.called)
1297 self.assertFalse(mock_log.warning.called)
1298 mock_util.write_file.assert_called_with(
1299 self.rule_file.format(res_dname),
1300- self._formatted_rule(rule_identifiers, res_dname))
1301-
1302- # run invalid dname
1303- res_dname = 'in_valid-name----------disk-part1'
1304- block_meta.make_dname('disk2p1', self.storage_config)
1305- self.assertTrue(mock_log.warning.called)
1306- mock_util.write_file.assert_called_with(
1307- self.rule_file.format(res_dname),
1308- self._formatted_rule(rule_identifiers, res_dname))
1309+ self._content(
1310+ [self._formatted_rule(rule_identifiers, res_dname)]))
1311
1312 @mock.patch('curtin.commands.block_meta.mdadm')
1313 @mock.patch('curtin.commands.block_meta.LOG')
1314@@ -158,7 +212,8 @@ class TestMakeDname(CiTestCase):
1315 self.assertFalse(mock_log.warning.called)
1316 mock_util.write_file.assert_called_with(
1317 self.rule_file.format(res_dname),
1318- self._formatted_rule(rule_identifiers, res_dname))
1319+ self._content(
1320+ [self._formatted_rule(rule_identifiers, res_dname)]))
1321
1322 # invalid name
1323 res_dname = 'mdadm-name'
1324@@ -166,7 +221,8 @@ class TestMakeDname(CiTestCase):
1325 self.assertTrue(mock_log.warning.called)
1326 mock_util.write_file.assert_called_with(
1327 self.rule_file.format(res_dname),
1328- self._formatted_rule(rule_identifiers, res_dname))
1329+ self._content(
1330+ [self._formatted_rule(rule_identifiers, res_dname)]))
1331
1332 @mock.patch('curtin.commands.block_meta.LOG')
1333 @mock.patch('curtin.commands.block_meta.get_path_to_storage_volume')
1334@@ -183,7 +239,8 @@ class TestMakeDname(CiTestCase):
1335 self.assertFalse(mock_log.warning.called)
1336 mock_util.write_file.assert_called_with(
1337 self.rule_file.format(res_dname),
1338- self._formatted_rule(rule_identifiers, res_dname))
1339+ self._content(
1340+ [self._formatted_rule(rule_identifiers, res_dname)]))
1341
1342 # with invalid name
1343 res_dname = 'vg1-lvm-part-2'
1344@@ -192,7 +249,8 @@ class TestMakeDname(CiTestCase):
1345 self.assertTrue(mock_log.warning.called)
1346 mock_util.write_file.assert_called_with(
1347 self.rule_file.format(res_dname),
1348- self._formatted_rule(rule_identifiers, res_dname))
1349+ self._content(
1350+ [self._formatted_rule(rule_identifiers, res_dname)]))
1351
1352 @mock.patch('curtin.commands.block_meta.LOG')
1353 @mock.patch('curtin.commands.block_meta.bcache')
1354@@ -213,7 +271,8 @@ class TestMakeDname(CiTestCase):
1355 self.assertFalse(mock_log.warning.called)
1356 mock_util.write_file.assert_called_with(
1357 self.rule_file.format(res_dname),
1358- self._formatted_rule(rule_identifiers, res_dname))
1359+ self._content(
1360+ [self._formatted_rule(rule_identifiers, res_dname)]))
1361
1362 def test_sanitize_dname(self):
1363 unsanitized_to_sanitized = [
1364@@ -226,4 +285,87 @@ class TestMakeDname(CiTestCase):
1365 for (unsanitized, sanitized) in unsanitized_to_sanitized:
1366 self.assertEqual(block_meta.sanitize_dname(unsanitized), sanitized)
1367
1368+
1369+class TestMakeDnameById(CiTestCase):
1370+
1371+ @mock.patch('curtin.commands.block_meta.udevadm_info')
1372+ def test_bad_path(self, m_udev):
1373+ """test dname_byid raises ValueError on invalid path."""
1374+ mypath = None
1375+ with self.assertRaises(ValueError):
1376+ block_meta.make_dname_byid(mypath)
1377+
1378+ @mock.patch('curtin.commands.block_meta.udevadm_info')
1379+ def test_non_disk(self, m_udev):
1380+ """test dname_byid raises ValueError on DEVTYPE != 'disk'"""
1381+ mypath = "/dev/" + self.random_string()
1382+ m_udev.return_value = {'DEVTYPE': 'not_a_disk'}
1383+ with self.assertRaises(ValueError):
1384+ block_meta.make_dname_byid(mypath)
1385+
1386+ @mock.patch('curtin.commands.block_meta.udevadm_info')
1387+ def test_disk_with_no_id_wwn(self, m_udev):
1388+ """test dname_byid raises RuntimeError on device without ID or WWN."""
1389+ mypath = "/dev/" + self.random_string()
1390+ m_udev.return_value = {'DEVTYPE': 'disk'}
1391+ with self.assertRaises(RuntimeError):
1392+ block_meta.make_dname_byid(mypath)
1393+
1394+ @mock.patch('curtin.commands.block_meta.udevadm_info')
1395+ def test_udevinfo_not_called_if_info_provided(self, m_udev):
1396+ """dname_byid does not invoke udevadm_info if using info dict"""
1397+ myserial = self.random_string()
1398+ self.assertEqual(
1399+ [['ENV{ID_SERIAL}=="%s"' % myserial]],
1400+ block_meta.make_dname_byid(
1401+ self.random_string(),
1402+ info={'DEVTYPE': 'disk', 'ID_SERIAL': myserial}))
1403+ self.assertEqual(0, m_udev.call_count)
1404+
1405+ @mock.patch('curtin.commands.block_meta.udevadm_info')
1406+ def test_udevinfo_called_if_info_not_provided(self, m_udev):
1407+ """dname_byid should call udevadm_info if no data given."""
1408+ myserial = self.random_string()
1409+ mypath = "/dev/" + self.random_string()
1410+ m_udev.return_value = {
1411+ 'DEVTYPE': 'disk', 'ID_SERIAL': myserial, 'DEVNAME': mypath}
1412+ self.assertEqual(
1413+ [['ENV{ID_SERIAL}=="%s"' % myserial]],
1414+ block_meta.make_dname_byid(mypath))
1415+ self.assertEqual(
1416+ [mock.call(path=mypath)], m_udev.call_args_list)
1417+
1418+ def test_disk_with_only_serial(self):
1419+ """test dname_byid returns rules for ID_SERIAL"""
1420+ mypath = "/dev/" + self.random_string()
1421+ myserial = self.random_string()
1422+ info = {'DEVTYPE': 'disk', 'DEVNAME': mypath, 'ID_SERIAL': myserial}
1423+ self.assertEqual(
1424+ [['ENV{ID_SERIAL}=="%s"' % myserial]],
1425+ block_meta.make_dname_byid(mypath, info=info))
1426+
1427+ def test_disk_with_only_wwn(self):
1428+ """test dname_byid returns rules for ID_WWN_WITH_EXTENSION"""
1429+ mypath = "/dev/" + self.random_string()
1430+ mywwn = self.random_string()
1431+ info = {'DEVTYPE': 'disk', 'DEVNAME': mypath,
1432+ 'ID_WWN_WITH_EXTENSION': mywwn}
1433+ self.assertEqual(
1434+ [['ENV{ID_WWN_WITH_EXTENSION}=="%s"' % mywwn]],
1435+ block_meta.make_dname_byid(mypath, info=info))
1436+
1437+ def test_disk_with_both_id_wwn(self):
1438+ """test dname_byid returns rules with both ID_SERIAL and ID_WWN"""
1439+ mypath = "/dev/" + self.random_string()
1440+ myserial = self.random_string()
1441+ mywwn = self.random_string()
1442+ info = {'DEVTYPE': 'disk', 'ID_SERIAL': myserial,
1443+ 'ID_WWN_WITH_EXTENSION': mywwn,
1444+ 'DEVNAME': mypath}
1445+ self.assertEqual(
1446+ [['ENV{ID_SERIAL}=="%s"' % myserial,
1447+ 'ENV{ID_WWN_WITH_EXTENSION}=="%s"' % mywwn]],
1448+ block_meta.make_dname_byid(mypath, info=info))
1449+
1450+
1451 # vi: ts=4 expandtab syntax=python
1452diff --git a/tests/unittests/test_swap.py b/tests/unittests/test_swap.py
1453index e12d12e..fd6c527 100644
1454--- a/tests/unittests/test_swap.py
1455+++ b/tests/unittests/test_swap.py
1456@@ -1,39 +1,53 @@
1457 import mock
1458
1459 from curtin import swap
1460+from curtin import util
1461 from .helpers import CiTestCase
1462
1463
1464 class TestSwap(CiTestCase):
1465- @mock.patch('curtin.swap.resource')
1466- @mock.patch('curtin.swap.util')
1467- def test_is_swap_device_read_offsets(self, mock_util, mock_resource):
1468- """swap.is_swap_device() checks offsets based on system pagesize"""
1469- path = '/mydev/dummydisk'
1470+ def _valid_swap_contents(self):
1471+ """Yields (pagesize, content) of things that should be considered
1472+ valid swap."""
1473 # 4k and 64k page size
1474 for pagesize in [4096, 65536]:
1475- magic_offset = pagesize - 10
1476- mock_resource.getpagesize.return_value = pagesize
1477- swap.is_swap_device(path)
1478- mock_util.load_file.assert_called_with(path, read_len=10,
1479- offset=magic_offset,
1480- decode=False)
1481+ for magic in [b'SWAPSPACE2', b'SWAP-SPACE']:
1482+ # yield content of 2 pages to trigger/avoid fence-post errors
1483+ yield (pagesize,
1484+ ((pagesize - len(magic)) * b'\0' +
1485+ magic + pagesize * b'\0'))
1486
1487- @mock.patch('curtin.swap.resource')
1488- @mock.patch('curtin.swap.util')
1489- def test_identify_swap_false(self, mock_util, mock_resource):
1490- """swap.is_swap_device() returns false on non swap magic"""
1491- mock_util.load_file.return_value = (
1492- b'\x00\x00c\x05\x00\x00\x11\x00\x19\x00')
1493- is_swap = swap.is_swap_device('ignored')
1494- self.assertFalse(is_swap)
1495+ @mock.patch('curtin.swap.resource.getpagesize')
1496+ def test_is_swap_device_read_offsets(self, mock_getpagesize):
1497+ """swap.is_swap_device() correctly identifies swap content."""
1498+ tmpd = self.tmp_dir()
1499+ for num, (pagesize, content) in enumerate(self._valid_swap_contents()):
1500+ path = self.tmp_path("swap-file-%02d" % num, tmpd)
1501+ util.write_file(path, content, omode="wb")
1502+ mock_getpagesize.return_value = pagesize
1503+ self.assertTrue(swap.is_swap_device(path))
1504
1505- @mock.patch('curtin.swap.resource')
1506- @mock.patch('curtin.swap.util')
1507- def test_identify_swap_true(self, mock_util, mock_resource):
1508- """swap.is_swap_device() returns true on swap magic strings"""
1509- path = '/mydev/dummydisk'
1510- for magic in [b'SWAPSPACE2', b'SWAP-SPACE']:
1511- mock_util.load_file.return_value = magic
1512- is_swap = swap.is_swap_device(path)
1513- self.assertTrue(is_swap)
1514+ @mock.patch('curtin.swap.resource.getpagesize', return_value=4096)
1515+ def test_identify_swap_false_if_tiny(self, mock_getpagesize):
1516+ """small files do not trip up is_swap_device()."""
1517+ path = self.tmp_path("tiny")
1518+ util.write_file(path, b'tinystuff', omode='wb')
1519+ self.assertFalse(swap.is_swap_device(path))
1520+
1521+ @mock.patch('curtin.swap.resource.getpagesize', return_value=4096)
1522+ def test_identify_zeros_are_swap(self, mock_getpagesize):
1523+ """swap.is_swap_device() returns false on all zeros"""
1524+ pagesize = mock_getpagesize()
1525+ path = self.tmp_path("notswap0")
1526+ util.write_file(path, pagesize * 2 * b'\0', omode="wb")
1527+ self.assertFalse(swap.is_swap_device(path))
1528+
1529+ @mock.patch('curtin.swap.resource.getpagesize', return_value=65536)
1530+ def test_identify_swap_false(self, mock_getpagesize):
1531+ """swap.is_swap_device() returns false on non swap content"""
1532+ pagesize = mock_getpagesize()
1533+ path = self.tmp_path("notswap1")
1534+ # this is just arbitrary content that is not swap content.
1535+ blob = b'\x00\x00c\x05\x00\x00\x11\x19'
1536+ util.write_file(path, int(pagesize * 2 / len(blob)) * blob, omode="wb")
1537+ self.assertFalse(swap.is_swap_device(path))
1538diff --git a/tests/unittests/test_udev.py b/tests/unittests/test_udev.py
1539new file mode 100644
1540index 0000000..0a070d5
1541--- /dev/null
1542+++ b/tests/unittests/test_udev.py
1543@@ -0,0 +1,68 @@
1544+# This file is part of curtin. See LICENSE file for copyright and license info.
1545+
1546+import mock
1547+
1548+from curtin import udev
1549+from curtin import util
1550+from .helpers import CiTestCase
1551+
1552+
1553+UDEVADM_INFO_QUERY = """\
1554+DEVLINKS=/dev/disk/by-id/nvme-eui.0025388b710116a1
1555+DEVNAME=/dev/nvme0n1
1556+DEVPATH=/devices/pci0000:00/0000:00:1c.4/0000:05:00.0/nvme/nvme0/nvme0n1
1557+DEVTYPE=disk
1558+ID_PART_TABLE_TYPE=gpt
1559+ID_PART_TABLE_UUID=ea0b9ddc-a114-4e01-b257-750d86e3a944
1560+ID_SERIAL=SAMSUNG MZVLB1T0HALR-000L7_S3TPNY0JB00151
1561+ID_SERIAL_SHORT=S3TPNY0JB00151
1562+MAJOR=259
1563+MINOR=0
1564+SUBSYSTEM=block
1565+TAGS=:systemd:
1566+USEC_INITIALIZED=2026691
1567+"""
1568+
1569+INFO_DICT = {
1570+ 'DEVLINKS': ['/dev/disk/by-id/nvme-eui.0025388b710116a1'],
1571+ 'DEVNAME': '/dev/nvme0n1',
1572+ 'DEVPATH':
1573+ '/devices/pci0000:00/0000:00:1c.4/0000:05:00.0/nvme/nvme0/nvme0n1',
1574+ 'DEVTYPE': 'disk',
1575+ 'ID_PART_TABLE_TYPE': 'gpt',
1576+ 'ID_PART_TABLE_UUID': 'ea0b9ddc-a114-4e01-b257-750d86e3a944',
1577+ 'ID_SERIAL': 'SAMSUNG MZVLB1T0HALR-000L7_S3TPNY0JB00151',
1578+ 'ID_SERIAL_SHORT': 'S3TPNY0JB00151',
1579+ 'MAJOR': '259',
1580+ 'MINOR': '0',
1581+ 'SUBSYSTEM': 'block',
1582+ 'TAGS': ':systemd:',
1583+ 'USEC_INITIALIZED': '2026691'
1584+}
1585+
1586+
1587+class TestUdevInfo(CiTestCase):
1588+
1589+ @mock.patch('curtin.util.subp')
1590+ def test_udevadm_info(self, m_subp):
1591+ """ udevadm_info returns dictionary for specified device """
1592+ mypath = '/dev/nvme0n1'
1593+ m_subp.return_value = (UDEVADM_INFO_QUERY, "")
1594+ info = udev.udevadm_info(mypath)
1595+ m_subp.assert_called_with(
1596+ ['udevadm', 'info', '--query=property', mypath], capture=True)
1597+ self.assertEqual(sorted(INFO_DICT), sorted(info))
1598+
1599+ def test_udevadm_info_no_path(self):
1600+ """ udevadm_info raises ValueError for invalid path value"""
1601+ mypath = None
1602+ with self.assertRaises(ValueError):
1603+ udev.udevadm_info(mypath)
1604+
1605+ @mock.patch('curtin.util.subp')
1606+ def test_udevadm_info_path_not_exists(self, m_subp):
1607+ """ udevadm_info raises ProcessExecutionError for invalid path value"""
1608+ mypath = self.random_string()
1609+ m_subp.side_effect = util.ProcessExecutionError()
1610+ with self.assertRaises(util.ProcessExecutionError):
1611+ udev.udevadm_info(mypath)
1612diff --git a/tests/vmtests/__init__.py b/tests/vmtests/__init__.py
1613index 3823e39..bc4a87b 100644
1614--- a/tests/vmtests/__init__.py
1615+++ b/tests/vmtests/__init__.py
1616@@ -21,6 +21,7 @@ from curtin.block import iscsi
1617
1618 from .report_webhook_logger import CaptureReporting
1619 from curtin.commands.install import INSTALL_PASS_MSG
1620+from curtin.commands.block_meta import sanitize_dname
1621
1622 from .image_sync import query as imagesync_query
1623 from .image_sync import mirror as imagesync_mirror
1624@@ -344,12 +345,17 @@ class TempDir(object):
1625
1626 def collect_output(self):
1627 logger.debug('extracting output disk')
1628- subprocess.check_call(['tar', '-C', self.collect, '-xf',
1629- self.output_disk],
1630- stdout=DEVNULL, stderr=subprocess.STDOUT)
1631- # make sure collect output dir is usable by non-root
1632- subprocess.check_call(['chmod', '-R', 'u+rwX', self.collect],
1633- stdout=DEVNULL, stderr=subprocess.STDOUT)
1634+ try:
1635+ subprocess.check_call(['tar', '-C', self.collect, '-xf',
1636+ self.output_disk],
1637+ stdout=DEVNULL, stderr=subprocess.STDOUT)
1638+ except subprocess.CalledProcessError as e:
1639+ logger.error('Failed unpacking collect output: %s', e)
1640+ finally:
1641+ logger.debug('Fixing collect output dir permissions.')
1642+ # make sure collect output dir is usable by non-root
1643+ subprocess.check_call(['chmod', '-R', 'u+rwX', self.collect],
1644+ stdout=DEVNULL, stderr=subprocess.STDOUT)
1645
1646
1647 def skip_if_flag(flag):
1648@@ -515,11 +521,17 @@ DEFAULT_COLLECT_SCRIPTS = {
1649 ls -al /dev/disk/by-dname/ | cat >ls_al_bydname
1650 ls -al /dev/disk/by-id/ | cat >ls_al_byid
1651 ls -al /dev/disk/by-uuid/ | cat >ls_al_byuuid
1652+ ls -al /dev/disk/by-partuuid/ | cat >ls_al_bypartuuid
1653 blkid -o export | cat >blkid.out
1654 find /boot | cat > find_boot.out
1655- [ -e /sys/firmware/efi ] && {
1656+ if [ -e /sys/firmware/efi ]; then
1657 efibootmgr -v | cat >efibootmgr.out;
1658- }
1659+ fi
1660+ [ ! -d /etc/default/grub.d ] ||
1661+ cp -a /etc/default/grub.d etc_default_grub_d
1662+ [ ! -f /etc/default/grub ] || cp /etc/default/grub etc_default_grub
1663+
1664+ exit 0
1665 """)],
1666 'centos': [textwrap.dedent("""
1667 # XXX: command | cat >output is required for Centos under SELinux
1668@@ -530,6 +542,8 @@ DEFAULT_COLLECT_SCRIPTS = {
1669 rpm -q --queryformat '%{VERSION}\n' cloud-init |tee rpm_ci_version
1670 rpm -E '%rhel' > rpm_dist_version_major
1671 cp -a /etc/centos-release .
1672+
1673+ exit 0
1674 """)],
1675 'ubuntu': [textwrap.dedent("""
1676 cd OUTPUT_COLLECT_D
1677@@ -543,6 +557,8 @@ DEFAULT_COLLECT_SCRIPTS = {
1678 out=$(apt-config shell v Acquire::HTTP::Proxy)
1679 eval "$out"
1680 echo "$v" > apt-proxy
1681+
1682+ exit 0
1683 """)]
1684 }
1685
1686@@ -890,6 +906,11 @@ class VMBaseClass(TestCase):
1687 "--append=ro",
1688 ])
1689
1690+ # Avoid LP: #1797218 and make vms boot faster
1691+ cmd.extend(['--append=%s' % service for service in
1692+ ["systemd.mask=snapd.seeded.service",
1693+ "systemd.mask=snapd.service"]])
1694+
1695 # getting resolvconf configured is only fixed in bionic
1696 # the iscsi_auto handles resolvconf setup via call to
1697 # configure_networking in initramfs
1698@@ -935,13 +956,21 @@ class VMBaseClass(TestCase):
1699
1700 # build disk arguments
1701 disks = []
1702- sc = cls.load_conf_file()
1703- storage_config = yaml.load(sc).get('storage', {}).get('config', {})
1704+ storage_config = cls.get_class_storage_config()
1705 cls.disk_wwns = ["wwn=%s" % x.get('wwn') for x in storage_config
1706 if 'wwn' in x]
1707- cls.disk_serials = ["serial=%s" % x.get('serial')
1708- for x in storage_config if 'serial' in x]
1709+ cls.disk_serials = []
1710+ cls.nvme_serials = []
1711+ for x in storage_config:
1712+ if 'serial' in x:
1713+ serial = x.get('serial')
1714+ if serial.startswith('nvme'):
1715+ cls.nvme_serials.append("serial=%s" % serial)
1716+ else:
1717+ cls.disk_serials.append("serial=%s" % serial)
1718
1719+ logger.info("disk_serials: %s", cls.disk_serials)
1720+ logger.info("nvme_serials: %s", cls.nvme_serials)
1721 target_disk = "{}:{}:{}:{}:".format(cls.td.target_disk,
1722 "",
1723 cls.disk_driver,
1724@@ -973,11 +1002,13 @@ class VMBaseClass(TestCase):
1725 disks.extend(['--disk', extra_disk])
1726
1727 # build nvme disk args if needed
1728+ logger.info('nvme disks: %s', cls.nvme_disks)
1729 for (disk_no, disk_sz) in enumerate(cls.nvme_disks):
1730 dpath = os.path.join(cls.td.disks, 'nvme_disk_%d.img' % disk_no)
1731+ nvme_serial = cls.nvme_serials[disk_no]
1732 nvme_disk = '{}:{}:nvme:{}:{}'.format(dpath, disk_sz,
1733 cls.disk_block_size,
1734- "serial=nvme-%d" % disk_no)
1735+ "%s" % nvme_serial)
1736 disks.extend(['--disk', nvme_disk])
1737
1738 # build iscsi disk args if needed
1739@@ -1206,6 +1237,8 @@ class VMBaseClass(TestCase):
1740 dpath = os.path.join(cls.td.disks, 'nvme_disk_%d.img' % disk_no)
1741 disk = '--disk={},driver={},format={},{}'.format(
1742 dpath, disk_driver, TARGET_IMAGE_FORMAT, bsize_args)
1743+ if len(cls.nvme_serials):
1744+ disk += ",%s" % cls.nvme_serials[disk_no]
1745 nvme_disks.extend([disk])
1746
1747 # unlike NVMe disks, we do not want to configure the iSCSI disks
1748@@ -1525,8 +1558,41 @@ class VMBaseClass(TestCase):
1749 for diskname, part in self.disk_to_check:
1750 if part is not 0:
1751 link = diskname + "-part" + str(part)
1752- self.assertIn(link, contents)
1753- self.assertIn(diskname, contents)
1754+ self.assertIn(link, contents.splitlines())
1755+ self.assertIn(diskname, contents.splitlines())
1756+
1757+ @skip_if_flag('expected_failure')
1758+ def test_dname_rules(self, disk_to_check=None):
1759+ if self.target_distro != "ubuntu":
1760+ raise SkipTest("dname not present in non-ubuntu releases")
1761+
1762+ if not disk_to_check:
1763+ disk_to_check = self.disk_to_check
1764+ if disk_to_check is None:
1765+ logger.debug('test_dname_rules: no disks to check')
1766+ return
1767+ logger.debug('test_dname_rules: checking disks: %s', disk_to_check)
1768+ self.output_files_exist(["udev_rules.d"])
1769+
1770+ cfg = yaml.load(self.load_collect_file("root/curtin-install-cfg.yaml"))
1771+ stgcfg = cfg.get("storage", {}).get("config", [])
1772+ disks = [ent for ent in stgcfg if (ent.get('type') == 'disk' and
1773+ 'name' in ent)]
1774+ key_to_udev = {
1775+ 'serial': 'ID_SERIAL',
1776+ 'wwn': 'ID_WWN_WITH_EXTENSION',
1777+ }
1778+ for disk in disks:
1779+ dname_file = "%s.rules" % sanitize_dname(disk.get('name'))
1780+ contents = self.load_collect_file("udev_rules.d/%s" % dname_file)
1781+ for key, key_name in key_to_udev.items():
1782+ value = disk.get(key)
1783+ if value:
1784+ # serials may include spaces, udev replaces them with # _
1785+ if ' ' in value:
1786+ value = value.replace(' ', '_')
1787+ self.assertIn(key_name, contents)
1788+ self.assertIn(value, contents)
1789
1790 @skip_if_flag('expected_failure')
1791 def test_reporting_data(self):
1792@@ -1570,6 +1636,29 @@ class VMBaseClass(TestCase):
1793 kpackage = self.get_kernel_package()
1794 self.assertIn(kpackage, self.debian_packages)
1795
1796+ @skip_if_flag('expected_failure')
1797+ def test_clear_holders_ran(self):
1798+ """ Test curtin install runs block-meta/clear-holders. """
1799+ if not self.has_storage_config():
1800+ raise SkipTest("This test does not use storage config.")
1801+
1802+ install_logfile = 'root/curtin-install.log'
1803+ self.output_files_exist([install_logfile])
1804+ install_log = self.load_collect_file(install_logfile)
1805+
1806+ # validate block-meta called clear-holders at least once
1807+ # We match both 'start' and 'finish' strings, so for each
1808+ # call we'll have 2 matches.
1809+ clear_holders_re = 'cmd-install/.*cmd-block-meta/clear-holders'
1810+ events = re.findall(clear_holders_re, install_log)
1811+ print('Matched clear-holder events:\n%s' % events)
1812+ self.assertGreaterEqual(len(events), 2)
1813+
1814+ # dirty_disks mode runs an early block-meta command which
1815+ # also runs clear-holders
1816+ if self.dirty_disks is True:
1817+ self.assertGreaterEqual(len(events), 4)
1818+
1819 def run(self, result):
1820 super(VMBaseClass, self).run(result)
1821 self.record_result(result)
1822@@ -1609,14 +1698,25 @@ class VMBaseClass(TestCase):
1823 self._debian_packages = pkgs
1824 return self._debian_packages
1825
1826+ @classmethod
1827+ def get_class_storage_config(cls):
1828+ sc = cls.load_conf_file()
1829+ return yaml.load(sc).get('storage', {}).get('config', {})
1830+
1831+ def get_storage_config(self):
1832+ cfg = yaml.load(self.load_collect_file("root/curtin-install-cfg.yaml"))
1833+ return cfg.get("storage", {}).get("config", [])
1834+
1835+ def has_storage_config(self):
1836+ '''check if test used storage config'''
1837+ return len(self.get_storage_config()) > 0
1838+
1839 @skip_if_flag('expected_failure')
1840 def test_swaps_used(self):
1841- cfg = yaml.load(self.load_collect_file("root/curtin-install-cfg.yaml"))
1842- stgcfg = cfg.get("storage", {}).get("config", [])
1843- if len(stgcfg) == 0:
1844- logger.debug("This test does not use storage config.")
1845- return
1846+ if not self.has_storage_config():
1847+ raise SkipTest("This test does not use storage config.")
1848
1849+ stgcfg = self.get_storage_config()
1850 swap_ids = [d["id"] for d in stgcfg if d.get("fstype") == "swap"]
1851 swap_mounts = [d for d in stgcfg if d.get("device") in swap_ids]
1852 self.assertEqual(len(swap_ids), len(swap_mounts),
1853@@ -1715,6 +1815,9 @@ class PsuedoVMBaseClass(VMBaseClass):
1854 def test_dname(self, disk_to_check=None):
1855 pass
1856
1857+ def test_dname_rules(self, disk_to_check=None):
1858+ pass
1859+
1860 def test_interfacesd_eth0_removed(self):
1861 pass
1862
1863@@ -1772,6 +1875,7 @@ def check_install_log(install_log, nrchars=200):
1864 install_fail = "({})".format("|".join([
1865 'Installation failed',
1866 'ImportError: No module named.*',
1867+ 'Out of memory:',
1868 'Unexpected error while running command',
1869 'E: Unable to locate package.*',
1870 'cloud-init.*: Traceback.*']))
1871diff --git a/tests/vmtests/releases.py b/tests/vmtests/releases.py
1872index 7be8feb..5dfb2d2 100644
1873--- a/tests/vmtests/releases.py
1874+++ b/tests/vmtests/releases.py
1875@@ -113,6 +113,11 @@ class _CosmicBase(_UbuntuBase):
1876 target_release = "cosmic"
1877
1878
1879+class _DiscoBase(_UbuntuBase):
1880+ release = "disco"
1881+ target_release = "disco"
1882+
1883+
1884 class _Releases(object):
1885 trusty = _TrustyBase
1886 precise = _PreciseBase
1887@@ -128,6 +133,7 @@ class _Releases(object):
1888 xenial_edge = _XenialEdge
1889 bionic = _BionicBase
1890 cosmic = _CosmicBase
1891+ disco = _DiscoBase
1892
1893
1894 class _CentosReleases(object):
1895diff --git a/tests/vmtests/test_apt_config_cmd.py b/tests/vmtests/test_apt_config_cmd.py
1896index f9b6a09..1296240 100644
1897--- a/tests/vmtests/test_apt_config_cmd.py
1898+++ b/tests/vmtests/test_apt_config_cmd.py
1899@@ -5,6 +5,7 @@
1900 apt-config standalone command.
1901 """
1902 import textwrap
1903+import yaml
1904
1905 from . import VMBaseClass
1906 from .releases import base_vm_classes as relbase
1907@@ -23,6 +24,8 @@ class TestAptConfigCMD(VMBaseClass):
1908 cp /etc/apt/sources.list.d/curtin-dev-ubuntu-test-archive-*.list .
1909 cp /etc/cloud/cloud.cfg.d/curtin-preserve-sources.cfg .
1910 apt-cache policy | grep proposed > proposed-enabled
1911+
1912+ exit 0
1913 """)]
1914
1915 def test_cmd_proposed_enabled(self):
1916@@ -44,8 +47,10 @@ class TestAptConfigCMD(VMBaseClass):
1917 def test_cmd_preserve_source(self):
1918 """check if cloud-init was prevented from overwriting"""
1919 self.output_files_exist(["curtin-preserve-sources.cfg"])
1920- self.check_file_regex("curtin-preserve-sources.cfg",
1921- "apt_preserve_sources_list.*true")
1922+ # For earlier than xenial 'apt_preserve_sources_list' is expected
1923+ self.assertEqual(
1924+ {'apt': {'preserve_sources_list': True}},
1925+ yaml.load(self.load_collect_file("curtin-preserve-sources.cfg")))
1926
1927
1928 class XenialTestAptConfigCMDCMD(relbase.xenial, TestAptConfigCMD):
1929@@ -62,4 +67,8 @@ class BionicTestAptConfigCMDCMD(relbase.bionic, TestAptConfigCMD):
1930 class CosmicTestAptConfigCMDCMD(relbase.cosmic, TestAptConfigCMD):
1931 __test__ = True
1932
1933+
1934+class DiscoTestAptConfigCMDCMD(relbase.disco, TestAptConfigCMD):
1935+ __test__ = True
1936+
1937 # vi: ts=4 expandtab syntax=python
1938diff --git a/tests/vmtests/test_apt_source.py b/tests/vmtests/test_apt_source.py
1939index bb502b2..2cd7267 100644
1940--- a/tests/vmtests/test_apt_source.py
1941+++ b/tests/vmtests/test_apt_source.py
1942@@ -4,6 +4,7 @@
1943 Collection of tests for the apt configuration features
1944 """
1945 import textwrap
1946+import yaml
1947
1948 from . import VMBaseClass
1949 from .releases import base_vm_classes as relbase
1950@@ -34,6 +35,8 @@ class TestAptSrcAbs(VMBaseClass):
1951 apt-config dump | grep Retries > aptconf
1952 cp /etc/apt/sources.list sources.list
1953 cp /etc/cloud/cloud.cfg.d/curtin-preserve-sources.cfg .
1954+
1955+ exit 0
1956 """)]
1957 mirror = "http://us.archive.ubuntu.com/ubuntu"
1958 secmirror = "http://security.ubuntu.com/ubuntu"
1959@@ -61,8 +64,10 @@ class TestAptSrcAbs(VMBaseClass):
1960 def test_preserve_source(self):
1961 """test_preserve_source - no clobbering sources.list by cloud-init"""
1962 self.output_files_exist(["curtin-preserve-sources.cfg"])
1963- self.check_file_regex("curtin-preserve-sources.cfg",
1964- "apt_preserve_sources_list.*true")
1965+ # For earlier than xenial 'apt_preserve_sources_list' is expected
1966+ self.assertEqual(
1967+ {'apt': {'preserve_sources_list': True}},
1968+ yaml.load(self.load_collect_file("curtin-preserve-sources.cfg")))
1969
1970 def test_source_files(self):
1971 """test_source_files - Check generated .lists for correct content"""
1972diff --git a/tests/vmtests/test_basic.py b/tests/vmtests/test_basic.py
1973index 54e3df8..48f07d6 100644
1974--- a/tests/vmtests/test_basic.py
1975+++ b/tests/vmtests/test_basic.py
1976@@ -17,9 +17,14 @@ class TestBasicAbs(VMBaseClass):
1977 dirty_disks = True
1978 conf_file = "examples/tests/basic.yaml"
1979 extra_disks = ['128G', '128G', '4G']
1980- nvme_disks = ['4G']
1981- disk_to_check = [('main_disk_with_in---valid--dname', 1),
1982- ('main_disk_with_in---valid--dname', 2)]
1983+ disk_to_check = [('btrfs_volume', 0),
1984+ ('main_disk_with_in---valid--dname', 0),
1985+ ('main_disk_with_in---valid--dname', 1),
1986+ ('main_disk_with_in---valid--dname', 2),
1987+ ('pnum_disk', 0),
1988+ ('pnum_disk', 1),
1989+ ('pnum_disk', 10),
1990+ ('sparedisk', 0)]
1991 extra_collect_scripts = [textwrap.dedent("""
1992 cd OUTPUT_COLLECT_D
1993 blkid -o export /dev/vda | cat >blkid_output_vda
1994@@ -32,6 +37,13 @@ class TestBasicAbs(VMBaseClass):
1995 btrfs inspect-internal dump-super $dev |
1996 awk '/^dev_item.fsid/ {print $2}'
1997 fi | cat >$f
1998+
1999+ # compare via /dev/zero 8MB
2000+ cmp --bytes=8388608 /dev/zero /dev/vde2; echo "$?" > cmp_prep.out
2001+ # extract partition info
2002+ udevadm info --export --query=property /dev/vde2 | cat >udev_info.out
2003+
2004+ exit 0
2005 """)]
2006
2007 def _kname_to_uuid(self, kname):
2008@@ -108,6 +120,21 @@ class TestBasicAbs(VMBaseClass):
2009 # compare them
2010 self.assertEqual(kname_uuid, btrfs_uuid)
2011
2012+ def _test_partition_is_prep(self, info_file):
2013+ udev_info = self.load_collect_file(info_file).rstrip()
2014+ entry_type = ''
2015+ for line in udev_info.splitlines():
2016+ if line.startswith('ID_PART_ENTRY_TYPE'):
2017+ entry_type = line.split("=", 1)[1].replace("'", "")
2018+ break
2019+ # https://en.wikipedia.org/wiki/GUID_Partition_Table
2020+ # GPT PReP boot UUID
2021+ self.assertEqual('9e1a2d38-c612-4316-aa26-8b49521e5a8b'.lower(),
2022+ entry_type.lower())
2023+
2024+ def _test_partition_is_zero(self, cmp_file):
2025+ self.assertEqual(0, int(self.load_collect_file(cmp_file).rstrip()))
2026+
2027 # class specific input
2028 def test_output_files_exist(self):
2029 self.output_files_exist(
2030@@ -118,9 +145,9 @@ class TestBasicAbs(VMBaseClass):
2031 self._test_ptable("blkid_output_vda", "dos")
2032
2033 def test_partition_numbers(self):
2034- # vde should have partitions 1 and 10
2035+ # vde should have partitions 1 2, and 10
2036 disk = "vde"
2037- expected = [disk + s for s in ["", "1", "10"]]
2038+ expected = [disk + s for s in ["", "1", "2", "10"]]
2039 self._test_partition_numbers(disk, expected)
2040
2041 def test_fstab_entries(self):
2042@@ -157,6 +184,12 @@ class TestBasicAbs(VMBaseClass):
2043 print('Source repo version: %s' % source_version)
2044 self.assertEqual(source_version, installed_version)
2045
2046+ def test_partition_is_prep(self):
2047+ self._test_partition_is_prep("udev_info.out")
2048+
2049+ def test_partition_is_zero(self):
2050+ self._test_partition_is_zero("cmp_prep.out")
2051+
2052
2053 class CentosTestBasicAbs(TestBasicAbs):
2054 def test_centos_release(self):
2055@@ -213,11 +246,14 @@ class CosmicTestBasic(relbase.cosmic, TestBasicAbs):
2056 __test__ = True
2057
2058
2059+class DiscoTestBasic(relbase.disco, TestBasicAbs):
2060+ __test__ = True
2061+
2062+
2063 class TestBasicScsiAbs(TestBasicAbs):
2064 conf_file = "examples/tests/basic_scsi.yaml"
2065 disk_driver = 'scsi-hd'
2066 extra_disks = ['128G', '128G', '4G']
2067- nvme_disks = ['4G']
2068 extra_collect_scripts = [textwrap.dedent("""
2069 cd OUTPUT_COLLECT_D
2070 blkid -o export /dev/sda | cat >blkid_output_sda
2071@@ -230,15 +266,22 @@ class TestBasicScsiAbs(TestBasicAbs):
2072 btrfs inspect-internal dump-super $dev |
2073 awk '/^dev_item.fsid/ {print $2}'
2074 fi | cat >$f
2075+
2076+ # compare via /dev/zero 8MB
2077+ cmp --bytes=8388608 /dev/zero /dev/sdd2; echo "$?" > cmp_prep.out
2078+ # extract partition info
2079+ udevadm info --export --query=property /dev/sdd2 | cat >udev_info.out
2080+
2081+ exit 0
2082 """)]
2083
2084 def test_ptable(self):
2085 self._test_ptable("blkid_output_sda", "dos")
2086
2087 def test_partition_numbers(self):
2088- # sdd should have partitions 1 and 10
2089+ # sdd should have partitions 1, 2, and 10
2090 disk = "sdd"
2091- expected = [disk + s for s in ["", "1", "10"]]
2092+ expected = [disk + s for s in ["", "1", "2", "10"]]
2093 self._test_partition_numbers(disk, expected)
2094
2095 def test_fstab_entries(self):
2096@@ -255,6 +298,12 @@ class TestBasicScsiAbs(TestBasicAbs):
2097 def test_whole_disk_uuid(self):
2098 self._test_whole_disk_uuid("sdc", "btrfs_uuid_sdc")
2099
2100+ def test_partition_is_prep(self):
2101+ self._test_partition_is_prep("udev_info.out")
2102+
2103+ def test_partition_is_zero(self):
2104+ self._test_partition_is_zero("cmp_prep.out")
2105+
2106
2107 class Centos70XenialTestScsiBasic(centos_relbase.centos70_xenial,
2108 TestBasicScsiAbs, CentosTestBasicAbs):
2109@@ -280,4 +329,8 @@ class BionicTestScsiBasic(relbase.bionic, TestBasicScsiAbs):
2110 class CosmicTestScsiBasic(relbase.cosmic, TestBasicScsiAbs):
2111 __test__ = True
2112
2113+
2114+class DiscoTestScsiBasic(relbase.disco, TestBasicScsiAbs):
2115+ __test__ = True
2116+
2117 # vi: ts=4 expandtab syntax=python
2118diff --git a/tests/vmtests/test_bcache_basic.py b/tests/vmtests/test_bcache_basic.py
2119index b4191b6..a62fb17 100644
2120--- a/tests/vmtests/test_bcache_basic.py
2121+++ b/tests/vmtests/test_bcache_basic.py
2122@@ -20,6 +20,8 @@ class TestBcacheBasic(VMBaseClass):
2123 bcache-super-show /dev/vda2 > bcache_super_vda2
2124 ls /sys/fs/bcache > bcache_ls
2125 cat /sys/block/bcache0/bcache/cache_mode > bcache_cache_mode
2126+
2127+ exit 0
2128 """)]
2129
2130 def test_bcache_output_files_exist(self):
2131@@ -69,4 +71,8 @@ class BionicBcacheBasic(relbase.bionic, TestBcacheBasic):
2132 class CosmicBcacheBasic(relbase.cosmic, TestBcacheBasic):
2133 __test__ = True
2134
2135+
2136+class DiscoBcacheBasic(relbase.disco, TestBcacheBasic):
2137+ __test__ = True
2138+
2139 # vi: ts=4 expandtab syntax=python
2140diff --git a/tests/vmtests/test_bcache_bug1718699.py b/tests/vmtests/test_bcache_bug1718699.py
2141index bc0f1e0..5410dc3 100644
2142--- a/tests/vmtests/test_bcache_bug1718699.py
2143+++ b/tests/vmtests/test_bcache_bug1718699.py
2144@@ -22,4 +22,8 @@ class BionicTestBcacheBug1718699(relbase.bionic, TestBcacheBug1718699):
2145 class CosmicTestBcacheBug1718699(relbase.cosmic, TestBcacheBug1718699):
2146 __test__ = True
2147
2148+
2149+class DiscoTestBcacheBug1718699(relbase.disco, TestBcacheBug1718699):
2150+ __test__ = True
2151+
2152 # vi: ts=4 expandtab syntax=python
2153diff --git a/tests/vmtests/test_fs_battery.py b/tests/vmtests/test_fs_battery.py
2154index defdf1a..347b62a 100644
2155--- a/tests/vmtests/test_fs_battery.py
2156+++ b/tests/vmtests/test_fs_battery.py
2157@@ -100,6 +100,8 @@ class TestFsBattery(VMBaseClass):
2158 echo "$part umount: PASS" ||
2159 echo "$part umount: FAIL: $out"
2160 done >> battery-mount-umount
2161+
2162+ exit 0
2163 """)]
2164
2165 def get_fs_entries(self):
2166@@ -243,4 +245,7 @@ class CosmicTestFsBattery(relbase.cosmic, TestFsBattery):
2167 __test__ = True
2168
2169
2170+class DiscoTestFsBattery(relbase.disco, TestFsBattery):
2171+ __test__ = True
2172+
2173 # vi: ts=4 expandtab syntax=python
2174diff --git a/tests/vmtests/test_iscsi.py b/tests/vmtests/test_iscsi.py
2175index a800df5..2707d40 100644
2176--- a/tests/vmtests/test_iscsi.py
2177+++ b/tests/vmtests/test_iscsi.py
2178@@ -23,6 +23,8 @@ class TestBasicIscsiAbs(VMBaseClass):
2179 cp -a /etc/iscsi ./etc_iscsi
2180 bash -c \
2181 'for f in /mnt/iscsi*; do cp $f/testfile testfile${f: -1}; done'
2182+
2183+ exit 0
2184 """)]
2185
2186 def test_fstab_has_netdev_option(self):
2187@@ -76,4 +78,8 @@ class BionicTestIscsiBasic(relbase.bionic, TestBasicIscsiAbs):
2188 class CosmicTestIscsiBasic(relbase.cosmic, TestBasicIscsiAbs):
2189 __test__ = True
2190
2191+
2192+class DiscoTestIscsiBasic(relbase.disco, TestBasicIscsiAbs):
2193+ __test__ = True
2194+
2195 # vi: ts=4 expandtab syntax=python
2196diff --git a/tests/vmtests/test_journald_reporter.py b/tests/vmtests/test_journald_reporter.py
2197index c60a862..80af61e 100644
2198--- a/tests/vmtests/test_journald_reporter.py
2199+++ b/tests/vmtests/test_journald_reporter.py
2200@@ -39,4 +39,8 @@ class BionicTestJournaldReporter(relbase.bionic, TestJournaldReporter):
2201 class CosmicTestJournaldReporter(relbase.cosmic, TestJournaldReporter):
2202 __test__ = True
2203
2204+
2205+class DiscoTestJournaldReporter(relbase.disco, TestJournaldReporter):
2206+ __test__ = True
2207+
2208 # vi: ts=4 expandtab syntax=python
2209diff --git a/tests/vmtests/test_lvm.py b/tests/vmtests/test_lvm.py
2210index 37053fe..fdb5314 100644
2211--- a/tests/vmtests/test_lvm.py
2212+++ b/tests/vmtests/test_lvm.py
2213@@ -17,6 +17,8 @@ class TestLvmAbs(VMBaseClass):
2214 cd OUTPUT_COLLECT_D
2215 pvdisplay -C --separator = -o vg_name,pv_name --noheadings > pvs
2216 lvdisplay -C --separator = -o lv_name,vg_name --noheadings > lvs
2217+
2218+ exit 0
2219 """)]
2220 fstab_expected = {
2221 '/dev/vg1/lv1': '/srv/data',
2222@@ -73,4 +75,7 @@ class CosmicTestLvm(relbase.cosmic, TestLvmAbs):
2223 __test__ = True
2224
2225
2226+class DiscoTestLvm(relbase.disco, TestLvmAbs):
2227+ __test__ = True
2228+
2229 # vi: ts=4 expandtab syntax=python
2230diff --git a/tests/vmtests/test_lvm_iscsi.py b/tests/vmtests/test_lvm_iscsi.py
2231index 091461e..6cdcab2 100644
2232--- a/tests/vmtests/test_lvm_iscsi.py
2233+++ b/tests/vmtests/test_lvm_iscsi.py
2234@@ -17,13 +17,15 @@ class TestLvmIscsiAbs(TestLvmAbs, TestBasicIscsiAbs):
2235 conf_file = "examples/tests/lvm_iscsi.yaml"
2236 nr_testfiles = 4
2237
2238- extra_collect_scripts = TestLvmAbs.extra_collect_scripts
2239- extra_collect_scripts += TestBasicIscsiAbs.extra_collect_scripts
2240- extra_collect_scripts += [textwrap.dedent(
2241- """
2242- cd OUTPUT_COLLECT_D
2243- ls -al /sys/class/block/dm*/slaves/ > dm_slaves
2244- """)]
2245+ extra_collect_scripts = (
2246+ TestLvmAbs.extra_collect_scripts +
2247+ TestBasicIscsiAbs.extra_collect_scripts +
2248+ [textwrap.dedent("""
2249+ cd OUTPUT_COLLECT_D
2250+ ls -al /sys/class/block/dm*/slaves/ > dm_slaves
2251+
2252+ exit 0
2253+ """)])
2254
2255 fstab_expected = {
2256 'UUID=6de56115-9500-424b-8151-221b270ec708': '/mnt/iscsi1',
2257@@ -87,4 +89,8 @@ class BionicTestIscsiLvm(relbase.bionic, TestLvmIscsiAbs):
2258 class CosmicTestIscsiLvm(relbase.cosmic, TestLvmIscsiAbs):
2259 __test__ = True
2260
2261+
2262+class DiscoTestIscsiLvm(relbase.disco, TestLvmIscsiAbs):
2263+ __test__ = True
2264+
2265 # vi: ts=4 expandtab syntax=python
2266diff --git a/tests/vmtests/test_lvm_raid.py b/tests/vmtests/test_lvm_raid.py
2267index 99a33f2..d70f3ef 100644
2268--- a/tests/vmtests/test_lvm_raid.py
2269+++ b/tests/vmtests/test_lvm_raid.py
2270@@ -14,14 +14,18 @@ class TestLvmOverRaidAbs(TestMdadmAbs, TestLvmAbs):
2271 dirty_disks = True
2272 extra_disks = ['10G'] * 4
2273
2274- extra_collect_scripts = TestLvmAbs.extra_collect_scripts
2275- extra_collect_scripts += TestMdadmAbs.extra_collect_scripts
2276- extra_collect_scripts += [textwrap.dedent("""
2277- cd OUTPUT_COLLECT_D
2278- ls -al /dev/md* > dev_md
2279- cp -a /etc/mdadm etc_mdadm
2280- cp -a /etc/lvm etc_lvm
2281- """)]
2282+ extra_collect_scripts = (
2283+ TestLvmAbs.extra_collect_scripts +
2284+ TestMdadmAbs.extra_collect_scripts +
2285+ [textwrap.dedent("""
2286+ cd OUTPUT_COLLECT_D
2287+ ls -al /dev/md* > dev_md
2288+ cp -a /etc/mdadm etc_mdadm
2289+ cp -a /etc/lvm etc_lvm
2290+
2291+ exit 0
2292+ """)]
2293+ )
2294
2295 fstab_expected = {
2296 '/dev/vg1/lv1': '/srv/data',
2297@@ -39,6 +43,10 @@ class TestLvmOverRaidAbs(TestMdadmAbs, TestLvmAbs):
2298 self.check_file_strippedline("pvs", "vg0=/dev/md1")
2299
2300
2301+class DiscoTestLvmOverRaid(relbase.disco, TestLvmOverRaidAbs):
2302+ __test__ = True
2303+
2304+
2305 class CosmicTestLvmOverRaid(relbase.cosmic, TestLvmOverRaidAbs):
2306 __test__ = True
2307
2308diff --git a/tests/vmtests/test_lvm_root.py b/tests/vmtests/test_lvm_root.py
2309index 7e7472d..d726a45 100644
2310--- a/tests/vmtests/test_lvm_root.py
2311+++ b/tests/vmtests/test_lvm_root.py
2312@@ -26,6 +26,8 @@ class TestLvmRootAbs(VMBaseClass):
2313 vgdisplay > vgdisplay
2314 lvdisplay > lvdisplay
2315 ls -al /dev/root_vg/ > dev_root_vg
2316+
2317+ exit 0
2318 """)]
2319 fstab_expected = {
2320 'UUID=04836770-e989-460f-8774-8e277ddcb40f': '/',
2321diff --git a/tests/vmtests/test_mdadm_bcache.py b/tests/vmtests/test_mdadm_bcache.py
2322index adfa55b..f38d4f7 100644
2323--- a/tests/vmtests/test_mdadm_bcache.py
2324+++ b/tests/vmtests/test_mdadm_bcache.py
2325@@ -23,6 +23,8 @@ class TestMdadmAbs(VMBaseClass):
2326 ls -al /sys/fs/bcache/* > lsal_sys_fs_bcache_star
2327 ls -al /dev/bcache* > lsal_dev_bcache_star
2328 ls -al /dev/bcache/by_uuid/* > lsal_dev_bcache_byuuid_star
2329+
2330+ exit 0
2331 """)]
2332
2333 def test_mdadm_output_files_exist(self):
2334@@ -63,6 +65,8 @@ class TestMdadmBcacheAbs(TestMdadmAbs):
2335 cat /sys/block/bcache0/bcache/cache_mode > bcache_cache_mode
2336 cat /sys/block/bcache1/bcache/cache_mode >> bcache_cache_mode
2337 cat /sys/block/bcache2/bcache/cache_mode >> bcache_cache_mode
2338+
2339+ exit 0
2340 """)]
2341 fstab_expected = {
2342 '/dev/vda1': '/media/sda1',
2343@@ -151,6 +155,10 @@ class CosmicTestMdadmBcache(relbase.cosmic, TestMdadmBcacheAbs):
2344 __test__ = True
2345
2346
2347+class DiscoTestMdadmBcache(relbase.disco, TestMdadmBcacheAbs):
2348+ __test__ = True
2349+
2350+
2351 class TestMirrorbootAbs(TestMdadmAbs):
2352 # alternative config for more complex setup
2353 conf_file = "examples/tests/mirrorboot.yaml"
2354@@ -196,6 +204,10 @@ class CosmicTestMirrorboot(relbase.cosmic, TestMirrorbootAbs):
2355 __test__ = True
2356
2357
2358+class DiscoTestMirrorboot(relbase.disco, TestMirrorbootAbs):
2359+ __test__ = True
2360+
2361+
2362 class TestMirrorbootPartitionsAbs(TestMdadmAbs):
2363 # alternative config for more complex setup
2364 conf_file = "examples/tests/mirrorboot-msdos-partition.yaml"
2365@@ -247,6 +259,11 @@ class CosmicTestMirrorbootPartitions(relbase.cosmic,
2366 __test__ = True
2367
2368
2369+class DiscoTestMirrorbootPartitions(relbase.disco,
2370+ TestMirrorbootPartitionsAbs):
2371+ __test__ = True
2372+
2373+
2374 class TestMirrorbootPartitionsUEFIAbs(TestMdadmAbs):
2375 # alternative config for more complex setup
2376 conf_file = "examples/tests/mirrorboot-uefi.yaml"
2377@@ -297,6 +314,11 @@ class CosmicTestMirrorbootPartitionsUEFI(relbase.cosmic,
2378 __test__ = True
2379
2380
2381+class DiscoTestMirrorbootPartitionsUEFI(relbase.disco,
2382+ TestMirrorbootPartitionsUEFIAbs):
2383+ __test__ = True
2384+
2385+
2386 class TestRaid5bootAbs(TestMdadmAbs):
2387 # alternative config for more complex setup
2388 conf_file = "examples/tests/raid5boot.yaml"
2389@@ -342,6 +364,10 @@ class CosmicTestRaid5boot(relbase.cosmic, TestRaid5bootAbs):
2390 __test__ = True
2391
2392
2393+class DiscoTestRaid5boot(relbase.disco, TestRaid5bootAbs):
2394+ __test__ = True
2395+
2396+
2397 class TestRaid6bootAbs(TestMdadmAbs):
2398 # alternative config for more complex setup
2399 conf_file = "examples/tests/raid6boot.yaml"
2400@@ -357,6 +383,8 @@ class TestRaid6bootAbs(TestMdadmAbs):
2401 TestMdadmAbs.extra_collect_scripts + [textwrap.dedent("""
2402 cd OUTPUT_COLLECT_D
2403 mdadm --detail --scan > mdadm_detail
2404+
2405+ exit 0
2406 """)])
2407
2408 def test_raid6_output_files_exist(self):
2409@@ -400,6 +428,10 @@ class CosmicTestRaid6boot(relbase.cosmic, TestRaid6bootAbs):
2410 __test__ = True
2411
2412
2413+class DiscoTestRaid6boot(relbase.disco, TestRaid6bootAbs):
2414+ __test__ = True
2415+
2416+
2417 class TestRaid10bootAbs(TestMdadmAbs):
2418 # alternative config for more complex setup
2419 conf_file = "examples/tests/raid10boot.yaml"
2420@@ -446,6 +478,10 @@ class CosmicTestRaid10boot(relbase.cosmic, TestRaid10bootAbs):
2421 __test__ = True
2422
2423
2424+class DiscoTestRaid10boot(relbase.disco, TestRaid10bootAbs):
2425+ __test__ = True
2426+
2427+
2428 class TestAllindataAbs(TestMdadmAbs):
2429 # more complex, needs more time
2430 # alternative config for more complex setup
2431@@ -493,6 +529,8 @@ class TestAllindataAbs(TestMdadmAbs):
2432 mkdir -p /tmp/xfstest
2433 mount /dev/mapper/dmcrypt0 /tmp/xfstest
2434 xfs_info /tmp/xfstest/ > xfs_info
2435+
2436+ exit 0
2437 """)])
2438 fstab_expected = {
2439 '/dev/vg1/lv1': '/srv/data',
2440@@ -547,4 +585,8 @@ class BionicTestAllindata(relbase.bionic, TestAllindataAbs):
2441 class CosmicTestAllindata(relbase.cosmic, TestAllindataAbs):
2442 __test__ = True
2443
2444+
2445+class DiscoTestAllindata(relbase.disco, TestAllindataAbs):
2446+ __test__ = True
2447+
2448 # vi: ts=4 expandtab syntax=python
2449diff --git a/tests/vmtests/test_mdadm_iscsi.py b/tests/vmtests/test_mdadm_iscsi.py
2450index 537baec..b43855d 100644
2451--- a/tests/vmtests/test_mdadm_iscsi.py
2452+++ b/tests/vmtests/test_mdadm_iscsi.py
2453@@ -18,12 +18,15 @@ class TestMdadmIscsiAbs(TestMdadmAbs, TestBasicIscsiAbs):
2454 conf_file = "examples/tests/mdadm_iscsi.yaml"
2455 nr_testfiles = 1
2456
2457- extra_collect_scripts = TestMdadmAbs.extra_collect_scripts
2458- extra_collect_scripts += TestBasicIscsiAbs.extra_collect_scripts
2459- extra_collect_scripts += [textwrap.dedent("""
2460- cd OUTPUT_COLLECT_D
2461- ls -al /sys/class/block/md*/slaves/ > md_slaves
2462- """)]
2463+ extra_collect_scripts = (
2464+ TestMdadmAbs.extra_collect_scripts +
2465+ TestBasicIscsiAbs.extra_collect_scripts +
2466+ [textwrap.dedent("""
2467+ cd OUTPUT_COLLECT_D
2468+ ls -al /sys/class/block/md*/slaves/ > md_slaves
2469+
2470+ exit 0
2471+ """)])
2472
2473
2474 class Centos70TestIscsiMdadm(centos_relbase.centos70_xenial,
2475@@ -54,4 +57,8 @@ class BionicTestIscsiMdadm(relbase.bionic, TestMdadmIscsiAbs):
2476 class CosmicTestIscsiMdadm(relbase.cosmic, TestMdadmIscsiAbs):
2477 __test__ = True
2478
2479+
2480+class DiscoTestIscsiMdadm(relbase.disco, TestMdadmIscsiAbs):
2481+ __test__ = True
2482+
2483 # vi: ts=4 expandtab syntax=python
2484diff --git a/tests/vmtests/test_multipath.py b/tests/vmtests/test_multipath.py
2485index a6bd8ce..a22bc0f 100644
2486--- a/tests/vmtests/test_multipath.py
2487+++ b/tests/vmtests/test_multipath.py
2488@@ -4,6 +4,8 @@ from . import VMBaseClass
2489 from .releases import base_vm_classes as relbase
2490 from .releases import centos_base_vm_classes as centos_relbase
2491
2492+from unittest import SkipTest
2493+import os
2494 import textwrap
2495
2496
2497@@ -22,6 +24,11 @@ class TestMultipathBasicAbs(VMBaseClass):
2498 cp -a /etc/multipath* .
2499 readlink -f /sys/class/block/sda/holders/dm-0 > holders_sda
2500 readlink -f /sys/class/block/sdb/holders/dm-0 > holders_sdb
2501+ command -v systemctl && {
2502+ systemctl show -- home.mount > systemctl_show_home.mount;
2503+ systemctl status --full home.mount > systemctl_status_home.mount
2504+ }
2505+ exit 0
2506 """)]
2507
2508 def test_multipath_disks_match(self):
2509@@ -31,6 +38,26 @@ class TestMultipathBasicAbs(VMBaseClass):
2510 print('sdb holders:\n%s' % sdb_data)
2511 self.assertEqual(sda_data, sdb_data)
2512
2513+ def test_home_mount_unit(self):
2514+ unit_file = 'systemctl_show_home.mount'
2515+ if not os.path.exists(self.collect_path(unit_file)):
2516+ raise SkipTest(
2517+ 'target_release=%s does not use systemd' % self.target_release)
2518+
2519+ # We can't use load_shell_content as systemctl show output
2520+ # does not quote values even though it's in Key=Value format
2521+ content = self.load_collect_file(unit_file)
2522+ expected_results = {
2523+ 'ActiveState': 'active',
2524+ 'Result': 'success',
2525+ 'SubState': 'mounted',
2526+ }
2527+ show = {key: value for key, value in
2528+ [line.split('=') for line in content.splitlines()
2529+ if line.split('=')[0] in expected_results.keys()]}
2530+
2531+ self.assertEqual(sorted(expected_results), sorted(show))
2532+
2533
2534 class Centos70TestMultipathBasic(centos_relbase.centos70_xenial,
2535 TestMultipathBasicAbs):
2536@@ -65,4 +92,8 @@ class BionicTestMultipathBasic(relbase.bionic, TestMultipathBasicAbs):
2537 class CosmicTestMultipathBasic(relbase.cosmic, TestMultipathBasicAbs):
2538 __test__ = True
2539
2540+
2541+class DiscoTestMultipathBasic(relbase.disco, TestMultipathBasicAbs):
2542+ __test__ = True
2543+
2544 # vi: ts=4 expandtab syntax=python
2545diff --git a/tests/vmtests/test_network.py b/tests/vmtests/test_network.py
2546index ce4cdb9..1b0e41c 100644
2547--- a/tests/vmtests/test_network.py
2548+++ b/tests/vmtests/test_network.py
2549@@ -49,6 +49,8 @@ class TestNetworkBaseTestsAbs(VMBaseClass):
2550 cp -a /etc/systemd ./etc_systemd ||:
2551 journalctl --no-pager -b -x | tee journalctl_out
2552 sleep 10 && ip a | tee ip_a
2553+
2554+ exit 0
2555 """)]
2556
2557 def test_output_files_exist(self):
2558@@ -473,6 +475,10 @@ class CosmicTestNetworkBasic(relbase.cosmic, TestNetworkBasicAbs):
2559 __test__ = True
2560
2561
2562+class DiscoTestNetworkBasic(relbase.disco, TestNetworkBasicAbs):
2563+ __test__ = True
2564+
2565+
2566 class Centos66TestNetworkBasic(centos_relbase.centos66_xenial,
2567 CentosTestNetworkBasicAbs):
2568 __test__ = True
2569diff --git a/tests/vmtests/test_network_alias.py b/tests/vmtests/test_network_alias.py
2570index 1d1837f..b2c4ed7 100644
2571--- a/tests/vmtests/test_network_alias.py
2572+++ b/tests/vmtests/test_network_alias.py
2573@@ -26,6 +26,8 @@ class CentosTestNetworkAliasAbs(TestNetworkAliasAbs):
2574 cp -a /var/log/cloud-init* .
2575 cp -a /var/lib/cloud ./var_lib_cloud
2576 cp -a /run/cloud-init ./run_cloud-init
2577+
2578+ exit 0
2579 """)]
2580
2581 def test_etc_resolvconf(self):
2582@@ -76,4 +78,8 @@ class BionicTestNetworkAlias(relbase.bionic, TestNetworkAliasAbs):
2583 class CosmicTestNetworkAlias(relbase.cosmic, TestNetworkAliasAbs):
2584 __test__ = True
2585
2586+
2587+class DiscoTestNetworkAlias(relbase.disco, TestNetworkAliasAbs):
2588+ __test__ = True
2589+
2590 # vi: ts=4 expandtab syntax=python
2591diff --git a/tests/vmtests/test_network_bonding.py b/tests/vmtests/test_network_bonding.py
2592index 572f240..fe3abe9 100644
2593--- a/tests/vmtests/test_network_bonding.py
2594+++ b/tests/vmtests/test_network_bonding.py
2595@@ -33,6 +33,8 @@ class CentosTestNetworkBondingAbs(TestNetworkBondingAbs):
2596 cp -a /var/lib/cloud ./var_lib_cloud
2597 cp -a /run/cloud-init ./run_cloud-init
2598 rpm -qf `which ifenslave` |tee ifenslave_installed
2599+
2600+ exit 0
2601 """)]
2602
2603 def test_ifenslave_package_status(self):
2604@@ -79,6 +81,10 @@ class CosmicTestBonding(relbase.cosmic, TestNetworkBondingAbs):
2605 __test__ = True
2606
2607
2608+class DiscoTestBonding(relbase.disco, TestNetworkBondingAbs):
2609+ __test__ = True
2610+
2611+
2612 class Centos66TestNetworkBonding(centos_relbase.centos66_xenial,
2613 CentosTestNetworkBondingAbs):
2614 __test__ = True
2615diff --git a/tests/vmtests/test_network_bridging.py b/tests/vmtests/test_network_bridging.py
2616index ed9b728..8576d60 100644
2617--- a/tests/vmtests/test_network_bridging.py
2618+++ b/tests/vmtests/test_network_bridging.py
2619@@ -102,6 +102,8 @@ class TestBridgeNetworkAbs(TestNetworkBaseTestsAbs):
2620 grep -r . /sys/class/net/br0 > sysfs_br0
2621 grep -r . /sys/class/net/br0/brif/eth1 > sysfs_br0_eth1
2622 grep -r . /sys/class/net/br0/brif/eth2 > sysfs_br0_eth2
2623+
2624+ exit 0
2625 """)]
2626
2627 def test_output_files_exist_bridge(self):
2628@@ -200,6 +202,8 @@ class CentosTestBridgeNetworkAbs(TestBridgeNetworkAbs):
2629 cp -a /var/lib/cloud ./var_lib_cloud
2630 cp -a /run/cloud-init ./run_cloud-init
2631 rpm -qf `which brctl` |tee bridge-utils_installed
2632+
2633+ exit 0
2634 """)]
2635
2636 def test_etc_network_interfaces(self):
2637@@ -236,4 +240,7 @@ class CosmicTestBridging(relbase.cosmic, TestBridgeNetworkAbs):
2638 __test__ = True
2639
2640
2641+class DiscoTestBridging(relbase.disco, TestBridgeNetworkAbs):
2642+ __test__ = True
2643+
2644 # vi: ts=4 expandtab syntax=python
2645diff --git a/tests/vmtests/test_network_ipv6.py b/tests/vmtests/test_network_ipv6.py
2646index b8a69e8..42adb20 100644
2647--- a/tests/vmtests/test_network_ipv6.py
2648+++ b/tests/vmtests/test_network_ipv6.py
2649@@ -21,6 +21,8 @@ class TestNetworkIPV6Abs(TestNetworkBaseTestsAbs):
2650 grep . -r /sys/class/net/bond0/ > sysfs_bond0 || :
2651 grep . -r /sys/class/net/bond0.108/ > sysfs_bond0.108 || :
2652 grep . -r /sys/class/net/bond0.208/ > sysfs_bond0.208 || :
2653+
2654+ exit 0
2655 """)]
2656
2657
2658@@ -32,6 +34,8 @@ class CentosTestNetworkIPV6Abs(TestNetworkIPV6Abs):
2659 cp -a /var/log/cloud-init* .
2660 cp -a /var/lib/cloud ./var_lib_cloud
2661 cp -a /run/cloud-init ./run_cloud-init
2662+
2663+ exit 0
2664 """)]
2665
2666 def test_etc_network_interfaces(self):
2667@@ -72,6 +76,10 @@ class CosmicTestNetworkIPV6(relbase.cosmic, TestNetworkIPV6Abs):
2668 __test__ = True
2669
2670
2671+class DiscoTestNetworkIPV6(relbase.disco, TestNetworkIPV6Abs):
2672+ __test__ = True
2673+
2674+
2675 class Centos66TestNetworkIPV6(centos_relbase.centos66_xenial,
2676 CentosTestNetworkIPV6Abs):
2677 __test__ = True
2678diff --git a/tests/vmtests/test_network_ipv6_static.py b/tests/vmtests/test_network_ipv6_static.py
2679index 7ebc5eb..bbd90c9 100644
2680--- a/tests/vmtests/test_network_ipv6_static.py
2681+++ b/tests/vmtests/test_network_ipv6_static.py
2682@@ -54,6 +54,10 @@ class CosmicTestNetworkIPV6Static(relbase.cosmic, TestNetworkIPV6StaticAbs):
2683 __test__ = True
2684
2685
2686+class DiscoTestNetworkIPV6Static(relbase.disco, TestNetworkIPV6StaticAbs):
2687+ __test__ = True
2688+
2689+
2690 class Centos66TestNetworkIPV6Static(centos_relbase.centos66_xenial,
2691 CentosTestNetworkIPV6StaticAbs):
2692 __test__ = True
2693diff --git a/tests/vmtests/test_network_ipv6_vlan.py b/tests/vmtests/test_network_ipv6_vlan.py
2694index 2f7bd49..7401d2c 100644
2695--- a/tests/vmtests/test_network_ipv6_vlan.py
2696+++ b/tests/vmtests/test_network_ipv6_vlan.py
2697@@ -35,6 +35,10 @@ class CosmicTestNetworkIPV6Vlan(relbase.cosmic, TestNetworkIPV6VlanAbs):
2698 __test__ = True
2699
2700
2701+class DiscoTestNetworkIPV6Vlan(relbase.disco, TestNetworkIPV6VlanAbs):
2702+ __test__ = True
2703+
2704+
2705 class Centos66TestNetworkIPV6Vlan(centos_relbase.centos66_xenial,
2706 CentosTestNetworkIPV6VlanAbs):
2707 __test__ = True
2708diff --git a/tests/vmtests/test_network_mtu.py b/tests/vmtests/test_network_mtu.py
2709index eaa383b..ffde5c7 100644
2710--- a/tests/vmtests/test_network_mtu.py
2711+++ b/tests/vmtests/test_network_mtu.py
2712@@ -36,6 +36,8 @@ class TestNetworkMtuAbs(TestNetworkIPV6Abs):
2713 if [ -e /var/log/upstart ]; then
2714 cp -a /var/log/upstart ./var_log_upstart
2715 fi
2716+
2717+ exit 0
2718 """)]
2719
2720 def _load_mtu_data(self, ifname):
2721@@ -128,6 +130,8 @@ class CentosTestNetworkMtuAbs(TestNetworkMtuAbs):
2722 cp -a /var/log/cloud-init* .
2723 cp -a /var/lib/cloud ./var_lib_cloud
2724 cp -a /run/cloud-init ./run_cloud-init
2725+
2726+ exit 0
2727 """)]
2728
2729 def test_etc_network_interfaces(self):
2730@@ -200,6 +204,11 @@ class CosmicTestNetworkMtu(relbase.cosmic, TestNetworkMtuAbs):
2731 __test__ = True
2732
2733
2734+@TestNetworkMtuAbs.skip_by_date("1671951", fixby="2019-01-02")
2735+class DiscoTestNetworkMtu(relbase.disco, TestNetworkMtuAbs):
2736+ __test__ = True
2737+
2738+
2739 class Centos66TestNetworkMtu(centos_relbase.centos66_xenial,
2740 CentosTestNetworkMtuAbs):
2741 __test__ = True
2742diff --git a/tests/vmtests/test_network_static.py b/tests/vmtests/test_network_static.py
2743index 9e042a5..6820c33 100644
2744--- a/tests/vmtests/test_network_static.py
2745+++ b/tests/vmtests/test_network_static.py
2746@@ -59,6 +59,10 @@ class CosmicTestNetworkStatic(relbase.cosmic, TestNetworkStaticAbs):
2747 __test__ = True
2748
2749
2750+class DiscoTestNetworkStatic(relbase.disco, TestNetworkStaticAbs):
2751+ __test__ = True
2752+
2753+
2754 class Centos66TestNetworkStatic(centos_relbase.centos66_xenial,
2755 CentosTestNetworkStaticAbs):
2756 __test__ = True
2757diff --git a/tests/vmtests/test_network_static_routes.py b/tests/vmtests/test_network_static_routes.py
2758index ad18eef..405a730 100644
2759--- a/tests/vmtests/test_network_static_routes.py
2760+++ b/tests/vmtests/test_network_static_routes.py
2761@@ -59,6 +59,11 @@ class CosmicTestNetworkStaticRoutes(relbase.cosmic,
2762 __test__ = True
2763
2764
2765+class DiscoTestNetworkStaticRoutes(relbase.disco,
2766+ TestNetworkStaticRoutesAbs):
2767+ __test__ = True
2768+
2769+
2770 class Centos66TestNetworkStaticRoutes(centos_relbase.centos66_xenial,
2771 CentosTestNetworkStaticRoutesAbs):
2772 __test__ = False
2773diff --git a/tests/vmtests/test_network_vlan.py b/tests/vmtests/test_network_vlan.py
2774index 9e55bfa..904f8c2 100644
2775--- a/tests/vmtests/test_network_vlan.py
2776+++ b/tests/vmtests/test_network_vlan.py
2777@@ -18,6 +18,8 @@ class TestNetworkVlanAbs(TestNetworkBaseTestsAbs):
2778 ip -d link show interface1.2668 |tee ip_link_show_interface1.2668
2779 ip -d link show interface1.2669 |tee ip_link_show_interface1.2669
2780 ip -d link show interface1.2670 |tee ip_link_show_interface1.2670
2781+
2782+ exit 0
2783 """)]
2784
2785 def get_vlans(self):
2786@@ -86,6 +88,10 @@ class CosmicTestNetworkVlan(relbase.cosmic, TestNetworkVlanAbs):
2787 __test__ = True
2788
2789
2790+class DiscoTestNetworkVlan(relbase.disco, TestNetworkVlanAbs):
2791+ __test__ = True
2792+
2793+
2794 class Centos66TestNetworkVlan(centos_relbase.centos66_xenial,
2795 CentosTestNetworkVlanAbs):
2796 __test__ = True
2797diff --git a/tests/vmtests/test_nvme.py b/tests/vmtests/test_nvme.py
2798index f3d1ae2..60d9d86 100644
2799--- a/tests/vmtests/test_nvme.py
2800+++ b/tests/vmtests/test_nvme.py
2801@@ -19,14 +19,17 @@ class TestNvmeAbs(VMBaseClass):
2802 conf_file = "examples/tests/nvme.yaml"
2803 extra_disks = []
2804 nvme_disks = ['4G', '4G']
2805- disk_to_check = [('main_disk', 1), ('main_disk', 2), ('main_disk', 15),
2806- ('nvme_disk', 1), ('nvme_disk', 2), ('nvme_disk', 3),
2807- ('second_nvme', 1)]
2808+ disk_to_check = [
2809+ ('main_disk', 1), ('main_disk', 2), ('main_disk', 15),
2810+ ('nvme_disk', 0), ('nvme_disk', 1), ('nvme_disk', 2), ('nvme_disk', 3),
2811+ ('second_nvme', 0), ('second_nvme', 1)]
2812 extra_collect_scripts = [textwrap.dedent("""
2813 cd OUTPUT_COLLECT_D
2814 ls /sys/class/ > sys_class
2815 ls /sys/class/nvme/ > ls_nvme
2816 ls /dev/nvme* > ls_dev_nvme
2817+
2818+ exit 0
2819 """)]
2820
2821 def _test_nvme_device_names(self, expected):
2822@@ -82,6 +85,10 @@ class CosmicTestNvme(relbase.cosmic, TestNvmeAbs):
2823 __test__ = True
2824
2825
2826+class DiscoTestNvme(relbase.cosmic, TestNvmeAbs):
2827+ __test__ = True
2828+
2829+
2830 class TestNvmeBcacheAbs(TestNvmeAbs):
2831 arch_skip = [
2832 "s390x", # nvme is a pci device, no pci on s390x
2833@@ -91,7 +98,8 @@ class TestNvmeBcacheAbs(TestNvmeAbs):
2834 extra_disks = ['10G']
2835 nvme_disks = ['6G']
2836 uefi = True
2837- disk_to_check = [('sda', 1), ('sda', 2), ('sda', 3)]
2838+ disk_to_check = [('sda', 1), ('sda', 2), ('sda', 3),
2839+ ('sdb', 0), ('nvme0n1', 0)]
2840
2841 extra_collect_scripts = [textwrap.dedent("""
2842 cd OUTPUT_COLLECT_D
2843@@ -102,6 +110,8 @@ class TestNvmeBcacheAbs(TestNvmeAbs):
2844 bcache-super-show /dev/nvme0n1p1 > bcache_super_nvme0n1p1
2845 ls /sys/fs/bcache > bcache_ls
2846 cat /sys/block/bcache0/bcache/cache_mode > bcache_cache_mode
2847+
2848+ exit 0
2849 """)]
2850
2851 def test_bcache_output_files_exist(self):
2852@@ -145,4 +155,7 @@ class CosmicTestNvmeBcache(relbase.cosmic, TestNvmeBcacheAbs):
2853 __test__ = True
2854
2855
2856+class DiscoTestNvmeBcache(relbase.disco, TestNvmeBcacheAbs):
2857+ __test__ = True
2858+
2859 # vi: ts=4 expandtab syntax=python
2860diff --git a/tests/vmtests/test_old_apt_features.py b/tests/vmtests/test_old_apt_features.py
2861index 33561a3..ec3765c 100644
2862--- a/tests/vmtests/test_old_apt_features.py
2863+++ b/tests/vmtests/test_old_apt_features.py
2864@@ -5,6 +5,7 @@
2865 """
2866 import re
2867 import textwrap
2868+import yaml
2869
2870 from . import VMBaseClass
2871 from .releases import base_vm_classes as relbase
2872@@ -50,6 +51,8 @@ class TestOldAptAbs(VMBaseClass):
2873 cp /etc/apt/sources.list .
2874 cp /etc/cloud/cloud.cfg.d/curtin-preserve-sources.cfg .
2875 cp /etc/cloud/cloud.cfg.d/90_dpkg.cfg .
2876+
2877+ exit 0
2878 """)]
2879 arch = util.get_architecture()
2880 if arch in ['amd64', 'i386']:
2881@@ -69,8 +72,10 @@ class TestOldAptAbs(VMBaseClass):
2882
2883 def test_preserve_source(self):
2884 """test_preserve_source - no clobbering sources.list by cloud-init"""
2885- self.check_file_regex("curtin-preserve-sources.cfg",
2886- "apt_preserve_sources_list.*true")
2887+ # For earlier than xenial 'apt_preserve_sources_list' is expected
2888+ self.assertEqual(
2889+ {'apt': {'preserve_sources_list': True}},
2890+ yaml.load(self.load_collect_file("curtin-preserve-sources.cfg")))
2891
2892 def test_debconf(self):
2893 """test_debconf - Check if debconf is in place"""
2894diff --git a/tests/vmtests/test_pollinate_useragent.py b/tests/vmtests/test_pollinate_useragent.py
2895index 13dd28c..c1c6c36 100644
2896--- a/tests/vmtests/test_pollinate_useragent.py
2897+++ b/tests/vmtests/test_pollinate_useragent.py
2898@@ -18,6 +18,8 @@ class TestPollinateUserAgent(VMBaseClass):
2899 cd OUTPUT_COLLECT_D
2900 cp -a /etc/pollinate etc_pollinate
2901 pollinate --print-user-agent > pollinate_print_user_agent
2902+
2903+ exit 0
2904 """)]
2905
2906 def test_pollinate_user_agent(self):
2907@@ -66,4 +68,8 @@ class BionicTestPollinateUserAgent(relbase.bionic, TestPollinateUserAgent):
2908 class CosmicTestPollinateUserAgent(relbase.cosmic, TestPollinateUserAgent):
2909 __test__ = True
2910
2911+
2912+class DiscoTestPollinateUserAgent(relbase.disco, TestPollinateUserAgent):
2913+ __test__ = True
2914+
2915 # vi: ts=4 expandtab syntax=python
2916diff --git a/tests/vmtests/test_raid5_bcache.py b/tests/vmtests/test_raid5_bcache.py
2917index 8d24f8e..76d0eed 100644
2918--- a/tests/vmtests/test_raid5_bcache.py
2919+++ b/tests/vmtests/test_raid5_bcache.py
2920@@ -16,6 +16,8 @@ class TestMdadmAbs(VMBaseClass):
2921 mdadm --detail --scan > mdadm_status
2922 mdadm --detail --scan | grep -c ubuntu > mdadm_active1
2923 grep -c active /proc/mdstat > mdadm_active2
2924+
2925+ exit 0
2926 """)]
2927
2928 def test_mdadm_output_files_exist(self):
2929@@ -33,13 +35,15 @@ class TestMdadmBcacheAbs(TestMdadmAbs):
2930 conf_file = "examples/tests/raid5bcache.yaml"
2931 disk_to_check = [('md0', 0), ('sda', 2)]
2932
2933- extra_collect_scripts = TestMdadmAbs.extra_collect_scripts
2934- extra_collect_scripts += [textwrap.dedent("""
2935- cd OUTPUT_COLLECT_D
2936- bcache-super-show /dev/vda2 > bcache_super_vda2
2937- ls /sys/fs/bcache > bcache_ls
2938- cat /sys/block/bcache0/bcache/cache_mode > bcache_cache_mode
2939- """)]
2940+ extra_collect_scripts = (
2941+ TestMdadmAbs.extra_collect_scripts +
2942+ [textwrap.dedent("""\
2943+ cd OUTPUT_COLLECT_D
2944+ bcache-super-show /dev/vda2 > bcache_super_vda2
2945+ ls /sys/fs/bcache > bcache_ls
2946+ cat /sys/block/bcache0/bcache/cache_mode > bcache_cache_mode
2947+
2948+ exit 0""")])
2949 fstab_expected = {
2950 '/dev/bcache0': '/',
2951 '/dev/md0': '/srv/data',
2952@@ -101,4 +105,8 @@ class BionicTestRaid5Bcache(relbase.bionic, TestMdadmBcacheAbs):
2953 class CosmicTestRaid5Bcache(relbase.cosmic, TestMdadmBcacheAbs):
2954 __test__ = True
2955
2956+
2957+class DiscoTestRaid5Bcache(relbase.disco, TestMdadmBcacheAbs):
2958+ __test__ = True
2959+
2960 # vi: ts=4 expandtab syntax=python
2961diff --git a/tests/vmtests/test_simple.py b/tests/vmtests/test_simple.py
2962index 8948f72..625f841 100644
2963--- a/tests/vmtests/test_simple.py
2964+++ b/tests/vmtests/test_simple.py
2965@@ -8,13 +8,15 @@ import textwrap
2966
2967
2968 class TestSimple(VMBaseClass):
2969- # Test that curtin with no config does the right thing
2970+ """ Test that curtin runs block-meta simple mode correctly. """
2971 conf_file = "examples/tests/simple.yaml"
2972 extra_disks = []
2973 extra_nics = []
2974 extra_collect_scripts = [textwrap.dedent("""
2975 cd OUTPUT_COLLECT_D
2976 cp /etc/netplan/50-cloud-init.yaml netplan.yaml
2977+
2978+ exit 0
2979 """)]
2980
2981
2982@@ -43,4 +45,68 @@ class CosmicTestSimple(relbase.cosmic, TestSimple):
2983 def test_output_files_exist(self):
2984 self.output_files_exist(["netplan.yaml"])
2985
2986+
2987+class DiscoTestSimple(relbase.disco, TestSimple):
2988+ __test__ = True
2989+
2990+ def test_output_files_exist(self):
2991+ self.output_files_exist(["netplan.yaml"])
2992+
2993+
2994+class TestSimpleStorage(VMBaseClass):
2995+ """ Test curtin runs clear-holders when mode=simple with storage cfg. """
2996+ conf_file = "examples/tests/simple-storage.yaml"
2997+ dirty_disks = True
2998+ extra_disks = ['5G', '5G']
2999+ extra_nics = []
3000+ extra_collect_scripts = [textwrap.dedent("""
3001+ cd OUTPUT_COLLECT_D
3002+ sfdisk --list > sfdisk_list
3003+ for d in /dev/[sv]d[a-z] /dev/xvd?; do
3004+ [ -b "$d" ] || continue
3005+ echo == $d ==
3006+ sgdisk --print $d
3007+ done > sgdisk_list
3008+ blkid > blkid
3009+ cat /proc/partitions > proc_partitions
3010+ cp /etc/network/interfaces interfaces
3011+ cp /etc/netplan/50-cloud-init.yaml netplan.yaml
3012+ if [ -f /var/log/cloud-init-output.log ]; then
3013+ cp /var/log/cloud-init-output.log .
3014+ fi
3015+ cp /var/log/cloud-init.log .
3016+ find /etc/network/interfaces.d > find_interfacesd
3017+ exit 0
3018+ """)]
3019+
3020+ def test_output_files_exist(self):
3021+ self.output_files_exist(["sfdisk_list", "blkid",
3022+ "proc_partitions"])
3023+
3024+
3025+class XenialGATestSimpleStorage(relbase.xenial, TestSimpleStorage):
3026+ __test__ = True
3027+
3028+
3029+class BionicTestSimpleStorage(relbase.bionic, TestSimpleStorage):
3030+ __test__ = True
3031+
3032+ def test_output_files_exist(self):
3033+ self.output_files_exist(["netplan.yaml"])
3034+
3035+
3036+class CosmicTestSimpleStorage(relbase.cosmic, TestSimpleStorage):
3037+ __test__ = True
3038+
3039+ def test_output_files_exist(self):
3040+ self.output_files_exist(["netplan.yaml"])
3041+
3042+
3043+class DiscoTestSimpleStorage(relbase.disco, TestSimpleStorage):
3044+ __test__ = True
3045+
3046+ def test_output_files_exist(self):
3047+ self.output_files_exist(["netplan.yaml"])
3048+
3049+
3050 # vi: ts=4 expandtab syntax=python
3051diff --git a/tests/vmtests/test_ubuntu_core.py b/tests/vmtests/test_ubuntu_core.py
3052index 732399b..a282940 100644
3053--- a/tests/vmtests/test_ubuntu_core.py
3054+++ b/tests/vmtests/test_ubuntu_core.py
3055@@ -18,6 +18,8 @@ class TestUbuntuCoreAbs(VMBaseClass):
3056 cp -a /etc/cloud ./etc_cloud |:
3057 cp -a /home . |:
3058 cp -a /var/lib/extrausers . |:
3059+
3060+ exit 0
3061 """)]
3062
3063 def test_ubuntu_core_snaps_installed(self):
3064diff --git a/tests/vmtests/test_uefi_basic.py b/tests/vmtests/test_uefi_basic.py
3065index 40ece65..f0e48c6 100644
3066--- a/tests/vmtests/test_uefi_basic.py
3067+++ b/tests/vmtests/test_uefi_basic.py
3068@@ -24,6 +24,8 @@ class TestBasicAbs(VMBaseClass):
3069 blockdev --getss /dev/vda | cat >vda_blockdev_getss
3070 blockdev --getpbsz /dev/vda | cat >vda_blockdev_getpbsz
3071 blockdev --getbsz /dev/vda | cat >vda_blockdev_getbsz
3072+
3073+ exit 0
3074 """)]
3075
3076 def test_sys_firmware_efi(self):
3077@@ -91,7 +93,7 @@ class TrustyUefiTestBasic(relbase.trusty, TestBasicAbs):
3078 __test__ = True
3079
3080
3081-class TrustyHWEXUefiTestBasic(relbase.trusty_hwe_x, TrustyUefiTestBasic):
3082+class TrustyHWEXUefiTestBasic(relbase.trusty_hwe_x, TestBasicAbs):
3083 __test__ = True
3084
3085
3086@@ -115,27 +117,35 @@ class CosmicUefiTestBasic(relbase.cosmic, TestBasicAbs):
3087 __test__ = True
3088
3089
3090+class DiscoUefiTestBasic(relbase.disco, TestBasicAbs):
3091+ __test__ = True
3092+
3093+
3094 class Centos70UefiTestBasic4k(centos_relbase.centos70_xenial, TestBasicAbs):
3095 disk_block_size = 4096
3096
3097
3098-class TrustyUefiTestBasic4k(TrustyUefiTestBasic):
3099+class TrustyUefiTestBasic4k(relbase.trusty, TestBasicAbs):
3100 disk_block_size = 4096
3101
3102
3103-class TrustyHWEXUefiTestBasic4k(relbase.trusty_hwe_x, TrustyUefiTestBasic4k):
3104- __test__ = True
3105+class TrustyHWEXUefiTestBasic4k(relbase.trusty_hwe_x, TestBasicAbs):
3106+ disk_block_size = 4096
3107+
3108+
3109+class XenialGAUefiTestBasic4k(relbase.xenial_ga, TestBasicAbs):
3110+ disk_block_size = 4096
3111
3112
3113-class XenialGAUefiTestBasic4k(XenialGAUefiTestBasic):
3114+class BionicUefiTestBasic4k(relbase.bionic, TestBasicAbs):
3115 disk_block_size = 4096
3116
3117
3118-class BionicUefiTestBasic4k(BionicUefiTestBasic):
3119+class CosmicUefiTestBasic4k(relbase.cosmic, TestBasicAbs):
3120 disk_block_size = 4096
3121
3122
3123-class CosmicUefiTestBasic4k(CosmicUefiTestBasic):
3124+class DiscoUefiTestBasic4k(relbase.disco, TestBasicAbs):
3125 disk_block_size = 4096
3126
3127 # vi: ts=4 expandtab syntax=python
3128diff --git a/tests/vmtests/test_zfsroot.py b/tests/vmtests/test_zfsroot.py
3129index 4e257ae..473c9e3 100644
3130--- a/tests/vmtests/test_zfsroot.py
3131+++ b/tests/vmtests/test_zfsroot.py
3132@@ -17,6 +17,8 @@ class TestZfsRootAbs(VMBaseClass):
3133 zfs list > zfs_list
3134 zpool list > zpool_list
3135 zpool status > zpool_status
3136+
3137+ exit 0
3138 """)]
3139
3140 @skip_if_flag('expected_failure')
3141@@ -88,6 +90,10 @@ class CosmicTestZfsRoot(relbase.cosmic, TestZfsRootAbs):
3142 __test__ = True
3143
3144
3145+class DiscoTestZfsRoot(relbase.disco, TestZfsRootAbs):
3146+ __test__ = True
3147+
3148+
3149 class TestZfsRootFsTypeAbs(TestZfsRootAbs):
3150 conf_file = "examples/tests/basic-zfsroot.yaml"
3151
3152@@ -108,3 +114,7 @@ class BionicTestZfsRootFsType(relbase.bionic, TestZfsRootFsTypeAbs):
3153
3154 class CosmicTestZfsRootFsType(relbase.cosmic, TestZfsRootFsTypeAbs):
3155 __test__ = True
3156+
3157+
3158+class DiscoTestZfsRootFsType(relbase.disco, TestZfsRootFsTypeAbs):
3159+ __test__ = True
3160diff --git a/tools/jenkins-runner b/tools/jenkins-runner
3161index c1cef8e..bf8ea0a 100755
3162--- a/tools/jenkins-runner
3163+++ b/tools/jenkins-runner
3164@@ -79,14 +79,14 @@ if [ "${#tests[@]}" -ne 0 -a "${#ntfilters[@]}" -ne 0 ]; then
3165 error "Passing test tests and --filter are incompatible."
3166 error "test arguments provided were: ${#tests[*]}"
3167 fail
3168-elif [ "${#tests[@]}" -eq 0 ]; then
3169+elif [ "${#tests[@]}" -eq 0 -a "${#ntfilters[@]}" -eq 0 ]; then
3170 tests=( tests/vmtests )
3171 elif [ "${#ntfilters[@]}" -ne 0 ]; then
3172- tests=( $(./tools/vmtest-filter "${ntfilters[@]}") )
3173- if [ "${#tests[@]}" -eq 0 ]; then
3174- error "Failed to find any tests with filter(s): \"${ntfilters[*]}\""
3175- fail "Try testing filters with: ./tools/vmtest-filter ${ntfilters[*]}"
3176- fi
3177+ tests=( $(./tools/vmtest-filter "${ntfilters[@]}") )
3178+ if [ "${#tests[@]}" -eq 0 ]; then
3179+ error "Failed to find any tests with filter(s): \"${ntfilters[*]}\""
3180+ fail "Try testing filters with: ./tools/vmtest-filter ${ntfilters[*]}"
3181+ fi
3182 fi
3183
3184 CURTIN_VMTEST_PARALLEL=$parallel

Subscribers

People subscribed via source and target branches