← Back to team overview

curtin-dev team mailing list archive

[Merge] ~raharper/curtin:vmtest/enable-h-series into curtin:master

 

Ryan Harper has proposed merging ~raharper/curtin:vmtest/enable-h-series into curtin:master.

Commit message:
vmtest: add Hirsute release classes, tool to add vmtest class

Add a tool to generate new release classes based on a previous
release, for example:

  ./tools/vmtest-add-release -p tests/vmtests \
    --previous-release=focal --distro-release=hirsute

User still needs to create the release class in tests/vmtests/releases.py


Requested reviews:
  curtin developers (curtin-dev)

For more details, see:
https://code.launchpad.net/~raharper/curtin/+git/curtin/+merge/396278
-- 
Your team curtin developers is requested to review the proposed merge of ~raharper/curtin:vmtest/enable-h-series into curtin:master.
diff --git a/tests/vmtests/releases.py b/tests/vmtests/releases.py
index c99ca7e..35b069b 100644
--- a/tests/vmtests/releases.py
+++ b/tests/vmtests/releases.py
@@ -193,6 +193,14 @@ class _GroovyBase(_UbuntuBase):
         subarch = "ga-20.10"
 
 
+class _HirsuteBase(_UbuntuBase):
+    release = "hirsute"
+    target_release = "hirsute"
+    mem = "2048"
+    if _UbuntuBase.arch == "arm64":
+        subarch = "ga-20.10"
+
+
 class _Releases(object):
     trusty = _TrustyBase
     precise = _PreciseBase
@@ -212,6 +220,7 @@ class _Releases(object):
     eoan = _EoanBase
     focal = _FocalBase
     groovy = _GroovyBase
+    hirsute = _HirsuteBase
 
 
 class _CentosReleases(object):
diff --git a/tests/vmtests/test_apt_config_cmd.py b/tests/vmtests/test_apt_config_cmd.py
index 874efad..2381118 100644
--- a/tests/vmtests/test_apt_config_cmd.py
+++ b/tests/vmtests/test_apt_config_cmd.py
@@ -68,6 +68,9 @@ class FocalTestAptConfigCMDCMD(relbase.focal, TestAptConfigCMD):
     __test__ = True
 
 
+class HirsuteTestAptConfigCMDCMD(relbase.hirsute, TestAptConfigCMD):
+    __test__ = True
+
 class GroovyTestAptConfigCMDCMD(relbase.groovy, TestAptConfigCMD):
     __test__ = True
 
diff --git a/tests/vmtests/test_basic.py b/tests/vmtests/test_basic.py
index 5723bc6..4a1f95d 100644
--- a/tests/vmtests/test_basic.py
+++ b/tests/vmtests/test_basic.py
@@ -259,6 +259,9 @@ class FocalTestBasic(relbase.focal, TestBasicAbs):
     __test__ = True
 
 
+class HirsuteTestBasic(relbase.hirsute, TestBasicAbs):
+    __test__ = True
+
 class GroovyTestBasic(relbase.groovy, TestBasicAbs):
     __test__ = True
 
@@ -379,6 +382,9 @@ class FocalTestScsiBasic(relbase.focal, TestBasicScsiAbs):
     __test__ = True
 
 
+class HirsuteTestScsiBasic(relbase.hirsute, TestBasicScsiAbs):
+    __test__ = True
+
 class GroovyTestScsiBasic(relbase.groovy, TestBasicScsiAbs):
     __test__ = True
 
diff --git a/tests/vmtests/test_basic_dasd.py b/tests/vmtests/test_basic_dasd.py
index d61e1b9..e5e3094 100644
--- a/tests/vmtests/test_basic_dasd.py
+++ b/tests/vmtests/test_basic_dasd.py
@@ -56,6 +56,9 @@ class FocalTestBasicDasd(relbase.focal, TestBasicDasd):
     __test__ = True
 
 
+class HirsuteTestBasicDasd(relbase.hirsute, TestBasicDasd):
+    __test__ = True
+
 class GroovyTestBasicDasd(relbase.groovy, TestBasicDasd):
     __test__ = True
 
diff --git a/tests/vmtests/test_bcache_basic.py b/tests/vmtests/test_bcache_basic.py
index 053225f..f4d3fe5 100644
--- a/tests/vmtests/test_bcache_basic.py
+++ b/tests/vmtests/test_bcache_basic.py
@@ -68,6 +68,9 @@ class FocalBcacheBasic(relbase.focal, TestBcacheBasic):
     __test__ = True
 
 
+class HirsuteBcacheBasic(relbase.hirsute, TestBcacheBasic):
+    __test__ = True
+
 class GroovyBcacheBasic(relbase.groovy, TestBcacheBasic):
     __test__ = True
 
diff --git a/tests/vmtests/test_bcache_bug1718699.py b/tests/vmtests/test_bcache_bug1718699.py
index ebb99ab..95086e8 100644
--- a/tests/vmtests/test_bcache_bug1718699.py
+++ b/tests/vmtests/test_bcache_bug1718699.py
@@ -23,6 +23,9 @@ class FocalTestBcacheBug1718699(relbase.focal, TestBcacheBug1718699):
     __test__ = True
 
 
+class HirsuteTestBcacheBug1718699(relbase.hirsute, TestBcacheBug1718699):
+    __test__ = True
+
 class GroovyTestBcacheBug1718699(relbase.groovy, TestBcacheBug1718699):
     __test__ = True
 
diff --git a/tests/vmtests/test_bcache_ceph.py b/tests/vmtests/test_bcache_ceph.py
index bff4dd4..7390b42 100644
--- a/tests/vmtests/test_bcache_ceph.py
+++ b/tests/vmtests/test_bcache_ceph.py
@@ -79,6 +79,9 @@ class FocalTestBcacheCeph(relbase.focal, TestBcacheCeph):
     __test__ = True
 
 
