Merge "sql: Squash ocata migrations"

This commit is contained in:
Zuul 2022-02-08 13:34:56 +00:00 committed by Gerrit Code Review
commit 4dca9cc0b0
41 changed files with 113 additions and 1558 deletions

View File

@ -1,18 +0,0 @@
# 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.
# This is a placeholder for Newton backports. Do not use this number for new
# Ocata work. New Ocata work starts after all the placeholders.
def upgrade(migrate_engine):
pass

View File

@ -1,18 +0,0 @@
# 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.
# This is a placeholder for Newton backports. Do not use this number for new
# Ocata work. New Ocata work starts after all the placeholders.
def upgrade(migrate_engine):
pass

View File

@ -1,18 +0,0 @@
# 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.
# This is a placeholder for Newton backports. Do not use this number for new
# Ocata work. New Ocata work starts after all the placeholders.
def upgrade(migrate_engine):
pass

View File

@ -1,18 +0,0 @@
# 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.
# This is a placeholder for Newton backports. Do not use this number for new
# Ocata work. New Ocata work starts after all the placeholders.
def upgrade(migrate_engine):
pass

View File

@ -1,18 +0,0 @@
# 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.
# This is a placeholder for Newton backports. Do not use this number for new
# Ocata work. New Ocata work starts after all the placeholders.
def upgrade(migrate_engine):
pass

View File

@ -1,15 +0,0 @@
# 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.
def upgrade(migrate_engine):
pass

View File

@ -1,23 +0,0 @@
# 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 migrate
import sqlalchemy as sql
def upgrade(migrate_engine):
meta = sql.MetaData()
meta.bind = migrate_engine
nonlocal_user = sql.Table('nonlocal_user', meta, autoload=True)
migrate.UniqueConstraint(nonlocal_user.c.user_id,
name='ixu_nonlocal_user_user_id').create()

View File

@ -1,38 +0,0 @@
# 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.common.sql import upgrades
def upgrade(migrate_engine):
meta = sql.MetaData()
meta.bind = migrate_engine
idp_table = sql.Table('identity_provider', meta, autoload=True)
idp_table.c.domain_id.alter(nullable=False, unique=True)
if upgrades.USE_TRIGGERS:
if migrate_engine.name == 'postgresql':
drop_idp_insert_trigger = (
'DROP TRIGGER idp_insert_read_only on identity_provider;'
)
elif migrate_engine.name == 'mysql':
drop_idp_insert_trigger = (
'DROP TRIGGER idp_insert_read_only;'
)
else:
drop_idp_insert_trigger = (
'DROP TRIGGER IF EXISTS idp_insert_read_only;'
)
migrate_engine.execute(drop_idp_insert_trigger)

View File

@ -1,31 +0,0 @@
# 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 migrate
import sqlalchemy as sql
def upgrade(migrate_engine):
meta = sql.MetaData()
meta.bind = migrate_engine
federated_table = sql.Table('federated_user', meta, autoload=True)
protocol_table = sql.Table('federation_protocol', meta, autoload=True)
migrate.ForeignKeyConstraint(
columns=[federated_table.c.protocol_id, federated_table.c.idp_id],
refcolumns=[protocol_table.c.id, protocol_table.c.idp_id]).drop()
migrate.ForeignKeyConstraint(
columns=[federated_table.c.protocol_id, federated_table.c.idp_id],
refcolumns=[protocol_table.c.id, protocol_table.c.idp_id],
ondelete='CASCADE').create()

View File

@ -1,94 +0,0 @@
# 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 migrate
import sqlalchemy as sql
from keystone.common.sql import upgrades
def upgrade(migrate_engine):
meta = sql.MetaData()
meta.bind = migrate_engine
inspector = sql.inspect(migrate_engine)
user = sql.Table('user', meta, autoload=True)
local_user = sql.Table('local_user', meta, autoload=True)
nonlocal_user = sql.Table('nonlocal_user', meta, autoload=True)
# drop previous fk constraints
fk_name = _get_fk_name(inspector, 'local_user', 'user_id')
if fk_name:
migrate.ForeignKeyConstraint(columns=[local_user.c.user_id],
refcolumns=[user.c.id],
name=fk_name).drop()
fk_name = _get_fk_name(inspector, 'nonlocal_user', 'user_id')
if fk_name:
migrate.ForeignKeyConstraint(columns=[nonlocal_user.c.user_id],
refcolumns=[user.c.id],
name=fk_name).drop()
# create user unique constraint needed for the new composite fk constraint
migrate.UniqueConstraint(user.c.id, user.c.domain_id,
name='ixu_user_id_domain_id').create()
# create new composite fk constraints
migrate.ForeignKeyConstraint(
columns=[local_user.c.user_id, local_user.c.domain_id],
refcolumns=[user.c.id, user.c.domain_id],
onupdate='CASCADE', ondelete='CASCADE').create()
migrate.ForeignKeyConstraint(
columns=[nonlocal_user.c.user_id, nonlocal_user.c.domain_id],
refcolumns=[user.c.id, user.c.domain_id],
onupdate='CASCADE', ondelete='CASCADE').create()
# drop triggers
if upgrades.USE_TRIGGERS:
if migrate_engine.name == 'postgresql':
drop_local_user_insert_trigger = (
'DROP TRIGGER local_user_after_insert_trigger on local_user;')
drop_local_user_update_trigger = (
'DROP TRIGGER local_user_after_update_trigger on local_user;')
drop_nonlocal_user_insert_trigger = (
'DROP TRIGGER nonlocal_user_after_insert_trigger '
'on nonlocal_user;')
drop_nonlocal_user_update_trigger = (
'DROP TRIGGER nonlocal_user_after_update_trigger '
'on nonlocal_user;')
elif migrate_engine.name == 'mysql':
drop_local_user_insert_trigger = (
'DROP TRIGGER local_user_after_insert_trigger;')
drop_local_user_update_trigger = (
'DROP TRIGGER local_user_after_update_trigger;')
drop_nonlocal_user_insert_trigger = (
'DROP TRIGGER nonlocal_user_after_insert_trigger;')
drop_nonlocal_user_update_trigger = (
'DROP TRIGGER nonlocal_user_after_update_trigger;')
else:
drop_local_user_insert_trigger = (
'DROP TRIGGER IF EXISTS local_user_after_insert_trigger;')
drop_local_user_update_trigger = (
'DROP TRIGGER IF EXISTS local_user_after_update_trigger;')
drop_nonlocal_user_insert_trigger = (
'DROP TRIGGER IF EXISTS nonlocal_user_after_insert_trigger;')
drop_nonlocal_user_update_trigger = (
'DROP TRIGGER IF EXISTS nonlocal_user_after_update_trigger;')
migrate_engine.execute(drop_local_user_insert_trigger)
migrate_engine.execute(drop_local_user_update_trigger)
migrate_engine.execute(drop_nonlocal_user_insert_trigger)
migrate_engine.execute(drop_nonlocal_user_update_trigger)
def _get_fk_name(inspector, table, fk_column):
for fk in inspector.get_foreign_keys(table):
if fk_column in fk['constrained_columns']:
return fk['name']

