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

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

Commit message

Release curtin version 20.2-0ubuntu1~16.04.1

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

On this Xenial branch we probably should add the following to debian/changelog

   * debian/control: drop Build-Depends on linting tools, they haven't been
   * debian/*: drop python-curtin, making the package Python 3-only.
   * debian/rules get in sync with 0.1.0~bzr470-0ubuntu1 [Paride Legovini]

I saw these lines automatically added to my debian/changelog due to a `new-upstream-snapshot 20.2`

Revision history for this message
Chad Smith (chad.smith) :
review: Needs Fixing
Revision history for this message
Chad Smith (chad.smith) wrote :

Oops take back that comment as you mentioned in MM. These commits were forward-ported to master, so we don't need to catalog them here.

review: Approve

Preview Diff

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

Subscribers

People subscribed via source and target branches