From e4c87208e85500d01a385c8b12a78a29ada5ee79 Mon Sep 17 00:00:00 2001 From: Daniel Vincze Date: Fri, 8 May 2026 17:54:34 +0300 Subject: [PATCH 1/3] Fix new cloud-init version service enablement. New cloud-init version (>26) changed its systemd unit service name, therefore the enablement method needs to reflect that. --- coriolis/osmorphing/base.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/coriolis/osmorphing/base.py b/coriolis/osmorphing/base.py index c3ccc74a..6a2837c0 100644 --- a/coriolis/osmorphing/base.py +++ b/coriolis/osmorphing/base.py @@ -25,6 +25,8 @@ "os_type", "distribution_name", "release_version", "friendly_release_name"] DEFAULT_CLOUD_USER = "cloud-user" +CLOUD_INIT_SERVICE_UNIT_NAME = "cloud-init" +CLOUD_INIT_SERVICE_UNIT_NAME_FALLBACK = "cloud-init-main" class BaseOSMorphingTools(object, with_metaclass(abc.ABCMeta)): @@ -472,7 +474,16 @@ def _configure_cloud_init(self): self._write_cloud_init_mods_config(cloud_cfg_mods) if self._has_systemd_chroot(): - self._enable_systemd_service("cloud-init") + try: + self._enable_systemd_service(CLOUD_INIT_SERVICE_UNIT_NAME) + except exception.CoriolisException: + LOG.warning( + "Failed to enable service unit with name " + f"{CLOUD_INIT_SERVICE_UNIT_NAME}. Trying new name.") + # NOTE(dvincze): New versions of cloud-init (>26) got its + # unit service name renamed. + self._enable_systemd_service( + CLOUD_INIT_SERVICE_UNIT_NAME_FALLBACK) def _test_path_chroot(self, path): # This method uses _exec_cmd_chroot() instead of SFTP stat() From 9d94516f724d9ae91437edfdfef9408f3afdb7e7 Mon Sep 17 00:00:00 2001 From: Daniel Vincze Date: Fri, 8 May 2026 17:55:51 +0300 Subject: [PATCH 2/3] Improve upon Ubuntu OSMorphing tool check This patch makes sure that the base Ubuntu osmorphing tool checks for the migrated machine's subversion, and makes sure that it's an LTS subversion, instead of checking each LTS version separately. This will allow for provider-side refactoring and tool grouping. --- coriolis/osmorphing/ubuntu.py | 15 ++++++++------- coriolis/tests/osmorphing/test_ubuntu.py | 22 +++++++++++++--------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/coriolis/osmorphing/ubuntu.py b/coriolis/osmorphing/ubuntu.py index 25ba11f0..3c473974 100644 --- a/coriolis/osmorphing/ubuntu.py +++ b/coriolis/osmorphing/ubuntu.py @@ -24,15 +24,16 @@ def check_os_supported(cls, detected_os_info): UBUNTU_DISTRO_IDENTIFIER): return False - lts_releases = [12.04, 14.04, 16.04, 18.04, 20.04] - for lts_release in lts_releases: - if cls._version_supported_util( - detected_os_info['release_version'], - minimum=lts_release, maximum=lts_release): - return True + _, subversion = detected_os_info['release_version'].split('.', 1) + if not subversion.startswith("04"): + LOG.warning( + "Detected Ubuntu release version " + f"'{detected_os_info['release_version']}' is not an LTS one. " + "Coriolis only supports morphing Ubuntu LTS versions.") + return False return cls._version_supported_util( - detected_os_info['release_version'], minimum=22.04) + detected_os_info['release_version'], minimum=12.04) def _set_netplan_ethernet_configs( self, nics_info, dhcp=False, iface_name_prefix=None): diff --git a/coriolis/tests/osmorphing/test_ubuntu.py b/coriolis/tests/osmorphing/test_ubuntu.py index ecd8f784..042b6104 100644 --- a/coriolis/tests/osmorphing/test_ubuntu.py +++ b/coriolis/tests/osmorphing/test_ubuntu.py @@ -4,11 +4,14 @@ import logging from unittest import mock +import ddt + from coriolis.osmorphing import base from coriolis.osmorphing import ubuntu from coriolis.tests import test_base +@ddt.ddt class BaseUbuntuMorphingToolsTestCase(test_base.CoriolisBaseTestCase): """Test suite for the BaseUbuntuMophingTools class.""" @@ -37,19 +40,20 @@ def test_check_os_supported_not_supported(self): self.assertFalse(result) - def test_check_os_supported_lts_release(self): - self.detected_os_info['release_version'] = '20.04' - - result = ubuntu.BaseUbuntuMorphingTools.check_os_supported( - self.detected_os_info) - - self.assertTrue(result) + @ddt.data( + ("20.04", True), + ("20.04.2 LTS", True), + ("20.10", False), + ("20.054", False), + ) + @ddt.unpack + def test_check_os_supported_lts_release(self, release_version, expected): + self.detected_os_info['release_version'] = release_version - def test_check_os_supported_non_lts_release(self): result = ubuntu.BaseUbuntuMorphingTools.check_os_supported( self.detected_os_info) - self.assertTrue(result) + self.assertEqual(expected, result) @mock.patch.object(base.BaseLinuxOSMorphingTools, '_exec_cmd') @mock.patch.object(base.BaseLinuxOSMorphingTools, '_write_file_sudo') From 915a12658655d9511331e17216ac2966fe2d8065 Mon Sep 17 00:00:00 2001 From: Daniel Vincze Date: Mon, 11 May 2026 09:50:10 +0300 Subject: [PATCH 3/3] Replace all deprecated `@abc.abstractclassmethod` declarations Replaced them with stacked `@classmethod\n@abc.abstractmethod` --- coriolis/osmorphing/base.py | 6 ++++-- coriolis/osmorphing/osdetect/base.py | 3 ++- coriolis/tasks/base.py | 12 ++++++++---- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/coriolis/osmorphing/base.py b/coriolis/osmorphing/base.py index 6a2837c0..3d580b7e 100644 --- a/coriolis/osmorphing/base.py +++ b/coriolis/osmorphing/base.py @@ -50,7 +50,8 @@ def __init__( self._osmorphing_parameters = osmorphing_parameters self._osmorphing_operation_timeout = operation_timeout - @abc.abstractclassmethod + @classmethod + @abc.abstractmethod def get_required_detected_os_info_fields(cls): raise NotImplementedError("Required OS params not defined.") @@ -76,7 +77,8 @@ def check_detected_os_info_parameters(cls, detected_os_info): extra_os_info_fields, cls.__name__, detected_os_info)) return True - @abc.abstractclassmethod + @classmethod + @abc.abstractmethod def check_os_supported(cls, detected_os_info): raise NotImplementedError( "OS compatibility check not implemented for tools class %s" % ( diff --git a/coriolis/osmorphing/osdetect/base.py b/coriolis/osmorphing/osdetect/base.py index 82746d93..68d3c3b7 100644 --- a/coriolis/osmorphing/osdetect/base.py +++ b/coriolis/osmorphing/osdetect/base.py @@ -25,7 +25,8 @@ def __init__(self, conn, os_root_dir, operation_timeout): self._environment = {} self._osdetect_operation_timeout = operation_timeout - @abc.abstractclassmethod + @classmethod + @abc.abstractmethod def returned_detected_os_info_fields(cls): raise NotImplementedError( "No returned OS info fields by class '%s'" % cls.__name__) diff --git a/coriolis/tasks/base.py b/coriolis/tasks/base.py index 1e1ec75e..aaa71f53 100644 --- a/coriolis/tasks/base.py +++ b/coriolis/tasks/base.py @@ -48,7 +48,8 @@ def get_shared_libs_for_providers( return required_libs - @abc.abstractclassmethod + @classmethod + @abc.abstractmethod def get_required_task_info_properties(cls): """ Returns a list of the string fields which are required to be present during the tasks' run method. """ @@ -56,7 +57,8 @@ def get_required_task_info_properties(cls): "No required task info properties specified for task class of " "type '%s'." % cls) - @abc.abstractclassmethod + @classmethod + @abc.abstractmethod def get_returned_task_info_properties(cls): """ Returns a list of the string fields which are returned by the tasks' run method to be added to the task info. @@ -65,7 +67,8 @@ def get_returned_task_info_properties(cls): "No returned task info properties specified for task class of " "type '%s'." % cls) - @abc.abstractclassmethod + @classmethod + @abc.abstractmethod def get_required_provider_types(cls): """ Returns a dict with 'source/destination' as keys containing a list of all the provider types (constants.PROVIDER_TYPE_*) required for the @@ -75,7 +78,8 @@ def get_required_provider_types(cls): "No required provider types specified for task class of " "type '%s'." % cls) - @abc.abstractclassmethod + @classmethod + @abc.abstractmethod def get_required_platform(cls): """ Returns whether the task operates on the source platform, the destination, or both. (constants.TASK_PLATFORM_*)