From 6d0ca2f869864ca5a34d96934b5802f930c3e741 Mon Sep 17 00:00:00 2001 From: wangxiyuan Date: Mon, 27 Nov 2017 15:27:04 +0800 Subject: [PATCH] Add new tables for unified limits This patch addes two tables called "registered limit" and "limit" for unified limits feature. Co-Authored-By: Colleen Murphy Change-Id: I67441454fc17d20e50e6fa7199c07014c97d83f6 bp: unified-limits --- .../033_contract_add_limits_tables.py | 15 +++ .../versions/033_migrate_add_limits_tables.py | 15 +++ .../versions/033_expand_add_limits_tables.py | 68 ++++++++++ keystone/tests/unit/test_sql_upgrade.py | 117 ++++++++++++++++++ 4 files changed, 215 insertions(+) create mode 100644 keystone/common/sql/contract_repo/versions/033_contract_add_limits_tables.py create mode 100644 keystone/common/sql/data_migration_repo/versions/033_migrate_add_limits_tables.py create mode 100644 keystone/common/sql/expand_repo/versions/033_expand_add_limits_tables.py diff --git a/keystone/common/sql/contract_repo/versions/033_contract_add_limits_tables.py b/keystone/common/sql/contract_repo/versions/033_contract_add_limits_tables.py new file mode 100644 index 0000000000..8aa15c1ef2 --- /dev/null +++ b/keystone/common/sql/contract_repo/versions/033_contract_add_limits_tables.py @@ -0,0 +1,15 @@ +# 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 diff --git a/keystone/common/sql/data_migration_repo/versions/033_migrate_add_limits_tables.py b/keystone/common/sql/data_migration_repo/versions/033_migrate_add_limits_tables.py new file mode 100644 index 0000000000..8aa15c1ef2 --- /dev/null +++ b/keystone/common/sql/data_migration_repo/versions/033_migrate_add_limits_tables.py @@ -0,0 +1,15 @@ +# 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 diff --git a/keystone/common/sql/expand_repo/versions/033_expand_add_limits_tables.py b/keystone/common/sql/expand_repo/versions/033_expand_add_limits_tables.py new file mode 100644 index 0000000000..cd6149c14e --- /dev/null +++ b/keystone/common/sql/expand_repo/versions/033_expand_add_limits_tables.py @@ -0,0 +1,68 @@ +# Copyright 2018 SUSE Linux Gmbh +# Copyright 2018 Huawei +# +# 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 + + service_table = sql.Table('service', meta, autoload=True) + region_table = sql.Table('region', meta, autoload=True) + project_table = sql.Table('project', meta, autoload=True) + + registered_limit_table = sql.Table( + 'registered_limit', + meta, + sql.Column('id', sql.String(length=64), primary_key=True), + sql.Column('service_id', + sql.String(255), + sql.ForeignKey(service_table.c.id)), + sql.Column('region_id', + sql.String(64), + sql.ForeignKey(region_table.c.id), nullable=True), + sql.Column('resource_name', sql.String(255)), + sql.Column('default_limit', sql.Integer, nullable=False), + sql.UniqueConstraint('service_id', 'region_id', 'resource_name'), + mysql_engine='InnoDB', + mysql_charset='utf8') + registered_limit_table.create(migrate_engine, checkfirst=True) + + limit_table = sql.Table( + 'limit', + meta, + sql.Column('id', sql.String(length=64), primary_key=True), + sql.Column('project_id', + sql.String(64), + sql.ForeignKey(project_table.c.id)), + sql.Column('service_id', sql.String(255)), + sql.Column('region_id', sql.String(64), nullable=True), + sql.Column('resource_name', sql.String(255)), + sql.Column('resource_limit', sql.Integer, nullable=False), + sql.UniqueConstraint('project_id', 'service_id', 'region_id', + 'resource_name'), + mysql_engine='InnoDB', + mysql_charset='utf8') + limit_table.create(migrate_engine, checkfirst=True) + + migrate.ForeignKeyConstraint( + columns=[limit_table.c.service_id, + limit_table.c.region_id, + limit_table.c.resource_name], + refcolumns=[registered_limit_table.c.service_id, + registered_limit_table.c.region_id, + registered_limit_table.c.resource_name]).create() diff --git a/keystone/tests/unit/test_sql_upgrade.py b/keystone/tests/unit/test_sql_upgrade.py index 16f2839632..32913eef88 100644 --- a/keystone/tests/unit/test_sql_upgrade.py +++ b/keystone/tests/unit/test_sql_upgrade.py @@ -2589,6 +2589,123 @@ class FullMigration(SqlMigrateBase, unit.TestCase): e = e.total_seconds() self.assertEqual(t.expires_at_int, int(e * 1000000)) + def test_migration_033_adds_limits_table(self): + self.expand(32) + self.migrate(32) + self.contract(32) + + registered_limit_table_name = 'registered_limit' + limit_table_name = 'limit' + self.assertTableDoesNotExist(registered_limit_table_name) + self.assertTableDoesNotExist(limit_table_name) + + self.expand(33) + self.migrate(33) + self.contract(33) + + self.assertTableExists(registered_limit_table_name) + self.assertTableColumns( + registered_limit_table_name, + ['id', 'service_id', 'resource_name', 'region_id', 'default_limit'] + ) + self.assertTableExists(limit_table_name) + self.assertTableColumns( + limit_table_name, + ['id', 'project_id', 'service_id', 'resource_name', 'region_id', + 'resource_limit'] + ) + + session = self.sessionmaker() + service_id = uuid.uuid4().hex + service = { + 'id': service_id, + 'type': 'compute', + 'enabled': True + } + region = { + 'id': 'RegionOne', + 'description': 'test' + } + project_id = uuid.uuid4().hex + project = { + 'id': project_id, + 'name': 'nova', + 'enabled': True, + 'domain_id': resource_base.NULL_DOMAIN_ID, + 'is_domain': False + } + self.insert_dict(session, 'service', service) + self.insert_dict(session, 'region', region) + self.insert_dict(session, 'project', project) + + # Insert one registered limit + registered_limit_table = sqlalchemy.Table( + registered_limit_table_name, self.metadata, autoload=True) + registered_limit = { + 'id': uuid.uuid4().hex, + 'service_id': service_id, + 'region_id': 'RegionOne', + 'resource_name': 'cores', + 'default_limit': 10 + } + registered_limit_table.insert().values(registered_limit).execute() + + # It will raise error if insert another one with same service_id, + # region_id and resource name. + registered_limit['id'] = uuid.uuid4().hex + registered_limit['default_limit'] = 20 + self.assertRaises(db_exception.DBDuplicateEntry, + registered_limit_table.insert().values( + registered_limit).execute) + + # Insert one without region_id + registered_limit_without_region = { + 'id': uuid.uuid4().hex, + 'service_id': service_id, + 'resource_name': 'cores', + 'default_limit': 10 + } + registered_limit_table.insert().values( + registered_limit_without_region).execute() + + # It will not raise error if insert another one with same service_id + # and resource_name but the region_id is None. Because that + # UniqueConstraint doesn't work if one of the columns is None. This + # should be controlled at the Manager layer to forbid this behavior. + registered_limit_without_region['id'] = uuid.uuid4().hex + registered_limit_table.insert().values( + registered_limit_without_region).execute() + + # Insert one limit + limit_table = sqlalchemy.Table( + limit_table_name, self.metadata, autoload=True) + limit = { + 'id': uuid.uuid4().hex, + 'project_id': project_id, + 'service_id': service_id, + 'region_id': 'RegionOne', + 'resource_name': 'cores', + 'resource_limit': 5 + } + limit_table.insert().values(limit).execute() + + # Insert another one with the same project_id, service_id, region_id + # and resource_name, then raise error. + limit['id'] = uuid.uuid4().hex + limit['resource_limit'] = 10 + self.assertRaises(db_exception.DBDuplicateEntry, + limit_table.insert().values(limit).execute) + + # Insert one without region_id + limit_without_region = { + 'id': uuid.uuid4().hex, + 'project_id': project_id, + 'service_id': service_id, + 'resource_name': 'cores', + 'resource_limit': 5 + } + limit_table.insert().values(limit_without_region).execute() + class MySQLOpportunisticFullMigration(FullMigration): FIXTURE = test_base.MySQLOpportunisticFixture