Merge ~paride/curtin:release/focal/20.2 into curtin:ubuntu/focal

Proposed by Paride Legovini
Status: Merged
Approved by: Chad Smith
Approved revision: f400f6636fa186debfd5544968a19afae8446a0c
Merged at revision: f400f6636fa186debfd5544968a19afae8446a0c
Proposed branch: ~paride/curtin:release/focal/20.2
Merge into: curtin:ubuntu/focal
Diff against target: 4247 lines (+1375/-933)
86 files modified
Makefile (+1/-1)
curtin/__init__.py (+5/-1)
curtin/block/__init__.py (+17/-2)
curtin/block/dasd.py (+2/-2)
curtin/block/multipath.py (+1/-1)
curtin/commands/block_meta.py (+8/-3)
curtin/commands/curthooks.py (+130/-16)
curtin/commands/install_grub.py (+1/-1)
curtin/commands/swap.py (+4/-1)
curtin/distro.py (+23/-7)
curtin/net/deps.py (+4/-1)
curtin/swap.py (+79/-5)
debian/changelog (+40/-0)
dev/null (+0/-7)
doc/topics/config.rst (+58/-1)
examples/tests/basic.yaml (+4/-0)
examples/tests/basic_scsi.yaml (+4/-0)
examples/tests/network_v2_ovs.yaml (+3/-32)
examples/tests/nvme_bcache.yaml (+2/-4)
examples/tests/preserve-partition-wipe-vg.yaml (+0/-1)
examples/tests/raid5boot.yaml (+1/-1)
helpers/common (+0/-578)
pylintrc (+1/-1)
requirements.txt (+3/-0)
tests/data/multipath-nvme.txt (+1/-0)
tests/unittests/helpers.py (+18/-0)
tests/unittests/test_block.py (+12/-0)
tests/unittests/test_block_multipath.py (+14/-0)
tests/unittests/test_commands_block_meta.py (+145/-5)
tests/unittests/test_curthooks.py (+340/-52)
tests/unittests/test_distro.py (+27/-6)
tests/unittests/test_feature.py (+6/-0)
tests/vmtests/__init__.py (+97/-21)
tests/vmtests/releases.py (+9/-0)
tests/vmtests/test_apt_config_cmd.py (+5/-1)
tests/vmtests/test_basic.py (+23/-9)
tests/vmtests/test_basic_dasd.py (+2/-2)
tests/vmtests/test_bcache_basic.py (+2/-2)
tests/vmtests/test_bcache_bug1718699.py (+2/-2)
tests/vmtests/test_bcache_ceph.py (+6/-2)
tests/vmtests/test_bcache_partitions.py (+2/-2)
tests/vmtests/test_fs_battery.py (+2/-2)
tests/vmtests/test_iscsi.py (+2/-2)
tests/vmtests/test_journald_reporter.py (+2/-2)
tests/vmtests/test_lvm.py (+2/-2)
tests/vmtests/test_lvm_iscsi.py (+2/-2)
tests/vmtests/test_lvm_raid.py (+4/-4)
tests/vmtests/test_lvm_root.py (+17/-4)
tests/vmtests/test_mdadm_bcache.py (+39/-30)
tests/vmtests/test_mdadm_iscsi.py (+2/-2)
tests/vmtests/test_multipath.py (+2/-2)
tests/vmtests/test_multipath_lvm.py (+7/-2)
tests/vmtests/test_network.py (+2/-2)
tests/vmtests/test_network_alias.py (+2/-2)
tests/vmtests/test_network_bonding.py (+2/-2)
tests/vmtests/test_network_bridging.py (+2/-2)
tests/vmtests/test_network_disabled.py (+17/-0)
tests/vmtests/test_network_ipv6.py (+5/-1)
tests/vmtests/test_network_ipv6_static.py (+2/-2)
tests/vmtests/test_network_ipv6_vlan.py (+6/-2)
tests/vmtests/test_network_mtu.py (+7/-15)
tests/vmtests/test_network_ovs.py (+2/-3)
tests/vmtests/test_network_static.py (+2/-2)
tests/vmtests/test_network_static_routes.py (+4/-4)
tests/vmtests/test_network_vlan.py (+3/-2)
tests/vmtests/test_nvme.py (+3/-4)
tests/vmtests/test_panic.py (+5/-0)
tests/vmtests/test_pollinate_useragent.py (+2/-2)
tests/vmtests/test_preserve.py (+2/-2)
tests/vmtests/test_preserve_bcache.py (+2/-2)
tests/vmtests/test_preserve_lvm.py (+2/-2)
tests/vmtests/test_preserve_partition_wipe_vg.py (+5/-4)
tests/vmtests/test_preserve_raid.py (+2/-2)
tests/vmtests/test_raid5_bcache.py (+5/-5)
tests/vmtests/test_reuse_lvm_member.py (+4/-4)
tests/vmtests/test_reuse_msdos_partitions.py (+4/-4)
tests/vmtests/test_reuse_raid_member.py (+6/-6)
tests/vmtests/test_reuse_uefi_esp.py (+10/-8)
tests/vmtests/test_simple.py (+11/-4)
tests/vmtests/test_uefi_basic.py (+9/-4)
tests/vmtests/test_zfsroot.py (+4/-5)
tools/curtainer (+43/-7)
tools/jenkins-runner (+14/-2)
tools/run-pep8 (+1/-1)
tools/vmtest-remove-release (+1/-1)
tox.ini (+1/-1)
Reviewer Review Type Date Requested Status
Chad Smith Approve
Server Team CI bot continuous-integration Approve
Review via email: mp+391530@code.launchpad.net

Commit message

Release curtin version 20.2-0ubuntu1~20.04.1

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Chad Smith (chad.smith) wrote :