View File

@ -1,34 +0,0 @@
# 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.common.sql import upgrades
def upgrade(migrate_engine):
meta = sql.MetaData()
meta.bind = migrate_engine
user_table = sql.Table('user', meta, autoload=True)
user_table.c.domain_id.alter(nullable=False)
if upgrades.USE_TRIGGERS:
if migrate_engine.name == 'postgresql':
drop_trigger_stmt = 'DROP TRIGGER federated_user_insert_trigger '
drop_trigger_stmt += 'on federated_user;'
elif migrate_engine.name == 'mysql':
drop_trigger_stmt = 'DROP TRIGGER federated_user_insert_trigger;'
else:
drop_trigger_stmt = (
'DROP TRIGGER IF EXISTS federated_user_insert_trigger;')
migrate_engine.execute(drop_trigger_stmt)

View File

@ -1,16 +0,0 @@
# 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.
def upgrade(migrate_engine):
# NOTE(notmorgan): This is a no-op, no data-migration needed.
pass

View File

@ -1,18 +0,0 @@
# 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.
# This is a placeholder for Newton backports. Do not use this number for new
# Ocata work. New Ocata work starts after all the placeholders.
def upgrade(migrate_engine):
pass

View File

@ -1,18 +0,0 @@
# 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.
# This is a placeholder for Newton backports. Do not use this number for new
# Ocata work. New Ocata work starts after all the placeholders.
def upgrade(migrate_engine):
pass

View File

@ -1,18 +0,0 @@
# 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.
# This is a placeholder for Newton backports. Do not use this number for new
# Ocata work. New Ocata work starts after all the placeholders.
def upgrade(migrate_engine):
pass

View File

@ -1,18 +0,0 @@
# 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.
# This is a placeholder for Newton backports. Do not use this number for new
# Ocata work. New Ocata work starts after all the placeholders.
def upgrade(migrate_engine):
pass

View File

@ -1,18 +0,0 @@
# 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.
# This is a placeholder for Newton backports. Do not use this number for new
# Ocata work. New Ocata work starts after all the placeholders.
def upgrade(migrate_engine):
pass

View File

@ -1,15 +0,0 @@
# 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.
def upgrade(migrate_engine):
pass

View File

@ -1,15 +0,0 @@
# 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.
def upgrade(migrate_engine):
pass

View File

@ -1,55 +0,0 @@
# 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 uuid
import sqlalchemy as sql
from sqlalchemy.orm import sessionmaker
from keystone.resource.backends import base
def upgrade(migrate_engine):
meta = sql.MetaData()
meta.bind = migrate_engine
maker = sessionmaker(bind=migrate_engine)
session = maker()
idp_table = sql.Table('identity_provider', meta, autoload=True)
for idp_row in idp_table.select().execute():
domain_id = _create_federated_domain(meta, session, idp_row['id'])
# update idp with the new federated domain_id
values = {'domain_id': domain_id}
stmt = idp_table.update().where(
idp_table.c.id == idp_row['id']).values(values)
stmt.execute()
def _create_federated_domain(meta, session, idp_id):
domain_id = uuid.uuid4().hex
desc = 'Auto generated federated domain for Identity Provider: ' + idp_id
federated_domain = {
'id': domain_id,
'name': domain_id,
'enabled': True,
'description': desc,
'domain_id': base.NULL_DOMAIN_ID,
'is_domain': True,
'parent_id': None,
'extra': '{}'
}
project_table = sql.Table('project', meta, autoload=True)
new_row = project_table.insert().values(**federated_domain)
session.execute(new_row)
session.commit()
return domain_id

View File

@ -1,15 +0,0 @@
# 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.
def upgrade(migrate_engine):
pass

View File

@ -1,45 +0,0 @@
# 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
import sqlalchemy.sql.expression as expression
def upgrade(migrate_engine):
meta = sql.MetaData()
meta.bind = migrate_engine
user_table = sql.Table('user', meta, autoload=True)
# update user domain_id from local_user
local_table = sql.Table('local_user', meta, autoload=True)
_update_user_domain_id(migrate_engine, user_table, local_table)
# update user domain_id from nonlocal_user
nonlocal_table = sql.Table('nonlocal_user', meta, autoload=True)
_update_user_domain_id(migrate_engine, user_table, nonlocal_table)
def _update_user_domain_id(migrate_engine, user_table, child_user_table):
join = sql.join(user_table, child_user_table,
user_table.c.id == child_user_table.c.user_id)
where = user_table.c.domain_id == expression.null()
sel = (
sql.select([user_table.c.id, child_user_table.c.domain_id])
.select_from(join).where(where)
)
with migrate_engine.begin() as conn:
for user in conn.execute(sel):
values = {'domain_id': user['domain_id']}
stmt = user_table.update().where(
user_table.c.id == user['id']).values(values)
conn.execute(stmt)

