summaryrefslogtreecommitdiff
path: root/glance
diff options
context:
space:
mode:
authorAbhishek Kekane <akekane@redhat.com>2019-06-17 08:06:15 +0000
committerAbhishek Kekane <akekane@redhat.com>2019-08-06 05:48:50 +0000
commitb4e3cb65ad2e04f0f5b94dcaf1fa951d01bfb025 (patch)
tree12f08f8a78f075daf8752b6eb49bd408dd43d30e /glance
parent733095a89947718e54b610ad528f6243e6ece440 (diff)
downloadglance-b4e3cb65ad2e04f0f5b94dcaf1fa951d01bfb025.tar.gz
Add migration script to change backend to store
As glance is changing metadata value of location from 'backend' to 'store', adding migration script which will update old image locations to use 'store' as a metadata value. NOTE: Bump master (train development) to 19 Change-Id: I1386c535bc8ff4519e6b0bb879026b05c930b791 Sem-Ver: api-break
Diffstat (limited to 'glance')
-rw-r--r--glance/db/migration.py4
-rw-r--r--glance/db/sqlalchemy/alembic_migrations/data_migrations/train_migrate01_backend_to_store.py42
-rw-r--r--glance/db/sqlalchemy/alembic_migrations/versions/train_contract01_empty.py25
-rw-r--r--glance/db/sqlalchemy/alembic_migrations/versions/train_expand01_empty.py30
-rw-r--r--glance/tests/functional/db/migrations/test_train_migrate01.py131
-rw-r--r--glance/tests/unit/test_manage.py7
6 files changed, 236 insertions, 3 deletions
diff --git a/glance/db/migration.py b/glance/db/migration.py
index 79d458427..b85866b8e 100644
--- a/glance/db/migration.py
+++ b/glance/db/migration.py
@@ -47,9 +47,9 @@ def get_backend():
# Migration-related constants
EXPAND_BRANCH = 'expand'
CONTRACT_BRANCH = 'contract'
-CURRENT_RELEASE = 'stein'
+CURRENT_RELEASE = 'train'
ALEMBIC_INIT_VERSION = 'liberty'
-LATEST_REVISION = 'queens_contract01'
+LATEST_REVISION = 'rocky_contract02'
INIT_VERSION = 0
MIGRATE_REPO_PATH = os.path.join(
diff --git a/glance/db/sqlalchemy/alembic_migrations/data_migrations/train_migrate01_backend_to_store.py b/glance/db/sqlalchemy/alembic_migrations/data_migrations/train_migrate01_backend_to_store.py
new file mode 100644
index 000000000..cfb2b8208
--- /dev/null
+++ b/glance/db/sqlalchemy/alembic_migrations/data_migrations/train_migrate01_backend_to_store.py
@@ -0,0 +1,42 @@
+# Copyright 2019 RedHat Inc
+#
+# 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 has_migrations(engine):
+ """Returns true if at least one data row can be migrated.
+
+ There are rows left to migrate if meta_data column has
+ {"backend": "...."}
+
+ Note: This method can return a false positive if data migrations
+ are running in the background as it's being called.
+ """
+ sql_query = ("select meta_data from image_locations where "
+ "INSTR(meta_data, '\"backend\":') > 0")
+ with engine.connect() as con:
+ metadata_backend = con.execute(sql_query)
+ if metadata_backend.rowcount > 0:
+ return True
+
+ return False
+
+
+def migrate(engine):
+ """Replace 'backend' with 'store' in meta_data column of image_locations"""
+ sql_query = ("UPDATE image_locations SET meta_data = REPLACE(meta_data, "
+ "'\"backend\":', '\"store\":') where INSTR(meta_data, "
+ " '\"backend\":') > 0")
+ with engine.connect() as con:
+ migrated_rows = con.execute(sql_query)
+ return migrated_rows.rowcount
diff --git a/glance/db/sqlalchemy/alembic_migrations/versions/train_contract01_empty.py b/glance/db/sqlalchemy/alembic_migrations/versions/train_contract01_empty.py
new file mode 100644
index 000000000..eaa6b546d
--- /dev/null
+++ b/glance/db/sqlalchemy/alembic_migrations/versions/train_contract01_empty.py
@@ -0,0 +1,25 @@
+# Copyright (C) 2019 RedHat Inc
+# All Rights Reserved.
+#
+# 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.
+
+
+# revision identifiers, used by Alembic.
+revision = 'train_contract01'
+down_revision = 'rocky_contract02'
+branch_labels = None
+depends_on = 'train_expand01'
+
+
+def upgrade():
+ pass
diff --git a/glance/db/sqlalchemy/alembic_migrations/versions/train_expand01_empty.py b/glance/db/sqlalchemy/alembic_migrations/versions/train_expand01_empty.py
new file mode 100644
index 000000000..d8fd277d8
--- /dev/null
+++ b/glance/db/sqlalchemy/alembic_migrations/versions/train_expand01_empty.py
@@ -0,0 +1,30 @@
+# Copyright (C) 2019 RedHat Inc
+# 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.
+
+"""empty expand for symmetry with train_contract01
+
+Revision ID: train_expand01
+Revises: rocky_expand02
+Create Date: 2019-06-17 11:55:16.657499
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = 'train_expand01'
+down_revision = 'rocky_expand02'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ pass
diff --git a/glance/tests/functional/db/migrations/test_train_migrate01.py b/glance/tests/functional/db/migrations/test_train_migrate01.py
new file mode 100644
index 000000000..6d13eb753
--- /dev/null
+++ b/glance/tests/functional/db/migrations/test_train_migrate01.py
@@ -0,0 +1,131 @@
+# Copyright 2019 RedHat Inc
+#
+# 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 datetime
+
+from oslo_db.sqlalchemy import test_base
+from oslo_db.sqlalchemy import utils as db_utils
+
+from glance.db.sqlalchemy.alembic_migrations import data_migrations
+from glance.tests.functional.db import test_migrations
+
+
+class TestTrainMigrate01Mixin(test_migrations.AlembicMigrationsMixin):
+
+ def _get_revisions(self, config):
+ return test_migrations.AlembicMigrationsMixin._get_revisions(
+ self, config, head='train_expand01')
+
+ def _pre_upgrade_train_expand01(self, engine):
+ images = db_utils.get_table(engine, 'images')
+ image_locations = db_utils.get_table(engine, 'image_locations')
+ now = datetime.datetime.now()
+
+ # inserting a public image record
+ image_1 = dict(deleted=False,
+ created_at=now,
+ status='active',
+ min_disk=0,
+ min_ram=0,
+ visibility='public',
+ id='image_1')
+ images.insert().values(image_1).execute()
+
+ image_2 = dict(deleted=False,
+ created_at=now,
+ status='active',
+ min_disk=0,
+ min_ram=0,
+ visibility='public',
+ id='image_2')
+ images.insert().values(image_2).execute()
+
+ # adding records to image_locations tables
+ temp = dict(deleted=False,
+ created_at=now,
+ image_id='image_1',
+ value='image_location_1',
+ meta_data='{"backend": "fast"}',
+ id=1)
+ image_locations.insert().values(temp).execute()
+
+ temp = dict(deleted=False,
+ created_at=now,
+ image_id='image_2',
+ value='image_location_2',
+ meta_data='{"backend": "cheap"}',
+ id=2)
+ image_locations.insert().values(temp).execute()
+
+ def _check_train_expand01(self, engine, data):
+ image_locations = db_utils.get_table(engine, 'image_locations')
+
+ # check that meta_data has 'backend' key for existing image_locations
+ rows = (image_locations.select()
+ .order_by(image_locations.c.id)
+ .execute()
+ .fetchall())
+ self.assertEqual(2, len(rows))
+ for row in rows:
+ self.assertIn('"backend":', row['meta_data'])
+
+ # run data migrations
+ data_migrations.migrate(engine)
+
+ # check that meta_data has 'backend' key replaced with 'store'
+ rows = (image_locations.select()
+ .order_by(image_locations.c.id)
+ .execute()
+ .fetchall())
+ self.assertEqual(2, len(rows))
+ for row in rows:
+ self.assertNotIn('"backend":', row['meta_data'])
+ self.assertIn('"store":', row['meta_data'])
+
+
+class TestTrainMigrate01MySQL(TestTrainMigrate01Mixin,
+ test_base.MySQLOpportunisticTestCase):
+ pass
+
+
+class TestTrainMigrate01_EmptyDBMixin(test_migrations.AlembicMigrationsMixin):
+ """This mixin is used to create an initial glance database and upgrade it
+ up to the train_expand01 revision.
+ """
+ def _get_revisions(self, config):
+ return test_migrations.AlembicMigrationsMixin._get_revisions(
+ self, config, head='train_expand01')
+
+ def _pre_upgrade_train_expand01(self, engine):
+ # New/empty database
+ pass
+
+ def _check_train_expand01(self, engine, data):
+ images = db_utils.get_table(engine, 'images')
+
+ # check that there are no rows in the images table
+ rows = (images.select()
+ .order_by(images.c.id)
+ .execute()
+ .fetchall())
+ self.assertEqual(0, len(rows))
+
+ # run data migrations
+ data_migrations.migrate(engine)
+
+
+class TestTrainMigrate01_EmptyDBMySQL(TestTrainMigrate01_EmptyDBMixin,
+ test_base.MySQLOpportunisticTestCase):
+ """This test runs the Train data migrations on an empty databse."""
+ pass
diff --git a/glance/tests/unit/test_manage.py b/glance/tests/unit/test_manage.py
index 4da916bda..f193d9171 100644
--- a/glance/tests/unit/test_manage.py
+++ b/glance/tests/unit/test_manage.py
@@ -379,6 +379,9 @@ class TestManage(TestManageBase):
exit.code)
@mock.patch(
+ 'glance.db.sqlalchemy.alembic_migrations.data_migrations.'
+ 'has_pending_migrations')
+ @mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_current_alembic_heads')
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_alembic_branch_head')
@@ -386,13 +389,15 @@ class TestManage(TestManageBase):
@mock.patch.object(manage.DbCommands, '_sync')
def test_contract(self, mock_sync, mock_validate_engine,
mock_get_alembic_branch_head,
- mock_get_current_alembic_heads):
+ mock_get_current_alembic_heads,
+ mock_has_pending_migrations):
engine = mock_validate_engine.return_value
engine.engine.name = 'mysql'
mock_get_current_alembic_heads.side_effect = ['pike_expand01',
'pike_contract01']
mock_get_alembic_branch_head.side_effect = ['pike_contract01',
'pike_expand01']
+ mock_has_pending_migrations.return_value = False
self.db.contract()
mock_sync.assert_called_once_with(version='pike_contract01')