Merge lp:~donal-lafferty/glance/fix739433 into lp:~glance-coresec/glance/cactus-trunk

Proposed by Donal Lafferty
Status: Merged
Approved by: Jay Pipes
Approved revision: 99
Merged at revision: 104
Proposed branch: lp:~donal-lafferty/glance/fix739433
Merge into: lp:~glance-coresec/glance/cactus-trunk
Diff against target: 277 lines (+201/-3)
5 files modified
glance/registry/db/migrate_repo/schema.py (+3/-0)
glance/registry/db/migrate_repo/versions/005_size_big_integer.py (+99/-0)
glance/registry/db/models.py (+2/-2)
tests/functional/__init__.py (+37/-1)
tests/functional/test_curl_api.py (+60/-0)
To merge this branch: bzr merge lp:~donal-lafferty/glance/fix739433
Reviewer Review Type Date Requested Status
Jay Pipes (community) Approve
Devin Carlen (community) Approve
Review via email: mp+55797@code.launchpad.net

Description of the change

Provide revised schema and migration scripts for turning 'size' column in 'images' table to BIGINT. This overcomes a 2 gig limit on images sizes that can be downloaded from Glance.

To post a comment you must log in.
Revision history for this message
Devin Carlen (devcamcar) wrote :

lgtm

review: Approve
Revision history for this message
Jay Pipes (jaypipes) wrote :

Donal, I took this branch and made changes to make the migration script Python-based, instead of just MySQL .sql migrate files. The reason is because we also needed PostgreSQL to migrate.

There is an issue with sa-migrate and SQLite, however, that produced an error in the sa-migrate workaround code for SQLite not having ALTER TABLE MODIFY COLUMN abilities. But, since SQLite doesn't have a concept of BIGINT, I simply made sure that the migrate script doesn't alter anything for SQLite engines.

Please merge lp:~jaypipes/glance/bug739433 into your branch locally and bzr commit && bzr push. While you do that, please do set this Merge Request to Work In Progress status, then back to Needs Review when you've pushed. Thanks for your patience on this.

Revision history for this message
Donal Lafferty (donal-lafferty) wrote :

Updated code to reflect changes from lp:~jaypipes/glance/bug739433

Ran migration unit tests locally. Noted that making use of MySQL for tests required updating the vitualenv to include an installation of MySQLdb. This is because MySQLdb is not a dependency for the test vitualenv. E.g. after tests fail, use

sudo pip install mysql-python -e ./.glance-venv

Then run the tests again.

Revision history for this message
Jay Pipes (jaypipes) wrote :