View File

@ -1,36 +0,0 @@
# 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
import sqlalchemy.sql.expression as expression
def upgrade(migrate_engine):
meta = sql.MetaData()
meta.bind = migrate_engine
user_table = sql.Table('user', meta, autoload=True)
federated_table = sql.Table('federated_user', meta, autoload=True)
idp_table = sql.Table('identity_provider', meta, autoload=True)
join = sql.join(federated_table, idp_table,
federated_table.c.idp_id == idp_table.c.id)
sel = sql.select(
[federated_table.c.user_id, idp_table.c.domain_id]).select_from(join)
with migrate_engine.begin() as conn:
for user in conn.execute(sel):
values = {'domain_id': user['domain_id']}
stmt = user_table.update().where(
sql.and_(
user_table.c.domain_id == expression.null(),
user_table.c.id == user['user_id'])).values(values)
conn.execute(stmt)

View File

@ -1,16 +0,0 @@
# 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.
def upgrade(migrate_engine):
# NOTE(notmorgan): This is a no-op, no data-migration needed.
pass

View File

@ -1,18 +0,0 @@
# 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.
# This is a placeholder for Newton backports. Do not use this number for new
# Ocata work. New Ocata work starts after all the placeholders.
def upgrade(migrate_engine):
pass

View File

@ -1,18 +0,0 @@
# 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.
# This is a placeholder for Newton backports. Do not use this number for new
# Ocata work. New Ocata work starts after all the placeholders.
def upgrade(migrate_engine):
pass

View File

@ -1,18 +0,0 @@
# 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.
# This is a placeholder for Newton backports. Do not use this number for new
# Ocata work. New Ocata work starts after all the placeholders.
def upgrade(migrate_engine):
pass

View File

@ -1,18 +0,0 @@
# 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.
# This is a placeholder for Newton backports. Do not use this number for new
# Ocata work. New Ocata work starts after all the placeholders.
def upgrade(migrate_engine):
pass

View File

@ -1,18 +0,0 @@
# 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.
# This is a placeholder for Newton backports. Do not use this number for new
# Ocata work. New Ocata work starts after all the placeholders.
def upgrade(migrate_engine):
pass

View File

@ -1,31 +0,0 @@
# 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
def upgrade(migrate_engine):
meta = sql.MetaData()
meta.bind = migrate_engine
revocation_event = sql.Table('revocation_event', meta, autoload=True)
sql.Index('ix_revocation_event_issued_before',
revocation_event.c.issued_before).create()
sql.Index('ix_revocation_event_project_id_issued_before',
revocation_event.c.project_id,
revocation_event.c.issued_before).create()
sql.Index('ix_revocation_event_user_id_issued_before',
revocation_event.c.user_id,
revocation_event.c.issued_before).create()
sql.Index('ix_revocation_event_audit_id_issued_before',
revocation_event.c.audit_id,
revocation_event.c.issued_before).create()

View File

@ -1,15 +0,0 @@
# 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.
def upgrade(migrate_engine):
pass

View File

@ -1,73 +0,0 @@
# 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.common.sql import upgrades
MYSQL_INSERT_TRIGGER = """
CREATE TRIGGER idp_insert_read_only BEFORE INSERT ON identity_provider
FOR EACH ROW
BEGIN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '%s';
END;
"""
SQLITE_INSERT_TRIGGER = """
CREATE TRIGGER idp_insert_read_only BEFORE INSERT ON identity_provider
BEGIN
SELECT RAISE (ABORT, '%s');
END;
"""
POSTGRESQL_INSERT_TRIGGER = """
CREATE OR REPLACE FUNCTION keystone_read_only_insert()
RETURNS trigger AS
$BODY$
BEGIN
RAISE EXCEPTION '%s';
END
$BODY$ LANGUAGE plpgsql;
CREATE TRIGGER idp_insert_read_only BEFORE INSERT ON identity_provider
FOR EACH ROW
EXECUTE PROCEDURE keystone_read_only_insert();
"""
def upgrade(migrate_engine):
meta = sql.MetaData()
meta.bind = migrate_engine
idp = sql.Table('identity_provider', meta, autoload=True)
project = sql.Table('project', meta, autoload=True)
domain_id = sql.Column('domain_id', sql.String(64),
sql.ForeignKey(project.c.id), nullable=True)
idp.create_column(domain_id)
if upgrades.USE_TRIGGERS:
# Setting idp to be read-only to prevent old code from creating an idp
# without a domain_id during an upgrade. This should be okay as it is
# highly unlikely that an idp would be created during the migration and
# the impact from preventing creations is minor.
error_message = ('Identity provider migration in progress. Cannot '
'insert new rows into the identity_provider table at '
'this time.')
if migrate_engine.name == 'postgresql':
idp_insert_trigger = POSTGRESQL_INSERT_TRIGGER % error_message
elif migrate_engine.name == 'sqlite':
idp_insert_trigger = SQLITE_INSERT_TRIGGER % error_message
else:
idp_insert_trigger = MYSQL_INSERT_TRIGGER % error_message
migrate_engine.execute(idp_insert_trigger)

View File

@ -1,15 +0,0 @@
# 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.
def upgrade(migrate_engine):
pass

View File