+1. same delta once noop commit summaries redacted from debian/changelog.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/Makefile b/Makefile
2index 68a3ad3..187132c 100644
3--- a/Makefile
4+++ b/Makefile
5@@ -9,7 +9,7 @@ ifeq ($(COVERAGE), 1)
6 endif
7 CURTIN_VMTEST_IMAGE_SYNC ?= False
8 export CURTIN_VMTEST_IMAGE_SYNC
9-noseopts ?= -vv --nologcapture
10+noseopts ?= -vv
11 pylintopts ?= --rcfile=pylintrc --errors-only
12 target_dirs ?= curtin tests tools
13
14diff --git a/curtin/__init__.py b/curtin/__init__.py
15index 2e1a0ed..82b6f26 100644
16--- a/curtin/__init__.py
17+++ b/curtin/__init__.py
18@@ -8,6 +8,8 @@ KERNEL_CMDLINE_COPY_TO_INSTALL_SEP = "---"
19 # can determine which features are supported. Each entry should have
20 # a consistent meaning.
21 FEATURES = [
22+ # curtin supports creating swapfiles on btrfs, if possible
23+ 'BTRFS_SWAPFILE',
24 # curtin can apply centos networking via centos_apply_network_config
25 'CENTOS_APPLY_NETWORK_CONFIG',
26 # curtin can configure centos storage devices and boot devices
27@@ -32,8 +34,10 @@ FEATURES = [
28 'APT_CONFIG_V1',
29 # has version module
30 'HAS_VERSION_MODULE',
31+ # uefi_reoder has fallback support if BootCurrent is missing
32+ 'UEFI_REORDER_FALLBACK_SUPPORT',
33 ]
34
35-__version__ = "20.1"
36+__version__ = "20.2"
37
38 # vi: ts=4 expandtab syntax=python
39diff --git a/curtin/block/__init__.py b/curtin/block/__init__.py
40index 35e3a64..0cf0866 100644
41--- a/curtin/block/__init__.py
42+++ b/curtin/block/__init__.py
43@@ -1,5 +1,5 @@
44 # This file is part of curtin. See LICENSE file for copyright and license info.
45-
46+import re
47 from contextlib import contextmanager
48 import errno
49 import itertools
50@@ -67,6 +67,19 @@ def dev_path(devname):
51 return '/dev/' + devname
52
53
54+def md_path(mdname):
55+ """ Convert device name to path in /dev/md """
56+ full_mdname = dev_path(mdname)
57+ if full_mdname.startswith('/dev/md/'):
58+ return full_mdname
59+ elif re.match(r'/dev/md\d+$', full_mdname):
60+ return full_mdname
61+ elif '/' in mdname:
62+ raise ValueError("Invalid RAID device name: {}".format(mdname))
63+ else:
64+ return '/dev/md/{}'.format(mdname)
65+
66+
67 def path_to_kname(path):
68 """
69 converts a path in /dev or a path in /sys/block to the device kname,
70@@ -320,7 +333,7 @@ def dmsetup_info(devname):
71 ','.join(fields), '--noheading',
72 '--separator', _SEP], capture=True)
73 except util.ProcessExecutionError as e:
74- LOG.error('Failed to run dmsetup info:', e)
75+ LOG.error('Failed to run dmsetup info: %s', e)
76 return {}
77
78 values = out.strip().split(_SEP)
79@@ -840,6 +853,8 @@ def _get_dev_disk_by_prefix(prefix):
80 '/dev/sda1': '/dev/disk/<prefix>/virtio-aaaa-part1',
81 }
82 """
83+ if not os.path.exists(prefix):
84+ return {}
85 return {
86 os.path.realpath(bypfx): bypfx
87 for bypfx in [os.path.join(prefix, path)
88diff --git a/curtin/block/dasd.py b/curtin/block/dasd.py
89index 682f9d3..b7008f6 100644
90--- a/curtin/block/dasd.py
91+++ b/curtin/block/dasd.py
92@@ -269,9 +269,9 @@ def _valid_device_id(device_id):
93 if not (0 <= int(dsn, 16) < 256):
94 raise ValueError("device_id invalid: dsn not in 0-255: '%s'" % dsn)
95
96- if not (0 <= int(dev.lower(), 16) < 65535):
97+ if not (0 <= int(dev.lower(), 16) <= 65535):
98 raise ValueError(
99- "device_id invalid: devno not in 0-0x10000: '%s'" % dev)
100+ "device_id invalid: devno not in 0-0xffff: '%s'" % dev)
101
102 return True
103
104diff --git a/curtin/block/multipath.py b/curtin/block/multipath.py
105index 9c7f510..7ad1791 100644
106--- a/curtin/block/multipath.py
107+++ b/curtin/block/multipath.py
108@@ -7,7 +7,7 @@ from curtin import udev
109 SHOW_PATHS_FMT = ("device='%d' serial='%z' multipath='%m' host_wwpn='%N' "
110 "target_wwnn='%n' host_wwpn='%R' target_wwpn='%r' "
111 "host_adapter='%a'")
112-SHOW_MAPS_FMT = "name=%n multipath='%w' sysfs='%d' paths='%N'"
113+SHOW_MAPS_FMT = "name='%n' multipath='%w' sysfs='%d' paths='%N'"
114
115
116 def _extract_mpath_data(cmd, show_verb):
117diff --git a/curtin/commands/block_meta.py b/curtin/commands/block_meta.py
118index ff0f2e9..dee73b1 100644
119--- a/curtin/commands/block_meta.py
120+++ b/curtin/commands/block_meta.py
121@@ -502,7 +502,7 @@ def get_path_to_storage_volume(volume, storage_config):
122 elif vol.get('type') == "raid":
123 # For raid partitions, block device is at /dev/mdX
124 name = vol.get('name')
125- volume_path = os.path.join("/dev", name)
126+ volume_path = block.md_path(name)
127
128 elif vol.get('type') == "bcache":
129 # For bcache setups, the only reliable way to determine the name of the
130@@ -1485,7 +1485,7 @@ def raid_handler(info, storage_config):
131 devices = info.get('devices')
132 raidlevel = info.get('raidlevel')
133 spare_devices = info.get('spare_devices')
134- md_devname = block.dev_path(info.get('name'))
135+ md_devname = block.md_path(info.get('name'))
136 preserve = config.value_as_boolean(info.get('preserve'))
137 if not devices:
138 raise ValueError("devices for raid must be specified")
139@@ -1744,7 +1744,12 @@ def get_device_paths_from_storage_config(storage_config):
140 dpaths = []
141 for (k, v) in storage_config.items():
142 if v.get('type') in ['disk', 'partition']:
143- if config.value_as_boolean(v.get('wipe')):
144+ wipe = config.value_as_boolean(v.get('wipe'))
145+ preserve = config.value_as_boolean(v.get('preserve'))
146+ if v.get('type') == 'disk' and all([wipe, preserve]):
147+ msg = 'type:disk id=%s has both wipe and preserve' % v['id']
148+ raise RuntimeError(msg)
149+ if wipe:
150 try:
151 # skip paths that do not exit, nothing to wipe
152 dpath = get_path_to_storage_volume(k, storage_config)
153diff --git a/curtin/commands/curthooks.py b/curtin/commands/curthooks.py
154index d66afa7..4cf7301 100644
155--- a/curtin/commands/curthooks.py
156+++ b/curtin/commands/curthooks.py
157@@ -85,6 +85,8 @@ do_initrd = yes
158 link_in_boot = {inboot}
159 """
160
161+UEFI_BOOT_ENTRY_IS_NETWORK = r'.*(Network|PXE|NIC|Ethernet|LAN|IP4|IP6)+.*'
162+
163
164 def do_apt_config(cfg, target):
165 cfg = apt_config.translate_old_apt_features(cfg)
166@@ -411,6 +413,7 @@ def install_kernel(cfg, target):
167 def uefi_remove_old_loaders(grubcfg, target):
168 """Removes the old UEFI loaders from efibootmgr."""
169 efi_output = util.get_efibootmgr(target)
170+ LOG.debug('UEFI remove old olders efi output:\n%s', efi_output)
171 current_uefi_boot = efi_output.get('current', None)
172 old_efi_entries = {
173 entry: info
174@@ -437,18 +440,90 @@ def uefi_remove_old_loaders(grubcfg, target):
175 "should be removed.", info['name'])
176
177
178-def uefi_reorder_loaders(grubcfg, target):
179+def uefi_boot_entry_is_network(boot_entry_name):
180+ """
181+ Return boolean if boot entry name looks like a known network entry.
182+ """
183+ return re.match(UEFI_BOOT_ENTRY_IS_NETWORK,
184+ boot_entry_name, re.IGNORECASE) is not None
185+
186+
187+def _reorder_new_entry(boot_order, efi_output, efi_orig=None, variant=None):
188+ """
189+ Reorder the EFI boot menu as follows
190+
191+ 1. All PXE/Network boot entries
192+ 2. The newly installed entry variant (ubuntu/centos)
193+ 3. The other items in the boot order that are not in [1, 2]
194+
195+ returns a list of bootnum strings
196+ """
197+
198+ if not boot_order:
199+ raise RuntimeError('boot_order is not a list')
200+
201+ if efi_orig is None:
202+ raise RuntimeError('Missing efi_orig boot dictionary')
203+
204+ if variant is None:
205+ variant = ""
206+
207+ net_boot = []
208+ other = []
209+ target = []
210+
211+ LOG.debug("UEFI previous boot order: %s", efi_orig['order'])
212+ LOG.debug("UEFI current boot order: %s", boot_order)
213+ new_entries = list(set(boot_order).difference(set(efi_orig['order'])))
214+ if new_entries:
215+ LOG.debug("UEFI Found new boot entries: %s", new_entries)
216+ LOG.debug('UEFI Looking for installed entry variant=%s', variant.lower())
217+ for bootnum in boot_order:
218+ entry = efi_output['entries'][bootnum]
219+ if uefi_boot_entry_is_network(entry['name']):
220+ net_boot.append(bootnum)
221+ else:
222+ if entry['name'].lower() == variant.lower():
223+ target.append(bootnum)
224+ else:
225+ other.append(bootnum)
226+
227+ if net_boot:
228+ LOG.debug("UEFI found netboot entries: %s", net_boot)
229+ if other:
230+ LOG.debug("UEFI found other entries: %s", other)
231+ if target:
232+ LOG.debug("UEFI found target entry: %s", target)
233+ else:
234+ LOG.debug("UEFI Did not find an entry with variant=%s",
235+ variant.lower())
236+ new_order = net_boot + target + other
237+ if boot_order == new_order:
238+ LOG.debug("UEFI Current and Previous bootorders match")
239+ return new_order
240+
241+
242+def uefi_reorder_loaders(grubcfg, target, efi_orig=None, variant=None):
243 """Reorders the UEFI BootOrder to place BootCurrent first.
244
245 The specifically doesn't try to do to much. The order in which grub places
246 a new EFI loader is up to grub. This only moves the BootCurrent to the
247 front of the BootOrder.
248+
249+ In some systems, BootCurrent may not be set/present. In this case
250+ curtin will attempt to place the new boot entry created when grub
251+ is installed after the the previous first entry (before we installed grub).
252+
253 """
254 if grubcfg.get('reorder_uefi', True):
255 efi_output = util.get_efibootmgr(target=target)
256+ LOG.debug('UEFI efibootmgr output after install:\n%s', efi_output)
257 currently_booted = efi_output.get('current', None)
258 boot_order = efi_output.get('order', [])
259- if currently_booted:
260+ new_boot_order = None
261+ force_fallback_reorder = config.value_as_boolean(
262+ grubcfg.get('reorder_uefi_force_fallback', False))
263+ if currently_booted and force_fallback_reorder is False:
264 if currently_booted in boot_order:
265 boot_order.remove(currently_booted)
266 boot_order = [currently_booted] + boot_order
267@@ -456,6 +531,23 @@ def uefi_reorder_loaders(grubcfg, target):
268 LOG.debug(
269 "Setting currently booted %s as the first "
270 "UEFI loader.", currently_booted)
271+ else:
272+ reason = (
273+ "config 'reorder_uefi_force_fallback' is True" if
274+ force_fallback_reorder else "missing 'BootCurrent' value")
275+ LOG.debug("Using fallback UEFI reordering: " + reason)
276+ if len(boot_order) < 2:
277+ LOG.debug(
278+ 'UEFI BootOrder has less than 2 entries, cannot reorder')
279+ return
280+ # look at efi entries before we added one to find the new addition
281+ new_order = _reorder_new_entry(
282+ copy.deepcopy(boot_order), efi_output, efi_orig, variant)
283+ if new_order != boot_order:
284+ new_boot_order = ','.join(new_order)
285+ else:
286+ LOG.debug("UEFI No changes to boot order.")
287+ if new_boot_order:
288 LOG.debug(
289 "New UEFI boot order: %s", new_boot_order)
290 with util.ChrootableTarget(target) as in_chroot:
291@@ -465,25 +557,44 @@ def uefi_reorder_loaders(grubcfg, target):
292 LOG.debug("Currently booted UEFI loader might no longer boot.")
293
294
295-def uefi_remove_duplicate_entries(grubcfg, target):
296+def uefi_remove_duplicate_entries(grubcfg, target, to_remove=None):
297+ if not grubcfg.get('remove_duplicate_entries', True):
298+ LOG.debug("Skipped removing duplicate UEFI boot entries per config.")
299+ return
300+ if to_remove is None:
301+ to_remove = uefi_find_duplicate_entries(grubcfg, target)
302+
303+ # check so we don't run ChrootableTarget code unless we have things to do
304+ if to_remove:
305+ with util.ChrootableTarget(target) as in_chroot:
306+ for bootnum, entry in to_remove:
307+ LOG.debug('Removing duplicate EFI entry (%s, %s)',
308+ bootnum, entry)
309+ in_chroot.subp(['efibootmgr', '--bootnum=%s' % bootnum,
310+ '--delete-bootnum'])
311+
312+
313+def uefi_find_duplicate_entries(grubcfg, target, efi_output=None):
314 seen = set()
315 to_remove = []
316- efi_output = util.get_efibootmgr(target=target)
317+ if efi_output is None:
318+ efi_output = util.get_efibootmgr(target=target)
319 entries = efi_output.get('entries', {})
320+ current_bootnum = efi_output.get('current', None)
321+ # adding BootCurrent to seen first allows us to remove any other duplicate
322+ # entry of BootCurrent.
323+ if current_bootnum:
324+ seen.add(tuple(entries[current_bootnum].items()))
325 for bootnum in sorted(entries):
326+ if bootnum == current_bootnum:
327+ continue
328 entry = entries[bootnum]
329 t = tuple(entry.items())
330 if t not in seen:
331 seen.add(t)
332 else:
333 to_remove.append((bootnum, entry))
334- if to_remove:
335- with util.ChrootableTarget(target) as in_chroot:
336- for bootnum, entry in to_remove:
337- LOG.debug('Removing duplicate EFI entry (%s, %s)',
338- bootnum, entry)
339- in_chroot.subp(['efibootmgr', '--bootnum=%s' % bootnum,
340- '--delete-bootnum'])
341+ return to_remove
342
343
344 def _debconf_multiselect(package, variable, choices):
345@@ -557,7 +668,7 @@ def uefi_find_grub_device_ids(sconfig):
346 esp_partitions.append(item_id)
347 continue
348
349- if item['type'] == 'mount' and item['path'] == '/boot/efi':
350+ if item['type'] == 'mount' and item.get('path') == '/boot/efi':
351 if primary_esp:
352 LOG.debug('Ignoring duplicate mounted primary ESP: %s',
353 item_id)
354@@ -592,7 +703,7 @@ def uefi_find_grub_device_ids(sconfig):
355 return grub_device_ids
356
357
358-def setup_grub(cfg, target, osfamily=DISTROS.debian):
359+def setup_grub(cfg, target, osfamily=DISTROS.debian, variant=None):
360 # target is the path to the mounted filesystem
361
362 # FIXME: these methods need moving to curtin.block
363@@ -692,13 +803,14 @@ def setup_grub(cfg, target, osfamily=DISTROS.debian):
364
365 update_nvram = grubcfg.get('update_nvram', True)
366 if uefi_bootable and update_nvram:
367+ efi_orig_output = util.get_efibootmgr(target)
368 uefi_remove_old_loaders(grubcfg, target)
369
370 install_grub(instdevs, target, uefi=uefi_bootable, grubcfg=grubcfg)
371
372 if uefi_bootable and update_nvram:
373+ uefi_reorder_loaders(grubcfg, target, efi_orig_output, variant)
374 uefi_remove_duplicate_entries(grubcfg, target)
375- uefi_reorder_loaders(grubcfg, target)
376
377
378 def update_initramfs(target=None, all_kernels=False):
379@@ -900,6 +1012,7 @@ def add_swap(cfg, target, fstab):
380 fname = swapcfg.get('filename', None)
381 size = swapcfg.get('size', None)
382 maxsize = swapcfg.get('maxsize', None)
383+ force = swapcfg.get('force', False)
384
385 if size:
386 size = util.human2bytes(str(size))
387@@ -907,7 +1020,7 @@ def add_swap(cfg, target, fstab):
388 maxsize = util.human2bytes(str(maxsize))
389
390 swap.setup_swapfile(target=target, fstab=fstab, swapfile=fname, size=size,
391- maxsize=maxsize)
392+ maxsize=maxsize, force=force)
393
394
395 def detect_and_handle_multipath(cfg, target, osfamily=DISTROS.debian):
396@@ -1733,7 +1846,8 @@ def builtin_curthooks(cfg, target, state):
397 name=stack_prefix + '/install-grub',
398 reporting_enabled=True, level="INFO",
399 description="installing grub to target devices"):
400- setup_grub(cfg, target, osfamily=osfamily)
401+ setup_grub(cfg, target, osfamily=osfamily,
402+ variant=distro_info.variant)
403
404
405 def curthooks(args):
406diff --git a/curtin/commands/install_grub.py b/curtin/commands/install_grub.py
407index 777aa35..5f8311f 100644
408--- a/curtin/commands/install_grub.py
409+++ b/curtin/commands/install_grub.py
410@@ -346,7 +346,7 @@ def install_grub(devices, target, uefi=None, grubcfg=None):
411
412 LOG.debug("installing grub to target=%s devices=%s [replace_defaults=%s]",
413 target, devices, grubcfg.get('replace_default'))
414- update_nvram = config.value_as_boolean(grubcfg.get('update_nvram', False))
415+ update_nvram = config.value_as_boolean(grubcfg.get('update_nvram', True))
416 distroinfo = distro.get_distroinfo(target=target)
417 target_arch = distro.get_architecture(target=target)
418 rhel_ver = (distro.rpm_get_dist_id(target)
419diff --git a/curtin/commands/swap.py b/curtin/commands/swap.py
420index f2381e6..089cd73 100644
421--- a/curtin/commands/swap.py
422+++ b/curtin/commands/swap.py
423@@ -40,7 +40,7 @@ def swap_main(args):
424
425 swap.setup_swapfile(target=state['target'], fstab=state['fstab'],
426 swapfile=args.swapfile, size=size,
427- maxsize=args.maxsize)
428+ maxsize=args.maxsize, force=args.force)
429 sys.exit(2)
430
431
432@@ -54,6 +54,9 @@ CMD_ARGUMENTS = (
433 'default is env[TARGET_MOUNT_POINT]'),
434 'action': 'store', 'metavar': 'TARGET',
435 'default': os.environ.get('TARGET_MOUNT_POINT')}),
436+ (('-F', '--force'),
437+ {'help': 'force creating of swapfile even if it may fail (btrfs,xfs)',
438+ 'default': False, 'action': 'store_true'}),
439 (('-s', '--size'),
440 {'help': 'size of swap file (eg: 1G, 1500M, 1024K, 100000. def: "auto")',
441 'default': None, 'action': 'store'}),
442diff --git a/curtin/distro.py b/curtin/distro.py
443index 43b0c19..82a4dd5 100644
444--- a/curtin/distro.py
445+++ b/curtin/distro.py
446@@ -264,7 +264,7 @@ def apt_update(target=None, env=None, force=False, comment=None,
447
448
449 def run_apt_command(mode, args=None, opts=None, env=None, target=None,
450- execute=True, allow_daemons=False):
451+ execute=True, allow_daemons=False, clean=True):
452 defopts = ['--quiet', '--assume-yes',
453 '--option=Dpkg::options::=--force-unsafe-io',
454 '--option=Dpkg::Options::=--force-confold']
455@@ -289,7 +289,11 @@ def run_apt_command(mode, args=None, opts=None, env=None, target=None,
456
457 apt_update(target, env=env, comment=' '.join(cmd))
458 with ChrootableTarget(target, allow_daemons=allow_daemons) as inchroot:
459- return inchroot.subp(cmd, env=env)
460+ cmd_rv = inchroot.subp(cmd, env=env)
461+ if clean and mode in ['dist-upgrade', 'install', 'upgrade']:
462+ inchroot.subp(['apt-get', 'clean'])
463+
464+ return cmd_rv
465
466
467 def run_yum_command(mode, args=None, opts=None, env=None, target=None,
468@@ -472,6 +476,7 @@ def parse_dpkg_version(raw, name=None, semx=None):
469 as the upstream version.
470
471 returns a dictionary with fields:
472+ 'epoch'
473 'major' (int), 'minor' (int), 'micro' (int),
474 'semantic_version' (int),
475 'extra' (string), 'raw' (string), 'upstream' (string),
476@@ -484,12 +489,20 @@ def parse_dpkg_version(raw, name=None, semx=None):
477 if semx is None:
478 semx = (10000, 100, 1)
479
480- if "-" in raw:
481- upstream = raw.rsplit('-', 1)[0]
482+ raw_offset = 0
483+ if ':' in raw:
484+ epoch, _, upstream = raw.partition(':')
485+ raw_offset = len(epoch) + 1
486 else:
487- # this is a native package, package version treated as upstream.
488+ epoch = 0
489 upstream = raw
490
491+ if "-" in raw[raw_offset:]:
492+ upstream = raw[raw_offset:].rsplit('-', 1)[0]
493+ else:
494+ # this is a native package, package version treated as upstream.
495+ upstream = raw[raw_offset:]
496+
497 match = re.search(r'[^0-9.]', upstream)
498 if match:
499 extra = upstream[match.start():]
500@@ -498,8 +511,10 @@ def parse_dpkg_version(raw, name=None, semx=None):
501 upstream_base = upstream
502 extra = None
503
504- toks = upstream_base.split(".", 2)
505- if len(toks) == 3:
506+ toks = upstream_base.split(".", 3)
507+ if len(toks) == 4:
508+ major, minor, micro, extra = toks
509+ elif len(toks) == 3:
510 major, minor, micro = toks
511 elif len(toks) == 2:
512 major, minor, micro = (toks[0], toks[1], 0)
513@@ -507,6 +522,7 @@ def parse_dpkg_version(raw, name=None, semx=None):
514 major, minor, micro = (toks[0], 0, 0)
515
516 version = {
517+ 'epoch': int(epoch),
518 'major': int(major),
519 'minor': int(minor),
520 'micro': int(micro),
521diff --git a/curtin/net/deps.py b/curtin/net/deps.py
522index f912d1d..b78654d 100644
523--- a/curtin/net/deps.py
524+++ b/curtin/net/deps.py
525@@ -34,10 +34,13 @@ def network_config_required_packages(network_config, mapping=None):
526 if cfgtype == 'version':
527 continue
528 dev_configs.add(cfgtype)
529- # the renderer type may trigger package adds
530+ # subkeys under the type may trigger package adds
531 for entry, entry_cfg in cfg.items():
532 if entry_cfg.get('renderer'):
533 dev_configs.add(entry_cfg.get('renderer'))
534+ else:
535+ for sub_entry, sub_cfg in entry_cfg.items():
536+ dev_configs.add(sub_entry)
537
538 needed_packages = []
539 for dev_type in dev_configs:
540diff --git a/curtin/swap.py b/curtin/swap.py
541index d3f29dc..11e95c4 100644
542--- a/curtin/swap.py
543+++ b/curtin/swap.py
544@@ -5,6 +5,8 @@ import resource
545
546 from .log import LOG
547 from . import util
548+from curtin import paths
549+from curtin import distro
550
551
552 def suggested_swapsize(memsize=None, maxsize=None, fsys=None):
553@@ -51,7 +53,62 @@ def suggested_swapsize(memsize=None, maxsize=None, fsys=None):
554 return maxsize
555
556
557-def setup_swapfile(target, fstab=None, swapfile=None, size=None, maxsize=None):
558+def get_fstype(target, source):
559+ target_source = paths.target_path(target, source)
560+ try:
561+ out, _ = util.subp(['findmnt', '--noheading', '--target',
562+ target_source, '-o', 'FSTYPE'], capture=True)
563+ except util.ProcessExecutionError as exc:
564+ LOG.warning('Failed to query %s fstype, findmnt returned error: %s',
565+ target_source, exc)
566+ return None
567+
568+ if out:
569+ """
570+ $ findmnt --noheading --target /btrfs -o FSTYPE
571+ btrfs
572+ """
573+ return out.splitlines()[-1]
574+
575+ return None
576+
577+
578+def get_target_kernel_version(target):
579+ pkg_ver = None
580+
581+ distro_info = distro.get_distroinfo(target=target)
582+ if not distro_info:
583+ raise RuntimeError('Failed to determine target distro')
584+ osfamily = distro_info.family
585+ if osfamily == distro.DISTROS.debian:
586+ try:
587+ # check in-target version
588+ pkg_ver = distro.get_package_version('linux-image-generic',
589+ target=target)
590+ except Exception as e:
591+ LOG.warn(
592+ "failed reading linux-image-generic package version, %s", e)
593+ return pkg_ver
594+
595+
596+def can_use_swapfile(target, fstype):
597+ if fstype is None:
598+ raise RuntimeError(
599+ 'Unknown target filesystem type, may not support swapfiles')
600+ if fstype in ['btrfs', 'xfs']:
601+ # check kernel version
602+ pkg_ver = get_target_kernel_version(target)
603+ if not pkg_ver:
604+ raise RuntimeError('Failed to read target kernel version')
605+ if fstype == 'btrfs' and pkg_ver['major'] < 5:
606+ raise RuntimeError(
607+ 'btrfs requiers kernel version 5.0+ to use swapfiles')
608+ elif fstype in ['zfs']:
609+ raise RuntimeError('ZFS cannot use swapfiles')
610+
611+
612+def setup_swapfile(target, fstab=None, swapfile=None, size=None, maxsize=None,
613+ force=False):
614 if size is None:
615 size = suggested_swapsize(fsys=target, maxsize=maxsize)
616
617@@ -65,6 +122,24 @@ def setup_swapfile(target, fstab=None, swapfile=None, size=None, maxsize=None):
618 if not swapfile.startswith("/"):
619 swapfile = "/" + swapfile
620
621+ # query the directory in which swapfile will reside
622+ fstype = get_fstype(target, os.path.dirname(swapfile))
623+ try:
624+ can_use_swapfile(target, fstype)
625+ except RuntimeError as err:
626+ if force:
627+ LOG.warning('swapfile may not work: %s', err)
628+ else:
629+ LOG.debug('Not creating swap: %s', err)
630+ return
631+
632+ allocate_cmd = 'fallocate -l "${2}M" "$1"'
633+ # fallocate uses IOCTLs to allocate space in a filesystem, however it's not
634+ # clear (from curtin's POV) that it creates non-sparse files as required by
635+ # mkswap so we'll skip fallocate for now and use dd.
636+ if fstype in ['btrfs', 'xfs']:
637+ allocate_cmd = 'dd if=/dev/zero "of=$1" bs=1M "count=$2"'
638+
639 mbsize = str(int(size / (2 ** 20)))
640 msg = "creating swap file '%s' of %sMB" % (swapfile, mbsize)
641 fpath = os.path.sep.join([target, swapfile])
642@@ -73,10 +148,9 @@ def setup_swapfile(target, fstab=None, swapfile=None, size=None, maxsize=None):
643 with util.LogTimer(LOG.debug, msg):
644 util.subp(
645 ['sh', '-c',
646- ('rm -f "$1" && umask 0066 && '
647- '{ fallocate -l "${2}M" "$1" || '
648- ' dd if=/dev/zero "of=$1" bs=1M "count=$2"; } && '
649- 'mkswap "$1" || { r=$?; rm -f "$1"; exit $r; }'),
650+ ('rm -f "$1" && umask 0066 && truncate -s 0 "$1" && '
651+ '{ chattr +C "$1" || true; } && ') + allocate_cmd +
652+ (' && mkswap "$1" || { r=$?; rm -f "$1"; exit $r; }'),
653 'setup_swap', fpath, mbsize])
654 except Exception:
655 LOG.warn("failed %s" % msg)
656diff --git a/debian/changelog b/debian/changelog
657index 67c16c4..246ea94 100644
658--- a/debian/changelog
659+++ b/debian/changelog
660@@ -1,3 +1,43 @@
661+curtin (20.2-0ubuntu1~20.04.1) focal; urgency=medium
662+
663+ * New upstream release. (LP: #1896947)
664+ - Release 20.2 [Paride Legovini]
665+ - Get debian/rules in sync with 0.1.0~bzr470-0ubuntu1 [Paride Legovini]
666+ - Fix the py3 pylint ci run [Paride Legovini]
667+ - vmtest: Fix multiple issues with vmtest on master
668+ - Refactor uefi_remove_duplicates into find/remove functions for reuse
669+ - distro: run apt-get clean after dist-upgrade, install, upgrade
670+ - curthooks: UEFI remove dupes: don't remove BootCurrent, config option
671+ - Pin the dependency on pyrsistent [Paride Legovini]
672+ - restore default of grub.update_nvram to True in install_grub
673+ [Michael Hudson-Doyle]
674+ - block: disk_to_byid_path handle missing /dev/disk/by-id directory
675+ - UEFI: Handle missing BootCurrent entry when reordering UEFI entries
676+ - dasd: fix off-by-one device_id devno range check [Paride Legovini]
677+ - curthooks: uefi_find_grub_device_ids handle type:mount without path
678+ - netplan openvswitch yaml changed
679+ - tools/curtainer: do not wait for snapd.seeded.service
680+ - tools/curtainer: enable using ubuntu-minimal images
681+ - vmtests: add Groovy [Paride Legovini]
682+ - Drop the Eoan vmtests (EOL) [Paride Legovini]
683+ - tools: rename remove-vmtest-release to vmtest-remove-release
684+ - Snooze the tests failing because of LP: #1861941 for two more months
685+ [Paride Legovini]
686+ - LP: #1671951 is Fix Released => Drop the PPA [Paride Legovini]
687+ - swaps: handle swapfiles on btrfs
688+ - curtainer: fail is masking of zfs-mount or zfs-share fails
689+ [Paride Legovini]
690+ - multipath: handle multipath nvme name fields correctly
691+ - curtainer: mask the zfs-mount and zfs-share services [Paride Legovini]
692+ - tools/jenkins-runner: shuffle test-cases to randomize load
693+ [Paride Legovini]
694+ - Add Trusty/UEFI/HWE-X vmtest, drop realpath add, drop shell code
695+ - LP: #1881977 - Install realpath on Trusty UEFI. [Lee Trager]
696+ - vmtests: fix PreservePartitionWipeVg storage config
697+ - Fix mdraid name creates broken configuration [James Falcon]
698+
699+ -- Paride Legovini <paride.legovini@canonical.com> Tue, 29 Sep 2020 14:22:54 +0200
700+
701 curtin (20.1-2-g42a9667f-0ubuntu1~20.04.1) focal; urgency=medium
702
703 * New upstream snapshot. (LP: #1881003)
704diff --git a/doc/topics/config.rst b/doc/topics/config.rst
705index 72cd683..ec8a109 100644
706--- a/doc/topics/config.rst
707+++ b/doc/topics/config.rst
708@@ -226,6 +226,42 @@ not provided, Curtin will set the value to 'console'. If the ``terminal``
709 value is 'unmodified' then Curtin will not set any value at all and will
710 use Grub defaults.
711
712+**reorder_uefi**: *<boolean: default True>*
713+
714+Curtin is typically used with MAAS where the systems are configured to boot
715+from the network leaving MAAS in control. On UEFI systems, after installing
716+a bootloader the systems BootOrder may be updated to boot from the new entry.
717+This breaks MAAS control over the system as all subsequent reboots of the node
718+will no longer boot over the network. Therefore, if ``reorder_uefi`` is True
719+curtin will modify the UEFI BootOrder settings to place the currently booted
720+entry (BootCurrent) to the first option after installing the new target OS into
721+the UEFI boot menu. The result is that the system will boot from the same
722+device that it booted to run curtin; for MAAS this will be a network device.
723+
724+On some UEFI systems the BootCurrent entry may not be present. This can
725+cause a system to not boot to the same device that it was previously booting.
726+If BootCurrent is not present, curtin will update the BootOrder such that
727+all Network related entries are placed before the newly installed boot entry and
728+all other entries are placed at the end. This enables the system to network
729+boot first and on failure will boot the most recently installed entry.
730+
731+This setting is ignored if *update_nvram* is False.
732+
733+**reorder_uefi_force_fallback**: *<boolean: default False>*
734+
735+The fallback reodering mechanism is only active if BootCurrent is not present
736+in the efibootmgr output. The fallback reordering method may be enabled
737+even if BootCurrent is present if *reorder_uefi_force_fallback* is True.
738+
739+This setting is ignored if *update_nvram* or *reorder_uefi* are False.
740+
741+**remove_duplicate_entries**: <*boolean: default True>*
742+
743+When curtin updates UEFI NVRAM it will remove duplicate entries that are
744+present in the UEFI menu. If you do not wish for curtin to remove duplicate
745+entries setting *remove_duplicate_entries* to False.
746+
747+This setting is ignored if *update_nvram* is False.
748
749 **Example**::
750
751@@ -235,6 +271,7 @@ use Grub defaults.
752 replace_linux_default: False
753 update_nvram: True
754 terminal: serial
755+ remove_duplicate_entries: True
756
757 **Default terminal value, GRUB_TERMINAL=console**::
758
759@@ -264,6 +301,12 @@ use Grub defaults.
760 probe_additional_os: True
761 terminal: unmodified
762
763+**Enable Fallback UEFI Reordering**::
764+
765+ grub:
766+ reorder_uefi: true
767+ reorder_uefi_force_fallback: true
768+
769
770 http_proxy
771 ~~~~~~~~~~
772@@ -752,13 +795,27 @@ Configure the max size of the swapfile, defaults to 8GB
773 Configure the exact size of the swapfile. Setting ``size`` to 0 will
774 disable swap.
775
776+**force**: *<boolean>*
777+
778+Force the creation of swapfile even if curtin detects it may not work.
779+In some target filesystems, e.g. btrfs, xfs, zfs, the use of a swap file has
780+restrictions. If curtin detects that there may be issues it will refuse
781+to create the swapfile. Users can force creation of a swapfile by passing
782+``force: true``. A forced swapfile may not be used by the target OS and could
783+log cause an error.
784+
785 **Example**::
786
787 swap:
788 filename: swap.img
789- size: None
790+ size: 1GB
791 maxsize: 4GB
792
793+ swap:
794+ filename: btrfs_swapfile.img
795+ size: 1GB
796+ force: true
797+
798
799 system_upgrade
800 ~~~~~~~~~~~~~~
801diff --git a/examples/tests/basic.yaml b/examples/tests/basic.yaml
802index 71730c0..82f5ad1 100644
803--- a/examples/tests/basic.yaml
804+++ b/examples/tests/basic.yaml
805@@ -1,4 +1,8 @@
806 showtrace: true
807+swap:
808+ filename: /btrfs/btrfsswap.img
809+ size: 1GB
810+ maxsize: 1GB
811 storage:
812 version: 1
813 config:
814diff --git a/examples/tests/basic_scsi.yaml b/examples/tests/basic_scsi.yaml
815index 51f5236..fd28bbe 100644
816--- a/examples/tests/basic_scsi.yaml
817+++ b/examples/tests/basic_scsi.yaml
818@@ -1,4 +1,8 @@
819 showtrace: true
820+swap:
821+ filename: /btrfs/btrfsswap.img
822+ size: 1GB
823+ maxsize: 1GB
824 storage:
825 version: 1
826 config:
827diff --git a/examples/tests/network_v2_ovs.yaml b/examples/tests/network_v2_ovs.yaml
828index 6d1dc3d..0d063ce 100644
829--- a/examples/tests/network_v2_ovs.yaml
830+++ b/examples/tests/network_v2_ovs.yaml
831@@ -8,35 +8,6 @@ network:
832 match:
833 macaddress: '52:54:00:12:34:00'
834 set-name: eth0
835- eth1:
836- match:
837- macaddress: '52:54:00:12:34:02'
838- set-name: eth1
839- eth2:
840- match:
841- macaddress: '52:54:00:12:34:04'
842- set-name: eth2
843- openvswitch:
844- bridges:
845- br-int:
846- fail-mode: secure
847- datapath_type: system
848- stp: false
849- rstp: false
850- mcast-snooping: false
851- controller:
852- addresses:
853- - tcp:127.0.0.1:6653
854- protocols:
855- - OpenFlow10
856- - OpenFlow12
857- - OpenFlow13
858- ports:
859- patch-tun:
860- type: patch
861- options:
862- peer: patch-int
863- eth1:
864- tag: 2
865- eth2:
866- tag: 2
867+ bridges:
868+ br-empty:
869+ openvswitch: {}
870diff --git a/examples/tests/nvme_bcache.yaml b/examples/tests/nvme_bcache.yaml
871index 4fefd94..ed705c4 100644
872--- a/examples/tests/nvme_bcache.yaml
873+++ b/examples/tests/nvme_bcache.yaml
874@@ -1,8 +1,7 @@
875 showtrace: true
876 storage:
877 config:
878- - grub_device: true
879- id: sda
880+ - id: sda
881 model: LOGICAL VOLUME
882 name: sda
883 ptable: gpt
884@@ -23,7 +22,7 @@ storage:
885 type: disk
886 wipe: superblock
887 - device: sda
888- flag: boot
889+ grub_device: true
890 id: sda-part1
891 name: sda-part1
892 number: 1
893@@ -33,7 +32,6 @@ storage:
894 uuid: 9f0fda16-c096-4cee-82ac-4f5f294253a2
895 wipe: superblock
896 - device: sda
897- flag: boot
898 id: sda-part2
899 name: sda-part2
900 number: 2
901diff --git a/examples/tests/preserve-partition-wipe-vg.yaml b/examples/tests/preserve-partition-wipe-vg.yaml
902index 97686e1..27a4235 100644
903--- a/examples/tests/preserve-partition-wipe-vg.yaml
904+++ b/examples/tests/preserve-partition-wipe-vg.yaml
905@@ -38,7 +38,6 @@ storage:
906 grub_device: true
907 type: disk
908 id: disk-sda
909- wipe: superblock
910 - serial: disk-b
911 name: disk-b
912 grub_device: false
913diff --git a/examples/tests/raid5boot.yaml b/examples/tests/raid5boot.yaml
914index b1df21d..d8afe7f 100644
915--- a/examples/tests/raid5boot.yaml
916+++ b/examples/tests/raid5boot.yaml
917@@ -42,7 +42,7 @@ storage:
918 size: 3GB
919 device: sdc
920 - id: mddevice
921- name: md0
922+ name: os-raid1
923 type: raid
924 raidlevel: 5
925 devices:
926diff --git a/helpers/common b/helpers/common
927index 5638d39..4afb6a1 100644
928--- a/helpers/common
929+++ b/helpers/common
930@@ -29,19 +29,6 @@ EOF
931 [ $# -eq 0 ] || echo "$@"
932 }
933
934-grub_install_usage() {
935- cat <<EOF
936-Usage: ${0##*/} [ options ] mount-point target-dev
937-
938- perform grub-install with mount-point onto target-dev.
939-
940- options:
941- --uefi install grub-efi instead of grub-pc
942- --update-nvram request grub to update nvram
943-EOF
944- [ $# -eq 0 ] || echo "$@"
945-}
946-
947 cleanup() {
948 if [ -d "$TEMP_D" ]; then
949 rm -Rf "$TEMP_D"
950@@ -480,569 +467,4 @@ getsize() {
951 fi
952 }
953
954-is_md() {
955- case "${1##*/}" in
956- md[0-9]) return 0;;
957- esac
958- return 1
959-}
960-
961-get_carryover_params() {
962- local cmdline=" $1 " extra="" lead="" carry_extra="" carry_lead=""
963- # return a string to append to installed systems boot parameters
964- # it may include a '--' after a '---'
965- # see LP: 1402042 for some history here.
966- # this is similar to 'user-params' from d-i
967- local preferred_sep="---" # KERNEL_CMDLINE_COPY_TO_INSTALL_SEP
968- local legacy_sep="--"
969- case "$cmdline" in
970- *\ ${preferred_sep}\ *)
971- extra=${cmdline#* ${preferred_sep} }
972- lead=${cmdline%% ${preferred_sep} *}
973- ;;
974- *\ ${legacy_sep}\ *)
975- extra="${cmdline#* ${legacy_sep} }"
976- lead=${cmdline%% ${legacy_sep} *}
977- ;;
978- *)
979- extra=""
980- lead="$cmdline"
981- ;;
982- esac
983-
984- if [ -n "$extra" ]; then
985- carry_extra=$(set -f;
986- c="";
987- for p in $extra; do
988- case "$p" in
989- (BOOTIF=*|initrd=*|BOOT_IMAGE=*) continue;;
990- esac
991- c="$c $p";
992- done
993- echo "${c# }"
994- )
995- fi
996-
997- # these get copied even if they werent after the separator
998- local padded=" $carry_extra "
999- carry_lead=$(set -f;
1000- padded=" ${carry_extra} "
1001- c=""
1002- for p in $lead; do
1003- # skip any that are already in carry_extra
1004- [ "${padded#* $p }" != "$padded" ] && continue
1005- case "$p" in
1006- (console=*) c="$c $p";;
1007- esac
1008- done
1009- echo "${c# }"
1010- )
1011- [ -n "${carry_lead}" -a -n "${carry_extra}" ] &&
1012- carry_lead="${carry_lead} "
1013- _RET="${carry_lead}${carry_extra}"
1014-}
1015-
1016-shell_config_update() {
1017- # shell_config_update(file, name, value)
1018- # update variable 'name' setting value to 'val' in shell syntax 'file'.
1019- # if 'name' is not present, then append declaration.
1020- local file="$1" name="$2" val="$3"
1021- if ! [ -f "$file" ] || ! grep -q "^$name=" "$file"; then
1022- debug 2 "appending to $file shell $name=\"$val\""
1023- echo "$name=\"$val\"" >> "$file"
1024- return
1025- fi
1026- local cand="" del=""
1027- for cand in "|" "," "/"; do
1028- [ "${val#*${del}}" = "${val}" ] && del="$cand" && break
1029- done
1030- [ -n "$del" ] || {
1031- error "Couldn't find a sed delimiter for '$val'";
1032- return 1;
1033- }
1034-
1035- sed -i -e "s${del}^$name=.*${del}$name=\"$val\"${del}" "$file" ||
1036- { error "Failed editing '$file' to set $name=$val"; return 1; }
1037- debug 2 "updated $file to set $name=\"$val\""
1038- return 0
1039-}
1040-
1041-apply_grub_cmdline_linux_default() {
1042- local mp="$1" newargs="$2" edg="${3:-etc/default/grub}"
1043- local gcld="GRUB_CMDLINE_LINUX_DEFAULT"
1044- debug 1 "setting $gcld to '$newargs' in $edg"
1045- shell_config_update "$mp/$edg" "$gcld" "$newargs" || {
1046- error "Failed to set '$gcld=$newargs' in $edg"
1047- return 1
1048- }
1049-}
1050-
1051-get_parent_disk() {
1052- # Look up the parent /dev path via sysfs. Using the partition
1053- # kname (nvme0n1p1), construct a /sys/class/block path, use
1054- # realpath to resolve this to an absolute path which includes
1055- # the parent:
1056- # /sys/devices/pci0000:00/*/*/nvme/nvme0/nvme0n1/nvme0n1p1
1057- # dirname to extract the parent, then read the 'dev' entry
1058- # /sys/devices/pci0000:00/*/*/nvme/nvme0/nvme0n1/dev
1059- # which contains the MAJOR:MINOR value and construct a /dev/block
1060- # path which is a symbolic link that udev constructs that points
1061- # to the real device name and use realpath to return the absolute path.
1062- # /dev/block/259:0 -> ../nvme0n1
1063- local devpath="${1}"
1064- local kname=$(basename "$devpath")
1065- local syspath=$(realpath "/sys/class/block/$kname")
1066- local disksyspath=$(dirname "$syspath")
1067- local diskmajmin=$(cat "${disksyspath}/dev")
1068- local diskdevpath=$(realpath "/dev/block/${diskmajmin}")
1069- echo $diskdevpath
1070-}
1071-
1072-install_grub() {
1073- local long_opts="uefi,update-nvram,os-family:"
1074- local getopt_out="" mp_efi=""
1075- getopt_out=$(getopt --name "${0##*/}" \
1076- --options "" --long "${long_opts}" -- "$@") &&
1077- eval set -- "${getopt_out}"
1078-
1079- local uefi=0 update_nvram=0 os_family=""
1080-
1081- while [ $# -ne 0 ]; do
1082- cur="$1"; next="$2";
1083- case "$cur" in
1084- --os-family) os_family=${next};;
1085- --uefi) uefi=$((${uefi}+1));;
1086- --update-nvram) update_nvram=$((${update_nvram}+1));;
1087- --) shift; break;;
1088- esac
1089- shift;
1090- done
1091-
1092- [ $# -lt 2 ] && { grub_install_usage "must provide mount-point and target-dev" 1>&2; return 1; }
1093-
1094- local mp="$1"
1095- local cmdline tmp r=""
1096- shift
1097- local grubdevs
1098- grubdevs=( "$@" )
1099- if [ "${#grubdevs[@]}" = "1" -a "${grubdevs[0]}" = "none" ]; then
1100- grubdevs=( )
1101- fi
1102- debug 1 "grubdevs: [${grubdevs[@]}]"
1103-
1104- # find the mp device
1105- local mp_dev="" fstype=""
1106- mp_dev=$(awk -v "MP=$mp" '$2 == MP { print $1 }' /proc/mounts) || {
1107- error "unable to determine device for mount $mp";
1108- return 1;
1109- }
1110- debug 1 "/proc/mounts shows $mp_dev is mounted at $mp"
1111-
1112- fstype=$(awk -v MP=$mp '$2 == MP { print $3 }' /proc/mounts) || {
1113- error "unable to fstype for mount $mp";
1114- return 1;
1115- }
1116-
1117- [ -z "$mp_dev" ] && {
1118- error "did not find '$mp' in /proc/mounts"
1119- cat /proc/mounts 1>&2
1120- return 1
1121- }
1122- # check if parsed mount point is a block device
1123- # error unless fstype is zfs, where entry will not point to block device.
1124- if ! [ -b "$mp_dev" ] && [ "$fstype" != "zfs" ]; then
1125- # error unless mp is zfs, entry doesn't point to block devs
1126- error "$mp_dev ($fstype) is not a block device!"; return 1;
1127- fi
1128-
1129- local os_variant=""
1130- if [ -e "${mp}/etc/os-release" ]; then
1131- os_variant=$(chroot "$mp" \
1132- /bin/sh -c 'echo $(. /etc/os-release; echo $ID)')
1133- else
1134- # Centos6 doesn't have os-release, so check for centos/redhat release
1135- # looks like: CentOS release 6.9 (Final)
1136- for rel in $(ls ${mp}/etc/*-release); do
1137- os_variant=$(awk '{print tolower($1)}' $rel)
1138- [ -n "$os_variant" ] && break
1139- done
1140- fi
1141- [ $? != 0 ] &&
1142- { error "Failed to read ID from $mp/etc/os-release"; return 1; }
1143-
1144- local rhel_ver=""
1145- case $os_variant in
1146- debian|ubuntu) os_family="debian";;
1147- centos|rhel)
1148- os_family="redhat"
1149- rhel_ver=$(chroot "$mp" rpm -E '%rhel')
1150- ;;
1151- esac
1152-
1153- # ensure we have both settings, family and variant are needed
1154- [ -n "${os_variant}" -a -n "${os_family}" ] ||
1155- { error "Failed to determine os variant and family"; return 1; }
1156-
1157- # get target arch
1158- local target_arch="" r="1"
1159- case $os_family in
1160- debian)
1161- target_arch=$(chroot "$mp" dpkg --print-architecture)
1162- r=$?
1163- ;;
1164- redhat)
1165- target_arch=$(chroot "$mp" rpm -E '%_arch')
1166- r=$?
1167- ;;
1168- esac
1169- [ $r -eq 0 ] || {
1170- error "failed to get target architecture [$r]"
1171- return 1;
1172- }
1173-
1174- # grub is not the bootloader you are looking for
1175- if [ "${target_arch}" = "s390x" ]; then
1176- return 0;
1177- fi
1178-
1179- # set correct grub package
1180- local grub_name=""
1181- local grub_target=""
1182- case "$target_arch" in
1183- i386|amd64)
1184- # debian
1185- grub_name="grub-pc"
1186- grub_target="i386-pc"
1187- ;;
1188- x86_64)
1189- case $rhel_ver in
1190- 6) grub_name="grub";;
1191- 7|8) grub_name="grub2-pc";;
1192- *)
1193- error "Unknown rhel_ver [$rhel_ver]";
1194- return 1;
1195- ;;
1196- esac
1197- grub_target="i386-pc"
1198- ;;
1199- esac
1200- if [ "${target_arch#ppc64}" != "${target_arch}" ]; then
1201- grub_name="grub-ieee1275"
1202- grub_target="powerpc-ieee1275"
1203- elif [ "$uefi" -ge 1 ]; then
1204- grub_name="grub-efi-$target_arch"
1205- case "$target_arch" in
1206- x86_64)
1207- # centos 7+, no centos6 support
1208- # grub2-efi-x64 installs a signed grub bootloader while
1209- # curtin uses grub2-efi-x64-modules to generate grubx64.efi.
1210- # Either works just check that one of them is installed.
1211- grub_name="grub2-efi-x64 grub2-efi-x64-modules"
1212- grub_target="x86_64-efi"
1213- ;;
1214- amd64)
1215- grub_target="x86_64-efi";;
1216- arm64)
1217- grub_target="arm64-efi";;
1218- esac
1219- fi
1220-
1221- # check that the grub package is installed
1222- local r=$?
1223- case $os_family in
1224- debian)
1225- tmp=$(chroot "$mp" dpkg-query --show \
1226- --showformat='${Status}\n' $grub_name)
1227- r=$?
1228- ;;
1229- redhat)
1230- tmp=$(chroot "$mp" rpm -q \
1231- --queryformat='install ok installed\n' $grub_name)
1232- r=$?
1233- ;;
1234- esac
1235- if [ $r -ne 0 -a $r -ne 1 ]; then
1236- error "failed to check if $grub_name installed";
1237- return 1;
1238- fi
1239- # Check that any of the packages in $grub_name are installed. If
1240- # grub_name contains multiple packages, as it does for CentOS 7+,
1241- # only one package has to be installed for this to pass.
1242- if ! echo $tmp | grep -q 'install ok installed'; then
1243- debug 1 "$grub_name not installed, not doing anything"
1244- return 1
1245- fi
1246-
1247- local grub_d="etc/default/grub.d"
1248- # ubuntu writes to /etc/default/grub.d/50-curtin-settings.cfg
1249- # to avoid tripping prompts on upgrade LP: #564853
1250- local mygrub_cfg="$grub_d/50-curtin-settings.cfg"
1251- case $os_family in
1252- redhat)
1253- grub_d="etc/default"
1254- mygrub_cfg="etc/default/grub";;
1255- esac
1256- [ -d "$mp/$grub_d" ] || mkdir -p "$mp/$grub_d" ||
1257- { error "Failed to create $grub_d"; return 1; }
1258-
1259- # LP: #1179940 . The 50-cloudig-settings.cfg file is written by the cloud
1260- # images build and defines/override some settings. Disable it.
1261- local cicfg="$grub_d/50-cloudimg-settings.cfg"
1262- if [ -f "$mp/$cicfg" ]; then
1263- debug 1 "moved $cicfg out of the way"
1264- mv "$mp/$cicfg" "$mp/$cicfg.disabled"
1265- fi
1266-
1267- # get the user provided / carry-over kernel arguments
1268- local newargs=""
1269- read cmdline < /proc/cmdline &&
1270- get_carryover_params "$cmdline" && newargs="$_RET" || {
1271- error "Failed to get carryover parrameters from cmdline";
1272- return 1;
1273- }
1274- # always append rd.auto=1 for centos
1275- case $os_family in
1276- redhat)
1277- newargs="${newargs:+${newargs} }rd.auto=1";;
1278- esac
1279- debug 1 "carryover command line params '$newargs'"
1280-
1281- if [ "${REPLACE_GRUB_LINUX_DEFAULT:-1}" != "0" ]; then
1282- apply_grub_cmdline_linux_default "$mp" "$newargs" || {
1283- error "Failed to apply grub cmdline."
1284- return 1
1285- }
1286- fi
1287-
1288- if [ "${DISABLE_OS_PROBER:-1}" == "1" ]; then
1289- {
1290- echo "# Curtin disable grub os prober that might find other OS installs."
1291- echo "GRUB_DISABLE_OS_PROBER=true"
1292- } >> "$mp/$mygrub_cfg"
1293- fi
1294-
1295- if [ -n "${GRUB_TERMINAL}" ]; then
1296- {
1297- echo "# Curtin configured GRUB_TERMINAL value"
1298- echo "GRUB_TERMINAL=${GRUB_TERMINAL}"
1299- } >> "$mp/$mygrub_cfg"
1300- fi
1301-
1302- debug 1 "processing grubdevs values for expansion if needed"
1303- local short="" bd="" grubdev grubdevs_new=""
1304- grubdevs_new=()
1305- for grubdev in "${grubdevs[@]}"; do
1306- if is_md "$grubdev"; then
1307- debug 1 "$grubdev is raid, find members"
1308- short=${grubdev##*/}
1309- for bd in "/sys/block/$short/slaves/"/*; do
1310- [ -d "$bd" ] || continue
1311- bd=${bd##*/}
1312- bd="/dev/${bd%[0-9]}" # FIXME: part2bd
1313- debug 1 "Add dev $bd to grubdevs_new"
1314- grubdevs_new[${#grubdevs_new[@]}]="$bd"
1315- done
1316- else
1317- debug 1 "Found dev [$grubdev] add to grubdevs_new"
1318- grubdevs_new[${#grubdevs_new[@]}]="$grubdev"
1319- fi
1320- done
1321- grubdevs=( "${grubdevs_new[@]}" )
1322- debug 1 "updated grubdevs: [${grubdevs[@]}]"
1323-
1324- if [ "$uefi" -ge 1 ]; then
1325- nvram="--no-nvram"
1326- if [ "$update_nvram" -ge 1 ]; then
1327- nvram=""
1328- fi
1329- debug 1 "number of entries in grubdevs_new: ${#grubdevs[@]}"
1330- if [ "${#grubdevs_new[@]}" -eq 1 ] && [ -b "${grubdevs_new[0]}" ]; then
1331- debug 1 "Found a single entry in grubdevs, ${grubdevs_new[0]}"
1332- # Currently UEFI can only be pointed to one system partition. If
1333- # for some reason multiple install locations are given only use the
1334- # first.
1335- efi_dev="${grubdevs_new[0]}"
1336- debug 1 "efi_dev=[${efi_dev}]"
1337- elif [ "${#grubdevs_new[@]}" -gt 1 ]; then
1338- error "Only one grub device supported on UEFI!"
1339- exit 1
1340- else
1341- debug 1 "no storage config, parsing /proc/mounts with awk"
1342- # If no storage configuration was given try to determine the system
1343- # partition.
1344- efi_dev=$(awk -v "MP=${mp}/boot/efi" '$2 == MP { print $1 }' /proc/mounts)
1345- debug 1 "efi_dev=[${efi_dev}]"
1346- [ -n "$efi_dev" ] || {
1347- error "Failed to find efi device from parsing /proc/mounts"
1348- return 1
1349- }
1350-
1351- fi
1352- # The partition number of block device name need to be determined here
1353- # so both getting the UEFI device from Curtin config and discovering it
1354- # work.
1355- efi_part_num=$(cat /sys/class/block/$(basename $efi_dev)/partition)
1356- debug 1 "efi_part_num: $efi_part_num"
1357- [ -n "${efi_part_num}" ] || {
1358- error "Failed to determine $efi_dev partition number"
1359- return 1
1360- }
1361- efi_disk=$(get_parent_disk "$efi_dev")
1362- debug 1 "efi_disk: [$efi_disk]"
1363- [ -b "${efi_disk}" ] || {
1364- error "${efi_disk} is not a valid block device"
1365- return 1
1366- }
1367- debug 1 "curtin uefi: installing ${grub_name} to: /boot/efi"
1368- chroot "$mp" env DEBIAN_FRONTEND=noninteractive sh -exc '
1369- echo "before grub-install efiboot settings"
1370- efibootmgr -v || echo "WARN: efibootmgr exited $?"
1371- bootid="$4"
1372- efi_disk="$5"
1373- efi_part_num="$6"
1374- grubpost=""
1375- grubmulti="/usr/lib/grub/grub-multi-install"
1376- case $bootid in
1377- debian|ubuntu)
1378- grubcmd="grub-install"
1379- if [ -e "${grubmulti}" ]; then
1380- grubcmd="${grubmulti}"
1381- fi
1382- dpkg-reconfigure "$1"
1383- update-grub
1384- ;;
1385- centos|redhat|rhel)
1386- grubcmd="grub2-install"
1387- # RHEL uses redhat instead of the os_variant rhel for the bootid.
1388- if [ "$bootid" = "rhel" ]; then
1389- bootid="redhat"
1390- fi
1391- if [ -f /boot/efi/EFI/$bootid/grubx64.efi ]; then
1392- grubpost="grub2-mkconfig -o /boot/efi/EFI/$bootid/grub.cfg"
1393- else
1394- grubpost="grub2-mkconfig -o /boot/grub2/grub.cfg"
1395- fi
1396- ;;
1397- *)
1398- echo "Unsupported OS: $bootid" 1>&2
1399- exit 1
1400- ;;
1401- esac
1402- # grub-install in 12.04 does not contain --no-nvram, --target,
1403- # or --efi-directory
1404- target="--target=$2"
1405- no_nvram="$3"
1406- efi_dir="--efi-directory=/boot/efi"
1407- gi_out=$($grubcmd --help 2>&1)
1408- echo "$gi_out" | grep -q -- "$no_nvram" || no_nvram=""
1409- echo "$gi_out" | grep -q -- "--target" || target=""
1410- echo "$gi_out" | grep -q -- "--efi-directory" || efi_dir=""
1411-
1412- # Do not overwrite grubx64.efi if it already exists. grub-install
1413- # generates grubx64.efi and overwrites any existing binary in
1414- # /boot/efi/EFI/$bootid. This binary is not signed and will cause
1415- # secure boot to fail.
1416- #
1417- # CentOS, RHEL, Fedora ship the signed boot loader in the package
1418- # grub2-efi-x64 which installs the signed boot loader to
1419- # /boot/efi/EFI/$bootid/grubx64.efi. All Curtin has to do is
1420- # configure the firmware. This mirrors what Anaconda does.
1421- #
1422- # Debian and Ubuntu come with a patched version of grub which
1423- # add the install flag --uefi-secure-boot which is enabled by
1424- # default. When enabled if a signed version of grub exists on
1425- # the filesystem it will be copied into /boot/efi/EFI/$bootid.
1426- # Stock Ubuntu images do not ship with anything in /boot. Those
1427- # files are generated by installing a kernel and grub.
1428- echo "Dumping /boot/efi contents"
1429- find /boot/efi
1430- echo "Checking for existing EFI grub entry on ESP"
1431- if [ "$grubcmd" = "grub2-install" -a -f /boot/efi/EFI/$bootid/grubx64.efi ]; then
1432- if [ -z "$no_nvram" ]; then
1433- # UEFI firmware should be pointed to the shim if available to
1434- # enable secure boot.
1435- for boot_uefi in \
1436- /boot/efi/EFI/$bootid/shimx64.efi \
1437- /boot/efi/EFI/BOOT/BOOTX64.EFI \
1438- /boot/efi/EFI/$bootid/grubx64.efi; do
1439- if [ -f $boot_uefi ]; then
1440- break
1441- fi
1442- done
1443- loader=$(echo ${boot_uefi##/boot/efi} | sed "s|/|\\\|g")
1444- efibootmgr --create --write-signature --label $bootid \
1445- --disk $efi_disk --part $efi_part_num --loader $loader
1446- rc=$?
1447- [ "$rc" != "0" ] && { exit $rc; }
1448- else
1449- echo "skip EFI entry creation due to \"$no_nvram\" flag"
1450- fi
1451- else
1452- echo "No previous EFI grub entry found on ESP, use $grubcmd"
1453- if [ "${grubcmd}" = "${grubmulti}" ]; then
1454- $grubcmd
1455- else
1456- $grubcmd $target $efi_dir \
1457- --bootloader-id=$bootid --recheck $no_nvram
1458- fi
1459- fi
1460- [ -z "$grubpost" ] || $grubpost;' \
1461- -- "$grub_name" "$grub_target" "$nvram" "$os_variant" "$efi_disk" "$efi_part_num" </dev/null ||
1462- { error "failed to install grub!"; return 1; }
1463-
1464- chroot "$mp" sh -exc '
1465- echo "after grub-install efiboot settings"
1466- efibootmgr -v || echo "WARN: efibootmgr exited $?"
1467- ' -- </dev/null ||
1468- { error "failed to list efi boot entries!"; return 1; }
1469- else
1470- # Note: dpkg-reconfigure calls grub-install on ppc64
1471- # this means that using '--no-nvram' below ends up
1472- # failing very oddly. This is because grub's post-inst
1473- # runs grub-install with no target. That ends up
1474- # updating nvram badly, and then the grub-install would
1475- # not fix it because of the no-nvram there.
1476- debug 1 "curtin non-uefi: installing ${grub_name} to: ${grubdevs[*]}"
1477- chroot "$mp" env DEBIAN_FRONTEND=noninteractive sh -exc '
1478- pkg=$1; shift;
1479- bootid=$1; shift;
1480- bootver=$1; shift;
1481- grubpost=""
1482- case $bootid in
1483- debian|ubuntu)
1484- grubcmd="grub-install"
1485- dpkg-reconfigure "$pkg"
1486- update-grub
1487- ;;
1488- centos|redhat|rhel)
1489- case $bootver in
1490- 6) grubcmd="grub-install";;
1491- 7|8) grubcmd="grub2-install"
1492- grubpost="grub2-mkconfig -o /boot/grub2/grub.cfg";;
1493- *)
1494- echo "Unknown rhel_ver [$bootver]"
1495- exit 1
1496- esac
1497- ;;
1498- *)
1499- echo "Unsupported OS: $bootid"; 1>&2
1500- exit 1
1501- ;;
1502- esac
1503- for d in "$@"; do
1504- echo $grubcmd "$d";
1505- $grubcmd "$d" || exit; done
1506- [ -z "$grubpost" ] || $grubpost;' \
1507- -- "${grub_name}" "${os_variant}" "${rhel_ver}" "${grubdevs[@]}" </dev/null ||
1508- { error "failed to install grub!"; return 1; }
1509- fi
1510-
1511- if [ -n "${mp_efi}" ]; then
1512- umount "$mp_efi" ||
1513- { error "failed to unmount $mp_efi"; return 1; }
1514- fi
1515-
1516- return
1517-}
1518-
1519 # vi: ts=4 expandtab syntax=sh
1520diff --git a/helpers/install-grub b/helpers/install-grub
1521deleted file mode 100755
1522index e1bfb23..0000000
1523--- a/helpers/install-grub
1524+++ /dev/null
1525@@ -1,7 +0,0 @@
1526-#!/bin/bash
1527-# This file is part of curtin. See LICENSE file for copyright and license info.
1528-
1529-[ "${0%/*}" = "$0" ] && . ./common || . "${0%/*}/common"
1530-install_grub "$@"
1531-
1532-# vi: ts=4 expandtab syntax=sh
1533diff --git a/pylintrc b/pylintrc
1534index 167cff0..67a4e01 100644
1535--- a/pylintrc
1536+++ b/pylintrc
1537@@ -7,7 +7,7 @@ jobs=0
1538 # List of members which are set dynamically and missed by pylint inference
1539 # system, and so shouldn't trigger E1101 when accessed. Python regular
1540 # expressions are accepted.
1541-generated-members=DISTROS.*,parse_*,*_data
1542+generated-members=DISTROS\.
1543
1544 # List of module names for which member attributes should not be checked
1545 # (useful for modules/projects where namespaces are manipulated during runtime
1546diff --git a/requirements.txt b/requirements.txt
1547index 9066728..6afa3b8 100644
1548--- a/requirements.txt
1549+++ b/requirements.txt
1550@@ -2,3 +2,6 @@ pyyaml
1551 oauthlib
1552 # For validation of storate configuration format
1553 jsonschema
1554+# Dependency of jsonschema.
1555+# Version 0.16.0 is the latest version supporting Python < 3.5.
1556+pyrsistent==0.16.0
1557diff --git a/tests/data/multipath-nvme.txt b/tests/data/multipath-nvme.txt
1558new file mode 100644
1559index 0000000..30b59e4
1560--- /dev/null
1561+++ b/tests/data/multipath-nvme.txt
1562@@ -0,0 +1 @@
1563+name='nqn.1994-11.com.samsung:nvme:PM1725a:HHHL:S3RVNA0J300208 :nsid.1' multipath='eui.335256304a3002080025384100000001' sysfs='nvme0n1' paths='1'
1564diff --git a/tests/unittests/helpers.py b/tests/unittests/helpers.py
1565index 2f5e51a..64a79ca 100644
1566--- a/tests/unittests/helpers.py
1567+++ b/tests/unittests/helpers.py
1568@@ -2,11 +2,13 @@
1569
1570 import imp
1571 import importlib
1572+import logging
1573 import mock
1574 import os
1575 import random
1576 import shutil
1577 import string
1578+import sys
1579 import tempfile
1580 from unittest import TestCase, skipIf
1581 from contextlib import contextmanager
1582@@ -55,6 +57,7 @@ def skipUnlessJsonSchema():
1583 class CiTestCase(TestCase):
1584 """Common testing class which all curtin unit tests subclass."""
1585
1586+ with_logs = False
1587 allowed_subp = False
1588 SUBP_SHELL_TRUE = "shell=True"
1589
1590@@ -69,6 +72,21 @@ class CiTestCase(TestCase):
1591
1592 def setUp(self):
1593 super(CiTestCase, self).setUp()
1594+ if self.with_logs:
1595+ # Create a log handler so unit tests can search expected logs.
1596+ self.logger = logging.getLogger()
1597+ if sys.version_info[0] == 2:
1598+ import StringIO
1599+ self.logs = StringIO.StringIO()
1600+ else:
1601+ import io
1602+ self.logs = io.StringIO()
1603+ formatter = logging.Formatter('%(levelname)s: %(message)s')
1604+ handler = logging.StreamHandler(self.logs)
1605+ handler.setFormatter(formatter)
1606+ self.old_handlers = self.logger.handlers
1607+ self.logger.handlers = [handler]
1608+
1609 if self.allowed_subp is True:
1610 util.subp = _real_subp
1611 else:
1612diff --git a/tests/unittests/test_block.py b/tests/unittests/test_block.py
1613index c62c153..78e331d 100644
1614--- a/tests/unittests/test_block.py
1615+++ b/tests/unittests/test_block.py
1616@@ -179,6 +179,18 @@ class TestBlock(CiTestCase):
1617 byid_path = block.disk_to_byid_path('/dev/sdb')
1618 self.assertEqual(mapping.get('/dev/sdb'), byid_path)
1619
1620+ @mock.patch("curtin.block.os.path.exists")
1621+ def test__get_dev_disk_by_prefix_returns_empty_dict(self, m_exists):
1622+ """ _get_disk_by_prefix returns empty dict prefix dir does not exit """
1623+ m_exists.return_value = False
1624+ self.assertEqual({}, block._get_dev_disk_by_prefix("/dev/disk/by-id"))
1625+
1626+ @mock.patch("curtin.block.os.path.exists")
1627+ def test_disk_to_byid_returns_none_if_disk_byid_missing(self, m_exists):
1628+ """ disk_to_byid path returns None if /dev/disk/by-id is missing """
1629+ m_exists.return_value = False
1630+ self.assertEqual(None, block.disk_to_byid_path('/dev/sdb'))
1631+
1632
1633 class TestSysBlockPath(CiTestCase):
1634 @mock.patch("curtin.block.get_blockdev_for_partition")
1635diff --git a/tests/unittests/test_block_multipath.py b/tests/unittests/test_block_multipath.py
1636index 2101eae..96cbcba 100644
1637--- a/tests/unittests/test_block_multipath.py
1638+++ b/tests/unittests/test_block_multipath.py
1639@@ -39,6 +39,20 @@ class TestMultipath(CiTestCase):
1640 multipath.show_maps())
1641 self.m_subp.assert_called_with(expected, capture=True)
1642
1643+ def test_show_maps_nvme(self):
1644+ """verify show_maps extracts mulitpath map data correctly."""
1645+ NVME_MP = multipath.util.load_file('tests/data/multipath-nvme.txt')
1646+ self.m_subp.return_value = (NVME_MP, "")
1647+ expected = ['multipathd', 'show', 'maps', 'raw', 'format',
1648+ multipath.SHOW_MAPS_FMT]
1649+ self.assertEqual([
1650+ {'name':
1651+ ('nqn.1994-11.com.samsung:nvme:PM1725a:HHHL:S3RVNA0J300208 '
1652+ ':nsid.1'),
1653+ 'multipath': 'eui.335256304a3002080025384100000001',
1654+ 'sysfs': 'nvme0n1', 'paths': '1'}], multipath.show_maps())
1655+ self.m_subp.assert_called_with(expected, capture=True)
1656+
1657 def test_is_mpath_device_true(self):
1658 """is_mpath_device returns true if dev DM_UUID starts with mpath-"""
1659 self.m_udev.udevadm_info.return_value = {'DM_UUID': 'mpath-mpatha-foo'}
1660diff --git a/tests/unittests/test_commands_block_meta.py b/tests/unittests/test_commands_block_meta.py
1661index b768cdc..d954296 100644
1662--- a/tests/unittests/test_commands_block_meta.py
1663+++ b/tests/unittests/test_commands_block_meta.py
1664@@ -1779,6 +1779,7 @@ class TestRaidHandler(CiTestCase):
1665 def setUp(self):
1666 super(TestRaidHandler, self).setUp()
1667
1668+ orig_md_path = block_meta.block.md_path
1669 basepath = 'curtin.commands.block_meta.'
1670 self.add_patch(basepath + 'get_path_to_storage_volume', 'm_getpath')
1671 self.add_patch(basepath + 'util', 'm_util')
1672@@ -1787,6 +1788,10 @@ class TestRaidHandler(CiTestCase):
1673 self.add_patch(basepath + 'block', 'm_block')
1674 self.add_patch(basepath + 'udevadm_settle', 'm_uset')
1675
1676+ # The behavior of this function is being directly tested in
1677+ # these tests, so we can't mock it
1678+ self.m_block.md_path = orig_md_path
1679+
1680 self.target = "my_target"
1681 self.config = {
1682 'storage': {
1683@@ -1850,12 +1855,40 @@ class TestRaidHandler(CiTestCase):
1684 block_meta.extract_storage_ordered_dict(self.config))
1685 self.m_util.load_command_environment.return_value = {'fstab': None}
1686
1687+ def test_md_name(self):
1688+ input_to_result = [
1689+ ('md1', '/dev/md1'),
1690+ ('os-raid1', '/dev/md/os-raid1'),
1691+ ('md/os-raid1', '/dev/md/os-raid1'),
1692+ ('/dev/md1', '/dev/md1'),
1693+ ('/dev/md/os-raid1', '/dev/md/os-raid1'),
1694+ ('bad/path', ValueError)
1695+ ]
1696+ for index, test in enumerate(input_to_result):
1697+ param, expected = test
1698+ self.storage_config['mddevice']['name'] = param
1699+ try:
1700+ block_meta.raid_handler(self.storage_config['mddevice'],
1701+ self.storage_config)
1702+ except ValueError:
1703+ if param in ['bad/path']:
1704+ continue
1705+ else:
1706+ raise
1707+
1708+ actual = self.m_mdadm.mdadm_create.call_args_list[index][0][0]
1709+ self.assertEqual(
1710+ expected,
1711+ actual,
1712+ "Expected {} to result in mdadm being called with {}. "
1713+ "mdadm instead called with {}".format(param, expected, actual)
1714+ )
1715+
1716 def test_raid_handler(self):
1717 """ raid_handler creates raid device. """
1718 devices = [self.random_string(), self.random_string(),
1719 self.random_string()]
1720 md_devname = '/dev/' + self.storage_config['mddevice']['name']
1721- self.m_block.dev_path.return_value = '/dev/md0'
1722 self.m_getpath.side_effect = iter(devices)
1723 block_meta.raid_handler(self.storage_config['mddevice'],
1724 self.storage_config)
1725@@ -1868,7 +1901,6 @@ class TestRaidHandler(CiTestCase):
1726
1727 devices = [self.random_string(), self.random_string(),
1728 self.random_string()]
1729- self.m_block.dev_path.return_value = '/dev/md0'
1730 self.m_getpath.side_effect = iter(devices)
1731 m_verify.return_value = True
1732 self.storage_config['mddevice']['preserve'] = True
1733@@ -1882,7 +1914,6 @@ class TestRaidHandler(CiTestCase):
1734 devices = [self.random_string(), self.random_string(),
1735 self.random_string()]
1736 md_devname = '/dev/' + self.storage_config['mddevice']['name']
1737- self.m_block.dev_path.return_value = '/dev/md0'
1738 self.m_getpath.side_effect = iter(devices)
1739 self.m_mdadm.md_check.return_value = True
1740 self.storage_config['mddevice']['preserve'] = True
1741@@ -1898,7 +1929,6 @@ class TestRaidHandler(CiTestCase):
1742 devices = [self.random_string(), self.random_string(),
1743 self.random_string()]
1744 md_devname = '/dev/' + self.storage_config['mddevice']['name']
1745- self.m_block.dev_path.return_value = '/dev/md0'
1746 self.m_getpath.side_effect = iter(devices)
1747 self.m_mdadm.md_check.side_effect = iter([False, True])
1748 self.storage_config['mddevice']['preserve'] = True
1749@@ -1916,7 +1946,6 @@ class TestRaidHandler(CiTestCase):
1750 devices = [self.random_string(), self.random_string(),
1751 self.random_string()]
1752 md_devname = '/dev/' + self.storage_config['mddevice']['name']
1753- self.m_block.dev_path.return_value = '/dev/md0'
1754 self.m_getpath.side_effect = iter(devices)
1755 self.m_mdadm.md_check.side_effect = iter([False, False])
1756 self.storage_config['mddevice']['preserve'] = True
1757@@ -2557,4 +2586,115 @@ class TestVerifyPtableFlag(CiTestCase):
1758 sfdisk_info=self.sfdisk_info_dos)
1759
1760
1761+class TestGetDevicePathsFromStorageConfig(CiTestCase):
1762+
1763+ def setUp(self):
1764+ super(TestGetDevicePathsFromStorageConfig, self).setUp()
1765+ base = 'curtin.commands.block_meta.'
1766+ self.add_patch(base + 'get_path_to_storage_volume', 'mock_getpath')
1767+ self.add_patch(base + 'os.path.exists', 'm_exists')
1768+ self.m_exists.return_value = True
1769+ self.mock_getpath.side_effect = self._getpath
1770+ self.prefix = '/test/dev/'
1771+ self.config = {
1772+ 'storage': {
1773+ 'version': 1,
1774+ 'config': [
1775+ {'id': 'sda',
1776+ 'type': 'disk',
1777+ 'name': 'main_disk',
1778+ 'ptable': 'gpt',
1779+ 'serial': 'disk-a'},
1780+ {'id': 'disk-sda-part-1',
1781+ 'type': 'partition',
1782+ 'device': 'sda',
1783+ 'name': 'bios_boot',
1784+ 'number': 1,
1785+ 'size': '1M',
1786+ 'flag': 'bios_grub'},
1787+ {'id': 'disk-sda-part-2',
1788+ 'type': 'partition',
1789+ 'device': 'sda',
1790+ 'number': 2,
1791+ 'size': '5GB'},
1792+ ],
1793+ }
1794+ }
1795+ self.disk1 = self.config['storage']['config'][0]
1796+ self.part1 = self.config['storage']['config'][1]
1797+ self.part2 = self.config['storage']['config'][2]
1798+ self.sconfig = self._sconfig(self.config)
1799+
1800+ def _sconfig(self, config):
1801+ return block_meta.extract_storage_ordered_dict(config)
1802+
1803+ def _getpath(self, item_id, _sconfig):
1804+ return self.prefix + item_id
1805+
1806+ def test_devpath_selects_disks_partitions_with_wipe_setting(self):
1807+ self.disk1['wipe'] = 'superblock'
1808+ self.part1['wipe'] = 'superblock'
1809+ self.sconfig = self._sconfig(self.config)
1810+
1811+ expected_devpaths = [
1812+ self.prefix + self.disk1['id'], self.prefix + self.part1['id']]
1813+ result = block_meta.get_device_paths_from_storage_config(self.sconfig)
1814+ self.assertEqual(sorted(expected_devpaths), sorted(result))
1815+ self.assertEqual([
1816+ call(self.disk1['id'], self.sconfig),
1817+ call(self.part1['id'], self.sconfig)],
1818+ self.mock_getpath.call_args_list)
1819+ self.assertEqual(
1820+ sorted([call(devpath) for devpath in expected_devpaths]),
1821+ sorted(self.m_exists.call_args_list))
1822+
1823+ def test_devpath_raises_exception_if_wipe_and_preserve_set(self):
1824+ self.disk1['wipe'] = 'superblock'
1825+ self.disk1['preserve'] = True
1826+ self.sconfig = self._sconfig(self.config)
1827+
1828+ with self.assertRaises(RuntimeError):
1829+ block_meta.get_device_paths_from_storage_config(self.sconfig)
1830+ self.assertEqual([], self.mock_getpath.call_args_list)
1831+ self.assertEqual([], self.m_exists.call_args_list)
1832+
1833+ def test_devpath_check_boolean_value_if_wipe_and_preserve_set(self):
1834+ self.disk1['wipe'] = 'superblock'
1835+ self.disk1['preserve'] = False
1836+ self.sconfig = self._sconfig(self.config)
1837+
1838+ expected_devpaths = [self.prefix + self.disk1['id']]
1839+ result = block_meta.get_device_paths_from_storage_config(self.sconfig)
1840+ self.assertEqual(expected_devpaths, result)
1841+ self.assertEqual(
1842+ [call(self.disk1['id'], self.sconfig)],
1843+ self.mock_getpath.call_args_list)
1844+ self.assertEqual(
1845+ sorted([call(devpath) for devpath in expected_devpaths]),
1846+ sorted(self.m_exists.call_args_list))
1847+
1848+ def test_devpath_check_preserved_devices_skipped(self):
1849+ self.disk1['preserve'] = True
1850+ self.sconfig = self._sconfig(self.config)
1851+
1852+ result = block_meta.get_device_paths_from_storage_config(self.sconfig)
1853+ self.assertEqual([], result)
1854+ self.assertEqual([], self.mock_getpath.call_args_list)
1855+ self.assertEqual([], self.m_exists.call_args_list)
1856+
1857+ def test_devpath_check_missing_path_devices_skipped(self):
1858+ self.disk1['wipe'] = 'superblock'
1859+ self.sconfig = self._sconfig(self.config)
1860+
1861+ self.m_exists.return_value = False
1862+ result = block_meta.get_device_paths_from_storage_config(self.sconfig)
1863+ self.assertEqual([], result)
1864+ self.assertEqual(
1865+ [call(self.disk1['id'], self.sconfig)],
1866+ self.mock_getpath.call_args_list)
1867+ self.assertEqual(
1868+ [call(self.prefix + self.disk1['id'])],
1869+ self.m_exists.call_args_list)
1870+
1871+
1872 # vi: ts=4 expandtab syntax=python
1873diff --git a/tests/unittests/test_curthooks.py b/tests/unittests/test_curthooks.py
1874index 2349456..e5fead3 100644
1875--- a/tests/unittests/test_curthooks.py
1876+++ b/tests/unittests/test_curthooks.py
1877@@ -1,5 +1,6 @@
1878 # This file is part of curtin. See LICENSE file for copyright and license info.
1879
1880+import copy
1881 import os
1882 from mock import call, patch
1883 import textwrap
1884@@ -10,7 +11,7 @@ from curtin import distro
1885 from curtin import util
1886 from curtin import config
1887 from curtin.reporter import events
1888-from .helpers import CiTestCase, dir2dict, populate_dir
1889+from .helpers import CiTestCase, dir2dict, populate_dir, random
1890
1891
1892 class TestGetFlashKernelPkgs(CiTestCase):
1893@@ -531,12 +532,55 @@ class TestSetupZipl(CiTestCase):
1894 content)
1895
1896
1897+class EfiOutput(object):
1898+
1899+ def __init__(self, current=None, order=None, entries=None):
1900+ self.entries = {}
1901+ if entries:
1902+ for entry in entries:
1903+ self.entries.update(entry)
1904+ self.current = current
1905+ self.order = order
1906+ if not order and self.entries:
1907+ self.order = sorted(self.entries.keys())
1908+
1909+ def add_entry(self, bootnum=None, name=None, path=None, current=False):
1910+ if not bootnum:
1911+ bootnum = "%04x" % random.randint(0, 1000)
1912+ if not name:
1913+ name = CiTestCase.random_string()
1914+ if not path:
1915+ path = ''
1916+ if bootnum not in self.entries:
1917+ self.entries[bootnum] = {'name': name, 'path': path}
1918+ if not self.order:
1919+ self.order = []
1920+ self.order.append(bootnum)
1921+ if current:
1922+ self.current = bootnum
1923+
1924+ def set_order(self, new_order):
1925+ self.order = new_order
1926+
1927+ def as_dict(self):
1928+ output = {}
1929+ if self.current:
1930+ output['current'] = self.current
1931+ if self.order:
1932+ output['order'] = self.order
1933+ output['entries'] = self.entries
1934+ return output
1935+
1936+
1937 class TestSetupGrub(CiTestCase):
1938
1939+ with_logs = True
1940+
1941 def setUp(self):
1942 super(TestSetupGrub, self).setUp()
1943 self.target = self.tmp_dir()
1944 self.distro_family = distro.DISTROS.debian
1945+ self.variant = 'ubuntu'
1946 self.add_patch('curtin.distro.lsb_release', 'mock_lsb_release')
1947 self.mock_lsb_release.return_value = {'codename': 'xenial'}
1948 self.add_patch('curtin.util.is_uefi_bootable',
1949@@ -556,7 +600,8 @@ class TestSetupGrub(CiTestCase):
1950 updated_cfg = {
1951 'install_devices': ['/dev/vdb']
1952 }
1953- curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family)
1954+ curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family,
1955+ variant=self.variant)
1956 self.m_install_grub.assert_called_with(
1957 ['/dev/vdb'], self.target, uefi=False, grubcfg=updated_cfg)
1958
1959@@ -588,7 +633,8 @@ class TestSetupGrub(CiTestCase):
1960 },
1961 }
1962 m_exists.return_value = True
1963- curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family)
1964+ curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family,
1965+ variant=self.variant)
1966 self.m_install_grub.assert_called_with(
1967 ['/dev/vdb'], self.target, uefi=False,
1968 grubcfg={'install_devices': ['/dev/vdb']})
1969@@ -638,7 +684,8 @@ class TestSetupGrub(CiTestCase):
1970 }
1971 m_exists.return_value = True
1972 m_is_valid_device.side_effect = (False, True, False, True)
1973- curthooks.setup_grub(cfg, self.target, osfamily=distro.DISTROS.redhat)
1974+ curthooks.setup_grub(cfg, self.target, osfamily=distro.DISTROS.redhat,
1975+ variant='centos')
1976 self.m_install_grub.assert_called_with(
1977 ['/dev/vdb1'], self.target, uefi=True,
1978 grubcfg={'update_nvram': False, 'install_devices': ['/dev/vdb1']}
1979@@ -650,7 +697,8 @@ class TestSetupGrub(CiTestCase):
1980 'install_devices': None,
1981 },
1982 }
1983- curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family)
1984+ curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family,
1985+ variant=self.variant)
1986 self.m_install_grub.assert_called_with(
1987 ['none'], self.target, uefi=False,
1988 grubcfg={'install_devices': None}
1989@@ -681,7 +729,8 @@ class TestSetupGrub(CiTestCase):
1990 }
1991 }
1992 }
1993- curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family)
1994+ curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family,
1995+ variant=self.variant)
1996 self.m_install_grub.assert_called_with(
1997 ['/dev/vdb'], self.target, uefi=True, grubcfg=cfg.get('grub')
1998 )
1999@@ -721,7 +770,8 @@ class TestSetupGrub(CiTestCase):
2000 }
2001 }
2002 self.mock_haspkg.return_value = False
2003- curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family)
2004+ curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family,
2005+ variant=self.variant)
2006
2007 expected_calls = [
2008 call(['efibootmgr', '-B', '-b', '0001'],
2009@@ -762,70 +812,304 @@ class TestSetupGrub(CiTestCase):
2010 }
2011 }
2012 self.mock_haspkg.return_value = False
2013- curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family)
2014+ curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family,
2015+ variant=self.variant)
2016 self.assertEquals([
2017 call(['efibootmgr', '-o', '0001,0000'], target=self.target)],
2018 self.mock_subp.call_args_list)
2019
2020+ @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
2021+ def test_grub_install_uefi_reorders_no_current_new_entry(self):
2022+ self.add_patch('curtin.distro.install_packages', 'mock_install')
2023+ self.add_patch('curtin.distro.has_pkg_available', 'mock_haspkg')
2024+ self.add_patch('curtin.util.get_efibootmgr', 'mock_efibootmgr')
2025+ self.mock_is_uefi_bootable.return_value = True
2026+ cfg = {
2027+ 'grub': {
2028+ 'install_devices': ['/dev/vdb'],
2029+ 'update_nvram': True,
2030+ 'remove_old_uefi_loaders': False,
2031+ 'reorder_uefi': True,
2032+ },
2033+ }
2034+
2035+ # Single existing entry 0001
2036+ efi_orig = EfiOutput()
2037+ efi_orig.add_entry(bootnum='0001', name='centos')
2038
2039-class TestUefiRemoveDuplicateEntries(CiTestCase):
2040+ # After install add a second entry, 0000 to the front of order
2041+ efi_post = copy.deepcopy(efi_orig)
2042+ efi_post.add_entry(bootnum='0000', name='ubuntu')
2043+ efi_post.set_order(['0000', '0001'])
2044
2045- def setUp(self):
2046- super(TestUefiRemoveDuplicateEntries, self).setUp()
2047- self.target = self.tmp_dir()
2048- self.add_patch('curtin.util.get_efibootmgr', 'm_efibootmgr')
2049- self.add_patch('curtin.util.subp', 'm_subp')
2050+ # After reorder we should have the target install first
2051+ efi_final = copy.deepcopy(efi_post)
2052+
2053+ self.mock_efibootmgr.side_effect = iter([
2054+ efi_orig.as_dict(), # collect original order before install
2055+ efi_orig.as_dict(), # remove_old_loaders query (no change)
2056+ efi_post.as_dict(), # efi table after grub install, (changed)
2057+ efi_final.as_dict(), # remove duplicates checks and finds reorder
2058+ # has changed
2059+ ])
2060+ self.mock_haspkg.return_value = False
2061+ curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family,
2062+ variant=self.variant)
2063+ logs = self.logs.getvalue()
2064+ print(logs)
2065+ self.assertEquals([], self.mock_subp.call_args_list)
2066+ self.assertIn("Using fallback UEFI reordering:", logs)
2067+ self.assertIn("missing 'BootCurrent' value", logs)
2068+ self.assertIn("Found new boot entries: ['0000']", logs)
2069
2070 @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
2071- def test_uefi_remove_duplicate_entries(self):
2072+ def test_grub_install_uefi_reorders_no_curr_same_size_order_no_match(self):
2073+ self.add_patch('curtin.distro.install_packages', 'mock_install')
2074+ self.add_patch('curtin.distro.has_pkg_available', 'mock_haspkg')
2075+ self.add_patch('curtin.util.get_efibootmgr', 'mock_efibootmgr')
2076+ self.add_patch('curtin.commands.curthooks.uefi_remove_old_loaders',
2077+ 'mock_remove_old_loaders')
2078+ self.mock_is_uefi_bootable.return_value = True
2079 cfg = {
2080 'grub': {
2081 'install_devices': ['/dev/vdb'],
2082 'update_nvram': True,
2083+ 'remove_old_uefi_loaders': False,
2084+ 'reorder_uefi': True,
2085 },
2086 }
2087- self.m_efibootmgr.return_value = {
2088- 'current': '0000',
2089- 'entries': {
2090- '0000': {
2091- 'name': 'ubuntu',
2092- 'path': (
2093- 'HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),
2094- },
2095- '0001': {
2096- 'name': 'ubuntu',
2097- 'path': (
2098- 'HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),
2099- },
2100- '0002': { # Is not a duplicate because of unique path
2101- 'name': 'ubuntu',
2102- 'path': (
2103- 'HD(2,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),
2104- },
2105- '0003': { # Is duplicate of 0000
2106- 'name': 'ubuntu',
2107- 'path': (
2108- 'HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),
2109- },
2110- }
2111+
2112+ # Existing Custom Ubuntu, usb and cd/dvd entry, booting Ubuntu
2113+ efi_orig = EfiOutput()
2114+ efi_orig.add_entry(bootnum='0001', name='Ubuntu Deluxe Edition')
2115+ efi_orig.add_entry(bootnum='0002', name='USB Device')
2116+ efi_orig.add_entry(bootnum='0000', name='CD/DVD')
2117+ efi_orig.set_order(['0001', '0002', '0000'])
2118+
2119+ # after install existing ubuntu entry is reused, no change in order
2120+ efi_post = efi_orig
2121+
2122+ # after reorder, no change is made due to the installed distro variant
2123+ # string 'ubuntu' is not found in the boot entries so we retain the
2124+ # original efi order.
2125+ efi_final = efi_post
2126+
2127+ self.mock_efibootmgr.side_effect = iter([
2128+ efi_orig.as_dict(), # collect original order before install
2129+ efi_orig.as_dict(), # remove_old_loaders query
2130+ efi_post.as_dict(), # reorder entries queries post install
2131+ efi_final.as_dict(), # remove duplicates checks and finds reorder
2132+ ])
2133+
2134+ self.mock_haspkg.return_value = False
2135+ curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family,
2136+ variant=self.variant)
2137+
2138+ logs = self.logs.getvalue()
2139+ print(logs)
2140+ self.assertEquals([], self.mock_subp.call_args_list)
2141+ self.assertIn("Using fallback UEFI reordering:", logs)
2142+ self.assertIn("missing 'BootCurrent' value", logs)
2143+ self.assertIn("Current and Previous bootorders match", logs)
2144+ self.assertIn("Looking for installed entry variant=", logs)
2145+ self.assertIn("Did not find an entry with variant=", logs)
2146+ self.assertIn("No changes to boot order.", logs)
2147+
2148+ @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
2149+ def test_grub_install_uefi_reorders_force_fallback(self):
2150+ self.add_patch('curtin.distro.install_packages', 'mock_install')
2151+ self.add_patch('curtin.distro.has_pkg_available', 'mock_haspkg')
2152+ self.add_patch('curtin.util.get_efibootmgr', 'mock_efibootmgr')
2153+ self.mock_is_uefi_bootable.return_value = True
2154+ cfg = {
2155+ 'grub': {
2156+ 'install_devices': ['/dev/vdb'],
2157+ 'update_nvram': True,
2158+ 'remove_old_uefi_loaders': True,
2159+ 'reorder_uefi': True,
2160+ 'reorder_uefi_force_fallback': True,
2161+ },
2162 }
2163+ # Single existing entry 0001 and set as current, which should avoid
2164+ # any fallback logic, but we're forcing fallback pack via config
2165+ efi_orig = EfiOutput()
2166+ efi_orig.add_entry(bootnum='0001', name='PXE', current=True)
2167+ print(efi_orig.as_dict())
2168+
2169+ # After install add a second entry, 0000 to the front of order
2170+ efi_post = copy.deepcopy(efi_orig)
2171+ efi_post.add_entry(bootnum='0000', name='ubuntu')
2172+ efi_post.set_order(['0000', '0001'])
2173+ print(efi_orig.as_dict())
2174+
2175+ # After reorder we should have the original boot entry 0001 as first
2176+ efi_final = copy.deepcopy(efi_post)
2177+ efi_final.set_order(['0001', '0000'])
2178+
2179+ self.mock_efibootmgr.side_effect = iter([
2180+ efi_orig.as_dict(), # collect original order before install
2181+ efi_orig.as_dict(), # remove_old_loaders query (no change)
2182+ efi_post.as_dict(), # efi table after grub install, (changed)
2183+ efi_final.as_dict(), # remove duplicates checks and finds reorder
2184+ # has changed
2185+ ])
2186
2187- curthooks.uefi_remove_duplicate_entries(cfg, self.target)
2188+ self.mock_haspkg.return_value = False
2189+ curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family,
2190+ variant=self.variant)
2191+ logs = self.logs.getvalue()
2192+ print(logs)
2193 self.assertEquals([
2194- call(['efibootmgr', '--bootnum=0001', '--delete-bootnum'],
2195- target=self.target),
2196- call(['efibootmgr', '--bootnum=0003', '--delete-bootnum'],
2197- target=self.target)
2198- ], self.m_subp.call_args_list)
2199+ call(['efibootmgr', '-o', '0001,0000'], target=self.target)],
2200+ self.mock_subp.call_args_list)
2201+ self.assertIn("Using fallback UEFI reordering:", logs)
2202+ self.assertIn("config 'reorder_uefi_force_fallback' is True", logs)
2203
2204 @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
2205- def test_uefi_remove_duplicate_entries_no_change(self):
2206+ def test_grub_install_uefi_reorders_network_first(self):
2207+ self.add_patch('curtin.distro.install_packages', 'mock_install')
2208+ self.add_patch('curtin.distro.has_pkg_available', 'mock_haspkg')
2209+ self.add_patch('curtin.util.get_efibootmgr', 'mock_efibootmgr')
2210+ self.mock_is_uefi_bootable.return_value = True
2211 cfg = {
2212 'grub': {
2213 'install_devices': ['/dev/vdb'],
2214 'update_nvram': True,
2215+ 'remove_old_uefi_loaders': True,
2216+ 'reorder_uefi': True,
2217+ },
2218+ }
2219+
2220+ # Existing ubuntu, usb and cd/dvd entry, booting ubuntu
2221+ efi_orig = EfiOutput()
2222+ efi_orig.add_entry(bootnum='0001', name='centos')
2223+ efi_orig.add_entry(bootnum='0002', name='Network')
2224+ efi_orig.add_entry(bootnum='0003', name='PXE')
2225+ efi_orig.add_entry(bootnum='0004', name='LAN')
2226+ efi_orig.add_entry(bootnum='0000', name='CD/DVD')
2227+ efi_orig.set_order(['0001', '0002', '0003', '0004', '0000'])
2228+ print(efi_orig.as_dict())
2229+
2230+ # after install we add an ubuntu entry, and grub puts it first
2231+ efi_post = copy.deepcopy(efi_orig)
2232+ efi_post.add_entry(bootnum='0007', name='ubuntu')
2233+ efi_post.set_order(['0007'] + efi_orig.order)
2234+ print(efi_post.as_dict())
2235+
2236+ # reorder must place all network devices first, then ubuntu, and others
2237+ efi_final = copy.deepcopy(efi_post)
2238+ expected_order = ['0002', '0003', '0004', '0007', '0001', '0000']
2239+ efi_final.set_order(expected_order)
2240+
2241+ self.mock_efibootmgr.side_effect = iter([
2242+ efi_orig.as_dict(), # collect original order before install
2243+ efi_orig.as_dict(), # remove_old_loaders query
2244+ efi_post.as_dict(), # reorder entries queries post install
2245+ efi_final.as_dict(), # remove duplicates checks and finds reorder
2246+ ])
2247+ self.mock_haspkg.return_value = False
2248+ curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family,
2249+ variant=self.variant)
2250+ logs = self.logs.getvalue()
2251+ print(logs)
2252+ print('Number of bootmgr calls: %s' % self.mock_efibootmgr.call_count)
2253+ self.assertEquals([
2254+ call(['efibootmgr', '-o', '%s' % (",".join(expected_order))],
2255+ target=self.target)],
2256+ self.mock_subp.call_args_list)
2257+ self.assertIn("Using fallback UEFI reordering:", logs)
2258+ self.assertIn("missing 'BootCurrent' value", logs)
2259+ self.assertIn("Looking for installed entry variant=", logs)
2260+ self.assertIn("found netboot entries: ['0002', '0003', '0004']", logs)
2261+ self.assertIn("found other entries: ['0001', '0000']", logs)
2262+ self.assertIn("found target entry: ['0007']", logs)
2263+
2264+
2265+class TestUefiRemoveDuplicateEntries(CiTestCase):
2266+
2267+ efibootmgr_output = {
2268+ 'current': '0000',
2269+ 'entries': {
2270+ '0000': {
2271+ 'name': 'ubuntu',
2272+ 'path': (
2273+ 'HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),
2274+ },
2275+ '0001': { # Is duplicate of 0000
2276+ 'name': 'ubuntu',
2277+ 'path': (
2278+ 'HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),
2279+ },
2280+ '0002': { # Is not a duplicate because of unique path
2281+ 'name': 'ubuntu',
2282+ 'path': (
2283+ 'HD(2,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),
2284+ },
2285+ '0003': { # Is duplicate of 0000
2286+ 'name': 'ubuntu',
2287+ 'path': (
2288+ 'HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),
2289 },
2290 }
2291+ }
2292+
2293+ def setUp(self):
2294+ super(TestUefiRemoveDuplicateEntries, self).setUp()
2295+ self.target = self.tmp_dir()
2296+ self.add_patch('curtin.util.get_efibootmgr', 'm_efibootmgr')
2297+ self.add_patch('curtin.util.subp', 'm_subp')
2298+ self.m_efibootmgr.return_value = copy.deepcopy(self.efibootmgr_output)
2299+
2300+ @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
2301+ def test_uefi_remove_duplicate_entries(self):
2302+ grubcfg = {}
2303+ curthooks.uefi_remove_duplicate_entries(grubcfg, self.target)
2304+ self.assertEquals([
2305+ call(['efibootmgr', '--bootnum=0001', '--delete-bootnum'],
2306+ target=self.target),
2307+ call(['efibootmgr', '--bootnum=0003', '--delete-bootnum'],
2308+ target=self.target)
2309+ ], self.m_subp.call_args_list)
2310+
2311+ @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
2312+ def test_uefi_remove_duplicate_entries_no_bootcurrent(self):
2313+ grubcfg = {}
2314+ efiout = copy.deepcopy(self.efibootmgr_output)
2315+ del efiout['current']
2316+ self.m_efibootmgr.return_value = efiout
2317+ curthooks.uefi_remove_duplicate_entries(grubcfg, self.target)
2318+ self.assertEquals([
2319+ call(['efibootmgr', '--bootnum=0001', '--delete-bootnum'],
2320+ target=self.target),
2321+ call(['efibootmgr', '--bootnum=0003', '--delete-bootnum'],
2322+ target=self.target)
2323+ ], self.m_subp.call_args_list)
2324+
2325+ @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
2326+ def test_uefi_remove_duplicate_entries_disabled(self):
2327+ grubcfg = {
2328+ 'remove_duplicate_entries': False,
2329+ }
2330+ curthooks.uefi_remove_duplicate_entries(grubcfg, self.target)
2331+ self.assertEquals([], self.m_subp.call_args_list)
2332+
2333+ @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
2334+ def test_uefi_remove_duplicate_entries_skip_bootcurrent(self):
2335+ grubcfg = {}
2336+ efiout = copy.deepcopy(self.efibootmgr_output)
2337+ efiout['current'] = '0003'
2338+ self.m_efibootmgr.return_value = efiout
2339+ curthooks.uefi_remove_duplicate_entries(grubcfg, self.target)
2340+ self.assertEquals([
2341+ call(['efibootmgr', '--bootnum=0000', '--delete-bootnum'],
2342+ target=self.target),
2343+ call(['efibootmgr', '--bootnum=0001', '--delete-bootnum'],
2344+ target=self.target),
2345+ ], self.m_subp.call_args_list)
2346+
2347+ @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
2348+ def test_uefi_remove_duplicate_entries_no_change(self):
2349+ grubcfg = {}
2350 self.m_efibootmgr.return_value = {
2351 'current': '0000',
2352 'entries': {
2353@@ -846,8 +1130,7 @@ class TestUefiRemoveDuplicateEntries(CiTestCase):
2354 },
2355 }
2356 }
2357-
2358- curthooks.uefi_remove_duplicate_entries(cfg, self.target)
2359+ curthooks.uefi_remove_duplicate_entries(grubcfg, self.target)
2360 self.assertEquals([], self.m_subp.call_args_list)
2361
2362
2363@@ -1110,9 +1393,8 @@ class TestDetectRequiredPackages(CiTestCase):
2364 {'type': 'static', 'address': '2001:1::1/64'}]}},
2365 2: {
2366 'openvswitch': {
2367- 'openvswitch': {
2368- 'bridges': {
2369- 'br-int': {'ports': {'eth15': {'tag': 2}}}}}},
2370+ 'bridges': {
2371+ 'br-int': {'openvswitch': {}}}},
2372 'vlans': {
2373 'vlans': {
2374 'en-intra': {'id': 1, 'link': 'eno1', 'dhcp4': 'yes'},
2375@@ -1245,7 +1527,7 @@ class TestDetectRequiredPackages(CiTestCase):
2376 ({'network': {
2377 'version': 2,
2378 'items': ('openvswitch',)}},
2379- ('openvswitch-switch', )),
2380+ ('bridge-utils', 'openvswitch-switch', )),
2381 ))
2382
2383 def test_network_v2_detect_renderers(self):
2384@@ -1726,6 +2008,12 @@ class TestUefiFindGrubDeviceIds(CiTestCase):
2385 'fstype': 'fat32',
2386 },
2387 {
2388+ 'id': 'vdb-part2-swap_mount',
2389+ 'type': 'mount',
2390+ 'device': 'vdb-part2-swap_format',
2391+ 'options': '',
2392+ },
2393+ {
2394 'id': 'vdb-part1_mount',
2395 'type': 'mount',
2396 'device': 'vdb-part1_format',
2397diff --git a/tests/unittests/test_distro.py b/tests/unittests/test_distro.py
2398index eb62dd8..380680c 100644
2399--- a/tests/unittests/test_distro.py
2400+++ b/tests/unittests/test_distro.py
2401@@ -65,7 +65,7 @@ class TestParseDpkgVersion(CiTestCase):
2402 def test_simple_native_package_version(self):
2403 """dpkg versions must have a -. If not present expect value error."""
2404 self.assertEqual(
2405- {'major': 2, 'minor': 28, 'micro': 0, 'extra': None,
2406+ {'epoch': 0, 'major': 2, 'minor': 28, 'micro': 0, 'extra': None,
2407 'raw': '2.28', 'upstream': '2.28', 'name': 'germinate',
2408 'semantic_version': 22800},
2409 distro.parse_dpkg_version('2.28', name='germinate'))
2410@@ -73,7 +73,7 @@ class TestParseDpkgVersion(CiTestCase):
2411 def test_complex_native_package_version(self):
2412 dver = '1.0.106ubuntu2+really1.0.97ubuntu1'
2413 self.assertEqual(
2414- {'major': 1, 'minor': 0, 'micro': 106,
2415+ {'epoch': 0, 'major': 1, 'minor': 0, 'micro': 106,
2416 'extra': 'ubuntu2+really1.0.97ubuntu1',
2417 'raw': dver, 'upstream': dver, 'name': 'debootstrap',
2418 'semantic_version': 100106},
2419@@ -82,14 +82,14 @@ class TestParseDpkgVersion(CiTestCase):
2420
2421 def test_simple_valid(self):
2422 self.assertEqual(
2423- {'major': 1, 'minor': 2, 'micro': 3, 'extra': None,
2424+ {'epoch': 0, 'major': 1, 'minor': 2, 'micro': 3, 'extra': None,
2425 'raw': '1.2.3-0', 'upstream': '1.2.3', 'name': 'foo',
2426 'semantic_version': 10203},
2427 distro.parse_dpkg_version('1.2.3-0', name='foo'))
2428
2429 def test_simple_valid_with_semx(self):
2430 self.assertEqual(
2431- {'major': 1, 'minor': 2, 'micro': 3, 'extra': None,
2432+ {'epoch': 0, 'major': 1, 'minor': 2, 'micro': 3, 'extra': None,
2433 'raw': '1.2.3-0', 'upstream': '1.2.3',
2434 'semantic_version': 123},
2435 distro.parse_dpkg_version('1.2.3-0', semx=(100, 10, 1)))
2436@@ -98,7 +98,8 @@ class TestParseDpkgVersion(CiTestCase):
2437 """upstream versions may have a hyphen."""
2438 cver = '18.2-14-g6d48d265-0ubuntu1'
2439 self.assertEqual(
2440- {'major': 18, 'minor': 2, 'micro': 0, 'extra': '-14-g6d48d265',
2441+ {'epoch': 0, 'major': 18, 'minor': 2, 'micro': 0,
2442+ 'extra': '-14-g6d48d265',
2443 'raw': cver, 'upstream': '18.2-14-g6d48d265',
2444 'name': 'cloud-init', 'semantic_version': 180200},
2445 distro.parse_dpkg_version(cver, name='cloud-init'))
2446@@ -107,11 +108,30 @@ class TestParseDpkgVersion(CiTestCase):
2447 """multipath tools has a + in it."""
2448 mver = '0.5.0+git1.656f8865-5ubuntu2.5'
2449 self.assertEqual(
2450- {'major': 0, 'minor': 5, 'micro': 0, 'extra': '+git1.656f8865',
2451+ {'epoch': 0, 'major': 0, 'minor': 5, 'micro': 0,
2452+ 'extra': '+git1.656f8865',
2453 'raw': mver, 'upstream': '0.5.0+git1.656f8865',
2454 'semantic_version': 500},
2455 distro.parse_dpkg_version(mver))
2456
2457+ def test_package_with_epoch(self):
2458+ """xxd has epoch"""
2459+ mver = '2:8.1.2269-1ubuntu5'
2460+ self.assertEqual(
2461+ {'epoch': 2, 'major': 8, 'minor': 1, 'micro': 2269,
2462+ 'extra': None, 'raw': mver, 'upstream': '8.1.2269',
2463+ 'semantic_version': 82369},
2464+ distro.parse_dpkg_version(mver))
2465+
2466+ def test_package_with_dot_in_extra(self):
2467+ """linux-image-generic has multiple dots in extra"""
2468+ mver = '5.4.0.37.40'
2469+ self.assertEqual(
2470+ {'epoch': 0, 'major': 5, 'minor': 4, 'micro': 0,
2471+ 'extra': '37.40', 'raw': mver, 'upstream': '5.4.0.37.40',
2472+ 'semantic_version': 50400},
2473+ distro.parse_dpkg_version(mver))
2474+
2475
2476 class TestDistros(CiTestCase):
2477
2478@@ -429,6 +449,7 @@ class TestSystemUpgrade(CiTestCase):
2479 auto_remove = apt_base + ['autoremove']
2480 expected_calls = [
2481 mock.call(apt_cmd, env=env, target=paths.target_path(target)),
2482+ mock.call(['apt-get', 'clean'], target=paths.target_path(target)),
2483 mock.call(auto_remove, env=env, target=paths.target_path(target)),
2484 ]
2485 which_calls = [mock.call('eatmydata', target=target)]
2486diff --git a/tests/unittests/test_feature.py b/tests/unittests/test_feature.py
2487index 7c55882..8690ad8 100644
2488--- a/tests/unittests/test_feature.py
2489+++ b/tests/unittests/test_feature.py
2490@@ -24,4 +24,10 @@ class TestExportsFeatures(CiTestCase):
2491 def test_has_centos_curthook_support(self):
2492 self.assertIn('CENTOS_CURTHOOK_SUPPORT', curtin.FEATURES)
2493
2494+ def test_has_btrfs_swapfile_support(self):
2495+ self.assertIn('BTRFS_SWAPFILE', curtin.FEATURES)
2496+
2497+ def test_has_uefi_reorder_fallback_support(self):
2498+ self.assertIn('UEFI_REORDER_FALLBACK_SUPPORT', curtin.FEATURES)
2499+
2500 # vi: ts=4 expandtab syntax=python
2501diff --git a/tests/vmtests/__init__.py b/tests/vmtests/__init__.py
2502index adfcd24..0b19d8f 100644
2503--- a/tests/vmtests/__init__.py
2504+++ b/tests/vmtests/__init__.py
2505@@ -633,6 +633,7 @@ class VMBaseClass(TestCase):
2506
2507 # these get set from base_vm_classes
2508 release = None
2509+ supported_releases = []
2510 arch = None
2511 target_arch = None
2512 kflavor = None
2513@@ -855,6 +856,13 @@ class VMBaseClass(TestCase):
2514 return {'kernel': {'fallback-package': package}}
2515
2516 @classmethod
2517+ def is_unsupported_release(cls):
2518+ # allow unsupported releases opt-in to avoid the skiptest
2519+ if cls.release in cls.supported_releases:
2520+ return False
2521+ return is_unsupported_ubuntu(cls.release)
2522+
2523+ @classmethod
2524 def skip_by_date(cls, *args, **kwargs):
2525 """skip_by_date wrapper. this way other modules do not have
2526 to add an import of skip_by_date to start skipping."""
2527@@ -883,7 +891,7 @@ class VMBaseClass(TestCase):
2528 "Class %s does not have required attrs set: %s" %
2529 (cls.__name__, missing))
2530
2531- if is_unsupported_ubuntu(cls.release):
2532+ if cls.is_unsupported_release():
2533 raise SkipTest('"%s" is unsupported release.' % cls.release)
2534
2535 # check if we should skip due to host arch
2536@@ -1668,8 +1676,8 @@ class VMBaseClass(TestCase):
2537 if spec in line:
2538 fstab_entry = line
2539 self.assertIsNotNone(fstab_entry)
2540- self.assertEqual(mp, fstab_entry.split(' ')[1])
2541- self.assertEqual(fsopts, fstab_entry.split(' ')[3])
2542+ self.assertEqual(mp, fstab_entry.split()[1])
2543+ self.assertEqual(fsopts, fstab_entry.split()[3])
2544 found.append((spec, mp, fsopts))
2545
2546 self.assertEqual(sorted(expected), sorted(found))
2547@@ -1755,12 +1763,28 @@ class VMBaseClass(TestCase):
2548 for line in ls_byid.split('\n')
2549 if ("virtio-" + serial) in line.split() or
2550 ("scsi-" + serial) in line.split() or
2551- ("wwn-" + serial) in line.split()]
2552+ ("wwn-" + serial) in line.split() or
2553+ (serial) in line.split()]
2554+ print("Looking for serial %s in 'ls_al_byid' content\n%s" % (serial,
2555+ ls_byid))
2556 self.assertEqual(len(kname), 1)
2557 kname = kname.pop()
2558 self.assertIsNotNone(kname)
2559 return kname
2560
2561+ def _mdname_to_kname(self, mdname):
2562+ # extract kname from /dev/md/ on /dev/<kname>
2563+ # parsing ls -al output on /dev/md/*:
2564+ # lrwxrwxrwx 1 root root 8 May 28 16:26 /dev/md/os-raid1 -> ../md127
2565+ ls_dev_md = self.load_collect_file("ls_al_dev_md")
2566+ knames = [os.path.basename(line.split()[-1])
2567+ for line in ls_dev_md.split('\n')
2568+ if mdname in line]
2569+ self.assertEqual(len(knames), 1)
2570+ kname = knames.pop()
2571+ self.assertIsNotNone(kname)
2572+ return kname
2573+
2574 def _kname_to_bypath(self, kname):
2575 # extract path from /dev/disk/by-path on /dev/<kname>
2576 # parsing ls -al output on /dev/disk/by-path
2577@@ -1789,6 +1813,36 @@ class VMBaseClass(TestCase):
2578 self.assertEqual(len(uuid), 36)
2579 return uuid
2580
2581+ def _byuuid_to_kname(self, devpath):
2582+ # lookup kname via /dev/disk/by-uuid symlink
2583+ # parsing ls -al output on /dev/disk/by-uuid:
2584+ # lrwxrwxrwx 1 root root 9 Dec 4 20:02
2585+ # d591e9e9-825a-4f0a-b280-3bfaf470b83c -> ../../vdg
2586+ uuid = os.path.basename(devpath)
2587+ self.assertIsNotNone(uuid)
2588+ print(uuid)
2589+ ls_uuid = self.load_collect_file("ls_al_byuuid")
2590+ kname = [line.split()[-1] for line in ls_uuid.split('\n')
2591+ if uuid in line.split()]
2592+ self.assertEqual(len(kname), 1)
2593+ kname = os.path.basename(kname.pop())
2594+ return kname
2595+
2596+ def _bypath_to_kname(self, devpath):
2597+ # lookup kname via /dev/disk/by-path symlink
2598+ # parsing ls -al output on /dev/disk/by-path:
2599+ # lrwxrwxrwx 1 root root 9 Dec 4 20:02
2600+ # pci-0000:00:03.0-scsi-0:0:0:0-part3 -> ../../sda3
2601+ dpath = os.path.basename(devpath)
2602+ self.assertIsNotNone(dpath)
2603+ print(dpath)
2604+ ls_bypath = self.load_collect_file("ls_al_bypath")
2605+ kname = [line.split()[-1] for line in ls_bypath.split('\n')
2606+ if dpath in line.split()]
2607+ self.assertEqual(len(kname), 1)
2608+ kname = os.path.basename(kname.pop())
2609+ return kname
2610+
2611 def _bcache_to_byuuid(self, kname):
2612 # extract bcache uuid from /dev/bcache/by-uuid on /dev/<kname>
2613 # parsing ls -al output on /dev/bcache/by-uuid
2614@@ -1970,25 +2024,44 @@ class VMBaseClass(TestCase):
2615
2616 @skip_if_flag('expected_failure')
2617 def test_swaps_used(self):
2618- if not self.has_storage_config():
2619- raise SkipTest("This test does not use storage config.")
2620
2621- stgcfg = self.get_storage_config()
2622- swap_ids = [d["id"] for d in stgcfg if d.get("fstype") == "swap"]
2623- swap_mounts = [d for d in stgcfg if d.get("device") in swap_ids]
2624- self.assertEqual(len(swap_ids), len(swap_mounts),
2625- "number config swap fstypes != number swap mounts")
2626+ def find_fstab_swaps():
2627+ swaps = []
2628+ path = self.collect_path("fstab")
2629+ if not os.path.exists(path):
2630+ return swaps
2631+ for line in util.load_file(path).splitlines():
2632+ if line.startswith("#"):
2633+ continue
2634+ (fs, mp, fstype, opts, dump, passno) = line.split()
2635+ if fstype == 'swap':
2636+ if fs.startswith('/dev/disk/by-uuid'):
2637+ swaps.append('/dev/' + self._byuuid_to_kname(fs))
2638+ elif fs.startswith('/dev/disk/by-id'):
2639+ kname = self._serial_to_kname(os.path.basename(fs))
2640+ swaps.append('/dev/' + kname)
2641+ elif fs.startswith('/dev/disk/by-path'):
2642+ swaps.append('/dev/' + self._bypath_to_kname(fs))
2643+ else:
2644+ swaps.append(fs)
2645+
2646+ return swaps
2647+
2648+ # we don't yet have a skip_by_date on specific releases
2649+ if is_devel_release(self.target_release):
2650+ name = "test_swaps_used"
2651+ bug = "1894910"
2652+ fixby = "2020-10-15"
2653+ removeby = "2020-11-01"
2654+ raise SkipTest(
2655+ "skip_by_date({name}) LP: #{bug} "
2656+ "fixby={fixby} removeby={removeby}: ".format(
2657+ name=name, bug=bug, fixby=fixby, removeby=removeby))
2658
2659- swaps_found = []
2660- for line in self.load_collect_file("proc-swaps").splitlines():
2661- fname, ttype, size, used, priority = line.split()
2662- if ttype == "partition":
2663- swaps_found.append(
2664- {"fname": fname, ttype: "ttype", "size": int(size),
2665- "used": int(used), "priority": int(priority)})
2666- self.assertEqual(
2667- len(swap_mounts), len(swaps_found),
2668- "Number swaps configured != number used")
2669+ expected_swaps = find_fstab_swaps()
2670+ proc_swaps = self.load_collect_file("proc-swaps")
2671+ for swap in expected_swaps:
2672+ self.assertIn(swap, proc_swaps)
2673
2674
2675 class PsuedoVMBaseClass(VMBaseClass):
2676@@ -2087,6 +2160,9 @@ class PsuedoVMBaseClass(VMBaseClass):
2677 def test_kernel_img_conf(self):
2678 pass
2679
2680+ def test_swaps_used(self):
2681+ pass
2682+
2683 def _maybe_raise(self, exc):
2684 if self.allow_test_fails:
2685 raise exc
2686diff --git a/tests/vmtests/releases.py b/tests/vmtests/releases.py
2687index 3dcb415..11abcb8 100644
2688--- a/tests/vmtests/releases.py
2689+++ b/tests/vmtests/releases.py
2690@@ -185,6 +185,14 @@ class _FocalBase(_UbuntuBase):
2691 subarch = "ga-20.04"
2692
2693
2694+class _GroovyBase(_UbuntuBase):
2695+ release = "groovy"
2696+ target_release = "groovy"
2697+ mem = "2048"
2698+ if _UbuntuBase.arch == "arm64":
2699+ subarch = "ga-20.04"
2700+
2701+
2702 class _Releases(object):
2703 trusty = _TrustyBase
2704 precise = _PreciseBase
2705@@ -203,6 +211,7 @@ class _Releases(object):
2706 disco = _DiscoBase
2707 eoan = _EoanBase
2708 focal = _FocalBase
2709+ groovy = _GroovyBase
2710
2711
2712 class _CentosReleases(object):
2713diff --git a/tests/vmtests/test_apt_config_cmd.py b/tests/vmtests/test_apt_config_cmd.py
2714index 4e43882..874efad 100644
2715--- a/tests/vmtests/test_apt_config_cmd.py
2716+++ b/tests/vmtests/test_apt_config_cmd.py
2717@@ -41,7 +41,7 @@ class TestAptConfigCMD(VMBaseClass):
2718 self.check_file_regex("curtin-dev-ubuntu-test-archive-%s.list" %
2719 self.release,
2720 (r"http://ppa.launchpad.net/"
2721- r"curtin-dev/test-archive/ubuntu"
2722+ r"curtin-dev/test-archive/ubuntu(/*)"
2723 r" %s main" % self.release))
2724
2725 def test_cmd_preserve_source(self):
2726@@ -68,4 +68,8 @@ class FocalTestAptConfigCMDCMD(relbase.focal, TestAptConfigCMD):
2727 __test__ = True
2728
2729
2730+class GroovyTestAptConfigCMDCMD(relbase.groovy, TestAptConfigCMD):
2731+ __test__ = True
2732+
2733+
2734 # vi: ts=4 expandtab syntax=python
2735diff --git a/tests/vmtests/test_basic.py b/tests/vmtests/test_basic.py
2736index 88b9897..5723bc6 100644
2737--- a/tests/vmtests/test_basic.py
2738+++ b/tests/vmtests/test_basic.py
2739@@ -143,11 +143,16 @@ class TestBasicAbs(VMBaseClass):
2740 def get_fstab_expected(self):
2741 rootdev = self._serial_to_kname('disk-a')
2742 btrfsdev = self._serial_to_kname('disk-c')
2743- return [
2744+ expected = [
2745 (self._kname_to_byuuid(rootdev + '1'), '/', 'defaults'),
2746 (self._kname_to_byuuid(rootdev + '2'), '/home', 'defaults'),
2747- (self._kname_to_byuuid(btrfsdev), '/btrfs', 'defaults,noatime')
2748+ (self._kname_to_byuuid(btrfsdev), '/btrfs', 'defaults,noatime'),
2749+ (self._kname_to_byuuid(rootdev + '3'), 'none', 'sw'),
2750 ]
2751+ if self.target_release in ['focal']:
2752+ expected.append(('/btrfs/btrfsswap.img', 'none', 'sw'))
2753+
2754+ return expected
2755
2756 def test_whole_disk_uuid(self):
2757 self._test_whole_disk_uuid(
2758@@ -250,11 +255,11 @@ class BionicTestBasic(relbase.bionic, TestBasicAbs):
2759 __test__ = True
2760
2761
2762-class EoanTestBasic(relbase.eoan, TestBasicAbs):
2763+class FocalTestBasic(relbase.focal, TestBasicAbs):
2764 __test__ = True
2765
2766
2767-class FocalTestBasic(relbase.focal, TestBasicAbs):
2768+class GroovyTestBasic(relbase.groovy, TestBasicAbs):
2769 __test__ = True
2770
2771
2772@@ -307,14 +312,23 @@ class TestBasicScsiAbs(TestBasicAbs):
2773 home_kname = (
2774 self._serial_to_kname('0x39cc071e72c64cc4-part2'))
2775 btrfs_kname = self._serial_to_kname('0x22dc58dc023c7008')
2776+ swap_kname = (
2777+ self._serial_to_kname('0x39cc071e72c64cc4-part3'))
2778
2779 map_func = self._kname_to_byuuid
2780 if self.arch == 's390x':
2781 map_func = self._kname_to_bypath
2782
2783- return [(map_func(root_kname), '/', 'defaults'),
2784- (map_func(home_kname), '/home', 'defaults'),
2785- (map_func(btrfs_kname), '/btrfs', 'defaults,noatime')]
2786+ expected = [
2787+ (map_func(root_kname), '/', 'defaults'),
2788+ (map_func(home_kname), '/home', 'defaults'),
2789+ (map_func(btrfs_kname), '/btrfs', 'defaults,noatime'),
2790+ (map_func(swap_kname), 'none', 'sw')]
2791+
2792+ if self.target_release in ['focal']:
2793+ expected.append(('/btrfs/btrfsswap.img', 'none', 'sw'))
2794+
2795+ return expected
2796
2797 @skip_if_arch('s390x')
2798 def test_whole_disk_uuid(self):
2799@@ -361,11 +375,11 @@ class BionicTestScsiBasic(relbase.bionic, TestBasicScsiAbs):
2800 __test__ = True
2801
2802
2803-class EoanTestScsiBasic(relbase.eoan, TestBasicScsiAbs):
2804+class FocalTestScsiBasic(relbase.focal, TestBasicScsiAbs):
2805 __test__ = True
2806
2807
2808-class FocalTestScsiBasic(relbase.focal, TestBasicScsiAbs):
2809+class GroovyTestScsiBasic(relbase.groovy, TestBasicScsiAbs):
2810 __test__ = True
2811
2812
2813diff --git a/tests/vmtests/test_basic_dasd.py b/tests/vmtests/test_basic_dasd.py
2814index 391bafc..d61e1b9 100644
2815--- a/tests/vmtests/test_basic_dasd.py
2816+++ b/tests/vmtests/test_basic_dasd.py
2817@@ -52,11 +52,11 @@ class BionicTestBasicDasd(relbase.bionic, TestBasicDasd):
2818 __test__ = True
2819
2820
2821-class EoanTestBasicDasd(relbase.eoan, TestBasicDasd):
2822+class FocalTestBasicDasd(relbase.focal, TestBasicDasd):
2823 __test__ = True
2824
2825
2826-class FocalTestBasicDasd(relbase.focal, TestBasicDasd):
2827+class GroovyTestBasicDasd(relbase.groovy, TestBasicDasd):
2828 __test__ = True
2829
2830
2831diff --git a/tests/vmtests/test_bcache_basic.py b/tests/vmtests/test_bcache_basic.py
2832index 54bac81..053225f 100644
2833--- a/tests/vmtests/test_bcache_basic.py
2834+++ b/tests/vmtests/test_bcache_basic.py
2835@@ -64,11 +64,11 @@ class BionicBcacheBasic(relbase.bionic, TestBcacheBasic):
2836 __test__ = True
2837
2838
2839-class EoanBcacheBasic(relbase.eoan, TestBcacheBasic):
2840+class FocalBcacheBasic(relbase.focal, TestBcacheBasic):
2841 __test__ = True
2842
2843
2844-class FocalBcacheBasic(relbase.focal, TestBcacheBasic):
2845+class GroovyBcacheBasic(relbase.groovy, TestBcacheBasic):
2846 __test__ = True
2847
2848
2849diff --git a/tests/vmtests/test_bcache_bug1718699.py b/tests/vmtests/test_bcache_bug1718699.py
2850index 8c29046..ebb99ab 100644
2851--- a/tests/vmtests/test_bcache_bug1718699.py
2852+++ b/tests/vmtests/test_bcache_bug1718699.py
2853@@ -19,11 +19,11 @@ class BionicTestBcacheBug1718699(relbase.bionic, TestBcacheBug1718699):
2854 __test__ = True
2855
2856
2857-class EoanTestBcacheBug1718699(relbase.eoan, TestBcacheBug1718699):
2858+class FocalTestBcacheBug1718699(relbase.focal, TestBcacheBug1718699):
2859 __test__ = True
2860
2861
2862-class FocalTestBcacheBug1718699(relbase.focal, TestBcacheBug1718699):
2863+class GroovyTestBcacheBug1718699(relbase.groovy, TestBcacheBug1718699):
2864 __test__ = True
2865
2866
2867diff --git a/tests/vmtests/test_bcache_ceph.py b/tests/vmtests/test_bcache_ceph.py
2868index d24994a..bff4dd4 100644
2869--- a/tests/vmtests/test_bcache_ceph.py
2870+++ b/tests/vmtests/test_bcache_ceph.py
2871@@ -75,11 +75,11 @@ class BionicTestBcacheCeph(relbase.bionic, TestBcacheCeph):
2872 __test__ = True
2873
2874
2875-class EoanTestBcacheCeph(relbase.eoan, TestBcacheCeph):
2876+class FocalTestBcacheCeph(relbase.focal, TestBcacheCeph):
2877 __test__ = True
2878
2879
2880-class FocalTestBcacheCeph(relbase.focal, TestBcacheCeph):
2881+class GroovyTestBcacheCeph(relbase.groovy, TestBcacheCeph):
2882 __test__ = True
2883
2884
2885@@ -109,4 +109,8 @@ class FocalTestBcacheCephLvm(relbase.focal, TestBcacheCephLvm):
2886 __test__ = True
2887
2888
2889+class GroovyTestBcacheCephLvm(relbase.groovy, TestBcacheCephLvm):
2890+ __test__ = True
2891+
2892+
2893 # vi: ts=4 expandtab syntax=python
2894diff --git a/tests/vmtests/test_bcache_partitions.py b/tests/vmtests/test_bcache_partitions.py
2895index f41e645..1ffea12 100644
2896--- a/tests/vmtests/test_bcache_partitions.py
2897+++ b/tests/vmtests/test_bcache_partitions.py
2898@@ -25,11 +25,11 @@ class BionicTestBcachePartitions(relbase.bionic, TestBcachePartitions):
2899 __test__ = True
2900
2901
2902-class EoanTestBcachePartitions(relbase.eoan, TestBcachePartitions):
2903+class FocalTestBcachePartitions(relbase.focal, TestBcachePartitions):
2904 __test__ = True
2905
2906
2907-class FocalTestBcachePartitions(relbase.focal, TestBcachePartitions):
2908+class GroovyTestBcachePartitions(relbase.groovy, TestBcachePartitions):
2909 __test__ = True
2910
2911
2912diff --git a/tests/vmtests/test_fs_battery.py b/tests/vmtests/test_fs_battery.py
2913index bd44905..7177fea 100644
2914--- a/tests/vmtests/test_fs_battery.py
2915+++ b/tests/vmtests/test_fs_battery.py
2916@@ -239,11 +239,11 @@ class BionicTestFsBattery(relbase.bionic, TestFsBattery):
2917 __test__ = True
2918
2919
2920-class EoanTestFsBattery(relbase.eoan, TestFsBattery):
2921+class FocalTestFsBattery(relbase.focal, TestFsBattery):
2922 __test__ = True
2923
2924
2925-class FocalTestFsBattery(relbase.focal, TestFsBattery):
2926+class GroovyTestFsBattery(relbase.groovy, TestFsBattery):
2927 __test__ = True
2928
2929
2930diff --git a/tests/vmtests/test_iscsi.py b/tests/vmtests/test_iscsi.py
2931index c99264c..f3406cd 100644
2932--- a/tests/vmtests/test_iscsi.py
2933+++ b/tests/vmtests/test_iscsi.py
2934@@ -72,11 +72,11 @@ class BionicTestIscsiBasic(relbase.bionic, TestBasicIscsiAbs):
2935 __test__ = True
2936
2937
2938-class EoanTestIscsiBasic(relbase.eoan, TestBasicIscsiAbs):
2939+class FocalTestIscsiBasic(relbase.focal, TestBasicIscsiAbs):
2940 __test__ = True
2941
2942
2943-class FocalTestIscsiBasic(relbase.focal, TestBasicIscsiAbs):
2944+class GroovyTestIscsiBasic(relbase.groovy, TestBasicIscsiAbs):
2945 __test__ = True
2946
2947
2948diff --git a/tests/vmtests/test_journald_reporter.py b/tests/vmtests/test_journald_reporter.py
2949index d29b4d4..ff003a5 100644
2950--- a/tests/vmtests/test_journald_reporter.py
2951+++ b/tests/vmtests/test_journald_reporter.py
2952@@ -32,11 +32,11 @@ class BionicTestJournaldReporter(relbase.bionic, TestJournaldReporter):
2953 __test__ = True
2954
2955
2956-class EoanTestJournaldReporter(relbase.eoan, TestJournaldReporter):
2957+class FocalTestJournaldReporter(relbase.focal, TestJournaldReporter):
2958 __test__ = True
2959
2960
2961-class FocalTestJournaldReporter(relbase.focal, TestJournaldReporter):
2962+class GroovyTestJournaldReporter(relbase.groovy, TestJournaldReporter):
2963 __test__ = True
2964
2965
2966diff --git a/tests/vmtests/test_lvm.py b/tests/vmtests/test_lvm.py
2967index a79a705..eb65c32 100644
2968--- a/tests/vmtests/test_lvm.py
2969+++ b/tests/vmtests/test_lvm.py
2970@@ -77,11 +77,11 @@ class BionicTestLvm(relbase.bionic, TestLvmAbs):
2971 __test__ = True
2972
2973
2974-class EoanTestLvm(relbase.eoan, TestLvmAbs):
2975+class FocalTestLvm(relbase.focal, TestLvmAbs):
2976 __test__ = True
2977
2978
2979-class FocalTestLvm(relbase.focal, TestLvmAbs):
2980+class GroovyTestLvm(relbase.groovy, TestLvmAbs):
2981 __test__ = True
2982
2983
2984diff --git a/tests/vmtests/test_lvm_iscsi.py b/tests/vmtests/test_lvm_iscsi.py
2985index 077b31a..e0b9606 100644
2986--- a/tests/vmtests/test_lvm_iscsi.py
2987+++ b/tests/vmtests/test_lvm_iscsi.py
2988@@ -95,11 +95,11 @@ class BionicTestIscsiLvm(relbase.bionic, TestLvmIscsiAbs):
2989 __test__ = True
2990
2991
2992-class EoanTestIscsiLvm(relbase.eoan, TestLvmIscsiAbs):
2993+class FocalTestIscsiLvm(relbase.focal, TestLvmIscsiAbs):
2994 __test__ = True
2995
2996
2997-class FocalTestIscsiLvm(relbase.focal, TestLvmIscsiAbs):
2998+class GroovyTestIscsiLvm(relbase.groovy, TestLvmIscsiAbs):
2999 __test__ = True
3000
3001
3002diff --git a/tests/vmtests/test_lvm_raid.py b/tests/vmtests/test_lvm_raid.py
3003index 8d42a1a..5fe7993 100644
3004--- a/tests/vmtests/test_lvm_raid.py
3005+++ b/tests/vmtests/test_lvm_raid.py
3006@@ -47,17 +47,17 @@ class TestLvmOverRaidAbs(TestMdadmAbs, TestLvmAbs):
3007 return self._test_pvs(dname_to_vg)
3008
3009
3010-class FocalTestLvmOverRaid(relbase.focal, TestLvmOverRaidAbs):
3011+class XenialGATestLvmOverRaid(relbase.xenial_ga, TestLvmOverRaidAbs):
3012 __test__ = True
3013
3014
3015-class EoanTestLvmOverRaid(relbase.eoan, TestLvmOverRaidAbs):
3016+class BionicTestLvmOverRaid(relbase.bionic, TestLvmOverRaidAbs):
3017 __test__ = True
3018
3019
3020-class BionicTestLvmOverRaid(relbase.bionic, TestLvmOverRaidAbs):
3021+class FocalTestLvmOverRaid(relbase.focal, TestLvmOverRaidAbs):
3022 __test__ = True
3023
3024
3025-class XenialGATestLvmOverRaid(relbase.xenial_ga, TestLvmOverRaidAbs):
3026+class GroovyTestLvmOverRaid(relbase.groovy, TestLvmOverRaidAbs):
3027 __test__ = True
3028diff --git a/tests/vmtests/test_lvm_root.py b/tests/vmtests/test_lvm_root.py
3029index 117406e..12b8ea8 100644
3030--- a/tests/vmtests/test_lvm_root.py
3031+++ b/tests/vmtests/test_lvm_root.py
3032@@ -94,6 +94,13 @@ class FocalTestLvmRootExt4(relbase.focal, TestLvmRootAbs):
3033 }
3034
3035
3036+class GroovyTestLvmRootExt4(relbase.groovy, TestLvmRootAbs):
3037+ __test__ = True
3038+ conf_replace = {
3039+ '__ROOTFS_FORMAT__': 'ext4',
3040+ }
3041+
3042+
3043 class XenialTestLvmRootXfs(relbase.xenial, TestLvmRootAbs):
3044 __test__ = True
3045 conf_replace = {
3046@@ -140,6 +147,14 @@ class FocalTestUefiLvmRootExt4(relbase.focal, TestUefiLvmRootAbs):
3047 }
3048
3049
3050+class GroovyTestUefiLvmRootExt4(relbase.groovy, TestUefiLvmRootAbs):
3051+ __test__ = True
3052+ conf_replace = {
3053+ '__BOOTFS_FORMAT__': 'ext4',
3054+ '__ROOTFS_FORMAT__': 'ext4',
3055+ }
3056+
3057+
3058 class XenialTestUefiLvmRootXfs(relbase.xenial, TestUefiLvmRootAbs):
3059 __test__ = True
3060 conf_replace = {
3061@@ -148,13 +163,11 @@ class XenialTestUefiLvmRootXfs(relbase.xenial, TestUefiLvmRootAbs):
3062 }
3063
3064
3065-@VMBaseClass.skip_by_date("1652822", fixby="2020-06-01", install=False)
3066 class XenialTestUefiLvmRootXfsBootXfs(relbase.xenial, TestUefiLvmRootAbs):
3067 """This tests xfs root and xfs boot with uefi.
3068
3069- It is known broken (LP: #1652822) and unlikely to be fixed without pushing,
3070- so we skip-by for a long time."""
3071- __test__ = True
3072+ It is known broken (LP: #1652822) and unlikely to be fixed."""
3073+ __test__ = False
3074 conf_replace = {
3075 '__BOOTFS_FORMAT__': 'xfs',
3076 '__ROOTFS_FORMAT__': 'xfs',
3077diff --git a/tests/vmtests/test_mdadm_bcache.py b/tests/vmtests/test_mdadm_bcache.py
3078index 8e250cc..5425221 100644
3079--- a/tests/vmtests/test_mdadm_bcache.py
3080+++ b/tests/vmtests/test_mdadm_bcache.py
3081@@ -26,6 +26,7 @@ class TestMdadmAbs(VMBaseClass):
3082 ls -al /dev/bcache* > lsal_dev_bcache_star
3083 ls -al /dev/bcache/by-uuid/ | cat >ls_al_bcache_byuuid
3084 ls -al /dev/bcache/by-label/ | cat >ls_al_bcache_bylabel
3085+ ls -al /dev/md/* | cat >ls_al_dev_md
3086
3087 exit 0
3088 """)]
3089@@ -153,18 +154,18 @@ class BionicTestMdadmBcache(relbase.bionic, TestMdadmBcacheAbs):
3090 __test__ = True
3091
3092
3093-class EoanTestMdadmBcache(relbase.eoan, TestMdadmBcacheAbs):
3094- __test__ = True
3095-
3096-
3097 class FocalTestMdadmBcache(relbase.focal, TestMdadmBcacheAbs):
3098 __test__ = True
3099
3100- @TestMdadmBcacheAbs.skip_by_date("1861941", fixby="2020-07-15")
3101+ @TestMdadmBcacheAbs.skip_by_date("1861941", fixby="2020-09-15")
3102 def test_fstab(self):
3103 return super().test_fstab()
3104
3105
3106+class GroovyTestMdadmBcache(relbase.groovy, TestMdadmBcacheAbs):
3107+ __test__ = True
3108+
3109+
3110 class TestMirrorbootAbs(TestMdadmAbs):
3111 # alternative config for more complex setup
3112 conf_file = "examples/tests/mirrorboot.yaml"
3113@@ -202,11 +203,11 @@ class BionicTestMirrorboot(relbase.bionic, TestMirrorbootAbs):
3114 __test__ = True
3115
3116
3117-class EoanTestMirrorboot(relbase.eoan, TestMirrorbootAbs):
3118+class FocalTestMirrorboot(relbase.focal, TestMirrorbootAbs):
3119 __test__ = True
3120
3121
3122-class FocalTestMirrorboot(relbase.focal, TestMirrorbootAbs):
3123+class GroovyTestMirrorboot(relbase.groovy, TestMirrorbootAbs):
3124 __test__ = True
3125
3126
3127@@ -250,13 +251,13 @@ class BionicTestMirrorbootPartitions(relbase.bionic,
3128 __test__ = True
3129
3130
3131-class EoanTestMirrorbootPartitions(relbase.eoan,
3132- TestMirrorbootPartitionsAbs):
3133+class FocalTestMirrorbootPartitions(relbase.focal,
3134+ TestMirrorbootPartitionsAbs):
3135 __test__ = True
3136
3137
3138-class FocalTestMirrorbootPartitions(relbase.focal,
3139- TestMirrorbootPartitionsAbs):
3140+class GroovyTestMirrorbootPartitions(relbase.groovy,
3141+ TestMirrorbootPartitionsAbs):
3142 __test__ = True
3143
3144
3145@@ -309,6 +310,16 @@ class TestMirrorbootPartitionsUEFIAbs(TestMdadmAbs):
3146 self.assertIn(
3147 ('grub-pc', 'grub-efi/install_devices', choice), found_selections)
3148
3149+ def test_backup_esp_matches_primary(self):
3150+ if self.target_distro != "ubuntu":
3151+ raise SkipTest("backup ESP supported only on Ubuntu")
3152+ if self.target_release in [
3153+ "trusty", "xenial", "bionic", "cosmic", "disco", "eoan"]:
3154+ raise SkipTest("backup ESP supported only on >= Focal")
3155+ primary_esp = self.load_collect_file("diska-part1-efi.out")
3156+ backup_esp = self.load_collect_file("diskb-part1-efi.out")
3157+ self.assertEqual(primary_esp, backup_esp)
3158+
3159
3160 class Centos70TestMirrorbootPartitionsUEFI(centos_relbase.centos70_xenial,
3161 TestMirrorbootPartitionsUEFIAbs):
3162@@ -335,19 +346,14 @@ class BionicTestMirrorbootPartitionsUEFI(relbase.bionic,
3163 __test__ = True
3164
3165
3166-class EoanTestMirrorbootPartitionsUEFI(relbase.eoan,
3167- TestMirrorbootPartitionsUEFIAbs):
3168- __test__ = True
3169-
3170-
3171 class FocalTestMirrorbootPartitionsUEFI(relbase.focal,
3172 TestMirrorbootPartitionsUEFIAbs):
3173 __test__ = True
3174
3175- def test_backup_esp_matches_primary(self):
3176- primary_esp = self.load_collect_file("diska-part1-efi.out")
3177- backup_esp = self.load_collect_file("diskb-part1-efi.out")
3178- self.assertEqual(primary_esp, backup_esp)
3179+
3180+class GroovyTestMirrorbootPartitionsUEFI(relbase.groovy,
3181+ TestMirrorbootPartitionsUEFIAbs):
3182+ __test__ = True
3183
3184
3185 class TestRaid5bootAbs(TestMdadmAbs):
3186@@ -359,11 +365,14 @@ class TestRaid5bootAbs(TestMdadmAbs):
3187 ('main_disk', 2),
3188 ('second_disk', 1),
3189 ('third_disk', 1),
3190- ('md0', 0)]
3191+ ('os-raid1', 0)]
3192
3193 def get_fstab_expected(self):
3194+ kname = self._mdname_to_kname('os-raid1')
3195 return [
3196- (self._kname_to_uuid_devpath('md-uuid', 'md0'), '/', 'defaults'),
3197+ (self._kname_to_uuid_devpath('md-uuid', kname),
3198+ '/',
3199+ 'defaults'),
3200 ]
3201
3202
3203@@ -387,11 +396,11 @@ class BionicTestRaid5boot(relbase.bionic, TestRaid5bootAbs):
3204 __test__ = True
3205
3206
3207-class EoanTestRaid5boot(relbase.eoan, TestRaid5bootAbs):
3208+class FocalTestRaid5boot(relbase.focal, TestRaid5bootAbs):
3209 __test__ = True
3210
3211
3212-class FocalTestRaid5boot(relbase.focal, TestRaid5bootAbs):
3213+class GroovyTestRaid5boot(relbase.groovy, TestRaid5bootAbs):
3214 __test__ = True
3215
3216
3217@@ -448,11 +457,11 @@ class BionicTestRaid6boot(relbase.bionic, TestRaid6bootAbs):
3218 __test__ = True
3219
3220
3221-class EoanTestRaid6boot(relbase.eoan, TestRaid6bootAbs):
3222+class FocalTestRaid6boot(relbase.focal, TestRaid6bootAbs):
3223 __test__ = True
3224
3225
3226-class FocalTestRaid6boot(relbase.focal, TestRaid6bootAbs):
3227+class GroovyTestRaid6boot(relbase.groovy, TestRaid6bootAbs):
3228 __test__ = True
3229
3230
3231@@ -495,11 +504,11 @@ class BionicTestRaid10boot(relbase.bionic, TestRaid10bootAbs):
3232 __test__ = True
3233
3234
3235-class EoanTestRaid10boot(relbase.eoan, TestRaid10bootAbs):
3236+class FocalTestRaid10boot(relbase.focal, TestRaid10bootAbs):
3237 __test__ = True
3238
3239
3240-class FocalTestRaid10boot(relbase.focal, TestRaid10bootAbs):
3241+class GroovyTestRaid10boot(relbase.groovy, TestRaid10bootAbs):
3242 __test__ = True
3243
3244
3245@@ -599,11 +608,11 @@ class BionicTestAllindata(relbase.bionic, TestAllindataAbs):
3246 __test__ = True
3247
3248
3249-class EoanTestAllindata(relbase.eoan, TestAllindataAbs):
3250+class FocalTestAllindata(relbase.focal, TestAllindataAbs):
3251 __test__ = True
3252
3253
3254-class FocalTestAllindata(relbase.focal, TestAllindataAbs):
3255+class GroovyTestAllindata(relbase.groovy, TestAllindataAbs):
3256 __test__ = True
3257
3258
3259diff --git a/tests/vmtests/test_mdadm_iscsi.py b/tests/vmtests/test_mdadm_iscsi.py
3260index 26b1f71..7e6fbf6 100644
3261--- a/tests/vmtests/test_mdadm_iscsi.py
3262+++ b/tests/vmtests/test_mdadm_iscsi.py
3263@@ -50,11 +50,11 @@ class BionicTestIscsiMdadm(relbase.bionic, TestMdadmIscsiAbs):
3264 __test__ = True
3265
3266
3267-class EoanTestIscsiMdadm(relbase.eoan, TestMdadmIscsiAbs):
3268+class FocalTestIscsiMdadm(relbase.focal, TestMdadmIscsiAbs):
3269 __test__ = True
3270
3271
3272-class FocalTestIscsiMdadm(relbase.focal, TestMdadmIscsiAbs):
3273+class GroovyTestIscsiMdadm(relbase.groovy, TestMdadmIscsiAbs):
3274 __test__ = True
3275
3276
3277diff --git a/tests/vmtests/test_multipath.py b/tests/vmtests/test_multipath.py
3278index 7c7e621..6d9c5df 100644
3279--- a/tests/vmtests/test_multipath.py
3280+++ b/tests/vmtests/test_multipath.py
3281@@ -158,11 +158,11 @@ class BionicTestMultipathBasic(relbase.bionic, TestMultipathBasicAbs):
3282 __test__ = True
3283
3284
3285-class EoanTestMultipathBasic(relbase.eoan, TestMultipathBasicAbs):
3286+class FocalTestMultipathBasic(relbase.focal, TestMultipathBasicAbs):
3287 __test__ = True
3288
3289
3290-class FocalTestMultipathBasic(relbase.focal, TestMultipathBasicAbs):
3291+class GroovyTestMultipathBasic(relbase.groovy, TestMultipathBasicAbs):
3292 __test__ = True
3293
3294
3295diff --git a/tests/vmtests/test_multipath_lvm.py b/tests/vmtests/test_multipath_lvm.py
3296index 39b8587..c5a1e42 100644
3297--- a/tests/vmtests/test_multipath_lvm.py
3298+++ b/tests/vmtests/test_multipath_lvm.py
3299@@ -56,11 +56,11 @@ class BionicTestMultipathLvm(relbase.bionic, TestMultipathLvmAbs):
3300 __test__ = True
3301
3302
3303-class EoanTestMultipathLvm(relbase.eoan, TestMultipathLvmAbs):
3304+class FocalTestMultipathLvm(relbase.focal, TestMultipathLvmAbs):
3305 __test__ = True
3306
3307
3308-class FocalTestMultipathLvm(relbase.focal, TestMultipathLvmAbs):
3309+class GroovyTestMultipathLvm(relbase.groovy, TestMultipathLvmAbs):
3310 __test__ = True
3311
3312
3313@@ -73,4 +73,9 @@ class FocalTestMultipathLvmPartWipe(relbase.focal,
3314 __test__ = True
3315
3316
3317+class GroovyTestMultipathLvmPartWipe(relbase.groovy,
3318+ TestMultipathLvmPartWipeAbs):
3319+ __test__ = True
3320+
3321+
3322 # vi: ts=4 expandtab syntax=python
3323diff --git a/tests/vmtests/test_network.py b/tests/vmtests/test_network.py
3324index e6ea6e2..43a7c6b 100644
3325--- a/tests/vmtests/test_network.py
3326+++ b/tests/vmtests/test_network.py
3327@@ -474,11 +474,11 @@ class BionicTestNetworkBasic(relbase.bionic, TestNetworkBasicAbs):
3328 __test__ = True
3329
3330
3331-class EoanTestNetworkBasic(relbase.eoan, TestNetworkBasicAbs):
3332+class FocalTestNetworkBasic(relbase.focal, TestNetworkBasicAbs):
3333 __test__ = True
3334
3335
3336-class FocalTestNetworkBasic(relbase.focal, TestNetworkBasicAbs):
3337+class GroovyTestNetworkBasic(relbase.groovy, TestNetworkBasicAbs):
3338 __test__ = True
3339
3340
3341diff --git a/tests/vmtests/test_network_alias.py b/tests/vmtests/test_network_alias.py
3342index 68e7de4..bc1fb22 100644
3343--- a/tests/vmtests/test_network_alias.py
3344+++ b/tests/vmtests/test_network_alias.py
3345@@ -52,11 +52,11 @@ class BionicTestNetworkAlias(relbase.bionic, TestNetworkAliasAbs):
3346 __test__ = True
3347
3348
3349-class EoanTestNetworkAlias(relbase.eoan, TestNetworkAliasAbs):
3350+class FocalTestNetworkAlias(relbase.focal, TestNetworkAliasAbs):
3351 __test__ = True
3352
3353
3354-class FocalTestNetworkAlias(relbase.focal, TestNetworkAliasAbs):
3355+class GroovyTestNetworkAlias(relbase.groovy, TestNetworkAliasAbs):
3356 __test__ = True
3357
3358
3359diff --git a/tests/vmtests/test_network_bonding.py b/tests/vmtests/test_network_bonding.py
3360index 913c7ff..6c6dd6d 100644
3361--- a/tests/vmtests/test_network_bonding.py
3362+++ b/tests/vmtests/test_network_bonding.py
3363@@ -57,11 +57,11 @@ class BionicTestBonding(relbase.bionic, TestNetworkBondingAbs):
3364 __test__ = True
3365
3366
3367-class EoanTestBonding(relbase.eoan, TestNetworkBondingAbs):
3368+class FocalTestBonding(relbase.focal, TestNetworkBondingAbs):
3369 __test__ = True
3370
3371
3372-class FocalTestBonding(relbase.focal, TestNetworkBondingAbs):
3373+class GroovyTestBonding(relbase.groovy, TestNetworkBondingAbs):
3374 __test__ = True
3375
3376
3377diff --git a/tests/vmtests/test_network_bridging.py b/tests/vmtests/test_network_bridging.py
3378index daaade5..9ecd2f6 100644
3379--- a/tests/vmtests/test_network_bridging.py
3380+++ b/tests/vmtests/test_network_bridging.py
3381@@ -236,11 +236,11 @@ class BionicTestBridging(relbase.bionic, TestBridgeNetworkAbs):
3382 __test__ = True
3383
3384
3385-class EoanTestBridging(relbase.eoan, TestBridgeNetworkAbs):
3386+class FocalTestBridging(relbase.focal, TestBridgeNetworkAbs):
3387 __test__ = True
3388
3389
3390-class FocalTestBridging(relbase.focal, TestBridgeNetworkAbs):
3391+class GroovyTestBridging(relbase.groovy, TestBridgeNetworkAbs):
3392 __test__ = True
3393
3394
3395diff --git a/tests/vmtests/test_network_disabled.py b/tests/vmtests/test_network_disabled.py
3396index b19ca64..ea8dae2 100644
3397--- a/tests/vmtests/test_network_disabled.py
3398+++ b/tests/vmtests/test_network_disabled.py
3399@@ -57,6 +57,11 @@ class FocalCurtinDisableNetworkRendering(relbase.focal,
3400 __test__ = True
3401
3402
3403+class GroovyCurtinDisableNetworkRendering(relbase.groovy,
3404+ CurtinDisableNetworkRendering):
3405+ __test__ = True
3406+
3407+
3408 class FocalCurtinDisableCloudInitNetworkingVersion1(
3409 relbase.focal,
3410 CurtinDisableCloudInitNetworkingVersion1
3411@@ -64,9 +69,21 @@ class FocalCurtinDisableCloudInitNetworkingVersion1(
3412 __test__ = True
3413
3414
3415+class GroovyCurtinDisableCloudInitNetworkingVersion1(
3416+ relbase.groovy,
3417+ CurtinDisableCloudInitNetworkingVersion1
3418+):
3419+ __test__ = True
3420+
3421+
3422 class FocalCurtinDisableCloudInitNetworking(relbase.focal,
3423 CurtinDisableCloudInitNetworking):
3424 __test__ = True
3425
3426
3427+class GroovyCurtinDisableCloudInitNetworking(relbase.groovy,
3428+ CurtinDisableCloudInitNetworking):
3429+ __test__ = True
3430+
3431+
3432 # vi: ts=4 expandtab syntax=python
3433diff --git a/tests/vmtests/test_network_ipv6.py b/tests/vmtests/test_network_ipv6.py
3434index 8f0dd54..50a139c 100644
3435--- a/tests/vmtests/test_network_ipv6.py
3436+++ b/tests/vmtests/test_network_ipv6.py
3437@@ -53,9 +53,13 @@ class BionicTestNetworkIPV6(relbase.bionic, TestNetworkIPV6Abs):
3438 __test__ = True
3439
3440
3441-class EoanTestNetworkIPV6(relbase.eoan, TestNetworkIPV6Abs):
3442+class GroovyTestNetworkIPV6(relbase.groovy, TestNetworkIPV6Abs):
3443 __test__ = True
3444
3445+ @TestNetworkIPV6Abs.skip_by_date("1888726", "2020-10-15")
3446+ def test_ip_output(self):
3447+ return super().test_ip_output()
3448+
3449
3450 class Centos66TestNetworkIPV6(centos_relbase.centos66_xenial,
3451 CentosTestNetworkIPV6Abs):
3452diff --git a/tests/vmtests/test_network_ipv6_static.py b/tests/vmtests/test_network_ipv6_static.py
3453index 8a1ba2f..28ff697 100644
3454--- a/tests/vmtests/test_network_ipv6_static.py
3455+++ b/tests/vmtests/test_network_ipv6_static.py
3456@@ -23,11 +23,11 @@ class BionicTestNetworkIPV6Static(relbase.bionic, TestNetworkIPV6StaticAbs):
3457 __test__ = True
3458
3459
3460-class EoanTestNetworkIPV6Static(relbase.eoan, TestNetworkIPV6StaticAbs):
3461+class FocalTestNetworkIPV6Static(relbase.focal, TestNetworkIPV6StaticAbs):
3462 __test__ = True
3463
3464
3465-class FocalTestNetworkIPV6Static(relbase.focal, TestNetworkIPV6StaticAbs):
3466+class GroovyTestNetworkIPV6Static(relbase.groovy, TestNetworkIPV6StaticAbs):
3467 __test__ = True
3468
3469
3470diff --git a/tests/vmtests/test_network_ipv6_vlan.py b/tests/vmtests/test_network_ipv6_vlan.py
3471index d8e4e16..a0bf267 100644
3472--- a/tests/vmtests/test_network_ipv6_vlan.py
3473+++ b/tests/vmtests/test_network_ipv6_vlan.py
3474@@ -22,13 +22,17 @@ class BionicTestNetworkIPV6Vlan(relbase.bionic, TestNetworkIPV6VlanAbs):
3475 __test__ = True
3476
3477
3478-class EoanTestNetworkIPV6Vlan(relbase.eoan, TestNetworkIPV6VlanAbs):
3479+class FocalTestNetworkIPV6Vlan(relbase.focal, TestNetworkIPV6VlanAbs):
3480 __test__ = True
3481
3482
3483-class FocalTestNetworkIPV6Vlan(relbase.focal, TestNetworkIPV6VlanAbs):
3484+class GroovyTestNetworkIPV6Vlan(relbase.groovy, TestNetworkIPV6VlanAbs):
3485 __test__ = True
3486
3487+ @TestNetworkVlanAbs.skip_by_date("1888726", "2020-10-15")
3488+ def test_ip_output(self):
3489+ return super().test_ip_output()
3490+
3491
3492 class Centos66TestNetworkIPV6Vlan(centos_relbase.centos66_xenial,
3493 CentosTestNetworkIPV6VlanAbs):
3494diff --git a/tests/vmtests/test_network_mtu.py b/tests/vmtests/test_network_mtu.py
3495index bf13459..c70b9e0 100644
3496--- a/tests/vmtests/test_network_mtu.py
3497+++ b/tests/vmtests/test_network_mtu.py
3498@@ -137,6 +137,10 @@ class TestNetworkMtuAbs(TestNetworkIPV6Abs):
3499 self._check_iface_subnets('interface7')
3500
3501
3502+class TestNetworkMtuNetworkdAbs(TestNetworkMtuAbs):
3503+ conf_file = "examples/tests/network_mtu_networkd.yaml"
3504+
3505+
3506 class CentosTestNetworkMtuAbs(TestNetworkMtuAbs):
3507 conf_file = "examples/tests/network_mtu.yaml"
3508 extra_collect_scripts = TestNetworkMtuAbs.extra_collect_scripts + [
3509@@ -181,28 +185,16 @@ class TestNetworkMtu(relbase.xenial, TestNetworkMtuAbs):
3510 __test__ = True
3511
3512
3513-class BionicTestNetworkMtu(relbase.bionic, TestNetworkMtuAbs):
3514- conf_file = "examples/tests/network_mtu_networkd.yaml"
3515+class BionicTestNetworkMtu(relbase.bionic, TestNetworkMtuNetworkdAbs):
3516 __test__ = True
3517- # Until systemd is released with the fix for LP:#1671951
3518- add_repos = "ppa:ddstreet/systemd"
3519- upgrade_packages = "cloud-init,systemd"
3520
3521
3522-class EoanTestNetworkMtu(relbase.eoan, TestNetworkMtuAbs):
3523- conf_file = "examples/tests/network_mtu_networkd.yaml"
3524+class FocalTestNetworkMtu(relbase.focal, TestNetworkMtuNetworkdAbs):
3525 __test__ = True
3526- # Until systemd is released with the fix for LP:#1671951
3527- add_repos = "ppa:ddstreet/systemd"
3528- upgrade_packages = "cloud-init,systemd"
3529
3530
3531-class FocalTestNetworkMtu(relbase.focal, TestNetworkMtuAbs):
3532- conf_file = "examples/tests/network_mtu_networkd.yaml"
3533+class GroovyTestNetworkMtu(relbase.groovy, TestNetworkMtuNetworkdAbs):
3534 __test__ = True
3535- # Until systemd is released with the fix for LP:#1671951
3536- add_repos = "ppa:ddstreet/systemd"
3537- upgrade_packages = "cloud-init,systemd"
3538
3539
3540 class Centos66TestNetworkMtu(centos_relbase.centos66_xenial,
3541diff --git a/tests/vmtests/test_network_ovs.py b/tests/vmtests/test_network_ovs.py
3542index 3e23bd0..0cee17e 100644
3543--- a/tests/vmtests/test_network_ovs.py
3544+++ b/tests/vmtests/test_network_ovs.py
3545@@ -34,12 +34,11 @@ class BionicTestNetworkOvs(relbase.bionic, TestNetworkOvsAbs):
3546 __test__ = True
3547
3548
3549-class EoanTestNetworkOvs(relbase.eoan, TestNetworkOvsAbs):
3550+class FocalTestNetworkOvs(relbase.focal, TestNetworkOvsAbs):
3551 __test__ = True
3552
3553
3554-class FocalTestNetworkOvs(relbase.focal, TestNetworkOvsAbs):
3555+class GroovyTestNetworkOvs(relbase.groovy, TestNetworkOvsAbs):
3556 __test__ = True
3557
3558-
3559 # vi: ts=4 expandtab syntax=python
3560diff --git a/tests/vmtests/test_network_static.py b/tests/vmtests/test_network_static.py
3561index 80ff2cd..e0abd54 100644
3562--- a/tests/vmtests/test_network_static.py
3563+++ b/tests/vmtests/test_network_static.py
3564@@ -28,11 +28,11 @@ class BionicTestNetworkStatic(relbase.bionic, TestNetworkStaticAbs):
3565 __test__ = True
3566
3567
3568-class EoanTestNetworkStatic(relbase.eoan, TestNetworkStaticAbs):
3569+class FocalTestNetworkStatic(relbase.focal, TestNetworkStaticAbs):
3570 __test__ = True
3571
3572
3573-class FocalTestNetworkStatic(relbase.focal, TestNetworkStaticAbs):
3574+class GroovyTestNetworkStatic(relbase.groovy, TestNetworkStaticAbs):
3575 __test__ = True
3576
3577
3578diff --git a/tests/vmtests/test_network_static_routes.py b/tests/vmtests/test_network_static_routes.py
3579index dfcbffe..f99d9d5 100644
3580--- a/tests/vmtests/test_network_static_routes.py
3581+++ b/tests/vmtests/test_network_static_routes.py
3582@@ -28,13 +28,13 @@ class BionicTestNetworkStaticRoutes(relbase.bionic,
3583 __test__ = True
3584
3585
3586-class EoanTestNetworkStaticRoutes(relbase.eoan,
3587- TestNetworkStaticRoutesAbs):
3588+class FocalTestNetworkStaticRoutes(relbase.focal,
3589+ TestNetworkStaticRoutesAbs):
3590 __test__ = True
3591
3592
3593-class FocalTestNetworkStaticRoutes(relbase.focal,
3594- TestNetworkStaticRoutesAbs):
3595+class GroovyTestNetworkStaticRoutes(relbase.groovy,
3596+ TestNetworkStaticRoutesAbs):
3597 __test__ = True
3598
3599
3600diff --git a/tests/vmtests/test_network_vlan.py b/tests/vmtests/test_network_vlan.py
3601index 4a8d776..9f1094b 100644
3602--- a/tests/vmtests/test_network_vlan.py
3603+++ b/tests/vmtests/test_network_vlan.py
3604@@ -76,16 +76,17 @@ class BionicTestNetworkVlan(relbase.bionic, TestNetworkVlanAbs):
3605 __test__ = True
3606
3607
3608-class EoanTestNetworkVlan(relbase.eoan, TestNetworkVlanAbs):
3609+class FocalTestNetworkVlan(relbase.focal, TestNetworkVlanAbs):
3610 __test__ = True
3611
3612 def test_ip_output(self):
3613 return super().test_ip_output()
3614
3615
3616-class FocalTestNetworkVlan(relbase.focal, TestNetworkVlanAbs):
3617+class GroovyTestNetworkVlan(relbase.groovy, TestNetworkVlanAbs):
3618 __test__ = True
3619
3620+ @TestNetworkVlanAbs.skip_by_date("1888726", "2020-10-15")
3621 def test_ip_output(self):
3622 return super().test_ip_output()
3623
3624diff --git a/tests/vmtests/test_nvme.py b/tests/vmtests/test_nvme.py
3625index c1576fa..39f9f3c 100644
3626--- a/tests/vmtests/test_nvme.py
3627+++ b/tests/vmtests/test_nvme.py
3628@@ -73,7 +73,7 @@ class BionicTestNvme(relbase.bionic, TestNvmeAbs):
3629 __test__ = True
3630
3631
3632-class EoanTestNvme(relbase.eoan, TestNvmeAbs):
3633+class GroovyTestNvme(relbase.groovy, TestNvmeAbs):
3634 __test__ = True
3635
3636
3637@@ -139,12 +139,11 @@ class BionicTestNvmeBcache(relbase.bionic, TestNvmeBcacheAbs):
3638 __test__ = True
3639
3640
3641-class EoanTestNvmeBcache(relbase.eoan, TestNvmeBcacheAbs):
3642+class FocalTestNvmeBcache(relbase.focal, TestNvmeBcacheAbs):
3643 __test__ = True
3644
3645
3646-@TestNvmeBcacheAbs.skip_by_date("1861941", fixby="2020-07-15")
3647-class FocalTestNvmeBcache(relbase.focal, TestNvmeBcacheAbs):
3648+class GroovyTestNvmeBcache(relbase.groovy, TestNvmeBcacheAbs):
3649 __test__ = True
3650
3651
3652diff --git a/tests/vmtests/test_panic.py b/tests/vmtests/test_panic.py
3653index fe4005e..7b1fdbe 100644
3654--- a/tests/vmtests/test_panic.py
3655+++ b/tests/vmtests/test_panic.py
3656@@ -28,4 +28,9 @@ class TestInstallPanic(VMBaseClass):
3657 class FocalTestInstallPanic(relbase.focal, TestInstallPanic):
3658 __test__ = True
3659
3660+
3661+class GroovyTestInstallPanic(relbase.groovy, TestInstallPanic):
3662+ __test__ = True
3663+
3664+
3665 # vi: ts=4 expandtab syntax=python
3666diff --git a/tests/vmtests/test_pollinate_useragent.py b/tests/vmtests/test_pollinate_useragent.py
3667index ff21f20..ed14719 100644
3668--- a/tests/vmtests/test_pollinate_useragent.py
3669+++ b/tests/vmtests/test_pollinate_useragent.py
3670@@ -61,11 +61,11 @@ class BionicTestPollinateUserAgent(relbase.bionic, TestPollinateUserAgent):
3671 __test__ = True
3672
3673
3674-class EoanTestPollinateUserAgent(relbase.eoan, TestPollinateUserAgent):
3675+class FocalTestPollinateUserAgent(relbase.focal, TestPollinateUserAgent):
3676 __test__ = True
3677
3678
3679-class FocalTestPollinateUserAgent(relbase.focal, TestPollinateUserAgent):
3680+class GroovyTestPollinateUserAgent(relbase.groovy, TestPollinateUserAgent):
3681 __test__ = True
3682
3683
3684diff --git a/tests/vmtests/test_preserve.py b/tests/vmtests/test_preserve.py
3685index f02ba6c..998218c 100644
3686--- a/tests/vmtests/test_preserve.py
3687+++ b/tests/vmtests/test_preserve.py
3688@@ -25,11 +25,11 @@ class BionicTestPreserve(relbase.bionic, TestPreserve):
3689 __test__ = True
3690
3691
3692-class EoanTestPreserve(relbase.eoan, TestPreserve):
3693+class FocalTestPreserve(relbase.focal, TestPreserve):
3694 __test__ = True
3695
3696
3697-class FocalTestPreserve(relbase.focal, TestPreserve):
3698+class GroovyTestPreserve(relbase.groovy, TestPreserve):
3699 __test__ = True
3700
3701
3702diff --git a/tests/vmtests/test_preserve_bcache.py b/tests/vmtests/test_preserve_bcache.py
3703index e2d2a34..bd91c5a 100644
3704--- a/tests/vmtests/test_preserve_bcache.py
3705+++ b/tests/vmtests/test_preserve_bcache.py
3706@@ -56,11 +56,11 @@ class BionicTestPreserveBcache(relbase.bionic, TestPreserveBcache):
3707 __test__ = True
3708
3709
3710-class EoanTestPreserveBcache(relbase.eoan, TestPreserveBcache):
3711+class FocalTestPreserveBcache(relbase.focal, TestPreserveBcache):
3712 __test__ = True
3713
3714
3715-class FocalTestPreserveBcache(relbase.focal, TestPreserveBcache):
3716+class GroovyTestPreserveBcache(relbase.groovy, TestPreserveBcache):
3717 __test__ = True
3718
3719
3720diff --git a/tests/vmtests/test_preserve_lvm.py b/tests/vmtests/test_preserve_lvm.py
3721index 90f15cb..0ed7ad4 100644
3722--- a/tests/vmtests/test_preserve_lvm.py
3723+++ b/tests/vmtests/test_preserve_lvm.py
3724@@ -69,11 +69,11 @@ class BionicTestLvmPreserve(relbase.bionic, TestLvmPreserveAbs):
3725 __test__ = True
3726
3727
3728-class EoanTestLvmPreserve(relbase.eoan, TestLvmPreserveAbs):
3729+class FocalTestLvmPreserve(relbase.focal, TestLvmPreserveAbs):
3730 __test__ = True
3731
3732
3733-class FocalTestLvmPreserve(relbase.focal, TestLvmPreserveAbs):
3734+class GroovyTestLvmPreserve(relbase.groovy, TestLvmPreserveAbs):
3735 __test__ = True
3736
3737
3738diff --git a/tests/vmtests/test_preserve_partition_wipe_vg.py b/tests/vmtests/test_preserve_partition_wipe_vg.py
3739index 96346ff..58b1f65 100644
3740--- a/tests/vmtests/test_preserve_partition_wipe_vg.py
3741+++ b/tests/vmtests/test_preserve_partition_wipe_vg.py
3742@@ -25,11 +25,11 @@ class BionicTestPreserveWipeLvm(relbase.bionic, TestPreserveWipeLvm):
3743 __test__ = True
3744
3745
3746-class EoanTestPreserveWipeLvm(relbase.eoan, TestPreserveWipeLvm):
3747+class FocalTestPreserveWipeLvm(relbase.focal, TestPreserveWipeLvm):
3748 __test__ = True
3749
3750
3751-class FocalTestPreserveWipeLvm(relbase.focal, TestPreserveWipeLvm):
3752+class GroovyTestPreserveWipeLvm(relbase.groovy, TestPreserveWipeLvm):
3753 __test__ = True
3754
3755
3756@@ -48,11 +48,12 @@ class BionicTestPreserveWipeLvmSimple(relbase.bionic,
3757 __test__ = True
3758
3759
3760-class EoanTestPreserveWipeLvmSimple(relbase.eoan, TestPreserveWipeLvmSimple):
3761+class FocalTestPreserveWipeLvmSimple(relbase.focal, TestPreserveWipeLvmSimple):
3762 __test__ = True
3763
3764
3765-class FocalTestPreserveWipeLvmSimple(relbase.focal, TestPreserveWipeLvmSimple):
3766+class GroovyTestPreserveWipeLvmSimple(relbase.groovy,
3767+ TestPreserveWipeLvmSimple):
3768 __test__ = True
3769
3770
3771diff --git a/tests/vmtests/test_preserve_raid.py b/tests/vmtests/test_preserve_raid.py
3772index cf3a6bb..15f2f50 100644
3773--- a/tests/vmtests/test_preserve_raid.py
3774+++ b/tests/vmtests/test_preserve_raid.py
3775@@ -25,11 +25,11 @@ class BionicTestPreserveRAID(relbase.bionic, TestPreserveRAID):
3776 __test__ = True
3777
3778
3779-class EoanTestPreserveRAID(relbase.eoan, TestPreserveRAID):
3780+class FocalTestPreserveRAID(relbase.focal, TestPreserveRAID):
3781 __test__ = True
3782
3783
3784-class FocalTestPreserveRAID(relbase.focal, TestPreserveRAID):
3785+class GroovyTestPreserveRAID(relbase.groovy, TestPreserveRAID):
3786 __test__ = True
3787
3788
3789diff --git a/tests/vmtests/test_raid5_bcache.py b/tests/vmtests/test_raid5_bcache.py
3790index 0f0b87b..3fdb217 100644
3791--- a/tests/vmtests/test_raid5_bcache.py
3792+++ b/tests/vmtests/test_raid5_bcache.py
3793@@ -88,16 +88,16 @@ class BionicTestRaid5Bcache(relbase.bionic, TestMdadmBcacheAbs):
3794 __test__ = True
3795
3796
3797-class EoanTestRaid5Bcache(relbase.eoan, TestMdadmBcacheAbs):
3798- __test__ = True
3799-
3800-
3801 class FocalTestRaid5Bcache(relbase.focal, TestMdadmBcacheAbs):
3802 __test__ = True
3803
3804- @TestMdadmBcacheAbs.skip_by_date("1861941", fixby="2020-07-15")
3805+ @TestMdadmBcacheAbs.skip_by_date("1861941", fixby="2020-09-15")
3806 def test_fstab(self):
3807 return super().test_fstab()
3808
3809
3810+class GroovyTestRaid5Bcache(relbase.groovy, TestMdadmBcacheAbs):
3811+ __test__ = True
3812+
3813+
3814 # vi: ts=4 expandtab syntax=python
3815diff --git a/tests/vmtests/test_reuse_lvm_member.py b/tests/vmtests/test_reuse_lvm_member.py
3816index 749ea24..87afcfb 100644
3817--- a/tests/vmtests/test_reuse_lvm_member.py
3818+++ b/tests/vmtests/test_reuse_lvm_member.py
3819@@ -21,13 +21,13 @@ class BionicTestReuseLVMMemberPartition(relbase.bionic,
3820 __test__ = True
3821
3822
3823-class EoanTestReuseLVMMemberPartition(relbase.eoan,
3824- TestReuseLVMMemberPartition):
3825+class FocalTestReuseLVMMemberPartition(relbase.focal,
3826+ TestReuseLVMMemberPartition):
3827 __test__ = True
3828
3829
3830-class FocalTestReuseLVMMemberPartition(relbase.focal,
3831- TestReuseLVMMemberPartition):
3832+class GroovyTestReuseLVMMemberPartition(relbase.groovy,
3833+ TestReuseLVMMemberPartition):
3834 __test__ = True
3835
3836
3837diff --git a/tests/vmtests/test_reuse_msdos_partitions.py b/tests/vmtests/test_reuse_msdos_partitions.py
3838index f8e20d9..9f18d3c 100644
3839--- a/tests/vmtests/test_reuse_msdos_partitions.py
3840+++ b/tests/vmtests/test_reuse_msdos_partitions.py
3841@@ -18,13 +18,13 @@ class BionicTestReuseMSDOSPartitions(relbase.bionic,
3842 __test__ = True
3843
3844
3845-class EoanTestReuseMSDOSPartitions(relbase.eoan,
3846- TestReuseMSDOSPartitions):
3847+class FocalTestReuseMSDOSPartitions(relbase.focal,
3848+ TestReuseMSDOSPartitions):
3849 __test__ = True
3850
3851
3852-class FocalTestReuseMSDOSPartitions(relbase.focal,
3853- TestReuseMSDOSPartitions):
3854+class GroovyTestReuseMSDOSPartitions(relbase.groovy,
3855+ TestReuseMSDOSPartitions):
3856 __test__ = True
3857
3858
3859diff --git a/tests/vmtests/test_reuse_raid_member.py b/tests/vmtests/test_reuse_raid_member.py
3860index 425105f..7be98f3 100644
3861--- a/tests/vmtests/test_reuse_raid_member.py
3862+++ b/tests/vmtests/test_reuse_raid_member.py
3863@@ -28,11 +28,11 @@ class BionicTestReuseRAIDMember(relbase.bionic, TestReuseRAIDMember):
3864 __test__ = True
3865
3866
3867-class EoanTestReuseRAIDMember(relbase.eoan, TestReuseRAIDMember):
3868+class FocalTestReuseRAIDMember(relbase.focal, TestReuseRAIDMember):
3869 __test__ = True
3870
3871
3872-class FocalTestReuseRAIDMember(relbase.focal, TestReuseRAIDMember):
3873+class GroovyTestReuseRAIDMember(relbase.groovy, TestReuseRAIDMember):
3874 __test__ = True
3875
3876
3877@@ -41,13 +41,13 @@ class BionicTestReuseRAIDMemberPartition(relbase.bionic,
3878 __test__ = True
3879
3880
3881-class EoanTestReuseRAIDMemberPartition(relbase.eoan,
3882- TestReuseRAIDMemberPartition):
3883+class FocalTestReuseRAIDMemberPartition(relbase.focal,
3884+ TestReuseRAIDMemberPartition):
3885 __test__ = True
3886
3887
3888-class FocalTestReuseRAIDMemberPartition(relbase.focal,
3889- TestReuseRAIDMemberPartition):
3890+class GroovyTestReuseRAIDMemberPartition(relbase.groovy,
3891+ TestReuseRAIDMemberPartition):
3892 __test__ = True
3893
3894
3895diff --git a/tests/vmtests/test_reuse_uefi_esp.py b/tests/vmtests/test_reuse_uefi_esp.py
3896index 31c5e7d..46e7ac7 100644
3897--- a/tests/vmtests/test_reuse_uefi_esp.py
3898+++ b/tests/vmtests/test_reuse_uefi_esp.py
3899@@ -3,20 +3,22 @@
3900 from .test_uefi_basic import TestBasicAbs
3901 from .releases import base_vm_classes as relbase
3902 from .releases import centos_base_vm_classes as cent_rbase
3903+from curtin.commands.curthooks import uefi_find_duplicate_entries
3904+from curtin import util
3905
3906
3907 class TestUefiReuseEspAbs(TestBasicAbs):
3908 conf_file = "examples/tests/uefi_reuse_esp.yaml"
3909
3910 def test_efiboot_menu_has_one_distro_entry(self):
3911- efiboot_mgr_content = self.load_collect_file("efibootmgr.out")
3912- distro_lines = [line for line in efiboot_mgr_content.splitlines()
3913- if self.target_distro in line]
3914- print(distro_lines)
3915- self.assertEqual(1, len(distro_lines))
3916+ efi_output = util.parse_efibootmgr(
3917+ self.load_collect_file("efibootmgr.out"))
3918+ duplicates = uefi_find_duplicate_entries(
3919+ grubcfg=None, target=None, efi_output=efi_output)
3920+ print(duplicates)
3921+ self.assertEqual(0, len(duplicates))
3922
3923
3924-@TestUefiReuseEspAbs.skip_by_date("1881030", fixby="2020-07-15")
3925 class Cent70TestUefiReuseEsp(cent_rbase.centos70_bionic, TestUefiReuseEspAbs):
3926 __test__ = True
3927
3928@@ -33,14 +35,14 @@ class BionicTestUefiReuseEsp(relbase.bionic, TestUefiReuseEspAbs):
3929 return super().test_efiboot_menu_has_one_distro_entry()
3930
3931
3932-class EoanTestUefiReuseEsp(relbase.eoan, TestUefiReuseEspAbs):
3933+class FocalTestUefiReuseEsp(relbase.focal, TestUefiReuseEspAbs):
3934 __test__ = True
3935
3936 def test_efiboot_menu_has_one_distro_entry(self):
3937 return super().test_efiboot_menu_has_one_distro_entry()
3938
3939
3940-class FocalTestUefiReuseEsp(relbase.focal, TestUefiReuseEspAbs):
3941+class GroovyTestUefiReuseEsp(relbase.groovy, TestUefiReuseEspAbs):
3942 __test__ = True
3943
3944 def test_efiboot_menu_has_one_distro_entry(self):
3945diff --git a/tests/vmtests/test_simple.py b/tests/vmtests/test_simple.py
3946index b34a6fc..9e71047 100644
3947--- a/tests/vmtests/test_simple.py
3948+++ b/tests/vmtests/test_simple.py
3949@@ -49,14 +49,14 @@ class BionicTestSimple(relbase.bionic, TestSimple):
3950 self.output_files_exist(["netplan.yaml"])
3951
3952
3953-class EoanTestSimple(relbase.eoan, TestSimple):
3954+class FocalTestSimple(relbase.focal, TestSimple):
3955 __test__ = True
3956
3957 def test_output_files_exist(self):
3958 self.output_files_exist(["netplan.yaml"])
3959
3960
3961-class FocalTestSimple(relbase.focal, TestSimple):
3962+class GroovyTestSimple(relbase.groovy, TestSimple):
3963 __test__ = True
3964
3965 def test_output_files_exist(self):
3966@@ -105,14 +105,14 @@ class BionicTestSimpleStorage(relbase.bionic, TestSimpleStorage):
3967 self.output_files_exist(["netplan.yaml"])
3968
3969
3970-class EoanTestSimpleStorage(relbase.eoan, TestSimpleStorage):
3971+class FocalTestSimpleStorage(relbase.focal, TestSimpleStorage):
3972 __test__ = True
3973
3974 def test_output_files_exist(self):
3975 self.output_files_exist(["netplan.yaml"])
3976
3977
3978-class FocalTestSimpleStorage(relbase.focal, TestSimpleStorage):
3979+class GroovyTestSimpleStorage(relbase.groovy, TestSimpleStorage):
3980 __test__ = True
3981
3982 def test_output_files_exist(self):
3983@@ -145,4 +145,11 @@ class FocalTestGrubNoDefaults(relbase.focal, TestGrubNoDefaults):
3984 self.output_files_exist(["netplan.yaml"])
3985
3986
3987+class GroovyTestGrubNoDefaults(relbase.groovy, TestGrubNoDefaults):
3988+ __test__ = True
3989+
3990+ def test_output_files_exist(self):
3991+ self.output_files_exist(["netplan.yaml"])
3992+
3993+
3994 # vi: ts=4 expandtab syntax=python
3995diff --git a/tests/vmtests/test_uefi_basic.py b/tests/vmtests/test_uefi_basic.py
3996index 90940dd..932c1c8 100644
3997--- a/tests/vmtests/test_uefi_basic.py
3998+++ b/tests/vmtests/test_uefi_basic.py
3999@@ -89,6 +89,11 @@ class PreciseHWETUefiTestBasic(relbase.precise_hwe_t, PreciseUefiTestBasic):
4000 __test__ = False
4001
4002
4003+class TrustyHWEXUefiTestBasic(relbase.trusty_hwe_x, TestBasicAbs):
4004+ supported_releases = ['trusty'] # avoid unsupported release skiptest
4005+ __test__ = False
4006+
4007+
4008 class XenialGAUefiTestBasic(relbase.xenial_ga, TestBasicAbs):
4009 __test__ = True
4010
4011@@ -105,11 +110,11 @@ class BionicUefiTestBasic(relbase.bionic, TestBasicAbs):
4012 __test__ = True
4013
4014
4015-class EoanUefiTestBasic(relbase.eoan, TestBasicAbs):
4016+class FocalUefiTestBasic(relbase.focal, TestBasicAbs):
4017 __test__ = True
4018
4019
4020-class FocalUefiTestBasic(relbase.focal, TestBasicAbs):
4021+class GroovyUefiTestBasic(relbase.groovy, TestBasicAbs):
4022 __test__ = True
4023
4024
4025@@ -128,12 +133,12 @@ class BionicUefiTestBasic4k(relbase.bionic, TestBasicAbs):
4026 disk_block_size = 4096
4027
4028
4029-class EoanUefiTestBasic4k(relbase.eoan, TestBasicAbs):
4030+class FocalUefiTestBasic4k(relbase.focal, TestBasicAbs):
4031 __test__ = True
4032 disk_block_size = 4096
4033
4034
4035-class FocalUefiTestBasic4k(relbase.focal, TestBasicAbs):
4036+class GroovyUefiTestBasic4k(relbase.groovy, TestBasicAbs):
4037 __test__ = True
4038 disk_block_size = 4096
4039
4040diff --git a/tests/vmtests/test_zfsroot.py b/tests/vmtests/test_zfsroot.py
4041index c9c73a3..952bf7b 100644
4042--- a/tests/vmtests/test_zfsroot.py
4043+++ b/tests/vmtests/test_zfsroot.py
4044@@ -96,12 +96,12 @@ class BionicTestZfsRoot(relbase.bionic, TestZfsRootAbs):
4045 __test__ = True
4046
4047
4048-class EoanTestZfsRoot(relbase.eoan, TestZfsRootAbs):
4049+class FocalTestZfsRoot(relbase.focal, TestZfsRootAbs):
4050 __test__ = True
4051 mem = 4096
4052
4053
4054-class FocalTestZfsRoot(relbase.focal, TestZfsRootAbs):
4055+class GroovyTestZfsRoot(relbase.groovy, TestZfsRootAbs):
4056 __test__ = True
4057 mem = 4096
4058
4059@@ -125,13 +125,12 @@ class BionicTestZfsRootFsType(relbase.bionic, TestZfsRootFsTypeAbs):
4060 __test__ = True
4061
4062
4063-class EoanTestZfsRootFsType(relbase.eoan, TestZfsRootFsTypeAbs):
4064+class FocalTestZfsRootFsType(relbase.focal, TestZfsRootFsTypeAbs):
4065 __test__ = True
4066 mem = 4096
4067
4068
4069-class FocalTestZfsRootFsType(relbase.focal, TestZfsRootFsTypeAbs):
4070+class GroovyTestZfsRootFsType(relbase.groovy, TestZfsRootFsTypeAbs):
4071 __test__ = True
4072- mem = 4096
4073
4074 # vi: ts=4 expandtab syntax=python
4075diff --git a/tools/curtainer b/tools/curtainer
4076index 466d719..b24884b 100755
4077--- a/tools/curtainer
4078+++ b/tools/curtainer
4079@@ -153,20 +153,35 @@ main() {
4080 fi
4081 getsource="${getsource%/}"
4082
4083- lxc launch "$src" "$name" || fail "failed lxc launch $src $name"
4084+ # launch container; mask snapd.seeded.service; not needed
4085+ {
4086+ lxc init "$src" "$name" &&
4087+ lxc file push \
4088+ /dev/null ${name}/etc/systemd/system/snapd.seeded.service &&
4089+ lxc start ${name}
4090+ } || fail "failed lxc launch $src $name"
4091 CONTAINER=$name
4092
4093 wait_for_ready "$name" $maxwait $VERBOSITY ||
4094 fail "$name did not become ready after $maxwait"
4095
4096 inside "$name" which eatmydata >/dev/null || eatmydata=""
4097+ release=$(inside $name lsb_release -sc) ||
4098+ fail "$name did not have a lsb release codename"
4099+
4100+ # curtin depends on zfsutils-linux via probert-storage, but zfsutils-linux
4101+ # can't be installed in an unprivileged container as it fails to start
4102+ # the zfs-mount and zfs-share services as /dev/zfs is missing. We do
4103+ # not actually need ZFS to work in the container, so the problem can be
4104+ # worked around by masking the services before the package is installed.
4105+ inside "$name" systemctl mask zfs-mount || fail "failed to mask zfs-mount"
4106+ inside "$name" systemctl mask zfs-share || fail "failed to mask zfs-share"
4107
4108 if $proposed; then
4109 mirror=$(inside $name awk '$1 == "deb" { print $2; exit(0); }' \
4110- /etc/apt/sources.list) &&
4111- rel=$(inside $name lsb_release -sc) ||
4112+ /etc/apt/sources.list) ||
4113 fail "failed to get mirror in $name"
4114- line="$mirror $rel-proposed main universe"
4115+ line="$mirror $release-proposed main universe"
4116 local fname="/etc/apt/sources.list.d/proposed.list"
4117 debug 1 "enabling proposed in $fname: deb $line"
4118 inside "$name" sh -c "echo deb $line > $fname" ||
4119@@ -179,9 +194,30 @@ main() {
4120 if $daily; then
4121 local daily_ppa="ppa:curtin-dev/daily"
4122 debug 1 "enabling daily: $daily_ppa"
4123- inside "$name" add-apt-repository --enable-source --yes \
4124- "${daily_ppa}" ||
4125- fail "failed add-apt-repository for daily."
4126+ local addaptrepo="add-apt-repository"
4127+ inside "$name" which $addaptrepo >/dev/null || addaptrepo=""
4128+ if [ -n "${addaptrepo}" ]; then
4129+ inside "$name" ${addaptrepo} --enable-source --yes --no-update \
4130+ "${daily_ppa}" ||
4131+ fail "failed add-apt-repository for daily."
4132+ else
4133+ # https://launchpad.net/~curtin-dev/+archive/ubuntu/daily
4134+ local url="http://ppa.launchpad.net/curtin-dev/daily/ubuntu"
4135+ local lfile="/etc/apt/sources.list.d/curtin-daily-ppa.list"
4136+ local kfile="/etc/apt/trusted.gpg.d/curtin-daily-ppa.asc"
4137+ local key="0x1bc30f715a3b861247a81a5e55fe7c8c0165013e"
4138+ local keyserver="keyserver.ubuntu.com"
4139+ local keyurl="https://${keyserver}/pks/lookup?op=get&search=${key}"
4140+ inside "$name" sh -c "
4141+ echo deb $url $release main > $lfile &&
4142+ wget -q \"$keyurl\" -O $kfile" ||
4143+ fail "failed to add $daily_ppa repository manually"
4144+ if [ "$getsource" != "none" ]; then
4145+ inside "$name" sh -c "
4146+ echo deb-src $url $release main >> $lfile" ||
4147+ fail "failed adding daily ppa deb-src to $lfile"
4148+ fi
4149+ fi
4150 fi
4151
4152 line="Acquire::Languages \"none\";"
4153diff --git a/tools/jenkins-runner b/tools/jenkins-runner
4154index 253b722..375dc3c 100755
4155--- a/tools/jenkins-runner
4156+++ b/tools/jenkins-runner
4157@@ -5,6 +5,7 @@ topdir="${CURTIN_VMTEST_TOPDIR:-${WORKSPACE:-$PWD}/output}"
4158 pkeep=${CURTIN_VMTEST_KEEP_DATA_PASS:-logs,collect}
4159 fkeep=${CURTIN_VMTEST_KEEP_DATA_FAIL:-logs,collect}
4160 reuse=${CURTIN_VMTEST_REUSE_TOPDIR:-0}
4161+shuffle=${CURTIN_VMTEST_SHUFFLE_TESTS:-1}
4162 declare -i ltimeout=${CURTIN_VMTEST_IMAGE_LOCK_TIMEOUT:-"-1"}
4163 export CURTIN_VMTEST_TAR_DISKS=${CURTIN_VMTEST_TAR_DISKS:-0}
4164 export CURTIN_VMTEST_REUSE_TOPDIR=$reuse
4165@@ -14,6 +15,7 @@ export CURTIN_VMTEST_KEEP_DATA_FAIL=$fkeep
4166 export CURTIN_VMTEST_TOPDIR="$topdir"
4167 export CURTIN_VMTEST_LOG="${CURTIN_VMTEST_LOG:-$topdir/debug.log}"
4168 export CURTIN_VMTEST_PARALLEL=${CURTIN_VMTEST_PARALLEL:-0}
4169+export CURTIN_VMTEST_SHUFFLE_TESTS=$shuffle
4170 export IMAGE_DIR=${IMAGE_DIR:-/srv/images}
4171
4172 # empty TGT_* variables in current env to avoid killing a pid we didn't start.
4173@@ -50,6 +52,15 @@ if [ "$reuse" != "1" ]; then
4174 mkdir -p "$topdir" || fail "failed mkdir $topdir"
4175 fi
4176
4177+# Use 'shuf' to randomize test case execution order
4178+if [ "$shuffle" == "1" ]; then
4179+ SHUFFLE="shuf"
4180+else
4181+ # when disabled just repeat the input to output
4182+ SHUFFLE="tee"
4183+fi
4184+
4185+
4186 start_s=$(date +%s)
4187 parallel=${CURTIN_VMTEST_PARALLEL}
4188 ntfilters=( )
4189@@ -83,9 +94,10 @@ if [ "${#tests[@]}" -ne 0 -a "${#ntfilters[@]}" -ne 0 ]; then
4190 error "test arguments provided were: ${#tests[*]}"
4191 fail
4192 elif [ "${#tests[@]}" -eq 0 -a "${#ntfilters[@]}" -eq 0 ]; then
4193- tests=( tests/vmtests )
4194+ # run filter without args to enumerate all tests and maybe shuffle
4195+ tests=( $(./tools/vmtest-filter | ${SHUFFLE}) )
4196 elif [ "${#ntfilters[@]}" -ne 0 ]; then
4197- tests=( $(./tools/vmtest-filter "${ntfilters[@]}") )
4198+ tests=( $(./tools/vmtest-filter "${ntfilters[@]}" | ${SHUFFLE}) )
4199 if [ "${#tests[@]}" -eq 0 ]; then
4200 error "Failed to find any tests with filter(s): \"${ntfilters[*]}\""
4201 fail "Try testing filters with: ./tools/vmtest-filter ${ntfilters[*]}"
4202diff --git a/tools/run-pep8 b/tools/run-pep8
4203index c27a96c..227129c 100755
4204--- a/tools/run-pep8
4205+++ b/tools/run-pep8
4206@@ -9,10 +9,10 @@ pycheck_dirs=(
4207 "tools/block-discover-to-config"
4208 "tools/curtin-log-print"
4209 "tools/noproxy"
4210- "tools/remove-vmtest-release"
4211 "tools/schema-validate-storage"
4212 "tools/ssh-keys-list"
4213 "tools/vmtest-filter"
4214+ "tools/vmtest-remove-release"
4215 "tools/vmtest-sync-images"
4216 "tools/webserv"
4217 "tools/write-curtin"
4218diff --git a/tools/remove-vmtest-release b/tools/vmtest-remove-release
4219similarity index 97%
4220rename from tools/remove-vmtest-release
4221rename to tools/vmtest-remove-release
4222old mode 100644
4223new mode 100755
4224index 8ab9b2e..d2c5f83
4225--- a/tools/remove-vmtest-release
4226+++ b/tools/vmtest-remove-release
4227@@ -36,7 +36,7 @@ def clean_file(fname, distro):
4228
4229 if __name__ == "__main__":
4230 parser = argparse.ArgumentParser(
4231- prog="remove-vmtest-release",
4232+ prog="vmtest-remove-release",
4233 description="Tool to remove vmtest classes by distro release")
4234 parser.add_argument('--distro-release', '-d',
4235 action='store', required=True)
4236diff --git a/tox.ini b/tox.ini
4237index 72d56d4..04b43b6 100644
4238--- a/tox.ini
4239+++ b/tox.ini
4240@@ -60,7 +60,7 @@ commands = {envpython} -m pyflakes {posargs:curtin/ tests/ tools/}
4241 # set basepython because tox 1.6 (trusty) does not support generated environments
4242 basepython = python3
4243 deps = {[testenv]deps}
4244- pylint==2.3.1
4245+ pylint==2.6.0
4246 git+https://git.launchpad.net/simplestreams
4247 commands = {envpython} -m pylint --errors-only {posargs:curtin tests/vmtests}
4248

Subscribers

People subscribed via source and target branches