-
Notifications
You must be signed in to change notification settings - Fork 60
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[RHCLOUD-35855] System roles assigments in migrator #1288
Conversation
0698d46
to
8171f76
Compare
188e976
to
df77675
Compare
@@ -52,6 +52,7 @@ class ReplicationEventType(str, Enum): | |||
MIGRATE_CUSTOM_ROLE = "migrate_custom_role" | |||
MIGRATE_TENANT_GROUPS = "migrate_tenant_groups" | |||
CUSTOMIZE_DEFAULT_GROUP = "customize_default_group" | |||
ASSIGN_SYSTEM_ROLE_IN_MIGRATOR = "assign_system_role_in_migrator" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe migrate_system_role_assignment
(so all migrate_
events have a common prefix / convention?)
rbac/migration_tool/migrate.py
Outdated
group, ReplicationEventType.ASSIGN_SYSTEM_ROLE_IN_MIGRATOR | ||
) | ||
if group.platform_default is True: | ||
dual_write_handler.generate_relations_to_add_roles(public_default_roles) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we can add all default roles, because they may have removed some of these.
But, if you just go by the policy like you do below, I think you should be okay. So I think you can just take this part out.
rbac/migration_tool/migrate.py
Outdated
public_default_roles = Role.objects.filter(platform_default=True, tenant=Tenant.objects.get(tenant_name="public")) | ||
|
||
with transaction.atomic(): | ||
for group in tenant.group_set.all(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we can combine this with migrate_users_for_groups in some way, which already iterates through (almost) all tenant groups.
rbac/migration_tool/migrate.py
Outdated
@@ -152,6 +154,19 @@ def migrate_data_for_tenant(tenant: Tenant, exclude_apps: list, replicator: Rela | |||
logger.info(f"Migration completed for role: {role.name} with UUID {role.uuid}.") | |||
logger.info(f"Migrated {roles.count()} roles for tenant: {tenant.org_id}") | |||
|
|||
public_default_roles = Role.objects.filter(platform_default=True, tenant=Tenant.objects.get(tenant_name="public")) | |||
|
|||
with transaction.atomic(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't hurt to be all in one tx, but nothing else in the migrator is currently dealing with concurrency. It is assumed that it runs without other concurrent writes.
You mentioned the issue about the middleware possibly doing concurrent writes which I'm not sure if you've looked into more yet but deserves some thought.
4060072
to
b018976
Compare
rbac/migration_tool/migrate.py
Outdated
"""Write users relationship to groups.""" | ||
def migrate_system_role_assignments_for_groups(groups: list[Group]) -> list[common_pb2.Relationship]: | ||
"""Generate system role assignments for groups.""" | ||
with transaction.atomic(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this required because of the select for update?
If not, I would probably take it out because it might imply it's needed or effective in some way that it isn't. Migration does NOT account for concurrent updates, so I am worried about implying that it does.
If it's required, maybe just add a comment to that effect (required due to select for update even though migrator does not generally account for concurrent updates and so should NOT be run outside of read-only mode.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes it is and I wanted to ask what scope for transaction block should be in this case and I will add comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It shouldn't really matter I guess since we shouldn't do this with concurrent writes. Not too big though, so a TX per group or per tenant is probably okay. Per group might be better but probably would require testing to confirm. (Normally there is a TX per every single SQL operation so per group is not too many.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We also may want to add a check in the beginning of the migrator that requires read only mode is set otherwise it doesn't run as a safety mechanism.
rbac/migration_tool/migrate.py
Outdated
if dual_write_handler is None: | ||
dual_write_handler = RelationApiDualWriteGroupHandler(group, None) | ||
else: | ||
dual_write_handler.group = group | ||
system_roles = group.roles().filter(system=True) | ||
if system_roles.exists() > 0: | ||
dual_write_handler.generate_relations_to_add_roles(system_roles) | ||
relationships.extend(dual_write_handler.group_relations_to_add) | ||
return relationships |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO I think it's cleaner & less risky to let the dual write handler replicate these each on its own, and reinitialize a new replicator for each loop. There's no requirement that these need to all be in their own message. In fact, being in their own message can be problematic because of message and SpiceDB request size limits.
If the class is not carefully designed for mutation, this could result in bugs. So it's kind of maybe optimizing something that doesn't need to be, and adding some risks. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes I know that dual write handler is not designed for multiple groups and I wanted to avoid to do same query for default workspace for each group. Do you think it is fine with this fact ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is fine, we can cache default workspaces in follow up PR.
b018976
to
87e2851
Compare
rbac/migration_tool/migrate.py
Outdated
user_relationships = migrate_user_relationships_for_groups(groups) | ||
replicator.replicate( | ||
ReplicationEvent( | ||
event_type=ReplicationEventType.MIGRATE_TENANT_GROUPS, | ||
info={"tenant": tenant.org_id}, | ||
partition_key=PartitionKey.byEnvironment(), | ||
add=tuples, | ||
add=user_relationships, | ||
) | ||
) | ||
|
||
system_role_relationships = migrate_system_role_assignments_for_groups(groups) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if we combined principals and roles together by using the group dual write handler?
Something like (psuedo-code):
for group in groups:
handler = RelationApiDualWriteGropuHandler(group, MIGRATE_GROUP)
handler.generate_relations_to_add_principals(group.principals)
handler.generate_relations_to_add_roles(group.system_roles())
handler.replicate()
It might require a small change to dealing with principals but it would consolidate the logic for group membership replication and clean up the code a bit.
Not required by any means I think as long as testing is good either way. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure, it is simpler, done 👍
87e2851
to
672b8db
Compare
bc7f434
to
49d4bf9
Compare
@alechenninger I addressed you suggestions, can you re-review ? 🙏 |
@@ -31,7 +36,7 @@ def setUp(self): | |||
"""Set up the utils tests.""" | |||
super().setUp() | |||
public_tenant = Tenant.objects.get(tenant_name="public") | |||
Group.objects.create(name="default", tenant=public_tenant, platform_default=True) | |||
default_group = Group.objects.create(name="default", tenant=public_tenant, platform_default=True, system=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we can use seed_group()
instead in the setup (probably after the roles are created then)? This would ensure test setup stays consistent with the app and maybe reduce some of this code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added seed_group
and clone_default_group_in_public_schema
it is possible use seed_group()
, function creates also admin default group
rbac/migration_tool/migrate.py
Outdated
system_roles = group.roles().filter(system=True) | ||
if system_roles.exists() > 0: | ||
dual_write_handler.generate_relations_to_add_roles(system_roles) | ||
dual_write_handler.replicate() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can address this in a follow-up PR if you would like, but along the lines of RHCLOUD-36014, I wonder if we should avoid calling this if we know there were no system_roles added and no principals added?
We could add that logic inside replicate()
, but then it makes you wonder if we should really warn there? And in that case it's redundant because the outbox replicator already warns. So it feels like we should avoid calling replicate if we know there's nothing to replicate, otherwise the replicate method will be "suspicious" in some way :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, .replicate()
should not be called when it is possible to determine that it is not needed otherwise we cannot capture unintended "empty" replication.
I made changes here. Existence checks are moved outside the transaction block to avoid initializing the dual write handler when it is not needed. These checks are also not needed before the generate_relations_XXX
method, as it they don't perform any work on empty arrays (system_role, principals)."
77e5167
to
0cd909b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
I have another branch where I've added another migrator test which is a little more exacting to be sure, but I'll open a PR for that separately. Thank you and nice work!
Co-authored-by: Alec Henninger <[email protected]>
Co-authored-by: Alec Henninger <[email protected]>
Co-authored-by: Alec Henninger <[email protected]>
… seed_group methods
794b916
to
e22761e
Compare
This generates and replicates relations for system role assignments of custom default groups and custom groups.
Also:
Links
RHCLOUD-35855