Merge ~paride/curtin:20.1-29-g81144052-0ubuntu1 into curtin:ubuntu/devel

Proposed by Paride Legovini
Status: Merged
Merged at revision: 602618759f4ab30847599de23b598339a1c99ebc
Proposed branch: ~paride/curtin:20.1-29-g81144052-0ubuntu1
Merge into: curtin:ubuntu/devel
Diff against target: 4246 lines (+1378/-939)
84 files modified
Makefile (+1/-1)
curtin/__init__.py (+4/-0)
curtin/block/__init__.py (+16/-1)
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 (+44/-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)
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 (+100/-22)
tests/vmtests/releases.py (+9/-0)
tests/vmtests/test_apt_config_cmd.py (+5/-1)
tests/vmtests/test_basic.py (+23/-10)
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/-6)
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/-4)
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/-10)
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)
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve
Ryan Harper Approve
Review via email: mp+390692@code.launchpad.net

Commit message

releasing curtin version 20.1-29-g81144052-0ubuntu1

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

LGTM!

(crispyboi) curtin % git fetch upstream
X11 forwarding request failed on channel 0
remote: Enumerating objects: 110, done.
remote: Counting objects: 100% (110/110), done.
remote: Compressing objects: 100% (76/76), done.
remote: Total 76 (delta 62), reused 0 (delta 0)
Unpacking objects: 100% (76/76), 14.69 KiB | 214.00 KiB/s, done.
From git+ssh://git.launchpad.net/curtin
   1304d3ea..81144052 master -> upstream/master
(crispyboi) curtin % git bn
ubuntu/devel
(crispyboi) curtin % git reset --hard upstream/ubuntu/devel
HEAD is now at 34894cc7 releasing curtin version 20.1-0ubuntu1
(crispyboi) curtin % pushd ~/work/git/uss-tableflip/
~/work/git/uss-tableflip ~/work/git/curtin/release/curtin
(crispyboi) uss-tableflip % git bn
master
(crispyboi) uss-tableflip % git pull
X11 forwarding request failed on channel 0
remote: Enumerating objects: 21, done.
remote: Counting objects: 100% (21/21), done.
remote: Compressing objects: 100% (7/7), done.
remote: Total 21 (delta 14), reused 17 (delta 14), pack-reused 0
Unpacking objects: 100% (21/21), 3.44 KiB | 251.00 KiB/s, done.
From github.com:CanonicalLtd/uss-tableflip
   c0641f9c..575fd654 master -> origin/master
Updating c0641f9c..575fd654
Fast-forward
 scripts/cla-query | 9 +++------
 scripts/log2dch | 10 +++++++++-
 scripts/lp-attach-file | 2 +-
 scripts/sru-changelog-format | 69 +++++++++++++++++++++++++++++++++++----------------------------------
 4 files changed, 48 insertions(+), 42 deletions(-)
(crispyboi) uss-tableflip % popd
~/work/git/curtin/release/curtin
(crispyboi) curtin % ~/work/git/uss-tableflip/scripts/new-upstream-snapshot
updating remote upstream for default commitish upstream/master.
X11 forwarding request failed on channel 0
diff --git a/debian/changelog b/debian/changelog
index e1a75e18..ba612827 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,47 @@
+curtin (20.1-29-g81144052-0ubuntu1) UNRELEASED; urgency=medium
+
+ * New upstream snapshot.
+ - vmtest: Fix multiple issues with vmtest on master
+ - Refactor uefi_remove_duplicates into find/remove functions for reuse
+ - distro: run apt-get clean after dist-upgrade, install, upgrade
+ - curthooks: UEFI remove dupes: don't remove BootCurrent, config option
+ (LP: #1894217)
+ - Pin the dependency on pyrsistent [Paride Legovini]
+ - restore default of grub.update_nvram to True in install_grub
+ [Michael Hudson-Doyle]
+ - block: disk_to_byid_path handle missing /dev/disk/by-id directory
+ (LP: #1876258)
+ - UEFI: Handle missing BootCurrent entry when reordering UEFI entries
+ (LP: #1789650)
+ - dasd: fix off-by-one device_id devno range check [Paride Legovini]
+ - curthooks: uefi_find_grub_device_ids handle type:mount without path
+ (LP: #1892242)
+ - netplan openvswitch yaml changed (LP: #1891608)
+ - tools/curtainer: do not wait for snapd.seeded.service
+ - tools/curtainer: enable using ubuntu-minimal images
+ - vmtests: add Groovy [Paride Legovini]
+ - Drop the Eoan vmtests (EOL) [Paride Legovini]
+ - tools: rename remove-vmtest-release to vmtest-remove-release
+ - Snooze the tests failing because...

Read more...

review: Approve
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)

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

Subscribers

People subscribed via source and target branches