66import pytest
77
88from cloudinit .config import cc_ssh_import_id
9+ from cloudinit .subp import ProcessExecutionError
910from tests .unittests .util import get_cloud
1011
1112LOG = logging .getLogger (__name__ )
1213
13- MODPATH = "cloudinit.config.cc_ssh_import_ids ."
14+ MODPATH = "cloudinit.config.cc_ssh_import_id ."
1415
1516
1617class TestIsKeyInNestedDict :
@@ -69,17 +70,17 @@ class TestHandleSshImportIDs:
6970 ({"ssh_import_id" : ["bobkey" ]}, "ssh-import-id is not installed" ),
7071 ),
7172 )
72- @mock .patch ("cloudinit. subp.which" )
73+ @mock .patch (MODPATH + " subp.which" )
7374 def test_skip_inapplicable_configs (self , m_which , cfg , log , caplog ):
7475 """Skip config without ssh_import_id"""
7576 m_which .return_value = None
7677 cloud = get_cloud ("ubuntu" )
7778 cc_ssh_import_id .handle ("name" , cfg , cloud , [])
7879 assert log in caplog .text
7980
80- @mock .patch ("cloudinit.ssh_util. pwd.getpwnam" )
81- @mock .patch ("cloudinit.config.cc_ssh_import_id. subp.subp" )
82- @mock .patch ("cloudinit. subp.which" )
81+ @mock .patch (MODPATH + " pwd.getpwnam" )
82+ @mock .patch (MODPATH + " subp.subp" )
83+ @mock .patch (MODPATH + " subp.which" )
8384 def test_use_sudo (self , m_which , m_subp , m_getpwnam ):
8485 """Check that sudo is available and use that"""
8586 m_which .return_value = "/usr/bin/ssh-import-id"
@@ -98,9 +99,9 @@ def test_use_sudo(self, m_which, m_subp, m_getpwnam):
9899 capture = False ,
99100 )
100101
101- @mock .patch ("cloudinit.ssh_util. pwd.getpwnam" )
102- @mock .patch ("cloudinit.config.cc_ssh_import_id. subp.subp" )
103- @mock .patch ("cloudinit. subp.which" )
102+ @mock .patch (MODPATH + " pwd.getpwnam" )
103+ @mock .patch (MODPATH + " subp.subp" )
104+ @mock .patch (MODPATH + " subp.which" )
104105 def test_use_doas (self , m_which , m_subp , m_getpwnam ):
105106 """Check that doas is available and use that"""
106107 m_which .side_effect = [None , "/usr/bin/doas" ]
@@ -111,9 +112,9 @@ def test_use_doas(self, m_which, m_subp, m_getpwnam):
111112 ["doas" , "-u" , user , "ssh-import-id" ] + ids , capture = False
112113 )
113114
114- @mock .patch ("cloudinit.ssh_util. pwd.getpwnam" )
115- @mock .patch ("cloudinit.config.cc_ssh_import_id. subp.subp" )
116- @mock .patch ("cloudinit. subp.which" )
115+ @mock .patch (MODPATH + " pwd.getpwnam" )
116+ @mock .patch (MODPATH + " subp.subp" )
117+ @mock .patch (MODPATH + " subp.which" )
117118 def test_use_neither_sudo_nor_doas (
118119 self , m_which , m_subp , m_getpwnam , caplog
119120 ):
@@ -125,3 +126,45 @@ def test_use_neither_sudo_nor_doas(
125126 assert (
126127 "Neither sudo nor doas available! Unable to import SSH ids"
127128 ) in caplog .text
129+
130+ @mock .patch (MODPATH + "time.sleep" )
131+ @mock .patch (MODPATH + "pwd.getpwnam" )
132+ @mock .patch (MODPATH + "subp.which" )
133+ def test_retry_once_on_exit_code_1 (
134+ self , m_which , m_getpwnam , m_sleep , mocker
135+ ):
136+ """Only attempt one retry when ssh-import-id exits with code 1."""
137+ m_subp = mocker .patch (
138+ "cloudinit.config.cc_ssh_import_id.subp.subp" ,
139+ side_effect = [
140+ ProcessExecutionError (exit_code = 1 , stderr = "try1" ),
141+ ProcessExecutionError (exit_code = 1 , stderr = "try2" ),
142+ ],
143+ )
144+ m_which .return_value = "/usr/bin/ssh-import-id"
145+ with pytest .raises (
146+ ProcessExecutionError ,
147+ match = r"(?s)Unexpected error while running command.*try2" ,
148+ ):
149+ cc_ssh_import_id .import_ssh_ids (["waffle" ], "bob" )
150+
151+ assert m_subp .call_count == 2
152+ assert m_sleep .call_count == 1
153+
154+ @mock .patch (MODPATH + "time.sleep" )
155+ @mock .patch (MODPATH + "pwd.getpwnam" )
156+ @mock .patch (MODPATH + "subp.which" )
157+ def test_retry_with_success (self , m_which , m_getpwnam , m_sleep , mocker ):
158+ """Retry succeeds on ssh-import-id with a retry."""
159+ m_subp = mocker .patch (
160+ "cloudinit.config.cc_ssh_import_id.subp.subp" ,
161+ side_effect = [
162+ ProcessExecutionError (exit_code = 1 , stderr = "try1" ),
163+ None ,
164+ ],
165+ )
166+ m_which .return_value = "/usr/bin/ssh-import-id"
167+ cc_ssh_import_id .import_ssh_ids (["waffle" ], "bob" )
168+
169+ assert m_subp .call_count == 2
170+ assert m_sleep .call_count == 1
0 commit comments