Implement name space for domains
Creates a separate name space for each domain for the name attribute of user, groups and projects - meaning that the names of these entities only have to be unique within that domain. Implementation of this within the SQL backends is handled by simply changing the uniqueness constraints on the relevant attributes. KVS and LDAP backends do not yet support domain separation (blocked by existing restrictions, already raised as bugs). An issue exists for the downward migration with this change in that if the database has been used and populated with the name space in place then the downward migration may fail due to clashing names when you try and revert to a global name space (raised as a separate bug) This patch also improves the group support in the KVS backend and cleans up string quoting in the 016 migration fucntions, and fixes an issue where the SQL update_project was not updating a change in domain_id. Change-Id: I8f0df0e1bf84bfd26b8ef5505fe5fafd930dc78b
This commit is contained in:
parent
b9d8a20fff
commit
ec326b39fa
@ -49,6 +49,7 @@ IntegrityError = sql.exc.IntegrityError
|
|||||||
NotFound = sql.orm.exc.NoResultFound
|
NotFound = sql.orm.exc.NoResultFound
|
||||||
Boolean = sql.Boolean
|
Boolean = sql.Boolean
|
||||||
Text = sql.Text
|
Text = sql.Text
|
||||||
|
UniqueConstraint = sql.UniqueConstraint
|
||||||
|
|
||||||
|
|
||||||
def initialize_decorator(init):
|
def initialize_decorator(init):
|
||||||
|
@ -28,9 +28,10 @@ def upgrade(migrate_engine):
|
|||||||
sql.Column('id', sql.String(64), primary_key=True),
|
sql.Column('id', sql.String(64), primary_key=True),
|
||||||
sql.Column('domain_id', sql.String(64), sql.ForeignKey('domain.id'),
|
sql.Column('domain_id', sql.String(64), sql.ForeignKey('domain.id'),
|
||||||
nullable=False),
|
nullable=False),
|
||||||
sql.Column('name', sql.String(64), unique=True, nullable=False),
|
sql.Column('name', sql.String(64), nullable=False),
|
||||||
sql.Column('description', sql.Text()),
|
sql.Column('description', sql.Text()),
|
||||||
sql.Column('extra', sql.Text()))
|
sql.Column('extra', sql.Text()),
|
||||||
|
sql.UniqueConstraint('domain_id', 'name'))
|
||||||
group_table.create(migrate_engine, checkfirst=True)
|
group_table.create(migrate_engine, checkfirst=True)
|
||||||
|
|
||||||
sql.Table('user', meta, autoload=True)
|
sql.Table('user', meta, autoload=True)
|
||||||
|
@ -70,16 +70,16 @@ def upgrade_user_table_with_copy(meta, migrate_engine, session):
|
|||||||
sql.Column('id', sql.String(64), primary_key=True),
|
sql.Column('id', sql.String(64), primary_key=True),
|
||||||
sql.Column('name', sql.String(64), unique=True, nullable=False),
|
sql.Column('name', sql.String(64), unique=True, nullable=False),
|
||||||
sql.Column('extra', sql.Text()),
|
sql.Column('extra', sql.Text()),
|
||||||
sql.Column("password", sql.String(128)),
|
sql.Column('password', sql.String(128)),
|
||||||
sql.Column("enabled", sql.Boolean, default=True))
|
sql.Column('enabled', sql.Boolean, default=True))
|
||||||
temp_user_table.create(migrate_engine, checkfirst=True)
|
temp_user_table.create(migrate_engine, checkfirst=True)
|
||||||
|
|
||||||
user_table = sql.Table('user', meta, autoload=True)
|
user_table = sql.Table('user', meta, autoload=True)
|
||||||
for user in session.query(user_table):
|
for user in session.query(user_table):
|
||||||
session.execute("insert into temp_user (id, name, extra, "
|
session.execute('insert into temp_user (id, name, extra, '
|
||||||
"password, enabled) "
|
'password, enabled) '
|
||||||
"values ( :id, :name, :extra, "
|
'values ( :id, :name, :extra, '
|
||||||
":password, :enabled);",
|
':password, :enabled);',
|
||||||
{'id': user.id,
|
{'id': user.id,
|
||||||
'name': user.name,
|
'name': user.name,
|
||||||
'extra': user.extra,
|
'extra': user.extra,
|
||||||
@ -99,21 +99,22 @@ def upgrade_user_table_with_copy(meta, migrate_engine, session):
|
|||||||
'user',
|
'user',
|
||||||
meta2,
|
meta2,
|
||||||
sql.Column('id', sql.String(64), primary_key=True),
|
sql.Column('id', sql.String(64), primary_key=True),
|
||||||
sql.Column('name', sql.String(64), unique=True, nullable=False),
|
sql.Column('name', sql.String(64), nullable=False),
|
||||||
sql.Column('extra', sql.Text()),
|
sql.Column('extra', sql.Text()),
|
||||||
sql.Column("password", sql.String(128)),
|
sql.Column("password", sql.String(128)),
|
||||||
sql.Column("enabled", sql.Boolean, default=True),
|
sql.Column("enabled", sql.Boolean, default=True),
|
||||||
sql.Column('domain_id', sql.String(64), sql.ForeignKey('domain.id'),
|
sql.Column('domain_id', sql.String(64), sql.ForeignKey('domain.id'),
|
||||||
nullable=False))
|
nullable=False),
|
||||||
|
sql.UniqueConstraint('domain_id', 'name'))
|
||||||
user_table.create(migrate_engine, checkfirst=True)
|
user_table.create(migrate_engine, checkfirst=True)
|
||||||
|
|
||||||
# Finally copy in the data from our temp table and then clean
|
# Finally copy in the data from our temp table and then clean
|
||||||
# up by deleting our temp table
|
# up by deleting our temp table
|
||||||
for user in session.query(temp_user_table):
|
for user in session.query(temp_user_table):
|
||||||
session.execute("insert into user (id, name, extra, "
|
session.execute('insert into user (id, name, extra, '
|
||||||
"password, enabled, domain_id) "
|
'password, enabled, domain_id) '
|
||||||
"values ( :id, :name, :extra, "
|
'values ( :id, :name, :extra, '
|
||||||
":password, :enabled, :domain_id);",
|
':password, :enabled, :domain_id);',
|
||||||
{'id': user.id,
|
{'id': user.id,
|
||||||
'name': user.name,
|
'name': user.name,
|
||||||
'extra': user.extra,
|
'extra': user.extra,
|
||||||
@ -121,7 +122,7 @@ def upgrade_user_table_with_copy(meta, migrate_engine, session):
|
|||||||
'enabled': user.enabled,
|
'enabled': user.enabled,
|
||||||
'domain_id': DEFAULT_DOMAIN_ID})
|
'domain_id': DEFAULT_DOMAIN_ID})
|
||||||
_enable_foreign_constraints(session, migrate_engine)
|
_enable_foreign_constraints(session, migrate_engine)
|
||||||
session.execute("drop table temp_user;")
|
session.execute('drop table temp_user;')
|
||||||
|
|
||||||
|
|
||||||
def upgrade_project_table_with_copy(meta, migrate_engine, session):
|
def upgrade_project_table_with_copy(meta, migrate_engine, session):
|
||||||
@ -138,16 +139,16 @@ def upgrade_project_table_with_copy(meta, migrate_engine, session):
|
|||||||
sql.Column('id', sql.String(64), primary_key=True),
|
sql.Column('id', sql.String(64), primary_key=True),
|
||||||
sql.Column('name', sql.String(64), unique=True, nullable=False),
|
sql.Column('name', sql.String(64), unique=True, nullable=False),
|
||||||
sql.Column('extra', sql.Text()),
|
sql.Column('extra', sql.Text()),
|
||||||
sql.Column("description", sql.Text()),
|
sql.Column('description', sql.Text()),
|
||||||
sql.Column("enabled", sql.Boolean, default=True))
|
sql.Column('enabled', sql.Boolean, default=True))
|
||||||
temp_project_table.create(migrate_engine, checkfirst=True)
|
temp_project_table.create(migrate_engine, checkfirst=True)
|
||||||
|
|
||||||
project_table = sql.Table('project', meta, autoload=True)
|
project_table = sql.Table('project', meta, autoload=True)
|
||||||
for project in session.query(project_table):
|
for project in session.query(project_table):
|
||||||
session.execute("insert into temp_project (id, name, extra, "
|
session.execute('insert into temp_project (id, name, extra, '
|
||||||
"description, enabled) "
|
'description, enabled) '
|
||||||
"values ( :id, :name, :extra, "
|
'values ( :id, :name, :extra, '
|
||||||
":description, :enabled);",
|
':description, :enabled);',
|
||||||
{'id': project.id,
|
{'id': project.id,
|
||||||
'name': project.name,
|
'name': project.name,
|
||||||
'extra': project.extra,
|
'extra': project.extra,
|
||||||
@ -157,7 +158,7 @@ def upgrade_project_table_with_copy(meta, migrate_engine, session):
|
|||||||
# Now switch off constraints while we drop and then re-create the
|
# Now switch off constraints while we drop and then re-create the
|
||||||
# project table, with the additional domain_id column
|
# project table, with the additional domain_id column
|
||||||
_disable_foreign_constraints(session, migrate_engine)
|
_disable_foreign_constraints(session, migrate_engine)
|
||||||
session.execute("drop table project;")
|
session.execute('drop table project;')
|
||||||
# Need to create a new metadata stream since we are going to load a
|
# Need to create a new metadata stream since we are going to load a
|
||||||
# different version of the project table
|
# different version of the project table
|
||||||
meta2 = sql.MetaData()
|
meta2 = sql.MetaData()
|
||||||
@ -167,21 +168,22 @@ def upgrade_project_table_with_copy(meta, migrate_engine, session):
|
|||||||
'project',
|
'project',
|
||||||
meta2,
|
meta2,
|
||||||
sql.Column('id', sql.String(64), primary_key=True),
|
sql.Column('id', sql.String(64), primary_key=True),
|
||||||
sql.Column('name', sql.String(64), unique=True, nullable=False),
|
sql.Column('name', sql.String(64), nullable=False),
|
||||||
sql.Column('extra', sql.Text()),
|
sql.Column('extra', sql.Text()),
|
||||||
sql.Column('description', sql.Text()),
|
sql.Column('description', sql.Text()),
|
||||||
sql.Column("enabled", sql.Boolean, default=True),
|
sql.Column('enabled', sql.Boolean, default=True),
|
||||||
sql.Column('domain_id', sql.String(64), sql.ForeignKey('domain.id'),
|
sql.Column('domain_id', sql.String(64), sql.ForeignKey('domain.id'),
|
||||||
nullable=False))
|
nullable=False),
|
||||||
|
sql.UniqueConstraint('domain_id', 'name'))
|
||||||
project_table.create(migrate_engine, checkfirst=True)
|
project_table.create(migrate_engine, checkfirst=True)
|
||||||
|
|
||||||
# Finally copy in the data from our temp table and then clean
|
# Finally copy in the data from our temp table and then clean
|
||||||
# up by deleting our temp table
|
# up by deleting our temp table
|
||||||
for project in session.query(temp_project_table):
|
for project in session.query(temp_project_table):
|
||||||
session.execute("insert into project (id, name, extra, "
|
session.execute('insert into project (id, name, extra, '
|
||||||
"description, enabled, domain_id) "
|
'description, enabled, domain_id) '
|
||||||
"values ( :id, :name, :extra, "
|
'values ( :id, :name, :extra, '
|
||||||
":description, :enabled, :domain_id);",
|
':description, :enabled, :domain_id);',
|
||||||
{'id': project.id,
|
{'id': project.id,
|
||||||
'name': project.name,
|
'name': project.name,
|
||||||
'extra': project.extra,
|
'extra': project.extra,
|
||||||
@ -189,7 +191,7 @@ def upgrade_project_table_with_copy(meta, migrate_engine, session):
|
|||||||
'enabled': project.enabled,
|
'enabled': project.enabled,
|
||||||
'domain_id': DEFAULT_DOMAIN_ID})
|
'domain_id': DEFAULT_DOMAIN_ID})
|
||||||
_enable_foreign_constraints(session, migrate_engine)
|
_enable_foreign_constraints(session, migrate_engine)
|
||||||
session.execute("drop table temp_project;")
|
session.execute('drop table temp_project;')
|
||||||
|
|
||||||
|
|
||||||
def downgrade_user_table_with_copy(meta, migrate_engine, session):
|
def downgrade_user_table_with_copy(meta, migrate_engine, session):
|
||||||
@ -204,22 +206,19 @@ def downgrade_user_table_with_copy(meta, migrate_engine, session):
|
|||||||
meta,
|
meta,
|
||||||
sql.Column('id', sql.String(64), primary_key=True),
|
sql.Column('id', sql.String(64), primary_key=True),
|
||||||
sql.Column('name', sql.String(64), unique=True, nullable=False),
|
sql.Column('name', sql.String(64), unique=True, nullable=False),
|
||||||
# Temporary table, so no need to make it a foreign key
|
sql.Column('password', sql.String(128)),
|
||||||
sql.Column('domain_id', sql.String(64), nullable=False),
|
sql.Column('enabled', sql.Boolean, default=True),
|
||||||
sql.Column("password", sql.String(128)),
|
|
||||||
sql.Column("enabled", sql.Boolean, default=True),
|
|
||||||
sql.Column('extra', sql.Text()))
|
sql.Column('extra', sql.Text()))
|
||||||
temp_user_table.create(migrate_engine, checkfirst=True)
|
temp_user_table.create(migrate_engine, checkfirst=True)
|
||||||
|
|
||||||
user_table = sql.Table('user', meta, autoload=True)
|
user_table = sql.Table('user', meta, autoload=True)
|
||||||
for user in session.query(user_table):
|
for user in session.query(user_table):
|
||||||
session.execute("insert into temp_user (id, name, domain_id, "
|
session.execute('insert into temp_user (id, name, '
|
||||||
"password, enabled, extra) "
|
'password, enabled, extra) '
|
||||||
"values ( :id, :name, :domain_id, "
|
'values ( :id, :name, '
|
||||||
":password, :enabled, :extra);",
|
':password, :enabled, :extra);',
|
||||||
{'id': user.id,
|
{'id': user.id,
|
||||||
'name': user.name,
|
'name': user.name,
|
||||||
'domain_id': user.domain_id,
|
|
||||||
'password': user.password,
|
'password': user.password,
|
||||||
'enabled': user.enabled,
|
'enabled': user.enabled,
|
||||||
'extra': user.extra})
|
'extra': user.extra})
|
||||||
@ -227,7 +226,7 @@ def downgrade_user_table_with_copy(meta, migrate_engine, session):
|
|||||||
# Now switch off constraints while we drop and then re-create the
|
# Now switch off constraints while we drop and then re-create the
|
||||||
# user table, less the columns we wanted to drop
|
# user table, less the columns we wanted to drop
|
||||||
_disable_foreign_constraints(session, migrate_engine)
|
_disable_foreign_constraints(session, migrate_engine)
|
||||||
session.execute("drop table user;")
|
session.execute('drop table user;')
|
||||||
# Need to create a new metadata stream since we are going to load a
|
# Need to create a new metadata stream since we are going to load a
|
||||||
# different version of the user table
|
# different version of the user table
|
||||||
meta2 = sql.MetaData()
|
meta2 = sql.MetaData()
|
||||||
@ -238,24 +237,24 @@ def downgrade_user_table_with_copy(meta, migrate_engine, session):
|
|||||||
sql.Column('id', sql.String(64), primary_key=True),
|
sql.Column('id', sql.String(64), primary_key=True),
|
||||||
sql.Column('name', sql.String(64), unique=True, nullable=False),
|
sql.Column('name', sql.String(64), unique=True, nullable=False),
|
||||||
sql.Column('extra', sql.Text()),
|
sql.Column('extra', sql.Text()),
|
||||||
sql.Column("password", sql.String(128)),
|
sql.Column('password', sql.String(128)),
|
||||||
sql.Column("enabled", sql.Boolean, default=True))
|
sql.Column('enabled', sql.Boolean, default=True))
|
||||||
user_table.create(migrate_engine, checkfirst=True)
|
user_table.create(migrate_engine, checkfirst=True)
|
||||||
_enable_foreign_constraints(session, migrate_engine)
|
_enable_foreign_constraints(session, migrate_engine)
|
||||||
|
|
||||||
# Finally copy in the data from our temp table and then clean
|
# Finally copy in the data from our temp table and then clean
|
||||||
# up by deleting our temp table
|
# up by deleting our temp table
|
||||||
for user in session.query(temp_user_table):
|
for user in session.query(temp_user_table):
|
||||||
session.execute("insert into user (id, name, extra, "
|
session.execute('insert into user (id, name, extra, '
|
||||||
"password, enabled) "
|
'password, enabled) '
|
||||||
"values ( :id, :name, :extra, "
|
'values ( :id, :name, :extra, '
|
||||||
":password, :enabled);",
|
':password, :enabled);',
|
||||||
{'id': user.id,
|
{'id': user.id,
|
||||||
'name': user.name,
|
'name': user.name,
|
||||||
'extra': user.extra,
|
'extra': user.extra,
|
||||||
'password': user.password,
|
'password': user.password,
|
||||||
'enabled': user.enabled})
|
'enabled': user.enabled})
|
||||||
session.execute("drop table temp_user;")
|
session.execute('drop table temp_user;')
|
||||||
|
|
||||||
|
|
||||||
def downgrade_project_table_with_copy(meta, migrate_engine, session):
|
def downgrade_project_table_with_copy(meta, migrate_engine, session):
|
||||||
@ -270,22 +269,19 @@ def downgrade_project_table_with_copy(meta, migrate_engine, session):
|
|||||||
meta,
|
meta,
|
||||||
sql.Column('id', sql.String(64), primary_key=True),
|
sql.Column('id', sql.String(64), primary_key=True),
|
||||||
sql.Column('name', sql.String(64), unique=True, nullable=False),
|
sql.Column('name', sql.String(64), unique=True, nullable=False),
|
||||||
# Temporary table, so no need to make it a foreign key
|
|
||||||
sql.Column('domain_id', sql.String(64), nullable=False),
|
|
||||||
sql.Column('description', sql.Text()),
|
sql.Column('description', sql.Text()),
|
||||||
sql.Column("enabled", sql.Boolean, default=True),
|
sql.Column('enabled', sql.Boolean, default=True),
|
||||||
sql.Column('extra', sql.Text()))
|
sql.Column('extra', sql.Text()))
|
||||||
temp_project_table.create(migrate_engine, checkfirst=True)
|
temp_project_table.create(migrate_engine, checkfirst=True)
|
||||||
|
|
||||||
project_table = sql.Table('project', meta, autoload=True)
|
project_table = sql.Table('project', meta, autoload=True)
|
||||||
for project in session.query(project_table):
|
for project in session.query(project_table):
|
||||||
session.execute("insert into temp_project (id, name, domain_id, "
|
session.execute('insert into temp_project (id, name, '
|
||||||
"description, enabled, extra) "
|
'description, enabled, extra) '
|
||||||
"values ( :id, :name, :domain_id, "
|
'values ( :id, :name, '
|
||||||
":description, :enabled, :extra);",
|
':description, :enabled, :extra);',
|
||||||
{'id': project.id,
|
{'id': project.id,
|
||||||
'name': project.name,
|
'name': project.name,
|
||||||
'domain_id': project.domain_id,
|
|
||||||
'description': project.description,
|
'description': project.description,
|
||||||
'enabled': project.enabled,
|
'enabled': project.enabled,
|
||||||
'extra': project.extra})
|
'extra': project.extra})
|
||||||
@ -293,7 +289,7 @@ def downgrade_project_table_with_copy(meta, migrate_engine, session):
|
|||||||
# Now switch off constraints while we drop and then re-create the
|
# Now switch off constraints while we drop and then re-create the
|
||||||
# project table, less the columns we wanted to drop
|
# project table, less the columns we wanted to drop
|
||||||
_disable_foreign_constraints(session, migrate_engine)
|
_disable_foreign_constraints(session, migrate_engine)
|
||||||
session.execute("drop table project;")
|
session.execute('drop table project;')
|
||||||
# Need to create a new metadata stream since we are going to load a
|
# Need to create a new metadata stream since we are going to load a
|
||||||
# different version of the project table
|
# different version of the project table
|
||||||
meta2 = sql.MetaData()
|
meta2 = sql.MetaData()
|
||||||
@ -304,18 +300,18 @@ def downgrade_project_table_with_copy(meta, migrate_engine, session):
|
|||||||
sql.Column('id', sql.String(64), primary_key=True),
|
sql.Column('id', sql.String(64), primary_key=True),
|
||||||
sql.Column('name', sql.String(64), unique=True, nullable=False),
|
sql.Column('name', sql.String(64), unique=True, nullable=False),
|
||||||
sql.Column('extra', sql.Text()),
|
sql.Column('extra', sql.Text()),
|
||||||
sql.Column("description", sql.Text()),
|
sql.Column('description', sql.Text()),
|
||||||
sql.Column("enabled", sql.Boolean, default=True))
|
sql.Column('enabled', sql.Boolean, default=True))
|
||||||
project_table.create(migrate_engine, checkfirst=True)
|
project_table.create(migrate_engine, checkfirst=True)
|
||||||
_enable_foreign_constraints(session, migrate_engine)
|
_enable_foreign_constraints(session, migrate_engine)
|
||||||
|
|
||||||
# Finally copy in the data from our temp table and then clean
|
# Finally copy in the data from our temp table and then clean
|
||||||
# up by deleting our temp table
|
# up by deleting our temp table
|
||||||
for project in session.query(temp_project_table):
|
for project in session.query(temp_project_table):
|
||||||
session.execute("insert into project (id, name, extra, "
|
session.execute('insert into project (id, name, extra, '
|
||||||
"description, enabled) "
|
'description, enabled) '
|
||||||
"values ( :id, :name, :extra, "
|
'values ( :id, :name, :extra, '
|
||||||
":description, :enabled);",
|
':description, :enabled);',
|
||||||
{'id': project.id,
|
{'id': project.id,
|
||||||
'name': project.name,
|
'name': project.name,
|
||||||
'extra': project.extra,
|
'extra': project.extra,
|
||||||
@ -345,6 +341,11 @@ def upgrade_user_table_with_col_create(meta, migrate_engine, session):
|
|||||||
session.commit()
|
session.commit()
|
||||||
user_table.columns.domain_id.alter(nullable=False)
|
user_table.columns.domain_id.alter(nullable=False)
|
||||||
|
|
||||||
|
# Finally, change the uniqueness settings for the name attribute
|
||||||
|
session.execute('ALTER TABLE "user" DROP CONSTRAINT user_name_key;')
|
||||||
|
session.execute('ALTER TABLE "user" ADD CONSTRAINT user_dom_name_unique '
|
||||||
|
'UNIQUE (domain_id, name);')
|
||||||
|
|
||||||
|
|
||||||
def upgrade_project_table_with_col_create(meta, migrate_engine, session):
|
def upgrade_project_table_with_col_create(meta, migrate_engine, session):
|
||||||
# Create the domain_id column. We want this to be not nullable
|
# Create the domain_id column. We want this to be not nullable
|
||||||
@ -367,8 +368,19 @@ def upgrade_project_table_with_col_create(meta, migrate_engine, session):
|
|||||||
session.commit()
|
session.commit()
|
||||||
project_table.columns.domain_id.alter(nullable=False)
|
project_table.columns.domain_id.alter(nullable=False)
|
||||||
|
|
||||||
|
# Finally, change the uniqueness settings for the name attribute
|
||||||
|
session.execute('ALTER TABLE project DROP CONSTRAINT tenant_name_key;')
|
||||||
|
session.execute('ALTER TABLE project ADD CONSTRAINT proj_dom_name_unique '
|
||||||
|
'UNIQUE (domain_id, name);')
|
||||||
|
|
||||||
def downgrade_user_table_with_col_drop(meta, migrate_engine):
|
|
||||||
|
def downgrade_user_table_with_col_drop(meta, migrate_engine, session):
|
||||||
|
# Revert uniqueness settings for the name attribute
|
||||||
|
session.execute('ALTER TABLE "user" DROP CONSTRAINT '
|
||||||
|
'user_dom_name_unique;')
|
||||||
|
session.execute('ALTER TABLE "user" ADD UNIQUE (name);')
|
||||||
|
session.commit()
|
||||||
|
# And now go ahead an drop the domain_id column
|
||||||
domain_table = sql.Table('domain', meta, autoload=True)
|
domain_table = sql.Table('domain', meta, autoload=True)
|
||||||
user_table = sql.Table('user', meta, autoload=True)
|
user_table = sql.Table('user', meta, autoload=True)
|
||||||
column = sql.Column('domain_id', sql.String(64),
|
column = sql.Column('domain_id', sql.String(64),
|
||||||
@ -376,7 +388,14 @@ def downgrade_user_table_with_col_drop(meta, migrate_engine):
|
|||||||
column.drop(user_table)
|
column.drop(user_table)
|
||||||
|
|
||||||
|
|
||||||
def downgrade_project_table_with_col_drop(meta, migrate_engine):
|
def downgrade_project_table_with_col_drop(meta, migrate_engine, session):
|
||||||
|
# Revert uniqueness settings for the name attribute
|
||||||
|
session.execute('ALTER TABLE project DROP CONSTRAINT '
|
||||||
|
'proj_dom_name_unique;')
|
||||||
|
session.execute('ALTER TABLE project ADD CONSTRAINT tenant_name_key '
|
||||||
|
'UNIQUE (name);')
|
||||||
|
session.commit()
|
||||||
|
# And now go ahead an drop the domain_id column
|
||||||
domain_table = sql.Table('domain', meta, autoload=True)
|
domain_table = sql.Table('domain', meta, autoload=True)
|
||||||
project_table = sql.Table('project', meta, autoload=True)
|
project_table = sql.Table('project', meta, autoload=True)
|
||||||
column = sql.Column('domain_id', sql.String(64),
|
column = sql.Column('domain_id', sql.String(64),
|
||||||
@ -408,7 +427,7 @@ def downgrade(migrate_engine):
|
|||||||
else:
|
else:
|
||||||
# MySQL should in theory be able to use this path, but seems to
|
# MySQL should in theory be able to use this path, but seems to
|
||||||
# have problems dropping columns which are foreign keys
|
# have problems dropping columns which are foreign keys
|
||||||
downgrade_user_table_with_col_drop(meta, migrate_engine)
|
downgrade_user_table_with_col_drop(meta, migrate_engine, session)
|
||||||
downgrade_project_table_with_col_drop(meta, migrate_engine)
|
downgrade_project_table_with_col_drop(meta, migrate_engine, session)
|
||||||
session.commit()
|
session.commit()
|
||||||
session.close()
|
session.close()
|
||||||
|
@ -573,7 +573,23 @@ class Identity(kvs.Base, identity.Driver):
|
|||||||
# group crud
|
# group crud
|
||||||
|
|
||||||
def create_group(self, group_id, group):
|
def create_group(self, group_id, group):
|
||||||
|
try:
|
||||||
|
return self.db.get('group-%s' % group_id)
|
||||||
|
except exception.NotFound:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
msg = _('Duplicate ID, %s.') % group_id
|
||||||
|
raise exception.Conflict(type='group', details=msg)
|
||||||
|
try:
|
||||||
|
self.db.get('group_name-%s' % group['name'])
|
||||||
|
except exception.NotFound:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
msg = _('Duplicate name, %s.') % group['name']
|
||||||
|
raise exception.Conflict(type='group', details=msg)
|
||||||
|
|
||||||
self.db.set('group-%s' % group_id, group)
|
self.db.set('group-%s' % group_id, group)
|
||||||
|
self.db.set('group_name-%s' % group['name'], group)
|
||||||
group_list = set(self.db.get('group_list', []))
|
group_list = set(self.db.get('group_list', []))
|
||||||
group_list.add(group_id)
|
group_list.add(group_id)
|
||||||
self.db.set('group_list', list(group_list))
|
self.db.set('group_list', list(group_list))
|
||||||
@ -590,10 +606,33 @@ class Identity(kvs.Base, identity.Driver):
|
|||||||
raise exception.GroupNotFound(group_id=group_id)
|
raise exception.GroupNotFound(group_id=group_id)
|
||||||
|
|
||||||
def update_group(self, group_id, group):
|
def update_group(self, group_id, group):
|
||||||
|
# First, make sure we are not trying to change the
|
||||||
|
# name to one that is already in use
|
||||||
|
try:
|
||||||
|
self.db.get('group_name-%s' % group['name'])
|
||||||
|
except exception.NotFound:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
msg = _('Duplicate name, %s.') % group['name']
|
||||||
|
raise exception.Conflict(type='group', details=msg)
|
||||||
|
|
||||||
|
# Now, get the old name and delete it
|
||||||
|
try:
|
||||||
|
old_group = self.db.get('group-%s' % group_id)
|
||||||
|
except exception.NotFound:
|
||||||
|
raise exception.GroupNotFound(group_id=group_id)
|
||||||
|
self.db.delete('group_name-%s' % old_group['name'])
|
||||||
|
|
||||||
|
# Finally, actually do the update
|
||||||
self.db.set('group-%s' % group_id, group)
|
self.db.set('group-%s' % group_id, group)
|
||||||
|
self.db.set('group_name-%s' % group['name'], group)
|
||||||
return group
|
return group
|
||||||
|
|
||||||
def delete_group(self, group_id):
|
def delete_group(self, group_id):
|
||||||
|
try:
|
||||||
|
group = self.db.get('group-%s' % group_id)
|
||||||
|
except exception.NotFound:
|
||||||
|
raise exception.GroupNotFound(group_id=group_id)
|
||||||
# Delete any entries in the group lists of all users
|
# Delete any entries in the group lists of all users
|
||||||
user_keys = filter(lambda x: x.startswith("user-"), self.db.keys())
|
user_keys = filter(lambda x: x.startswith("user-"), self.db.keys())
|
||||||
user_refs = [self.db.get(key) for key in user_keys]
|
user_refs = [self.db.get(key) for key in user_keys]
|
||||||
@ -605,6 +644,7 @@ class Identity(kvs.Base, identity.Driver):
|
|||||||
|
|
||||||
# Now delete the group itself
|
# Now delete the group itself
|
||||||
self.db.delete('group-%s' % group_id)
|
self.db.delete('group-%s' % group_id)
|
||||||
|
self.db.delete('group_name-%s' % group['name'])
|
||||||
group_list = set(self.db.get('group_list', []))
|
group_list = set(self.db.get('group_list', []))
|
||||||
group_list.remove(group_id)
|
group_list.remove(group_id)
|
||||||
self.db.set('group_list', list(group_list))
|
self.db.set('group_list', list(group_list))
|
||||||
|
@ -42,23 +42,29 @@ class User(sql.ModelBase, sql.DictBase):
|
|||||||
__tablename__ = 'user'
|
__tablename__ = 'user'
|
||||||
attributes = ['id', 'name', 'domain_id', 'password', 'enabled']
|
attributes = ['id', 'name', 'domain_id', 'password', 'enabled']
|
||||||
id = sql.Column(sql.String(64), primary_key=True)
|
id = sql.Column(sql.String(64), primary_key=True)
|
||||||
name = sql.Column(sql.String(64), unique=True, nullable=False)
|
name = sql.Column(sql.String(64), nullable=False)
|
||||||
domain_id = sql.Column(sql.String(64), sql.ForeignKey('domain.id'),
|
domain_id = sql.Column(sql.String(64), sql.ForeignKey('domain.id'),
|
||||||
nullable=False)
|
nullable=False)
|
||||||
password = sql.Column(sql.String(128))
|
password = sql.Column(sql.String(128))
|
||||||
enabled = sql.Column(sql.Boolean)
|
enabled = sql.Column(sql.Boolean)
|
||||||
extra = sql.Column(sql.JsonBlob())
|
extra = sql.Column(sql.JsonBlob())
|
||||||
|
# Unique constraint across two columns to create the separation
|
||||||
|
# rather than just only 'name' being unique
|
||||||
|
__table_args__ = (sql.UniqueConstraint('domain_id', 'name'), {})
|
||||||
|
|
||||||
|
|
||||||
class Group(sql.ModelBase, sql.DictBase):
|
class Group(sql.ModelBase, sql.DictBase):
|
||||||
__tablename__ = 'group'
|
__tablename__ = 'group'
|
||||||
attributes = ['id', 'name', 'domain_id']
|
attributes = ['id', 'name', 'domain_id']
|
||||||
id = sql.Column(sql.String(64), primary_key=True)
|
id = sql.Column(sql.String(64), primary_key=True)
|
||||||
name = sql.Column(sql.String(64), unique=True, nullable=False)
|
name = sql.Column(sql.String(64), nullable=False)
|
||||||
domain_id = sql.Column(sql.String(64), sql.ForeignKey('domain.id'),
|
domain_id = sql.Column(sql.String(64), sql.ForeignKey('domain.id'),
|
||||||
nullable=False)
|
nullable=False)
|
||||||
description = sql.Column(sql.Text())
|
description = sql.Column(sql.Text())
|
||||||
extra = sql.Column(sql.JsonBlob())
|
extra = sql.Column(sql.JsonBlob())
|
||||||
|
# Unique constraint across two columns to create the separation
|
||||||
|
# rather than just only 'name' being unique
|
||||||
|
__table_args__ = (sql.UniqueConstraint('domain_id', 'name'), {})
|
||||||
|
|
||||||
|
|
||||||
class Credential(sql.ModelBase, sql.DictBase):
|
class Credential(sql.ModelBase, sql.DictBase):
|
||||||
@ -87,12 +93,15 @@ class Project(sql.ModelBase, sql.DictBase):
|
|||||||
__tablename__ = 'project'
|
__tablename__ = 'project'
|
||||||
attributes = ['id', 'name', 'domain_id']
|
attributes = ['id', 'name', 'domain_id']
|
||||||
id = sql.Column(sql.String(64), primary_key=True)
|
id = sql.Column(sql.String(64), primary_key=True)
|
||||||
name = sql.Column(sql.String(64), unique=True, nullable=False)
|
name = sql.Column(sql.String(64), nullable=False)
|
||||||
domain_id = sql.Column(sql.String(64), sql.ForeignKey('domain.id'),
|
domain_id = sql.Column(sql.String(64), sql.ForeignKey('domain.id'),
|
||||||
nullable=False)
|
nullable=False)
|
||||||
description = sql.Column(sql.Text())
|
description = sql.Column(sql.Text())
|
||||||
enabled = sql.Column(sql.Boolean)
|
enabled = sql.Column(sql.Boolean)
|
||||||
extra = sql.Column(sql.JsonBlob())
|
extra = sql.Column(sql.JsonBlob())
|
||||||
|
# Unique constraint across two columns to create the separation
|
||||||
|
# rather than just only 'name' being unique
|
||||||
|
__table_args__ = (sql.UniqueConstraint('domain_id', 'name'), {})
|
||||||
|
|
||||||
|
|
||||||
class Role(sql.ModelBase, sql.DictBase):
|
class Role(sql.ModelBase, sql.DictBase):
|
||||||
@ -451,14 +460,15 @@ class Identity(sql.Base, identity.Driver):
|
|||||||
tenant_ref = session.query(Project).filter_by(id=tenant_id).one()
|
tenant_ref = session.query(Project).filter_by(id=tenant_id).one()
|
||||||
except sql.NotFound:
|
except sql.NotFound:
|
||||||
raise exception.ProjectNotFound(project_id=tenant_id)
|
raise exception.ProjectNotFound(project_id=tenant_id)
|
||||||
# FIXME(henry-nash) Think about how we detect potential name clash
|
|
||||||
# when we move domains
|
|
||||||
with session.begin():
|
with session.begin():
|
||||||
old_project_dict = tenant_ref.to_dict()
|
old_project_dict = tenant_ref.to_dict()
|
||||||
for k in tenant:
|
for k in tenant:
|
||||||
old_project_dict[k] = tenant[k]
|
old_project_dict[k] = tenant[k]
|
||||||
new_project = Project.from_dict(old_project_dict)
|
new_project = Project.from_dict(old_project_dict)
|
||||||
tenant_ref.name = new_project.name
|
for attr in Project.attributes:
|
||||||
|
if attr != 'id':
|
||||||
|
setattr(tenant_ref, attr, getattr(new_project, attr))
|
||||||
tenant_ref.extra = new_project.extra
|
tenant_ref.extra = new_project.extra
|
||||||
session.flush()
|
session.flush()
|
||||||
return tenant_ref.to_dict(include_extra_dict=True)
|
return tenant_ref.to_dict(include_extra_dict=True)
|
||||||
@ -675,8 +685,7 @@ class Identity(sql.Base, identity.Driver):
|
|||||||
session = self.get_session()
|
session = self.get_session()
|
||||||
if 'id' in user and user_id != user['id']:
|
if 'id' in user and user_id != user['id']:
|
||||||
raise exception.ValidationError('Cannot change user ID')
|
raise exception.ValidationError('Cannot change user ID')
|
||||||
# FIXME(henry-nash) Think about how we detect potential name clash
|
|
||||||
# when we move domains
|
|
||||||
with session.begin():
|
with session.begin():
|
||||||
user_ref = session.query(User).filter_by(id=user_id).first()
|
user_ref = session.query(User).filter_by(id=user_id).first()
|
||||||
if user_ref is None:
|
if user_ref is None:
|
||||||
@ -806,8 +815,7 @@ class Identity(sql.Base, identity.Driver):
|
|||||||
@handle_conflicts(type='group')
|
@handle_conflicts(type='group')
|
||||||
def update_group(self, group_id, group):
|
def update_group(self, group_id, group):
|
||||||
session = self.get_session()
|
session = self.get_session()
|
||||||
# FIXME(henry-nash) Think about how we detect potential name clash
|
|
||||||
# when we move domains
|
|
||||||
with session.begin():
|
with session.begin():
|
||||||
ref = session.query(Group).filter_by(id=group_id).first()
|
ref = session.query(Group).filter_by(id=group_id).first()
|
||||||
if ref is None:
|
if ref is None:
|
||||||
|
@ -285,6 +285,59 @@ class IdentityTests(object):
|
|||||||
'fake2',
|
'fake2',
|
||||||
user)
|
user)
|
||||||
|
|
||||||
|
def test_create_duplicate_user_name_in_different_domains(self):
|
||||||
|
new_domain = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
|
||||||
|
self.identity_api.create_domain(new_domain['id'], new_domain)
|
||||||
|
user1 = {'id': uuid.uuid4().hex,
|
||||||
|
'name': uuid.uuid4().hex,
|
||||||
|
'domain_id': DEFAULT_DOMAIN_ID,
|
||||||
|
'password': uuid.uuid4().hex}
|
||||||
|
user2 = {'id': uuid.uuid4().hex,
|
||||||
|
'name': user1['name'],
|
||||||
|
'domain_id': new_domain['id'],
|
||||||
|
'password': uuid.uuid4().hex}
|
||||||
|
self.identity_api.create_user(user1['id'], user1)
|
||||||
|
self.identity_api.create_user(user2['id'], user2)
|
||||||
|
|
||||||
|
def test_move_user_between_domains(self):
|
||||||
|
domain1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
|
||||||
|
self.identity_api.create_domain(domain1['id'], domain1)
|
||||||
|
domain2 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
|
||||||
|
self.identity_api.create_domain(domain2['id'], domain2)
|
||||||
|
user = {'id': uuid.uuid4().hex,
|
||||||
|
'name': uuid.uuid4().hex,
|
||||||
|
'domain_id': domain1['id'],
|
||||||
|
'password': uuid.uuid4().hex}
|
||||||
|
self.identity_api.create_user(user['id'], user)
|
||||||
|
user['domain_id'] = domain2['id']
|
||||||
|
self.identity_api.update_user(user['id'], user)
|
||||||
|
|
||||||
|
def test_move_user_between_domains_with_clashing_names_fails(self):
|
||||||
|
domain1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
|
||||||
|
self.identity_api.create_domain(domain1['id'], domain1)
|
||||||
|
domain2 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
|
||||||
|
self.identity_api.create_domain(domain2['id'], domain2)
|
||||||
|
# First, create a user in domain1
|
||||||
|
user1 = {'id': uuid.uuid4().hex,
|
||||||
|
'name': uuid.uuid4().hex,
|
||||||
|
'domain_id': domain1['id'],
|
||||||
|
'password': uuid.uuid4().hex}
|
||||||
|
self.identity_api.create_user(user1['id'], user1)
|
||||||
|
# Now create a user in domain2 with a potentially clashing
|
||||||
|
# name - which should work since we have domain separation
|
||||||
|
user2 = {'id': uuid.uuid4().hex,
|
||||||
|
'name': user1['name'],
|
||||||
|
'domain_id': domain2['id'],
|
||||||
|
'password': uuid.uuid4().hex}
|
||||||
|
self.identity_api.create_user(user2['id'], user2)
|
||||||
|
# Now try and move user1 into the 2nd domain - which should
|
||||||
|
# fail since the names clash
|
||||||
|
user1['domain_id'] = domain2['id']
|
||||||
|
self.assertRaises(exception.Conflict,
|
||||||
|
self.identity_api.update_user,
|
||||||
|
user1['id'],
|
||||||
|
user1)
|
||||||
|
|
||||||
def test_rename_duplicate_user_name_fails(self):
|
def test_rename_duplicate_user_name_fails(self):
|
||||||
user1 = {'id': 'fake1',
|
user1 = {'id': 'fake1',
|
||||||
'name': 'fake1',
|
'name': 'fake1',
|
||||||
@ -342,6 +395,52 @@ class IdentityTests(object):
|
|||||||
'fake1',
|
'fake1',
|
||||||
tenant)
|
tenant)
|
||||||
|
|
||||||
|
def test_create_duplicate_project_name_in_different_domains(self):
|
||||||
|
new_domain = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
|
||||||
|
self.identity_api.create_domain(new_domain['id'], new_domain)
|
||||||
|
tenant1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
|
||||||
|
'domain_id': DEFAULT_DOMAIN_ID}
|
||||||
|
tenant2 = {'id': uuid.uuid4().hex, 'name': tenant1['name'],
|
||||||
|
'domain_id': new_domain['id']}
|
||||||
|
self.identity_api.create_project(tenant1['id'], tenant1)
|
||||||
|
self.identity_api.create_project(tenant2['id'], tenant2)
|
||||||
|
|
||||||
|
def test_move_project_between_domains(self):
|
||||||
|
domain1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
|
||||||
|
self.identity_api.create_domain(domain1['id'], domain1)
|
||||||
|
domain2 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
|
||||||
|
self.identity_api.create_domain(domain2['id'], domain2)
|
||||||
|
project = {'id': uuid.uuid4().hex,
|
||||||
|
'name': uuid.uuid4().hex,
|
||||||
|
'domain_id': domain1['id']}
|
||||||
|
self.identity_api.create_project(project['id'], project)
|
||||||
|
project['domain_id'] = domain2['id']
|
||||||
|
self.identity_api.update_project(project['id'], project)
|
||||||
|
|
||||||
|
def test_move_project_between_domains_with_clashing_names_fails(self):
|
||||||
|
domain1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
|
||||||
|
self.identity_api.create_domain(domain1['id'], domain1)
|
||||||
|
domain2 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
|
||||||
|
self.identity_api.create_domain(domain2['id'], domain2)
|
||||||
|
# First, create a project in domain1
|
||||||
|
project1 = {'id': uuid.uuid4().hex,
|
||||||
|
'name': uuid.uuid4().hex,
|
||||||
|
'domain_id': domain1['id']}
|
||||||
|
self.identity_api.create_project(project1['id'], project1)
|
||||||
|
# Now create a project in domain2 with a potentially clashing
|
||||||
|
# name - which should work since we have domain separation
|
||||||
|
project2 = {'id': uuid.uuid4().hex,
|
||||||
|
'name': project1['name'],
|
||||||
|
'domain_id': domain2['id']}
|
||||||
|
self.identity_api.create_project(project2['id'], project2)
|
||||||
|
# Now try and move project1 into the 2nd domain - which should
|
||||||
|
# fail since the names clash
|
||||||
|
project1['domain_id'] = domain2['id']
|
||||||
|
self.assertRaises(exception.Conflict,
|
||||||
|
self.identity_api.update_project,
|
||||||
|
project1['id'],
|
||||||
|
project1)
|
||||||
|
|
||||||
def test_rename_duplicate_project_name_fails(self):
|
def test_rename_duplicate_project_name_fails(self):
|
||||||
tenant1 = {'id': 'fake1', 'name': 'fake1',
|
tenant1 = {'id': 'fake1', 'name': 'fake1',
|
||||||
'domain_id': DEFAULT_DOMAIN_ID}
|
'domain_id': DEFAULT_DOMAIN_ID}
|
||||||
@ -1639,6 +1738,62 @@ class IdentityTests(object):
|
|||||||
self.identity_api.get_group,
|
self.identity_api.get_group,
|
||||||
group['id'])
|
group['id'])
|
||||||
|
|
||||||
|
def test_create_duplicate_group_name_fails(self):
|
||||||
|
group1 = {'id': uuid.uuid4().hex, 'domain_id': DEFAULT_DOMAIN_ID,
|
||||||
|
'name': uuid.uuid4().hex}
|
||||||
|
group2 = {'id': uuid.uuid4().hex, 'domain_id': DEFAULT_DOMAIN_ID,
|
||||||
|
'name': group1['name']}
|
||||||
|
self.identity_api.create_group(group1['id'], group1)
|
||||||
|
self.assertRaises(exception.Conflict,
|
||||||
|
self.identity_api.create_group,
|
||||||
|
group2['id'], group2)
|
||||||
|
|
||||||
|
def test_create_duplicate_group_name_in_different_domains(self):
|
||||||
|
new_domain = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
|
||||||
|
self.identity_api.create_domain(new_domain['id'], new_domain)
|
||||||
|
group1 = {'id': uuid.uuid4().hex, 'domain_id': DEFAULT_DOMAIN_ID,
|
||||||
|
'name': uuid.uuid4().hex}
|
||||||
|
group2 = {'id': uuid.uuid4().hex, 'domain_id': new_domain['id'],
|
||||||
|
'name': group1['name']}
|
||||||
|
self.identity_api.create_group(group1['id'], group1)
|
||||||
|
self.identity_api.create_group(group2['id'], group2)
|
||||||
|
|
||||||
|
def test_move_group_between_domains(self):
|
||||||
|
domain1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
|
||||||
|
self.identity_api.create_domain(domain1['id'], domain1)
|
||||||
|
domain2 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
|
||||||
|
self.identity_api.create_domain(domain2['id'], domain2)
|
||||||
|
group = {'id': uuid.uuid4().hex,
|
||||||
|
'name': uuid.uuid4().hex,
|
||||||
|
'domain_id': domain1['id']}
|
||||||
|
self.identity_api.create_group(group['id'], group)
|
||||||
|
group['domain_id'] = domain2['id']
|
||||||
|
self.identity_api.update_group(group['id'], group)
|
||||||
|
|
||||||
|
def test_move_group_between_domains_with_clashing_names_fails(self):
|
||||||
|
domain1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
|
||||||
|
self.identity_api.create_domain(domain1['id'], domain1)
|
||||||
|
domain2 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
|
||||||
|
self.identity_api.create_domain(domain2['id'], domain2)
|
||||||
|
# First, create a group in domain1
|
||||||
|
group1 = {'id': uuid.uuid4().hex,
|
||||||
|
'name': uuid.uuid4().hex,
|
||||||
|
'domain_id': domain1['id']}
|
||||||
|
self.identity_api.create_group(group1['id'], group1)
|
||||||
|
# Now create a group in domain2 with a potentially clashing
|
||||||
|
# name - which should work since we have domain separation
|
||||||
|
group2 = {'id': uuid.uuid4().hex,
|
||||||
|
'name': group1['name'],
|
||||||
|
'domain_id': domain2['id']}
|
||||||
|
self.identity_api.create_group(group2['id'], group2)
|
||||||
|
# Now try and move group1 into the 2nd domain - which should
|
||||||
|
# fail since the names clash
|
||||||
|
group1['domain_id'] = domain2['id']
|
||||||
|
self.assertRaises(exception.Conflict,
|
||||||
|
self.identity_api.update_group,
|
||||||
|
group1['id'],
|
||||||
|
group1)
|
||||||
|
|
||||||
def test_project_crud(self):
|
def test_project_crud(self):
|
||||||
project = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
|
project = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
|
||||||
'domain_id': uuid.uuid4().hex}
|
'domain_id': uuid.uuid4().hex}
|
||||||
|
@ -37,6 +37,33 @@ class KvsIdentity(test.TestCase, test_backend.IdentityTests):
|
|||||||
# NOTE(chungg): not implemented
|
# NOTE(chungg): not implemented
|
||||||
raise nose.exc.SkipTest('Blocked by bug 1119770')
|
raise nose.exc.SkipTest('Blocked by bug 1119770')
|
||||||
|
|
||||||
|
def test_create_duplicate_group_name_in_different_domains(self):
|
||||||
|
raise nose.exc.SkipTest('Blocked by bug 1119770')
|
||||||
|
|
||||||
|
def test_create_duplicate_user_name_in_different_domains(self):
|
||||||
|
raise nose.exc.SkipTest('Blocked by bug 1119770')
|
||||||
|
|
||||||
|
def test_create_duplicate_project_name_in_different_domains(self):
|
||||||
|
raise nose.exc.SkipTest('Blocked by bug 1119770')
|
||||||
|
|
||||||
|
def test_move_user_between_domains(self):
|
||||||
|
raise nose.exc.SkipTest('Blocked by bug 1119770')
|
||||||
|
|
||||||
|
def test_move_user_between_domains_with_clashing_names_fails(self):
|
||||||
|
raise nose.exc.SkipTest('Blocked by bug 1119770')
|
||||||
|
|
||||||
|
def test_move_group_between_domains(self):
|
||||||
|
raise nose.exc.SkipTest('Blocked by bug 1119770')
|
||||||
|
|
||||||
|
def test_move_group_between_domains_with_clashing_names_fails(self):
|
||||||
|
raise nose.exc.SkipTest('Blocked by bug 1119770')
|
||||||
|
|
||||||
|
def test_move_project_between_domains(self):
|
||||||
|
raise nose.exc.SkipTest('Blocked by bug 1119770')
|
||||||
|
|
||||||
|
def test_move_project_between_domains_with_clashing_names_fails(self):
|
||||||
|
raise nose.exc.SkipTest('Blocked by bug 1119770')
|
||||||
|
|
||||||
|
|
||||||
class KvsToken(test.TestCase, test_backend.TokenTests):
|
class KvsToken(test.TestCase, test_backend.TokenTests):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -438,3 +438,33 @@ class LDAPIdentity(test.TestCase, test_backend.IdentityTests):
|
|||||||
|
|
||||||
def test_get_project_users(self):
|
def test_get_project_users(self):
|
||||||
raise nose.exc.SkipTest('Blocked by bug 1101287')
|
raise nose.exc.SkipTest('Blocked by bug 1101287')
|
||||||
|
|
||||||
|
def test_create_duplicate_user_name_in_different_domains(self):
|
||||||
|
raise nose.exc.SkipTest('Blocked by bug 1101276')
|
||||||
|
|
||||||
|
def test_create_duplicate_project_name_in_different_domains(self):
|
||||||
|
raise nose.exc.SkipTest('Blocked by bug 1101276')
|
||||||
|
|
||||||
|
def test_create_duplicate_group_name_fails(self):
|
||||||
|
raise nose.exc.SkipTest('Blocked by bug 1092187')
|
||||||
|
|
||||||
|
def test_create_duplicate_group_name_in_different_domains(self):
|
||||||
|
raise nose.exc.SkipTest('Blocked by bug 1101276')
|
||||||
|
|
||||||
|
def test_move_user_between_domains(self):
|
||||||
|
raise nose.exc.SkipTest('Blocked by bug 1101276')
|
||||||
|
|
||||||
|
def test_move_user_between_domains_with_clashing_names_fails(self):
|
||||||
|
raise nose.exc.SkipTest('Blocked by bug 1101276')
|
||||||
|
|
||||||
|
def test_move_group_between_domains(self):
|
||||||
|
raise nose.exc.SkipTest('Blocked by bug 1101276')
|
||||||
|
|
||||||
|
def test_move_group_between_domains_with_clashing_names_fails(self):
|
||||||
|
raise nose.exc.SkipTest('Blocked by bug 1101276')
|
||||||
|
|
||||||
|
def test_move_project_between_domains(self):
|
||||||
|
raise nose.exc.SkipTest('Blocked by bug 1101276')
|
||||||
|
|
||||||
|
def test_move_project_between_domains_with_clashing_names_fails(self):
|
||||||
|
raise nose.exc.SkipTest('Blocked by bug 1101276')
|
||||||
|
@ -279,6 +279,7 @@ class SqlUpgradeTests(test.TestCase):
|
|||||||
self.populate_user_table(with_pass_enab=True)
|
self.populate_user_table(with_pass_enab=True)
|
||||||
self.populate_tenant_table(with_desc_enab=True)
|
self.populate_tenant_table(with_desc_enab=True)
|
||||||
self.upgrade(16)
|
self.upgrade(16)
|
||||||
|
|
||||||
self.assertTableColumns("user",
|
self.assertTableColumns("user",
|
||||||
["id", "name", "extra",
|
["id", "name", "extra",
|
||||||
"password", "enabled", "domain_id"])
|
"password", "enabled", "domain_id"])
|
||||||
@ -299,9 +300,12 @@ class SqlUpgradeTests(test.TestCase):
|
|||||||
self.assertEqual(a_project.description,
|
self.assertEqual(a_project.description,
|
||||||
default_fixtures.TENANTS[1]['description'])
|
default_fixtures.TENANTS[1]['description'])
|
||||||
self.assertEqual(a_project.domain_id, DEFAULT_DOMAIN_ID)
|
self.assertEqual(a_project.domain_id, DEFAULT_DOMAIN_ID)
|
||||||
|
|
||||||
session.commit()
|
session.commit()
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
|
self.check_uniqueness_constraints()
|
||||||
|
|
||||||
def test_downgrade_16_to_14(self):
|
def test_downgrade_16_to_14(self):
|
||||||
self.upgrade(16)
|
self.upgrade(16)
|
||||||
self.populate_user_table(with_pass_enab_domain=True)
|
self.populate_user_table(with_pass_enab_domain=True)
|
||||||
@ -452,6 +456,76 @@ class SqlUpgradeTests(test.TestCase):
|
|||||||
self.downgrade(16)
|
self.downgrade(16)
|
||||||
self.assertEquals(0, count_member_roles())
|
self.assertEquals(0, count_member_roles())
|
||||||
|
|
||||||
|
def check_uniqueness_constraints(self):
|
||||||
|
# Check uniqueness constraints for User & Project tables are
|
||||||
|
# correct following schema modification. The Group table's
|
||||||
|
# schema is never modified, so we don't bother to check that.
|
||||||
|
domain_table = sqlalchemy.Table('domain',
|
||||||
|
self.metadata,
|
||||||
|
autoload=True)
|
||||||
|
domain1 = {'id': uuid.uuid4().hex,
|
||||||
|
'name': uuid.uuid4().hex,
|
||||||
|
'enabled': True}
|
||||||
|
domain2 = {'id': uuid.uuid4().hex,
|
||||||
|
'name': uuid.uuid4().hex,
|
||||||
|
'enabled': True}
|
||||||
|
cmd = domain_table.insert().values(domain1)
|
||||||
|
self.engine.execute(cmd)
|
||||||
|
cmd = domain_table.insert().values(domain2)
|
||||||
|
self.engine.execute(cmd)
|
||||||
|
|
||||||
|
# First, the User table.
|
||||||
|
this_table = sqlalchemy.Table('user',
|
||||||
|
self.metadata,
|
||||||
|
autoload=True)
|
||||||
|
user = {'id': uuid.uuid4().hex,
|
||||||
|
'name': uuid.uuid4().hex,
|
||||||
|
'domain_id': domain1['id'],
|
||||||
|
'password': uuid.uuid4().hex,
|
||||||
|
'enabled': True,
|
||||||
|
'extra': json.dumps({})}
|
||||||
|
cmd = this_table.insert().values(user)
|
||||||
|
self.engine.execute(cmd)
|
||||||
|
# now insert a user with the same name into a different
|
||||||
|
# domain - which should work.
|
||||||
|
user['id'] = uuid.uuid4().hex
|
||||||
|
user['domain_id'] = domain2['id']
|
||||||
|
cmd = this_table.insert().values(user)
|
||||||
|
self.engine.execute(cmd)
|
||||||
|
# TODO(henry-nash). For now, as part of clean-up we
|
||||||
|
# delete one of these users. Although not part of this test,
|
||||||
|
# unless we do so the downgrade(16->15) that is part of
|
||||||
|
# teardown with fail due to having two uses with clashing
|
||||||
|
# name as we try to revert to a single global name space. This
|
||||||
|
# limitation is raised as Bug #1125046 and the delete
|
||||||
|
# could be removed depending on how that bug is resolved.
|
||||||
|
cmd = this_table.delete(id=user['id'])
|
||||||
|
self.engine.execute(cmd)
|
||||||
|
|
||||||
|
# Now, the Project table.
|
||||||
|
this_table = sqlalchemy.Table('project',
|
||||||
|
self.metadata,
|
||||||
|
autoload=True)
|
||||||
|
project = {'id': uuid.uuid4().hex,
|
||||||
|
'name': uuid.uuid4().hex,
|
||||||
|
'domain_id': domain1['id'],
|
||||||
|
'description': uuid.uuid4().hex,
|
||||||
|
'enabled': True,
|
||||||
|
'extra': json.dumps({})}
|
||||||
|
cmd = this_table.insert().values(project)
|
||||||
|
self.engine.execute(cmd)
|
||||||
|
# now insert a project with the same name into a different
|
||||||
|
# domain - which should work.
|
||||||
|
project['id'] = uuid.uuid4().hex
|
||||||
|
project['domain_id'] = domain2['id']
|
||||||
|
cmd = this_table.insert().values(project)
|
||||||
|
self.engine.execute(cmd)
|
||||||
|
# TODO(henry-nash) For now, we delete one of the projects for
|
||||||
|
# the same reason as we delete one of the users (Bug #1125046).
|
||||||
|
# This delete could be removed depending on that bug resolution.
|
||||||
|
cmd = this_table.delete(id=project['id'])
|
||||||
|
self.engine.execute(cmd)
|
||||||
|
|
||||||
def populate_user_table(self, with_pass_enab=False,
|
def populate_user_table(self, with_pass_enab=False,
|
||||||
with_pass_enab_domain=False):
|
with_pass_enab_domain=False):
|
||||||
# Populate the appropriate fields in the user
|
# Populate the appropriate fields in the user
|
||||||
|
Loading…
Reference in New Issue
Block a user