Skip to content

Commit 648ea9a

Browse files
authored
[Core] Prevent DB Failure from Breaking Enforcer (#7802)
Add fix.
1 parent a4f8094 commit 648ea9a

File tree

2 files changed

+57
-1
lines changed

2 files changed

+57
-1
lines changed

sky/users/permission.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ def _lazy_initialize(self):
4343
with _policy_lock():
4444
global _enforcer_instance
4545
if _enforcer_instance is None:
46-
_enforcer_instance = self
4746
engine = global_user_state.initialize_and_get_db()
4847
db_utils.add_all_tables_to_db_sqlalchemy(
4948
sqlalchemy_adapter.Base.metadata, engine)
@@ -53,6 +52,10 @@ def _lazy_initialize(self):
5352
'model.conf')
5453
enforcer = casbin.Enforcer(model_path, adapter)
5554
self.enforcer = enforcer
55+
# Only set the enforcer instance once the enforcer
56+
# is successfully initialized, if we change it and then fail
57+
# we will set it to None and all subsequent calls will fail.
58+
_enforcer_instance = self
5659
self._maybe_initialize_policies()
5760
self._maybe_initialize_basic_auth_user()
5861
else:

tests/unit_tests/test_sky/users/test_permission.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,3 +742,56 @@ def test_no_duplicate_policies_when_policies_already_exist(
742742

743743
# The save_policy might not be called if no changes were made
744744
# We mainly want to ensure no duplicate additions occurred
745+
746+
@mock.patch('sky.users.permission._policy_lock')
747+
@mock.patch(
748+
'sky.users.permission.PermissionService._maybe_initialize_policies')
749+
@mock.patch(
750+
'sky.users.permission.PermissionService._maybe_initialize_basic_auth_user'
751+
)
752+
@mock.patch('sky.users.permission.casbin.Enforcer')
753+
@mock.patch('sky.users.permission.sqlalchemy_adapter.Adapter')
754+
@mock.patch('sky.users.permission.db_utils.add_all_tables_to_db_sqlalchemy')
755+
@mock.patch('sky.global_user_state.initialize_and_get_db')
756+
def test_lazy_initialize_db_failure_recovery(
757+
self, mock_init_db, mock_add_tables, mock_adapter_class,
758+
mock_enforcer_class, mock_init_basic_auth, mock_init_policies,
759+
mock_policy_lock):
760+
"""Test that _lazy_initialize doesn't permanently set
761+
enforcer to None after DB failure on retry."""
762+
# Configure context manager to allow exceptions to propagate
763+
mock_context = mock.Mock()
764+
mock_context.__enter__ = mock.Mock(return_value=None)
765+
mock_context.__exit__ = mock.Mock(
766+
return_value=False) # Don't suppress exceptions
767+
mock_policy_lock.return_value = mock_context
768+
769+
mock_engine = mock.Mock()
770+
mock_init_db.return_value = mock_engine
771+
772+
mock_adapter = mock.Mock()
773+
mock_adapter_class.return_value = mock_adapter
774+
775+
mock_enforcer = mock.Mock()
776+
mock_enforcer_class.return_value = mock_enforcer
777+
778+
# First call: add_all_tables_to_db_sqlalchemy raises an error
779+
# Second call: it succeeds
780+
mock_add_tables.side_effect = [
781+
Exception("Database connection failed"),
782+
None # Success on second call
783+
]
784+
785+
service = permission.PermissionService()
786+
787+
# First call - should raise an exception
788+
with pytest.raises(Exception) as exc_info:
789+
service._lazy_initialize()
790+
assert "Database connection failed" in str(exc_info.value)
791+
792+
# Second call - should succeed
793+
service._lazy_initialize()
794+
795+
# Verify that self.enforcer is not None after successful initialization
796+
assert service.enforcer is not None
797+
assert service.enforcer == mock_enforcer

0 commit comments

Comments
 (0)