+class HirsuteTestBcacheCeph(relbase.hirsute, TestBcacheCeph):
+    __test__ = True
+
 class GroovyTestBcacheCeph(relbase.groovy, TestBcacheCeph):
     __test__ = True
 
@@ -109,6 +112,9 @@ class FocalTestBcacheCephLvm(relbase.focal, TestBcacheCephLvm):
     __test__ = True
 
 
+class HirsuteTestBcacheCephLvm(relbase.hirsute, TestBcacheCephLvm):
+    __test__ = True
+
 class GroovyTestBcacheCephLvm(relbase.groovy, TestBcacheCephLvm):
     __test__ = True
 
diff --git a/tests/vmtests/test_bcache_partitions.py b/tests/vmtests/test_bcache_partitions.py
index 1ffea12..9eede80 100644
--- a/tests/vmtests/test_bcache_partitions.py
+++ b/tests/vmtests/test_bcache_partitions.py
@@ -29,6 +29,9 @@ class FocalTestBcachePartitions(relbase.focal, TestBcachePartitions):
     __test__ = True
 
 
+class HirsuteTestBcachePartitions(relbase.hirsute, TestBcachePartitions):
+    __test__ = True
+
 class GroovyTestBcachePartitions(relbase.groovy, TestBcachePartitions):
     __test__ = True
 
diff --git a/tests/vmtests/test_fs_battery.py b/tests/vmtests/test_fs_battery.py
index 7177fea..63413bd 100644
--- a/tests/vmtests/test_fs_battery.py
+++ b/tests/vmtests/test_fs_battery.py
@@ -243,6 +243,9 @@ class FocalTestFsBattery(relbase.focal, TestFsBattery):
     __test__ = True
 
 
+class HirsuteTestFsBattery(relbase.hirsute, TestFsBattery):
+    __test__ = True
+
 class GroovyTestFsBattery(relbase.groovy, TestFsBattery):
     __test__ = True
 
diff --git a/tests/vmtests/test_iscsi.py b/tests/vmtests/test_iscsi.py
index f3406cd..063b17d 100644
--- a/tests/vmtests/test_iscsi.py
+++ b/tests/vmtests/test_iscsi.py
@@ -76,6 +76,9 @@ class FocalTestIscsiBasic(relbase.focal, TestBasicIscsiAbs):
     __test__ = True
 
 
+class HirsuteTestIscsiBasic(relbase.hirsute, TestBasicIscsiAbs):
+    __test__ = True
+
 class GroovyTestIscsiBasic(relbase.groovy, TestBasicIscsiAbs):
     __test__ = True
 
diff --git a/tests/vmtests/test_journald_reporter.py b/tests/vmtests/test_journald_reporter.py
index ff003a5..596aed6 100644
--- a/tests/vmtests/test_journald_reporter.py
+++ b/tests/vmtests/test_journald_reporter.py
@@ -36,6 +36,9 @@ class FocalTestJournaldReporter(relbase.focal, TestJournaldReporter):
     __test__ = True
 
 
+class HirsuteTestJournaldReporter(relbase.hirsute, TestJournaldReporter):
+    __test__ = True
+
 class GroovyTestJournaldReporter(relbase.groovy, TestJournaldReporter):
     __test__ = True
 
diff --git a/tests/vmtests/test_lvm.py b/tests/vmtests/test_lvm.py
index eb65c32..04eedd1 100644
--- a/tests/vmtests/test_lvm.py
+++ b/tests/vmtests/test_lvm.py
@@ -81,6 +81,9 @@ class FocalTestLvm(relbase.focal, TestLvmAbs):
     __test__ = True
 
 
+class HirsuteTestLvm(relbase.hirsute, TestLvmAbs):
+    __test__ = True
+
 class GroovyTestLvm(relbase.groovy, TestLvmAbs):
     __test__ = True
 
diff --git a/tests/vmtests/test_lvm_iscsi.py b/tests/vmtests/test_lvm_iscsi.py
index e0b9606..d2deff3 100644
--- a/tests/vmtests/test_lvm_iscsi.py
+++ b/tests/vmtests/test_lvm_iscsi.py
@@ -99,6 +99,9 @@ class FocalTestIscsiLvm(relbase.focal, TestLvmIscsiAbs):
     __test__ = True
 
 
+class HirsuteTestIscsiLvm(relbase.hirsute, TestLvmIscsiAbs):
+    __test__ = True
+
 class GroovyTestIscsiLvm(relbase.groovy, TestLvmIscsiAbs):
     __test__ = True
 
diff --git a/tests/vmtests/test_lvm_raid.py b/tests/vmtests/test_lvm_raid.py
index 5fe7993..58f87e5 100644
--- a/tests/vmtests/test_lvm_raid.py
+++ b/tests/vmtests/test_lvm_raid.py
@@ -59,5 +59,8 @@ class FocalTestLvmOverRaid(relbase.focal, TestLvmOverRaidAbs):
     __test__ = True
 
 
+class HirsuteTestLvmOverRaid(relbase.hirsute, TestLvmOverRaidAbs):
+    __test__ = True
+
 class GroovyTestLvmOverRaid(relbase.groovy, TestLvmOverRaidAbs):
     __test__ = True
diff --git a/tests/vmtests/test_lvm_root.py b/tests/vmtests/test_lvm_root.py
index 12b8ea8..b2dbf69 100644
--- a/tests/vmtests/test_lvm_root.py
+++ b/tests/vmtests/test_lvm_root.py
@@ -94,6 +94,12 @@ class FocalTestLvmRootExt4(relbase.focal, TestLvmRootAbs):
     }
 
 