@ -1,165 +0,0 @@
# 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.common.sql import upgrades
# define the local_user triggers for insert and update
MYSQL_LOCAL_USER_INSERT_TRIGGER = """
CREATE TRIGGER local_user_after_insert_trigger
AFTER INSERT
ON local_user FOR EACH ROW
BEGIN
UPDATE user SET domain_id = NEW.domain_id
WHERE id = NEW.user_id and domain_id IS NULL;
END;
"""
MYSQL_LOCAL_USER_UPDATE_TRIGGER = """
CREATE TRIGGER local_user_after_update_trigger
AFTER UPDATE
ON local_user FOR EACH ROW
BEGIN
UPDATE user SET domain_id = NEW.domain_id
WHERE id = NEW.user_id and domain_id <> NEW.domain_id;
END;
"""
SQLITE_LOCAL_USER_INSERT_TRIGGER = """
CREATE TRIGGER local_user_after_insert_trigger
AFTER INSERT
ON local_user
BEGIN
UPDATE user SET domain_id = NEW.domain_id
WHERE id = NEW.user_id and domain_id IS NULL;
END;
"""
SQLITE_LOCAL_USER_UPDATE_TRIGGER = """
CREATE TRIGGER local_user_after_update_trigger
AFTER UPDATE
ON local_user
BEGIN
UPDATE user SET domain_id = NEW.domain_id
WHERE id = NEW.user_id and domain_id <> NEW.domain_id;
END;
"""
POSTGRESQL_LOCAL_USER_INSERT_TRIGGER = """
CREATE OR REPLACE FUNCTION update_user_domain_id()
RETURNS trigger AS
$BODY$
BEGIN
UPDATE "user" SET domain_id = NEW.domain_id
WHERE id = NEW.user_id;
RETURN NULL;
END
$BODY$ LANGUAGE plpgsql;
CREATE TRIGGER local_user_after_insert_trigger AFTER INSERT ON local_user
FOR EACH ROW
EXECUTE PROCEDURE update_user_domain_id();
"""
POSTGRESQL_LOCAL_USER_UPDATE_TRIGGER = """
CREATE TRIGGER local_user_after_update_trigger AFTER UPDATE ON local_user
FOR EACH ROW
EXECUTE PROCEDURE update_user_domain_id();
"""
MYSQL_NONLOCAL_USER_INSERT_TRIGGER = """
CREATE TRIGGER nonlocal_user_after_insert_trigger
AFTER INSERT
ON nonlocal_user FOR EACH ROW
BEGIN
UPDATE user SET domain_id = NEW.domain_id
WHERE id = NEW.user_id and domain_id IS NULL;
END;
"""
# define the nonlocal_user triggers for insert and update
MYSQL_NONLOCAL_USER_UPDATE_TRIGGER = """
CREATE TRIGGER nonlocal_user_after_update_trigger
AFTER UPDATE
ON nonlocal_user FOR EACH ROW
BEGIN
UPDATE user SET domain_id = NEW.domain_id
WHERE id = NEW.user_id and domain_id <> NEW.domain_id;
END;
"""
SQLITE_NONLOCAL_USER_INSERT_TRIGGER = """
CREATE TRIGGER nonlocal_user_after_insert_trigger
AFTER INSERT
ON nonlocal_user
BEGIN
UPDATE user SET domain_id = NEW.domain_id
WHERE id = NEW.user_id and domain_id IS NULL;
END;
"""
SQLITE_NONLOCAL_USER_UPDATE_TRIGGER = """
CREATE TRIGGER nonlocal_user_after_update_trigger
AFTER UPDATE
ON nonlocal_user
BEGIN
UPDATE user SET domain_id = NEW.domain_id
WHERE id = NEW.user_id and domain_id <> NEW.domain_id;
END;
"""
POSTGRESQL_NONLOCAL_USER_INSERT_TRIGGER = """
CREATE TRIGGER nonlocal_user_after_insert_trigger AFTER INSERT ON nonlocal_user
FOR EACH ROW
EXECUTE PROCEDURE update_user_domain_id();
"""
POSTGRESQL_NONLOCAL_USER_UPDATE_TRIGGER = """
CREATE TRIGGER nonlocal_user_after_update_trigger AFTER UPDATE ON nonlocal_user
FOR EACH ROW
EXECUTE PROCEDURE update_user_domain_id();
"""
def upgrade(migrate_engine):
meta = sql.MetaData()
meta.bind = migrate_engine
user = sql.Table('user', meta, autoload=True)
project = sql.Table('project', meta, autoload=True)
domain_id = sql.Column('domain_id', sql.String(64),
sql.ForeignKey(project.c.id), nullable=True)
user.create_column(domain_id)
if upgrades.USE_TRIGGERS:
if migrate_engine.name == 'postgresql':
local_user_insert_trigger = POSTGRESQL_LOCAL_USER_INSERT_TRIGGER
local_user_update_trigger = POSTGRESQL_LOCAL_USER_UPDATE_TRIGGER
nonlocal_user_insert_trigger = (
POSTGRESQL_NONLOCAL_USER_INSERT_TRIGGER)
nonlocal_user_update_trigger = (
POSTGRESQL_NONLOCAL_USER_UPDATE_TRIGGER)
elif migrate_engine.name == 'sqlite':
local_user_insert_trigger = SQLITE_LOCAL_USER_INSERT_TRIGGER
local_user_update_trigger = SQLITE_LOCAL_USER_UPDATE_TRIGGER
nonlocal_user_insert_trigger = SQLITE_NONLOCAL_USER_INSERT_TRIGGER
nonlocal_user_update_trigger = SQLITE_NONLOCAL_USER_UPDATE_TRIGGER
else:
local_user_insert_trigger = MYSQL_LOCAL_USER_INSERT_TRIGGER
local_user_update_trigger = MYSQL_LOCAL_USER_UPDATE_TRIGGER
nonlocal_user_insert_trigger = MYSQL_NONLOCAL_USER_INSERT_TRIGGER
nonlocal_user_update_trigger = MYSQL_NONLOCAL_USER_UPDATE_TRIGGER
migrate_engine.execute(local_user_insert_trigger)
migrate_engine.execute(local_user_update_trigger)
migrate_engine.execute(nonlocal_user_insert_trigger)
migrate_engine.execute(nonlocal_user_update_trigger)

View File

