From 7d92647917ef6022de33bdbe37f5bc17defe121d Mon Sep 17 00:00:00 2001 From: Alberto Contreras Date: Mon, 2 May 2022 14:07:56 +0200 Subject: [PATCH 1/5] Handle error if SSH service no present. - Hanlde the case when user-data provides ssh_pwauth and the open-ssh service is not available - Add openssh-server as suggested package for debian-based distros --- cloudinit/config/cc_set_passwords.py | 11 +++++++--- packages/debian/control.in | 2 +- .../unittests/config/test_cc_set_passwords.py | 22 ++++++++++++++++++- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/cloudinit/config/cc_set_passwords.py b/cloudinit/config/cc_set_passwords.py index 939e773bab8..bbff63d900e 100644 --- a/cloudinit/config/cc_set_passwords.py +++ b/cloudinit/config/cc_set_passwords.py @@ -112,8 +112,13 @@ def handle_ssh_pwauth(pw_auth, distro): LOG.debug("No need to restart SSH service, %s not updated.", cfg_name) return - distro.manage_service("restart", distro.get_option("ssh_svcname", "ssh")) - LOG.debug("Restarted the SSH daemon.") + service = distro.get_option("ssh_svcname", "ssh") + try: + distro.manage_service("restart", service) + except subp.ProcessExecutionError as e: + LOG.warning("Failed to restart the SSH deamon. %s: %s", service, e) + else: + LOG.debug("Restarted the SSH daemon.") def handle(_name, cfg, cloud, log, args): @@ -226,7 +231,7 @@ def handle(_name, cfg, cloud, log, args): handle_ssh_pwauth(cfg.get("ssh_pwauth"), cloud.distro) if len(errors): - log.debug("%s errors occured, re-raising the last one", len(errors)) + log.debug("%s errors occurred, re-raising the last one", len(errors)) raise errors[-1] diff --git a/packages/debian/control.in b/packages/debian/control.in index 7cded051134..5bb915a9197 100644 --- a/packages/debian/control.in +++ b/packages/debian/control.in @@ -14,7 +14,7 @@ Depends: ${misc:Depends}, iproute2, isc-dhcp-client Recommends: eatmydata, sudo, software-properties-common, gdisk -Suggests: ssh-import-id +Suggests: ssh-import-id, openssh-server Description: Init scripts for cloud instances Cloud instances need special scripts to run during initialisation to retrieve and install ssh keys and to let the user run various scripts. diff --git a/tests/unittests/config/test_cc_set_passwords.py b/tests/unittests/config/test_cc_set_passwords.py index 94c1e8cc3a8..5139bb1a25c 100644 --- a/tests/unittests/config/test_cc_set_passwords.py +++ b/tests/unittests/config/test_cc_set_passwords.py @@ -4,7 +4,7 @@ import pytest -from cloudinit import util +from cloudinit import subp, util from cloudinit.config import cc_set_passwords as setpass from cloudinit.config.schema import ( SchemaValidationError, @@ -40,6 +40,7 @@ def test_systemctl_as_service_cmd(self, m_subp, m_update_ssh_config): m_subp.assert_called_with( ["systemctl", "restart", "ssh"], capture=True ) + self.assertIn("DEBUG: Restarted the SSH daemon.", self.logs.getvalue()) @mock.patch(MODPATH + "update_ssh_config", return_value=False) @mock.patch("cloudinit.distros.subp.subp") @@ -72,6 +73,25 @@ def test_valid_change_values(self, m_subp): m_update.assert_called_with({optname: optval}) m_subp.assert_not_called() + @mock.patch(MODPATH + "update_ssh_config", return_value=True) + @mock.patch("cloudinit.distros.subp.subp") + def test_unchanged_does_nothing_TODO_RENAME( + self, m_subp, m_update_ssh_config + ): + """If 'unchanged', then no updates to config and no restart.""" + cloud = self.tmp_cloud(distro="ubuntu") + cloud.distro.init_cmd = ["systemctl"] + process_error = "Unexpected error while running command." + cloud.distro.manage_service = mock.Mock( + side_effect=subp.ProcessExecutionError(process_error) + ) + setpass.handle_ssh_pwauth(True, cloud.distro) + expected_warning = ( + f"WARNING: Failed to restart the SSH deamon. ssh: {process_error}" + ) + self.assertIn(expected_warning, self.logs.getvalue()) + cloud.distro.manage_service.assert_called_with("restart", "ssh") + class TestSetPasswordsHandle(CiTestCase): """Test cc_set_passwords.handle""" From 506e867460b29862d052a1501914a012fbda6374 Mon Sep 17 00:00:00 2001 From: Alberto Contreras Date: Fri, 6 May 2022 10:49:07 +0200 Subject: [PATCH 2/5] Pre-flight check using service status. --- cloudinit/config/cc_set_passwords.py | 29 ++++++---- cloudinit/distros/__init__.py | 4 +- .../unittests/config/test_cc_set_passwords.py | 54 ++++++++++++++----- 3 files changed, 64 insertions(+), 23 deletions(-) diff --git a/cloudinit/config/cc_set_passwords.py b/cloudinit/config/cc_set_passwords.py index bbff63d900e..9f7b6c1acfd 100644 --- a/cloudinit/config/cc_set_passwords.py +++ b/cloudinit/config/cc_set_passwords.py @@ -14,7 +14,7 @@ from cloudinit import log as logging from cloudinit import subp, util from cloudinit.config.schema import MetaSchema, get_meta_doc -from cloudinit.distros import ALL_DISTROS, ug_util +from cloudinit.distros import ALL_DISTROS, Distro, ug_util from cloudinit.settings import PER_INSTANCE from cloudinit.ssh_util import update_ssh_config @@ -79,7 +79,7 @@ PW_SET = "".join([x for x in ascii_letters + digits if x not in "loLOI01"]) -def handle_ssh_pwauth(pw_auth, distro): +def handle_ssh_pwauth(pw_auth, distro: Distro): """Apply sshd PasswordAuthentication changes. @param pw_auth: config setting from 'pw_auth'. @@ -87,6 +87,22 @@ def handle_ssh_pwauth(pw_auth, distro): @param distro: an instance of the distro class for the target distribution @return: None""" + service = distro.get_option("ssh_svcname", "ssh") + try: + distro.manage_service("status", service) + except subp.ProcessExecutionError as e: + LOG.warning( + "Ignoring config 'ssh_pwauth: %s'. Service '%s' is not running.", + pw_auth, + service, + ) + LOG.debug( + "Service '%s' is not running: %s", + service, + e, + ) + return + cfg_name = "PasswordAuthentication" if isinstance(pw_auth, str): @@ -112,13 +128,8 @@ def handle_ssh_pwauth(pw_auth, distro): LOG.debug("No need to restart SSH service, %s not updated.", cfg_name) return - service = distro.get_option("ssh_svcname", "ssh") - try: - distro.manage_service("restart", service) - except subp.ProcessExecutionError as e: - LOG.warning("Failed to restart the SSH deamon. %s: %s", service, e) - else: - LOG.debug("Restarted the SSH daemon.") + distro.manage_service("restart", service) + LOG.debug("Restarted the SSH daemon.") def handle(_name, cfg, cloud, log, args): diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py index b97ee3ab4e9..b034e2c848d 100644 --- a/cloudinit/distros/__init__.py +++ b/cloudinit/distros/__init__.py @@ -850,7 +850,7 @@ def shutdown_command(self, *, mode, delay, message): args.append(message) return args - def manage_service(self, action, service): + def manage_service(self, action: str, service: str): """ Perform the requested action on a service. This handles the common 'systemctl' and 'service' cases and may be overridden in subclasses @@ -867,6 +867,7 @@ def manage_service(self, action, service): "restart": ["restart", service], "reload": ["reload-or-restart", service], "try-reload": ["reload-or-try-restart", service], + "status": ["status", service], } else: cmds = { @@ -876,6 +877,7 @@ def manage_service(self, action, service): "restart": [service, "restart"], "reload": [service, "restart"], "try-reload": [service, "restart"], + "status": [service, "status"], } cmd = list(init_cmd) + list(cmds[action]) return subp.subp(cmd, capture=True) diff --git a/tests/unittests/config/test_cc_set_passwords.py b/tests/unittests/config/test_cc_set_passwords.py index 5139bb1a25c..7e099e516bd 100644 --- a/tests/unittests/config/test_cc_set_passwords.py +++ b/tests/unittests/config/test_cc_set_passwords.py @@ -28,7 +28,9 @@ def test_unknown_value_logs_warning(self, m_subp): self.assertIn( "Unrecognized value: ssh_pwauth=floo", self.logs.getvalue() ) - m_subp.assert_not_called() + m_subp.assert_called_once_with( + ["systemctl", "status", "ssh"], capture=True + ) @mock.patch(MODPATH + "update_ssh_config", return_value=True) @mock.patch("cloudinit.distros.subp.subp") @@ -48,7 +50,10 @@ def test_not_restarted_if_not_updated(self, m_subp, m_update_ssh_config): """If config is not updated, then no system restart should be done.""" cloud = self.tmp_cloud(distro="ubuntu") setpass.handle_ssh_pwauth(True, cloud.distro) - m_subp.assert_not_called() + m_subp.assert_called_once_with( + ["systemctl", "status", "ssh"], + capture=True, + ) self.assertIn("No need to restart SSH", self.logs.getvalue()) @mock.patch(MODPATH + "update_ssh_config", return_value=True) @@ -58,7 +63,9 @@ def test_unchanged_does_nothing(self, m_subp, m_update_ssh_config): cloud = self.tmp_cloud(distro="ubuntu") setpass.handle_ssh_pwauth("unchanged", cloud.distro) m_update_ssh_config.assert_not_called() - m_subp.assert_not_called() + m_subp.assert_called_once_with( + ["systemctl", "status", "ssh"], capture=True + ) @mock.patch("cloudinit.distros.subp.subp") def test_valid_change_values(self, m_subp): @@ -66,19 +73,25 @@ def test_valid_change_values(self, m_subp): cloud = self.tmp_cloud(distro="ubuntu") upname = MODPATH + "update_ssh_config" optname = "PasswordAuthentication" - for value in util.FALSE_STRINGS + util.TRUE_STRINGS: + for n, value in enumerate(util.FALSE_STRINGS + util.TRUE_STRINGS, 1): optval = "yes" if value in util.TRUE_STRINGS else "no" with mock.patch(upname, return_value=False) as m_update: setpass.handle_ssh_pwauth(value, cloud.distro) m_update.assert_called_with({optname: optval}) - m_subp.assert_not_called() + m_subp.call_count = n + m_subp.assert_called_with( + ["systemctl", "status", "ssh"], + capture=True, + ) @mock.patch(MODPATH + "update_ssh_config", return_value=True) @mock.patch("cloudinit.distros.subp.subp") - def test_unchanged_does_nothing_TODO_RENAME( + def test_failed_ssh_service_does_nothing( self, m_subp, m_update_ssh_config ): - """If 'unchanged', then no updates to config and no restart.""" + """If the ssh service is not running, then no updates to config and + no restart. + """ cloud = self.tmp_cloud(distro="ubuntu") cloud.distro.init_cmd = ["systemctl"] process_error = "Unexpected error while running command." @@ -86,11 +99,20 @@ def test_unchanged_does_nothing_TODO_RENAME( side_effect=subp.ProcessExecutionError(process_error) ) setpass.handle_ssh_pwauth(True, cloud.distro) - expected_warning = ( - f"WARNING: Failed to restart the SSH deamon. ssh: {process_error}" + self.assertIn( + ( + r"WARNING: Ignoring config 'ssh_pwauth: True'. Service 'ssh' " + r"is not running." + ), + self.logs.getvalue(), + ) + self.assertIn( + rf"DEBUG: Service 'ssh' is not running: {process_error}", + self.logs.getvalue(), ) - self.assertIn(expected_warning, self.logs.getvalue()) - cloud.distro.manage_service.assert_called_with("restart", "ssh") + cloud.distro.manage_service.assert_called_once_with("status", "ssh") + m_update_ssh_config.assert_not_called() + m_subp.assert_not_called() class TestSetPasswordsHandle(CiTestCase): @@ -98,7 +120,8 @@ class TestSetPasswordsHandle(CiTestCase): with_logs = True - def test_handle_on_empty_config(self, *args): + @mock.patch(MODPATH + "subp.subp") + def test_handle_on_empty_config(self, m_subp): """handle logs that no password has changed when config is empty.""" cloud = self.tmp_cloud(distro="ubuntu") setpass.handle( @@ -109,8 +132,12 @@ def test_handle_on_empty_config(self, *args): "ssh_pwauth=None\n", self.logs.getvalue(), ) + m_subp.assert_called_once_with( + ["systemctl", "status", "ssh"], capture=True + ) - def test_handle_on_chpasswd_list_parses_common_hashes(self): + @mock.patch(MODPATH + "subp.subp") + def test_handle_on_chpasswd_list_parses_common_hashes(self, m_subp): """handle parses command password hashes.""" cloud = self.tmp_cloud(distro="ubuntu") valid_hashed_pwds = [ @@ -156,6 +183,7 @@ def test_bsd_calls_custom_pw_cmds_to_set_and_expire_passwords( logstring="chpasswd for ubuntu", ), mock.call(["pw", "usermod", "ubuntu", "-p", "01-Jan-1970"]), + mock.call(["systemctl", "status", "sshd"], capture=True), ], m_subp.call_args_list, ) From 928e83a50e1fb6e9e19eb736ebf9dce47196e61a Mon Sep 17 00:00:00 2001 From: Alberto Contreras Date: Fri, 6 May 2022 11:31:09 +0200 Subject: [PATCH 3/5] Homogenize log msgs. --- cloudinit/config/cc_set_passwords.py | 7 +++++-- tests/unittests/config/test_cc_set_passwords.py | 9 ++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/cloudinit/config/cc_set_passwords.py b/cloudinit/config/cc_set_passwords.py index 9f7b6c1acfd..d952ba4d7ed 100644 --- a/cloudinit/config/cc_set_passwords.py +++ b/cloudinit/config/cc_set_passwords.py @@ -92,12 +92,15 @@ def handle_ssh_pwauth(pw_auth, distro: Distro): distro.manage_service("status", service) except subp.ProcessExecutionError as e: LOG.warning( - "Ignoring config 'ssh_pwauth: %s'. Service '%s' is not running.", + ( + "Ignoring config 'ssh_pwauth: %s'. SSH deamon service '%s' is " + "not running." + ), pw_auth, service, ) LOG.debug( - "Service '%s' is not running: %s", + "SSH deamon service '%s' is not running: %s", service, e, ) diff --git a/tests/unittests/config/test_cc_set_passwords.py b/tests/unittests/config/test_cc_set_passwords.py index 7e099e516bd..dd2d7a2dfee 100644 --- a/tests/unittests/config/test_cc_set_passwords.py +++ b/tests/unittests/config/test_cc_set_passwords.py @@ -101,13 +101,16 @@ def test_failed_ssh_service_does_nothing( setpass.handle_ssh_pwauth(True, cloud.distro) self.assertIn( ( - r"WARNING: Ignoring config 'ssh_pwauth: True'. Service 'ssh' " - r"is not running." + r"WARNING: Ignoring config 'ssh_pwauth: True'. SSH deamon " + r"service 'ssh' is not running." ), self.logs.getvalue(), ) self.assertIn( - rf"DEBUG: Service 'ssh' is not running: {process_error}", + ( + r"DEBUG: SSH deamon service 'ssh' is not running: " + rf"{process_error}" + ), self.logs.getvalue(), ) cloud.distro.manage_service.assert_called_once_with("status", "ssh") From b940f2b2e8ed6aed995e37f05b2ed74133119ddf Mon Sep 17 00:00:00 2001 From: Alberto Contreras Date: Mon, 9 May 2022 09:20:00 +0200 Subject: [PATCH 4/5] Improve test assertions. --- .../unittests/config/test_cc_set_passwords.py | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/tests/unittests/config/test_cc_set_passwords.py b/tests/unittests/config/test_cc_set_passwords.py index dd2d7a2dfee..8c7a70cd794 100644 --- a/tests/unittests/config/test_cc_set_passwords.py +++ b/tests/unittests/config/test_cc_set_passwords.py @@ -28,8 +28,9 @@ def test_unknown_value_logs_warning(self, m_subp): self.assertIn( "Unrecognized value: ssh_pwauth=floo", self.logs.getvalue() ) - m_subp.assert_called_once_with( - ["systemctl", "status", "ssh"], capture=True + self.assertEqual( + [mock.call(["systemctl", "status", "ssh"], capture=True)], + m_subp.call_args_list, ) @mock.patch(MODPATH + "update_ssh_config", return_value=True) @@ -50,9 +51,9 @@ def test_not_restarted_if_not_updated(self, m_subp, m_update_ssh_config): """If config is not updated, then no system restart should be done.""" cloud = self.tmp_cloud(distro="ubuntu") setpass.handle_ssh_pwauth(True, cloud.distro) - m_subp.assert_called_once_with( - ["systemctl", "status", "ssh"], - capture=True, + self.assertEqual( + [mock.call(["systemctl", "status", "ssh"], capture=True)], + m_subp.call_args_list, ) self.assertIn("No need to restart SSH", self.logs.getvalue()) @@ -63,8 +64,10 @@ def test_unchanged_does_nothing(self, m_subp, m_update_ssh_config): cloud = self.tmp_cloud(distro="ubuntu") setpass.handle_ssh_pwauth("unchanged", cloud.distro) m_update_ssh_config.assert_not_called() - m_subp.assert_called_once_with( - ["systemctl", "status", "ssh"], capture=True + self.assertEqual(m_update_ssh_config.call_count, 0) + self.assertEqual( + [mock.call(["systemctl", "status", "ssh"], capture=True)], + m_subp.call_args_list, ) @mock.patch("cloudinit.distros.subp.subp") @@ -77,11 +80,13 @@ def test_valid_change_values(self, m_subp): optval = "yes" if value in util.TRUE_STRINGS else "no" with mock.patch(upname, return_value=False) as m_update: setpass.handle_ssh_pwauth(value, cloud.distro) - m_update.assert_called_with({optname: optval}) - m_subp.call_count = n - m_subp.assert_called_with( - ["systemctl", "status", "ssh"], - capture=True, + self.assertEqual( + mock.call({optname: optval}), m_update.call_args_list[-1] + ) + self.assertEqual(m_subp.call_count, n) + self.assertEqual( + mock.call(["systemctl", "status", "ssh"], capture=True), + m_subp.call_args_list[-1], ) @mock.patch(MODPATH + "update_ssh_config", return_value=True) @@ -114,8 +119,8 @@ def test_failed_ssh_service_does_nothing( self.logs.getvalue(), ) cloud.distro.manage_service.assert_called_once_with("status", "ssh") - m_update_ssh_config.assert_not_called() - m_subp.assert_not_called() + self.assertEqual(m_update_ssh_config.call_count, 0) + self.assertEqual(m_subp.call_count, 0) class TestSetPasswordsHandle(CiTestCase): @@ -135,8 +140,9 @@ def test_handle_on_empty_config(self, m_subp): "ssh_pwauth=None\n", self.logs.getvalue(), ) - m_subp.assert_called_once_with( - ["systemctl", "status", "ssh"], capture=True + self.assertEqual( + [mock.call(["systemctl", "status", "ssh"], capture=True)], + m_subp.call_args_list, ) @mock.patch(MODPATH + "subp.subp") From 9bdf932c3c792ec2978e424d0f237f4827371a59 Mon Sep 17 00:00:00 2001 From: Alberto Contreras Date: Tue, 10 May 2022 13:25:39 +0200 Subject: [PATCH 5/5] React to error codes. --- cloudinit/config/cc_set_passwords.py | 49 +++++++---- .../unittests/config/test_cc_set_passwords.py | 86 ++++++++++++++++--- 2 files changed, 106 insertions(+), 29 deletions(-) diff --git a/cloudinit/config/cc_set_passwords.py b/cloudinit/config/cc_set_passwords.py index d952ba4d7ed..b572b26a9c3 100644 --- a/cloudinit/config/cc_set_passwords.py +++ b/cloudinit/config/cc_set_passwords.py @@ -88,23 +88,37 @@ def handle_ssh_pwauth(pw_auth, distro: Distro): @return: None""" service = distro.get_option("ssh_svcname", "ssh") + restart_ssh = True try: distro.manage_service("status", service) except subp.ProcessExecutionError as e: - LOG.warning( - ( - "Ignoring config 'ssh_pwauth: %s'. SSH deamon service '%s' is " - "not running." - ), - pw_auth, - service, - ) - LOG.debug( - "SSH deamon service '%s' is not running: %s", - service, - e, - ) - return + if e.exit_code == 3: + # Service is not running. Write ssh config. + LOG.warning( + "Writing config 'ssh_pwauth: %s'." + " SSH service '%s' will not be restarted because is stopped.", + pw_auth, + service, + ) + restart_ssh = False + elif e.exit_code == 4: + # Service status is unknown + LOG.warning( + "Ignoring config 'ssh_pwauth: %s'." + " SSH service '%s' is not installed.", + pw_auth, + service, + ) + return + else: + LOG.warning( + "Ignoring config 'ssh_pwauth: %s'." + " SSH service '%s' is not available. Error: %s.", + pw_auth, + service, + e, + ) + return cfg_name = "PasswordAuthentication" @@ -131,8 +145,11 @@ def handle_ssh_pwauth(pw_auth, distro: Distro): LOG.debug("No need to restart SSH service, %s not updated.", cfg_name) return - distro.manage_service("restart", service) - LOG.debug("Restarted the SSH daemon.") + if restart_ssh: + distro.manage_service("restart", service) + LOG.debug("Restarted the SSH daemon.") + else: + LOG.debug("Not restarting SSH service: service is stopped.") def handle(_name, cfg, cloud, log, args): diff --git a/tests/unittests/config/test_cc_set_passwords.py b/tests/unittests/config/test_cc_set_passwords.py index 8c7a70cd794..0b3de83908c 100644 --- a/tests/unittests/config/test_cc_set_passwords.py +++ b/tests/unittests/config/test_cc_set_passwords.py @@ -91,34 +91,94 @@ def test_valid_change_values(self, m_subp): @mock.patch(MODPATH + "update_ssh_config", return_value=True) @mock.patch("cloudinit.distros.subp.subp") - def test_failed_ssh_service_does_nothing( + def test_failed_ssh_service_is_not_runing( self, m_subp, m_update_ssh_config ): - """If the ssh service is not running, then no updates to config and + """If the ssh service is not running, then the config is updated and no restart. """ cloud = self.tmp_cloud(distro="ubuntu") cloud.distro.init_cmd = ["systemctl"] - process_error = "Unexpected error while running command." cloud.distro.manage_service = mock.Mock( - side_effect=subp.ProcessExecutionError(process_error) + side_effect=subp.ProcessExecutionError( + stderr="Service is not running.", exit_code=3 + ) ) + setpass.handle_ssh_pwauth(True, cloud.distro) self.assertIn( - ( - r"WARNING: Ignoring config 'ssh_pwauth: True'. SSH deamon " - r"service 'ssh' is not running." - ), + r"WARNING: Writing config 'ssh_pwauth: True'." + r" SSH service 'ssh' will not be restarted because is stopped.", self.logs.getvalue(), ) self.assertIn( - ( - r"DEBUG: SSH deamon service 'ssh' is not running: " - rf"{process_error}" - ), + r"DEBUG: Not restarting SSH service: service is stopped.", self.logs.getvalue(), ) - cloud.distro.manage_service.assert_called_once_with("status", "ssh") + self.assertEqual( + [mock.call("status", "ssh")], + cloud.distro.manage_service.call_args_list, + ) + self.assertEqual(m_update_ssh_config.call_count, 1) + self.assertEqual(m_subp.call_count, 0) + + @mock.patch(MODPATH + "update_ssh_config", return_value=True) + @mock.patch("cloudinit.distros.subp.subp") + def test_failed_ssh_service_is_not_installed( + self, m_subp, m_update_ssh_config + ): + """If the ssh service is not installed, then no updates config and + no restart. + """ + cloud = self.tmp_cloud(distro="ubuntu") + cloud.distro.init_cmd = ["systemctl"] + cloud.distro.manage_service = mock.Mock( + side_effect=subp.ProcessExecutionError( + stderr="Service is not installed.", exit_code=4 + ) + ) + + setpass.handle_ssh_pwauth(True, cloud.distro) + self.assertIn( + r"WARNING: Ignoring config 'ssh_pwauth: True'." + r" SSH service 'ssh' is not installed.", + self.logs.getvalue(), + ) + self.assertEqual( + [mock.call("status", "ssh")], + cloud.distro.manage_service.call_args_list, + ) + self.assertEqual(m_update_ssh_config.call_count, 0) + self.assertEqual(m_subp.call_count, 0) + + @mock.patch(MODPATH + "update_ssh_config", return_value=True) + @mock.patch("cloudinit.distros.subp.subp") + def test_failed_ssh_service_is_not_available( + self, m_subp, m_update_ssh_config + ): + """If the ssh service is not available, then no updates config and + no restart. + """ + cloud = self.tmp_cloud(distro="ubuntu") + cloud.distro.init_cmd = ["systemctl"] + process_error = "Service is not available." + cloud.distro.manage_service = mock.Mock( + side_effect=subp.ProcessExecutionError( + stderr=process_error, exit_code=2 + ) + ) + + setpass.handle_ssh_pwauth(True, cloud.distro) + self.assertIn( + r"WARNING: Ignoring config 'ssh_pwauth: True'." + r" SSH service 'ssh' is not available. Error: ", + self.logs.getvalue(), + ) + self.assertIn(process_error, self.logs.getvalue()) + self.assertEqual( + [mock.call("status", "ssh")], + cloud.distro.manage_service.call_args_list, + ) self.assertEqual(m_update_ssh_config.call_count, 0) self.assertEqual(m_subp.call_count, 0)