Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions coriolis/osmorphing/osmount/windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from oslo_log import log as logging

from coriolis import constants, exception, utils, wsman
from coriolis import constants, exception, utils, windows_conn
from coriolis.osmorphing.osmount import base

LOG = logging.getLogger(__name__)
Expand All @@ -22,14 +22,19 @@ def __init__(self, *args, **kwargs):

def _connect(self):
connection_info = self._connection_info

host = connection_info["ip"]
port = connection_info.get("port", 5986)
self._event_manager.progress_update(
"Connecting to WinRM host: %(host)s:%(port)s" % {"host": host, "port": port}
)
if windows_conn.uses_winrm(connection_info):
port = windows_conn.connection_port(connection_info)
self._event_manager.progress_update(
"Connecting to WinRM host: %(host)s:%(port)s"
% {"host": host, "port": port}
)
else:
self._event_manager.progress_update(
"Connecting through SSH to OSMorphing host on: %s" % host
)

self._conn = wsman.WSManConnection.from_connection_info(
self._conn = windows_conn.from_connection_info(
connection_info, self._osmount_operation_timeout
)

Expand Down
52 changes: 34 additions & 18 deletions coriolis/providers/provider_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import requests
from oslo_log import log as logging

from coriolis import constants, exception, utils, wsman
from coriolis import constants, exception, utils, windows_conn, wsman

LOG = logging.getLogger(__name__)

Expand Down Expand Up @@ -170,15 +170,19 @@ def _poll_instance_until_reachable_ssh(
timeout: int = 600,
poll_interval: int = 10,
):
if not connection_info.get("password") and not connection_info.get("pkey"):
raise exception.InvalidInput(
"SSH connection info must include password or pkey."
)
start = time.time()
while (time.time() - start) < timeout:
try:
ssh = utils.connect_ssh(
hostname=connection_info["ip"],
port=connection_info["port"],
username=connection_info["username"],
password=connection_info["password"],
pkey=connection_info["pkey"],
password=connection_info.get("password"),
pkey=connection_info.get("pkey"),
)
try:
# "exit 0" should work across platforms.
Expand Down Expand Up @@ -225,9 +229,17 @@ def _poll_instance_until_reachable_winrm(
)


def _protocol_from_connection_info(connection_info, protocol):
if protocol:
return protocol
if windows_conn.uses_winrm(connection_info):
return constants.PROTOCOL_WINRM
return constants.PROTOCOL_SSH


def poll_instance_until_reachable(
connection_info: dict,
protocol: str = constants.PROTOCOL_SSH,
protocol: str = None,
timeout: int = 600,
poll_interval: int = 10,
) -> paramiko.SSHClient:
Expand All @@ -239,22 +251,26 @@ def poll_instance_until_reachable(
* username
* password
* pkey - Paramiko keypair
:param protocol: connection protocol, "ssh" or "winrm"
:param protocol: connection protocol, "ssh" or "winrm". If omitted,
[windows_minion] protocol selects SSH or WinRM. The port does not
select the protocol.
:param timeout: the maximum amount of time to wait
:param poll_interval: the amount of time to wait between retries
"""
# TODO(lpetrut): consider including the connection protocol in the
# connection info. We'd have to modify a few schemas used during os
# morphing. We currently pick the protocol based on the OS type but
# we may want to use SSH on Windows as well.
if protocol == constants.PROTOCOL_SSH:
helper = _poll_instance_until_reachable_ssh
elif protocol == constants.PROTOCOL_WINRM:
helper = _poll_instance_until_reachable_winrm
else:
raise exception.InvalidInput(
f"Unsupported instance connection protocol: {protocol}"
resolved = _protocol_from_connection_info(connection_info, protocol)
connection_info = windows_conn.with_provider_port(connection_info)
if resolved == constants.PROTOCOL_SSH:
return _poll_instance_until_reachable_ssh(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you give up on the helper var?

connection_info=connection_info,
timeout=timeout,
poll_interval=poll_interval,
)
if resolved == constants.PROTOCOL_WINRM:
return _poll_instance_until_reachable_winrm(
connection_info=connection_info,
timeout=timeout,
poll_interval=poll_interval,
)
return helper(
connection_info=connection_info, timeout=timeout, poll_interval=poll_interval
raise exception.InvalidInput(
f"Unsupported instance connection protocol: {resolved}"
)
8 changes: 4 additions & 4 deletions coriolis/tests/osmorphing/osmount/test_windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ class CoriolisTestException(Exception):
class WindowsMountToolsTestCase(test_base.CoriolisBaseTestCase):
"""Test suite for the WindowsMountTools class."""

@mock.patch.object(windows.wsman, 'WSManConnection')
def setUp(self, mock_wsman_connection):
@mock.patch.object(windows.windows_conn, 'from_connection_info')
def setUp(self, mock_from_connection_info):
super(WindowsMountToolsTestCase, self).setUp()
self.event_manager = mock.MagicMock()
self.ssh = mock.MagicMock()
Expand All @@ -43,9 +43,9 @@ def setUp(self, mock_wsman_connection):
mock.sentinel.ignore_devices,
mock.sentinel.operation_timeout,
)
self.tools._conn = mock_wsman_connection
self.tools._conn = mock.MagicMock()

@mock.patch.object(windows.wsman.WSManConnection, 'from_connection_info')
@mock.patch.object(windows.windows_conn, 'from_connection_info')
def test__connect(self, mock_from_connection_info):
result = self.tools._connect()
self.assertIsNone(result)
Expand Down
24 changes: 24 additions & 0 deletions coriolis/tests/providers/test_provider_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,21 @@ def test_poll_instance_ssh_timeout(
poll_interval=poll_interval,
)

def test_poll_instance_missing_auth(self):
connection_info = {
"ip": "1.2.3.4",
"port": 22,
"username": "Administrator",
}
self.assertRaises(
exception.InvalidInput,
provider_utils.poll_instance_until_reachable,
connection_info=connection_info,
protocol=constants.PROTOCOL_SSH,
timeout=600,
poll_interval=5,
)

@mock.patch("coriolis.wsman.WSManConnection", new_callable=mock.Mock)
@mock.patch("time.sleep")
def test_poll_instance_winrm(
Expand Down Expand Up @@ -400,3 +415,12 @@ def test_poll_instance_winrm_timeout(
timeout=30,
poll_interval=poll_interval,
)

def test_poll_instance_unsupported_protocol(self):
connection_info = self._get_mock_conn_info()
self.assertRaises(
exception.InvalidInput,
provider_utils.poll_instance_until_reachable,
connection_info=connection_info,
protocol="ftp",
)
165 changes: 165 additions & 0 deletions coriolis/tests/test_windows_conn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# Copyright 2026 Cloudbase Solutions Srl
# All Rights Reserved.

from unittest import mock

from coriolis import windows_conn, windows_ssh, wsman
from coriolis.tests import test_base


class WindowsConnTestCase(test_base.CoriolisBaseTestCase):
def test_uses_winrm_unset_protocol_follows_the_provider_port(self):
windows_conn.CONF.clear_override("protocol", group="windows_minion")
self.assertTrue(windows_conn.uses_winrm({"ip": "10.0.0.1"}))
self.assertTrue(windows_conn.uses_winrm({"ip": "10.0.0.1", "port": 5986}))
self.assertTrue(
windows_conn.uses_winrm(
{"ip": "10.0.0.1", "port": "5986", "protocol": "ssh"}
)
)
self.assertTrue(windows_conn.uses_winrm(None))
self.assertFalse(windows_conn.uses_winrm({"ip": "10.0.0.1", "port": 22}))
self.assertFalse(windows_conn.uses_winrm({"ip": "10.0.0.1", "port": 2222}))

def test_uses_winrm_explicit_ssh_ignores_port_5986(self):
windows_conn.CONF.set_override("protocol", "ssh", group="windows_minion")
self.addCleanup(
windows_conn.CONF.clear_override, "protocol", group="windows_minion"
)
self.assertFalse(windows_conn.uses_winrm({"ip": "10.0.0.1", "port": 5986}))
self.assertFalse(windows_conn.uses_winrm(None))

def test_uses_winrm_core_winrm_overrides_connection_info(self):
windows_conn.CONF.set_override("protocol", "winrm", group="windows_minion")
self.addCleanup(
windows_conn.CONF.clear_override, "protocol", group="windows_minion"
)
self.assertTrue(windows_conn.uses_winrm({"ip": "10.0.0.1", "port": 22}))
self.assertTrue(windows_conn.uses_winrm({"port": 5986, "protocol": "ssh"}))
self.assertTrue(windows_conn.uses_winrm(None))

@mock.patch.object(wsman.WSManConnection, "from_connection_info")
@mock.patch.object(windows_ssh.WindowsSSHConnection, "from_connection_info")
def test_from_connection_info_winrm(self, mock_ssh, mock_winrm):
windows_conn.CONF.set_override("protocol", "winrm", group="windows_minion")
self.addCleanup(
windows_conn.CONF.clear_override, "protocol", group="windows_minion"
)
conn_info = {
"ip": "10.0.0.1",
"port": 5986,
"protocol": "ssh",
"username": "u",
"password": "p",
}
windows_conn.from_connection_info(conn_info, timeout=30)
mock_winrm.assert_called_once_with(conn_info, 30)
mock_ssh.assert_not_called()

@mock.patch.object(wsman.WSManConnection, "from_connection_info")
@mock.patch.object(windows_ssh.WindowsSSHConnection, "from_connection_info")
def test_from_connection_info_winrm_on_port_22(self, mock_ssh, mock_winrm):
windows_conn.CONF.set_override("protocol", "winrm", group="windows_minion")
self.addCleanup(
windows_conn.CONF.clear_override, "protocol", group="windows_minion"
)
conn_info = {"ip": "10.0.0.1", "port": 22, "username": "u", "password": "p"}
windows_conn.from_connection_info(conn_info, timeout=30)
mock_winrm.assert_called_once_with(conn_info, 30)
mock_ssh.assert_not_called()

@mock.patch.object(wsman.WSManConnection, "from_connection_info")
@mock.patch.object(windows_ssh.WindowsSSHConnection, "from_connection_info")
def test_from_connection_info_port_5986_uses_winrm(self, mock_ssh, mock_winrm):
windows_conn.CONF.clear_override("protocol", group="windows_minion")
conn_info = {"ip": "10.0.0.1", "port": 5986, "username": "u", "password": "p"}
windows_conn.from_connection_info(conn_info, timeout=30)
mock_winrm.assert_called_once_with(conn_info, 30)
mock_ssh.assert_not_called()

@mock.patch.object(wsman.WSManConnection, "from_connection_info")
@mock.patch.object(windows_ssh.WindowsSSHConnection, "from_connection_info")
def test_from_connection_info_ssh(self, mock_ssh, mock_winrm):
conn_info = {
"ip": "10.0.0.1",
"port": 2222,
"protocol": "ssh",
"username": "u",
"password": "p",
}
windows_conn.from_connection_info(conn_info, timeout=30)
mock_ssh.assert_called_once_with(conn_info, 30)
mock_winrm.assert_not_called()

def test_minion_connection_default_is_ssh(self):
windows_conn.CONF.clear_override("protocol", group="windows_minion")
windows_conn.CONF.clear_override("port", group="windows_minion")
self.assertEqual("ssh", windows_conn.minion_connection())
self.assertTrue(windows_conn.minion_uses_ssh())
self.assertEqual(22, windows_conn.minion_port())

def test_minion_protocol_and_port_are_independent(self):
windows_conn.CONF.set_override("protocol", "ssh", group="windows_minion")
windows_conn.CONF.set_override("port", 2222, group="windows_minion")
self.addCleanup(
windows_conn.CONF.clear_override, "protocol", group="windows_minion"
)
self.addCleanup(
windows_conn.CONF.clear_override, "port", group="windows_minion"
)
self.assertEqual("ssh", windows_conn.minion_connection())
self.assertTrue(windows_conn.minion_uses_ssh())
self.assertEqual(2222, windows_conn.minion_port())
self.assertFalse(windows_conn.uses_winrm({"port": 2222, "protocol": "winrm"}))

def test_minion_winrm_custom_port(self):
windows_conn.CONF.set_override("protocol", "winrm", group="windows_minion")
windows_conn.CONF.set_override("port", 5987, group="windows_minion")
self.addCleanup(
windows_conn.CONF.clear_override, "protocol", group="windows_minion"
)
self.addCleanup(
windows_conn.CONF.clear_override, "port", group="windows_minion"
)
self.assertEqual("winrm", windows_conn.minion_connection())
self.assertFalse(windows_conn.minion_uses_ssh())
self.assertEqual(5987, windows_conn.minion_port())
self.assertTrue(windows_conn.uses_winrm({"port": 22, "protocol": "ssh"}))

def test_minion_winrm_uses_5986_when_port_is_unset(self):
windows_conn.CONF.set_override("protocol", "winrm", group="windows_minion")
windows_conn.CONF.clear_override("port", group="windows_minion")
self.addCleanup(
windows_conn.CONF.clear_override, "protocol", group="windows_minion"
)
self.assertEqual(5986, windows_conn.minion_port())

def test_minion_winrm_keeps_explicit_port_22(self):
windows_conn.CONF.set_override("protocol", "winrm", group="windows_minion")
windows_conn.CONF.set_override("port", 22, group="windows_minion")
self.addCleanup(
windows_conn.CONF.clear_override, "protocol", group="windows_minion"
)
self.addCleanup(
windows_conn.CONF.clear_override, "port", group="windows_minion"
)
self.assertEqual(22, windows_conn.minion_port())

def test_connection_port_provider_wins(self):
windows_conn.CONF.set_override("port", 2222, group="windows_minion")
self.addCleanup(
windows_conn.CONF.clear_override, "port", group="windows_minion"
)
info = {"ip": "10.0.0.1", "port": 5986}
self.assertEqual(5986, windows_conn.connection_port(info))
self.assertIs(info, windows_conn.with_provider_port(info))

def test_connection_port_uses_core_option(self):
windows_conn.CONF.set_override("port", 2222, group="windows_minion")
self.addCleanup(
windows_conn.CONF.clear_override, "port", group="windows_minion"
)
self.assertEqual(2222, windows_conn.connection_port({"ip": "10.0.0.1"}))
self.assertEqual(2222, windows_conn.connection_port(None))
filled = windows_conn.with_provider_port({"ip": "10.0.0.1"})
self.assertEqual(2222, filled["port"])
Loading
Loading