@ -1,69 +0,0 @@
# 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.common.sql import upgrades
MYSQL_INSERT_TRIGGER = """
CREATE TRIGGER federated_user_insert_trigger
AFTER INSERT
ON federated_user FOR EACH ROW
BEGIN
UPDATE user SET domain_id = (
SELECT domain_id FROM identity_provider WHERE id = NEW.idp_id)
WHERE id = NEW.user_id and domain_id IS NULL;
END;
"""
SQLITE_INSERT_TRIGGER = """
CREATE TRIGGER federated_user_insert_trigger
AFTER INSERT
ON federated_user
BEGIN
UPDATE user SET domain_id = (
SELECT domain_id FROM identity_provider WHERE id = NEW.idp_id)
WHERE id = NEW.user_id and domain_id IS NULL;
END;
"""
POSTGRESQL_INSERT_TRIGGER = """
CREATE OR REPLACE FUNCTION update_federated_user_domain_id()
RETURNS trigger AS
$BODY$
BEGIN
UPDATE "user" SET domain_id = (
SELECT domain_id FROM identity_provider WHERE id = NEW.idp_id)
WHERE id = NEW.user_id and domain_id IS NULL;
RETURN NULL;
END
$BODY$ LANGUAGE plpgsql;
CREATE TRIGGER federated_user_insert_trigger AFTER INSERT ON federated_user
FOR EACH ROW
EXECUTE PROCEDURE update_federated_user_domain_id();
"""
def upgrade(migrate_engine):
meta = sql.MetaData()
meta.bind = migrate_engine
if upgrades.USE_TRIGGERS:
if migrate_engine.name == 'postgresql':
insert_trigger = POSTGRESQL_INSERT_TRIGGER
elif migrate_engine.name == 'sqlite':
insert_trigger = SQLITE_INSERT_TRIGGER
else:
insert_trigger = MYSQL_INSERT_TRIGGER
migrate_engine.execute(insert_trigger)

View File

@ -1,34 +0,0 @@
# 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.common import sql as ks_sql
def upgrade(migrate_engine):
meta = sql.MetaData()
meta.bind = migrate_engine
user_table = sql.Table('user', meta, autoload=True)
user_option = sql.Table(
'user_option',
meta,
sql.Column('user_id', sql.String(64), sql.ForeignKey(user_table.c.id,
ondelete='CASCADE'), nullable=False, primary_key=True),
sql.Column('option_id', sql.String(4), nullable=False,
primary_key=True),
sql.Column('option_value', ks_sql.JsonBlob, nullable=True),
mysql_engine='InnoDB',
mysql_charset='utf8')
user_option.create(migrate_engine, checkfirst=True)

View File