approve from me :)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'glance/registry/db/migrate_repo/schema.py'
--- glance/registry/db/migrate_repo/schema.py 2011-02-02 23:44:56 +0000
+++ glance/registry/db/migrate_repo/schema.py 2011-04-05 21:27:46 +0000
@@ -47,6 +47,9 @@
47Integer = lambda: sqlalchemy.types.Integer()47Integer = lambda: sqlalchemy.types.Integer()
4848
4949
50BigInteger = lambda: sqlalchemy.types.BigInteger()
51
52
50def from_migration_import(module_name, fromlist):53def from_migration_import(module_name, fromlist):
51 """Import a migration file and return the module54 """Import a migration file and return the module
5255
5356
=== added file 'glance/registry/db/migrate_repo/versions/005_size_big_integer.py'
--- glance/registry/db/migrate_repo/versions/005_size_big_integer.py 1970-01-01 00:00:00 +0000
+++ glance/registry/db/migrate_repo/versions/005_size_big_integer.py 2011-04-05 21:27:46 +0000
@@ -0,0 +1,99 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2011 OpenStack LLC.
4# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17
18from migrate.changeset import *
19from sqlalchemy import *
20
21from glance.registry.db.migrate_repo.schema import (
22 Boolean, DateTime, BigInteger, Integer, String,
23 Text, from_migration_import)
24
25
26def get_images_table(meta):
27 """
28 Returns the Table object for the images table that
29 corresponds to the images table definition of this version.
30 """
31 images = Table('images', meta,
32 Column('id', Integer(), primary_key=True, nullable=False),
33 Column('name', String(255)),
34 Column('disk_format', String(20)),
35 Column('container_format', String(20)),
36 Column('size', BigInteger()),
37 Column('status', String(30), nullable=False),
38 Column('is_public', Boolean(), nullable=False, default=False,
39 index=True),
40 Column('location', Text()),
41 Column('created_at', DateTime(), nullable=False),
42 Column('updated_at', DateTime()),
43 Column('deleted_at', DateTime()),
44 Column('deleted', Boolean(), nullable=False, default=False,
45 index=True),
46 mysql_engine='InnoDB',
47 useexisting=True)
48
49 return images
50
51
52def get_image_properties_table(meta):
53 """
54 No changes to the image properties table from 002...
55 """
56 (define_image_properties_table,) = from_migration_import(
57 '002_add_image_properties_table', ['define_image_properties_table'])
58
59 image_properties = define_image_properties_table(meta)
60 return image_properties
61
62
63def upgrade(migrate_engine):
64 meta = MetaData()
65 meta.bind = migrate_engine
66
67 # No changes to SQLite stores are necessary, since
68 # there is no BIG INTEGER type in SQLite. Unfortunately,
69 # running the Python 005_size_big_integer.py migration script
70 # on a SQLite datastore results in an error in the sa-migrate
71 # code that does the workarounds for SQLite not having
72 # ALTER TABLE MODIFY COLUMN ability
73
74 dialect = migrate_engine.url.get_dialect().name
75
76 if not dialect.startswith('sqlite'):
77 (get_images_table,) = from_migration_import(
78 '003_add_disk_format', ['get_images_table'])
79
80 images = get_images_table(meta)
81 images.columns['size'].alter(type=BigInteger())
82
83
84def downgrade(migrate_engine):
85 meta = MetaData()
86 meta.bind = migrate_engine
87
88 # No changes to SQLite stores are necessary, since
89 # there is no BIG INTEGER type in SQLite. Unfortunately,
90 # running the Python 005_size_big_integer.py migration script
91 # on a SQLite datastore results in an error in the sa-migrate
92 # code that does the workarounds for SQLite not having
93 # ALTER TABLE MODIFY COLUMN ability
94
95 dialect = migrate_engine.url.get_dialect().name
96
97 if not dialect.startswith('sqlite'):
98 images = get_images_table(meta)
99 images.columns['size'].alter(type=Integer())
0100
=== modified file 'glance/registry/db/models.py'
--- glance/registry/db/models.py 2011-03-08 15:22:44 +0000
+++ glance/registry/db/models.py 2011-04-05 21:27:46 +0000
@@ -24,7 +24,7 @@
24import datetime24import datetime
2525
26from sqlalchemy.orm import relationship, backref, exc, object_mapper, validates26from sqlalchemy.orm import relationship, backref, exc, object_mapper, validates
27from sqlalchemy import Column, Integer, String27from sqlalchemy import Column, Integer, String, BigInteger
28from sqlalchemy import ForeignKey, DateTime, Boolean, Text28from sqlalchemy import ForeignKey, DateTime, Boolean, Text
29from sqlalchemy import UniqueConstraint29from sqlalchemy import UniqueConstraint
30from sqlalchemy.ext.declarative import declarative_base30from sqlalchemy.ext.declarative import declarative_base
@@ -100,7 +100,7 @@
100 name = Column(String(255))100 name = Column(String(255))
101 disk_format = Column(String(20))101 disk_format = Column(String(20))
102 container_format = Column(String(20))102 container_format = Column(String(20))
103 size = Column(Integer)103 size = Column(BigInteger)
104 status = Column(String(30), nullable=False)104 status = Column(String(30), nullable=False)
105 is_public = Column(Boolean, nullable=False, default=False)105 is_public = Column(Boolean, nullable=False, default=False)
106 location = Column(Text)106 location = Column(Text)
107107
=== modified file 'tests/functional/__init__.py'
--- tests/functional/__init__.py 2011-03-25 18:59:56 +0000
+++ tests/functional/__init__.py 2011-04-05 21:27:46 +0000
@@ -32,6 +32,7 @@
32import tempfile32import tempfile
33import time33import time
34import unittest34import unittest
35import urlparse
3536
36from tests.utils import execute, get_unused_port37from tests.utils import execute, get_unused_port
3738
@@ -60,7 +61,7 @@
6061
61 self.image_dir = "/tmp/test.%d/images" % self.test_id62 self.image_dir = "/tmp/test.%d/images" % self.test_id
6263
63 self.sql_connection = os.environ.get('GLANCE_SQL_CONNECTION',64 self.sql_connection = os.environ.get('GLANCE_TEST_SQL_CONNECTION',
64 "sqlite://")65 "sqlite://")
65 self.pid_files = [self.api_pid_file,66 self.pid_files = [self.api_pid_file,
66 self.registry_pid_file]67 self.registry_pid_file]
@@ -68,6 +69,41 @@
6869
69 def tearDown(self):70 def tearDown(self):
70 self.cleanup()71 self.cleanup()
72 # We destroy the test data store between each test case,
73 # and recreate it, which ensures that we have no side-effects
74 # from the tests
75 self._reset_database()
76
77 def _reset_database(self):
78 conn_string = self.sql_connection
79 conn_pieces = urlparse.urlparse(conn_string)
80 if conn_string.startswith('sqlite'):
81 # We can just delete the SQLite database, which is
82 # the easiest and cleanest solution
83 db_path = conn_pieces.path.strip('/')
84 if db_path and os.path.exists(db_path):
85 os.unlink(db_path)
86 # No need to recreate the SQLite DB. SQLite will
87 # create it for us if it's not there...
88 elif conn_string.startswith('mysql'):
89 # We can execute the MySQL client to destroy and re-create
90 # the MYSQL database, which is easier and less error-prone
91 # than using SQLAlchemy to do this via MetaData...trust me.
92 database = conn_pieces.path.strip('/')
93 loc_pieces = conn_pieces.netloc.split('@')
94 host = loc_pieces[1]
95 auth_pieces = loc_pieces[0].split(':')
96 user = auth_pieces[0]
97 password = ""
98 if len(auth_pieces) > 1:
99 if auth_pieces[1].strip():
100 password = "-p%s" % auth_pieces[1]
101 sql = ("drop database if exists %(database)s; "
102 "create database %(database)s;") % locals()
103 cmd = ("mysql -u%(user)s %(password)s -h%(host)s "
104 "-e\"%(sql)s\"") % locals()
105 exitcode, out, err = execute(cmd)
106 self.assertEqual(0, exitcode)
71107
72 def cleanup(self):108 def cleanup(self):
73 """109 """
74110
=== modified file 'tests/functional/test_curl_api.py'
--- tests/functional/test_curl_api.py 2011-03-28 18:02:21 +0000
+++ tests/functional/test_curl_api.py 2011-04-05 21:27:46 +0000
@@ -25,6 +25,7 @@
25from tests.utils import execute25from tests.utils import execute
2626
27FIVE_KB = 5 * 102427FIVE_KB = 5 * 1024
28FIVE_GB = 5 * 1024 * 1024 * 1024
2829
2930
30class TestCurlApi(functional.FunctionalTest):31class TestCurlApi(functional.FunctionalTest):
@@ -324,3 +325,62 @@
324 self.assertEqual('x86_64', image['properties']['arch'])325 self.assertEqual('x86_64', image['properties']['arch'])
325326
326 self.stop_servers()327 self.stop_servers()
328
329 def test_size_greater_2G_mysql(self):
330 """
331 A test against the actual datastore backend for the registry
332 to ensure that the image size property is not truncated.
333
334 :see https://bugs.launchpad.net/glance/+bug/739433
335 """
336
337 self.cleanup()
338 api_port, reg_port, conf_file = self.start_servers()
339
340 # 1. POST /images with public image named Image1
341 # attribute and a size of 5G. Use the HTTP engine with an
342 # X-Image-Meta-Location attribute to make Glance forego
343 # "adding" the image data.
344 # Verify a 200 OK is returned
345 cmd = ("curl -i -X POST "
346 "-H 'Expect: ' " # Necessary otherwise sends 100 Continue
347 "-H 'X-Image-Meta-Location: http://example.com/fakeimage' "
348 "-H 'X-Image-Meta-Size: %d' "
349 "-H 'X-Image-Meta-Name: Image1' "
350 "-H 'X-Image-Meta-Is-Public: True' "
351 "http://0.0.0.0:%d/images") % (FIVE_GB, api_port)
352
353 exitcode, out, err = execute(cmd)
354 self.assertEqual(0, exitcode)
355
356 lines = out.split("\r\n")
357 status_line = lines[0]
358
359 self.assertEqual("HTTP/1.1 201 Created", status_line)
360
361 # Get the ID of the just-added image. This may NOT be 1, since the
362 # database in the environ variable TEST_GLANCE_CONNECTION may not
363 # have been cleared between test cases... :(
364 new_image_uri = None
365 for line in lines:
366 if line.startswith('Location:'):
367 new_image_uri = line[line.find(':') + 1:].strip()
368
369 self.assertTrue(new_image_uri is not None,
370 "Could not find a new image URI!")
371
372 # 2. HEAD /images
373 # Verify image size is what was passed in, and not truncated
374 cmd = "curl -i -X HEAD %s" % new_image_uri
375
376 exitcode, out, err = execute(cmd)
377
378 self.assertEqual(0, exitcode)
379
380 lines = out.split("\r\n")
381 status_line = lines[0]
382
383 self.assertEqual("HTTP/1.1 200 OK", status_line)
384 self.assertTrue("X-Image-Meta-Size: %d" % FIVE_GB in out,
385 "Size was supposed to be %d. Got:\n%s."
386 % (FIVE_GB, out))

Subscribers

People subscribed via source and target branches