Add migration 19 - move image location data
Migration 19 moves image location data from images.location to the new image_locations table. The sqlalchemy db driver has been updated to look at this new table, but the complexity of multiple locations is not yet exposed to the reset of the system. Related to bp multiple-image-locations Change-Id: I6c4c684f522d9b41b9930e7f79a4e6fb3e3f5c39
This commit is contained in:
parent
b0360709bd
commit
fd9d069a6e
|
@ -266,6 +266,16 @@ def image_destroy(context, image_id):
|
|||
return image_ref
|
||||
|
||||
|
||||
def _limit_image_locations(image):
|
||||
#NOTE(bcwaldon): mock this out until we support multiple images above
|
||||
# the sqlalchemy db layer
|
||||
if len(image.locations) > 0:
|
||||
image.location = image.locations[0].value
|
||||
else:
|
||||
image.location = None
|
||||
return image
|
||||
|
||||
|
||||
def image_get(context, image_id, session=None, force_show_deleted=False):
|
||||
"""Get an image or raise if it does not exist."""
|
||||
session = session or get_session()
|
||||
|
@ -273,6 +283,7 @@ def image_get(context, image_id, session=None, force_show_deleted=False):
|
|||
try:
|
||||
query = session.query(models.Image)\
|
||||
.options(sa_orm.joinedload(models.Image.properties))\
|
||||
.options(sa_orm.joinedload(models.Image.locations))\
|
||||
.filter_by(id=image_id)
|
||||
|
||||
# filter out deleted images if context disallows it
|
||||
|
@ -288,7 +299,11 @@ def image_get(context, image_id, session=None, force_show_deleted=False):
|
|||
if not is_image_visible(context, image):
|
||||
raise exception.Forbidden("Image not visible to you")
|
||||
|
||||
return image
|
||||
#NOTE(bcwaldon): mock this out until we support multiple images above
|
||||
# the sqlalchemy db layer
|
||||
image.location = _image_location_get(image_id, session)
|
||||
|
||||
return _limit_image_locations(image)
|
||||
|
||||
|
||||
def is_image_mutable(context, image):
|
||||
|
@ -484,7 +499,8 @@ def image_get_all(context, filters=None, marker=None, limit=None,
|
|||
|
||||
session = get_session()
|
||||
query = session.query(models.Image)\
|
||||
.options(sa_orm.joinedload(models.Image.properties))
|
||||
.options(sa_orm.joinedload(models.Image.properties))\
|
||||
.options(sa_orm.joinedload(models.Image.locations))
|
||||
|
||||
# NOTE(markwash) treat is_public=None as if it weren't filtered
|
||||
if 'is_public' in filters and filters['is_public'] is None:
|
||||
|
@ -554,7 +570,7 @@ def image_get_all(context, filters=None, marker=None, limit=None,
|
|||
marker=marker_image,
|
||||
sort_dir=sort_dir)
|
||||
|
||||
return query.all()
|
||||
return [_limit_image_locations(image) for image in query.all()]
|
||||
|
||||
|
||||
def _drop_protected_attrs(model_class, values):
|
||||
|
@ -613,6 +629,12 @@ def _image_update(context, values, image_id, purge_props=False):
|
|||
# not a dict.
|
||||
properties = values.pop('properties', {})
|
||||
|
||||
try:
|
||||
location = values.pop('location')
|
||||
location_provided = True
|
||||
except KeyError:
|
||||
location_provided = False
|
||||
|
||||
if image_id:
|
||||
image_ref = image_get(context, image_id, session=session)
|
||||
|
||||
|
@ -660,9 +682,36 @@ def _image_update(context, values, image_id, purge_props=False):
|
|||
_set_properties_for_image(context, image_ref, properties, purge_props,
|
||||
session)
|
||||
|
||||
if location_provided:
|
||||
_image_location_set(image_ref.id, location, session)
|
||||
|
||||
return image_get(context, image_ref.id)
|
||||
|
||||
|
||||
def _image_location_get(image_id, session):
|
||||
location = session.query(models.ImageLocation)\
|
||||
.filter_by(image_id=image_id)\
|
||||
.filter_by(deleted=False)\
|
||||
.first()
|
||||
try:
|
||||
return location['value']
|
||||
except TypeError:
|
||||
return None
|
||||
|
||||
|
||||
def _image_location_set(image_id, location, session):
|
||||
locations = session.query(models.ImageLocation)\
|
||||
.filter_by(image_id=image_id)\
|
||||
.filter_by(deleted=False)\
|
||||
.all()
|
||||
for location_ref in locations:
|
||||
location_ref.delete(session=session)
|
||||
|
||||
if location is not None:
|
||||
location_ref = models.ImageLocation(image_id=image_id, value=location)
|
||||
location_ref.save()
|
||||
|
||||
|
||||
def _set_properties_for_image(context, image_ref, properties,
|
||||
purge_props=False, session=None):
|
||||
"""
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# 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.
|
||||
|
||||
import sqlalchemy
|
||||
|
||||
import logging as base_logging
|
||||
import glance.openstack.common.log as logging
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
sa_logger = base_logging.getLogger('sqlalchemy.engine')
|
||||
sa_logger.setLevel(base_logging.DEBUG)
|
||||
|
||||
|
||||
def get_images_table(meta):
|
||||
return sqlalchemy.Table('images', meta, autoload=True)
|
||||
|
||||
|
||||
def get_image_locations_table(meta):
|
||||
return sqlalchemy.Table('image_locations', meta, autoload=True)
|
||||
|
||||
|
||||
def upgrade(migrate_engine):
|
||||
meta = sqlalchemy.schema.MetaData(migrate_engine)
|
||||
|
||||
images_table = get_images_table(meta)
|
||||
image_locations_table = get_image_locations_table(meta)
|
||||
|
||||
image_records = images_table.select().execute().fetchall()
|
||||
for image in image_records:
|
||||
if image.location is not None:
|
||||
values = {
|
||||
'image_id': image.id,
|
||||
'value': image.location,
|
||||
'created_at': image.created_at,
|
||||
'updated_at': image.updated_at,
|
||||
'deleted': image.deleted,
|
||||
'deleted_at': image.deleted_at,
|
||||
}
|
||||
image_locations_table.insert(values=values).execute()
|
||||
|
||||
|
||||
def downgrade(migrate_engine):
|
||||
meta = sqlalchemy.schema.MetaData(migrate_engine)
|
||||
|
||||
images_table = get_images_table(meta)
|
||||
image_locations_table = get_image_locations_table(meta)
|
||||
|
||||
image_records = image_locations_table.select().execute().fetchall()
|
||||
|
||||
for image_location in image_records:
|
||||
images_table.update(values={'location': image_location.value})\
|
||||
.where(images_table.c.id == image_location.image_id)\
|
||||
.execute()
|
|
@ -108,7 +108,6 @@ class Image(BASE, ModelBase):
|
|||
size = Column(BigInteger)
|
||||
status = Column(String(30), nullable=False)
|
||||
is_public = Column(Boolean, nullable=False, default=False)
|
||||
location = Column(Text)
|
||||
checksum = Column(String(32))
|
||||
min_disk = Column(Integer(), nullable=False, default=0)
|
||||
min_ram = Column(Integer(), nullable=False, default=0)
|
||||
|
@ -139,6 +138,16 @@ class ImageTag(BASE, ModelBase):
|
|||
value = Column(String(255), nullable=False)
|
||||
|
||||
|
||||
class ImageLocation(BASE, ModelBase):
|
||||
"""Represents an image location in the datastore"""
|
||||
__tablename__ = 'image_locations'
|
||||
|
||||
id = Column(Integer, primary_key=True, nullable=False)
|
||||
image_id = Column(String(36), ForeignKey('images.id'), nullable=False)
|
||||
image = relationship(Image, backref=backref('locations'))
|
||||
value = Column(Text(), nullable=False)
|
||||
|
||||
|
||||
class ImageMember(BASE, ModelBase):
|
||||
"""Represents an image members in the datastore"""
|
||||
__tablename__ = 'image_members'
|
||||
|
|
|
@ -606,3 +606,59 @@ class TestMigrations(utils.BaseTestCase):
|
|||
migration_api.downgrade(16)
|
||||
|
||||
assert_locations()
|
||||
|
||||
def test_migration_19(self):
|
||||
for key, engine in self.engines.items():
|
||||
self.config(sql_connection=TestMigrations.TEST_DATABASES[key])
|
||||
|
||||
migration_api.version_control(version=0)
|
||||
migration_api.upgrade(18)
|
||||
|
||||
images_table = Table('images', MetaData(engine), autoload=True)
|
||||
|
||||
now = datetime.datetime.now()
|
||||
base_values = {
|
||||
'deleted': False,
|
||||
'created_at': now,
|
||||
'updated_at': now,
|
||||
'status': 'active',
|
||||
'is_public': True,
|
||||
'min_disk': 0,
|
||||
'min_ram': 0,
|
||||
}
|
||||
images = [
|
||||
{'id': 1, 'location': 'http://glance.example.com'},
|
||||
#NOTE(bcwaldon): images with a location of None should
|
||||
# not be migrated
|
||||
{'id': 2, 'location': None},
|
||||
]
|
||||
map(lambda image: image.update(base_values), images)
|
||||
for image in images:
|
||||
images_table.insert().values(image).execute()
|
||||
|
||||
migration_api.upgrade(19)
|
||||
|
||||
image_locations_table = Table('image_locations', MetaData(engine),
|
||||
autoload=True)
|
||||
records = image_locations_table.select().execute().fetchall()
|
||||
|
||||
self.assertEqual(len(records), 1)
|
||||
locations = dict([(il.image_id, il.value) for il in records])
|
||||
self.assertEqual({'1': 'http://glance.example.com'}, locations)
|
||||
|
||||
image_locations_table = Table('image_locations', MetaData(engine),
|
||||
autoload=True)
|
||||
image_locations_table.update()\
|
||||
.where(image_locations_table.c.image_id == 1)\
|
||||
.values(value='http://swift.example.com')\
|
||||
.execute()
|
||||
|
||||
migration_api.downgrade(18)
|
||||
|
||||
images_table = Table('images', MetaData(engine), autoload=True)
|
||||
records = images_table.select().execute().fetchall()
|
||||
|
||||
self.assertEqual(len(records), 2)
|
||||
locations = dict([(i.id, i.location) for i in records])
|
||||
self.assertEqual({'1': 'http://swift.example.com', '2': None},
|
||||
locations)
|
||||
|
|
Loading…
Reference in New Issue