Skip to content

Commit 0587e62

Browse files
mihaelabalutoiuDany9966
authored andcommitted
Add support for SUSE machines
Add `SUSEOSMountTools` to detect `SUSE` machines as osmorphing workers. Also, handle `sshd_config` relocation to `/usr/etc/ssh/` on newer `SUSE` versions by copying the vendor config to `/etc/ssh/` before modifying it. Skip `zypper install` when `lvm2` is already present, as some minimal images ship without repositories configured. Signed-off-by: Mihaela Balutoiu <mbalutoiu@cloudbasesolutions.com>
1 parent edf263c commit 0587e62

4 files changed

Lines changed: 162 additions & 5 deletions

File tree

coriolis/osmorphing/osmount/factory.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from coriolis import constants
99
from coriolis import exception
1010
from coriolis.osmorphing.osmount import redhat
11+
from coriolis.osmorphing.osmount import suse
1112
from coriolis.osmorphing.osmount import ubuntu
1213
from coriolis.osmorphing.osmount import windows
1314

@@ -17,7 +18,8 @@
1718
def get_os_mount_tools(os_type, connection_info, event_manager,
1819
ignore_devices, operation_timeout):
1920
os_mount_tools = {constants.OS_TYPE_LINUX: [ubuntu.UbuntuOSMountTools,
20-
redhat.RedHatOSMountTools],
21+
redhat.RedHatOSMountTools,
22+
suse.SUSEOSMountTools],
2123
constants.OS_TYPE_WINDOWS: [windows.WindowsMountTools]}
2224

2325
if os_type and os_type not in os_mount_tools:
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Copyright 2026 Cloudbase Solutions Srl
2+
# All Rights Reserved.
3+
4+
from oslo_log import log as logging
5+
6+
from coriolis import exception
7+
from coriolis.osmorphing.osmount import base
8+
from coriolis import utils
9+
10+
LOG = logging.getLogger(__name__)
11+
12+
SUSE_DISTRO_IDENTIFIERS = [
13+
'sles', 'opensuse-leap', 'opensuse-tumbleweed', 'opensuse']
14+
15+
SSHD_CONFIG_PATH = "/etc/ssh/sshd_config"
16+
USR_SSHD_CONFIG_PATH = "/usr/etc/ssh/sshd_config"
17+
18+
19+
class SUSEOSMountTools(base.BaseLinuxOSMountTools):
20+
def check_os(self):
21+
os_info = utils.get_linux_os_info(self._ssh)
22+
if os_info and os_info[0] in SUSE_DISTRO_IDENTIFIERS:
23+
return True
24+
25+
def _allow_ssh_env_vars(self):
26+
if not utils.test_ssh_path(self._ssh, SSHD_CONFIG_PATH):
27+
self._exec_cmd(
28+
"sudo cp %s %s" % (USR_SSHD_CONFIG_PATH, SSHD_CONFIG_PATH))
29+
self._exec_cmd(
30+
'sudo sed -i -e "\\$aAcceptEnv *" %s' % SSHD_CONFIG_PATH)
31+
try:
32+
utils.restart_service(self._ssh, "sshd")
33+
except exception.CoriolisException:
34+
LOG.warning(
35+
"Could not restart sshd service. The SSH connection "
36+
"may have been reset during the restart.")
37+
return True
38+
39+
def setup(self):
40+
super(SUSEOSMountTools, self).setup()
41+
retry_ssh_cmd = utils.retry_on_error(
42+
max_attempts=10, sleep_seconds=30)(self._exec_cmd)
43+
retry_ssh_cmd("sudo -E zypper --non-interactive install lvm2 psmisc")
44+
self._exec_cmd("sudo modprobe dm-mod")
45+
self._exec_cmd("sudo rm -f /etc/lvm/devices/system.devices")

