Skip to content

Commit 2b568a9

Browse files
fix and cleanup e2e after refactoring (#185)
After running the e2e tests, I've located some small bugs appeared after the refactoring. I've provided both fixes and additional tests to verify that everything works smoothly.
1 parent 5c39c98 commit 2b568a9

File tree

14 files changed

+167
-37
lines changed

14 files changed

+167
-37
lines changed

src/databricks/labs/ucx/inventory/permissions_inventory.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import logging
22

3-
import pandas as pd
43
from databricks.sdk import WorkspaceClient
54
from pyspark.sql import DataFrame
65
from pyspark.sql.types import StringType, StructField, StructType
@@ -39,7 +38,7 @@ def cleanup(self):
3938
def save(self, items: list[PermissionsInventoryItem]):
4039
# TODO: update instead of append
4140
logger.info(f"Saving {len(items)} items to inventory table {self._table}")
42-
serialized_items = pd.DataFrame([item.as_dict() for item in items])
41+
serialized_items = [item.as_dict() for item in items]
4342
df = self.spark.createDataFrame(serialized_items, schema=self._table_schema)
4443
df.write.mode("append").format("delta").saveAsTable(self._table)
4544
logger.info("Successfully saved the items to inventory table")

src/databricks/labs/ucx/inventory/verification.py

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
from typing import Literal
22

33
from databricks.sdk import WorkspaceClient
4-
from databricks.sdk.service import workspace
54

65
from databricks.labs.ucx.providers.groups_info import GroupMigrationState
6+
from databricks.labs.ucx.support.secrets import SecretScopesSupport
77

88

99
class VerificationManager:
10-
def __init__(self, ws: WorkspaceClient):
10+
def __init__(self, ws: WorkspaceClient, secrets_support: SecretScopesSupport):
1111
self._ws = ws
12+
self._secrets_support = secrets_support
1213

1314
def verify(
1415
self, migration_state: GroupMigrationState, target: Literal["backup", "account"], tuples: list[tuple[str, str]]
@@ -51,8 +52,8 @@ def verify_applied_scope_acls(
5152
for mi in migration_state.groups:
5253
src_name = getattr(mi, base_attr).display_name
5354
dst_name = getattr(mi, target).display_name
54-
src_permission = self._secret_scope_permission(scope_name, src_name)
55-
dst_permission = self._secret_scope_permission(scope_name, dst_name)
55+
src_permission = self._secrets_support.secret_scope_permission(scope_name, src_name)
56+
dst_permission = self._secrets_support.secret_scope_permission(scope_name, dst_name)
5657
assert src_permission == dst_permission, "Scope ACLs were not applied correctly"
5758

5859
def verify_roles_and_entitlements(self, migration_state: GroupMigrationState, target: Literal["backup", "account"]):
@@ -65,9 +66,3 @@ def verify_roles_and_entitlements(self, migration_state: GroupMigrationState, ta
6566

6667
assert base_group_info.roles == target_group_info.roles
6768
assert base_group_info.entitlements == target_group_info.entitlements
68-
69-
def _secret_scope_permission(self, scope_name: str, group_name: str) -> workspace.AclPermission | None:
70-
for acl in self._ws.secrets.list_acls(scope=scope_name):
71-
if acl.principal == group_name:
72-
return acl.permission
73-
return None

src/databricks/labs/ucx/support/group_level.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def _crawler_task(self, group: iam.Group, property_name: str):
2020
@sleep_and_retry
2121
@limits(calls=10, period=1)
2222
def _applier_task(self, group_id: str, value: list[iam.ComplexValue], property_name: str):
23-
operations = [iam.Patch(op=iam.PatchOp.ADD, path=property_name, value=value)]
23+
operations = [iam.Patch(op=iam.PatchOp.ADD, path=property_name, value=[e.as_dict() for e in value])]
2424
schemas = [iam.PatchSchema.URN_IETF_PARAMS_SCIM_API_MESSAGES_2_0_PATCH_OP]
2525
self._ws.groups.patch(id=group_id, operations=operations, schemas=schemas)
2626

src/databricks/labs/ucx/support/impl.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@ def __init__(self, ws: WorkspaceClient, num_threads: int, workspace_start_path:
3030
ws=ws,
3131
listings=[
3232
listing_wrapper(ws.clusters.list, "cluster_id", RequestObjectType.CLUSTERS),
33-
listing_wrapper(ws.cluster_policies.list, "cluster_policy_id", RequestObjectType.CLUSTER_POLICIES),
33+
listing_wrapper(ws.cluster_policies.list, "policy_id", RequestObjectType.CLUSTER_POLICIES),
3434
listing_wrapper(ws.instance_pools.list, "instance_pool_id", RequestObjectType.INSTANCE_POOLS),
3535
listing_wrapper(ws.warehouses.list, "id", RequestObjectType.SQL_WAREHOUSES),
3636
listing_wrapper(ws.jobs.list, "job_id", RequestObjectType.JOBS),
37-
listing_wrapper(ws.pipelines.list, "pipeline_id", RequestObjectType.PIPELINES),
37+
listing_wrapper(ws.pipelines.list_pipelines, "pipeline_id", RequestObjectType.PIPELINES),
3838
listing_wrapper(experiments_listing(ws), "experiment_id", RequestObjectType.EXPERIMENTS),
3939
listing_wrapper(models_listing(ws), "id", RequestObjectType.REGISTERED_MODELS),
4040
workspace_listing(ws, num_threads=num_threads, start_path=workspace_start_path),
@@ -64,13 +64,13 @@ def supports(self) -> dict[str, BaseSupport]:
6464
"roles": self._scim_support,
6565
# generic API
6666
"clusters": self._generic_support,
67-
"cluster_policies": self._generic_support,
68-
"instance_pools": self._generic_support,
69-
"sql_warehouses": self._generic_support,
67+
"cluster-policies": self._generic_support,
68+
"instance-pools": self._generic_support,
69+
"sql/warehouses": self._generic_support,
7070
"jobs": self._generic_support,
7171
"pipelines": self._generic_support,
7272
"experiments": self._generic_support,
73-
"registered_models": self._generic_support,
73+
"registered-models": self._generic_support,
7474
"tokens": self._generic_support,
7575
"passwords": self._generic_support,
7676
# workspace objects

src/databricks/labs/ucx/support/permissions.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,12 @@ def _safe_get_permissions(
3939
permissions = ws.permissions.get(request_object_type, object_id)
4040
return permissions
4141
except DatabricksError as e:
42-
if e.error_code in ["RESOURCE_DOES_NOT_EXIST", "RESOURCE_NOT_FOUND", "PERMISSION_DENIED"]:
42+
if e.error_code in [
43+
"RESOURCE_DOES_NOT_EXIST",
44+
"RESOURCE_NOT_FOUND",
45+
"PERMISSION_DENIED",
46+
"FEATURE_DISABLED",
47+
]:
4348
logger.warning(f"Could not get permissions for {request_object_type} {object_id} due to {e.error_code}")
4449
return None
4550
else:
@@ -79,7 +84,7 @@ def _prepare_new_acl(
7984
def _applier_task(
8085
self, ws: WorkspaceClient, object_id: str, acl: list[iam.AccessControlRequest], request_type: RequestObjectType
8186
):
82-
ws.permissions.update(request_type, object_id, acl)
87+
ws.permissions.update(request_object_type=request_type, request_object_id=object_id, access_control_list=acl)
8388

8489
@sleep_and_retry
8590
@limits(calls=100, period=1)
@@ -90,10 +95,13 @@ def _crawler_task(
9095
request_type: RequestObjectType,
9196
) -> PermissionsInventoryItem | None:
9297
permissions = self._safe_get_permissions(ws, request_type, object_id)
98+
99+
support = object_id if request_type == RequestObjectType.AUTHORIZATION else request_type.value
100+
93101
if permissions:
94102
return PermissionsInventoryItem(
95103
object_id=object_id,
96-
support=request_type.value,
104+
support=support,
97105
raw_object_permissions=json.dumps(permissions.as_dict()),
98106
)
99107

@@ -103,10 +111,17 @@ def _get_apply_task(
103111
new_acl = self._prepare_new_acl(
104112
iam.ObjectPermissions.from_dict(json.loads(item.raw_object_permissions)), migration_state, destination
105113
)
114+
115+
request_type = (
116+
RequestObjectType.AUTHORIZATION
117+
if item.support in ("passwords", "tokens")
118+
else RequestObjectType(item.support)
119+
)
120+
106121
return partial(
107122
self._applier_task,
108123
ws=self._ws,
109-
request_type=RequestObjectType(item.support),
124+
request_type=request_type,
110125
acl=new_acl,
111126
object_id=item.object_id,
112127
)

src/databricks/labs/ucx/support/secrets.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import json
2+
import random
3+
import time
24
from functools import partial
35

46
from databricks.sdk import WorkspaceClient
@@ -33,10 +35,42 @@ def is_item_relevant(self, item: PermissionsInventoryItem, migration_state: Grou
3335
mentioned_groups = [acl.principal for acl in acls]
3436
return any(g in mentioned_groups for g in [info.workspace.display_name for info in migration_state.groups])
3537

38+
def secret_scope_permission(self, scope_name: str, group_name: str) -> workspace.AclPermission | None:
39+
for acl in self._ws.secrets.list_acls(scope=scope_name):
40+
if acl.principal == group_name:
41+
return acl.permission
42+
return None
43+
44+
def _inflight_check(
45+
self, scope_name: str, group_name: str, expected_permission: workspace.AclPermission, num_retries: int = 5
46+
):
47+
# in-flight check for the applied permissions
48+
# the api might be inconsistent, therefore we need to check that the permissions were applied
49+
# TODO: add mixin to SDK
50+
retries_left = num_retries
51+
while retries_left > 0:
52+
time.sleep(random.random() * 2)
53+
applied_permission = self.secret_scope_permission(scope_name=scope_name, group_name=group_name)
54+
if applied_permission:
55+
if applied_permission == expected_permission:
56+
return
57+
else:
58+
msg = (
59+
f"Applied permission {applied_permission} is not "
60+
f"equal to expected permission {expected_permission}"
61+
)
62+
raise ValueError(msg)
63+
64+
retries_left -= 1
65+
66+
msg = f"Failed to apply permissions for {group_name} on scope {scope_name} in {num_retries} retries"
67+
raise ValueError(msg)
68+
3669
@sleep_and_retry
3770
@limits(calls=30, period=1)
3871
def _rate_limited_put_acl(self, object_id: str, principal: str, permission: workspace.AclPermission):
3972
self._ws.secrets.put_acl(object_id, principal, permission)
73+
self._inflight_check(scope_name=object_id, group_name=principal, expected_permission=permission)
4074

4175
def _get_apply_task(
4276
self, item: PermissionsInventoryItem, migration_state: GroupMigrationState, destination: Destination

src/databricks/labs/ucx/support/sql.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def _applier_task(self, object_type: sql.ObjectTypePlural, object_id: str, acl:
6464
Please note that we only have SET option (DBSQL Permissions API doesn't support UPDATE operation).
6565
This affects the way how we prepare the new ACL request.
6666
"""
67-
self._ws.dbsql_permissions.set(object_type=object_type, object_id=object_id, acl=acl)
67+
self._ws.dbsql_permissions.set(object_type=object_type, object_id=object_id, access_control_list=acl)
6868

6969
def get_crawler_tasks(self):
7070
for listing in self._listings:

src/databricks/labs/ucx/toolkits/group_migration.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,11 @@ def __init__(self, config: MigrationConfig):
2828

2929
self._group_manager = GroupManager(self._ws, config.groups)
3030
self._permissions_inventory = PermissionsInventoryTable(config.inventory_database, self._ws)
31+
self._supports_provider = SupportsProvider(self._ws, self._num_threads, self._workspace_start_path)
3132
self._permissions_manager = PermissionManager(
32-
self._ws,
33-
self._permissions_inventory,
34-
supports_provider=SupportsProvider(self._ws, self._num_threads, self._workspace_start_path),
33+
self._ws, self._permissions_inventory, supports_provider=self._supports_provider
3534
)
36-
self._verification_manager = VerificationManager(self._ws)
35+
self._verification_manager = VerificationManager(self._ws, self._supports_provider.supports["secrets"])
3736

3837
@staticmethod
3938
def _verify_ws_client(w: WorkspaceClient):

tests/unit/support/test_group_level.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def test_scim_apply(migration_state):
5757
task()
5858
ws.groups.patch.assert_called_once_with(
5959
id="test-backup",
60-
operations=[iam.Patch(op=iam.PatchOp.ADD, path="roles", value=sample_permissions)],
60+
operations=[iam.Patch(op=iam.PatchOp.ADD, path="roles", value=[p.as_dict() for p in sample_permissions])],
6161
schemas=[iam.PatchSchema.URN_IETF_PARAMS_SCIM_API_MESSAGES_2_0_PATCH_OP],
6262
)
6363

tests/unit/support/test_impl.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ def test_supports_provider():
99
"entitlements",
1010
"roles",
1111
"clusters",
12-
"cluster_policies",
13-
"instance_pools",
14-
"sql_warehouses",
12+
"cluster-policies",
13+
"instance-pools",
14+
"sql/warehouses",
1515
"jobs",
1616
"pipelines",
1717
"experiments",
18-
"registered_models",
18+
"registered-models",
1919
"tokens",
2020
"passwords",
2121
"notebooks",

0 commit comments

Comments
 (0)