Skip to content

Commit a9ffc62

Browse files
committed
Retry user load profile on Windows
On Windows, the load user profile may fail on laggy systems, if the Windows subsystems are not ready at that moment. Retrying the load should fix the issue most of the times. Change-Id: I28cc564ebeac6d901dcbbef7cebe882a5ccb41b1
1 parent 97295ba commit a9ffc62

4 files changed

Lines changed: 32 additions & 7 deletions

File tree

cloudbaseinit/exception.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,14 @@ def __init__(self, msg="%r", error_code=None):
7171
except TypeError:
7272
formatted_msg = msg
7373
super(WindowsCloudbaseInitException, self).__init__(formatted_msg)
74+
75+
76+
class LoadUserProfileCloudbaseInitException(WindowsCloudbaseInitException):
77+
"""Windows cannot load the newly created user profile.
78+
79+
The load user profile can fail if the Windows subsystems responsible for
80+
the action are not ready. This usually happens on laggy systems and should
81+
be retried.
82+
"""
83+
84+
pass

cloudbaseinit/osutils/windows.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,9 @@ def get_user_sid(self, username):
642642
# User not found
643643
pass
644644

645+
@retry_decorator.retry_decorator(
646+
max_retry_count=3,
647+
exceptions=exception.LoadUserProfileCloudbaseInitException)
645648
def create_user_logon_session(self, username, password, domain='.',
646649
load_profile=True,
647650
logon_type=LOGON32_LOGON_INTERACTIVE):
@@ -666,7 +669,7 @@ def create_user_logon_session(self, username, password, domain='.',
666669
ret_val = userenv.LoadUserProfileW(token, ctypes.byref(pi))
667670
if not ret_val:
668671
kernel32.CloseHandle(token)
669-
raise exception.WindowsCloudbaseInitException(
672+
raise exception.LoadUserProfileCloudbaseInitException(
670673
"Cannot load user profile: %r")
671674

672675
return token

cloudbaseinit/tests/osutils/test_windows.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,9 @@ def test_create_user_fail(self):
452452
self._test_create_user(fail=True)
453453

454454
@mock.patch('cloudbaseinit.osutils.windows.Win32_PROFILEINFO')
455-
def _test_create_user_logon_session(self, mock_Win32_PROFILEINFO, logon,
455+
@mock.patch('time.sleep')
456+
def _test_create_user_logon_session(self, mock_time_sleep,
457+
mock_Win32_PROFILEINFO, logon,
456458
loaduser, load_profile=True,
457459
last_error=None):
458460
self._wintypes_mock.HANDLE = mock.MagicMock()
@@ -474,7 +476,9 @@ def _test_create_user_logon_session(self, mock_Win32_PROFILEINFO, logon,
474476
userenv.LoadUserProfileW.return_value = None
475477
kernel32.CloseHandle.return_value = None
476478
with self.assert_raises_windows_message(
477-
"Cannot load user profile: %r", last_error):
479+
"Cannot load user profile: %r", last_error,
480+
get_last_error_called_times=4,
481+
format_error_called_times=4):
478482
self._winutils.create_user_logon_session(
479483
self._USERNAME, self._PASSWORD, domain='.',
480484
load_profile=load_profile)

cloudbaseinit/tests/testutils.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,9 @@ class CloudbaseInitTestBase(unittest.TestCase):
159159
@contextlib.contextmanager
160160
def assert_raises_windows_message(
161161
self, expected_msg, error_code,
162-
exc=exception.WindowsCloudbaseInitException):
162+
exc=exception.WindowsCloudbaseInitException,
163+
get_last_error_called_times=1,
164+
format_error_called_times=1):
163165
"""Helper method for testing raised error messages
164166
165167
This assert method is similar to :meth:`~assertRaises`, but
@@ -188,11 +190,16 @@ def assert_raises_windows_message(
188190
# This can be called when the error code is not given,
189191
# but we don't have control over that, so test that
190192
# it's actually called only once.
191-
mock_get_last_error.assert_called_once_with()
192-
mock_format_error.assert_called_once_with(
193+
mock_get_last_error.assert_called()
194+
self.assertEqual(mock_get_last_error.call_count,
195+
get_last_error_called_times)
196+
mock_format_error.assert_called_with(
193197
mock_get_last_error.return_value)
194198
else:
195-
mock_format_error.assert_called_once_with(error_code)
199+
mock_format_error.assert_called_with(error_code)
200+
201+
self.assertEqual(mock_format_error.call_count,
202+
format_error_called_times)
196203

197204
expected_msg = expected_msg % mock_format_error.return_value
198205
self.assertEqual(expected_msg, cm.exception.args[0])

0 commit comments

Comments
 (0)