coriolis/tests/osmorphing/osmount/test_factory.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@ def test_get_os_mount_tools_unsupported_os_type(self):
2525
return_value=False)
2626
@mock.patch.object(factory.redhat.RedHatOSMountTools, 'check_os',
2727
return_value=False)
28+
@mock.patch.object(factory.suse.SUSEOSMountTools, 'check_os',
29+
return_value=False)
2830
@mock.patch.object(factory.windows.WindowsMountTools, 'check_os',
2931
return_value=False)
3032
def test_get_os_mount_tools_no_os_found(
31-
self, mock_windows_check, mock_redhat_check, mock_ubuntu_check,
32-
mock_exec_cmd, mock_connect):
33+
self, mock_windows_check, mock_suse_check, mock_redhat_check,
34+
mock_ubuntu_check, mock_exec_cmd, mock_connect):
3335
mock_exec_cmd.return_value = ("Ubuntu", "")
3436
self.assertRaises(
3537
exception.CoriolisException, factory.get_os_mount_tools,
@@ -39,6 +41,7 @@ def test_get_os_mount_tools_no_os_found(
3941

4042
mock_redhat_check.assert_called_once_with()
4143
mock_ubuntu_check.assert_called_once_with()
44+
mock_suse_check.assert_called_once_with()
4245
mock_windows_check.assert_not_called()
4346

4447
@mock.patch.object(base.BaseSSHOSMountTools, '_connect')
@@ -47,11 +50,13 @@ def test_get_os_mount_tools_no_os_found(
4750
return_value=True)
4851
@mock.patch.object(factory.redhat.RedHatOSMountTools, 'check_os',
4952
return_value=False)
53+
@mock.patch.object(factory.suse.SUSEOSMountTools, 'check_os',
54+
return_value=False)
5055
@mock.patch.object(factory.windows.WindowsMountTools, 'check_os',
5156
return_value=False)
5257
def test_get_os_mount_tools_os_found(
53-
self, mock_windows_check, mock_redhat_check, mock_ubuntu_check,
54-
mock_exec_cmd, mock_connect):
58+
self, mock_windows_check, mock_suse_check, mock_redhat_check,
59+
mock_ubuntu_check, mock_exec_cmd, mock_connect):
5560
mock_exec_cmd.return_value = ("Ubuntu", "")
5661
tools = factory.get_os_mount_tools(
5762
factory.constants.OS_TYPE_LINUX, mock_connect,
@@ -61,4 +66,5 @@ def test_get_os_mount_tools_os_found(
6166

6267
mock_ubuntu_check.assert_called_once_with()
6368
mock_redhat_check.assert_not_called()
69+
mock_suse_check.assert_not_called()
6470
mock_windows_check.assert_not_called()
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Copyright 2026 Cloudbase Solutions Srl
2+
# All Rights Reserved.
3+
4+
from unittest import mock
5+
6+
from coriolis.osmorphing.osmount import suse
7+
from coriolis.tests import test_base
8+
9+
10+
class BaseSUSEOSMountToolsTestCase(test_base.CoriolisBaseTestCase):
11+
"""Test suite for the SUSEOSMountTools class."""
12+
13+
@mock.patch.object(suse.base.BaseSSHOSMountTools, '_connect')
14+
def setUp(self, mock_connect):
15+
super(BaseSUSEOSMountToolsTestCase, self).setUp()
16+
self.ssh = mock.MagicMock()
17+
18+
self.tools = suse.SUSEOSMountTools(
19+
self.ssh, mock.sentinel.event_manager,
20+
mock.sentinel.ignore_devices,
21+
mock.sentinel.operation_timeout)
22+
23+
mock_connect.assert_called_once_with()
24+
25+
self.tools._ssh = self.ssh
26+
27+
@mock.patch.object(suse.utils, 'get_linux_os_info')
28+
def test_check_os(self, mock_get_linux_os_info):
29+
mock_get_linux_os_info.return_value = ['sles']
30+
31+
result = self.tools.check_os()
32+
self.assertTrue(result)
33+
34+
@mock.patch.object(suse.utils, 'get_linux_os_info')
35+
def test_check_os_opensuse_leap(self, mock_get_linux_os_info):
36+
mock_get_linux_os_info.return_value = ['opensuse-leap']
37+
38+
result = self.tools.check_os()
39+
self.assertTrue(result)
40+
41+
@mock.patch.object(suse.utils, 'get_linux_os_info')
42+
def test_check_os_opensuse_tumbleweed(self, mock_get_linux_os_info):
43+
mock_get_linux_os_info.return_value = ['opensuse-tumbleweed']
44+
45+
result = self.tools.check_os()
46+
self.assertTrue(result)
47+
48+
@mock.patch.object(suse.utils, 'get_linux_os_info')
49+
def test_check_os_not_suse(self, mock_get_linux_os_info):
50+
mock_get_linux_os_info.return_value = ['ubuntu']
51+
52+
result = self.tools.check_os()
53+
self.assertIsNone(result)
54+
55+
@mock.patch.object(suse.utils, 'retry_on_error')
56+
@mock.patch.object(suse.base.BaseSSHOSMountTools, '_exec_cmd')
57+
@mock.patch.object(suse.base.BaseSSHOSMountTools, 'setup')
58+
def test_setup(self, mock_setup, mock_exec_cmd, mock_retry_on_error):
59+
mock_retry_on_error.return_value = lambda f: f
60+
result = self.tools.setup()
61+
self.assertIsNone(result)
62+
63+
mock_setup.assert_called_once_with()
64+
mock_retry_on_error.assert_called_once_with(
65+
max_attempts=10, sleep_seconds=30)
66+
mock_exec_cmd.assert_has_calls([
67+
mock.call(
68+
"sudo -E zypper --non-interactive install lvm2 psmisc"),
69+
mock.call("sudo modprobe dm-mod"),
70+
mock.call("sudo rm -f /etc/lvm/devices/system.devices")
71+
])
72+
73+
@mock.patch.object(suse.base.BaseSSHOSMountTools, '_exec_cmd')
74+
@mock.patch.object(suse.utils, 'restart_service')
75+
@mock.patch.object(suse.utils, 'test_ssh_path', return_value=True)
76+
def test__allow_ssh_env_vars(
77+
self, mock_test_ssh_path, mock_restart_service, mock_exec_cmd):
78+
result = self.tools._allow_ssh_env_vars()
79+
self.assertTrue(result)
80+
81+
mock_test_ssh_path.assert_called_once_with(
82+
self.ssh, suse.SSHD_CONFIG_PATH)
83+
mock_exec_cmd.assert_called_once_with(
84+
'sudo sed -i -e "\\$aAcceptEnv *" %s' % suse.SSHD_CONFIG_PATH)
85+
mock_restart_service.assert_called_once_with(self.ssh, "sshd")
86+
87+
@mock.patch.object(suse.base.BaseSSHOSMountTools, '_exec_cmd')
88+
@mock.patch.object(suse.utils, 'restart_service')
89+
@mock.patch.object(suse.utils, 'test_ssh_path', return_value=False)
90+
def test__allow_ssh_env_vars_usr_etc(
91+
self, mock_test_ssh_path, mock_restart_service, mock_exec_cmd):
92+
result = self.tools._allow_ssh_env_vars()
93+
self.assertTrue(result)
94+
95+
mock_test_ssh_path.assert_called_once_with(
96+
self.ssh, suse.SSHD_CONFIG_PATH)
97+
mock_exec_cmd.assert_has_calls([
98+
mock.call(
99+
"sudo cp %s %s" % (
100+
suse.USR_SSHD_CONFIG_PATH, suse.SSHD_CONFIG_PATH)),
101+
mock.call(
102+
'sudo sed -i -e "\\$aAcceptEnv *" %s' %
103+
suse.SSHD_CONFIG_PATH)])
104+
mock_restart_service.assert_called_once_with(self.ssh, "sshd")

0 commit comments

Comments
 (0)