diff --git a/keystone/tests/unit/test_sql_banned_operations.py b/keystone/tests/unit/test_sql_banned_operations.py new file mode 100644 index 0000000000..84f4f2a876 --- /dev/null +++ b/keystone/tests/unit/test_sql_banned_operations.py @@ -0,0 +1,162 @@ +# Copyright 2016 Intel Corporation +# +# 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 os + +import fixtures +from migrate.versioning import repository +from oslo_db.sqlalchemy import test_base +from oslo_db.sqlalchemy import test_migrations +import sqlalchemy +import testtools + +from keystone.common.sql import migrate_repo + + +class DBOperationNotAllowed(Exception): + pass + + +class BannedDBSchemaOperations(fixtures.Fixture): + """Ban some operations for migrations.""" + + def __init__(self, banned_resources=None): + super(BannedDBSchemaOperations, self).__init__() + self._banned_resources = banned_resources or [] + + @staticmethod + def _explode(resource, op): + raise DBOperationNotAllowed( + 'Operation %s.%s() is not allowed in a database migration' % ( + resource, op)) + + def setUp(self): + super(BannedDBSchemaOperations, self).setUp() + for resource in self._banned_resources: + self.useFixture(fixtures.MonkeyPatch( + 'sqlalchemy.%s.drop' % resource, + lambda *a, **k: self._explode(resource, 'drop'))) + self.useFixture(fixtures.MonkeyPatch( + 'sqlalchemy.%s.alter' % resource, + lambda *a, **k: self._explode(resource, 'alter'))) + + +class TestBannedDBSchemaOperations(testtools.TestCase): + """Test the BannedDBSchemaOperations fixture.""" + + def test_column(self): + """Test column drops and alters raise DBOperationNotAllowed.""" + column = sqlalchemy.Column() + with BannedDBSchemaOperations(banned_resources=['Column']): + self.assertRaises(DBOperationNotAllowed, column.drop) + self.assertRaises(DBOperationNotAllowed, column.alter) + + def test_table(self): + """Test table drops and alters raise DBOperationNotAllowed.""" + table = sqlalchemy.Table() + with BannedDBSchemaOperations(banned_resources=['Table']): + self.assertRaises(DBOperationNotAllowed, table.drop) + self.assertRaises(DBOperationNotAllowed, table.alter) + + +class KeystoneMigrationsCheckers(test_migrations.WalkVersionsMixin): + """Walk over and test all sqlalchemy-migrate migrations.""" + + @property + def INIT_VERSION(self): + return migrate_repo.DB_INIT_VERSION + + @property + def REPOSITORY(self): + migrate_file = migrate_repo.__file__ + return repository.Repository( + os.path.abspath(os.path.dirname(migrate_file)) + ) + + @property + def migration_api(self): + temp = __import__('oslo_db.sqlalchemy.migration', globals(), + locals(), ['versioning_api'], 0) + return temp.versioning_api + + @property + def migrate_engine(self): + return self.engine + + def migrate_up(self, version, with_data=False): + """Check that migrations don't cause downtime. + + Schema migrations can be done online, allowing for rolling upgrades. + """ + # NOTE(xek): + # This is a list of migrations where we allow dropping and altering + # things. The rules for adding exceptions are very specific: + # + # 1) Migrations which don't cause incompatibilities are allowed, + # for example dropping an index or constraint. + # + # 2) Migrations removing structures not used in the previous version + # are allowed (we keep compatibility between releases), ex.: + # + # a) feature is deprecated according to the deprecation policies + # (release 1), + # + # b) code supporting the feature is removed the following release + # (release 2), + # + # c) table can be dropped a release after the code has been removed + # (i.e. in release 3). + # + # 3) Any other changes which don't pass this test are disallowed. + # + # Please follow the guidelines outlined at: + # http://docs.openstack.org/developer/keystone/developing.html#online-migration + + exceptions = [ + # NOTE(xek): Reviewers: DO NOT ALLOW THINGS TO BE ADDED HERE + ] + + # NOTE(xek): We start requiring things be additive in Mitaka, so + # ignore all migrations before that point. + MITAKA_START = 81 + + if version >= MITAKA_START and version not in exceptions: + banned = ['Table', 'Column'] + else: + banned = None + with BannedDBSchemaOperations(banned): + super(KeystoneMigrationsCheckers, + self).migrate_up(version, with_data) + + snake_walk = False + downgrade = False + + def test_walk_versions(self): + self.walk_versions(self.snake_walk, self.downgrade) + + +class TestKeystoneMigrationsMySQL( + KeystoneMigrationsCheckers, test_base.MySQLOpportunisticTestCase): + pass + + +class TestKeystoneMigrationsPostgreSQL( + KeystoneMigrationsCheckers, test_base.PostgreSQLOpportunisticTestCase): + pass + + +class TestKeystoneMigrationsSQLite( + KeystoneMigrationsCheckers, test_base.DbTestCase): + pass diff --git a/test-requirements.txt b/test-requirements.txt index 65a0847149..9e28374989 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -37,3 +37,6 @@ tempest-lib>=0.13.0 # Apache-2.0 # Functional tests. requests!=2.9.0,>=2.8.1 # Apache-2.0 + +# For fixtures from oslo.db +testresources>=0.2.4 # Apache-2.0/BSD