@ -192,6 +192,7 @@ def upgrade(migrate_engine):
sql.Column('id', sql.String(64), primary_key=True),
sql.Column('enabled', sql.Boolean, nullable=False),
sql.Column('description', sql.Text(), nullable=True),
sql.Column('domain_id', sql.String(64), nullable=False),
mysql_engine='InnoDB',
mysql_charset='utf8',
)
@ -225,7 +226,6 @@ def upgrade(migrate_engine):
sql.Column(
'user_id',
sql.String(64),
sql.ForeignKey('user.id', ondelete='CASCADE'),
nullable=False,
unique=True,
),
@ -401,6 +401,22 @@ def upgrade(migrate_engine):
# was 'revocation_event_new' so the index got that name. We may wish to
# rename this eventually.
sql.Index('ix_revocation_event_new_revoked_at', 'revoked_at'),
sql.Index('ix_revocation_event_issued_before', 'issued_before'),
sql.Index(
'ix_revocation_event_project_id_issued_before',
'project_id',
'issued_before',
),
sql.Index(
'ix_revocation_event_user_id_issued_before',
'user_id',
'issued_before',
),
sql.Index(
'ix_revocation_event_audit_id_issued_before',
'audit_id',
'issued_before',
),
)
role = sql.Table(
@ -469,9 +485,7 @@ def upgrade(migrate_engine):
sql.Column('trust_id', sql.String(length=64)),
sql.Column('user_id', sql.String(length=64)),
sql.Index('ix_token_expires', 'expires'),
sql.Index(
'ix_token_expires_valid', 'expires', 'valid'
),
sql.Index('ix_token_expires_valid', 'expires', 'valid'),
sql.Index('ix_token_user_id', 'user_id'),
sql.Index('ix_token_trust_id', 'trust_id'),
mysql_engine='InnoDB',
@ -524,6 +538,31 @@ def upgrade(migrate_engine):
sql.Column('default_project_id', sql.String(length=64)),
sql.Column('created_at', sql.DateTime(), nullable=True),
sql.Column('last_active_at', sql.Date(), nullable=True),
sql.Column(
'domain_id',
sql.String(64),
sql.ForeignKey(project.c.id),
nullable=False,
),
sql.UniqueConstraint('id', 'domain_id', name='ixu_user_id_domain_id'),
mysql_engine='InnoDB',
mysql_charset='utf8',
)
user_option = sql.Table(
'user_option',
meta,
sql.Column(
'user_id',
sql.String(64),
sql.ForeignKey(user.c.id, ondelete='CASCADE'),
nullable=False,
primary_key=True,
),
sql.Column(
'option_id', sql.String(4), nullable=False, primary_key=True
),
sql.Column('option_value', ks_sql.JsonBlob, nullable=True),
mysql_engine='InnoDB',
mysql_charset='utf8',
)
@ -536,9 +575,9 @@ def upgrade(migrate_engine):
sql.Column(
'user_id',
sql.String(64),
sql.ForeignKey(user.c.id, ondelete='CASCADE'),
nullable=False,
),
sql.UniqueConstraint('user_id', name='ixu_nonlocal_user_user_id'),
mysql_engine='InnoDB',
mysql_charset='utf8',
)
@ -656,6 +695,7 @@ def upgrade(migrate_engine):
trust,
trust_role,
user,
user_option,
user_group_membership,
region,
assignment,
@ -738,6 +778,24 @@ def upgrade(migrate_engine):
federation_protocol.c.id,
federation_protocol.c.idp_id,
],
'ondelete': 'CASCADE',
},
{
'columns': [identity_provider.c.domain_id],
'references': [project.c.id],
'name': 'domain_id',
},
{
'columns': [local_user.c.user_id, local_user.c.domain_id],
'references': [user.c.id, user.c.domain_id],
'onupdate': 'CASCADE',
'ondelete': 'CASCADE',
},
{
'columns': [nonlocal_user.c.user_id, nonlocal_user.c.domain_id],
'references': [user.c.id, user.c.domain_id],
'onupdate': 'CASCADE',
'ondelete': 'CASCADE',
},
]
@ -758,6 +816,7 @@ def upgrade(migrate_engine):
refcolumns=fkey['references'],
name=fkey.get('name'),
ondelete=fkey.get('ondelete'),
onupdate=fkey.get('onupdate'),
).create()
# TODO(stephenfin): Remove these procedures in a future contract migration
@ -783,3 +842,46 @@ def upgrade(migrate_engine):
$BODY$ LANGUAGE plpgsql;
""")
migrate_engine.execute(credential_update_trigger)
error_message = (
'Identity provider migration in progress. Cannot '
'insert new rows into the identity_provider table at '
'this time.'
)
identity_provider_insert_trigger = textwrap.dedent(f"""
CREATE OR REPLACE FUNCTION keystone_read_only_insert()
RETURNS trigger AS
$BODY$
BEGIN
RAISE EXCEPTION '{error_message}';
END
$BODY$ LANGUAGE plpgsql;
""")
migrate_engine.execute(identity_provider_insert_trigger)
federated_user_insert_trigger = textwrap.dedent("""
CREATE OR REPLACE FUNCTION update_federated_user_domain_id()
RETURNS trigger AS
$BODY$
BEGIN
UPDATE "user" SET domain_id = (
SELECT domain_id FROM identity_provider WHERE id = NEW.idp_id)
WHERE id = NEW.user_id and domain_id IS NULL;
RETURN NULL;
END
$BODY$ LANGUAGE plpgsql;
""")
migrate_engine.execute(federated_user_insert_trigger)
local_user_insert_trigger = textwrap.dedent("""
CREATE OR REPLACE FUNCTION update_user_domain_id()
RETURNS trigger AS
$BODY$
BEGIN
UPDATE "user" SET domain_id = NEW.domain_id
WHERE id = NEW.user_id;
RETURN NULL;
END
$BODY$ LANGUAGE plpgsql;
""")
migrate_engine.execute(local_user_insert_trigger)

View File

@ -29,7 +29,7 @@ from keystone.i18n import _
USE_TRIGGERS = True
INITIAL_VERSION = 3
INITIAL_VERSION = 15
EXPAND_REPO = 'expand_repo'
DATA_MIGRATION_REPO = 'data_migration_repo'
CONTRACT_REPO = 'contract_repo'

View File

@ -41,7 +41,6 @@ For further information, see `oslo.db documentation
import datetime
import glob
import json
import os
import uuid
@ -112,7 +111,10 @@ INITIAL_TABLE_STRUCTURE = {
],
'user': [
'id', 'extra', 'enabled', 'default_project_id', 'created_at',
'last_active_at',
'last_active_at', 'domain_id',
],
'user_option': [
'user_id', 'option_id', 'option_value',
],
'user_group_membership': [
'user_id', 'group_id',
@ -136,7 +138,7 @@ INITIAL_TABLE_STRUCTURE = {
'id', 'policy_id', 'endpoint_id', 'service_id', 'region_id',
],
'identity_provider': [
'id', 'enabled', 'description',
'id', 'enabled', 'description', 'domain_id',
],
'federation_protocol': [
'id', 'idp_id', 'mapping_id',
@ -603,431 +605,6 @@ class FullMigration(MigrateBase, unit.TestCase):
upgrades.INITIAL_VERSION + 2,
)
def test_migration_010_add_revocation_event_indexes(self):
self.expand(9)
self.migrate(9)
self.contract(9)
self.assertFalse(self.does_index_exist(
'revocation_event',
'ix_revocation_event_issued_before'))
self.assertFalse(self.does_index_exist(
'revocation_event',
'ix_revocation_event_project_id_issued_before'))
self.assertFalse(self.does_index_exist(
'revocation_event',
'ix_revocation_event_user_id_issued_before'))
self.assertFalse(self.does_index_exist(
'revocation_event',
'ix_revocation_event_audit_id_issued_before'))
self.expand(10)
self.migrate(10)
self.contract(10)
self.assertTrue(self.does_index_exist(
'revocation_event',
'ix_revocation_event_issued_before'))
self.assertTrue(self.does_index_exist(
'revocation_event',
'ix_revocation_event_project_id_issued_before'))
self.assertTrue(self.does_index_exist(
'revocation_event',
'ix_revocation_event_user_id_issued_before'))
self.assertTrue(self.does_index_exist(
'revocation_event',
'ix_revocation_event_audit_id_issued_before'))
def test_migration_011_user_id_unique_for_nonlocal_user(self):
table_name = 'nonlocal_user'
column = 'user_id'
self.expand(10)
self.migrate(10)
self.contract(10)
self.assertFalse(self.does_unique_constraint_exist(table_name, column))
self.expand(11)
self.migrate(11)
self.contract(11)
self.assertTrue(self.does_unique_constraint_exist(table_name, column))
def test_migration_012_add_domain_id_to_idp(self):
def _create_domain():
domain_id = uuid.uuid4().hex
domain = {
'id': domain_id,
'name': domain_id,
'enabled': True,
'description': uuid.uuid4().hex,
'domain_id': resource_base.NULL_DOMAIN_ID,
'is_domain': True,
'parent_id': None,
'extra': '{}'
}
self.insert_dict(session, 'project', domain)
return domain_id
def _get_new_idp(domain_id):
new_idp = {'id': uuid.uuid4().hex,
'domain_id': domain_id,
'enabled': True,
'description': uuid.uuid4().hex}
return new_idp
session = self.sessionmaker()
idp_name = 'identity_provider'
self.expand(11)
self.migrate(11)
self.contract(11)
self.assertTableColumns(idp_name,
['id',
'enabled',
'description'])
# add some data
for i in range(5):
idp = {'id': uuid.uuid4().hex,
'enabled': True,
'description': uuid.uuid4().hex}
self.insert_dict(session, idp_name, idp)
# upgrade
self.expand(12)
self.assertTableColumns(idp_name,
['id',
'domain_id',
'enabled',
'description'])
# confirm we cannot insert an idp during expand
domain_id = _create_domain()
new_idp = _get_new_idp(domain_id)
self.assertRaises(db_exception.DBError, self.insert_dict, session,
idp_name, new_idp)
# confirm we cannot insert an idp during migrate
self.migrate(12)
self.assertRaises(db_exception.DBError, self.insert_dict, session,
idp_name, new_idp)
# confirm we can insert a new idp after contract
self.contract(12)
self.insert_dict(session, idp_name, new_idp)
# confirm domain_id column is not null
idp_table = sqlalchemy.Table(idp_name, self.metadata, autoload=True)
self.assertFalse(idp_table.c.domain_id.nullable)
def test_migration_013_protocol_cascade_delete_for_federated_user(self):
if self.engine.name == 'sqlite':
self.skipTest('sqlite backend does not support foreign keys')
self.expand(12)
self.migrate(12)
self.contract(12)
# This test requires a bit of setup to properly work, first we create
# an identity provider, mapping and a protocol. Then, we create a
# federated user and delete the protocol. We expect the federated user
# to be deleted as well.
session = self.sessionmaker()
def _create_protocol():
domain = {
'id': uuid.uuid4().hex,
'name': uuid.uuid4().hex,
'domain_id': resource_base.NULL_DOMAIN_ID,
'is_domain': True,
'parent_id': None
}
self.insert_dict(session, 'project', domain)
idp = {'id': uuid.uuid4().hex, 'enabled': True,
'domain_id': domain['id']}
self.insert_dict(session, 'identity_provider', idp)
mapping = {'id': uuid.uuid4().hex, 'rules': json.dumps([])}
self.insert_dict(session, 'mapping', mapping)
protocol = {'id': uuid.uuid4().hex, 'idp_id': idp['id'],
'mapping_id': mapping['id']}
protocol_table = sqlalchemy.Table(
'federation_protocol', self.metadata, autoload=True)
self.insert_dict(session, 'federation_protocol', protocol,
table=protocol_table)
return protocol, protocol_table
def _create_federated_user(idp_id, protocol_id):
user = {'id': uuid.uuid4().hex}
self.insert_dict(session, 'user', user)
# NOTE(rodrigods): do not set the ID, the engine will do that
# for us and we won't need it later.
federated_user = {
'user_id': user['id'], 'idp_id': idp_id,
'protocol_id': protocol_id, 'unique_id': uuid.uuid4().hex}
federated_table = sqlalchemy.Table(
'federated_user', self.metadata, autoload=True)
self.insert_dict(session, 'federated_user', federated_user,
table=federated_table)
return federated_user, federated_table
protocol, protocol_table = _create_protocol()
federated_user, federated_table = _create_federated_user(
protocol['idp_id'], protocol['id'])
# before updating the foreign key, we won't be able to delete the
# protocol
self.assertRaises(db_exception.DBError,
session.execute,
protocol_table.delete().where(
protocol_table.c.id == protocol['id']))
self.expand(13)
self.migrate(13)
self.contract(13)
# now we are able to delete the protocol
session.execute(
protocol_table.delete().where(
protocol_table.c.id == protocol['id']))
# assert the cascade deletion worked
federated_users = session.query(federated_table).filter_by(
protocol_id=federated_user['protocol_id']).all()
self.assertThat(federated_users, matchers.HasLength(0))
def test_migration_014_add_domain_id_to_user_table(self):
def create_domain():
table = sqlalchemy.Table('project', self.metadata, autoload=True)
domain_id = uuid.uuid4().hex
domain = {
'id': domain_id,
'name': domain_id,
'enabled': True,
'description': uuid.uuid4().hex,
'domain_id': resource_base.NULL_DOMAIN_ID,
'is_domain': True,
'parent_id': None,
'extra': '{}'
}
table.insert().values(domain).execute()
return domain_id
def create_user(table):
user_id = uuid.uuid4().hex
user = {'id': user_id, 'enabled': True}
table.insert().values(user).execute()
return user_id
# insert local_user or nonlocal_user
def create_child_user(table, user_id, domain_id):
child_user = {
'user_id': user_id,
'domain_id': domain_id,
'name': uuid.uuid4().hex
}
table.insert().values(child_user).execute()
# update local_user or nonlocal_user
def update_child_user(table, user_id, new_domain_id):
table.update().where(table.c.user_id == user_id).values(
domain_id=new_domain_id).execute()
def assertUserDomain(user_id, domain_id):
user = sqlalchemy.Table('user', self.metadata, autoload=True)
cols = [user.c.domain_id]
filter = user.c.id == user_id
sel = sqlalchemy.select(cols).where(filter)
domains = sel.execute().fetchone()
self.assertEqual(domain_id, domains[0])
user_table_name = 'user'
self.expand(13)
self.migrate(13)
self.contract(13)
self.assertTableColumns(
user_table_name, ['id', 'extra', 'enabled', 'default_project_id',
'created_at', 'last_active_at'])
self.expand(14)
self.assertTableColumns(
user_table_name, ['id', 'extra', 'enabled', 'default_project_id',
'created_at', 'last_active_at', 'domain_id'])
user_table = sqlalchemy.Table(user_table_name, self.metadata,
autoload=True)
local_user_table = sqlalchemy.Table('local_user', self.metadata,
autoload=True)
nonlocal_user_table = sqlalchemy.Table('nonlocal_user', self.metadata,
autoload=True)
# add users before migrate to test that the user.domain_id gets updated
# after migrate
user_ids = []
expected_domain_id = create_domain()
user_id = create_user(user_table)
create_child_user(local_user_table, user_id, expected_domain_id)
user_ids.append(user_id)
user_id = create_user(user_table)
create_child_user(nonlocal_user_table, user_id, expected_domain_id)
user_ids.append(user_id)
self.migrate(14)
# test local_user insert trigger updates user.domain_id
user_id = create_user(user_table)
domain_id = create_domain()
create_child_user(local_user_table, user_id, domain_id)
assertUserDomain(user_id, domain_id)
# test local_user update trigger updates user.domain_id
new_domain_id = create_domain()
update_child_user(local_user_table, user_id, new_domain_id)
assertUserDomain(user_id, new_domain_id)
# test nonlocal_user insert trigger updates user.domain_id
user_id = create_user(user_table)
create_child_user(nonlocal_user_table, user_id, domain_id)
assertUserDomain(user_id, domain_id)
# test nonlocal_user update trigger updates user.domain_id
update_child_user(nonlocal_user_table, user_id, new_domain_id)
assertUserDomain(user_id, new_domain_id)
self.contract(14)
# test migrate updated the user.domain_id
for user_id in user_ids:
assertUserDomain(user_id, expected_domain_id)
# test unique and fk constraints
if self.engine.name == 'mysql':
self.assertTrue(
self.does_index_exist('user', 'ixu_user_id_domain_id'))
else:
self.assertTrue(
self.does_constraint_exist('user', 'ixu_user_id_domain_id'))
self.assertTrue(self.does_fk_exist('local_user', 'user_id'))
self.assertTrue(self.does_fk_exist('local_user', 'domain_id'))
self.assertTrue(self.does_fk_exist('nonlocal_user', 'user_id'))
self.assertTrue(self.does_fk_exist('nonlocal_user', 'domain_id'))
def test_migration_015_update_federated_user_domain(self):
def create_domain():
table = sqlalchemy.Table('project', self.metadata, autoload=True)
domain_id = uuid.uuid4().hex
domain = {
'id': domain_id,
'name': domain_id,
'enabled': True,
'description': uuid.uuid4().hex,
'domain_id': resource_base.NULL_DOMAIN_ID,
'is_domain': True,
'parent_id': None,
'extra': '{}'
}
table.insert().values(domain).execute()
return domain_id
def create_idp(domain_id):
table = sqlalchemy.Table('identity_provider', self.metadata,
autoload=True)
idp_id = uuid.uuid4().hex
idp = {
'id': idp_id,
'domain_id': domain_id,
'enabled': True,
'description': uuid.uuid4().hex
}
table.insert().values(idp).execute()
return idp_id
def create_protocol(idp_id):
table = sqlalchemy.Table('federation_protocol', self.metadata,
autoload=True)
protocol_id = uuid.uuid4().hex
protocol = {
'id': protocol_id,
'idp_id': idp_id,
'mapping_id': uuid.uuid4().hex
}
table.insert().values(protocol).execute()
return protocol_id
def create_user():
table = sqlalchemy.Table('user', self.metadata, autoload=True)
user_id = uuid.uuid4().hex
user = {'id': user_id, 'enabled': True}
table.insert().values(user).execute()
return user_id
def create_federated_user(user_id, idp_id, protocol_id):
table = sqlalchemy.Table('federated_user', self.metadata,
autoload=True)
federated_user = {
'user_id': user_id,
'idp_id': idp_id,
'protocol_id': protocol_id,
'unique_id': uuid.uuid4().hex,
'display_name': uuid.uuid4().hex
}
table.insert().values(federated_user).execute()
def assertUserDomain(user_id, domain_id):
table = sqlalchemy.Table('user', self.metadata, autoload=True)
where = table.c.id == user_id
stmt = sqlalchemy.select([table.c.domain_id]).where(where)
domains = stmt.execute().fetchone()
self.assertEqual(domain_id, domains[0])
def assertUserDomainIsNone(user_id):
table = sqlalchemy.Table('user', self.metadata, autoload=True)
where = table.c.id == user_id
stmt = sqlalchemy.select([table.c.domain_id]).where(where)
domains = stmt.execute().fetchone()
self.assertIsNone(domains[0])
self.expand(14)
self.migrate(14)
self.contract(14)
domain_id = create_domain()
idp_id = create_idp(domain_id)
protocol_id = create_protocol(idp_id)
# create user before expand to test data migration
user_id_before_expand = create_user()
create_federated_user(user_id_before_expand, idp_id, protocol_id)
assertUserDomainIsNone(user_id_before_expand)
self.expand(15)
# create user before migrate to test insert trigger
user_id_before_migrate = create_user()
create_federated_user(user_id_before_migrate, idp_id, protocol_id)
assertUserDomain(user_id_before_migrate, domain_id)
self.migrate(15)
# test insert trigger after migrate
user_id = create_user()
create_federated_user(user_id, idp_id, protocol_id)
assertUserDomain(user_id, domain_id)
self.contract(15)
# test migrate updated the user.domain_id
assertUserDomain(user_id_before_expand, domain_id)
# verify that the user.domain_id is now not nullable
user_table = sqlalchemy.Table('user', self.metadata, autoload=True)
self.assertFalse(user_table.c.domain_id.nullable)
def test_migration_016_add_user_options(self):
self.expand(15)
self.migrate(15)
self.contract(15)
user_option = 'user_option'
self.assertTableDoesNotExist(user_option)
self.expand(16)
self.migrate(16)
self.contract(16)
self.assertTableColumns(user_option,
['user_id', 'option_id', 'option_value'])
def test_migration_024_add_created_expires_at_int_columns_password(self):
self.expand(23)
@ -2181,10 +1758,6 @@ class FullMigration(MigrateBase, unit.TestCase):
class MySQLOpportunisticFullMigration(FullMigration):
FIXTURE = db_fixtures.MySQLOpportunisticFixture
def test_migration_012_add_domain_id_to_idp(self):
self.skip_test_overrides('skipped to update u-c for PyMySql version'
'to 0.10.0')
class PostgreSQLOpportunisticFullMigration(FullMigration):
FIXTURE = db_fixtures.PostgresqlOpportunisticFixture