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

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

Commit message

Release curtin version 20.2-0ubuntu1~20.04.1

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

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

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/Makefile b/Makefile
index 68a3ad3..187132c 100644
--- a/Makefile
+++ b/Makefile
@@ -9,7 +9,7 @@ ifeq ($(COVERAGE), 1)
9endif9endif
10CURTIN_VMTEST_IMAGE_SYNC ?= False10CURTIN_VMTEST_IMAGE_SYNC ?= False
11export CURTIN_VMTEST_IMAGE_SYNC11export CURTIN_VMTEST_IMAGE_SYNC
12noseopts ?= -vv --nologcapture12noseopts ?= -vv
13pylintopts ?= --rcfile=pylintrc --errors-only13pylintopts ?= --rcfile=pylintrc --errors-only
14target_dirs ?= curtin tests tools14target_dirs ?= curtin tests tools
1515
diff --git a/curtin/__init__.py b/curtin/__init__.py
index 2e1a0ed..82b6f26 100644
--- a/curtin/__init__.py
+++ b/curtin/__init__.py
@@ -8,6 +8,8 @@ KERNEL_CMDLINE_COPY_TO_INSTALL_SEP = "---"
8# can determine which features are supported. Each entry should have8# can determine which features are supported. Each entry should have
9# a consistent meaning.9# a consistent meaning.
10FEATURES = [10FEATURES = [
11 # curtin supports creating swapfiles on btrfs, if possible
12 'BTRFS_SWAPFILE',
11 # curtin can apply centos networking via centos_apply_network_config13 # curtin can apply centos networking via centos_apply_network_config
12 'CENTOS_APPLY_NETWORK_CONFIG',14 'CENTOS_APPLY_NETWORK_CONFIG',
13 # curtin can configure centos storage devices and boot devices15 # curtin can configure centos storage devices and boot devices
@@ -32,8 +34,10 @@ FEATURES = [
32 'APT_CONFIG_V1',34 'APT_CONFIG_V1',
33 # has version module35 # has version module
34 'HAS_VERSION_MODULE',36 'HAS_VERSION_MODULE',
37 # uefi_reoder has fallback support if BootCurrent is missing
38 'UEFI_REORDER_FALLBACK_SUPPORT',
35]39]
3640
37__version__ = "20.1"41__version__ = "20.2"
3842
39# vi: ts=4 expandtab syntax=python43# vi: ts=4 expandtab syntax=python
diff --git a/curtin/block/__init__.py b/curtin/block/__init__.py
index 35e3a64..0cf0866 100644
--- a/curtin/block/__init__.py
+++ b/curtin/block/__init__.py
@@ -1,5 +1,5 @@
1# This file is part of curtin. See LICENSE file for copyright and license info.1# This file is part of curtin. See LICENSE file for copyright and license info.
22import re
3from contextlib import contextmanager3from contextlib import contextmanager
4import errno4import errno
5import itertools5import itertools
@@ -67,6 +67,19 @@ def dev_path(devname):
67 return '/dev/' + devname67 return '/dev/' + devname
6868
6969
70def md_path(mdname):
71 """ Convert device name to path in /dev/md """
72 full_mdname = dev_path(mdname)
73 if full_mdname.startswith('/dev/md/'):
74 return full_mdname
75 elif re.match(r'/dev/md\d+$', full_mdname):
76 return full_mdname
77 elif '/' in mdname:
78 raise ValueError("Invalid RAID device name: {}".format(mdname))
79 else:
80 return '/dev/md/{}'.format(mdname)
81
82
70def path_to_kname(path):83def path_to_kname(path):
71 """84 """
72 converts a path in /dev or a path in /sys/block to the device kname,85 converts a path in /dev or a path in /sys/block to the device kname,
@@ -320,7 +333,7 @@ def dmsetup_info(devname):
320 ','.join(fields), '--noheading',333 ','.join(fields), '--noheading',
321 '--separator', _SEP], capture=True)334 '--separator', _SEP], capture=True)
322 except util.ProcessExecutionError as e:335 except util.ProcessExecutionError as e:
323 LOG.error('Failed to run dmsetup info:', e)336 LOG.error('Failed to run dmsetup info: %s', e)
324 return {}337 return {}
325338
326 values = out.strip().split(_SEP)339 values = out.strip().split(_SEP)
@@ -840,6 +853,8 @@ def _get_dev_disk_by_prefix(prefix):
840 '/dev/sda1': '/dev/disk/<prefix>/virtio-aaaa-part1',853 '/dev/sda1': '/dev/disk/<prefix>/virtio-aaaa-part1',
841 }854 }
842 """855 """
856 if not os.path.exists(prefix):
857 return {}
843 return {858 return {
844 os.path.realpath(bypfx): bypfx859 os.path.realpath(bypfx): bypfx
845 for bypfx in [os.path.join(prefix, path)860 for bypfx in [os.path.join(prefix, path)
diff --git a/curtin/block/dasd.py b/curtin/block/dasd.py
index 682f9d3..b7008f6 100644
--- a/curtin/block/dasd.py
+++ b/curtin/block/dasd.py
@@ -269,9 +269,9 @@ def _valid_device_id(device_id):
269 if not (0 <= int(dsn, 16) < 256):269 if not (0 <= int(dsn, 16) < 256):
270 raise ValueError("device_id invalid: dsn not in 0-255: '%s'" % dsn)270 raise ValueError("device_id invalid: dsn not in 0-255: '%s'" % dsn)
271271
272 if not (0 <= int(dev.lower(), 16) < 65535):272 if not (0 <= int(dev.lower(), 16) <= 65535):
273 raise ValueError(273 raise ValueError(
274 "device_id invalid: devno not in 0-0x10000: '%s'" % dev)274 "device_id invalid: devno not in 0-0xffff: '%s'" % dev)
275275
276 return True276 return True
277277
diff --git a/curtin/block/multipath.py b/curtin/block/multipath.py
index 9c7f510..7ad1791 100644
--- a/curtin/block/multipath.py
+++ b/curtin/block/multipath.py
@@ -7,7 +7,7 @@ from curtin import udev
7SHOW_PATHS_FMT = ("device='%d' serial='%z' multipath='%m' host_wwpn='%N' "7SHOW_PATHS_FMT = ("device='%d' serial='%z' multipath='%m' host_wwpn='%N' "
8 "target_wwnn='%n' host_wwpn='%R' target_wwpn='%r' "8 "target_wwnn='%n' host_wwpn='%R' target_wwpn='%r' "
9 "host_adapter='%a'")9 "host_adapter='%a'")
10SHOW_MAPS_FMT = "name=%n multipath='%w' sysfs='%d' paths='%N'"10SHOW_MAPS_FMT = "name='%n' multipath='%w' sysfs='%d' paths='%N'"
1111
1212
13def _extract_mpath_data(cmd, show_verb):13def _extract_mpath_data(cmd, show_verb):
diff --git a/curtin/commands/block_meta.py b/curtin/commands/block_meta.py
index ff0f2e9..dee73b1 100644
--- a/curtin/commands/block_meta.py
+++ b/curtin/commands/block_meta.py
@@ -502,7 +502,7 @@ def get_path_to_storage_volume(volume, storage_config):
502 elif vol.get('type') == "raid":502 elif vol.get('type') == "raid":
503 # For raid partitions, block device is at /dev/mdX503 # For raid partitions, block device is at /dev/mdX
504 name = vol.get('name')504 name = vol.get('name')
505 volume_path = os.path.join("/dev", name)505 volume_path = block.md_path(name)
506506
507 elif vol.get('type') == "bcache":507 elif vol.get('type') == "bcache":
508 # For bcache setups, the only reliable way to determine the name of the508 # For bcache setups, the only reliable way to determine the name of the
@@ -1485,7 +1485,7 @@ def raid_handler(info, storage_config):
1485 devices = info.get('devices')1485 devices = info.get('devices')
1486 raidlevel = info.get('raidlevel')1486 raidlevel = info.get('raidlevel')
1487 spare_devices = info.get('spare_devices')1487 spare_devices = info.get('spare_devices')
1488 md_devname = block.dev_path(info.get('name'))1488 md_devname = block.md_path(info.get('name'))
1489 preserve = config.value_as_boolean(info.get('preserve'))1489 preserve = config.value_as_boolean(info.get('preserve'))
1490 if not devices:1490 if not devices:
1491 raise ValueError("devices for raid must be specified")1491 raise ValueError("devices for raid must be specified")
@@ -1744,7 +1744,12 @@ def get_device_paths_from_storage_config(storage_config):
1744 dpaths = []1744 dpaths = []
1745 for (k, v) in storage_config.items():1745 for (k, v) in storage_config.items():
1746 if v.get('type') in ['disk', 'partition']:1746 if v.get('type') in ['disk', 'partition']:
1747 if config.value_as_boolean(v.get('wipe')):1747 wipe = config.value_as_boolean(v.get('wipe'))
1748 preserve = config.value_as_boolean(v.get('preserve'))
1749 if v.get('type') == 'disk' and all([wipe, preserve]):
1750 msg = 'type:disk id=%s has both wipe and preserve' % v['id']
1751 raise RuntimeError(msg)
1752 if wipe:
1748 try:1753 try:
1749 # skip paths that do not exit, nothing to wipe1754 # skip paths that do not exit, nothing to wipe
1750 dpath = get_path_to_storage_volume(k, storage_config)1755 dpath = get_path_to_storage_volume(k, storage_config)
diff --git a/curtin/commands/curthooks.py b/curtin/commands/curthooks.py
index d66afa7..4cf7301 100644
--- a/curtin/commands/curthooks.py
+++ b/curtin/commands/curthooks.py
@@ -85,6 +85,8 @@ do_initrd = yes
85link_in_boot = {inboot}85link_in_boot = {inboot}
86"""86"""
8787
88UEFI_BOOT_ENTRY_IS_NETWORK = r'.*(Network|PXE|NIC|Ethernet|LAN|IP4|IP6)+.*'
89
8890
89def do_apt_config(cfg, target):91def do_apt_config(cfg, target):
90 cfg = apt_config.translate_old_apt_features(cfg)92 cfg = apt_config.translate_old_apt_features(cfg)
@@ -411,6 +413,7 @@ def install_kernel(cfg, target):
411def uefi_remove_old_loaders(grubcfg, target):413def uefi_remove_old_loaders(grubcfg, target):
412 """Removes the old UEFI loaders from efibootmgr."""414 """Removes the old UEFI loaders from efibootmgr."""
413 efi_output = util.get_efibootmgr(target)415 efi_output = util.get_efibootmgr(target)
416 LOG.debug('UEFI remove old olders efi output:\n%s', efi_output)
414 current_uefi_boot = efi_output.get('current', None)417 current_uefi_boot = efi_output.get('current', None)
415 old_efi_entries = {418 old_efi_entries = {
416 entry: info419 entry: info
@@ -437,18 +440,90 @@ def uefi_remove_old_loaders(grubcfg, target):
437 "should be removed.", info['name'])440 "should be removed.", info['name'])
438441
439442
440def uefi_reorder_loaders(grubcfg, target):443def uefi_boot_entry_is_network(boot_entry_name):
444 """
445 Return boolean if boot entry name looks like a known network entry.
446 """
447 return re.match(UEFI_BOOT_ENTRY_IS_NETWORK,
448 boot_entry_name, re.IGNORECASE) is not None
449
450
451def _reorder_new_entry(boot_order, efi_output, efi_orig=None, variant=None):
452 """
453 Reorder the EFI boot menu as follows
454
455 1. All PXE/Network boot entries
456 2. The newly installed entry variant (ubuntu/centos)
457 3. The other items in the boot order that are not in [1, 2]
458
459 returns a list of bootnum strings
460 """
461
462 if not boot_order:
463 raise RuntimeError('boot_order is not a list')
464
465 if efi_orig is None:
466 raise RuntimeError('Missing efi_orig boot dictionary')
467
468 if variant is None:
469 variant = ""
470
471 net_boot = []
472 other = []
473 target = []
474
475 LOG.debug("UEFI previous boot order: %s", efi_orig['order'])
476 LOG.debug("UEFI current boot order: %s", boot_order)
477 new_entries = list(set(boot_order).difference(set(efi_orig['order'])))
478 if new_entries:
479 LOG.debug("UEFI Found new boot entries: %s", new_entries)
480 LOG.debug('UEFI Looking for installed entry variant=%s', variant.lower())
481 for bootnum in boot_order:
482 entry = efi_output['entries'][bootnum]
483 if uefi_boot_entry_is_network(entry['name']):
484 net_boot.append(bootnum)
485 else:
486 if entry['name'].lower() == variant.lower():
487 target.append(bootnum)
488 else:
489 other.append(bootnum)
490
491 if net_boot:
492 LOG.debug("UEFI found netboot entries: %s", net_boot)
493 if other:
494 LOG.debug("UEFI found other entries: %s", other)
495 if target:
496 LOG.debug("UEFI found target entry: %s", target)
497 else:
498 LOG.debug("UEFI Did not find an entry with variant=%s",
499 variant.lower())
500 new_order = net_boot + target + other
501 if boot_order == new_order:
502 LOG.debug("UEFI Current and Previous bootorders match")
503 return new_order
504
505
506def uefi_reorder_loaders(grubcfg, target, efi_orig=None, variant=None):
441 """Reorders the UEFI BootOrder to place BootCurrent first.507 """Reorders the UEFI BootOrder to place BootCurrent first.
442508
443 The specifically doesn't try to do to much. The order in which grub places509 The specifically doesn't try to do to much. The order in which grub places
444 a new EFI loader is up to grub. This only moves the BootCurrent to the510 a new EFI loader is up to grub. This only moves the BootCurrent to the
445 front of the BootOrder.511 front of the BootOrder.
512
513 In some systems, BootCurrent may not be set/present. In this case
514 curtin will attempt to place the new boot entry created when grub
515 is installed after the the previous first entry (before we installed grub).
516
446 """517 """
447 if grubcfg.get('reorder_uefi', True):518 if grubcfg.get('reorder_uefi', True):
448 efi_output = util.get_efibootmgr(target=target)519 efi_output = util.get_efibootmgr(target=target)
520 LOG.debug('UEFI efibootmgr output after install:\n%s', efi_output)
449 currently_booted = efi_output.get('current', None)521 currently_booted = efi_output.get('current', None)
450 boot_order = efi_output.get('order', [])522 boot_order = efi_output.get('order', [])
451 if currently_booted:523 new_boot_order = None
524 force_fallback_reorder = config.value_as_boolean(
525 grubcfg.get('reorder_uefi_force_fallback', False))
526 if currently_booted and force_fallback_reorder is False:
452 if currently_booted in boot_order:527 if currently_booted in boot_order:
453 boot_order.remove(currently_booted)528 boot_order.remove(currently_booted)
454 boot_order = [currently_booted] + boot_order529 boot_order = [currently_booted] + boot_order
@@ -456,6 +531,23 @@ def uefi_reorder_loaders(grubcfg, target):
456 LOG.debug(531 LOG.debug(
457 "Setting currently booted %s as the first "532 "Setting currently booted %s as the first "
458 "UEFI loader.", currently_booted)533 "UEFI loader.", currently_booted)
534 else:
535 reason = (
536 "config 'reorder_uefi_force_fallback' is True" if
537 force_fallback_reorder else "missing 'BootCurrent' value")
538 LOG.debug("Using fallback UEFI reordering: " + reason)
539 if len(boot_order) < 2:
540 LOG.debug(
541 'UEFI BootOrder has less than 2 entries, cannot reorder')
542 return
543 # look at efi entries before we added one to find the new addition
544 new_order = _reorder_new_entry(
545 copy.deepcopy(boot_order), efi_output, efi_orig, variant)
546 if new_order != boot_order:
547 new_boot_order = ','.join(new_order)
548 else:
549 LOG.debug("UEFI No changes to boot order.")
550 if new_boot_order:
459 LOG.debug(551 LOG.debug(
460 "New UEFI boot order: %s", new_boot_order)552 "New UEFI boot order: %s", new_boot_order)
461 with util.ChrootableTarget(target) as in_chroot:553 with util.ChrootableTarget(target) as in_chroot:
@@ -465,25 +557,44 @@ def uefi_reorder_loaders(grubcfg, target):
465 LOG.debug("Currently booted UEFI loader might no longer boot.")557 LOG.debug("Currently booted UEFI loader might no longer boot.")
466558
467559
468def uefi_remove_duplicate_entries(grubcfg, target):560def uefi_remove_duplicate_entries(grubcfg, target, to_remove=None):
561 if not grubcfg.get('remove_duplicate_entries', True):
562 LOG.debug("Skipped removing duplicate UEFI boot entries per config.")
563 return
564 if to_remove is None:
565 to_remove = uefi_find_duplicate_entries(grubcfg, target)
566
567 # check so we don't run ChrootableTarget code unless we have things to do
568 if to_remove:
569 with util.ChrootableTarget(target) as in_chroot:
570 for bootnum, entry in to_remove:
571 LOG.debug('Removing duplicate EFI entry (%s, %s)',
572 bootnum, entry)
573 in_chroot.subp(['efibootmgr', '--bootnum=%s' % bootnum,
574 '--delete-bootnum'])
575
576
577def uefi_find_duplicate_entries(grubcfg, target, efi_output=None):
469 seen = set()578 seen = set()
470 to_remove = []579 to_remove = []
471 efi_output = util.get_efibootmgr(target=target)580 if efi_output is None:
581 efi_output = util.get_efibootmgr(target=target)
472 entries = efi_output.get('entries', {})582 entries = efi_output.get('entries', {})
583 current_bootnum = efi_output.get('current', None)
584 # adding BootCurrent to seen first allows us to remove any other duplicate
585 # entry of BootCurrent.
586 if current_bootnum:
587 seen.add(tuple(entries[current_bootnum].items()))
473 for bootnum in sorted(entries):588 for bootnum in sorted(entries):
589 if bootnum == current_bootnum:
590 continue
474 entry = entries[bootnum]591 entry = entries[bootnum]
475 t = tuple(entry.items())592 t = tuple(entry.items())
476 if t not in seen:593 if t not in seen:
477 seen.add(t)594 seen.add(t)
478 else:595 else:
479 to_remove.append((bootnum, entry))596 to_remove.append((bootnum, entry))
480 if to_remove:597 return to_remove
481 with util.ChrootableTarget(target) as in_chroot:
482 for bootnum, entry in to_remove:
483 LOG.debug('Removing duplicate EFI entry (%s, %s)',
484 bootnum, entry)
485 in_chroot.subp(['efibootmgr', '--bootnum=%s' % bootnum,
486 '--delete-bootnum'])
487598
488599
489def _debconf_multiselect(package, variable, choices):600def _debconf_multiselect(package, variable, choices):
@@ -557,7 +668,7 @@ def uefi_find_grub_device_ids(sconfig):
557 esp_partitions.append(item_id)668 esp_partitions.append(item_id)
558 continue669 continue
559670
560 if item['type'] == 'mount' and item['path'] == '/boot/efi':671 if item['type'] == 'mount' and item.get('path') == '/boot/efi':
561 if primary_esp:672 if primary_esp:
562 LOG.debug('Ignoring duplicate mounted primary ESP: %s',673 LOG.debug('Ignoring duplicate mounted primary ESP: %s',
563 item_id)674 item_id)
@@ -592,7 +703,7 @@ def uefi_find_grub_device_ids(sconfig):
592 return grub_device_ids703 return grub_device_ids
593704
594705
595def setup_grub(cfg, target, osfamily=DISTROS.debian):706def setup_grub(cfg, target, osfamily=DISTROS.debian, variant=None):
596 # target is the path to the mounted filesystem707 # target is the path to the mounted filesystem
597708
598 # FIXME: these methods need moving to curtin.block709 # FIXME: these methods need moving to curtin.block
@@ -692,13 +803,14 @@ def setup_grub(cfg, target, osfamily=DISTROS.debian):
692803
693 update_nvram = grubcfg.get('update_nvram', True)804 update_nvram = grubcfg.get('update_nvram', True)
694 if uefi_bootable and update_nvram:805 if uefi_bootable and update_nvram:
806 efi_orig_output = util.get_efibootmgr(target)
695 uefi_remove_old_loaders(grubcfg, target)807 uefi_remove_old_loaders(grubcfg, target)
696808
697 install_grub(instdevs, target, uefi=uefi_bootable, grubcfg=grubcfg)809 install_grub(instdevs, target, uefi=uefi_bootable, grubcfg=grubcfg)
698810
699 if uefi_bootable and update_nvram:811 if uefi_bootable and update_nvram:
812 uefi_reorder_loaders(grubcfg, target, efi_orig_output, variant)
700 uefi_remove_duplicate_entries(grubcfg, target)813 uefi_remove_duplicate_entries(grubcfg, target)
701 uefi_reorder_loaders(grubcfg, target)
702814
703815
704def update_initramfs(target=None, all_kernels=False):816def update_initramfs(target=None, all_kernels=False):
@@ -900,6 +1012,7 @@ def add_swap(cfg, target, fstab):
900 fname = swapcfg.get('filename', None)1012 fname = swapcfg.get('filename', None)
901 size = swapcfg.get('size', None)1013 size = swapcfg.get('size', None)
902 maxsize = swapcfg.get('maxsize', None)1014 maxsize = swapcfg.get('maxsize', None)
1015 force = swapcfg.get('force', False)
9031016
904 if size:1017 if size:
905 size = util.human2bytes(str(size))1018 size = util.human2bytes(str(size))
@@ -907,7 +1020,7 @@ def add_swap(cfg, target, fstab):
907 maxsize = util.human2bytes(str(maxsize))1020 maxsize = util.human2bytes(str(maxsize))
9081021
909 swap.setup_swapfile(target=target, fstab=fstab, swapfile=fname, size=size,1022 swap.setup_swapfile(target=target, fstab=fstab, swapfile=fname, size=size,
910 maxsize=maxsize)1023 maxsize=maxsize, force=force)
9111024
9121025
913def detect_and_handle_multipath(cfg, target, osfamily=DISTROS.debian):1026def detect_and_handle_multipath(cfg, target, osfamily=DISTROS.debian):
@@ -1733,7 +1846,8 @@ def builtin_curthooks(cfg, target, state):
1733 name=stack_prefix + '/install-grub',1846 name=stack_prefix + '/install-grub',
1734 reporting_enabled=True, level="INFO",1847 reporting_enabled=True, level="INFO",
1735 description="installing grub to target devices"):1848 description="installing grub to target devices"):
1736 setup_grub(cfg, target, osfamily=osfamily)1849 setup_grub(cfg, target, osfamily=osfamily,
1850 variant=distro_info.variant)
17371851
17381852
1739def curthooks(args):1853def curthooks(args):
diff --git a/curtin/commands/install_grub.py b/curtin/commands/install_grub.py
index 777aa35..5f8311f 100644
--- a/curtin/commands/install_grub.py
+++ b/curtin/commands/install_grub.py
@@ -346,7 +346,7 @@ def install_grub(devices, target, uefi=None, grubcfg=None):
346346
347 LOG.debug("installing grub to target=%s devices=%s [replace_defaults=%s]",347 LOG.debug("installing grub to target=%s devices=%s [replace_defaults=%s]",
348 target, devices, grubcfg.get('replace_default'))348 target, devices, grubcfg.get('replace_default'))
349 update_nvram = config.value_as_boolean(grubcfg.get('update_nvram', False))349 update_nvram = config.value_as_boolean(grubcfg.get('update_nvram', True))
350 distroinfo = distro.get_distroinfo(target=target)350 distroinfo = distro.get_distroinfo(target=target)
351 target_arch = distro.get_architecture(target=target)351 target_arch = distro.get_architecture(target=target)
352 rhel_ver = (distro.rpm_get_dist_id(target)352 rhel_ver = (distro.rpm_get_dist_id(target)
diff --git a/curtin/commands/swap.py b/curtin/commands/swap.py
index f2381e6..089cd73 100644
--- a/curtin/commands/swap.py
+++ b/curtin/commands/swap.py
@@ -40,7 +40,7 @@ def swap_main(args):
4040
41 swap.setup_swapfile(target=state['target'], fstab=state['fstab'],41 swap.setup_swapfile(target=state['target'], fstab=state['fstab'],
42 swapfile=args.swapfile, size=size,42 swapfile=args.swapfile, size=size,
43 maxsize=args.maxsize)43 maxsize=args.maxsize, force=args.force)
44 sys.exit(2)44 sys.exit(2)
4545
4646
@@ -54,6 +54,9 @@ CMD_ARGUMENTS = (
54 'default is env[TARGET_MOUNT_POINT]'),54 'default is env[TARGET_MOUNT_POINT]'),
55 'action': 'store', 'metavar': 'TARGET',55 'action': 'store', 'metavar': 'TARGET',
56 'default': os.environ.get('TARGET_MOUNT_POINT')}),56 'default': os.environ.get('TARGET_MOUNT_POINT')}),
57 (('-F', '--force'),
58 {'help': 'force creating of swapfile even if it may fail (btrfs,xfs)',
59 'default': False, 'action': 'store_true'}),
57 (('-s', '--size'),60 (('-s', '--size'),
58 {'help': 'size of swap file (eg: 1G, 1500M, 1024K, 100000. def: "auto")',61 {'help': 'size of swap file (eg: 1G, 1500M, 1024K, 100000. def: "auto")',
59 'default': None, 'action': 'store'}),62 'default': None, 'action': 'store'}),
diff --git a/curtin/distro.py b/curtin/distro.py
index 43b0c19..82a4dd5 100644
--- a/curtin/distro.py
+++ b/curtin/distro.py
@@ -264,7 +264,7 @@ def apt_update(target=None, env=None, force=False, comment=None,
264264
265265
266def run_apt_command(mode, args=None, opts=None, env=None, target=None,266def run_apt_command(mode, args=None, opts=None, env=None, target=None,
267 execute=True, allow_daemons=False):267 execute=True, allow_daemons=False, clean=True):
268 defopts = ['--quiet', '--assume-yes',268 defopts = ['--quiet', '--assume-yes',
269 '--option=Dpkg::options::=--force-unsafe-io',269 '--option=Dpkg::options::=--force-unsafe-io',
270 '--option=Dpkg::Options::=--force-confold']270 '--option=Dpkg::Options::=--force-confold']
@@ -289,7 +289,11 @@ def run_apt_command(mode, args=None, opts=None, env=None, target=None,
289289
290 apt_update(target, env=env, comment=' '.join(cmd))290 apt_update(target, env=env, comment=' '.join(cmd))
291 with ChrootableTarget(target, allow_daemons=allow_daemons) as inchroot:291 with ChrootableTarget(target, allow_daemons=allow_daemons) as inchroot:
292 return inchroot.subp(cmd, env=env)292 cmd_rv = inchroot.subp(cmd, env=env)
293 if clean and mode in ['dist-upgrade', 'install', 'upgrade']:
294 inchroot.subp(['apt-get', 'clean'])
295
296 return cmd_rv
293297
294298
295def run_yum_command(mode, args=None, opts=None, env=None, target=None,299def run_yum_command(mode, args=None, opts=None, env=None, target=None,
@@ -472,6 +476,7 @@ def parse_dpkg_version(raw, name=None, semx=None):
472 as the upstream version.476 as the upstream version.
473477
474 returns a dictionary with fields:478 returns a dictionary with fields:
479 'epoch'
475 'major' (int), 'minor' (int), 'micro' (int),480 'major' (int), 'minor' (int), 'micro' (int),
476 'semantic_version' (int),481 'semantic_version' (int),
477 'extra' (string), 'raw' (string), 'upstream' (string),482 'extra' (string), 'raw' (string), 'upstream' (string),
@@ -484,12 +489,20 @@ def parse_dpkg_version(raw, name=None, semx=None):
484 if semx is None:489 if semx is None:
485 semx = (10000, 100, 1)490 semx = (10000, 100, 1)
486491
487 if "-" in raw:492 raw_offset = 0
488 upstream = raw.rsplit('-', 1)[0]493 if ':' in raw:
494 epoch, _, upstream = raw.partition(':')
495 raw_offset = len(epoch) + 1
489 else:496 else:
490 # this is a native package, package version treated as upstream.497 epoch = 0
491 upstream = raw498 upstream = raw
492499
500 if "-" in raw[raw_offset:]:
501 upstream = raw[raw_offset:].rsplit('-', 1)[0]
502 else:
503 # this is a native package, package version treated as upstream.
504 upstream = raw[raw_offset:]
505
493 match = re.search(r'[^0-9.]', upstream)506 match = re.search(r'[^0-9.]', upstream)
494 if match:507 if match:
495 extra = upstream[match.start():]508 extra = upstream[match.start():]
@@ -498,8 +511,10 @@ def parse_dpkg_version(raw, name=None, semx=None):
498 upstream_base = upstream511 upstream_base = upstream
499 extra = None512 extra = None
500513
501 toks = upstream_base.split(".", 2)514 toks = upstream_base.split(".", 3)
502 if len(toks) == 3:515 if len(toks) == 4:
516 major, minor, micro, extra = toks
517 elif len(toks) == 3:
503 major, minor, micro = toks518 major, minor, micro = toks
504 elif len(toks) == 2:519 elif len(toks) == 2:
505 major, minor, micro = (toks[0], toks[1], 0)520 major, minor, micro = (toks[0], toks[1], 0)
@@ -507,6 +522,7 @@ def parse_dpkg_version(raw, name=None, semx=None):
507 major, minor, micro = (toks[0], 0, 0)522 major, minor, micro = (toks[0], 0, 0)
508523
509 version = {524 version = {
525 'epoch': int(epoch),
510 'major': int(major),526 'major': int(major),
511 'minor': int(minor),527 'minor': int(minor),
512 'micro': int(micro),528 'micro': int(micro),
diff --git a/curtin/net/deps.py b/curtin/net/deps.py
index f912d1d..b78654d 100644
--- a/curtin/net/deps.py
+++ b/curtin/net/deps.py
@@ -34,10 +34,13 @@ def network_config_required_packages(network_config, mapping=None):
34 if cfgtype == 'version':34 if cfgtype == 'version':
35 continue35 continue
36 dev_configs.add(cfgtype)36 dev_configs.add(cfgtype)
37 # the renderer type may trigger package adds37 # subkeys under the type may trigger package adds
38 for entry, entry_cfg in cfg.items():38 for entry, entry_cfg in cfg.items():
39 if entry_cfg.get('renderer'):39 if entry_cfg.get('renderer'):
40 dev_configs.add(entry_cfg.get('renderer'))40 dev_configs.add(entry_cfg.get('renderer'))
41 else:
42 for sub_entry, sub_cfg in entry_cfg.items():
43 dev_configs.add(sub_entry)
4144
42 needed_packages = []45 needed_packages = []
43 for dev_type in dev_configs:46 for dev_type in dev_configs:
diff --git a/curtin/swap.py b/curtin/swap.py
index d3f29dc..11e95c4 100644
--- a/curtin/swap.py
+++ b/curtin/swap.py
@@ -5,6 +5,8 @@ import resource
55
6from .log import LOG6from .log import LOG
7from . import util7from . import util
8from curtin import paths
9from curtin import distro
810
911
10def suggested_swapsize(memsize=None, maxsize=None, fsys=None):12def suggested_swapsize(memsize=None, maxsize=None, fsys=None):
@@ -51,7 +53,62 @@ def suggested_swapsize(memsize=None, maxsize=None, fsys=None):
51 return maxsize53 return maxsize
5254
5355
54def setup_swapfile(target, fstab=None, swapfile=None, size=None, maxsize=None):56def get_fstype(target, source):
57 target_source = paths.target_path(target, source)
58 try:
59 out, _ = util.subp(['findmnt', '--noheading', '--target',
60 target_source, '-o', 'FSTYPE'], capture=True)
61 except util.ProcessExecutionError as exc:
62 LOG.warning('Failed to query %s fstype, findmnt returned error: %s',
63 target_source, exc)
64 return None
65
66 if out:
67 """
68 $ findmnt --noheading --target /btrfs -o FSTYPE
69 btrfs
70 """
71 return out.splitlines()[-1]
72
73 return None
74
75
76def get_target_kernel_version(target):
77 pkg_ver = None
78
79 distro_info = distro.get_distroinfo(target=target)
80 if not distro_info:
81 raise RuntimeError('Failed to determine target distro')
82 osfamily = distro_info.family
83 if osfamily == distro.DISTROS.debian:
84 try:
85 # check in-target version
86 pkg_ver = distro.get_package_version('linux-image-generic',
87 target=target)
88 except Exception as e:
89 LOG.warn(
90 "failed reading linux-image-generic package version, %s", e)
91 return pkg_ver
92
93
94def can_use_swapfile(target, fstype):
95 if fstype is None:
96 raise RuntimeError(
97 'Unknown target filesystem type, may not support swapfiles')
98 if fstype in ['btrfs', 'xfs']:
99 # check kernel version
100 pkg_ver = get_target_kernel_version(target)
101 if not pkg_ver:
102 raise RuntimeError('Failed to read target kernel version')
103 if fstype == 'btrfs' and pkg_ver['major'] < 5:
104 raise RuntimeError(
105 'btrfs requiers kernel version 5.0+ to use swapfiles')
106 elif fstype in ['zfs']:
107 raise RuntimeError('ZFS cannot use swapfiles')
108
109
110def setup_swapfile(target, fstab=None, swapfile=None, size=None, maxsize=None,
111 force=False):
55 if size is None:112 if size is None:
56 size = suggested_swapsize(fsys=target, maxsize=maxsize)113 size = suggested_swapsize(fsys=target, maxsize=maxsize)
57114
@@ -65,6 +122,24 @@ def setup_swapfile(target, fstab=None, swapfile=None, size=None, maxsize=None):
65 if not swapfile.startswith("/"):122 if not swapfile.startswith("/"):
66 swapfile = "/" + swapfile123 swapfile = "/" + swapfile
67124
125 # query the directory in which swapfile will reside
126 fstype = get_fstype(target, os.path.dirname(swapfile))
127 try:
128 can_use_swapfile(target, fstype)
129 except RuntimeError as err:
130 if force:
131 LOG.warning('swapfile may not work: %s', err)
132 else:
133 LOG.debug('Not creating swap: %s', err)
134 return
135
136 allocate_cmd = 'fallocate -l "${2}M" "$1"'
137 # fallocate uses IOCTLs to allocate space in a filesystem, however it's not
138 # clear (from curtin's POV) that it creates non-sparse files as required by
139 # mkswap so we'll skip fallocate for now and use dd.
140 if fstype in ['btrfs', 'xfs']:
141 allocate_cmd = 'dd if=/dev/zero "of=$1" bs=1M "count=$2"'
142
68 mbsize = str(int(size / (2 ** 20)))143 mbsize = str(int(size / (2 ** 20)))
69 msg = "creating swap file '%s' of %sMB" % (swapfile, mbsize)144 msg = "creating swap file '%s' of %sMB" % (swapfile, mbsize)
70 fpath = os.path.sep.join([target, swapfile])145 fpath = os.path.sep.join([target, swapfile])
@@ -73,10 +148,9 @@ def setup_swapfile(target, fstab=None, swapfile=None, size=None, maxsize=None):
73 with util.LogTimer(LOG.debug, msg):148 with util.LogTimer(LOG.debug, msg):
74 util.subp(149 util.subp(
75 ['sh', '-c',150 ['sh', '-c',
76 ('rm -f "$1" && umask 0066 && '151 ('rm -f "$1" && umask 0066 && truncate -s 0 "$1" && '
77 '{ fallocate -l "${2}M" "$1" || '152 '{ chattr +C "$1" || true; } && ') + allocate_cmd +
78 ' dd if=/dev/zero "of=$1" bs=1M "count=$2"; } && '153 (' && mkswap "$1" || { r=$?; rm -f "$1"; exit $r; }'),
79 'mkswap "$1" || { r=$?; rm -f "$1"; exit $r; }'),
80 'setup_swap', fpath, mbsize])154 'setup_swap', fpath, mbsize])
81 except Exception:155 except Exception:
82 LOG.warn("failed %s" % msg)156 LOG.warn("failed %s" % msg)
diff --git a/debian/changelog b/debian/changelog
index 67c16c4..246ea94 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,43 @@
1curtin (20.2-0ubuntu1~20.04.1) focal; urgency=medium
2
3 * New upstream release. (LP: #1896947)
4 - Release 20.2 [Paride Legovini]
5 - Get debian/rules in sync with 0.1.0~bzr470-0ubuntu1 [Paride Legovini]
6 - Fix the py3 pylint ci run [Paride Legovini]
7 - vmtest: Fix multiple issues with vmtest on master
8 - Refactor uefi_remove_duplicates into find/remove functions for reuse
9 - distro: run apt-get clean after dist-upgrade, install, upgrade
10 - curthooks: UEFI remove dupes: don't remove BootCurrent, config option
11 - Pin the dependency on pyrsistent [Paride Legovini]
12 - restore default of grub.update_nvram to True in install_grub
13 [Michael Hudson-Doyle]
14 - block: disk_to_byid_path handle missing /dev/disk/by-id directory
15 - UEFI: Handle missing BootCurrent entry when reordering UEFI entries
16 - dasd: fix off-by-one device_id devno range check [Paride Legovini]
17 - curthooks: uefi_find_grub_device_ids handle type:mount without path
18 - netplan openvswitch yaml changed
19 - tools/curtainer: do not wait for snapd.seeded.service
20 - tools/curtainer: enable using ubuntu-minimal images
21 - vmtests: add Groovy [Paride Legovini]
22 - Drop the Eoan vmtests (EOL) [Paride Legovini]
23 - tools: rename remove-vmtest-release to vmtest-remove-release
24 - Snooze the tests failing because of LP: #1861941 for two more months
25 [Paride Legovini]
26 - LP: #1671951 is Fix Released => Drop the PPA [Paride Legovini]
27 - swaps: handle swapfiles on btrfs
28 - curtainer: fail is masking of zfs-mount or zfs-share fails
29 [Paride Legovini]
30 - multipath: handle multipath nvme name fields correctly
31 - curtainer: mask the zfs-mount and zfs-share services [Paride Legovini]
32 - tools/jenkins-runner: shuffle test-cases to randomize load
33 [Paride Legovini]
34 - Add Trusty/UEFI/HWE-X vmtest, drop realpath add, drop shell code
35 - LP: #1881977 - Install realpath on Trusty UEFI. [Lee Trager]
36 - vmtests: fix PreservePartitionWipeVg storage config
37 - Fix mdraid name creates broken configuration [James Falcon]
38
39 -- Paride Legovini <paride.legovini@canonical.com> Tue, 29 Sep 2020 14:22:54 +0200
40
1curtin (20.1-2-g42a9667f-0ubuntu1~20.04.1) focal; urgency=medium41curtin (20.1-2-g42a9667f-0ubuntu1~20.04.1) focal; urgency=medium
242
3 * New upstream snapshot. (LP: #1881003)43 * New upstream snapshot. (LP: #1881003)
diff --git a/doc/topics/config.rst b/doc/topics/config.rst
index 72cd683..ec8a109 100644
--- a/doc/topics/config.rst
+++ b/doc/topics/config.rst
@@ -226,6 +226,42 @@ not provided, Curtin will set the value to 'console'. If the ``terminal``
226value is 'unmodified' then Curtin will not set any value at all and will226value is 'unmodified' then Curtin will not set any value at all and will
227use Grub defaults.227use Grub defaults.
228228
229**reorder_uefi**: *<boolean: default True>*
230
231Curtin is typically used with MAAS where the systems are configured to boot
232from the network leaving MAAS in control. On UEFI systems, after installing
233a bootloader the systems BootOrder may be updated to boot from the new entry.
234This breaks MAAS control over the system as all subsequent reboots of the node
235will no longer boot over the network. Therefore, if ``reorder_uefi`` is True
236curtin will modify the UEFI BootOrder settings to place the currently booted
237entry (BootCurrent) to the first option after installing the new target OS into
238the UEFI boot menu. The result is that the system will boot from the same
239device that it booted to run curtin; for MAAS this will be a network device.
240
241On some UEFI systems the BootCurrent entry may not be present. This can
242cause a system to not boot to the same device that it was previously booting.
243If BootCurrent is not present, curtin will update the BootOrder such that
244all Network related entries are placed before the newly installed boot entry and
245all other entries are placed at the end. This enables the system to network
246boot first and on failure will boot the most recently installed entry.
247
248This setting is ignored if *update_nvram* is False.
249
250**reorder_uefi_force_fallback**: *<boolean: default False>*
251
252The fallback reodering mechanism is only active if BootCurrent is not present
253in the efibootmgr output. The fallback reordering method may be enabled
254even if BootCurrent is present if *reorder_uefi_force_fallback* is True.
255
256This setting is ignored if *update_nvram* or *reorder_uefi* are False.
257
258**remove_duplicate_entries**: <*boolean: default True>*
259
260When curtin updates UEFI NVRAM it will remove duplicate entries that are
261present in the UEFI menu. If you do not wish for curtin to remove duplicate
262entries setting *remove_duplicate_entries* to False.
263
264This setting is ignored if *update_nvram* is False.
229265
230**Example**::266**Example**::
231267
@@ -235,6 +271,7 @@ use Grub defaults.
235 replace_linux_default: False271 replace_linux_default: False
236 update_nvram: True272 update_nvram: True
237 terminal: serial273 terminal: serial
274 remove_duplicate_entries: True
238275
239**Default terminal value, GRUB_TERMINAL=console**::276**Default terminal value, GRUB_TERMINAL=console**::
240277
@@ -264,6 +301,12 @@ use Grub defaults.
264 probe_additional_os: True301 probe_additional_os: True
265 terminal: unmodified302 terminal: unmodified
266303
304**Enable Fallback UEFI Reordering**::
305
306 grub:
307 reorder_uefi: true
308 reorder_uefi_force_fallback: true
309
267310
268http_proxy311http_proxy
269~~~~~~~~~~312~~~~~~~~~~
@@ -752,13 +795,27 @@ Configure the max size of the swapfile, defaults to 8GB
752Configure the exact size of the swapfile. Setting ``size`` to 0 will795Configure the exact size of the swapfile. Setting ``size`` to 0 will
753disable swap.796disable swap.
754797
798**force**: *<boolean>*
799
800Force the creation of swapfile even if curtin detects it may not work.
801In some target filesystems, e.g. btrfs, xfs, zfs, the use of a swap file has
802restrictions. If curtin detects that there may be issues it will refuse
803to create the swapfile. Users can force creation of a swapfile by passing
804``force: true``. A forced swapfile may not be used by the target OS and could
805log cause an error.
806
755**Example**::807**Example**::
756808
757 swap:809 swap:
758 filename: swap.img810 filename: swap.img
759 size: None811 size: 1GB
760 maxsize: 4GB812 maxsize: 4GB
761813
814 swap:
815 filename: btrfs_swapfile.img
816 size: 1GB
817 force: true
818
762819
763system_upgrade820system_upgrade
764~~~~~~~~~~~~~~821~~~~~~~~~~~~~~
diff --git a/examples/tests/basic.yaml b/examples/tests/basic.yaml
index 71730c0..82f5ad1 100644
--- a/examples/tests/basic.yaml
+++ b/examples/tests/basic.yaml
@@ -1,4 +1,8 @@
1showtrace: true1showtrace: true
2swap:
3 filename: /btrfs/btrfsswap.img
4 size: 1GB
5 maxsize: 1GB
2storage:6storage:
3 version: 17 version: 1
4 config:8 config:
diff --git a/examples/tests/basic_scsi.yaml b/examples/tests/basic_scsi.yaml
index 51f5236..fd28bbe 100644
--- a/examples/tests/basic_scsi.yaml
+++ b/examples/tests/basic_scsi.yaml
@@ -1,4 +1,8 @@
1showtrace: true1showtrace: true
2swap:
3 filename: /btrfs/btrfsswap.img
4 size: 1GB
5 maxsize: 1GB
2storage:6storage:
3 version: 17 version: 1
4 config:8 config:
diff --git a/examples/tests/network_v2_ovs.yaml b/examples/tests/network_v2_ovs.yaml
index 6d1dc3d..0d063ce 100644
--- a/examples/tests/network_v2_ovs.yaml
+++ b/examples/tests/network_v2_ovs.yaml
@@ -8,35 +8,6 @@ network:
8 match:8 match:
9 macaddress: '52:54:00:12:34:00'9 macaddress: '52:54:00:12:34:00'
10 set-name: eth010 set-name: eth0
11 eth1:11 bridges:
12 match:12 br-empty:
13 macaddress: '52:54:00:12:34:02'13 openvswitch: {}
14 set-name: eth1
15 eth2:
16 match:
17 macaddress: '52:54:00:12:34:04'
18 set-name: eth2
19 openvswitch:
20 bridges:
21 br-int:
22 fail-mode: secure
23 datapath_type: system
24 stp: false
25 rstp: false
26 mcast-snooping: false
27 controller:
28 addresses:
29 - tcp:127.0.0.1:6653
30 protocols:
31 - OpenFlow10
32 - OpenFlow12
33 - OpenFlow13
34 ports:
35 patch-tun:
36 type: patch
37 options:
38 peer: patch-int
39 eth1:
40 tag: 2
41 eth2:
42 tag: 2
diff --git a/examples/tests/nvme_bcache.yaml b/examples/tests/nvme_bcache.yaml
index 4fefd94..ed705c4 100644
--- a/examples/tests/nvme_bcache.yaml
+++ b/examples/tests/nvme_bcache.yaml
@@ -1,8 +1,7 @@
1showtrace: true1showtrace: true
2storage:2storage:
3 config:3 config:
4 - grub_device: true4 - id: sda
5 id: sda
6 model: LOGICAL VOLUME5 model: LOGICAL VOLUME
7 name: sda6 name: sda
8 ptable: gpt7 ptable: gpt
@@ -23,7 +22,7 @@ storage:
23 type: disk22 type: disk
24 wipe: superblock23 wipe: superblock
25 - device: sda24 - device: sda
26 flag: boot25 grub_device: true
27 id: sda-part126 id: sda-part1
28 name: sda-part127 name: sda-part1
29 number: 128 number: 1
@@ -33,7 +32,6 @@ storage:
33 uuid: 9f0fda16-c096-4cee-82ac-4f5f294253a232 uuid: 9f0fda16-c096-4cee-82ac-4f5f294253a2
34 wipe: superblock33 wipe: superblock
35 - device: sda34 - device: sda
36 flag: boot
37 id: sda-part235 id: sda-part2
38 name: sda-part236 name: sda-part2
39 number: 237 number: 2
diff --git a/examples/tests/preserve-partition-wipe-vg.yaml b/examples/tests/preserve-partition-wipe-vg.yaml
index 97686e1..27a4235 100644
--- a/examples/tests/preserve-partition-wipe-vg.yaml
+++ b/examples/tests/preserve-partition-wipe-vg.yaml
@@ -38,7 +38,6 @@ storage:
38 grub_device: true38 grub_device: true
39 type: disk39 type: disk
40 id: disk-sda40 id: disk-sda
41 wipe: superblock
42 - serial: disk-b41 - serial: disk-b
43 name: disk-b42 name: disk-b
44 grub_device: false43 grub_device: false
diff --git a/examples/tests/raid5boot.yaml b/examples/tests/raid5boot.yaml
index b1df21d..d8afe7f 100644
--- a/examples/tests/raid5boot.yaml
+++ b/examples/tests/raid5boot.yaml
@@ -42,7 +42,7 @@ storage:
42 size: 3GB42 size: 3GB
43 device: sdc43 device: sdc
44 - id: mddevice44 - id: mddevice
45 name: md045 name: os-raid1
46 type: raid46 type: raid
47 raidlevel: 547 raidlevel: 5
48 devices:48 devices:
diff --git a/helpers/common b/helpers/common
index 5638d39..4afb6a1 100644
--- a/helpers/common
+++ b/helpers/common
@@ -29,19 +29,6 @@ EOF
29 [ $# -eq 0 ] || echo "$@"29 [ $# -eq 0 ] || echo "$@"
30}30}
3131
32grub_install_usage() {
33 cat <<EOF
34Usage: ${0##*/} [ options ] mount-point target-dev
35
36 perform grub-install with mount-point onto target-dev.
37
38 options:
39 --uefi install grub-efi instead of grub-pc
40 --update-nvram request grub to update nvram
41EOF
42 [ $# -eq 0 ] || echo "$@"
43}
44
45cleanup() {32cleanup() {
46 if [ -d "$TEMP_D" ]; then33 if [ -d "$TEMP_D" ]; then
47 rm -Rf "$TEMP_D"34 rm -Rf "$TEMP_D"
@@ -480,569 +467,4 @@ getsize() {
480 fi467 fi
481}468}
482469
483is_md() {
484 case "${1##*/}" in
485 md[0-9]) return 0;;
486 esac
487 return 1
488}
489
490get_carryover_params() {
491 local cmdline=" $1 " extra="" lead="" carry_extra="" carry_lead=""
492 # return a string to append to installed systems boot parameters
493 # it may include a '--' after a '---'
494 # see LP: 1402042 for some history here.
495 # this is similar to 'user-params' from d-i
496 local preferred_sep="---" # KERNEL_CMDLINE_COPY_TO_INSTALL_SEP
497 local legacy_sep="--"
498 case "$cmdline" in
499 *\ ${preferred_sep}\ *)
500 extra=${cmdline#* ${preferred_sep} }
501 lead=${cmdline%% ${preferred_sep} *}
502 ;;
503 *\ ${legacy_sep}\ *)
504 extra="${cmdline#* ${legacy_sep} }"
505 lead=${cmdline%% ${legacy_sep} *}
506 ;;
507 *)
508 extra=""
509 lead="$cmdline"
510 ;;
511 esac
512
513 if [ -n "$extra" ]; then
514 carry_extra=$(set -f;
515 c="";
516 for p in $extra; do
517 case "$p" in
518 (BOOTIF=*|initrd=*|BOOT_IMAGE=*) continue;;
519 esac
520 c="$c $p";
521 done
522 echo "${c# }"
523 )
524 fi
525
526 # these get copied even if they werent after the separator
527 local padded=" $carry_extra "
528 carry_lead=$(set -f;
529 padded=" ${carry_extra} "
530 c=""
531 for p in $lead; do
532 # skip any that are already in carry_extra
533 [ "${padded#* $p }" != "$padded" ] && continue
534 case "$p" in
535 (console=*) c="$c $p";;
536 esac
537 done
538 echo "${c# }"
539 )
540 [ -n "${carry_lead}" -a -n "${carry_extra}" ] &&
541 carry_lead="${carry_lead} "
542 _RET="${carry_lead}${carry_extra}"
543}
544
545shell_config_update() {
546 # shell_config_update(file, name, value)
547 # update variable 'name' setting value to 'val' in shell syntax 'file'.
548 # if 'name' is not present, then append declaration.
549 local file="$1" name="$2" val="$3"
550 if ! [ -f "$file" ] || ! grep -q "^$name=" "$file"; then
551 debug 2 "appending to $file shell $name=\"$val\""
552 echo "$name=\"$val\"" >> "$file"
553 return
554 fi
555 local cand="" del=""
556 for cand in "|" "," "/"; do
557 [ "${val#*${del}}" = "${val}" ] && del="$cand" && break
558 done
559 [ -n "$del" ] || {
560 error "Couldn't find a sed delimiter for '$val'";
561 return 1;
562 }
563
564 sed -i -e "s${del}^$name=.*${del}$name=\"$val\"${del}" "$file" ||
565 { error "Failed editing '$file' to set $name=$val"; return 1; }
566 debug 2 "updated $file to set $name=\"$val\""
567 return 0
568}
569
570apply_grub_cmdline_linux_default() {
571 local mp="$1" newargs="$2" edg="${3:-etc/default/grub}"
572 local gcld="GRUB_CMDLINE_LINUX_DEFAULT"
573 debug 1 "setting $gcld to '$newargs' in $edg"
574 shell_config_update "$mp/$edg" "$gcld" "$newargs" || {
575 error "Failed to set '$gcld=$newargs' in $edg"
576 return 1
577 }
578}
579
580get_parent_disk() {
581 # Look up the parent /dev path via sysfs. Using the partition
582 # kname (nvme0n1p1), construct a /sys/class/block path, use
583 # realpath to resolve this to an absolute path which includes
584 # the parent:
585 # /sys/devices/pci0000:00/*/*/nvme/nvme0/nvme0n1/nvme0n1p1
586 # dirname to extract the parent, then read the 'dev' entry
587 # /sys/devices/pci0000:00/*/*/nvme/nvme0/nvme0n1/dev
588 # which contains the MAJOR:MINOR value and construct a /dev/block
589 # path which is a symbolic link that udev constructs that points
590 # to the real device name and use realpath to return the absolute path.
591 # /dev/block/259:0 -> ../nvme0n1
592 local devpath="${1}"
593 local kname=$(basename "$devpath")
594 local syspath=$(realpath "/sys/class/block/$kname")
595 local disksyspath=$(dirname "$syspath")
596 local diskmajmin=$(cat "${disksyspath}/dev")
597 local diskdevpath=$(realpath "/dev/block/${diskmajmin}")
598 echo $diskdevpath
599}
600
601install_grub() {
602 local long_opts="uefi,update-nvram,os-family:"
603 local getopt_out="" mp_efi=""
604 getopt_out=$(getopt --name "${0##*/}" \
605 --options "" --long "${long_opts}" -- "$@") &&
606 eval set -- "${getopt_out}"
607
608 local uefi=0 update_nvram=0 os_family=""
609
610 while [ $# -ne 0 ]; do
611 cur="$1"; next="$2";
612 case "$cur" in
613 --os-family) os_family=${next};;
614 --uefi) uefi=$((${uefi}+1));;
615 --update-nvram) update_nvram=$((${update_nvram}+1));;
616 --) shift; break;;
617 esac
618 shift;
619 done
620
621 [ $# -lt 2 ] && { grub_install_usage "must provide mount-point and target-dev" 1>&2; return 1; }
622
623 local mp="$1"
624 local cmdline tmp r=""
625 shift
626 local grubdevs
627 grubdevs=( "$@" )
628 if [ "${#grubdevs[@]}" = "1" -a "${grubdevs[0]}" = "none" ]; then
629 grubdevs=( )
630 fi
631 debug 1 "grubdevs: [${grubdevs[@]}]"
632
633 # find the mp device
634 local mp_dev="" fstype=""
635 mp_dev=$(awk -v "MP=$mp" '$2 == MP { print $1 }' /proc/mounts) || {
636 error "unable to determine device for mount $mp";
637 return 1;
638 }
639 debug 1 "/proc/mounts shows $mp_dev is mounted at $mp"
640
641 fstype=$(awk -v MP=$mp '$2 == MP { print $3 }' /proc/mounts) || {
642 error "unable to fstype for mount $mp";
643 return 1;
644 }
645
646 [ -z "$mp_dev" ] && {
647 error "did not find '$mp' in /proc/mounts"
648 cat /proc/mounts 1>&2
649 return 1
650 }
651 # check if parsed mount point is a block device
652 # error unless fstype is zfs, where entry will not point to block device.
653 if ! [ -b "$mp_dev" ] && [ "$fstype" != "zfs" ]; then
654 # error unless mp is zfs, entry doesn't point to block devs
655 error "$mp_dev ($fstype) is not a block device!"; return 1;
656 fi
657
658 local os_variant=""
659 if [ -e "${mp}/etc/os-release" ]; then
660 os_variant=$(chroot "$mp" \
661 /bin/sh -c 'echo $(. /etc/os-release; echo $ID)')
662 else
663 # Centos6 doesn't have os-release, so check for centos/redhat release
664 # looks like: CentOS release 6.9 (Final)
665 for rel in $(ls ${mp}/etc/*-release); do
666 os_variant=$(awk '{print tolower($1)}' $rel)
667 [ -n "$os_variant" ] && break
668 done
669 fi
670 [ $? != 0 ] &&
671 { error "Failed to read ID from $mp/etc/os-release"; return 1; }
672
673 local rhel_ver=""
674 case $os_variant in
675 debian|ubuntu) os_family="debian";;
676 centos|rhel)
677 os_family="redhat"
678 rhel_ver=$(chroot "$mp" rpm -E '%rhel')
679 ;;
680 esac
681
682 # ensure we have both settings, family and variant are needed
683 [ -n "${os_variant}" -a -n "${os_family}" ] ||
684 { error "Failed to determine os variant and family"; return 1; }
685
686 # get target arch
687 local target_arch="" r="1"
688 case $os_family in
689 debian)
690 target_arch=$(chroot "$mp" dpkg --print-architecture)
691 r=$?
692 ;;
693 redhat)
694 target_arch=$(chroot "$mp" rpm -E '%_arch')
695 r=$?
696 ;;
697 esac
698 [ $r -eq 0 ] || {
699 error "failed to get target architecture [$r]"
700 return 1;
701 }
702
703 # grub is not the bootloader you are looking for
704 if [ "${target_arch}" = "s390x" ]; then
705 return 0;
706 fi
707
708 # set correct grub package
709 local grub_name=""
710 local grub_target=""
711 case "$target_arch" in
712 i386|amd64)
713 # debian
714 grub_name="grub-pc"
715 grub_target="i386-pc"
716 ;;
717 x86_64)
718 case $rhel_ver in
719 6) grub_name="grub";;
720 7|8) grub_name="grub2-pc";;
721 *)
722 error "Unknown rhel_ver [$rhel_ver]";
723 return 1;
724 ;;
725 esac
726 grub_target="i386-pc"
727 ;;
728 esac
729 if [ "${target_arch#ppc64}" != "${target_arch}" ]; then
730 grub_name="grub-ieee1275"
731 grub_target="powerpc-ieee1275"
732 elif [ "$uefi" -ge 1 ]; then
733 grub_name="grub-efi-$target_arch"
734 case "$target_arch" in
735 x86_64)
736 # centos 7+, no centos6 support
737 # grub2-efi-x64 installs a signed grub bootloader while
738 # curtin uses grub2-efi-x64-modules to generate grubx64.efi.
739 # Either works just check that one of them is installed.
740 grub_name="grub2-efi-x64 grub2-efi-x64-modules"
741 grub_target="x86_64-efi"
742 ;;
743 amd64)
744 grub_target="x86_64-efi";;
745 arm64)
746 grub_target="arm64-efi";;
747 esac
748 fi
749
750 # check that the grub package is installed
751 local r=$?
752 case $os_family in
753 debian)
754 tmp=$(chroot "$mp" dpkg-query --show \
755 --showformat='${Status}\n' $grub_name)
756 r=$?
757 ;;
758 redhat)
759 tmp=$(chroot "$mp" rpm -q \
760 --queryformat='install ok installed\n' $grub_name)
761 r=$?
762 ;;
763 esac
764 if [ $r -ne 0 -a $r -ne 1 ]; then
765 error "failed to check if $grub_name installed";
766 return 1;
767 fi
768 # Check that any of the packages in $grub_name are installed. If
769 # grub_name contains multiple packages, as it does for CentOS 7+,
770 # only one package has to be installed for this to pass.
771 if ! echo $tmp | grep -q 'install ok installed'; then
772 debug 1 "$grub_name not installed, not doing anything"
773 return 1
774 fi
775
776 local grub_d="etc/default/grub.d"
777 # ubuntu writes to /etc/default/grub.d/50-curtin-settings.cfg
778 # to avoid tripping prompts on upgrade LP: #564853
779 local mygrub_cfg="$grub_d/50-curtin-settings.cfg"
780 case $os_family in
781 redhat)
782 grub_d="etc/default"
783 mygrub_cfg="etc/default/grub";;
784 esac
785 [ -d "$mp/$grub_d" ] || mkdir -p "$mp/$grub_d" ||
786 { error "Failed to create $grub_d"; return 1; }
787
788 # LP: #1179940 . The 50-cloudig-settings.cfg file is written by the cloud
789 # images build and defines/override some settings. Disable it.
790 local cicfg="$grub_d/50-cloudimg-settings.cfg"
791 if [ -f "$mp/$cicfg" ]; then
792 debug 1 "moved $cicfg out of the way"
793 mv "$mp/$cicfg" "$mp/$cicfg.disabled"
794 fi
795
796 # get the user provided / carry-over kernel arguments
797 local newargs=""
798 read cmdline < /proc/cmdline &&
799 get_carryover_params "$cmdline" && newargs="$_RET" || {
800 error "Failed to get carryover parrameters from cmdline";
801 return 1;
802 }
803 # always append rd.auto=1 for centos
804 case $os_family in
805 redhat)
806 newargs="${newargs:+${newargs} }rd.auto=1";;
807 esac
808 debug 1 "carryover command line params '$newargs'"
809
810 if [ "${REPLACE_GRUB_LINUX_DEFAULT:-1}" != "0" ]; then
811 apply_grub_cmdline_linux_default "$mp" "$newargs" || {
812 error "Failed to apply grub cmdline."
813 return 1
814 }
815 fi
816
817 if [ "${DISABLE_OS_PROBER:-1}" == "1" ]; then
818 {
819 echo "# Curtin disable grub os prober that might find other OS installs."
820 echo "GRUB_DISABLE_OS_PROBER=true"
821 } >> "$mp/$mygrub_cfg"
822 fi
823
824 if [ -n "${GRUB_TERMINAL}" ]; then
825 {
826 echo "# Curtin configured GRUB_TERMINAL value"
827 echo "GRUB_TERMINAL=${GRUB_TERMINAL}"
828 } >> "$mp/$mygrub_cfg"
829 fi
830
831 debug 1 "processing grubdevs values for expansion if needed"
832 local short="" bd="" grubdev grubdevs_new=""
833 grubdevs_new=()
834 for grubdev in "${grubdevs[@]}"; do
835 if is_md "$grubdev"; then
836 debug 1 "$grubdev is raid, find members"
837 short=${grubdev##*/}
838 for bd in "/sys/block/$short/slaves/"/*; do
839 [ -d "$bd" ] || continue
840 bd=${bd##*/}
841 bd="/dev/${bd%[0-9]}" # FIXME: part2bd
842 debug 1 "Add dev $bd to grubdevs_new"
843 grubdevs_new[${#grubdevs_new[@]}]="$bd"
844 done
845 else
846 debug 1 "Found dev [$grubdev] add to grubdevs_new"
847 grubdevs_new[${#grubdevs_new[@]}]="$grubdev"
848 fi
849 done
850 grubdevs=( "${grubdevs_new[@]}" )
851 debug 1 "updated grubdevs: [${grubdevs[@]}]"
852
853 if [ "$uefi" -ge 1 ]; then
854 nvram="--no-nvram"
855 if [ "$update_nvram" -ge 1 ]; then
856 nvram=""
857 fi
858 debug 1 "number of entries in grubdevs_new: ${#grubdevs[@]}"
859 if [ "${#grubdevs_new[@]}" -eq 1 ] && [ -b "${grubdevs_new[0]}" ]; then
860 debug 1 "Found a single entry in grubdevs, ${grubdevs_new[0]}"
861 # Currently UEFI can only be pointed to one system partition. If
862 # for some reason multiple install locations are given only use the
863 # first.
864 efi_dev="${grubdevs_new[0]}"
865 debug 1 "efi_dev=[${efi_dev}]"
866 elif [ "${#grubdevs_new[@]}" -gt 1 ]; then
867 error "Only one grub device supported on UEFI!"
868 exit 1
869 else
870 debug 1 "no storage config, parsing /proc/mounts with awk"
871 # If no storage configuration was given try to determine the system
872 # partition.
873 efi_dev=$(awk -v "MP=${mp}/boot/efi" '$2 == MP { print $1 }' /proc/mounts)
874 debug 1 "efi_dev=[${efi_dev}]"
875 [ -n "$efi_dev" ] || {
876 error "Failed to find efi device from parsing /proc/mounts"
877 return 1
878 }
879
880 fi
881 # The partition number of block device name need to be determined here
882 # so both getting the UEFI device from Curtin config and discovering it
883 # work.
884 efi_part_num=$(cat /sys/class/block/$(basename $efi_dev)/partition)
885 debug 1 "efi_part_num: $efi_part_num"
886 [ -n "${efi_part_num}" ] || {
887 error "Failed to determine $efi_dev partition number"
888 return 1
889 }
890 efi_disk=$(get_parent_disk "$efi_dev")
891 debug 1 "efi_disk: [$efi_disk]"
892 [ -b "${efi_disk}" ] || {
893 error "${efi_disk} is not a valid block device"
894 return 1
895 }
896 debug 1 "curtin uefi: installing ${grub_name} to: /boot/efi"
897 chroot "$mp" env DEBIAN_FRONTEND=noninteractive sh -exc '
898 echo "before grub-install efiboot settings"
899 efibootmgr -v || echo "WARN: efibootmgr exited $?"
900 bootid="$4"
901 efi_disk="$5"
902 efi_part_num="$6"
903 grubpost=""
904 grubmulti="/usr/lib/grub/grub-multi-install"
905 case $bootid in
906 debian|ubuntu)
907 grubcmd="grub-install"
908 if [ -e "${grubmulti}" ]; then
909 grubcmd="${grubmulti}"
910 fi
911 dpkg-reconfigure "$1"
912 update-grub
913 ;;
914 centos|redhat|rhel)
915 grubcmd="grub2-install"
916 # RHEL uses redhat instead of the os_variant rhel for the bootid.
917 if [ "$bootid" = "rhel" ]; then
918 bootid="redhat"
919 fi
920 if [ -f /boot/efi/EFI/$bootid/grubx64.efi ]; then
921 grubpost="grub2-mkconfig -o /boot/efi/EFI/$bootid/grub.cfg"
922 else
923 grubpost="grub2-mkconfig -o /boot/grub2/grub.cfg"
924 fi
925 ;;
926 *)
927 echo "Unsupported OS: $bootid" 1>&2
928 exit 1
929 ;;
930 esac
931 # grub-install in 12.04 does not contain --no-nvram, --target,
932 # or --efi-directory
933 target="--target=$2"
934 no_nvram="$3"
935 efi_dir="--efi-directory=/boot/efi"
936 gi_out=$($grubcmd --help 2>&1)
937 echo "$gi_out" | grep -q -- "$no_nvram" || no_nvram=""
938 echo "$gi_out" | grep -q -- "--target" || target=""
939 echo "$gi_out" | grep -q -- "--efi-directory" || efi_dir=""
940
941 # Do not overwrite grubx64.efi if it already exists. grub-install
942 # generates grubx64.efi and overwrites any existing binary in
943 # /boot/efi/EFI/$bootid. This binary is not signed and will cause
944 # secure boot to fail.
945 #
946 # CentOS, RHEL, Fedora ship the signed boot loader in the package
947 # grub2-efi-x64 which installs the signed boot loader to
948 # /boot/efi/EFI/$bootid/grubx64.efi. All Curtin has to do is
949 # configure the firmware. This mirrors what Anaconda does.
950 #
951 # Debian and Ubuntu come with a patched version of grub which
952 # add the install flag --uefi-secure-boot which is enabled by
953 # default. When enabled if a signed version of grub exists on
954 # the filesystem it will be copied into /boot/efi/EFI/$bootid.
955 # Stock Ubuntu images do not ship with anything in /boot. Those
956 # files are generated by installing a kernel and grub.
957 echo "Dumping /boot/efi contents"
958 find /boot/efi
959 echo "Checking for existing EFI grub entry on ESP"
960 if [ "$grubcmd" = "grub2-install" -a -f /boot/efi/EFI/$bootid/grubx64.efi ]; then
961 if [ -z "$no_nvram" ]; then
962 # UEFI firmware should be pointed to the shim if available to
963 # enable secure boot.
964 for boot_uefi in \
965 /boot/efi/EFI/$bootid/shimx64.efi \
966 /boot/efi/EFI/BOOT/BOOTX64.EFI \
967 /boot/efi/EFI/$bootid/grubx64.efi; do
968 if [ -f $boot_uefi ]; then
969 break
970 fi
971 done
972 loader=$(echo ${boot_uefi##/boot/efi} | sed "s|/|\\\|g")
973 efibootmgr --create --write-signature --label $bootid \
974 --disk $efi_disk --part $efi_part_num --loader $loader
975 rc=$?
976 [ "$rc" != "0" ] && { exit $rc; }
977 else
978 echo "skip EFI entry creation due to \"$no_nvram\" flag"
979 fi
980 else
981 echo "No previous EFI grub entry found on ESP, use $grubcmd"
982 if [ "${grubcmd}" = "${grubmulti}" ]; then
983 $grubcmd
984 else
985 $grubcmd $target $efi_dir \
986 --bootloader-id=$bootid --recheck $no_nvram
987 fi
988 fi
989 [ -z "$grubpost" ] || $grubpost;' \
990 -- "$grub_name" "$grub_target" "$nvram" "$os_variant" "$efi_disk" "$efi_part_num" </dev/null ||
991 { error "failed to install grub!"; return 1; }
992
993 chroot "$mp" sh -exc '
994 echo "after grub-install efiboot settings"
995 efibootmgr -v || echo "WARN: efibootmgr exited $?"
996 ' -- </dev/null ||
997 { error "failed to list efi boot entries!"; return 1; }
998 else
999 # Note: dpkg-reconfigure calls grub-install on ppc64
1000 # this means that using '--no-nvram' below ends up
1001 # failing very oddly. This is because grub's post-inst
1002 # runs grub-install with no target. That ends up
1003 # updating nvram badly, and then the grub-install would
1004 # not fix it because of the no-nvram there.
1005 debug 1 "curtin non-uefi: installing ${grub_name} to: ${grubdevs[*]}"
1006 chroot "$mp" env DEBIAN_FRONTEND=noninteractive sh -exc '
1007 pkg=$1; shift;
1008 bootid=$1; shift;
1009 bootver=$1; shift;
1010 grubpost=""
1011 case $bootid in
1012 debian|ubuntu)
1013 grubcmd="grub-install"
1014 dpkg-reconfigure "$pkg"
1015 update-grub
1016 ;;
1017 centos|redhat|rhel)
1018 case $bootver in
1019 6) grubcmd="grub-install";;
1020 7|8) grubcmd="grub2-install"
1021 grubpost="grub2-mkconfig -o /boot/grub2/grub.cfg";;
1022 *)
1023 echo "Unknown rhel_ver [$bootver]"
1024 exit 1
1025 esac
1026 ;;
1027 *)
1028 echo "Unsupported OS: $bootid"; 1>&2
1029 exit 1
1030 ;;
1031 esac
1032 for d in "$@"; do
1033 echo $grubcmd "$d";
1034 $grubcmd "$d" || exit; done
1035 [ -z "$grubpost" ] || $grubpost;' \
1036 -- "${grub_name}" "${os_variant}" "${rhel_ver}" "${grubdevs[@]}" </dev/null ||
1037 { error "failed to install grub!"; return 1; }
1038 fi
1039
1040 if [ -n "${mp_efi}" ]; then
1041 umount "$mp_efi" ||
1042 { error "failed to unmount $mp_efi"; return 1; }
1043 fi
1044
1045 return
1046}
1047
1048# vi: ts=4 expandtab syntax=sh470# vi: ts=4 expandtab syntax=sh
diff --git a/helpers/install-grub b/helpers/install-grub
1049deleted file mode 100755471deleted file mode 100755
index e1bfb23..0000000
--- a/helpers/install-grub
+++ /dev/null
@@ -1,7 +0,0 @@
1#!/bin/bash
2# This file is part of curtin. See LICENSE file for copyright and license info.
3
4[ "${0%/*}" = "$0" ] && . ./common || . "${0%/*}/common"
5install_grub "$@"
6
7# vi: ts=4 expandtab syntax=sh
diff --git a/pylintrc b/pylintrc
index 167cff0..67a4e01 100644
--- a/pylintrc
+++ b/pylintrc
@@ -7,7 +7,7 @@ jobs=0
7# List of members which are set dynamically and missed by pylint inference7# List of members which are set dynamically and missed by pylint inference
8# system, and so shouldn't trigger E1101 when accessed. Python regular8# system, and so shouldn't trigger E1101 when accessed. Python regular
9# expressions are accepted.9# expressions are accepted.
10generated-members=DISTROS.*,parse_*,*_data10generated-members=DISTROS\.
1111
12# List of module names for which member attributes should not be checked12# List of module names for which member attributes should not be checked
13# (useful for modules/projects where namespaces are manipulated during runtime13# (useful for modules/projects where namespaces are manipulated during runtime
diff --git a/requirements.txt b/requirements.txt
index 9066728..6afa3b8 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,3 +2,6 @@ pyyaml
2oauthlib2oauthlib
3# For validation of storate configuration format3# For validation of storate configuration format
4jsonschema4jsonschema
5# Dependency of jsonschema.
6# Version 0.16.0 is the latest version supporting Python < 3.5.
7pyrsistent==0.16.0
diff --git a/tests/data/multipath-nvme.txt b/tests/data/multipath-nvme.txt
5new file mode 1006448new file mode 100644
index 0000000..30b59e4
--- /dev/null
+++ b/tests/data/multipath-nvme.txt
@@ -0,0 +1 @@
1name='nqn.1994-11.com.samsung:nvme:PM1725a:HHHL:S3RVNA0J300208 :nsid.1' multipath='eui.335256304a3002080025384100000001' sysfs='nvme0n1' paths='1'
diff --git a/tests/unittests/helpers.py b/tests/unittests/helpers.py
index 2f5e51a..64a79ca 100644
--- a/tests/unittests/helpers.py
+++ b/tests/unittests/helpers.py
@@ -2,11 +2,13 @@
22
3import imp3import imp
4import importlib4import importlib
5import logging
5import mock6import mock
6import os7import os
7import random8import random
8import shutil9import shutil
9import string10import string
11import sys
10import tempfile12import tempfile
11from unittest import TestCase, skipIf13from unittest import TestCase, skipIf
12from contextlib import contextmanager14from contextlib import contextmanager
@@ -55,6 +57,7 @@ def skipUnlessJsonSchema():
55class CiTestCase(TestCase):57class CiTestCase(TestCase):
56 """Common testing class which all curtin unit tests subclass."""58 """Common testing class which all curtin unit tests subclass."""
5759
60 with_logs = False
58 allowed_subp = False61 allowed_subp = False
59 SUBP_SHELL_TRUE = "shell=True"62 SUBP_SHELL_TRUE = "shell=True"
6063
@@ -69,6 +72,21 @@ class CiTestCase(TestCase):
6972
70 def setUp(self):73 def setUp(self):
71 super(CiTestCase, self).setUp()74 super(CiTestCase, self).setUp()
75 if self.with_logs:
76 # Create a log handler so unit tests can search expected logs.
77 self.logger = logging.getLogger()
78 if sys.version_info[0] == 2:
79 import StringIO
80 self.logs = StringIO.StringIO()
81 else:
82 import io
83 self.logs = io.StringIO()
84 formatter = logging.Formatter('%(levelname)s: %(message)s')
85 handler = logging.StreamHandler(self.logs)
86 handler.setFormatter(formatter)
87 self.old_handlers = self.logger.handlers
88 self.logger.handlers = [handler]
89
72 if self.allowed_subp is True:90 if self.allowed_subp is True:
73 util.subp = _real_subp91 util.subp = _real_subp
74 else:92 else:
diff --git a/tests/unittests/test_block.py b/tests/unittests/test_block.py
index c62c153..78e331d 100644
--- a/tests/unittests/test_block.py
+++ b/tests/unittests/test_block.py
@@ -179,6 +179,18 @@ class TestBlock(CiTestCase):
179 byid_path = block.disk_to_byid_path('/dev/sdb')179 byid_path = block.disk_to_byid_path('/dev/sdb')
180 self.assertEqual(mapping.get('/dev/sdb'), byid_path)180 self.assertEqual(mapping.get('/dev/sdb'), byid_path)
181181
182 @mock.patch("curtin.block.os.path.exists")
183 def test__get_dev_disk_by_prefix_returns_empty_dict(self, m_exists):
184 """ _get_disk_by_prefix returns empty dict prefix dir does not exit """
185 m_exists.return_value = False
186 self.assertEqual({}, block._get_dev_disk_by_prefix("/dev/disk/by-id"))
187
188 @mock.patch("curtin.block.os.path.exists")
189 def test_disk_to_byid_returns_none_if_disk_byid_missing(self, m_exists):
190 """ disk_to_byid path returns None if /dev/disk/by-id is missing """
191 m_exists.return_value = False
192 self.assertEqual(None, block.disk_to_byid_path('/dev/sdb'))
193
182194
183class TestSysBlockPath(CiTestCase):195class TestSysBlockPath(CiTestCase):
184 @mock.patch("curtin.block.get_blockdev_for_partition")196 @mock.patch("curtin.block.get_blockdev_for_partition")
diff --git a/tests/unittests/test_block_multipath.py b/tests/unittests/test_block_multipath.py
index 2101eae..96cbcba 100644
--- a/tests/unittests/test_block_multipath.py
+++ b/tests/unittests/test_block_multipath.py
@@ -39,6 +39,20 @@ class TestMultipath(CiTestCase):
39 multipath.show_maps())39 multipath.show_maps())
40 self.m_subp.assert_called_with(expected, capture=True)40 self.m_subp.assert_called_with(expected, capture=True)
4141
42 def test_show_maps_nvme(self):
43 """verify show_maps extracts mulitpath map data correctly."""
44 NVME_MP = multipath.util.load_file('tests/data/multipath-nvme.txt')
45 self.m_subp.return_value = (NVME_MP, "")
46 expected = ['multipathd', 'show', 'maps', 'raw', 'format',
47 multipath.SHOW_MAPS_FMT]
48 self.assertEqual([
49 {'name':
50 ('nqn.1994-11.com.samsung:nvme:PM1725a:HHHL:S3RVNA0J300208 '
51 ':nsid.1'),
52 'multipath': 'eui.335256304a3002080025384100000001',
53 'sysfs': 'nvme0n1', 'paths': '1'}], multipath.show_maps())
54 self.m_subp.assert_called_with(expected, capture=True)
55
42 def test_is_mpath_device_true(self):56 def test_is_mpath_device_true(self):
43 """is_mpath_device returns true if dev DM_UUID starts with mpath-"""57 """is_mpath_device returns true if dev DM_UUID starts with mpath-"""
44 self.m_udev.udevadm_info.return_value = {'DM_UUID': 'mpath-mpatha-foo'}58 self.m_udev.udevadm_info.return_value = {'DM_UUID': 'mpath-mpatha-foo'}
diff --git a/tests/unittests/test_commands_block_meta.py b/tests/unittests/test_commands_block_meta.py
index b768cdc..d954296 100644
--- a/tests/unittests/test_commands_block_meta.py
+++ b/tests/unittests/test_commands_block_meta.py
@@ -1779,6 +1779,7 @@ class TestRaidHandler(CiTestCase):
1779 def setUp(self):1779 def setUp(self):
1780 super(TestRaidHandler, self).setUp()1780 super(TestRaidHandler, self).setUp()
17811781
1782 orig_md_path = block_meta.block.md_path
1782 basepath = 'curtin.commands.block_meta.'1783 basepath = 'curtin.commands.block_meta.'
1783 self.add_patch(basepath + 'get_path_to_storage_volume', 'm_getpath')1784 self.add_patch(basepath + 'get_path_to_storage_volume', 'm_getpath')
1784 self.add_patch(basepath + 'util', 'm_util')1785 self.add_patch(basepath + 'util', 'm_util')
@@ -1787,6 +1788,10 @@ class TestRaidHandler(CiTestCase):
1787 self.add_patch(basepath + 'block', 'm_block')1788 self.add_patch(basepath + 'block', 'm_block')
1788 self.add_patch(basepath + 'udevadm_settle', 'm_uset')1789 self.add_patch(basepath + 'udevadm_settle', 'm_uset')
17891790
1791 # The behavior of this function is being directly tested in
1792 # these tests, so we can't mock it
1793 self.m_block.md_path = orig_md_path
1794
1790 self.target = "my_target"1795 self.target = "my_target"
1791 self.config = {1796 self.config = {
1792 'storage': {1797 'storage': {
@@ -1850,12 +1855,40 @@ class TestRaidHandler(CiTestCase):
1850 block_meta.extract_storage_ordered_dict(self.config))1855 block_meta.extract_storage_ordered_dict(self.config))
1851 self.m_util.load_command_environment.return_value = {'fstab': None}1856 self.m_util.load_command_environment.return_value = {'fstab': None}
18521857
1858 def test_md_name(self):
1859 input_to_result = [
1860 ('md1', '/dev/md1'),
1861 ('os-raid1', '/dev/md/os-raid1'),
1862 ('md/os-raid1', '/dev/md/os-raid1'),
1863 ('/dev/md1', '/dev/md1'),
1864 ('/dev/md/os-raid1', '/dev/md/os-raid1'),
1865 ('bad/path', ValueError)
1866 ]
1867 for index, test in enumerate(input_to_result):
1868 param, expected = test
1869 self.storage_config['mddevice']['name'] = param
1870 try:
1871 block_meta.raid_handler(self.storage_config['mddevice'],
1872 self.storage_config)
1873 except ValueError:
1874 if param in ['bad/path']:
1875 continue
1876 else:
1877 raise
1878
1879 actual = self.m_mdadm.mdadm_create.call_args_list[index][0][0]
1880 self.assertEqual(
1881 expected,
1882 actual,
1883 "Expected {} to result in mdadm being called with {}. "
1884 "mdadm instead called with {}".format(param, expected, actual)
1885 )
1886
1853 def test_raid_handler(self):1887 def test_raid_handler(self):
1854 """ raid_handler creates raid device. """1888 """ raid_handler creates raid device. """
1855 devices = [self.random_string(), self.random_string(),1889 devices = [self.random_string(), self.random_string(),
1856 self.random_string()]1890 self.random_string()]
1857 md_devname = '/dev/' + self.storage_config['mddevice']['name']1891 md_devname = '/dev/' + self.storage_config['mddevice']['name']
1858 self.m_block.dev_path.return_value = '/dev/md0'
1859 self.m_getpath.side_effect = iter(devices)1892 self.m_getpath.side_effect = iter(devices)
1860 block_meta.raid_handler(self.storage_config['mddevice'],1893 block_meta.raid_handler(self.storage_config['mddevice'],
1861 self.storage_config)1894 self.storage_config)
@@ -1868,7 +1901,6 @@ class TestRaidHandler(CiTestCase):
18681901
1869 devices = [self.random_string(), self.random_string(),1902 devices = [self.random_string(), self.random_string(),
1870 self.random_string()]1903 self.random_string()]
1871 self.m_block.dev_path.return_value = '/dev/md0'
1872 self.m_getpath.side_effect = iter(devices)1904 self.m_getpath.side_effect = iter(devices)
1873 m_verify.return_value = True1905 m_verify.return_value = True
1874 self.storage_config['mddevice']['preserve'] = True1906 self.storage_config['mddevice']['preserve'] = True
@@ -1882,7 +1914,6 @@ class TestRaidHandler(CiTestCase):
1882 devices = [self.random_string(), self.random_string(),1914 devices = [self.random_string(), self.random_string(),
1883 self.random_string()]1915 self.random_string()]
1884 md_devname = '/dev/' + self.storage_config['mddevice']['name']1916 md_devname = '/dev/' + self.storage_config['mddevice']['name']
1885 self.m_block.dev_path.return_value = '/dev/md0'
1886 self.m_getpath.side_effect = iter(devices)1917 self.m_getpath.side_effect = iter(devices)
1887 self.m_mdadm.md_check.return_value = True1918 self.m_mdadm.md_check.return_value = True
1888 self.storage_config['mddevice']['preserve'] = True1919 self.storage_config['mddevice']['preserve'] = True
@@ -1898,7 +1929,6 @@ class TestRaidHandler(CiTestCase):
1898 devices = [self.random_string(), self.random_string(),1929 devices = [self.random_string(), self.random_string(),
1899 self.random_string()]1930 self.random_string()]
1900 md_devname = '/dev/' + self.storage_config['mddevice']['name']1931 md_devname = '/dev/' + self.storage_config['mddevice']['name']
1901 self.m_block.dev_path.return_value = '/dev/md0'
1902 self.m_getpath.side_effect = iter(devices)1932 self.m_getpath.side_effect = iter(devices)
1903 self.m_mdadm.md_check.side_effect = iter([False, True])1933 self.m_mdadm.md_check.side_effect = iter([False, True])
1904 self.storage_config['mddevice']['preserve'] = True1934 self.storage_config['mddevice']['preserve'] = True
@@ -1916,7 +1946,6 @@ class TestRaidHandler(CiTestCase):
1916 devices = [self.random_string(), self.random_string(),1946 devices = [self.random_string(), self.random_string(),
1917 self.random_string()]1947 self.random_string()]
1918 md_devname = '/dev/' + self.storage_config['mddevice']['name']1948 md_devname = '/dev/' + self.storage_config['mddevice']['name']
1919 self.m_block.dev_path.return_value = '/dev/md0'
1920 self.m_getpath.side_effect = iter(devices)1949 self.m_getpath.side_effect = iter(devices)
1921 self.m_mdadm.md_check.side_effect = iter([False, False])1950 self.m_mdadm.md_check.side_effect = iter([False, False])
1922 self.storage_config['mddevice']['preserve'] = True1951 self.storage_config['mddevice']['preserve'] = True
@@ -2557,4 +2586,115 @@ class TestVerifyPtableFlag(CiTestCase):
2557 sfdisk_info=self.sfdisk_info_dos)2586 sfdisk_info=self.sfdisk_info_dos)
25582587
25592588
2589class TestGetDevicePathsFromStorageConfig(CiTestCase):
2590
2591 def setUp(self):
2592 super(TestGetDevicePathsFromStorageConfig, self).setUp()
2593 base = 'curtin.commands.block_meta.'
2594 self.add_patch(base + 'get_path_to_storage_volume', 'mock_getpath')
2595 self.add_patch(base + 'os.path.exists', 'm_exists')
2596 self.m_exists.return_value = True
2597 self.mock_getpath.side_effect = self._getpath
2598 self.prefix = '/test/dev/'
2599 self.config = {
2600 'storage': {
2601 'version': 1,
2602 'config': [
2603 {'id': 'sda',
2604 'type': 'disk',
2605 'name': 'main_disk',
2606 'ptable': 'gpt',
2607 'serial': 'disk-a'},
2608 {'id': 'disk-sda-part-1',
2609 'type': 'partition',
2610 'device': 'sda',
2611 'name': 'bios_boot',
2612 'number': 1,
2613 'size': '1M',
2614 'flag': 'bios_grub'},
2615 {'id': 'disk-sda-part-2',
2616 'type': 'partition',
2617 'device': 'sda',
2618 'number': 2,
2619 'size': '5GB'},
2620 ],
2621 }
2622 }
2623 self.disk1 = self.config['storage']['config'][0]
2624 self.part1 = self.config['storage']['config'][1]
2625 self.part2 = self.config['storage']['config'][2]
2626 self.sconfig = self._sconfig(self.config)
2627
2628 def _sconfig(self, config):
2629 return block_meta.extract_storage_ordered_dict(config)
2630
2631 def _getpath(self, item_id, _sconfig):
2632 return self.prefix + item_id
2633
2634 def test_devpath_selects_disks_partitions_with_wipe_setting(self):
2635 self.disk1['wipe'] = 'superblock'
2636 self.part1['wipe'] = 'superblock'
2637 self.sconfig = self._sconfig(self.config)
2638
2639 expected_devpaths = [
2640 self.prefix + self.disk1['id'], self.prefix + self.part1['id']]
2641 result = block_meta.get_device_paths_from_storage_config(self.sconfig)
2642 self.assertEqual(sorted(expected_devpaths), sorted(result))
2643 self.assertEqual([
2644 call(self.disk1['id'], self.sconfig),
2645 call(self.part1['id'], self.sconfig)],
2646 self.mock_getpath.call_args_list)
2647 self.assertEqual(
2648 sorted([call(devpath) for devpath in expected_devpaths]),
2649 sorted(self.m_exists.call_args_list))
2650
2651 def test_devpath_raises_exception_if_wipe_and_preserve_set(self):
2652 self.disk1['wipe'] = 'superblock'
2653 self.disk1['preserve'] = True
2654 self.sconfig = self._sconfig(self.config)
2655
2656 with self.assertRaises(RuntimeError):
2657 block_meta.get_device_paths_from_storage_config(self.sconfig)
2658 self.assertEqual([], self.mock_getpath.call_args_list)
2659 self.assertEqual([], self.m_exists.call_args_list)
2660
2661 def test_devpath_check_boolean_value_if_wipe_and_preserve_set(self):
2662 self.disk1['wipe'] = 'superblock'
2663 self.disk1['preserve'] = False
2664 self.sconfig = self._sconfig(self.config)
2665
2666 expected_devpaths = [self.prefix + self.disk1['id']]
2667 result = block_meta.get_device_paths_from_storage_config(self.sconfig)
2668 self.assertEqual(expected_devpaths, result)
2669 self.assertEqual(
2670 [call(self.disk1['id'], self.sconfig)],
2671 self.mock_getpath.call_args_list)
2672 self.assertEqual(
2673 sorted([call(devpath) for devpath in expected_devpaths]),
2674 sorted(self.m_exists.call_args_list))
2675
2676 def test_devpath_check_preserved_devices_skipped(self):
2677 self.disk1['preserve'] = True
2678 self.sconfig = self._sconfig(self.config)
2679
2680 result = block_meta.get_device_paths_from_storage_config(self.sconfig)
2681 self.assertEqual([], result)
2682 self.assertEqual([], self.mock_getpath.call_args_list)
2683 self.assertEqual([], self.m_exists.call_args_list)
2684
2685 def test_devpath_check_missing_path_devices_skipped(self):
2686 self.disk1['wipe'] = 'superblock'
2687 self.sconfig = self._sconfig(self.config)
2688
2689 self.m_exists.return_value = False
2690 result = block_meta.get_device_paths_from_storage_config(self.sconfig)
2691 self.assertEqual([], result)
2692 self.assertEqual(
2693 [call(self.disk1['id'], self.sconfig)],
2694 self.mock_getpath.call_args_list)
2695 self.assertEqual(
2696 [call(self.prefix + self.disk1['id'])],
2697 self.m_exists.call_args_list)
2698
2699
2560# vi: ts=4 expandtab syntax=python2700# vi: ts=4 expandtab syntax=python
diff --git a/tests/unittests/test_curthooks.py b/tests/unittests/test_curthooks.py
index 2349456..e5fead3 100644
--- a/tests/unittests/test_curthooks.py
+++ b/tests/unittests/test_curthooks.py
@@ -1,5 +1,6 @@
1# This file is part of curtin. See LICENSE file for copyright and license info.1# This file is part of curtin. See LICENSE file for copyright and license info.
22
3import copy
3import os4import os
4from mock import call, patch5from mock import call, patch
5import textwrap6import textwrap
@@ -10,7 +11,7 @@ from curtin import distro
10from curtin import util11from curtin import util
11from curtin import config12from curtin import config
12from curtin.reporter import events13from curtin.reporter import events
13from .helpers import CiTestCase, dir2dict, populate_dir14from .helpers import CiTestCase, dir2dict, populate_dir, random
1415
1516
16class TestGetFlashKernelPkgs(CiTestCase):17class TestGetFlashKernelPkgs(CiTestCase):
@@ -531,12 +532,55 @@ class TestSetupZipl(CiTestCase):
531 content)532 content)
532533
533534
535class EfiOutput(object):
536
537 def __init__(self, current=None, order=None, entries=None):
538 self.entries = {}
539 if entries:
540 for entry in entries:
541 self.entries.update(entry)
542 self.current = current
543 self.order = order
544 if not order and self.entries:
545 self.order = sorted(self.entries.keys())
546
547 def add_entry(self, bootnum=None, name=None, path=None, current=False):
548 if not bootnum:
549 bootnum = "%04x" % random.randint(0, 1000)
550 if not name:
551 name = CiTestCase.random_string()
552 if not path:
553 path = ''
554 if bootnum not in self.entries:
555 self.entries[bootnum] = {'name': name, 'path': path}
556 if not self.order:
557 self.order = []
558 self.order.append(bootnum)
559 if current:
560 self.current = bootnum
561
562 def set_order(self, new_order):
563 self.order = new_order
564
565 def as_dict(self):
566 output = {}
567 if self.current:
568 output['current'] = self.current
569 if self.order:
570 output['order'] = self.order
571 output['entries'] = self.entries
572 return output
573
574
534class TestSetupGrub(CiTestCase):575class TestSetupGrub(CiTestCase):
535576
577 with_logs = True
578
536 def setUp(self):579 def setUp(self):
537 super(TestSetupGrub, self).setUp()580 super(TestSetupGrub, self).setUp()
538 self.target = self.tmp_dir()581 self.target = self.tmp_dir()
539 self.distro_family = distro.DISTROS.debian582 self.distro_family = distro.DISTROS.debian
583 self.variant = 'ubuntu'
540 self.add_patch('curtin.distro.lsb_release', 'mock_lsb_release')584 self.add_patch('curtin.distro.lsb_release', 'mock_lsb_release')
541 self.mock_lsb_release.return_value = {'codename': 'xenial'}585 self.mock_lsb_release.return_value = {'codename': 'xenial'}
542 self.add_patch('curtin.util.is_uefi_bootable',586 self.add_patch('curtin.util.is_uefi_bootable',
@@ -556,7 +600,8 @@ class TestSetupGrub(CiTestCase):
556 updated_cfg = {600 updated_cfg = {
557 'install_devices': ['/dev/vdb']601 'install_devices': ['/dev/vdb']
558 }602 }
559 curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family)603 curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family,
604 variant=self.variant)
560 self.m_install_grub.assert_called_with(605 self.m_install_grub.assert_called_with(
561 ['/dev/vdb'], self.target, uefi=False, grubcfg=updated_cfg)606 ['/dev/vdb'], self.target, uefi=False, grubcfg=updated_cfg)
562607
@@ -588,7 +633,8 @@ class TestSetupGrub(CiTestCase):
588 },633 },
589 }634 }
590 m_exists.return_value = True635 m_exists.return_value = True
591 curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family)636 curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family,
637 variant=self.variant)
592 self.m_install_grub.assert_called_with(638 self.m_install_grub.assert_called_with(
593 ['/dev/vdb'], self.target, uefi=False,639 ['/dev/vdb'], self.target, uefi=False,
594 grubcfg={'install_devices': ['/dev/vdb']})640 grubcfg={'install_devices': ['/dev/vdb']})
@@ -638,7 +684,8 @@ class TestSetupGrub(CiTestCase):
638 }684 }
639 m_exists.return_value = True685 m_exists.return_value = True
640 m_is_valid_device.side_effect = (False, True, False, True)686 m_is_valid_device.side_effect = (False, True, False, True)
641 curthooks.setup_grub(cfg, self.target, osfamily=distro.DISTROS.redhat)687 curthooks.setup_grub(cfg, self.target, osfamily=distro.DISTROS.redhat,
688 variant='centos')
642 self.m_install_grub.assert_called_with(689 self.m_install_grub.assert_called_with(
643 ['/dev/vdb1'], self.target, uefi=True,690 ['/dev/vdb1'], self.target, uefi=True,
644 grubcfg={'update_nvram': False, 'install_devices': ['/dev/vdb1']}691 grubcfg={'update_nvram': False, 'install_devices': ['/dev/vdb1']}
@@ -650,7 +697,8 @@ class TestSetupGrub(CiTestCase):
650 'install_devices': None,697 'install_devices': None,
651 },698 },
652 }699 }
653 curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family)700 curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family,
701 variant=self.variant)
654 self.m_install_grub.assert_called_with(702 self.m_install_grub.assert_called_with(
655 ['none'], self.target, uefi=False,703 ['none'], self.target, uefi=False,
656 grubcfg={'install_devices': None}704 grubcfg={'install_devices': None}
@@ -681,7 +729,8 @@ class TestSetupGrub(CiTestCase):
681 }729 }
682 }730 }
683 }731 }
684 curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family)732 curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family,
733 variant=self.variant)
685 self.m_install_grub.assert_called_with(734 self.m_install_grub.assert_called_with(
686 ['/dev/vdb'], self.target, uefi=True, grubcfg=cfg.get('grub')735 ['/dev/vdb'], self.target, uefi=True, grubcfg=cfg.get('grub')
687 )736 )
@@ -721,7 +770,8 @@ class TestSetupGrub(CiTestCase):
721 }770 }
722 }771 }
723 self.mock_haspkg.return_value = False772 self.mock_haspkg.return_value = False
724 curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family)773 curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family,
774 variant=self.variant)
725775
726 expected_calls = [776 expected_calls = [
727 call(['efibootmgr', '-B', '-b', '0001'],777 call(['efibootmgr', '-B', '-b', '0001'],
@@ -762,70 +812,304 @@ class TestSetupGrub(CiTestCase):
762 }812 }
763 }813 }
764 self.mock_haspkg.return_value = False814 self.mock_haspkg.return_value = False
765 curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family)815 curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family,
816 variant=self.variant)
766 self.assertEquals([817 self.assertEquals([
767 call(['efibootmgr', '-o', '0001,0000'], target=self.target)],818 call(['efibootmgr', '-o', '0001,0000'], target=self.target)],
768 self.mock_subp.call_args_list)819 self.mock_subp.call_args_list)
769820
821 @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
822 def test_grub_install_uefi_reorders_no_current_new_entry(self):
823 self.add_patch('curtin.distro.install_packages', 'mock_install')
824 self.add_patch('curtin.distro.has_pkg_available', 'mock_haspkg')
825 self.add_patch('curtin.util.get_efibootmgr', 'mock_efibootmgr')
826 self.mock_is_uefi_bootable.return_value = True
827 cfg = {
828 'grub': {
829 'install_devices': ['/dev/vdb'],
830 'update_nvram': True,
831 'remove_old_uefi_loaders': False,
832 'reorder_uefi': True,
833 },
834 }
835
836 # Single existing entry 0001
837 efi_orig = EfiOutput()
838 efi_orig.add_entry(bootnum='0001', name='centos')
770839
771class TestUefiRemoveDuplicateEntries(CiTestCase):840 # After install add a second entry, 0000 to the front of order
841 efi_post = copy.deepcopy(efi_orig)
842 efi_post.add_entry(bootnum='0000', name='ubuntu')
843 efi_post.set_order(['0000', '0001'])
772844
773 def setUp(self):845 # After reorder we should have the target install first
774 super(TestUefiRemoveDuplicateEntries, self).setUp()846 efi_final = copy.deepcopy(efi_post)
775 self.target = self.tmp_dir()847
776 self.add_patch('curtin.util.get_efibootmgr', 'm_efibootmgr')848 self.mock_efibootmgr.side_effect = iter([
777 self.add_patch('curtin.util.subp', 'm_subp')849 efi_orig.as_dict(), # collect original order before install
850 efi_orig.as_dict(), # remove_old_loaders query (no change)
851 efi_post.as_dict(), # efi table after grub install, (changed)
852 efi_final.as_dict(), # remove duplicates checks and finds reorder
853 # has changed
854 ])
855 self.mock_haspkg.return_value = False
856 curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family,
857 variant=self.variant)
858 logs = self.logs.getvalue()
859 print(logs)
860 self.assertEquals([], self.mock_subp.call_args_list)
861 self.assertIn("Using fallback UEFI reordering:", logs)
862 self.assertIn("missing 'BootCurrent' value", logs)
863 self.assertIn("Found new boot entries: ['0000']", logs)
778864
779 @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)865 @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
780 def test_uefi_remove_duplicate_entries(self):866 def test_grub_install_uefi_reorders_no_curr_same_size_order_no_match(self):
867 self.add_patch('curtin.distro.install_packages', 'mock_install')
868 self.add_patch('curtin.distro.has_pkg_available', 'mock_haspkg')
869 self.add_patch('curtin.util.get_efibootmgr', 'mock_efibootmgr')
870 self.add_patch('curtin.commands.curthooks.uefi_remove_old_loaders',
871 'mock_remove_old_loaders')
872 self.mock_is_uefi_bootable.return_value = True
781 cfg = {873 cfg = {
782 'grub': {874 'grub': {
783 'install_devices': ['/dev/vdb'],875 'install_devices': ['/dev/vdb'],
784 'update_nvram': True,876 'update_nvram': True,
877 'remove_old_uefi_loaders': False,
878 'reorder_uefi': True,
785 },879 },
786 }880 }
787 self.m_efibootmgr.return_value = {881
788 'current': '0000',882 # Existing Custom Ubuntu, usb and cd/dvd entry, booting Ubuntu
789 'entries': {883 efi_orig = EfiOutput()
790 '0000': {884 efi_orig.add_entry(bootnum='0001', name='Ubuntu Deluxe Edition')
791 'name': 'ubuntu',885 efi_orig.add_entry(bootnum='0002', name='USB Device')
792 'path': (886 efi_orig.add_entry(bootnum='0000', name='CD/DVD')
793 'HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),887 efi_orig.set_order(['0001', '0002', '0000'])
794 },888
795 '0001': {889 # after install existing ubuntu entry is reused, no change in order
796 'name': 'ubuntu',890 efi_post = efi_orig
797 'path': (891
798 'HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),892 # after reorder, no change is made due to the installed distro variant
799 },893 # string 'ubuntu' is not found in the boot entries so we retain the
800 '0002': { # Is not a duplicate because of unique path894 # original efi order.
801 'name': 'ubuntu',895 efi_final = efi_post
802 'path': (896
803 'HD(2,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),897 self.mock_efibootmgr.side_effect = iter([
804 },898 efi_orig.as_dict(), # collect original order before install
805 '0003': { # Is duplicate of 0000899 efi_orig.as_dict(), # remove_old_loaders query
806 'name': 'ubuntu',900 efi_post.as_dict(), # reorder entries queries post install
807 'path': (901 efi_final.as_dict(), # remove duplicates checks and finds reorder
808 'HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),902 ])
809 },903
810 }904 self.mock_haspkg.return_value = False
905 curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family,
906 variant=self.variant)
907
908 logs = self.logs.getvalue()
909 print(logs)
910 self.assertEquals([], self.mock_subp.call_args_list)
911 self.assertIn("Using fallback UEFI reordering:", logs)
912 self.assertIn("missing 'BootCurrent' value", logs)
913 self.assertIn("Current and Previous bootorders match", logs)
914 self.assertIn("Looking for installed entry variant=", logs)
915 self.assertIn("Did not find an entry with variant=", logs)
916 self.assertIn("No changes to boot order.", logs)
917
918 @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
919 def test_grub_install_uefi_reorders_force_fallback(self):
920 self.add_patch('curtin.distro.install_packages', 'mock_install')
921 self.add_patch('curtin.distro.has_pkg_available', 'mock_haspkg')
922 self.add_patch('curtin.util.get_efibootmgr', 'mock_efibootmgr')
923 self.mock_is_uefi_bootable.return_value = True
924 cfg = {
925 'grub': {
926 'install_devices': ['/dev/vdb'],
927 'update_nvram': True,
928 'remove_old_uefi_loaders': True,
929 'reorder_uefi': True,
930 'reorder_uefi_force_fallback': True,
931 },
811 }932 }
933 # Single existing entry 0001 and set as current, which should avoid
934 # any fallback logic, but we're forcing fallback pack via config
935 efi_orig = EfiOutput()
936 efi_orig.add_entry(bootnum='0001', name='PXE', current=True)
937 print(efi_orig.as_dict())
938
939 # After install add a second entry, 0000 to the front of order
940 efi_post = copy.deepcopy(efi_orig)
941 efi_post.add_entry(bootnum='0000', name='ubuntu')
942 efi_post.set_order(['0000', '0001'])
943 print(efi_orig.as_dict())
944
945 # After reorder we should have the original boot entry 0001 as first
946 efi_final = copy.deepcopy(efi_post)
947 efi_final.set_order(['0001', '0000'])
948
949 self.mock_efibootmgr.side_effect = iter([
950 efi_orig.as_dict(), # collect original order before install
951 efi_orig.as_dict(), # remove_old_loaders query (no change)
952 efi_post.as_dict(), # efi table after grub install, (changed)
953 efi_final.as_dict(), # remove duplicates checks and finds reorder
954 # has changed
955 ])
812956
813 curthooks.uefi_remove_duplicate_entries(cfg, self.target)957 self.mock_haspkg.return_value = False
958 curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family,
959 variant=self.variant)
960 logs = self.logs.getvalue()
961 print(logs)
814 self.assertEquals([962 self.assertEquals([
815 call(['efibootmgr', '--bootnum=0001', '--delete-bootnum'],963 call(['efibootmgr', '-o', '0001,0000'], target=self.target)],
816 target=self.target),964 self.mock_subp.call_args_list)
817 call(['efibootmgr', '--bootnum=0003', '--delete-bootnum'],965 self.assertIn("Using fallback UEFI reordering:", logs)
818 target=self.target)966 self.assertIn("config 'reorder_uefi_force_fallback' is True", logs)
819 ], self.m_subp.call_args_list)
820967
821 @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)968 @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
822 def test_uefi_remove_duplicate_entries_no_change(self):969 def test_grub_install_uefi_reorders_network_first(self):
970 self.add_patch('curtin.distro.install_packages', 'mock_install')
971 self.add_patch('curtin.distro.has_pkg_available', 'mock_haspkg')
972 self.add_patch('curtin.util.get_efibootmgr', 'mock_efibootmgr')
973 self.mock_is_uefi_bootable.return_value = True
823 cfg = {974 cfg = {
824 'grub': {975 'grub': {
825 'install_devices': ['/dev/vdb'],976 'install_devices': ['/dev/vdb'],
826 'update_nvram': True,977 'update_nvram': True,
978 'remove_old_uefi_loaders': True,
979 'reorder_uefi': True,
980 },
981 }
982
983 # Existing ubuntu, usb and cd/dvd entry, booting ubuntu
984 efi_orig = EfiOutput()
985 efi_orig.add_entry(bootnum='0001', name='centos')
986 efi_orig.add_entry(bootnum='0002', name='Network')
987 efi_orig.add_entry(bootnum='0003', name='PXE')
988 efi_orig.add_entry(bootnum='0004', name='LAN')
989 efi_orig.add_entry(bootnum='0000', name='CD/DVD')
990 efi_orig.set_order(['0001', '0002', '0003', '0004', '0000'])
991 print(efi_orig.as_dict())
992
993 # after install we add an ubuntu entry, and grub puts it first
994 efi_post = copy.deepcopy(efi_orig)
995 efi_post.add_entry(bootnum='0007', name='ubuntu')
996 efi_post.set_order(['0007'] + efi_orig.order)
997 print(efi_post.as_dict())
998
999 # reorder must place all network devices first, then ubuntu, and others
1000 efi_final = copy.deepcopy(efi_post)
1001 expected_order = ['0002', '0003', '0004', '0007', '0001', '0000']
1002 efi_final.set_order(expected_order)
1003
1004 self.mock_efibootmgr.side_effect = iter([
1005 efi_orig.as_dict(), # collect original order before install
1006 efi_orig.as_dict(), # remove_old_loaders query
1007 efi_post.as_dict(), # reorder entries queries post install
1008 efi_final.as_dict(), # remove duplicates checks and finds reorder
1009 ])
1010 self.mock_haspkg.return_value = False
1011 curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family,
1012 variant=self.variant)
1013 logs = self.logs.getvalue()
1014 print(logs)
1015 print('Number of bootmgr calls: %s' % self.mock_efibootmgr.call_count)
1016 self.assertEquals([
1017 call(['efibootmgr', '-o', '%s' % (",".join(expected_order))],
1018 target=self.target)],
1019 self.mock_subp.call_args_list)
1020 self.assertIn("Using fallback UEFI reordering:", logs)
1021 self.assertIn("missing 'BootCurrent' value", logs)
1022 self.assertIn("Looking for installed entry variant=", logs)
1023 self.assertIn("found netboot entries: ['0002', '0003', '0004']", logs)
1024 self.assertIn("found other entries: ['0001', '0000']", logs)
1025 self.assertIn("found target entry: ['0007']", logs)
1026
1027
1028class TestUefiRemoveDuplicateEntries(CiTestCase):
1029
1030 efibootmgr_output = {
1031 'current': '0000',
1032 'entries': {
1033 '0000': {
1034 'name': 'ubuntu',
1035 'path': (
1036 'HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),
1037 },
1038 '0001': { # Is duplicate of 0000
1039 'name': 'ubuntu',
1040 'path': (
1041 'HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),
1042 },
1043 '0002': { # Is not a duplicate because of unique path
1044 'name': 'ubuntu',
1045 'path': (
1046 'HD(2,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),
1047 },
1048 '0003': { # Is duplicate of 0000
1049 'name': 'ubuntu',
1050 'path': (
1051 'HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),
827 },1052 },
828 }1053 }
1054 }
1055
1056 def setUp(self):
1057 super(TestUefiRemoveDuplicateEntries, self).setUp()
1058 self.target = self.tmp_dir()
1059 self.add_patch('curtin.util.get_efibootmgr', 'm_efibootmgr')
1060 self.add_patch('curtin.util.subp', 'm_subp')
1061 self.m_efibootmgr.return_value = copy.deepcopy(self.efibootmgr_output)
1062
1063 @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
1064 def test_uefi_remove_duplicate_entries(self):
1065 grubcfg = {}
1066 curthooks.uefi_remove_duplicate_entries(grubcfg, self.target)
1067 self.assertEquals([
1068 call(['efibootmgr', '--bootnum=0001', '--delete-bootnum'],
1069 target=self.target),
1070 call(['efibootmgr', '--bootnum=0003', '--delete-bootnum'],
1071 target=self.target)
1072 ], self.m_subp.call_args_list)
1073
1074 @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
1075 def test_uefi_remove_duplicate_entries_no_bootcurrent(self):
1076 grubcfg = {}
1077 efiout = copy.deepcopy(self.efibootmgr_output)
1078 del efiout['current']
1079 self.m_efibootmgr.return_value = efiout
1080 curthooks.uefi_remove_duplicate_entries(grubcfg, self.target)
1081 self.assertEquals([
1082 call(['efibootmgr', '--bootnum=0001', '--delete-bootnum'],
1083 target=self.target),
1084 call(['efibootmgr', '--bootnum=0003', '--delete-bootnum'],
1085 target=self.target)
1086 ], self.m_subp.call_args_list)
1087
1088 @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
1089 def test_uefi_remove_duplicate_entries_disabled(self):
1090 grubcfg = {
1091 'remove_duplicate_entries': False,
1092 }
1093 curthooks.uefi_remove_duplicate_entries(grubcfg, self.target)
1094 self.assertEquals([], self.m_subp.call_args_list)
1095
1096 @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
1097 def test_uefi_remove_duplicate_entries_skip_bootcurrent(self):
1098 grubcfg = {}
1099 efiout = copy.deepcopy(self.efibootmgr_output)
1100 efiout['current'] = '0003'
1101 self.m_efibootmgr.return_value = efiout
1102 curthooks.uefi_remove_duplicate_entries(grubcfg, self.target)
1103 self.assertEquals([
1104 call(['efibootmgr', '--bootnum=0000', '--delete-bootnum'],
1105 target=self.target),
1106 call(['efibootmgr', '--bootnum=0001', '--delete-bootnum'],
1107 target=self.target),
1108 ], self.m_subp.call_args_list)
1109
1110 @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
1111 def test_uefi_remove_duplicate_entries_no_change(self):
1112 grubcfg = {}
829 self.m_efibootmgr.return_value = {1113 self.m_efibootmgr.return_value = {
830 'current': '0000',1114 'current': '0000',
831 'entries': {1115 'entries': {
@@ -846,8 +1130,7 @@ class TestUefiRemoveDuplicateEntries(CiTestCase):
846 },1130 },
847 }1131 }
848 }1132 }
8491133 curthooks.uefi_remove_duplicate_entries(grubcfg, self.target)
850 curthooks.uefi_remove_duplicate_entries(cfg, self.target)
851 self.assertEquals([], self.m_subp.call_args_list)1134 self.assertEquals([], self.m_subp.call_args_list)
8521135
8531136
@@ -1110,9 +1393,8 @@ class TestDetectRequiredPackages(CiTestCase):
1110 {'type': 'static', 'address': '2001:1::1/64'}]}},1393 {'type': 'static', 'address': '2001:1::1/64'}]}},
1111 2: {1394 2: {
1112 'openvswitch': {1395 'openvswitch': {
1113 'openvswitch': {1396 'bridges': {
1114 'bridges': {1397 'br-int': {'openvswitch': {}}}},
1115 'br-int': {'ports': {'eth15': {'tag': 2}}}}}},
1116 'vlans': {1398 'vlans': {
1117 'vlans': {1399 'vlans': {
1118 'en-intra': {'id': 1, 'link': 'eno1', 'dhcp4': 'yes'},1400 'en-intra': {'id': 1, 'link': 'eno1', 'dhcp4': 'yes'},
@@ -1245,7 +1527,7 @@ class TestDetectRequiredPackages(CiTestCase):
1245 ({'network': {1527 ({'network': {
1246 'version': 2,1528 'version': 2,
1247 'items': ('openvswitch',)}},1529 'items': ('openvswitch',)}},
1248 ('openvswitch-switch', )),1530 ('bridge-utils', 'openvswitch-switch', )),
1249 ))1531 ))
12501532
1251 def test_network_v2_detect_renderers(self):1533 def test_network_v2_detect_renderers(self):
@@ -1726,6 +2008,12 @@ class TestUefiFindGrubDeviceIds(CiTestCase):
1726 'fstype': 'fat32',2008 'fstype': 'fat32',
1727 },2009 },
1728 {2010 {
2011 'id': 'vdb-part2-swap_mount',
2012 'type': 'mount',
2013 'device': 'vdb-part2-swap_format',
2014 'options': '',
2015 },
2016 {
1729 'id': 'vdb-part1_mount',2017 'id': 'vdb-part1_mount',
1730 'type': 'mount',2018 'type': 'mount',
1731 'device': 'vdb-part1_format',2019 'device': 'vdb-part1_format',
diff --git a/tests/unittests/test_distro.py b/tests/unittests/test_distro.py
index eb62dd8..380680c 100644
--- a/tests/unittests/test_distro.py
+++ b/tests/unittests/test_distro.py
@@ -65,7 +65,7 @@ class TestParseDpkgVersion(CiTestCase):
65 def test_simple_native_package_version(self):65 def test_simple_native_package_version(self):
66 """dpkg versions must have a -. If not present expect value error."""66 """dpkg versions must have a -. If not present expect value error."""
67 self.assertEqual(67 self.assertEqual(
68 {'major': 2, 'minor': 28, 'micro': 0, 'extra': None,68 {'epoch': 0, 'major': 2, 'minor': 28, 'micro': 0, 'extra': None,
69 'raw': '2.28', 'upstream': '2.28', 'name': 'germinate',69 'raw': '2.28', 'upstream': '2.28', 'name': 'germinate',
70 'semantic_version': 22800},70 'semantic_version': 22800},
71 distro.parse_dpkg_version('2.28', name='germinate'))71 distro.parse_dpkg_version('2.28', name='germinate'))
@@ -73,7 +73,7 @@ class TestParseDpkgVersion(CiTestCase):
73 def test_complex_native_package_version(self):73 def test_complex_native_package_version(self):
74 dver = '1.0.106ubuntu2+really1.0.97ubuntu1'74 dver = '1.0.106ubuntu2+really1.0.97ubuntu1'
75 self.assertEqual(75 self.assertEqual(
76 {'major': 1, 'minor': 0, 'micro': 106,76 {'epoch': 0, 'major': 1, 'minor': 0, 'micro': 106,
77 'extra': 'ubuntu2+really1.0.97ubuntu1',77 'extra': 'ubuntu2+really1.0.97ubuntu1',
78 'raw': dver, 'upstream': dver, 'name': 'debootstrap',78 'raw': dver, 'upstream': dver, 'name': 'debootstrap',
79 'semantic_version': 100106},79 'semantic_version': 100106},
@@ -82,14 +82,14 @@ class TestParseDpkgVersion(CiTestCase):
8282
83 def test_simple_valid(self):83 def test_simple_valid(self):
84 self.assertEqual(84 self.assertEqual(
85 {'major': 1, 'minor': 2, 'micro': 3, 'extra': None,85 {'epoch': 0, 'major': 1, 'minor': 2, 'micro': 3, 'extra': None,
86 'raw': '1.2.3-0', 'upstream': '1.2.3', 'name': 'foo',86 'raw': '1.2.3-0', 'upstream': '1.2.3', 'name': 'foo',
87 'semantic_version': 10203},87 'semantic_version': 10203},
88 distro.parse_dpkg_version('1.2.3-0', name='foo'))88 distro.parse_dpkg_version('1.2.3-0', name='foo'))
8989
90 def test_simple_valid_with_semx(self):90 def test_simple_valid_with_semx(self):
91 self.assertEqual(91 self.assertEqual(
92 {'major': 1, 'minor': 2, 'micro': 3, 'extra': None,92 {'epoch': 0, 'major': 1, 'minor': 2, 'micro': 3, 'extra': None,
93 'raw': '1.2.3-0', 'upstream': '1.2.3',93 'raw': '1.2.3-0', 'upstream': '1.2.3',
94 'semantic_version': 123},94 'semantic_version': 123},
95 distro.parse_dpkg_version('1.2.3-0', semx=(100, 10, 1)))95 distro.parse_dpkg_version('1.2.3-0', semx=(100, 10, 1)))
@@ -98,7 +98,8 @@ class TestParseDpkgVersion(CiTestCase):
98 """upstream versions may have a hyphen."""98 """upstream versions may have a hyphen."""
99 cver = '18.2-14-g6d48d265-0ubuntu1'99 cver = '18.2-14-g6d48d265-0ubuntu1'
100 self.assertEqual(100 self.assertEqual(
101 {'major': 18, 'minor': 2, 'micro': 0, 'extra': '-14-g6d48d265',101 {'epoch': 0, 'major': 18, 'minor': 2, 'micro': 0,
102 'extra': '-14-g6d48d265',
102 'raw': cver, 'upstream': '18.2-14-g6d48d265',103 'raw': cver, 'upstream': '18.2-14-g6d48d265',
103 'name': 'cloud-init', 'semantic_version': 180200},104 'name': 'cloud-init', 'semantic_version': 180200},
104 distro.parse_dpkg_version(cver, name='cloud-init'))105 distro.parse_dpkg_version(cver, name='cloud-init'))
@@ -107,11 +108,30 @@ class TestParseDpkgVersion(CiTestCase):
107 """multipath tools has a + in it."""108 """multipath tools has a + in it."""
108 mver = '0.5.0+git1.656f8865-5ubuntu2.5'109 mver = '0.5.0+git1.656f8865-5ubuntu2.5'
109 self.assertEqual(110 self.assertEqual(
110 {'major': 0, 'minor': 5, 'micro': 0, 'extra': '+git1.656f8865',111 {'epoch': 0, 'major': 0, 'minor': 5, 'micro': 0,
112 'extra': '+git1.656f8865',
111 'raw': mver, 'upstream': '0.5.0+git1.656f8865',113 'raw': mver, 'upstream': '0.5.0+git1.656f8865',
112 'semantic_version': 500},114 'semantic_version': 500},
113 distro.parse_dpkg_version(mver))115 distro.parse_dpkg_version(mver))
114116
117 def test_package_with_epoch(self):
118 """xxd has epoch"""
119 mver = '2:8.1.2269-1ubuntu5'
120 self.assertEqual(
121 {'epoch': 2, 'major': 8, 'minor': 1, 'micro': 2269,
122 'extra': None, 'raw': mver, 'upstream': '8.1.2269',
123 'semantic_version': 82369},
124 distro.parse_dpkg_version(mver))
125
126 def test_package_with_dot_in_extra(self):
127 """linux-image-generic has multiple dots in extra"""
128 mver = '5.4.0.37.40'
129 self.assertEqual(
130 {'epoch': 0, 'major': 5, 'minor': 4, 'micro': 0,
131 'extra': '37.40', 'raw': mver, 'upstream': '5.4.0.37.40',
132 'semantic_version': 50400},
133 distro.parse_dpkg_version(mver))
134
115135
116class TestDistros(CiTestCase):136class TestDistros(CiTestCase):
117137
@@ -429,6 +449,7 @@ class TestSystemUpgrade(CiTestCase):
429 auto_remove = apt_base + ['autoremove']449 auto_remove = apt_base + ['autoremove']
430 expected_calls = [450 expected_calls = [
431 mock.call(apt_cmd, env=env, target=paths.target_path(target)),451 mock.call(apt_cmd, env=env, target=paths.target_path(target)),
452 mock.call(['apt-get', 'clean'], target=paths.target_path(target)),
432 mock.call(auto_remove, env=env, target=paths.target_path(target)),453 mock.call(auto_remove, env=env, target=paths.target_path(target)),
433 ]454 ]
434 which_calls = [mock.call('eatmydata', target=target)]455 which_calls = [mock.call('eatmydata', target=target)]
diff --git a/tests/unittests/test_feature.py b/tests/unittests/test_feature.py
index 7c55882..8690ad8 100644
--- a/tests/unittests/test_feature.py
+++ b/tests/unittests/test_feature.py
@@ -24,4 +24,10 @@ class TestExportsFeatures(CiTestCase):
24 def test_has_centos_curthook_support(self):24 def test_has_centos_curthook_support(self):
25 self.assertIn('CENTOS_CURTHOOK_SUPPORT', curtin.FEATURES)25 self.assertIn('CENTOS_CURTHOOK_SUPPORT', curtin.FEATURES)
2626
27 def test_has_btrfs_swapfile_support(self):
28 self.assertIn('BTRFS_SWAPFILE', curtin.FEATURES)
29
30 def test_has_uefi_reorder_fallback_support(self):
31 self.assertIn('UEFI_REORDER_FALLBACK_SUPPORT', curtin.FEATURES)
32
27# vi: ts=4 expandtab syntax=python33# vi: ts=4 expandtab syntax=python
diff --git a/tests/vmtests/__init__.py b/tests/vmtests/__init__.py
index adfcd24..0b19d8f 100644
--- a/tests/vmtests/__init__.py
+++ b/tests/vmtests/__init__.py
@@ -633,6 +633,7 @@ class VMBaseClass(TestCase):
633633
634 # these get set from base_vm_classes634 # these get set from base_vm_classes
635 release = None635 release = None
636 supported_releases = []
636 arch = None637 arch = None
637 target_arch = None638 target_arch = None
638 kflavor = None639 kflavor = None
@@ -855,6 +856,13 @@ class VMBaseClass(TestCase):
855 return {'kernel': {'fallback-package': package}}856 return {'kernel': {'fallback-package': package}}
856857
857 @classmethod858 @classmethod
859 def is_unsupported_release(cls):
860 # allow unsupported releases opt-in to avoid the skiptest
861 if cls.release in cls.supported_releases:
862 return False
863 return is_unsupported_ubuntu(cls.release)
864
865 @classmethod
858 def skip_by_date(cls, *args, **kwargs):866 def skip_by_date(cls, *args, **kwargs):
859 """skip_by_date wrapper. this way other modules do not have867 """skip_by_date wrapper. this way other modules do not have
860 to add an import of skip_by_date to start skipping."""868 to add an import of skip_by_date to start skipping."""
@@ -883,7 +891,7 @@ class VMBaseClass(TestCase):
883 "Class %s does not have required attrs set: %s" %891 "Class %s does not have required attrs set: %s" %
884 (cls.__name__, missing))892 (cls.__name__, missing))
885893
886 if is_unsupported_ubuntu(cls.release):894 if cls.is_unsupported_release():
887 raise SkipTest('"%s" is unsupported release.' % cls.release)895 raise SkipTest('"%s" is unsupported release.' % cls.release)
888896
889 # check if we should skip due to host arch897 # check if we should skip due to host arch
@@ -1668,8 +1676,8 @@ class VMBaseClass(TestCase):
1668 if spec in line:1676 if spec in line:
1669 fstab_entry = line1677 fstab_entry = line
1670 self.assertIsNotNone(fstab_entry)1678 self.assertIsNotNone(fstab_entry)
1671 self.assertEqual(mp, fstab_entry.split(' ')[1])1679 self.assertEqual(mp, fstab_entry.split()[1])
1672 self.assertEqual(fsopts, fstab_entry.split(' ')[3])1680 self.assertEqual(fsopts, fstab_entry.split()[3])
1673 found.append((spec, mp, fsopts))1681 found.append((spec, mp, fsopts))
16741682
1675 self.assertEqual(sorted(expected), sorted(found))1683 self.assertEqual(sorted(expected), sorted(found))
@@ -1755,12 +1763,28 @@ class VMBaseClass(TestCase):
1755 for line in ls_byid.split('\n')1763 for line in ls_byid.split('\n')
1756 if ("virtio-" + serial) in line.split() or1764 if ("virtio-" + serial) in line.split() or
1757 ("scsi-" + serial) in line.split() or1765 ("scsi-" + serial) in line.split() or
1758 ("wwn-" + serial) in line.split()]1766 ("wwn-" + serial) in line.split() or
1767 (serial) in line.split()]
1768 print("Looking for serial %s in 'ls_al_byid' content\n%s" % (serial,
1769 ls_byid))
1759 self.assertEqual(len(kname), 1)1770 self.assertEqual(len(kname), 1)
1760 kname = kname.pop()1771 kname = kname.pop()
1761 self.assertIsNotNone(kname)1772 self.assertIsNotNone(kname)
1762 return kname1773 return kname
17631774
1775 def _mdname_to_kname(self, mdname):
1776 # extract kname from /dev/md/ on /dev/<kname>
1777 # parsing ls -al output on /dev/md/*:
1778 # lrwxrwxrwx 1 root root 8 May 28 16:26 /dev/md/os-raid1 -> ../md127
1779 ls_dev_md = self.load_collect_file("ls_al_dev_md")
1780 knames = [os.path.basename(line.split()[-1])
1781 for line in ls_dev_md.split('\n')
1782 if mdname in line]
1783 self.assertEqual(len(knames), 1)
1784 kname = knames.pop()
1785 self.assertIsNotNone(kname)
1786 return kname
1787
1764 def _kname_to_bypath(self, kname):1788 def _kname_to_bypath(self, kname):
1765 # extract path from /dev/disk/by-path on /dev/<kname>1789 # extract path from /dev/disk/by-path on /dev/<kname>
1766 # parsing ls -al output on /dev/disk/by-path1790 # parsing ls -al output on /dev/disk/by-path
@@ -1789,6 +1813,36 @@ class VMBaseClass(TestCase):
1789 self.assertEqual(len(uuid), 36)1813 self.assertEqual(len(uuid), 36)
1790 return uuid1814 return uuid
17911815
1816 def _byuuid_to_kname(self, devpath):
1817 # lookup kname via /dev/disk/by-uuid symlink
1818 # parsing ls -al output on /dev/disk/by-uuid:
1819 # lrwxrwxrwx 1 root root 9 Dec 4 20:02
1820 # d591e9e9-825a-4f0a-b280-3bfaf470b83c -> ../../vdg
1821 uuid = os.path.basename(devpath)
1822 self.assertIsNotNone(uuid)
1823 print(uuid)
1824 ls_uuid = self.load_collect_file("ls_al_byuuid")
1825 kname = [line.split()[-1] for line in ls_uuid.split('\n')
1826 if uuid in line.split()]
1827 self.assertEqual(len(kname), 1)
1828 kname = os.path.basename(kname.pop())
1829 return kname
1830
1831 def _bypath_to_kname(self, devpath):
1832 # lookup kname via /dev/disk/by-path symlink
1833 # parsing ls -al output on /dev/disk/by-path:
1834 # lrwxrwxrwx 1 root root 9 Dec 4 20:02
1835 # pci-0000:00:03.0-scsi-0:0:0:0-part3 -> ../../sda3
1836 dpath = os.path.basename(devpath)
1837 self.assertIsNotNone(dpath)
1838 print(dpath)
1839 ls_bypath = self.load_collect_file("ls_al_bypath")
1840 kname = [line.split()[-1] for line in ls_bypath.split('\n')
1841 if dpath in line.split()]
1842 self.assertEqual(len(kname), 1)
1843 kname = os.path.basename(kname.pop())
1844 return kname
1845
1792 def _bcache_to_byuuid(self, kname):1846 def _bcache_to_byuuid(self, kname):
1793 # extract bcache uuid from /dev/bcache/by-uuid on /dev/<kname>1847 # extract bcache uuid from /dev/bcache/by-uuid on /dev/<kname>
1794 # parsing ls -al output on /dev/bcache/by-uuid1848 # parsing ls -al output on /dev/bcache/by-uuid
@@ -1970,25 +2024,44 @@ class VMBaseClass(TestCase):
19702024
1971 @skip_if_flag('expected_failure')2025 @skip_if_flag('expected_failure')
1972 def test_swaps_used(self):2026 def test_swaps_used(self):
1973 if not self.has_storage_config():
1974 raise SkipTest("This test does not use storage config.")
19752027
1976 stgcfg = self.get_storage_config()2028 def find_fstab_swaps():
1977 swap_ids = [d["id"] for d in stgcfg if d.get("fstype") == "swap"]2029 swaps = []
1978 swap_mounts = [d for d in stgcfg if d.get("device") in swap_ids]2030 path = self.collect_path("fstab")
1979 self.assertEqual(len(swap_ids), len(swap_mounts),2031 if not os.path.exists(path):
1980 "number config swap fstypes != number swap mounts")2032 return swaps
2033 for line in util.load_file(path).splitlines():
2034 if line.startswith("#"):
2035 continue
2036 (fs, mp, fstype, opts, dump, passno) = line.split()
2037 if fstype == 'swap':
2038 if fs.startswith('/dev/disk/by-uuid'):
2039 swaps.append('/dev/' + self._byuuid_to_kname(fs))
2040 elif fs.startswith('/dev/disk/by-id'):
2041 kname = self._serial_to_kname(os.path.basename(fs))
2042 swaps.append('/dev/' + kname)
2043 elif fs.startswith('/dev/disk/by-path'):
2044 swaps.append('/dev/' + self._bypath_to_kname(fs))
2045 else:
2046 swaps.append(fs)
2047
2048 return swaps
2049
2050 # we don't yet have a skip_by_date on specific releases
2051 if is_devel_release(self.target_release):
2052 name = "test_swaps_used"
2053 bug = "1894910"
2054 fixby = "2020-10-15"
2055 removeby = "2020-11-01"
2056 raise SkipTest(
2057 "skip_by_date({name}) LP: #{bug} "
2058 "fixby={fixby} removeby={removeby}: ".format(
2059 name=name, bug=bug, fixby=fixby, removeby=removeby))
19812060
1982 swaps_found = []2061 expected_swaps = find_fstab_swaps()
1983 for line in self.load_collect_file("proc-swaps").splitlines():2062 proc_swaps = self.load_collect_file("proc-swaps")
1984 fname, ttype, size, used, priority = line.split()2063 for swap in expected_swaps:
1985 if ttype == "partition":2064 self.assertIn(swap, proc_swaps)
1986 swaps_found.append(
1987 {"fname": fname, ttype: "ttype", "size": int(size),
1988 "used": int(used), "priority": int(priority)})
1989 self.assertEqual(
1990 len(swap_mounts), len(swaps_found),
1991 "Number swaps configured != number used")
19922065
19932066
1994class PsuedoVMBaseClass(VMBaseClass):2067class PsuedoVMBaseClass(VMBaseClass):
@@ -2087,6 +2160,9 @@ class PsuedoVMBaseClass(VMBaseClass):
2087 def test_kernel_img_conf(self):2160 def test_kernel_img_conf(self):
2088 pass2161 pass
20892162
2163 def test_swaps_used(self):
2164 pass
2165
2090 def _maybe_raise(self, exc):2166 def _maybe_raise(self, exc):
2091 if self.allow_test_fails:2167 if self.allow_test_fails:
2092 raise exc2168 raise exc
diff --git a/tests/vmtests/releases.py b/tests/vmtests/releases.py
index 3dcb415..11abcb8 100644
--- a/tests/vmtests/releases.py
+++ b/tests/vmtests/releases.py
@@ -185,6 +185,14 @@ class _FocalBase(_UbuntuBase):
185 subarch = "ga-20.04"185 subarch = "ga-20.04"
186186
187187
188class _GroovyBase(_UbuntuBase):
189 release = "groovy"
190 target_release = "groovy"
191 mem = "2048"
192 if _UbuntuBase.arch == "arm64":
193 subarch = "ga-20.04"
194
195
188class _Releases(object):196class _Releases(object):
189 trusty = _TrustyBase197 trusty = _TrustyBase
190 precise = _PreciseBase198 precise = _PreciseBase
@@ -203,6 +211,7 @@ class _Releases(object):
203 disco = _DiscoBase211 disco = _DiscoBase
204 eoan = _EoanBase212 eoan = _EoanBase
205 focal = _FocalBase213 focal = _FocalBase
214 groovy = _GroovyBase
206215
207216
208class _CentosReleases(object):217class _CentosReleases(object):
diff --git a/tests/vmtests/test_apt_config_cmd.py b/tests/vmtests/test_apt_config_cmd.py
index 4e43882..874efad 100644
--- a/tests/vmtests/test_apt_config_cmd.py
+++ b/tests/vmtests/test_apt_config_cmd.py
@@ -41,7 +41,7 @@ class TestAptConfigCMD(VMBaseClass):
41 self.check_file_regex("curtin-dev-ubuntu-test-archive-%s.list" %41 self.check_file_regex("curtin-dev-ubuntu-test-archive-%s.list" %
42 self.release,42 self.release,
43 (r"http://ppa.launchpad.net/"43 (r"http://ppa.launchpad.net/"
44 r"curtin-dev/test-archive/ubuntu"44 r"curtin-dev/test-archive/ubuntu(/*)"
45 r" %s main" % self.release))45 r" %s main" % self.release))
4646
47 def test_cmd_preserve_source(self):47 def test_cmd_preserve_source(self):
@@ -68,4 +68,8 @@ class FocalTestAptConfigCMDCMD(relbase.focal, TestAptConfigCMD):
68 __test__ = True68 __test__ = True
6969
7070
71class GroovyTestAptConfigCMDCMD(relbase.groovy, TestAptConfigCMD):
72 __test__ = True
73
74
71# vi: ts=4 expandtab syntax=python75# vi: ts=4 expandtab syntax=python
diff --git a/tests/vmtests/test_basic.py b/tests/vmtests/test_basic.py
index 88b9897..5723bc6 100644
--- a/tests/vmtests/test_basic.py
+++ b/tests/vmtests/test_basic.py
@@ -143,11 +143,16 @@ class TestBasicAbs(VMBaseClass):
143 def get_fstab_expected(self):143 def get_fstab_expected(self):
144 rootdev = self._serial_to_kname('disk-a')144 rootdev = self._serial_to_kname('disk-a')
145 btrfsdev = self._serial_to_kname('disk-c')145 btrfsdev = self._serial_to_kname('disk-c')
146 return [146 expected = [
147 (self._kname_to_byuuid(rootdev + '1'), '/', 'defaults'),147 (self._kname_to_byuuid(rootdev + '1'), '/', 'defaults'),
148 (self._kname_to_byuuid(rootdev + '2'), '/home', 'defaults'),148 (self._kname_to_byuuid(rootdev + '2'), '/home', 'defaults'),
149 (self._kname_to_byuuid(btrfsdev), '/btrfs', 'defaults,noatime')149 (self._kname_to_byuuid(btrfsdev), '/btrfs', 'defaults,noatime'),
150 (self._kname_to_byuuid(rootdev + '3'), 'none', 'sw'),
150 ]151 ]
152 if self.target_release in ['focal']:
153 expected.append(('/btrfs/btrfsswap.img', 'none', 'sw'))
154
155 return expected
151156
152 def test_whole_disk_uuid(self):157 def test_whole_disk_uuid(self):
153 self._test_whole_disk_uuid(158 self._test_whole_disk_uuid(
@@ -250,11 +255,11 @@ class BionicTestBasic(relbase.bionic, TestBasicAbs):
250 __test__ = True255 __test__ = True
251256
252257
253class EoanTestBasic(relbase.eoan, TestBasicAbs):258class FocalTestBasic(relbase.focal, TestBasicAbs):
254 __test__ = True259 __test__ = True
255260
256261
257class FocalTestBasic(relbase.focal, TestBasicAbs):262class GroovyTestBasic(relbase.groovy, TestBasicAbs):
258 __test__ = True263 __test__ = True
259264
260265
@@ -307,14 +312,23 @@ class TestBasicScsiAbs(TestBasicAbs):
307 home_kname = (312 home_kname = (
308 self._serial_to_kname('0x39cc071e72c64cc4-part2'))313 self._serial_to_kname('0x39cc071e72c64cc4-part2'))
309 btrfs_kname = self._serial_to_kname('0x22dc58dc023c7008')314 btrfs_kname = self._serial_to_kname('0x22dc58dc023c7008')
315 swap_kname = (
316 self._serial_to_kname('0x39cc071e72c64cc4-part3'))
310317
311 map_func = self._kname_to_byuuid318 map_func = self._kname_to_byuuid
312 if self.arch == 's390x':319 if self.arch == 's390x':
313 map_func = self._kname_to_bypath320 map_func = self._kname_to_bypath
314321
315 return [(map_func(root_kname), '/', 'defaults'),322 expected = [
316 (map_func(home_kname), '/home', 'defaults'),323 (map_func(root_kname), '/', 'defaults'),
317 (map_func(btrfs_kname), '/btrfs', 'defaults,noatime')]324 (map_func(home_kname), '/home', 'defaults'),
325 (map_func(btrfs_kname), '/btrfs', 'defaults,noatime'),
326 (map_func(swap_kname), 'none', 'sw')]
327
328 if self.target_release in ['focal']:
329 expected.append(('/btrfs/btrfsswap.img', 'none', 'sw'))
330
331 return expected
318332
319 @skip_if_arch('s390x')333 @skip_if_arch('s390x')
320 def test_whole_disk_uuid(self):334 def test_whole_disk_uuid(self):
@@ -361,11 +375,11 @@ class BionicTestScsiBasic(relbase.bionic, TestBasicScsiAbs):
361 __test__ = True375 __test__ = True
362376
363377
364class EoanTestScsiBasic(relbase.eoan, TestBasicScsiAbs):378class FocalTestScsiBasic(relbase.focal, TestBasicScsiAbs):
365 __test__ = True379 __test__ = True
366380
367381
368class FocalTestScsiBasic(relbase.focal, TestBasicScsiAbs):382class GroovyTestScsiBasic(relbase.groovy, TestBasicScsiAbs):
369 __test__ = True383 __test__ = True
370384
371385
diff --git a/tests/vmtests/test_basic_dasd.py b/tests/vmtests/test_basic_dasd.py
index 391bafc..d61e1b9 100644
--- a/tests/vmtests/test_basic_dasd.py
+++ b/tests/vmtests/test_basic_dasd.py
@@ -52,11 +52,11 @@ class BionicTestBasicDasd(relbase.bionic, TestBasicDasd):
52 __test__ = True52 __test__ = True
5353
5454
55class EoanTestBasicDasd(relbase.eoan, TestBasicDasd):55class FocalTestBasicDasd(relbase.focal, TestBasicDasd):
56 __test__ = True56 __test__ = True
5757
5858
59class FocalTestBasicDasd(relbase.focal, TestBasicDasd):59class GroovyTestBasicDasd(relbase.groovy, TestBasicDasd):
60 __test__ = True60 __test__ = True
6161
6262
diff --git a/tests/vmtests/test_bcache_basic.py b/tests/vmtests/test_bcache_basic.py
index 54bac81..053225f 100644
--- a/tests/vmtests/test_bcache_basic.py
+++ b/tests/vmtests/test_bcache_basic.py
@@ -64,11 +64,11 @@ class BionicBcacheBasic(relbase.bionic, TestBcacheBasic):
64 __test__ = True64 __test__ = True
6565
6666
67class EoanBcacheBasic(relbase.eoan, TestBcacheBasic):67class FocalBcacheBasic(relbase.focal, TestBcacheBasic):
68 __test__ = True68 __test__ = True
6969
7070
71class FocalBcacheBasic(relbase.focal, TestBcacheBasic):71class GroovyBcacheBasic(relbase.groovy, TestBcacheBasic):
72 __test__ = True72 __test__ = True
7373
7474
diff --git a/tests/vmtests/test_bcache_bug1718699.py b/tests/vmtests/test_bcache_bug1718699.py
index 8c29046..ebb99ab 100644
--- a/tests/vmtests/test_bcache_bug1718699.py
+++ b/tests/vmtests/test_bcache_bug1718699.py
@@ -19,11 +19,11 @@ class BionicTestBcacheBug1718699(relbase.bionic, TestBcacheBug1718699):
19 __test__ = True19 __test__ = True
2020
2121
22class EoanTestBcacheBug1718699(relbase.eoan, TestBcacheBug1718699):22class FocalTestBcacheBug1718699(relbase.focal, TestBcacheBug1718699):
23 __test__ = True23 __test__ = True
2424
2525
26class FocalTestBcacheBug1718699(relbase.focal, TestBcacheBug1718699):26class GroovyTestBcacheBug1718699(relbase.groovy, TestBcacheBug1718699):
27 __test__ = True27 __test__ = True
2828
2929
diff --git a/tests/vmtests/test_bcache_ceph.py b/tests/vmtests/test_bcache_ceph.py
index d24994a..bff4dd4 100644
--- a/tests/vmtests/test_bcache_ceph.py
+++ b/tests/vmtests/test_bcache_ceph.py
@@ -75,11 +75,11 @@ class BionicTestBcacheCeph(relbase.bionic, TestBcacheCeph):
75 __test__ = True75 __test__ = True
7676
7777
78class EoanTestBcacheCeph(relbase.eoan, TestBcacheCeph):78class FocalTestBcacheCeph(relbase.focal, TestBcacheCeph):
79 __test__ = True79 __test__ = True
8080
8181
82class FocalTestBcacheCeph(relbase.focal, TestBcacheCeph):82class GroovyTestBcacheCeph(relbase.groovy, TestBcacheCeph):
83 __test__ = True83 __test__ = True
8484
8585
@@ -109,4 +109,8 @@ class FocalTestBcacheCephLvm(relbase.focal, TestBcacheCephLvm):
109 __test__ = True109 __test__ = True
110110
111111
112class GroovyTestBcacheCephLvm(relbase.groovy, TestBcacheCephLvm):
113 __test__ = True
114
115
112# vi: ts=4 expandtab syntax=python116# vi: ts=4 expandtab syntax=python
diff --git a/tests/vmtests/test_bcache_partitions.py b/tests/vmtests/test_bcache_partitions.py
index f41e645..1ffea12 100644
--- a/tests/vmtests/test_bcache_partitions.py
+++ b/tests/vmtests/test_bcache_partitions.py
@@ -25,11 +25,11 @@ class BionicTestBcachePartitions(relbase.bionic, TestBcachePartitions):
25 __test__ = True25 __test__ = True
2626
2727
28class EoanTestBcachePartitions(relbase.eoan, TestBcachePartitions):28class FocalTestBcachePartitions(relbase.focal, TestBcachePartitions):
29 __test__ = True29 __test__ = True
3030
3131
32class FocalTestBcachePartitions(relbase.focal, TestBcachePartitions):32class GroovyTestBcachePartitions(relbase.groovy, TestBcachePartitions):
33 __test__ = True33 __test__ = True
3434
3535
diff --git a/tests/vmtests/test_fs_battery.py b/tests/vmtests/test_fs_battery.py
index bd44905..7177fea 100644
--- a/tests/vmtests/test_fs_battery.py
+++ b/tests/vmtests/test_fs_battery.py
@@ -239,11 +239,11 @@ class BionicTestFsBattery(relbase.bionic, TestFsBattery):
239 __test__ = True239 __test__ = True
240240
241241
242class EoanTestFsBattery(relbase.eoan, TestFsBattery):242class FocalTestFsBattery(relbase.focal, TestFsBattery):
243 __test__ = True243 __test__ = True
244244
245245
246class FocalTestFsBattery(relbase.focal, TestFsBattery):246class GroovyTestFsBattery(relbase.groovy, TestFsBattery):
247 __test__ = True247 __test__ = True
248248
249249
diff --git a/tests/vmtests/test_iscsi.py b/tests/vmtests/test_iscsi.py
index c99264c..f3406cd 100644
--- a/tests/vmtests/test_iscsi.py
+++ b/tests/vmtests/test_iscsi.py
@@ -72,11 +72,11 @@ class BionicTestIscsiBasic(relbase.bionic, TestBasicIscsiAbs):
72 __test__ = True72 __test__ = True
7373
7474
75class EoanTestIscsiBasic(relbase.eoan, TestBasicIscsiAbs):75class FocalTestIscsiBasic(relbase.focal, TestBasicIscsiAbs):
76 __test__ = True76 __test__ = True
7777
7878
79class FocalTestIscsiBasic(relbase.focal, TestBasicIscsiAbs):79class GroovyTestIscsiBasic(relbase.groovy, TestBasicIscsiAbs):
80 __test__ = True80 __test__ = True
8181
8282
diff --git a/tests/vmtests/test_journald_reporter.py b/tests/vmtests/test_journald_reporter.py
index d29b4d4..ff003a5 100644
--- a/tests/vmtests/test_journald_reporter.py
+++ b/tests/vmtests/test_journald_reporter.py
@@ -32,11 +32,11 @@ class BionicTestJournaldReporter(relbase.bionic, TestJournaldReporter):
32 __test__ = True32 __test__ = True
3333
3434
35class EoanTestJournaldReporter(relbase.eoan, TestJournaldReporter):35class FocalTestJournaldReporter(relbase.focal, TestJournaldReporter):
36 __test__ = True36 __test__ = True
3737
3838
39class FocalTestJournaldReporter(relbase.focal, TestJournaldReporter):39class GroovyTestJournaldReporter(relbase.groovy, TestJournaldReporter):
40 __test__ = True40 __test__ = True
4141
4242
diff --git a/tests/vmtests/test_lvm.py b/tests/vmtests/test_lvm.py
index a79a705..eb65c32 100644
--- a/tests/vmtests/test_lvm.py
+++ b/tests/vmtests/test_lvm.py
@@ -77,11 +77,11 @@ class BionicTestLvm(relbase.bionic, TestLvmAbs):
77 __test__ = True77 __test__ = True
7878
7979
80class EoanTestLvm(relbase.eoan, TestLvmAbs):80class FocalTestLvm(relbase.focal, TestLvmAbs):
81 __test__ = True81 __test__ = True
8282
8383
84class FocalTestLvm(relbase.focal, TestLvmAbs):84class GroovyTestLvm(relbase.groovy, TestLvmAbs):
85 __test__ = True85 __test__ = True
8686
8787
diff --git a/tests/vmtests/test_lvm_iscsi.py b/tests/vmtests/test_lvm_iscsi.py
index 077b31a..e0b9606 100644
--- a/tests/vmtests/test_lvm_iscsi.py
+++ b/tests/vmtests/test_lvm_iscsi.py
@@ -95,11 +95,11 @@ class BionicTestIscsiLvm(relbase.bionic, TestLvmIscsiAbs):
95 __test__ = True95 __test__ = True
9696
9797
98class EoanTestIscsiLvm(relbase.eoan, TestLvmIscsiAbs):98class FocalTestIscsiLvm(relbase.focal, TestLvmIscsiAbs):
99 __test__ = True99 __test__ = True
100100
101101
102class FocalTestIscsiLvm(relbase.focal, TestLvmIscsiAbs):102class GroovyTestIscsiLvm(relbase.groovy, TestLvmIscsiAbs):
103 __test__ = True103 __test__ = True
104104
105105
diff --git a/tests/vmtests/test_lvm_raid.py b/tests/vmtests/test_lvm_raid.py
index 8d42a1a..5fe7993 100644
--- a/tests/vmtests/test_lvm_raid.py
+++ b/tests/vmtests/test_lvm_raid.py
@@ -47,17 +47,17 @@ class TestLvmOverRaidAbs(TestMdadmAbs, TestLvmAbs):
47 return self._test_pvs(dname_to_vg)47 return self._test_pvs(dname_to_vg)
4848
4949
50class FocalTestLvmOverRaid(relbase.focal, TestLvmOverRaidAbs):50class XenialGATestLvmOverRaid(relbase.xenial_ga, TestLvmOverRaidAbs):
51 __test__ = True51 __test__ = True
5252
5353
54class EoanTestLvmOverRaid(relbase.eoan, TestLvmOverRaidAbs):54class BionicTestLvmOverRaid(relbase.bionic, TestLvmOverRaidAbs):
55 __test__ = True55 __test__ = True
5656
5757
58class BionicTestLvmOverRaid(relbase.bionic, TestLvmOverRaidAbs):58class FocalTestLvmOverRaid(relbase.focal, TestLvmOverRaidAbs):
59 __test__ = True59 __test__ = True
6060
6161
62class XenialGATestLvmOverRaid(relbase.xenial_ga, TestLvmOverRaidAbs):62class GroovyTestLvmOverRaid(relbase.groovy, TestLvmOverRaidAbs):
63 __test__ = True63 __test__ = True
diff --git a/tests/vmtests/test_lvm_root.py b/tests/vmtests/test_lvm_root.py
index 117406e..12b8ea8 100644
--- a/tests/vmtests/test_lvm_root.py
+++ b/tests/vmtests/test_lvm_root.py
@@ -94,6 +94,13 @@ class FocalTestLvmRootExt4(relbase.focal, TestLvmRootAbs):
94 }94 }
9595
9696
97class GroovyTestLvmRootExt4(relbase.groovy, TestLvmRootAbs):
98 __test__ = True
99 conf_replace = {
100 '__ROOTFS_FORMAT__': 'ext4',
101 }
102
103
97class XenialTestLvmRootXfs(relbase.xenial, TestLvmRootAbs):104class XenialTestLvmRootXfs(relbase.xenial, TestLvmRootAbs):
98 __test__ = True105 __test__ = True
99 conf_replace = {106 conf_replace = {
@@ -140,6 +147,14 @@ class FocalTestUefiLvmRootExt4(relbase.focal, TestUefiLvmRootAbs):
140 }147 }
141148
142149
150class GroovyTestUefiLvmRootExt4(relbase.groovy, TestUefiLvmRootAbs):
151 __test__ = True
152 conf_replace = {
153 '__BOOTFS_FORMAT__': 'ext4',
154 '__ROOTFS_FORMAT__': 'ext4',
155 }
156
157
143class XenialTestUefiLvmRootXfs(relbase.xenial, TestUefiLvmRootAbs):158class XenialTestUefiLvmRootXfs(relbase.xenial, TestUefiLvmRootAbs):
144 __test__ = True159 __test__ = True
145 conf_replace = {160 conf_replace = {
@@ -148,13 +163,11 @@ class XenialTestUefiLvmRootXfs(relbase.xenial, TestUefiLvmRootAbs):
148 }163 }
149164
150165
151@VMBaseClass.skip_by_date("1652822", fixby="2020-06-01", install=False)
152class XenialTestUefiLvmRootXfsBootXfs(relbase.xenial, TestUefiLvmRootAbs):166class XenialTestUefiLvmRootXfsBootXfs(relbase.xenial, TestUefiLvmRootAbs):
153 """This tests xfs root and xfs boot with uefi.167 """This tests xfs root and xfs boot with uefi.
154168
155 It is known broken (LP: #1652822) and unlikely to be fixed without pushing,169 It is known broken (LP: #1652822) and unlikely to be fixed."""
156 so we skip-by for a long time."""170 __test__ = False
157 __test__ = True
158 conf_replace = {171 conf_replace = {
159 '__BOOTFS_FORMAT__': 'xfs',172 '__BOOTFS_FORMAT__': 'xfs',
160 '__ROOTFS_FORMAT__': 'xfs',173 '__ROOTFS_FORMAT__': 'xfs',
diff --git a/tests/vmtests/test_mdadm_bcache.py b/tests/vmtests/test_mdadm_bcache.py
index 8e250cc..5425221 100644
--- a/tests/vmtests/test_mdadm_bcache.py
+++ b/tests/vmtests/test_mdadm_bcache.py
@@ -26,6 +26,7 @@ class TestMdadmAbs(VMBaseClass):
26 ls -al /dev/bcache* > lsal_dev_bcache_star26 ls -al /dev/bcache* > lsal_dev_bcache_star
27 ls -al /dev/bcache/by-uuid/ | cat >ls_al_bcache_byuuid27 ls -al /dev/bcache/by-uuid/ | cat >ls_al_bcache_byuuid
28 ls -al /dev/bcache/by-label/ | cat >ls_al_bcache_bylabel28 ls -al /dev/bcache/by-label/ | cat >ls_al_bcache_bylabel
29 ls -al /dev/md/* | cat >ls_al_dev_md
2930
30 exit 031 exit 0
31 """)]32 """)]
@@ -153,18 +154,18 @@ class BionicTestMdadmBcache(relbase.bionic, TestMdadmBcacheAbs):
153 __test__ = True154 __test__ = True
154155
155156
156class EoanTestMdadmBcache(relbase.eoan, TestMdadmBcacheAbs):
157 __test__ = True
158
159
160class FocalTestMdadmBcache(relbase.focal, TestMdadmBcacheAbs):157class FocalTestMdadmBcache(relbase.focal, TestMdadmBcacheAbs):
161 __test__ = True158 __test__ = True
162159
163 @TestMdadmBcacheAbs.skip_by_date("1861941", fixby="2020-07-15")160 @TestMdadmBcacheAbs.skip_by_date("1861941", fixby="2020-09-15")
164 def test_fstab(self):161 def test_fstab(self):
165 return super().test_fstab()162 return super().test_fstab()
166163
167164
165class GroovyTestMdadmBcache(relbase.groovy, TestMdadmBcacheAbs):
166 __test__ = True
167
168
168class TestMirrorbootAbs(TestMdadmAbs):169class TestMirrorbootAbs(TestMdadmAbs):
169 # alternative config for more complex setup170 # alternative config for more complex setup
170 conf_file = "examples/tests/mirrorboot.yaml"171 conf_file = "examples/tests/mirrorboot.yaml"
@@ -202,11 +203,11 @@ class BionicTestMirrorboot(relbase.bionic, TestMirrorbootAbs):
202 __test__ = True203 __test__ = True
203204
204205
205class EoanTestMirrorboot(relbase.eoan, TestMirrorbootAbs):206class FocalTestMirrorboot(relbase.focal, TestMirrorbootAbs):
206 __test__ = True207 __test__ = True
207208
208209
209class FocalTestMirrorboot(relbase.focal, TestMirrorbootAbs):210class GroovyTestMirrorboot(relbase.groovy, TestMirrorbootAbs):
210 __test__ = True211 __test__ = True
211212
212213
@@ -250,13 +251,13 @@ class BionicTestMirrorbootPartitions(relbase.bionic,
250 __test__ = True251 __test__ = True
251252
252253
253class EoanTestMirrorbootPartitions(relbase.eoan,254class FocalTestMirrorbootPartitions(relbase.focal,
254 TestMirrorbootPartitionsAbs):255 TestMirrorbootPartitionsAbs):
255 __test__ = True256 __test__ = True
256257
257258
258class FocalTestMirrorbootPartitions(relbase.focal,259class GroovyTestMirrorbootPartitions(relbase.groovy,
259 TestMirrorbootPartitionsAbs):260 TestMirrorbootPartitionsAbs):
260 __test__ = True261 __test__ = True
261262
262263
@@ -309,6 +310,16 @@ class TestMirrorbootPartitionsUEFIAbs(TestMdadmAbs):
309 self.assertIn(310 self.assertIn(
310 ('grub-pc', 'grub-efi/install_devices', choice), found_selections)311 ('grub-pc', 'grub-efi/install_devices', choice), found_selections)
311312
313 def test_backup_esp_matches_primary(self):
314 if self.target_distro != "ubuntu":
315 raise SkipTest("backup ESP supported only on Ubuntu")
316 if self.target_release in [
317 "trusty", "xenial", "bionic", "cosmic", "disco", "eoan"]:
318 raise SkipTest("backup ESP supported only on >= Focal")
319 primary_esp = self.load_collect_file("diska-part1-efi.out")
320 backup_esp = self.load_collect_file("diskb-part1-efi.out")
321 self.assertEqual(primary_esp, backup_esp)
322
312323
313class Centos70TestMirrorbootPartitionsUEFI(centos_relbase.centos70_xenial,324class Centos70TestMirrorbootPartitionsUEFI(centos_relbase.centos70_xenial,
314 TestMirrorbootPartitionsUEFIAbs):325 TestMirrorbootPartitionsUEFIAbs):
@@ -335,19 +346,14 @@ class BionicTestMirrorbootPartitionsUEFI(relbase.bionic,
335 __test__ = True346 __test__ = True
336347
337348
338class EoanTestMirrorbootPartitionsUEFI(relbase.eoan,
339 TestMirrorbootPartitionsUEFIAbs):
340 __test__ = True
341
342
343class FocalTestMirrorbootPartitionsUEFI(relbase.focal,349class FocalTestMirrorbootPartitionsUEFI(relbase.focal,
344 TestMirrorbootPartitionsUEFIAbs):350 TestMirrorbootPartitionsUEFIAbs):
345 __test__ = True351 __test__ = True
346352
347 def test_backup_esp_matches_primary(self):353
348 primary_esp = self.load_collect_file("diska-part1-efi.out")354class GroovyTestMirrorbootPartitionsUEFI(relbase.groovy,
349 backup_esp = self.load_collect_file("diskb-part1-efi.out")355 TestMirrorbootPartitionsUEFIAbs):
350 self.assertEqual(primary_esp, backup_esp)356 __test__ = True
351357
352358
353class TestRaid5bootAbs(TestMdadmAbs):359class TestRaid5bootAbs(TestMdadmAbs):
@@ -359,11 +365,14 @@ class TestRaid5bootAbs(TestMdadmAbs):
359 ('main_disk', 2),365 ('main_disk', 2),
360 ('second_disk', 1),366 ('second_disk', 1),
361 ('third_disk', 1),367 ('third_disk', 1),
362 ('md0', 0)]368 ('os-raid1', 0)]
363369
364 def get_fstab_expected(self):370 def get_fstab_expected(self):
371 kname = self._mdname_to_kname('os-raid1')
365 return [372 return [
366 (self._kname_to_uuid_devpath('md-uuid', 'md0'), '/', 'defaults'),373 (self._kname_to_uuid_devpath('md-uuid', kname),
374 '/',
375 'defaults'),
367 ]376 ]
368377
369378
@@ -387,11 +396,11 @@ class BionicTestRaid5boot(relbase.bionic, TestRaid5bootAbs):
387 __test__ = True396 __test__ = True
388397
389398
390class EoanTestRaid5boot(relbase.eoan, TestRaid5bootAbs):399class FocalTestRaid5boot(relbase.focal, TestRaid5bootAbs):
391 __test__ = True400 __test__ = True
392401
393402
394class FocalTestRaid5boot(relbase.focal, TestRaid5bootAbs):403class GroovyTestRaid5boot(relbase.groovy, TestRaid5bootAbs):
395 __test__ = True404 __test__ = True
396405
397406
@@ -448,11 +457,11 @@ class BionicTestRaid6boot(relbase.bionic, TestRaid6bootAbs):
448 __test__ = True457 __test__ = True
449458
450459
451class EoanTestRaid6boot(relbase.eoan, TestRaid6bootAbs):460class FocalTestRaid6boot(relbase.focal, TestRaid6bootAbs):
452 __test__ = True461 __test__ = True
453462
454463
455class FocalTestRaid6boot(relbase.focal, TestRaid6bootAbs):464class GroovyTestRaid6boot(relbase.groovy, TestRaid6bootAbs):
456 __test__ = True465 __test__ = True
457466
458467
@@ -495,11 +504,11 @@ class BionicTestRaid10boot(relbase.bionic, TestRaid10bootAbs):
495 __test__ = True504 __test__ = True
496505
497506
498class EoanTestRaid10boot(relbase.eoan, TestRaid10bootAbs):507class FocalTestRaid10boot(relbase.focal, TestRaid10bootAbs):
499 __test__ = True508 __test__ = True
500509
501510
502class FocalTestRaid10boot(relbase.focal, TestRaid10bootAbs):511class GroovyTestRaid10boot(relbase.groovy, TestRaid10bootAbs):
503 __test__ = True512 __test__ = True
504513
505514
@@ -599,11 +608,11 @@ class BionicTestAllindata(relbase.bionic, TestAllindataAbs):
599 __test__ = True608 __test__ = True
600609
601610
602class EoanTestAllindata(relbase.eoan, TestAllindataAbs):611class FocalTestAllindata(relbase.focal, TestAllindataAbs):
603 __test__ = True612 __test__ = True
604613
605614
606class FocalTestAllindata(relbase.focal, TestAllindataAbs):615class GroovyTestAllindata(relbase.groovy, TestAllindataAbs):
607 __test__ = True616 __test__ = True
608617
609618
diff --git a/tests/vmtests/test_mdadm_iscsi.py b/tests/vmtests/test_mdadm_iscsi.py
index 26b1f71..7e6fbf6 100644
--- a/tests/vmtests/test_mdadm_iscsi.py
+++ b/tests/vmtests/test_mdadm_iscsi.py
@@ -50,11 +50,11 @@ class BionicTestIscsiMdadm(relbase.bionic, TestMdadmIscsiAbs):
50 __test__ = True50 __test__ = True
5151
5252
53class EoanTestIscsiMdadm(relbase.eoan, TestMdadmIscsiAbs):53class FocalTestIscsiMdadm(relbase.focal, TestMdadmIscsiAbs):
54 __test__ = True54 __test__ = True
5555
5656
57class FocalTestIscsiMdadm(relbase.focal, TestMdadmIscsiAbs):57class GroovyTestIscsiMdadm(relbase.groovy, TestMdadmIscsiAbs):
58 __test__ = True58 __test__ = True
5959
6060
diff --git a/tests/vmtests/test_multipath.py b/tests/vmtests/test_multipath.py
index 7c7e621..6d9c5df 100644
--- a/tests/vmtests/test_multipath.py
+++ b/tests/vmtests/test_multipath.py
@@ -158,11 +158,11 @@ class BionicTestMultipathBasic(relbase.bionic, TestMultipathBasicAbs):
158 __test__ = True158 __test__ = True
159159
160160
161class EoanTestMultipathBasic(relbase.eoan, TestMultipathBasicAbs):161class FocalTestMultipathBasic(relbase.focal, TestMultipathBasicAbs):
162 __test__ = True162 __test__ = True
163163
164164
165class FocalTestMultipathBasic(relbase.focal, TestMultipathBasicAbs):165class GroovyTestMultipathBasic(relbase.groovy, TestMultipathBasicAbs):
166 __test__ = True166 __test__ = True
167167
168168
diff --git a/tests/vmtests/test_multipath_lvm.py b/tests/vmtests/test_multipath_lvm.py
index 39b8587..c5a1e42 100644
--- a/tests/vmtests/test_multipath_lvm.py
+++ b/tests/vmtests/test_multipath_lvm.py
@@ -56,11 +56,11 @@ class BionicTestMultipathLvm(relbase.bionic, TestMultipathLvmAbs):
56 __test__ = True56 __test__ = True
5757
5858
59class EoanTestMultipathLvm(relbase.eoan, TestMultipathLvmAbs):59class FocalTestMultipathLvm(relbase.focal, TestMultipathLvmAbs):
60 __test__ = True60 __test__ = True
6161
6262
63class FocalTestMultipathLvm(relbase.focal, TestMultipathLvmAbs):63class GroovyTestMultipathLvm(relbase.groovy, TestMultipathLvmAbs):
64 __test__ = True64 __test__ = True
6565
6666
@@ -73,4 +73,9 @@ class FocalTestMultipathLvmPartWipe(relbase.focal,
73 __test__ = True73 __test__ = True
7474
7575
76class GroovyTestMultipathLvmPartWipe(relbase.groovy,
77 TestMultipathLvmPartWipeAbs):
78 __test__ = True
79
80
76# vi: ts=4 expandtab syntax=python81# vi: ts=4 expandtab syntax=python
diff --git a/tests/vmtests/test_network.py b/tests/vmtests/test_network.py
index e6ea6e2..43a7c6b 100644
--- a/tests/vmtests/test_network.py
+++ b/tests/vmtests/test_network.py
@@ -474,11 +474,11 @@ class BionicTestNetworkBasic(relbase.bionic, TestNetworkBasicAbs):
474 __test__ = True474 __test__ = True
475475
476476
477class EoanTestNetworkBasic(relbase.eoan, TestNetworkBasicAbs):477class FocalTestNetworkBasic(relbase.focal, TestNetworkBasicAbs):
478 __test__ = True478 __test__ = True
479479
480480
481class FocalTestNetworkBasic(relbase.focal, TestNetworkBasicAbs):481class GroovyTestNetworkBasic(relbase.groovy, TestNetworkBasicAbs):
482 __test__ = True482 __test__ = True
483483
484484
diff --git a/tests/vmtests/test_network_alias.py b/tests/vmtests/test_network_alias.py
index 68e7de4..bc1fb22 100644
--- a/tests/vmtests/test_network_alias.py
+++ b/tests/vmtests/test_network_alias.py
@@ -52,11 +52,11 @@ class BionicTestNetworkAlias(relbase.bionic, TestNetworkAliasAbs):
52 __test__ = True52 __test__ = True
5353
5454
55class EoanTestNetworkAlias(relbase.eoan, TestNetworkAliasAbs):55class FocalTestNetworkAlias(relbase.focal, TestNetworkAliasAbs):
56 __test__ = True56 __test__ = True
5757
5858
59class FocalTestNetworkAlias(relbase.focal, TestNetworkAliasAbs):59class GroovyTestNetworkAlias(relbase.groovy, TestNetworkAliasAbs):
60 __test__ = True60 __test__ = True
6161
6262
diff --git a/tests/vmtests/test_network_bonding.py b/tests/vmtests/test_network_bonding.py
index 913c7ff..6c6dd6d 100644
--- a/tests/vmtests/test_network_bonding.py
+++ b/tests/vmtests/test_network_bonding.py
@@ -57,11 +57,11 @@ class BionicTestBonding(relbase.bionic, TestNetworkBondingAbs):
57 __test__ = True57 __test__ = True
5858
5959
60class EoanTestBonding(relbase.eoan, TestNetworkBondingAbs):60class FocalTestBonding(relbase.focal, TestNetworkBondingAbs):
61 __test__ = True61 __test__ = True
6262
6363
64class FocalTestBonding(relbase.focal, TestNetworkBondingAbs):64class GroovyTestBonding(relbase.groovy, TestNetworkBondingAbs):
65 __test__ = True65 __test__ = True
6666
6767
diff --git a/tests/vmtests/test_network_bridging.py b/tests/vmtests/test_network_bridging.py
index daaade5..9ecd2f6 100644
--- a/tests/vmtests/test_network_bridging.py
+++ b/tests/vmtests/test_network_bridging.py
@@ -236,11 +236,11 @@ class BionicTestBridging(relbase.bionic, TestBridgeNetworkAbs):
236 __test__ = True236 __test__ = True
237237
238238
239class EoanTestBridging(relbase.eoan, TestBridgeNetworkAbs):239class FocalTestBridging(relbase.focal, TestBridgeNetworkAbs):
240 __test__ = True240 __test__ = True
241241
242242
243class FocalTestBridging(relbase.focal, TestBridgeNetworkAbs):243class GroovyTestBridging(relbase.groovy, TestBridgeNetworkAbs):
244 __test__ = True244 __test__ = True
245245
246246
diff --git a/tests/vmtests/test_network_disabled.py b/tests/vmtests/test_network_disabled.py
index b19ca64..ea8dae2 100644
--- a/tests/vmtests/test_network_disabled.py
+++ b/tests/vmtests/test_network_disabled.py
@@ -57,6 +57,11 @@ class FocalCurtinDisableNetworkRendering(relbase.focal,
57 __test__ = True57 __test__ = True
5858
5959
60class GroovyCurtinDisableNetworkRendering(relbase.groovy,
61 CurtinDisableNetworkRendering):
62 __test__ = True
63
64
60class FocalCurtinDisableCloudInitNetworkingVersion1(65class FocalCurtinDisableCloudInitNetworkingVersion1(
61 relbase.focal,66 relbase.focal,
62 CurtinDisableCloudInitNetworkingVersion167 CurtinDisableCloudInitNetworkingVersion1
@@ -64,9 +69,21 @@ class FocalCurtinDisableCloudInitNetworkingVersion1(
64 __test__ = True69 __test__ = True
6570
6671
72class GroovyCurtinDisableCloudInitNetworkingVersion1(
73 relbase.groovy,
74 CurtinDisableCloudInitNetworkingVersion1
75):
76 __test__ = True
77
78
67class FocalCurtinDisableCloudInitNetworking(relbase.focal,79class FocalCurtinDisableCloudInitNetworking(relbase.focal,
68 CurtinDisableCloudInitNetworking):80 CurtinDisableCloudInitNetworking):
69 __test__ = True81 __test__ = True
7082
7183
84class GroovyCurtinDisableCloudInitNetworking(relbase.groovy,
85 CurtinDisableCloudInitNetworking):
86 __test__ = True
87
88
72# vi: ts=4 expandtab syntax=python89# vi: ts=4 expandtab syntax=python
diff --git a/tests/vmtests/test_network_ipv6.py b/tests/vmtests/test_network_ipv6.py
index 8f0dd54..50a139c 100644
--- a/tests/vmtests/test_network_ipv6.py
+++ b/tests/vmtests/test_network_ipv6.py
@@ -53,9 +53,13 @@ class BionicTestNetworkIPV6(relbase.bionic, TestNetworkIPV6Abs):
53 __test__ = True53 __test__ = True
5454
5555
56class EoanTestNetworkIPV6(relbase.eoan, TestNetworkIPV6Abs):56class GroovyTestNetworkIPV6(relbase.groovy, TestNetworkIPV6Abs):
57 __test__ = True57 __test__ = True
5858
59 @TestNetworkIPV6Abs.skip_by_date("1888726", "2020-10-15")
60 def test_ip_output(self):
61 return super().test_ip_output()
62
5963
60class Centos66TestNetworkIPV6(centos_relbase.centos66_xenial,64class Centos66TestNetworkIPV6(centos_relbase.centos66_xenial,
61 CentosTestNetworkIPV6Abs):65 CentosTestNetworkIPV6Abs):
diff --git a/tests/vmtests/test_network_ipv6_static.py b/tests/vmtests/test_network_ipv6_static.py
index 8a1ba2f..28ff697 100644
--- a/tests/vmtests/test_network_ipv6_static.py
+++ b/tests/vmtests/test_network_ipv6_static.py
@@ -23,11 +23,11 @@ class BionicTestNetworkIPV6Static(relbase.bionic, TestNetworkIPV6StaticAbs):
23 __test__ = True23 __test__ = True
2424
2525
26class EoanTestNetworkIPV6Static(relbase.eoan, TestNetworkIPV6StaticAbs):26class FocalTestNetworkIPV6Static(relbase.focal, TestNetworkIPV6StaticAbs):
27 __test__ = True27 __test__ = True
2828
2929
30class FocalTestNetworkIPV6Static(relbase.focal, TestNetworkIPV6StaticAbs):30class GroovyTestNetworkIPV6Static(relbase.groovy, TestNetworkIPV6StaticAbs):
31 __test__ = True31 __test__ = True
3232
3333
diff --git a/tests/vmtests/test_network_ipv6_vlan.py b/tests/vmtests/test_network_ipv6_vlan.py
index d8e4e16..a0bf267 100644
--- a/tests/vmtests/test_network_ipv6_vlan.py
+++ b/tests/vmtests/test_network_ipv6_vlan.py
@@ -22,13 +22,17 @@ class BionicTestNetworkIPV6Vlan(relbase.bionic, TestNetworkIPV6VlanAbs):
22 __test__ = True22 __test__ = True
2323
2424
25class EoanTestNetworkIPV6Vlan(relbase.eoan, TestNetworkIPV6VlanAbs):25class FocalTestNetworkIPV6Vlan(relbase.focal, TestNetworkIPV6VlanAbs):
26 __test__ = True26 __test__ = True
2727
2828
29class FocalTestNetworkIPV6Vlan(relbase.focal, TestNetworkIPV6VlanAbs):29class GroovyTestNetworkIPV6Vlan(relbase.groovy, TestNetworkIPV6VlanAbs):
30 __test__ = True30 __test__ = True
3131
32 @TestNetworkVlanAbs.skip_by_date("1888726", "2020-10-15")
33 def test_ip_output(self):
34 return super().test_ip_output()
35
3236
33class Centos66TestNetworkIPV6Vlan(centos_relbase.centos66_xenial,37class Centos66TestNetworkIPV6Vlan(centos_relbase.centos66_xenial,
34 CentosTestNetworkIPV6VlanAbs):38 CentosTestNetworkIPV6VlanAbs):
diff --git a/tests/vmtests/test_network_mtu.py b/tests/vmtests/test_network_mtu.py
index bf13459..c70b9e0 100644
--- a/tests/vmtests/test_network_mtu.py
+++ b/tests/vmtests/test_network_mtu.py
@@ -137,6 +137,10 @@ class TestNetworkMtuAbs(TestNetworkIPV6Abs):
137 self._check_iface_subnets('interface7')137 self._check_iface_subnets('interface7')
138138
139139
140class TestNetworkMtuNetworkdAbs(TestNetworkMtuAbs):
141 conf_file = "examples/tests/network_mtu_networkd.yaml"
142
143
140class CentosTestNetworkMtuAbs(TestNetworkMtuAbs):144class CentosTestNetworkMtuAbs(TestNetworkMtuAbs):
141 conf_file = "examples/tests/network_mtu.yaml"145 conf_file = "examples/tests/network_mtu.yaml"
142 extra_collect_scripts = TestNetworkMtuAbs.extra_collect_scripts + [146 extra_collect_scripts = TestNetworkMtuAbs.extra_collect_scripts + [
@@ -181,28 +185,16 @@ class TestNetworkMtu(relbase.xenial, TestNetworkMtuAbs):
181 __test__ = True185 __test__ = True
182186
183187
184class BionicTestNetworkMtu(relbase.bionic, TestNetworkMtuAbs):188class BionicTestNetworkMtu(relbase.bionic, TestNetworkMtuNetworkdAbs):
185 conf_file = "examples/tests/network_mtu_networkd.yaml"
186 __test__ = True189 __test__ = True
187 # Until systemd is released with the fix for LP:#1671951
188 add_repos = "ppa:ddstreet/systemd"
189 upgrade_packages = "cloud-init,systemd"
190190
191191
192class EoanTestNetworkMtu(relbase.eoan, TestNetworkMtuAbs):192class FocalTestNetworkMtu(relbase.focal, TestNetworkMtuNetworkdAbs):
193 conf_file = "examples/tests/network_mtu_networkd.yaml"
194 __test__ = True193 __test__ = True
195 # Until systemd is released with the fix for LP:#1671951
196 add_repos = "ppa:ddstreet/systemd"
197 upgrade_packages = "cloud-init,systemd"
198194
199195
200class FocalTestNetworkMtu(relbase.focal, TestNetworkMtuAbs):196class GroovyTestNetworkMtu(relbase.groovy, TestNetworkMtuNetworkdAbs):
201 conf_file = "examples/tests/network_mtu_networkd.yaml"
202 __test__ = True197 __test__ = True
203 # Until systemd is released with the fix for LP:#1671951
204 add_repos = "ppa:ddstreet/systemd"
205 upgrade_packages = "cloud-init,systemd"
206198
207199
208class Centos66TestNetworkMtu(centos_relbase.centos66_xenial,200class Centos66TestNetworkMtu(centos_relbase.centos66_xenial,
diff --git a/tests/vmtests/test_network_ovs.py b/tests/vmtests/test_network_ovs.py
index 3e23bd0..0cee17e 100644
--- a/tests/vmtests/test_network_ovs.py
+++ b/tests/vmtests/test_network_ovs.py
@@ -34,12 +34,11 @@ class BionicTestNetworkOvs(relbase.bionic, TestNetworkOvsAbs):
34 __test__ = True34 __test__ = True
3535
3636
37class EoanTestNetworkOvs(relbase.eoan, TestNetworkOvsAbs):37class FocalTestNetworkOvs(relbase.focal, TestNetworkOvsAbs):
38 __test__ = True38 __test__ = True
3939
4040
41class FocalTestNetworkOvs(relbase.focal, TestNetworkOvsAbs):41class GroovyTestNetworkOvs(relbase.groovy, TestNetworkOvsAbs):
42 __test__ = True42 __test__ = True
4343
44
45# vi: ts=4 expandtab syntax=python44# vi: ts=4 expandtab syntax=python
diff --git a/tests/vmtests/test_network_static.py b/tests/vmtests/test_network_static.py
index 80ff2cd..e0abd54 100644
--- a/tests/vmtests/test_network_static.py
+++ b/tests/vmtests/test_network_static.py
@@ -28,11 +28,11 @@ class BionicTestNetworkStatic(relbase.bionic, TestNetworkStaticAbs):
28 __test__ = True28 __test__ = True
2929
3030
31class EoanTestNetworkStatic(relbase.eoan, TestNetworkStaticAbs):31class FocalTestNetworkStatic(relbase.focal, TestNetworkStaticAbs):
32 __test__ = True32 __test__ = True
3333
3434
35class FocalTestNetworkStatic(relbase.focal, TestNetworkStaticAbs):35class GroovyTestNetworkStatic(relbase.groovy, TestNetworkStaticAbs):
36 __test__ = True36 __test__ = True
3737
3838
diff --git a/tests/vmtests/test_network_static_routes.py b/tests/vmtests/test_network_static_routes.py
index dfcbffe..f99d9d5 100644
--- a/tests/vmtests/test_network_static_routes.py
+++ b/tests/vmtests/test_network_static_routes.py
@@ -28,13 +28,13 @@ class BionicTestNetworkStaticRoutes(relbase.bionic,
28 __test__ = True28 __test__ = True
2929
3030
31class EoanTestNetworkStaticRoutes(relbase.eoan,31class FocalTestNetworkStaticRoutes(relbase.focal,
32 TestNetworkStaticRoutesAbs):32 TestNetworkStaticRoutesAbs):
33 __test__ = True33 __test__ = True
3434
3535
36class FocalTestNetworkStaticRoutes(relbase.focal,36class GroovyTestNetworkStaticRoutes(relbase.groovy,
37 TestNetworkStaticRoutesAbs):37 TestNetworkStaticRoutesAbs):
38 __test__ = True38 __test__ = True
3939
4040
diff --git a/tests/vmtests/test_network_vlan.py b/tests/vmtests/test_network_vlan.py
index 4a8d776..9f1094b 100644
--- a/tests/vmtests/test_network_vlan.py
+++ b/tests/vmtests/test_network_vlan.py
@@ -76,16 +76,17 @@ class BionicTestNetworkVlan(relbase.bionic, TestNetworkVlanAbs):
76 __test__ = True76 __test__ = True
7777
7878
79class EoanTestNetworkVlan(relbase.eoan, TestNetworkVlanAbs):79class FocalTestNetworkVlan(relbase.focal, TestNetworkVlanAbs):
80 __test__ = True80 __test__ = True
8181
82 def test_ip_output(self):82 def test_ip_output(self):
83 return super().test_ip_output()83 return super().test_ip_output()
8484
8585
86class FocalTestNetworkVlan(relbase.focal, TestNetworkVlanAbs):86class GroovyTestNetworkVlan(relbase.groovy, TestNetworkVlanAbs):
87 __test__ = True87 __test__ = True
8888
89 @TestNetworkVlanAbs.skip_by_date("1888726", "2020-10-15")
89 def test_ip_output(self):90 def test_ip_output(self):
90 return super().test_ip_output()91 return super().test_ip_output()
9192
diff --git a/tests/vmtests/test_nvme.py b/tests/vmtests/test_nvme.py
index c1576fa..39f9f3c 100644
--- a/tests/vmtests/test_nvme.py
+++ b/tests/vmtests/test_nvme.py
@@ -73,7 +73,7 @@ class BionicTestNvme(relbase.bionic, TestNvmeAbs):
73 __test__ = True73 __test__ = True
7474
7575
76class EoanTestNvme(relbase.eoan, TestNvmeAbs):76class GroovyTestNvme(relbase.groovy, TestNvmeAbs):
77 __test__ = True77 __test__ = True
7878
7979
@@ -139,12 +139,11 @@ class BionicTestNvmeBcache(relbase.bionic, TestNvmeBcacheAbs):
139 __test__ = True139 __test__ = True
140140
141141
142class EoanTestNvmeBcache(relbase.eoan, TestNvmeBcacheAbs):142class FocalTestNvmeBcache(relbase.focal, TestNvmeBcacheAbs):
143 __test__ = True143 __test__ = True
144144
145145
146@TestNvmeBcacheAbs.skip_by_date("1861941", fixby="2020-07-15")146class GroovyTestNvmeBcache(relbase.groovy, TestNvmeBcacheAbs):
147class FocalTestNvmeBcache(relbase.focal, TestNvmeBcacheAbs):
148 __test__ = True147 __test__ = True
149148
150149
diff --git a/tests/vmtests/test_panic.py b/tests/vmtests/test_panic.py
index fe4005e..7b1fdbe 100644
--- a/tests/vmtests/test_panic.py
+++ b/tests/vmtests/test_panic.py
@@ -28,4 +28,9 @@ class TestInstallPanic(VMBaseClass):
28class FocalTestInstallPanic(relbase.focal, TestInstallPanic):28class FocalTestInstallPanic(relbase.focal, TestInstallPanic):
29 __test__ = True29 __test__ = True
3030
31
32class GroovyTestInstallPanic(relbase.groovy, TestInstallPanic):
33 __test__ = True
34
35
31# vi: ts=4 expandtab syntax=python36# vi: ts=4 expandtab syntax=python
diff --git a/tests/vmtests/test_pollinate_useragent.py b/tests/vmtests/test_pollinate_useragent.py
index ff21f20..ed14719 100644
--- a/tests/vmtests/test_pollinate_useragent.py
+++ b/tests/vmtests/test_pollinate_useragent.py
@@ -61,11 +61,11 @@ class BionicTestPollinateUserAgent(relbase.bionic, TestPollinateUserAgent):
61 __test__ = True61 __test__ = True
6262
6363
64class EoanTestPollinateUserAgent(relbase.eoan, TestPollinateUserAgent):64class FocalTestPollinateUserAgent(relbase.focal, TestPollinateUserAgent):
65 __test__ = True65 __test__ = True
6666
6767
68class FocalTestPollinateUserAgent(relbase.focal, TestPollinateUserAgent):68class GroovyTestPollinateUserAgent(relbase.groovy, TestPollinateUserAgent):
69 __test__ = True69 __test__ = True
7070
7171
diff --git a/tests/vmtests/test_preserve.py b/tests/vmtests/test_preserve.py
index f02ba6c..998218c 100644
--- a/tests/vmtests/test_preserve.py
+++ b/tests/vmtests/test_preserve.py
@@ -25,11 +25,11 @@ class BionicTestPreserve(relbase.bionic, TestPreserve):
25 __test__ = True25 __test__ = True
2626
2727
28class EoanTestPreserve(relbase.eoan, TestPreserve):28class FocalTestPreserve(relbase.focal, TestPreserve):
29 __test__ = True29 __test__ = True
3030
3131
32class FocalTestPreserve(relbase.focal, TestPreserve):32class GroovyTestPreserve(relbase.groovy, TestPreserve):
33 __test__ = True33 __test__ = True
3434
3535
diff --git a/tests/vmtests/test_preserve_bcache.py b/tests/vmtests/test_preserve_bcache.py
index e2d2a34..bd91c5a 100644
--- a/tests/vmtests/test_preserve_bcache.py
+++ b/tests/vmtests/test_preserve_bcache.py
@@ -56,11 +56,11 @@ class BionicTestPreserveBcache(relbase.bionic, TestPreserveBcache):
56 __test__ = True56 __test__ = True
5757
5858
59class EoanTestPreserveBcache(relbase.eoan, TestPreserveBcache):59class FocalTestPreserveBcache(relbase.focal, TestPreserveBcache):
60 __test__ = True60 __test__ = True
6161
6262
63class FocalTestPreserveBcache(relbase.focal, TestPreserveBcache):63class GroovyTestPreserveBcache(relbase.groovy, TestPreserveBcache):
64 __test__ = True64 __test__ = True
6565
6666
diff --git a/tests/vmtests/test_preserve_lvm.py b/tests/vmtests/test_preserve_lvm.py
index 90f15cb..0ed7ad4 100644
--- a/tests/vmtests/test_preserve_lvm.py
+++ b/tests/vmtests/test_preserve_lvm.py
@@ -69,11 +69,11 @@ class BionicTestLvmPreserve(relbase.bionic, TestLvmPreserveAbs):
69 __test__ = True69 __test__ = True
7070
7171
72class EoanTestLvmPreserve(relbase.eoan, TestLvmPreserveAbs):72class FocalTestLvmPreserve(relbase.focal, TestLvmPreserveAbs):
73 __test__ = True73 __test__ = True
7474
7575
76class FocalTestLvmPreserve(relbase.focal, TestLvmPreserveAbs):76class GroovyTestLvmPreserve(relbase.groovy, TestLvmPreserveAbs):
77 __test__ = True77 __test__ = True
7878
7979
diff --git a/tests/vmtests/test_preserve_partition_wipe_vg.py b/tests/vmtests/test_preserve_partition_wipe_vg.py
index 96346ff..58b1f65 100644
--- a/tests/vmtests/test_preserve_partition_wipe_vg.py
+++ b/tests/vmtests/test_preserve_partition_wipe_vg.py
@@ -25,11 +25,11 @@ class BionicTestPreserveWipeLvm(relbase.bionic, TestPreserveWipeLvm):
25 __test__ = True25 __test__ = True
2626
2727
28class EoanTestPreserveWipeLvm(relbase.eoan, TestPreserveWipeLvm):28class FocalTestPreserveWipeLvm(relbase.focal, TestPreserveWipeLvm):
29 __test__ = True29 __test__ = True
3030
3131
32class FocalTestPreserveWipeLvm(relbase.focal, TestPreserveWipeLvm):32class GroovyTestPreserveWipeLvm(relbase.groovy, TestPreserveWipeLvm):
33 __test__ = True33 __test__ = True
3434
3535
@@ -48,11 +48,12 @@ class BionicTestPreserveWipeLvmSimple(relbase.bionic,
48 __test__ = True48 __test__ = True
4949
5050
51class EoanTestPreserveWipeLvmSimple(relbase.eoan, TestPreserveWipeLvmSimple):51class FocalTestPreserveWipeLvmSimple(relbase.focal, TestPreserveWipeLvmSimple):
52 __test__ = True52 __test__ = True
5353
5454
55class FocalTestPreserveWipeLvmSimple(relbase.focal, TestPreserveWipeLvmSimple):55class GroovyTestPreserveWipeLvmSimple(relbase.groovy,
56 TestPreserveWipeLvmSimple):
56 __test__ = True57 __test__ = True
5758
5859
diff --git a/tests/vmtests/test_preserve_raid.py b/tests/vmtests/test_preserve_raid.py
index cf3a6bb..15f2f50 100644
--- a/tests/vmtests/test_preserve_raid.py
+++ b/tests/vmtests/test_preserve_raid.py
@@ -25,11 +25,11 @@ class BionicTestPreserveRAID(relbase.bionic, TestPreserveRAID):
25 __test__ = True25 __test__ = True
2626
2727
28class EoanTestPreserveRAID(relbase.eoan, TestPreserveRAID):28class FocalTestPreserveRAID(relbase.focal, TestPreserveRAID):
29 __test__ = True29 __test__ = True
3030
3131
32class FocalTestPreserveRAID(relbase.focal, TestPreserveRAID):32class GroovyTestPreserveRAID(relbase.groovy, TestPreserveRAID):
33 __test__ = True33 __test__ = True
3434
3535
diff --git a/tests/vmtests/test_raid5_bcache.py b/tests/vmtests/test_raid5_bcache.py
index 0f0b87b..3fdb217 100644
--- a/tests/vmtests/test_raid5_bcache.py
+++ b/tests/vmtests/test_raid5_bcache.py
@@ -88,16 +88,16 @@ class BionicTestRaid5Bcache(relbase.bionic, TestMdadmBcacheAbs):
88 __test__ = True88 __test__ = True
8989
9090
91class EoanTestRaid5Bcache(relbase.eoan, TestMdadmBcacheAbs):
92 __test__ = True
93
94
95class FocalTestRaid5Bcache(relbase.focal, TestMdadmBcacheAbs):91class FocalTestRaid5Bcache(relbase.focal, TestMdadmBcacheAbs):
96 __test__ = True92 __test__ = True
9793
98 @TestMdadmBcacheAbs.skip_by_date("1861941", fixby="2020-07-15")94 @TestMdadmBcacheAbs.skip_by_date("1861941", fixby="2020-09-15")
99 def test_fstab(self):95 def test_fstab(self):
100 return super().test_fstab()96 return super().test_fstab()
10197
10298
99class GroovyTestRaid5Bcache(relbase.groovy, TestMdadmBcacheAbs):
100 __test__ = True
101
102
103# vi: ts=4 expandtab syntax=python103# vi: ts=4 expandtab syntax=python
diff --git a/tests/vmtests/test_reuse_lvm_member.py b/tests/vmtests/test_reuse_lvm_member.py
index 749ea24..87afcfb 100644
--- a/tests/vmtests/test_reuse_lvm_member.py
+++ b/tests/vmtests/test_reuse_lvm_member.py
@@ -21,13 +21,13 @@ class BionicTestReuseLVMMemberPartition(relbase.bionic,
21 __test__ = True21 __test__ = True
2222
2323
24class EoanTestReuseLVMMemberPartition(relbase.eoan,24class FocalTestReuseLVMMemberPartition(relbase.focal,
25 TestReuseLVMMemberPartition):25 TestReuseLVMMemberPartition):
26 __test__ = True26 __test__ = True
2727
2828
29class FocalTestReuseLVMMemberPartition(relbase.focal,29class GroovyTestReuseLVMMemberPartition(relbase.groovy,
30 TestReuseLVMMemberPartition):30 TestReuseLVMMemberPartition):
31 __test__ = True31 __test__ = True
3232
3333
diff --git a/tests/vmtests/test_reuse_msdos_partitions.py b/tests/vmtests/test_reuse_msdos_partitions.py
index f8e20d9..9f18d3c 100644
--- a/tests/vmtests/test_reuse_msdos_partitions.py
+++ b/tests/vmtests/test_reuse_msdos_partitions.py
@@ -18,13 +18,13 @@ class BionicTestReuseMSDOSPartitions(relbase.bionic,
18 __test__ = True18 __test__ = True
1919
2020
21class EoanTestReuseMSDOSPartitions(relbase.eoan,21class FocalTestReuseMSDOSPartitions(relbase.focal,
22 TestReuseMSDOSPartitions):22 TestReuseMSDOSPartitions):
23 __test__ = True23 __test__ = True
2424
2525
26class FocalTestReuseMSDOSPartitions(relbase.focal,26class GroovyTestReuseMSDOSPartitions(relbase.groovy,
27 TestReuseMSDOSPartitions):27 TestReuseMSDOSPartitions):
28 __test__ = True28 __test__ = True
2929
3030
diff --git a/tests/vmtests/test_reuse_raid_member.py b/tests/vmtests/test_reuse_raid_member.py
index 425105f..7be98f3 100644
--- a/tests/vmtests/test_reuse_raid_member.py
+++ b/tests/vmtests/test_reuse_raid_member.py
@@ -28,11 +28,11 @@ class BionicTestReuseRAIDMember(relbase.bionic, TestReuseRAIDMember):
28 __test__ = True28 __test__ = True
2929
3030
31class EoanTestReuseRAIDMember(relbase.eoan, TestReuseRAIDMember):31class FocalTestReuseRAIDMember(relbase.focal, TestReuseRAIDMember):
32 __test__ = True32 __test__ = True
3333
3434
35class FocalTestReuseRAIDMember(relbase.focal, TestReuseRAIDMember):35class GroovyTestReuseRAIDMember(relbase.groovy, TestReuseRAIDMember):
36 __test__ = True36 __test__ = True
3737
3838
@@ -41,13 +41,13 @@ class BionicTestReuseRAIDMemberPartition(relbase.bionic,
41 __test__ = True41 __test__ = True
4242
4343
44class EoanTestReuseRAIDMemberPartition(relbase.eoan,44class FocalTestReuseRAIDMemberPartition(relbase.focal,
45 TestReuseRAIDMemberPartition):45 TestReuseRAIDMemberPartition):
46 __test__ = True46 __test__ = True
4747
4848
49class FocalTestReuseRAIDMemberPartition(relbase.focal,49class GroovyTestReuseRAIDMemberPartition(relbase.groovy,
50 TestReuseRAIDMemberPartition):50 TestReuseRAIDMemberPartition):
51 __test__ = True51 __test__ = True
5252
5353
diff --git a/tests/vmtests/test_reuse_uefi_esp.py b/tests/vmtests/test_reuse_uefi_esp.py
index 31c5e7d..46e7ac7 100644
--- a/tests/vmtests/test_reuse_uefi_esp.py
+++ b/tests/vmtests/test_reuse_uefi_esp.py
@@ -3,20 +3,22 @@
3from .test_uefi_basic import TestBasicAbs3from .test_uefi_basic import TestBasicAbs
4from .releases import base_vm_classes as relbase4from .releases import base_vm_classes as relbase
5from .releases import centos_base_vm_classes as cent_rbase5from .releases import centos_base_vm_classes as cent_rbase
6from curtin.commands.curthooks import uefi_find_duplicate_entries
7from curtin import util
68
79
8class TestUefiReuseEspAbs(TestBasicAbs):10class TestUefiReuseEspAbs(TestBasicAbs):
9 conf_file = "examples/tests/uefi_reuse_esp.yaml"11 conf_file = "examples/tests/uefi_reuse_esp.yaml"
1012
11 def test_efiboot_menu_has_one_distro_entry(self):13 def test_efiboot_menu_has_one_distro_entry(self):
12 efiboot_mgr_content = self.load_collect_file("efibootmgr.out")14 efi_output = util.parse_efibootmgr(
13 distro_lines = [line for line in efiboot_mgr_content.splitlines()15 self.load_collect_file("efibootmgr.out"))
14 if self.target_distro in line]16 duplicates = uefi_find_duplicate_entries(
15 print(distro_lines)17 grubcfg=None, target=None, efi_output=efi_output)
16 self.assertEqual(1, len(distro_lines))18 print(duplicates)
19 self.assertEqual(0, len(duplicates))
1720
1821
19@TestUefiReuseEspAbs.skip_by_date("1881030", fixby="2020-07-15")
20class Cent70TestUefiReuseEsp(cent_rbase.centos70_bionic, TestUefiReuseEspAbs):22class Cent70TestUefiReuseEsp(cent_rbase.centos70_bionic, TestUefiReuseEspAbs):
21 __test__ = True23 __test__ = True
2224
@@ -33,14 +35,14 @@ class BionicTestUefiReuseEsp(relbase.bionic, TestUefiReuseEspAbs):
33 return super().test_efiboot_menu_has_one_distro_entry()35 return super().test_efiboot_menu_has_one_distro_entry()
3436
3537
36class EoanTestUefiReuseEsp(relbase.eoan, TestUefiReuseEspAbs):38class FocalTestUefiReuseEsp(relbase.focal, TestUefiReuseEspAbs):
37 __test__ = True39 __test__ = True
3840
39 def test_efiboot_menu_has_one_distro_entry(self):41 def test_efiboot_menu_has_one_distro_entry(self):
40 return super().test_efiboot_menu_has_one_distro_entry()42 return super().test_efiboot_menu_has_one_distro_entry()
4143
4244
43class FocalTestUefiReuseEsp(relbase.focal, TestUefiReuseEspAbs):45class GroovyTestUefiReuseEsp(relbase.groovy, TestUefiReuseEspAbs):
44 __test__ = True46 __test__ = True
4547
46 def test_efiboot_menu_has_one_distro_entry(self):48 def test_efiboot_menu_has_one_distro_entry(self):
diff --git a/tests/vmtests/test_simple.py b/tests/vmtests/test_simple.py
index b34a6fc..9e71047 100644
--- a/tests/vmtests/test_simple.py
+++ b/tests/vmtests/test_simple.py
@@ -49,14 +49,14 @@ class BionicTestSimple(relbase.bionic, TestSimple):
49 self.output_files_exist(["netplan.yaml"])49 self.output_files_exist(["netplan.yaml"])
5050
5151
52class EoanTestSimple(relbase.eoan, TestSimple):52class FocalTestSimple(relbase.focal, TestSimple):
53 __test__ = True53 __test__ = True
5454
55 def test_output_files_exist(self):55 def test_output_files_exist(self):
56 self.output_files_exist(["netplan.yaml"])56 self.output_files_exist(["netplan.yaml"])
5757
5858
59class FocalTestSimple(relbase.focal, TestSimple):59class GroovyTestSimple(relbase.groovy, TestSimple):
60 __test__ = True60 __test__ = True
6161
62 def test_output_files_exist(self):62 def test_output_files_exist(self):
@@ -105,14 +105,14 @@ class BionicTestSimpleStorage(relbase.bionic, TestSimpleStorage):
105 self.output_files_exist(["netplan.yaml"])105 self.output_files_exist(["netplan.yaml"])
106106
107107
108class EoanTestSimpleStorage(relbase.eoan, TestSimpleStorage):108class FocalTestSimpleStorage(relbase.focal, TestSimpleStorage):
109 __test__ = True109 __test__ = True
110110
111 def test_output_files_exist(self):111 def test_output_files_exist(self):
112 self.output_files_exist(["netplan.yaml"])112 self.output_files_exist(["netplan.yaml"])
113113
114114
115class FocalTestSimpleStorage(relbase.focal, TestSimpleStorage):115class GroovyTestSimpleStorage(relbase.groovy, TestSimpleStorage):
116 __test__ = True116 __test__ = True
117117
118 def test_output_files_exist(self):118 def test_output_files_exist(self):
@@ -145,4 +145,11 @@ class FocalTestGrubNoDefaults(relbase.focal, TestGrubNoDefaults):
145 self.output_files_exist(["netplan.yaml"])145 self.output_files_exist(["netplan.yaml"])
146146
147147
148class GroovyTestGrubNoDefaults(relbase.groovy, TestGrubNoDefaults):
149 __test__ = True
150
151 def test_output_files_exist(self):
152 self.output_files_exist(["netplan.yaml"])
153
154
148# vi: ts=4 expandtab syntax=python155# vi: ts=4 expandtab syntax=python
diff --git a/tests/vmtests/test_uefi_basic.py b/tests/vmtests/test_uefi_basic.py
index 90940dd..932c1c8 100644
--- a/tests/vmtests/test_uefi_basic.py
+++ b/tests/vmtests/test_uefi_basic.py
@@ -89,6 +89,11 @@ class PreciseHWETUefiTestBasic(relbase.precise_hwe_t, PreciseUefiTestBasic):
89 __test__ = False89 __test__ = False
9090
9191
92class TrustyHWEXUefiTestBasic(relbase.trusty_hwe_x, TestBasicAbs):
93 supported_releases = ['trusty'] # avoid unsupported release skiptest
94 __test__ = False
95
96
92class XenialGAUefiTestBasic(relbase.xenial_ga, TestBasicAbs):97class XenialGAUefiTestBasic(relbase.xenial_ga, TestBasicAbs):
93 __test__ = True98 __test__ = True
9499
@@ -105,11 +110,11 @@ class BionicUefiTestBasic(relbase.bionic, TestBasicAbs):
105 __test__ = True110 __test__ = True
106111
107112
108class EoanUefiTestBasic(relbase.eoan, TestBasicAbs):113class FocalUefiTestBasic(relbase.focal, TestBasicAbs):
109 __test__ = True114 __test__ = True
110115
111116
112class FocalUefiTestBasic(relbase.focal, TestBasicAbs):117class GroovyUefiTestBasic(relbase.groovy, TestBasicAbs):
113 __test__ = True118 __test__ = True
114119
115120
@@ -128,12 +133,12 @@ class BionicUefiTestBasic4k(relbase.bionic, TestBasicAbs):
128 disk_block_size = 4096133 disk_block_size = 4096
129134
130135
131class EoanUefiTestBasic4k(relbase.eoan, TestBasicAbs):136class FocalUefiTestBasic4k(relbase.focal, TestBasicAbs):
132 __test__ = True137 __test__ = True
133 disk_block_size = 4096138 disk_block_size = 4096
134139
135140
136class FocalUefiTestBasic4k(relbase.focal, TestBasicAbs):141class GroovyUefiTestBasic4k(relbase.groovy, TestBasicAbs):
137 __test__ = True142 __test__ = True
138 disk_block_size = 4096143 disk_block_size = 4096
139144
diff --git a/tests/vmtests/test_zfsroot.py b/tests/vmtests/test_zfsroot.py
index c9c73a3..952bf7b 100644
--- a/tests/vmtests/test_zfsroot.py
+++ b/tests/vmtests/test_zfsroot.py
@@ -96,12 +96,12 @@ class BionicTestZfsRoot(relbase.bionic, TestZfsRootAbs):
96 __test__ = True96 __test__ = True
9797
9898
99class EoanTestZfsRoot(relbase.eoan, TestZfsRootAbs):99class FocalTestZfsRoot(relbase.focal, TestZfsRootAbs):
100 __test__ = True100 __test__ = True
101 mem = 4096101 mem = 4096
102102
103103
104class FocalTestZfsRoot(relbase.focal, TestZfsRootAbs):104class GroovyTestZfsRoot(relbase.groovy, TestZfsRootAbs):
105 __test__ = True105 __test__ = True
106 mem = 4096106 mem = 4096
107107
@@ -125,13 +125,12 @@ class BionicTestZfsRootFsType(relbase.bionic, TestZfsRootFsTypeAbs):
125 __test__ = True125 __test__ = True
126126
127127
128class EoanTestZfsRootFsType(relbase.eoan, TestZfsRootFsTypeAbs):128class FocalTestZfsRootFsType(relbase.focal, TestZfsRootFsTypeAbs):
129 __test__ = True129 __test__ = True
130 mem = 4096130 mem = 4096
131131
132132
133class FocalTestZfsRootFsType(relbase.focal, TestZfsRootFsTypeAbs):133class GroovyTestZfsRootFsType(relbase.groovy, TestZfsRootFsTypeAbs):
134 __test__ = True134 __test__ = True
135 mem = 4096
136135
137# vi: ts=4 expandtab syntax=python136# vi: ts=4 expandtab syntax=python
diff --git a/tools/curtainer b/tools/curtainer
index 466d719..b24884b 100755
--- a/tools/curtainer
+++ b/tools/curtainer
@@ -153,20 +153,35 @@ main() {
153 fi153 fi
154 getsource="${getsource%/}"154 getsource="${getsource%/}"
155155
156 lxc launch "$src" "$name" || fail "failed lxc launch $src $name"156 # launch container; mask snapd.seeded.service; not needed
157 {
158 lxc init "$src" "$name" &&
159 lxc file push \
160 /dev/null ${name}/etc/systemd/system/snapd.seeded.service &&
161 lxc start ${name}
162 } || fail "failed lxc launch $src $name"
157 CONTAINER=$name163 CONTAINER=$name
158164
159 wait_for_ready "$name" $maxwait $VERBOSITY ||165 wait_for_ready "$name" $maxwait $VERBOSITY ||
160 fail "$name did not become ready after $maxwait"166 fail "$name did not become ready after $maxwait"
161167
162 inside "$name" which eatmydata >/dev/null || eatmydata=""168 inside "$name" which eatmydata >/dev/null || eatmydata=""
169 release=$(inside $name lsb_release -sc) ||
170 fail "$name did not have a lsb release codename"
171
172 # curtin depends on zfsutils-linux via probert-storage, but zfsutils-linux
173 # can't be installed in an unprivileged container as it fails to start
174 # the zfs-mount and zfs-share services as /dev/zfs is missing. We do
175 # not actually need ZFS to work in the container, so the problem can be
176 # worked around by masking the services before the package is installed.
177 inside "$name" systemctl mask zfs-mount || fail "failed to mask zfs-mount"
178 inside "$name" systemctl mask zfs-share || fail "failed to mask zfs-share"
163179
164 if $proposed; then180 if $proposed; then
165 mirror=$(inside $name awk '$1 == "deb" { print $2; exit(0); }' \181 mirror=$(inside $name awk '$1 == "deb" { print $2; exit(0); }' \
166 /etc/apt/sources.list) &&182 /etc/apt/sources.list) ||
167 rel=$(inside $name lsb_release -sc) ||
168 fail "failed to get mirror in $name"183 fail "failed to get mirror in $name"
169 line="$mirror $rel-proposed main universe"184 line="$mirror $release-proposed main universe"
170 local fname="/etc/apt/sources.list.d/proposed.list"185 local fname="/etc/apt/sources.list.d/proposed.list"
171 debug 1 "enabling proposed in $fname: deb $line"186 debug 1 "enabling proposed in $fname: deb $line"
172 inside "$name" sh -c "echo deb $line > $fname" ||187 inside "$name" sh -c "echo deb $line > $fname" ||
@@ -179,9 +194,30 @@ main() {
179 if $daily; then194 if $daily; then
180 local daily_ppa="ppa:curtin-dev/daily"195 local daily_ppa="ppa:curtin-dev/daily"
181 debug 1 "enabling daily: $daily_ppa"196 debug 1 "enabling daily: $daily_ppa"
182 inside "$name" add-apt-repository --enable-source --yes \197 local addaptrepo="add-apt-repository"
183 "${daily_ppa}" ||198 inside "$name" which $addaptrepo >/dev/null || addaptrepo=""
184 fail "failed add-apt-repository for daily."199 if [ -n "${addaptrepo}" ]; then
200 inside "$name" ${addaptrepo} --enable-source --yes --no-update \
201 "${daily_ppa}" ||
202 fail "failed add-apt-repository for daily."
203 else
204 # https://launchpad.net/~curtin-dev/+archive/ubuntu/daily
205 local url="http://ppa.launchpad.net/curtin-dev/daily/ubuntu"
206 local lfile="/etc/apt/sources.list.d/curtin-daily-ppa.list"
207 local kfile="/etc/apt/trusted.gpg.d/curtin-daily-ppa.asc"
208 local key="0x1bc30f715a3b861247a81a5e55fe7c8c0165013e"
209 local keyserver="keyserver.ubuntu.com"
210 local keyurl="https://${keyserver}/pks/lookup?op=get&search=${key}"
211 inside "$name" sh -c "
212 echo deb $url $release main > $lfile &&
213 wget -q \"$keyurl\" -O $kfile" ||
214 fail "failed to add $daily_ppa repository manually"
215 if [ "$getsource" != "none" ]; then
216 inside "$name" sh -c "
217 echo deb-src $url $release main >> $lfile" ||
218 fail "failed adding daily ppa deb-src to $lfile"
219 fi
220 fi
185 fi221 fi
186222
187 line="Acquire::Languages \"none\";"223 line="Acquire::Languages \"none\";"
diff --git a/tools/jenkins-runner b/tools/jenkins-runner
index 253b722..375dc3c 100755
--- a/tools/jenkins-runner
+++ b/tools/jenkins-runner
@@ -5,6 +5,7 @@ topdir="${CURTIN_VMTEST_TOPDIR:-${WORKSPACE:-$PWD}/output}"
5pkeep=${CURTIN_VMTEST_KEEP_DATA_PASS:-logs,collect}5pkeep=${CURTIN_VMTEST_KEEP_DATA_PASS:-logs,collect}
6fkeep=${CURTIN_VMTEST_KEEP_DATA_FAIL:-logs,collect}6fkeep=${CURTIN_VMTEST_KEEP_DATA_FAIL:-logs,collect}
7reuse=${CURTIN_VMTEST_REUSE_TOPDIR:-0}7reuse=${CURTIN_VMTEST_REUSE_TOPDIR:-0}
8shuffle=${CURTIN_VMTEST_SHUFFLE_TESTS:-1}
8declare -i ltimeout=${CURTIN_VMTEST_IMAGE_LOCK_TIMEOUT:-"-1"}9declare -i ltimeout=${CURTIN_VMTEST_IMAGE_LOCK_TIMEOUT:-"-1"}
9export CURTIN_VMTEST_TAR_DISKS=${CURTIN_VMTEST_TAR_DISKS:-0}10export CURTIN_VMTEST_TAR_DISKS=${CURTIN_VMTEST_TAR_DISKS:-0}
10export CURTIN_VMTEST_REUSE_TOPDIR=$reuse11export CURTIN_VMTEST_REUSE_TOPDIR=$reuse
@@ -14,6 +15,7 @@ export CURTIN_VMTEST_KEEP_DATA_FAIL=$fkeep
14export CURTIN_VMTEST_TOPDIR="$topdir"15export CURTIN_VMTEST_TOPDIR="$topdir"
15export CURTIN_VMTEST_LOG="${CURTIN_VMTEST_LOG:-$topdir/debug.log}"16export CURTIN_VMTEST_LOG="${CURTIN_VMTEST_LOG:-$topdir/debug.log}"
16export CURTIN_VMTEST_PARALLEL=${CURTIN_VMTEST_PARALLEL:-0}17export CURTIN_VMTEST_PARALLEL=${CURTIN_VMTEST_PARALLEL:-0}
18export CURTIN_VMTEST_SHUFFLE_TESTS=$shuffle
17export IMAGE_DIR=${IMAGE_DIR:-/srv/images}19export IMAGE_DIR=${IMAGE_DIR:-/srv/images}
1820
19# empty TGT_* variables in current env to avoid killing a pid we didn't start.21# empty TGT_* variables in current env to avoid killing a pid we didn't start.
@@ -50,6 +52,15 @@ if [ "$reuse" != "1" ]; then
50 mkdir -p "$topdir" || fail "failed mkdir $topdir"52 mkdir -p "$topdir" || fail "failed mkdir $topdir"
51fi53fi
5254
55# Use 'shuf' to randomize test case execution order
56if [ "$shuffle" == "1" ]; then
57 SHUFFLE="shuf"
58else
59 # when disabled just repeat the input to output
60 SHUFFLE="tee"
61fi
62
63
53start_s=$(date +%s)64start_s=$(date +%s)
54parallel=${CURTIN_VMTEST_PARALLEL}65parallel=${CURTIN_VMTEST_PARALLEL}
55ntfilters=( )66ntfilters=( )
@@ -83,9 +94,10 @@ if [ "${#tests[@]}" -ne 0 -a "${#ntfilters[@]}" -ne 0 ]; then
83 error "test arguments provided were: ${#tests[*]}"94 error "test arguments provided were: ${#tests[*]}"
84 fail95 fail
85elif [ "${#tests[@]}" -eq 0 -a "${#ntfilters[@]}" -eq 0 ]; then96elif [ "${#tests[@]}" -eq 0 -a "${#ntfilters[@]}" -eq 0 ]; then
86 tests=( tests/vmtests )97 # run filter without args to enumerate all tests and maybe shuffle
98 tests=( $(./tools/vmtest-filter | ${SHUFFLE}) )
87elif [ "${#ntfilters[@]}" -ne 0 ]; then99elif [ "${#ntfilters[@]}" -ne 0 ]; then
88 tests=( $(./tools/vmtest-filter "${ntfilters[@]}") )100 tests=( $(./tools/vmtest-filter "${ntfilters[@]}" | ${SHUFFLE}) )
89 if [ "${#tests[@]}" -eq 0 ]; then101 if [ "${#tests[@]}" -eq 0 ]; then
90 error "Failed to find any tests with filter(s): \"${ntfilters[*]}\""102 error "Failed to find any tests with filter(s): \"${ntfilters[*]}\""
91 fail "Try testing filters with: ./tools/vmtest-filter ${ntfilters[*]}"103 fail "Try testing filters with: ./tools/vmtest-filter ${ntfilters[*]}"
diff --git a/tools/run-pep8 b/tools/run-pep8
index c27a96c..227129c 100755
--- a/tools/run-pep8
+++ b/tools/run-pep8
@@ -9,10 +9,10 @@ pycheck_dirs=(
9 "tools/block-discover-to-config"9 "tools/block-discover-to-config"
10 "tools/curtin-log-print"10 "tools/curtin-log-print"
11 "tools/noproxy"11 "tools/noproxy"
12 "tools/remove-vmtest-release"
13 "tools/schema-validate-storage"12 "tools/schema-validate-storage"
14 "tools/ssh-keys-list"13 "tools/ssh-keys-list"
15 "tools/vmtest-filter"14 "tools/vmtest-filter"
15 "tools/vmtest-remove-release"
16 "tools/vmtest-sync-images"16 "tools/vmtest-sync-images"
17 "tools/webserv"17 "tools/webserv"
18 "tools/write-curtin"18 "tools/write-curtin"
diff --git a/tools/remove-vmtest-release b/tools/vmtest-remove-release
19similarity index 97%19similarity index 97%
20rename from tools/remove-vmtest-release20rename from tools/remove-vmtest-release
21rename to tools/vmtest-remove-release21rename to tools/vmtest-remove-release
22old mode 10064422old mode 100644
23new mode 10075523new mode 100755
index 8ab9b2e..d2c5f83
--- a/tools/remove-vmtest-release
+++ b/tools/vmtest-remove-release
@@ -36,7 +36,7 @@ def clean_file(fname, distro):
3636
37if __name__ == "__main__":37if __name__ == "__main__":
38 parser = argparse.ArgumentParser(38 parser = argparse.ArgumentParser(
39 prog="remove-vmtest-release",39 prog="vmtest-remove-release",
40 description="Tool to remove vmtest classes by distro release")40 description="Tool to remove vmtest classes by distro release")
41 parser.add_argument('--distro-release', '-d',41 parser.add_argument('--distro-release', '-d',
42 action='store', required=True)42 action='store', required=True)
diff --git a/tox.ini b/tox.ini
index 72d56d4..04b43b6 100644
--- a/tox.ini
+++ b/tox.ini
@@ -60,7 +60,7 @@ commands = {envpython} -m pyflakes {posargs:curtin/ tests/ tools/}
60# set basepython because tox 1.6 (trusty) does not support generated environments60# set basepython because tox 1.6 (trusty) does not support generated environments
61basepython = python361basepython = python3
62deps = {[testenv]deps}62deps = {[testenv]deps}
63 pylint==2.3.163 pylint==2.6.0
64 git+https://git.launchpad.net/simplestreams64 git+https://git.launchpad.net/simplestreams
65commands = {envpython} -m pylint --errors-only {posargs:curtin tests/vmtests}65commands = {envpython} -m pylint --errors-only {posargs:curtin tests/vmtests}
6666

Subscribers

People subscribed via source and target branches