Merge "Add migration 19 - move image location data"

This commit is contained in:
Jenkins
2013-02-21 18:53:43 +00:00
committed by Gerrit Code Review
4 changed files with 187 additions and 4 deletions

View File

@@ -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):
"""

View File

@@ -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()

View File

@@ -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'

View File

@@ -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)