+class HirsuteTestLvmRootExt4(relbase.hirsute, TestLvmRootAbs):
+    __test__ = True
+    conf_replace = {
+        '__ROOTFS_FORMAT__': 'ext4',
+    }
+
 class GroovyTestLvmRootExt4(relbase.groovy, TestLvmRootAbs):
     __test__ = True
     conf_replace = {
@@ -147,6 +153,13 @@ class FocalTestUefiLvmRootExt4(relbase.focal, TestUefiLvmRootAbs):
     }
 
 
+class HirsuteTestUefiLvmRootExt4(relbase.hirsute, TestUefiLvmRootAbs):
+    __test__ = True
+    conf_replace = {
+        '__BOOTFS_FORMAT__': 'ext4',
+        '__ROOTFS_FORMAT__': 'ext4',
+    }
+
 class GroovyTestUefiLvmRootExt4(relbase.groovy, TestUefiLvmRootAbs):
     __test__ = True
     conf_replace = {
@@ -164,10 +177,17 @@ class XenialTestUefiLvmRootXfs(relbase.xenial, TestUefiLvmRootAbs):
 
 
 class XenialTestUefiLvmRootXfsBootXfs(relbase.xenial, TestUefiLvmRootAbs):
-    """This tests xfs root and xfs boot with uefi.
+    """This tests xfs root and xfs boot with uefi."""
+    __test__ = True
+    conf_replace = {
+        '__BOOTFS_FORMAT__': 'xfs',
+        '__ROOTFS_FORMAT__': 'xfs',
+    }
 
-    It is known broken (LP: #1652822) and unlikely to be fixed."""
-    __test__ = False
+
+class GroovyTestUefiLvmRootXfsBootXfs(relbase.groovy, TestUefiLvmRootAbs):
+    """This tests xfs root and xfs boot with uefi."""
+    __test__ = True
     conf_replace = {
         '__BOOTFS_FORMAT__': 'xfs',
         '__ROOTFS_FORMAT__': 'xfs',
diff --git a/tests/vmtests/test_mdadm_bcache.py b/tests/vmtests/test_mdadm_bcache.py
index ce909f8..0869b5c 100644
--- a/tests/vmtests/test_mdadm_bcache.py
+++ b/tests/vmtests/test_mdadm_bcache.py
@@ -158,6 +158,9 @@ class FocalTestMdadmBcache(relbase.focal, TestMdadmBcacheAbs):
     __test__ = True
 
 
+class HirsuteTestMdadmBcache(relbase.hirsute, TestMdadmBcacheAbs):
+    __test__ = True
+
 class GroovyTestMdadmBcache(relbase.groovy, TestMdadmBcacheAbs):
     __test__ = True
 
@@ -203,6 +206,9 @@ class FocalTestMirrorboot(relbase.focal, TestMirrorbootAbs):
     __test__ = True
 
 
+class HirsuteTestMirrorboot(relbase.hirsute, TestMirrorbootAbs):
+    __test__ = True
+
 class GroovyTestMirrorboot(relbase.groovy, TestMirrorbootAbs):
     __test__ = True
 
@@ -252,6 +258,10 @@ class FocalTestMirrorbootPartitions(relbase.focal,
     __test__ = True
 
 
+class HirsuteTestMirrorbootPartitions(relbase.hirsute,
+                                    TestMirrorbootPartitionsAbs):
+    __test__ = True
+
 class GroovyTestMirrorbootPartitions(relbase.groovy,
                                      TestMirrorbootPartitionsAbs):
     __test__ = True
@@ -347,6 +357,10 @@ class FocalTestMirrorbootPartitionsUEFI(relbase.focal,
     __test__ = True
 
 
+class HirsuteTestMirrorbootPartitionsUEFI(relbase.hirsute,
+                                        TestMirrorbootPartitionsUEFIAbs):
+    __test__ = True
+
 class GroovyTestMirrorbootPartitionsUEFI(relbase.groovy,
                                          TestMirrorbootPartitionsUEFIAbs):
     __test__ = True
@@ -396,6 +410,9 @@ class FocalTestRaid5boot(relbase.focal, TestRaid5bootAbs):
     __test__ = True
 
 
+class HirsuteTestRaid5boot(relbase.hirsute, TestRaid5bootAbs):
+    __test__ = True
+
 class GroovyTestRaid5boot(relbase.groovy, TestRaid5bootAbs):
     __test__ = True
 
@@ -457,6 +474,9 @@ class FocalTestRaid6boot(relbase.focal, TestRaid6bootAbs):
     __test__ = True
 
 
+class HirsuteTestRaid6boot(relbase.hirsute, TestRaid6bootAbs):
+    __test__ = True
+
 class GroovyTestRaid6boot(relbase.groovy, TestRaid6bootAbs):
     __test__ = True
 
@@ -504,6 +524,9 @@ class FocalTestRaid10boot(relbase.focal, TestRaid10bootAbs):
     __test__ = True
 
 
+class HirsuteTestRaid10boot(relbase.hirsute, TestRaid10bootAbs):
+    __test__ = True
+
 class GroovyTestRaid10boot(relbase.groovy, TestRaid10bootAbs):
     __test__ = True
 
@@ -608,6 +631,9 @@ class FocalTestAllindata(relbase.focal, TestAllindataAbs):
     __test__ = True
 
 
+class HirsuteTestAllindata(relbase.hirsute, TestAllindataAbs):
+    __test__ = True
+
 class GroovyTestAllindata(relbase.groovy, TestAllindataAbs):
     __test__ = True
 
diff --git a/tests/vmtests/test_mdadm_iscsi.py b/tests/vmtests/test_mdadm_iscsi.py
index 7e6fbf6..0a9c94f 100644
--- a/tests/vmtests/test_mdadm_iscsi.py
+++ b/tests/vmtests/test_mdadm_iscsi.py
@@ -54,6 +54,9 @@ class FocalTestIscsiMdadm(relbase.focal, TestMdadmIscsiAbs):
     __test__ = True
 
 
+class HirsuteTestIscsiMdadm(relbase.hirsute, TestMdadmIscsiAbs):
+    __test__ = True
+
 class GroovyTestIscsiMdadm(relbase.groovy, TestMdadmIscsiAbs):
     __test__ = True
 
diff --git a/tests/vmtests/test_multipath.py b/tests/vmtests/test_multipath.py
index 6d9c5df..072b05a 100644
--- a/tests/vmtests/test_multipath.py
+++ b/tests/vmtests/test_multipath.py
@@ -162,6 +162,9 @@ class FocalTestMultipathBasic(relbase.focal, TestMultipathBasicAbs):
     __test__ = True
 
 
+class HirsuteTestMultipathBasic(relbase.hirsute, TestMultipathBasicAbs):
+    __test__ = True
+
 class GroovyTestMultipathBasic(relbase.groovy, TestMultipathBasicAbs):
     __test__ = True
 
diff --git a/tests/vmtests/test_multipath_lvm.py b/tests/vmtests/test_multipath_lvm.py
index c5a1e42..28e6e94 100644
--- a/tests/vmtests/test_multipath_lvm.py
+++ b/tests/vmtests/test_multipath_lvm.py
@@ -60,6 +60,9 @@ class FocalTestMultipathLvm(relbase.focal, TestMultipathLvmAbs):
     __test__ = True
 
 
+class HirsuteTestMultipathLvm(relbase.hirsute, TestMultipathLvmAbs):
+    __test__ = True
+
 class GroovyTestMultipathLvm(relbase.groovy, TestMultipathLvmAbs):
     __test__ = True
 
@@ -73,6 +76,10 @@ class FocalTestMultipathLvmPartWipe(relbase.focal,
     __test__ = True
 
 
+class HirsuteTestMultipathLvmPartWipe(relbase.hirsute,
+                                    TestMultipathLvmPartWipeAbs):
+    __test__ = True
+
 class GroovyTestMultipathLvmPartWipe(relbase.groovy,
                                      TestMultipathLvmPartWipeAbs):
     __test__ = True
diff --git a/tests/vmtests/test_network.py b/tests/vmtests/test_network.py
index 43a7c6b..6e3927d 100644
--- a/tests/vmtests/test_network.py
+++ b/tests/vmtests/test_network.py
@@ -478,6 +478,9 @@ class FocalTestNetworkBasic(relbase.focal, TestNetworkBasicAbs):
     __test__ = True
 
 
+class HirsuteTestNetworkBasic(relbase.hirsute, TestNetworkBasicAbs):
+    __test__ = True
+
 class GroovyTestNetworkBasic(relbase.groovy, TestNetworkBasicAbs):
     __test__ = True
 
diff --git a/tests/vmtests/test_network_alias.py b/tests/vmtests/test_network_alias.py
index bc1fb22..4f41b88 100644
--- a/tests/vmtests/test_network_alias.py
+++ b/tests/vmtests/test_network_alias.py
@@ -56,6 +56,9 @@ class FocalTestNetworkAlias(relbase.focal, TestNetworkAliasAbs):
     __test__ = True
 
 
+class HirsuteTestNetworkAlias(relbase.hirsute, TestNetworkAliasAbs):
+    __test__ = True
+
 class GroovyTestNetworkAlias(relbase.groovy, TestNetworkAliasAbs):
     __test__ = True
 
diff --git a/tests/vmtests/test_network_bonding.py b/tests/vmtests/test_network_bonding.py
index 6c6dd6d..da68829 100644
--- a/tests/vmtests/test_network_bonding.py
+++ b/tests/vmtests/test_network_bonding.py
@@ -61,6 +61,9 @@ class FocalTestBonding(relbase.focal, TestNetworkBondingAbs):
     __test__ = True
 
 
+class HirsuteTestBonding(relbase.hirsute, TestNetworkBondingAbs):
+    __test__ = True
+
 class GroovyTestBonding(relbase.groovy, TestNetworkBondingAbs):
     __test__ = True
 
diff --git a/tests/vmtests/test_network_bridging.py b/tests/vmtests/test_network_bridging.py
index 9ecd2f6..912b0a3 100644
--- a/tests/vmtests/test_network_bridging.py
+++ b/tests/vmtests/test_network_bridging.py
@@ -240,6 +240,9 @@ class FocalTestBridging(relbase.focal, TestBridgeNetworkAbs):
     __test__ = True
 
 
+class HirsuteTestBridging(relbase.hirsute, TestBridgeNetworkAbs):
+    __test__ = True
+
 class GroovyTestBridging(relbase.groovy, TestBridgeNetworkAbs):
     __test__ = True
 
diff --git a/tests/vmtests/test_network_disabled.py b/tests/vmtests/test_network_disabled.py
index ea8dae2..530a1e0 100644
--- a/tests/vmtests/test_network_disabled.py
+++ b/tests/vmtests/test_network_disabled.py
@@ -57,6 +57,10 @@ class FocalCurtinDisableNetworkRendering(relbase.focal,
     __test__ = True
 
 
+class HirsuteCurtinDisableNetworkRendering(relbase.hirsute,
+                                         CurtinDisableNetworkRendering):
+    __test__ = True
+
 class GroovyCurtinDisableNetworkRendering(relbase.groovy,
                                           CurtinDisableNetworkRendering):
     __test__ = True
@@ -65,6 +69,9 @@ class GroovyCurtinDisableNetworkRendering(relbase.groovy,
 class FocalCurtinDisableCloudInitNetworkingVersion1(
     relbase.focal,
     CurtinDisableCloudInitNetworkingVersion1
+class HirsuteCurtinDisableCloudInitNetworkingVersion1(
+    relbase.hirsute,
+    CurtinDisableCloudInitNetworkingVersion1
 ):
     __test__ = True
 
@@ -81,6 +88,10 @@ class FocalCurtinDisableCloudInitNetworking(relbase.focal,
     __test__ = True
 
 
+class HirsuteCurtinDisableCloudInitNetworking(relbase.hirsute,
+                                            CurtinDisableCloudInitNetworking):
+    __test__ = True
+
 class GroovyCurtinDisableCloudInitNetworking(relbase.groovy,
                                              CurtinDisableCloudInitNetworking):
     __test__ = True
diff --git a/tests/vmtests/test_network_ipv6_static.py b/tests/vmtests/test_network_ipv6_static.py
index 28ff697..7af5585 100644
--- a/tests/vmtests/test_network_ipv6_static.py
+++ b/tests/vmtests/test_network_ipv6_static.py
@@ -27,6 +27,9 @@ class FocalTestNetworkIPV6Static(relbase.focal, TestNetworkIPV6StaticAbs):
     __test__ = True
 
 
+class HirsuteTestNetworkIPV6Static(relbase.hirsute, TestNetworkIPV6StaticAbs):
+    __test__ = True
+
 class GroovyTestNetworkIPV6Static(relbase.groovy, TestNetworkIPV6StaticAbs):
     __test__ = True
 
diff --git a/tests/vmtests/test_network_ipv6_vlan.py b/tests/vmtests/test_network_ipv6_vlan.py
index 226f52e..a4080b9 100644
--- a/tests/vmtests/test_network_ipv6_vlan.py
+++ b/tests/vmtests/test_network_ipv6_vlan.py
@@ -26,6 +26,9 @@ class FocalTestNetworkIPV6Vlan(relbase.focal, TestNetworkIPV6VlanAbs):
     __test__ = True
 
 
+class HirsuteTestNetworkIPV6Vlan(relbase.hirsute, TestNetworkIPV6VlanAbs):
+    __test__ = True
+
 class GroovyTestNetworkIPV6Vlan(relbase.groovy, TestNetworkIPV6VlanAbs):
     __test__ = True
 
diff --git a/tests/vmtests/test_network_mtu.py b/tests/vmtests/test_network_mtu.py
index c70b9e0..2dd2837 100644
--- a/tests/vmtests/test_network_mtu.py
+++ b/tests/vmtests/test_network_mtu.py
@@ -193,6 +193,9 @@ class FocalTestNetworkMtu(relbase.focal, TestNetworkMtuNetworkdAbs):
     __test__ = True
 
 
+class HirsuteTestNetworkMtu(relbase.hirsute, TestNetworkMtuNetworkdAbs):
+    __test__ = True
+
 class GroovyTestNetworkMtu(relbase.groovy, TestNetworkMtuNetworkdAbs):
     __test__ = True
 
diff --git a/tests/vmtests/test_network_ovs.py b/tests/vmtests/test_network_ovs.py
index 0cee17e..842527b 100644
--- a/tests/vmtests/test_network_ovs.py
+++ b/tests/vmtests/test_network_ovs.py
@@ -38,6 +38,9 @@ class FocalTestNetworkOvs(relbase.focal, TestNetworkOvsAbs):
     __test__ = True
 
 
+class HirsuteTestNetworkOvs(relbase.hirsute, TestNetworkOvsAbs):
+    __test__ = True
+
 class GroovyTestNetworkOvs(relbase.groovy, TestNetworkOvsAbs):
     __test__ = True
 
diff --git a/tests/vmtests/test_network_static.py b/tests/vmtests/test_network_static.py
index e0abd54..e381de9 100644
--- a/tests/vmtests/test_network_static.py
+++ b/tests/vmtests/test_network_static.py
@@ -32,6 +32,9 @@ class FocalTestNetworkStatic(relbase.focal, TestNetworkStaticAbs):
     __test__ = True
 
 
+class HirsuteTestNetworkStatic(relbase.hirsute, TestNetworkStaticAbs):
+    __test__ = True
+
 class GroovyTestNetworkStatic(relbase.groovy, TestNetworkStaticAbs):
     __test__ = True
 
diff --git a/tests/vmtests/test_network_static_routes.py b/tests/vmtests/test_network_static_routes.py
index f99d9d5..da24556 100644
--- a/tests/vmtests/test_network_static_routes.py
+++ b/tests/vmtests/test_network_static_routes.py
@@ -33,6 +33,10 @@ class FocalTestNetworkStaticRoutes(relbase.focal,
     __test__ = True
 
 
+class HirsuteTestNetworkStaticRoutes(relbase.hirsute,
+                                   TestNetworkStaticRoutesAbs):
+    __test__ = True
+
 class GroovyTestNetworkStaticRoutes(relbase.groovy,
                                     TestNetworkStaticRoutesAbs):
     __test__ = True
diff --git a/tests/vmtests/test_network_vlan.py b/tests/vmtests/test_network_vlan.py
index 691ba83..736512a 100644
--- a/tests/vmtests/test_network_vlan.py
+++ b/tests/vmtests/test_network_vlan.py
@@ -83,6 +83,12 @@ class FocalTestNetworkVlan(relbase.focal, TestNetworkVlanAbs):
         return super().test_ip_output()
 
 
+class HirsuteTestNetworkVlan(relbase.hirsute, TestNetworkVlanAbs):
+    __test__ = True
+
+    def test_ip_output(self):
+        return super().test_ip_output()
+
 class GroovyTestNetworkVlan(relbase.groovy, TestNetworkVlanAbs):
     __test__ = True
 
diff --git a/tests/vmtests/test_nvme.py b/tests/vmtests/test_nvme.py
index 39f9f3c..88000ae 100644
--- a/tests/vmtests/test_nvme.py
+++ b/tests/vmtests/test_nvme.py
@@ -143,6 +143,9 @@ class FocalTestNvmeBcache(relbase.focal, TestNvmeBcacheAbs):
     __test__ = True
 
 
+class HirsuteTestNvmeBcache(relbase.hirsute, TestNvmeBcacheAbs):
+    __test__ = True
+
 class GroovyTestNvmeBcache(relbase.groovy, TestNvmeBcacheAbs):
     __test__ = True
 
diff --git a/tests/vmtests/test_panic.py b/tests/vmtests/test_panic.py
index 7b1fdbe..6b56c0e 100644
--- a/tests/vmtests/test_panic.py
+++ b/tests/vmtests/test_panic.py
@@ -29,6 +29,9 @@ class FocalTestInstallPanic(relbase.focal, TestInstallPanic):
     __test__ = True
 
 
+class HirsuteTestInstallPanic(relbase.hirsute, TestInstallPanic):
+    __test__ = True
+
 class GroovyTestInstallPanic(relbase.groovy, TestInstallPanic):
     __test__ = True
 
diff --git a/tests/vmtests/test_pollinate_useragent.py b/tests/vmtests/test_pollinate_useragent.py
index ed14719..5b4ecaa 100644
--- a/tests/vmtests/test_pollinate_useragent.py
+++ b/tests/vmtests/test_pollinate_useragent.py
@@ -65,6 +65,9 @@ class FocalTestPollinateUserAgent(relbase.focal, TestPollinateUserAgent):
     __test__ = True
 
 
+class HirsuteTestPollinateUserAgent(relbase.hirsute, TestPollinateUserAgent):
+    __test__ = True
+
 class GroovyTestPollinateUserAgent(relbase.groovy, TestPollinateUserAgent):
     __test__ = True
 
diff --git a/tests/vmtests/test_preserve.py b/tests/vmtests/test_preserve.py
index 998218c..9fb689d 100644
--- a/tests/vmtests/test_preserve.py
+++ b/tests/vmtests/test_preserve.py
@@ -29,6 +29,9 @@ class FocalTestPreserve(relbase.focal, TestPreserve):
     __test__ = True
 
 
+class HirsuteTestPreserve(relbase.hirsute, TestPreserve):
+    __test__ = True
+
 class GroovyTestPreserve(relbase.groovy, TestPreserve):
     __test__ = True
 
diff --git a/tests/vmtests/test_preserve_bcache.py b/tests/vmtests/test_preserve_bcache.py
index bd91c5a..ffbd7b4 100644
--- a/tests/vmtests/test_preserve_bcache.py
+++ b/tests/vmtests/test_preserve_bcache.py
@@ -60,6 +60,9 @@ class FocalTestPreserveBcache(relbase.focal, TestPreserveBcache):
     __test__ = True
 
 
+class HirsuteTestPreserveBcache(relbase.hirsute, TestPreserveBcache):
+    __test__ = True
+
 class GroovyTestPreserveBcache(relbase.groovy, TestPreserveBcache):
     __test__ = True
 
diff --git a/tests/vmtests/test_preserve_lvm.py b/tests/vmtests/test_preserve_lvm.py
index 0ed7ad4..8401ca3 100644
--- a/tests/vmtests/test_preserve_lvm.py
+++ b/tests/vmtests/test_preserve_lvm.py
@@ -73,6 +73,9 @@ class FocalTestLvmPreserve(relbase.focal, TestLvmPreserveAbs):
     __test__ = True
 
 
+class HirsuteTestLvmPreserve(relbase.hirsute, TestLvmPreserveAbs):
+    __test__ = True
+
 class GroovyTestLvmPreserve(relbase.groovy, TestLvmPreserveAbs):
     __test__ = True
 
diff --git a/tests/vmtests/test_preserve_partition_wipe_vg.py b/tests/vmtests/test_preserve_partition_wipe_vg.py
index 58b1f65..9a27876 100644
--- a/tests/vmtests/test_preserve_partition_wipe_vg.py
+++ b/tests/vmtests/test_preserve_partition_wipe_vg.py
@@ -29,6 +29,9 @@ class FocalTestPreserveWipeLvm(relbase.focal, TestPreserveWipeLvm):
     __test__ = True
 
 
+class HirsuteTestPreserveWipeLvm(relbase.hirsute, TestPreserveWipeLvm):
+    __test__ = True
+
 class GroovyTestPreserveWipeLvm(relbase.groovy, TestPreserveWipeLvm):
     __test__ = True
 
@@ -52,6 +55,9 @@ class FocalTestPreserveWipeLvmSimple(relbase.focal, TestPreserveWipeLvmSimple):
     __test__ = True
 
 
+class HirsuteTestPreserveWipeLvmSimple(relbase.hirsute, TestPreserveWipeLvmSimple):
+    __test__ = True
+
 class GroovyTestPreserveWipeLvmSimple(relbase.groovy,
                                       TestPreserveWipeLvmSimple):
     __test__ = True
diff --git a/tests/vmtests/test_preserve_raid.py b/tests/vmtests/test_preserve_raid.py
index 15f2f50..d59617f 100644
--- a/tests/vmtests/test_preserve_raid.py
+++ b/tests/vmtests/test_preserve_raid.py
@@ -29,6 +29,9 @@ class FocalTestPreserveRAID(relbase.focal, TestPreserveRAID):
     __test__ = True
 
 
+class HirsuteTestPreserveRAID(relbase.hirsute, TestPreserveRAID):
+    __test__ = True
+
 class GroovyTestPreserveRAID(relbase.groovy, TestPreserveRAID):
     __test__ = True
 
diff --git a/tests/vmtests/test_raid5_bcache.py b/tests/vmtests/test_raid5_bcache.py
index 12c1878..6ae7e77 100644
--- a/tests/vmtests/test_raid5_bcache.py
+++ b/tests/vmtests/test_raid5_bcache.py
@@ -92,6 +92,9 @@ class FocalTestRaid5Bcache(relbase.focal, TestMdadmBcacheAbs):
     __test__ = True
 
 
+class HirsuteTestRaid5Bcache(relbase.hirsute, TestMdadmBcacheAbs):
+    __test__ = True
+
 class GroovyTestRaid5Bcache(relbase.groovy, TestMdadmBcacheAbs):
     __test__ = True
 
diff --git a/tests/vmtests/test_raid_partition_to_disk.py b/tests/vmtests/test_raid_partition_to_disk.py
index 4635713..cccf896 100644
--- a/tests/vmtests/test_raid_partition_to_disk.py
+++ b/tests/vmtests/test_raid_partition_to_disk.py
@@ -22,6 +22,9 @@ class FocalTestRAIDPartitionToDisk(relbase.focal, TestRAIDPartitionToDisk):
     __test__ = True
 
 
+class HirsuteTestRAIDPartitionToDisk(relbase.hirsute, TestRAIDPartitionToDisk):
+    __test__ = True
+
 class GroovyTestRAIDPartitionToDisk(relbase.groovy, TestRAIDPartitionToDisk):
     __test__ = True
 
diff --git a/tests/vmtests/test_reuse_lvm_member.py b/tests/vmtests/test_reuse_lvm_member.py
index 87afcfb..971fd52 100644
--- a/tests/vmtests/test_reuse_lvm_member.py
+++ b/tests/vmtests/test_reuse_lvm_member.py
@@ -26,6 +26,10 @@ class FocalTestReuseLVMMemberPartition(relbase.focal,
     __test__ = True
 
 
+class HirsuteTestReuseLVMMemberPartition(relbase.hirsute,
+                                       TestReuseLVMMemberPartition):
+    __test__ = True
+
 class GroovyTestReuseLVMMemberPartition(relbase.groovy,
                                         TestReuseLVMMemberPartition):
     __test__ = True
diff --git a/tests/vmtests/test_reuse_msdos_partitions.py b/tests/vmtests/test_reuse_msdos_partitions.py
index 9f18d3c..ae245ef 100644
--- a/tests/vmtests/test_reuse_msdos_partitions.py
+++ b/tests/vmtests/test_reuse_msdos_partitions.py
@@ -23,6 +23,10 @@ class FocalTestReuseMSDOSPartitions(relbase.focal,
     __test__ = True
 
 
+class HirsuteTestReuseMSDOSPartitions(relbase.hirsute,
+                                    TestReuseMSDOSPartitions):
+    __test__ = True
+
 class GroovyTestReuseMSDOSPartitions(relbase.groovy,
                                      TestReuseMSDOSPartitions):
     __test__ = True
diff --git a/tests/vmtests/test_reuse_raid_member.py b/tests/vmtests/test_reuse_raid_member.py
index 7be98f3..4e92252 100644
--- a/tests/vmtests/test_reuse_raid_member.py
+++ b/tests/vmtests/test_reuse_raid_member.py
@@ -32,6 +32,9 @@ class FocalTestReuseRAIDMember(relbase.focal, TestReuseRAIDMember):
     __test__ = True
 
 
+class HirsuteTestReuseRAIDMember(relbase.hirsute, TestReuseRAIDMember):
+    __test__ = True
+
 class GroovyTestReuseRAIDMember(relbase.groovy, TestReuseRAIDMember):
     __test__ = True
 
@@ -46,6 +49,10 @@ class FocalTestReuseRAIDMemberPartition(relbase.focal,
     __test__ = True
 
 
+class HirsuteTestReuseRAIDMemberPartition(relbase.hirsute,
+                                        TestReuseRAIDMemberPartition):
+    __test__ = True
+
 class GroovyTestReuseRAIDMemberPartition(relbase.groovy,
                                          TestReuseRAIDMemberPartition):
     __test__ = True
diff --git a/tests/vmtests/test_reuse_uefi_esp.py b/tests/vmtests/test_reuse_uefi_esp.py
index 46e7ac7..eee0f43 100644
--- a/tests/vmtests/test_reuse_uefi_esp.py
+++ b/tests/vmtests/test_reuse_uefi_esp.py
@@ -42,6 +42,12 @@ class FocalTestUefiReuseEsp(relbase.focal, TestUefiReuseEspAbs):
         return super().test_efiboot_menu_has_one_distro_entry()
 
 
+class HirsuteTestUefiReuseEsp(relbase.hirsute, TestUefiReuseEspAbs):
+    __test__ = True
+
+    def test_efiboot_menu_has_one_distro_entry(self):
+        return super().test_efiboot_menu_has_one_distro_entry()
+
 class GroovyTestUefiReuseEsp(relbase.groovy, TestUefiReuseEspAbs):
     __test__ = True
 
diff --git a/tests/vmtests/test_simple.py b/tests/vmtests/test_simple.py
index 9e71047..5f09080 100644
--- a/tests/vmtests/test_simple.py
+++ b/tests/vmtests/test_simple.py
@@ -56,6 +56,12 @@ class FocalTestSimple(relbase.focal, TestSimple):
         self.output_files_exist(["netplan.yaml"])
 
 
+class HirsuteTestSimple(relbase.hirsute, TestSimple):
+    __test__ = True
+
+    def test_output_files_exist(self):
+        self.output_files_exist(["netplan.yaml"])
+
 class GroovyTestSimple(relbase.groovy, TestSimple):
     __test__ = True
 
@@ -112,6 +118,12 @@ class FocalTestSimpleStorage(relbase.focal, TestSimpleStorage):
         self.output_files_exist(["netplan.yaml"])
 
 
+class HirsuteTestSimpleStorage(relbase.hirsute, TestSimpleStorage):
+    __test__ = True
+
+    def test_output_files_exist(self):
+        self.output_files_exist(["netplan.yaml"])
+
 class GroovyTestSimpleStorage(relbase.groovy, TestSimpleStorage):
     __test__ = True
 
@@ -145,6 +157,12 @@ class FocalTestGrubNoDefaults(relbase.focal, TestGrubNoDefaults):
         self.output_files_exist(["netplan.yaml"])
 
 
+class HirsuteTestGrubNoDefaults(relbase.hirsute, TestGrubNoDefaults):
+    __test__ = True
+
+    def test_output_files_exist(self):
+        self.output_files_exist(["netplan.yaml"])
+
 class GroovyTestGrubNoDefaults(relbase.groovy, TestGrubNoDefaults):
     __test__ = True
 
diff --git a/tests/vmtests/test_uefi_basic.py b/tests/vmtests/test_uefi_basic.py
index 932c1c8..077131b 100644
--- a/tests/vmtests/test_uefi_basic.py
+++ b/tests/vmtests/test_uefi_basic.py
@@ -114,6 +114,9 @@ class FocalUefiTestBasic(relbase.focal, TestBasicAbs):
     __test__ = True
 
 
+class HirsuteUefiTestBasic(relbase.hirsute, TestBasicAbs):
+    __test__ = True
+
 class GroovyUefiTestBasic(relbase.groovy, TestBasicAbs):
     __test__ = True
 
@@ -138,6 +141,10 @@ class FocalUefiTestBasic4k(relbase.focal, TestBasicAbs):
     disk_block_size = 4096
 
 
+class HirsuteUefiTestBasic4k(relbase.hirsute, TestBasicAbs):
+    __test__ = True
+    disk_block_size = 4096
+
 class GroovyUefiTestBasic4k(relbase.groovy, TestBasicAbs):
     __test__ = True
     disk_block_size = 4096
diff --git a/tests/vmtests/test_zfsroot.py b/tests/vmtests/test_zfsroot.py
index 952bf7b..69fc9c8 100644
--- a/tests/vmtests/test_zfsroot.py
+++ b/tests/vmtests/test_zfsroot.py
@@ -101,6 +101,10 @@ class FocalTestZfsRoot(relbase.focal, TestZfsRootAbs):
     mem = 4096
 
 
+class HirsuteTestZfsRoot(relbase.hirsute, TestZfsRootAbs):
+    __test__ = True
+    mem = 4096
+
 class GroovyTestZfsRoot(relbase.groovy, TestZfsRootAbs):
     __test__ = True
     mem = 4096
@@ -130,6 +134,10 @@ class FocalTestZfsRootFsType(relbase.focal, TestZfsRootFsTypeAbs):
     mem = 4096
 
 
+class HirsuteTestZfsRootFsType(relbase.hirsute, TestZfsRootFsTypeAbs):
+    __test__ = True
+    mem = 4096
+
 class GroovyTestZfsRootFsType(relbase.groovy, TestZfsRootFsTypeAbs):
     __test__ = True
 
diff --git a/tools/vmtest-add-release b/tools/vmtest-add-release
new file mode 100755
index 0000000..cd5bfd4
--- /dev/null
+++ b/tools/vmtest-add-release
@@ -0,0 +1,65 @@
+#!/usr/bin/python3
+
+import argparse
+import glob
+import os
+
+
+def update_file(fname, previous, distro):
+    new_content = []
+    new_class = []
+    modified = False
+    copying_class = False
+    with open(fname, 'r') as fh:
+        # using read().splitlines() to strip the newline char
+        for line in fh.read().splitlines():
+            # if we find class of previous release, copy and replace with
+            # new release
+            if line.startswith('class %s' % previous):
+                copying_class = True
+            elif copying_class and line and not line[0].isspace():
+                copying_class = False
+                # done copying class; transform and extend
+                nc = "\n".join(new_class)
+                nc = nc.replace(previous, distro)  # Focal -> Groovy
+                nc = nc.replace(previous.lower(), distro.lower())  # focal -> groovy
+                new_content.extend(nc.splitlines())
+                new_class = []
+
+            if copying_class:
+                # retain existing lines, make a copy for the new class
+                new_content.append(line)
+                new_class.append(line)
+                modified = True
+            else:
+                new_content.append(line)
+
+    # skip a re-write of content if no modifications
+    if modified:
+        updated_fn = fname
+        with open(updated_fn, 'w') as wfh:
+            wfh.write("\n".join(new_content) + '\n')
+        print("Wrote updated file: %s" % updated_fn)
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(
+        prog="vmtest-add-release",
+        description="Tool to add vmtest classes by distro release")
+    parser.add_argument('--distro-release', '-d',
+                        action='store', required=True)
+    parser.add_argument('--path', '-p', action='store', required=True)
+    parser.add_argument('--previous-release', '-r',
+                        action='store', required=True)
+
+    args = parser.parse_args()
+    distro = args.distro_release.title()
+    previous = args.previous_release.title()
+    target = args.path
+    if os.path.isdir(target):
+        files = glob.glob(os.path.normpath(target) + '/' + 'test_*.py')
+    else:
+        files = [target]
+
+    for fname in files:
+        update_file(fname, previous, distro)

Follow ups