Merge "Add migration 19 - move image location data"
This commit is contained in:
@@ -267,6 +267,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()
|
||||
@@ -274,6 +284,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
|
||||
@@ -289,7 +300,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):
|
||||
@@ -487,7 +502,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:
|
||||
@@ -581,7 +597,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):
|
||||
@@ -640,6 +656,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)
|
||||
|
||||
@@ -687,9 +709,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)
|
||||
|
||||
Reference in New Issue
Block a user