Skip to content

Commit 11753d9

Browse files
nfxfannijako
andauthored
Added recovery mode for workspace-local groups from temporary groups (#435)
This fix helps to recover from a state where - the temporary groups were created, - the account groups were not, - the original workspace-local groups were deleted. - The fix recreates the original state, with recreating all the workspace-local groups, which does have a temp pair but not an account level pair attached to the workspace. This helps to recreate the state of the migration with the already existing group_manager.migration_groups_provider --------- Co-authored-by: Fanni Jakó <[email protected]>
1 parent a3c325a commit 11753d9

File tree

3 files changed

+78
-0
lines changed

3 files changed

+78
-0
lines changed

docs/local-group-migration.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,4 +238,14 @@ ws_group_names = {_.display_name for _ in workspace_groups}
238238
ac_group_names = {_.display_name for _ in account_groups}
239239
group_names = list(ws_group_names.intersection(ac_group_names))
240240
print(f"Found {len(group_names)} groups to migrate")
241+
```
242+
243+
2. Recover workspace-local groups from backup groups from within a debug notebook:
244+
245+
```python
246+
from databricks.labs.ucx.workspace_access.groups import GroupManager
247+
from databricks.labs.ucx.config import GroupsConfig
248+
249+
group_manager = GroupManager(ws, GroupsConfig(auto=True))
250+
group_manager.ws_local_group_deletion_recovery()
241251
```

src/databricks/labs/ucx/workspace_access/groups.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,3 +272,30 @@ def delete_backup_groups(self):
272272
self._delete_workspace_group(group)
273273

274274
logger.info("Backup groups were successfully deleted")
275+
276+
def ws_local_group_deletion_recovery(self):
277+
workspace_groups = {_.display_name for _ in self._workspace_groups}
278+
source_groups = [
279+
g
280+
for g in self._workspace_groups
281+
if g.display_name.removeprefix(self.config.backup_group_prefix) not in workspace_groups
282+
]
283+
284+
logger.info(
285+
f"Recovering from workspace-local group deletion. "
286+
f"In total, {len(source_groups)} temporary groups found, which do not have corresponding workspace groups"
287+
)
288+
289+
for source_group in source_groups:
290+
ws_local_group = self._ws.groups.create(
291+
display_name=source_group.display_name.removeprefix(self.config.backup_group_prefix),
292+
meta=source_group.meta,
293+
entitlements=source_group.entitlements,
294+
roles=source_group.roles,
295+
members=source_group.members,
296+
)
297+
self._workspace_groups.append(ws_local_group)
298+
self._migration_state.add(ws_local_group)
299+
logger.info(f"Workspace-local group {ws_local_group} successfully recovered")
300+
301+
logger.info("Workspace-local group deletion recovery completed")

tests/integration/workspace_access/test_groups.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,44 @@ def test_id_validity(ws: WorkspaceClient, make_ucx_group):
5252
manager = GroupManager(ws, GroupsConfig(selected=[ws_group.display_name]))
5353
assert ws_group.id == manager._get_group(ws_group.display_name, "workspace").id
5454
assert acc_group.id == manager._get_group(acc_group.display_name, "account").id
55+
56+
57+
def test_recover_from_ws_local_deletion(ws, make_ucx_group):
58+
ws_group, _ = make_ucx_group()
59+
ws_group_two, _ = make_ucx_group()
60+
61+
group_manager = GroupManager(ws, GroupsConfig(auto=True))
62+
group_manager.prepare_groups_in_environment()
63+
64+
# simulate disaster
65+
ws.groups.delete(ws_group.id)
66+
ws.groups.delete(ws_group_two.id)
67+
68+
# recovery run from a debug notebook
69+
group_manager = GroupManager(ws, GroupsConfig(auto=True))
70+
group_manager.ws_local_group_deletion_recovery()
71+
72+
# normal run after from a job
73+
group_manager = GroupManager(ws, GroupsConfig(auto=True))
74+
group_manager.prepare_groups_in_environment()
75+
76+
migration_state = group_manager.migration_groups_provider
77+
78+
recovered_state = {}
79+
for gi in migration_state.groups:
80+
recovered_state[gi.workspace.display_name] = gi.workspace
81+
82+
assert sorted([member.display for member in ws_group.members]) == sorted(
83+
[member.display for member in recovered_state[ws_group.display_name].members]
84+
)
85+
assert sorted([member.display for member in ws_group_two.members]) == sorted(
86+
[member.display for member in recovered_state[ws_group_two.display_name].members]
87+
)
88+
89+
assert sorted([member.value for member in ws_group.members]) == sorted(
90+
[member.value for member in recovered_state[ws_group.display_name].members]
91+
)
92+
93+
assert sorted([member.value for member in ws_group_two.members]) == sorted(
94+
[member.value for member in recovered_state[ws_group_two.display_name].members]
95+
)

0 commit comments

Comments
 (0)