-
Notifications
You must be signed in to change notification settings - Fork 693
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rationalize the Assignment Grant Tables
Currently there are four different tables in the assignment sql backend, one for each type of grant. This leads to significantly more complex code. This patch rationalizes these four tables into a single Assignment table, with the minimal of rework to calling functions of the sql backend. A subsequent patch will improve efficiency of the calling code by taking advantage of a single Assignment table. Blueprint: role-assignments-unified-sql Change-Id: I7e604d718b29ad28b4d59458c7a528c01ff9804d
- Loading branch information
Showing
7 changed files
with
937 additions
and
441 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
51 changes: 51 additions & 0 deletions
51
keystone/common/sql/migrate_repo/versions/038_add_assignment_table.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
# Copyright 2014 IBM Corp. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
# not use this file except in compliance with the License. You may obtain | ||
# a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
# License for the specific language governing permissions and limitations | ||
# under the License. | ||
|
||
import sqlalchemy as sql | ||
|
||
from keystone.assignment.backends import sql as assignment_sql | ||
|
||
ASSIGNMENT_TABLE = 'assignment' | ||
|
||
|
||
def upgrade(migrate_engine): | ||
meta = sql.MetaData() | ||
meta.bind = migrate_engine | ||
|
||
sql.Table('role', meta, autoload=True) | ||
assignment_table = sql.Table( | ||
ASSIGNMENT_TABLE, | ||
meta, | ||
sql.Column('type', sql.Enum( | ||
assignment_sql.AssignmentType.USER_PROJECT, | ||
assignment_sql.AssignmentType.GROUP_PROJECT, | ||
assignment_sql.AssignmentType.USER_DOMAIN, | ||
assignment_sql.AssignmentType.GROUP_DOMAIN, | ||
name='type'), | ||
nullable=False), | ||
sql.Column('actor_id', sql.String(64), nullable=False), | ||
sql.Column('target_id', sql.String(64), nullable=False), | ||
sql.Column('role_id', sql.String(64), sql.ForeignKey('role.id'), | ||
nullable=False), | ||
sql.Column('inherited', sql.Boolean, default=False, nullable=False), | ||
sql.PrimaryKeyConstraint('type', 'actor_id', 'target_id', 'role_id')) | ||
assignment_table.create(migrate_engine, checkfirst=True) | ||
|
||
|
||
def downgrade(migrate_engine): | ||
meta = sql.MetaData() | ||
meta.bind = migrate_engine | ||
|
||
assignment = sql.Table(ASSIGNMENT_TABLE, meta, autoload=True) | ||
assignment.drop(migrate_engine, checkfirst=True) |
231 changes: 231 additions & 0 deletions
231
keystone/common/sql/migrate_repo/versions/039_grant_to_assignment.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,231 @@ | ||
# Copyright 2014 IBM Corp. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
# not use this file except in compliance with the License. You may obtain | ||
# a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
# License for the specific language governing permissions and limitations | ||
# under the License. | ||
|
||
import json | ||
|
||
import sqlalchemy as sql | ||
|
||
from keystone.assignment.backends import sql as assignment_sql | ||
|
||
USER_PROJECT_TABLE = 'user_project_metadata' | ||
GROUP_PROJECT_TABLE = 'group_project_metadata' | ||
USER_DOMAIN_TABLE = 'user_domain_metadata' | ||
GROUP_DOMAIN_TABLE = 'group_domain_metadata' | ||
|
||
ASSIGNMENT_TABLE = 'assignment' | ||
|
||
GRANT_TABLES = [USER_PROJECT_TABLE, USER_DOMAIN_TABLE, | ||
GROUP_PROJECT_TABLE, GROUP_DOMAIN_TABLE] | ||
|
||
|
||
def migrate_grant_table(meta, migrate_engine, session, table_name): | ||
|
||
def extract_actor_and_target(table_name, composite_grant): | ||
if table_name == USER_PROJECT_TABLE: | ||
return {'type': assignment_sql.AssignmentType.USER_PROJECT, | ||
'actor_id': composite_grant.user_id, | ||
'target_id': composite_grant.project_id} | ||
elif table_name == GROUP_PROJECT_TABLE: | ||
return {'type': assignment_sql.AssignmentType.GROUP_PROJECT, | ||
'actor_id': composite_grant.group_id, | ||
'target_id': composite_grant.project_id} | ||
elif table_name == USER_DOMAIN_TABLE: | ||
return {'type': assignment_sql.AssignmentType.USER_DOMAIN, | ||
'actor_id': composite_grant.user_id, | ||
'target_id': composite_grant.domain_id} | ||
else: | ||
return {'type': assignment_sql.AssignmentType.GROUP_DOMAIN, | ||
'actor_id': composite_grant.group_id, | ||
'target_id': composite_grant.domain_id} | ||
|
||
def grant_to_grant_dict_list(table_name, composite_grant): | ||
"""Make each role in the list of this entry a separate assignment.""" | ||
json_metadata = json.loads(composite_grant.data) | ||
role_dict_list = [] | ||
if 'roles' in json_metadata: | ||
for x in json_metadata['roles']: | ||
if x.get('id') is None: | ||
# Looks like an invalid role, drop it | ||
break | ||
grant = extract_actor_and_target(table_name, composite_grant) | ||
grant['role_id'] = x.get('id') | ||
grant['inherited'] = False | ||
if x.get('inherited_to') == 'projects': | ||
grant['inherited'] = True | ||
role_dict_list.append(grant) | ||
return role_dict_list | ||
|
||
upgrade_table = sql.Table(table_name, meta, autoload=True) | ||
assignment_table = sql.Table(ASSIGNMENT_TABLE, meta, autoload=True) | ||
|
||
# For each grant in this table, expand it out to be an assignment entry for | ||
# each role in the metadata | ||
for grant in session.query(upgrade_table).all(): | ||
for grant_role in grant_to_grant_dict_list(table_name, grant): | ||
new_entry = assignment_table.insert().values( | ||
type=grant_role['type'], | ||
actor_id=grant_role['actor_id'], | ||
target_id=grant_role['target_id'], | ||
role_id=grant_role['role_id'], | ||
inherited=grant_role['inherited']) | ||
migrate_engine.execute(new_entry) | ||
|
||
# Delete all the rows | ||
migrate_engine.execute(upgrade_table.delete()) | ||
|
||
|
||
def downgrade_assignment_table(meta, migrate_engine): | ||
|
||
def add_to_dict_list(metadata, assignment_row): | ||
"""Update a metadata dict list with the role. | ||
For the assignment row supplied, we need to append the role_id into | ||
the metadata list of dicts. If the row is inherited, then we mark | ||
it so in the dict we append. | ||
""" | ||
new_entry = {'id': assignment_row.role_id} | ||
if assignment_row.inherited and ( | ||
assignment_row.type == | ||
assignment_sql.AssignmentType.USER_DOMAIN or | ||
assignment_row.type == | ||
assignment_sql.AssignmentType.GROUP_DOMAIN): | ||
new_entry['inherited_to'] = 'projects' | ||
|
||
if metadata is not None: | ||
json_metadata = json.loads(metadata) | ||
else: | ||
json_metadata = {} | ||
|
||
if json_metadata.get('roles') is None: | ||
json_metadata['roles'] = [] | ||
|
||
json_metadata['roles'].append(new_entry) | ||
return json.dumps(json_metadata) | ||
|
||
def build_user_project_entry(meta, session, row): | ||
update_table = sql.Table(USER_PROJECT_TABLE, meta, autoload=True) | ||
q = session.query(update_table) | ||
q = q.filter_by(user_id=row.actor_id) | ||
q = q.filter_by(project_id=row.target_id) | ||
ref = q.first() | ||
if ref is not None: | ||
values = {'data': add_to_dict_list(ref.data, row)} | ||
update = update_table.update().where( | ||
update_table.c.user_id == ref.user_id).where( | ||
update_table.c.project_id == ref.project_id).values(values) | ||
else: | ||
values = {'user_id': row.actor_id, | ||
'project_id': row.target_id, | ||
'data': add_to_dict_list(None, row)} | ||
update = update_table.insert().values(values) | ||
return update | ||
|
||
def build_group_project_entry(meta, session, row): | ||
update_table = sql.Table(GROUP_PROJECT_TABLE, meta, autoload=True) | ||
q = session.query(update_table) | ||
q = q.filter_by(group_id=row.actor_id) | ||
q = q.filter_by(project_id=row.target_id) | ||
ref = q.first() | ||
if ref is not None: | ||
values = {'data': add_to_dict_list(ref.data, row)} | ||
update = update_table.update().where( | ||
update_table.c.group_id == ref.group_id).where( | ||
update_table.c.project_id == ref.project_id).values(values) | ||
else: | ||
values = {'group_id': row.actor_id, | ||
'project_id': row.target_id, | ||
'data': add_to_dict_list(None, row)} | ||
update = update_table.insert().values(values) | ||
return update | ||
|
||
def build_user_domain_entry(meta, session, row): | ||
update_table = sql.Table(USER_DOMAIN_TABLE, meta, autoload=True) | ||
q = session.query(update_table) | ||
q = q.filter_by(user_id=row.actor_id) | ||
q = q.filter_by(domain_id=row.target_id) | ||
ref = q.first() | ||
if ref is not None: | ||
values = {'data': add_to_dict_list(ref.data, row)} | ||
update = update_table.update().where( | ||
update_table.c.user_id == ref.user_id).where( | ||
update_table.c.domain_id == ref.domain_id).values(values) | ||
else: | ||
values = {'user_id': row.actor_id, | ||
'domain_id': row.target_id, | ||
'data': add_to_dict_list(None, row)} | ||
update = update_table.insert().values(values) | ||
return update | ||
|
||
def build_group_domain_entry(meta, session, row): | ||
update_table = sql.Table(GROUP_DOMAIN_TABLE, meta, autoload=True) | ||
q = session.query(update_table) | ||
q = q.filter_by(group_id=row.actor_id) | ||
q = q.filter_by(domain_id=row.target_id) | ||
ref = q.first() | ||
if ref is not None: | ||
values = {'data': add_to_dict_list(ref.data, row)} | ||
update = update_table.update().where( | ||
update_table.c.group_id == ref.group_id).where( | ||
update_table.c.domain_id == ref.domain_id).values(values) | ||
else: | ||
values = {'group_id': row.actor_id, | ||
'domain_id': row.target_id, | ||
'data': add_to_dict_list(None, row)} | ||
update = update_table.insert().values(values) | ||
return update | ||
|
||
def build_update(meta, session, row): | ||
"""Build an update or an insert to the correct metadata table.""" | ||
if row.type == assignment_sql.AssignmentType.USER_PROJECT: | ||
return build_user_project_entry(meta, session, row) | ||
elif row.type == assignment_sql.AssignmentType.GROUP_PROJECT: | ||
return build_group_project_entry(meta, session, row) | ||
elif row.type == assignment_sql.AssignmentType.USER_DOMAIN: | ||
return build_user_domain_entry(meta, session, row) | ||
elif row.type == assignment_sql.AssignmentType.GROUP_DOMAIN: | ||
return build_group_domain_entry(meta, session, row) | ||
# If the row type doesn't match any that we understand we drop | ||
# the data. | ||
|
||
session = sql.orm.sessionmaker(bind=migrate_engine)() | ||
downgrade_table = sql.Table(ASSIGNMENT_TABLE, meta, autoload=True) | ||
for assignment in session.query(downgrade_table).all(): | ||
update = build_update(meta, session, assignment) | ||
if update is not None: | ||
migrate_engine.execute(update) | ||
|
||
# Delete all the rows | ||
migrate_engine.execute(downgrade_table.delete()) | ||
|
||
session.commit() | ||
session.close() | ||
|
||
|
||
def upgrade(migrate_engine): | ||
meta = sql.MetaData() | ||
meta.bind = migrate_engine | ||
|
||
session = sql.orm.sessionmaker(bind=migrate_engine)() | ||
for table_name in GRANT_TABLES: | ||
migrate_grant_table(meta, migrate_engine, session, table_name) | ||
session.commit() | ||
session.close() | ||
|
||
|
||
def downgrade(migrate_engine): | ||
meta = sql.MetaData() | ||
meta.bind = migrate_engine | ||
|
||
downgrade_assignment_table(meta, migrate_engine) |
Oops, something went wrong.