From f4daa5e97b0d20b6602f30a6ec4eb0b70544cc57 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Tue, 7 Oct 2014 15:29:56 -0400 Subject: [PATCH] - Update the migrations tool to verify migrations work up and down for both MySQL and PostgresSQL. - Add migrations for the squashed image tables and for backfilling the uncompressed sizes - Make sure gzip stream uses a max length when determining the uncompressed size --- data/database.py | 2 +- data/migrations/env.py | 7 +- data/migrations/generate-schema-migration.sh | 21 ----- data/migrations/migration.sh | 82 ++++++++++++++++++ ...5_calculate_uncompressed_sizes_for_all_.py | 22 +++++ ...4461dc_add_support_for_squashed_images.py} | 36 ++++---- data/model/legacy.py | 2 +- test/data/test.db | Bin 626688 -> 684032 bytes util/gzipstream.py | 9 +- {tools => util}/uncompressedsize.py | 14 ++- 10 files changed, 152 insertions(+), 43 deletions(-) delete mode 100755 data/migrations/generate-schema-migration.sh create mode 100755 data/migrations/migration.sh create mode 100644 data/migrations/versions/2430f55c41d5_calculate_uncompressed_sizes_for_all_.py rename data/migrations/versions/{3f6d26399bd2_add_support_for_squashing_images.py => 3b4d3a4461dc_add_support_for_squashed_images.py} (64%) rename {tools => util}/uncompressedsize.py (84%) diff --git a/data/database.py b/data/database.py index 3ad10b7b2..b0b1c031a 100644 --- a/data/database.py +++ b/data/database.py @@ -168,7 +168,7 @@ class Visibility(BaseModel): class Repository(BaseModel): - namespace_user = ForeignKeyField(User) + namespace_user = ForeignKeyField(User, null=True) name = CharField() visibility = ForeignKeyField(Visibility) description = TextField(null=True) diff --git a/data/migrations/env.py b/data/migrations/env.py index 5f7bb986d..f27a483f7 100644 --- a/data/migrations/env.py +++ b/data/migrations/env.py @@ -17,7 +17,12 @@ from util.morecollections import AttrDict # access to the values within the .ini file in use. db_uri = unquote(app.config['DB_URI']) if 'GENMIGRATE' in os.environ: - db_uri = 'mysql+pymysql://root:password@192.168.59.103/genschema' + docker_host = os.environ.get('DOCKER_HOST') + docker_host_ip = docker_host[len('tcp://'):].split(':')[0] + if os.environ.get('GENMIGRATE') == 'mysql': + db_uri = 'mysql+pymysql://root:password@%s/genschema' % (docker_host_ip) + else: + db_uri = 'postgresql://postgres@%s/genschema' % (docker_host_ip) config = context.config config.set_main_option('sqlalchemy.url', db_uri) diff --git a/data/migrations/generate-schema-migration.sh b/data/migrations/generate-schema-migration.sh deleted file mode 100755 index c7db14b32..000000000 --- a/data/migrations/generate-schema-migration.sh +++ /dev/null @@ -1,21 +0,0 @@ -set -e - -# Run a MySQL database on port 3306 inside of Docker. -docker run --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=password -d mysql - -# Sleep for 5s to get MySQL get started. -echo 'Sleeping for 5...' -sleep 5 - -# Add the database to mysql. -docker run --link mysql:mysql mysql sh -c 'echo "create database genschema" | mysql -h"$MYSQL_PORT_3306_TCP_ADDR" -P"$MYSQL_PORT_3306_TCP_PORT" -uroot -ppassword' - -# Generate a SQLite database with the schema as defined by the existing alembic model. -GENMIGRATE=true PYTHONPATH=. alembic upgrade head - -# Generate the migration to the current model. -GENMIGRATE=true PYTHONPATH=. alembic revision --autogenerate -m "$@" - -# Kill the MySQL instance. -docker kill mysql -docker rm mysql \ No newline at end of file diff --git a/data/migrations/migration.sh b/data/migrations/migration.sh new file mode 100755 index 000000000..98a12d6ac --- /dev/null +++ b/data/migrations/migration.sh @@ -0,0 +1,82 @@ +set -e + +up_mysql() { + # Run a SQL database on port 3306 inside of Docker. + docker run --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=password -d mysql + + # Sleep for 5s to get MySQL get started. + echo 'Sleeping for 5...' + sleep 5 + + # Add the database to mysql. + docker run --rm --link mysql:mysql mysql sh -c 'echo "create database genschema" | mysql -h"$MYSQL_PORT_3306_TCP_ADDR" -P"$MYSQL_PORT_3306_TCP_PORT" -uroot -ppassword' +} + +down_mysql() { + docker kill mysql + docker rm mysql +} + +up_postgres() { + # Run a SQL database on port 5432 inside of Docker. + docker run --name postgres -p 5432:5432 -d postgres + + # Sleep for 5s to get SQL get started. + echo 'Sleeping for 5...' + sleep 5 + + # Add the database to postgres. + docker run --rm --link postgres:postgres postgres sh -c 'echo "create database genschema" | psql -h "$POSTGRES_PORT_5432_TCP_ADDR" -p "$POSTGRES_PORT_5432_TCP_PORT" -U postgres' +} + +down_postgres() { + docker kill postgres + docker rm postgres +} + +gen_migrate() { + # Generate the migration to the current model. + GENMIGRATE=$1 PYTHONPATH=. alembic revision --autogenerate -m "$@" + + # Generate a SQLite database with the schema as defined by the existing alembic model. + GENMIGRATE=$1 PYTHONPATH=. alembic upgrade head +} + +test_migrate() { + # Generate a SQLite database with the schema as defined by the existing alembic model. + GENMIGRATE=$1 PYTHONPATH=. alembic upgrade head + + # Downgrade to verify it works in both directions. + COUNT=`ls data/migrations/versions/*.py | wc -l | tr -d ' '` + GENMIGRATE=$1 PYTHONPATH=. alembic downgrade "-$COUNT" +} + +# Test (and generate, if requested) via MySQL. +echo '> Starting MySQL' +up_mysql + +if [ ! -z "$@" ] + then + set +e + echo '> Generating Migration' + gen_migrate "mysql" + set -e + fi + +echo '> Testing Migration (mysql)' +set +e +test_migrate "mysql" +set -e +down_mysql + +# Test via Postgres. +echo '> Starting Postgres' +up_postgres + +echo '> Testing Migration (postgres)' +set +e +test_migrate "postgres" +set -e +down_postgres + + diff --git a/data/migrations/versions/2430f55c41d5_calculate_uncompressed_sizes_for_all_.py b/data/migrations/versions/2430f55c41d5_calculate_uncompressed_sizes_for_all_.py new file mode 100644 index 000000000..df2ed6f14 --- /dev/null +++ b/data/migrations/versions/2430f55c41d5_calculate_uncompressed_sizes_for_all_.py @@ -0,0 +1,22 @@ +"""Calculate uncompressed sizes for all images + +Revision ID: 2430f55c41d5 +Revises: 3b4d3a4461dc +Create Date: 2014-10-07 14:50:04.660315 + +""" + +# revision identifiers, used by Alembic. +revision = '2430f55c41d5' +down_revision = '3b4d3a4461dc' + +from alembic import op +import sqlalchemy as sa +from util.uncompressedsize import backfill_sizes_from_data + + +def upgrade(tables): + backfill_sizes_from_data() + +def downgrade(tables): + pass diff --git a/data/migrations/versions/3f6d26399bd2_add_support_for_squashing_images.py b/data/migrations/versions/3b4d3a4461dc_add_support_for_squashed_images.py similarity index 64% rename from data/migrations/versions/3f6d26399bd2_add_support_for_squashing_images.py rename to data/migrations/versions/3b4d3a4461dc_add_support_for_squashed_images.py index 932ffde51..7d85f0508 100644 --- a/data/migrations/versions/3f6d26399bd2_add_support_for_squashing_images.py +++ b/data/migrations/versions/3b4d3a4461dc_add_support_for_squashed_images.py @@ -1,25 +1,24 @@ -"""add support for squashing images +"""Add support for squashed images -Revision ID: 3f6d26399bd2 -Revises: 34fd69f63809 -Create Date: 2014-09-22 14:37:30.821785 +Revision ID: 3b4d3a4461dc +Revises: b1d41e2071b +Create Date: 2014-10-07 14:49:13.105746 """ # revision identifiers, used by Alembic. -revision = '3f6d26399bd2' -down_revision = '34fd69f63809' +revision = '3b4d3a4461dc' +down_revision = 'b1d41e2071b' from alembic import op import sqlalchemy as sa - def upgrade(tables): ### commands auto generated by Alembic - please adjust! ### op.create_table('imagestoragetransformation', sa.Column('id', sa.Integer(), nullable=False), sa.Column('name', sa.String(length=255), nullable=False), - sa.PrimaryKeyConstraint('id') + sa.PrimaryKeyConstraint('id', name=op.f('pk_imagestoragetransformation')) ) op.create_index('imagestoragetransformation_name', 'imagestoragetransformation', ['name'], unique=True) op.create_table('derivedimagestorage', @@ -27,10 +26,10 @@ def upgrade(tables): sa.Column('source_id', sa.Integer(), nullable=True), sa.Column('derivative_id', sa.Integer(), nullable=False), sa.Column('transformation_id', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['derivative_id'], ['imagestorage.id'], ), - sa.ForeignKeyConstraint(['source_id'], ['imagestorage.id'], ), - sa.ForeignKeyConstraint(['transformation_id'], ['imagestoragetransformation.id'], ), - sa.PrimaryKeyConstraint('id') + sa.ForeignKeyConstraint(['derivative_id'], ['imagestorage.id'], name=op.f('fk_derivedimagestorage_derivative_id_imagestorage')), + sa.ForeignKeyConstraint(['source_id'], ['imagestorage.id'], name=op.f('fk_derivedimagestorage_source_id_imagestorage')), + sa.ForeignKeyConstraint(['transformation_id'], ['imagestoragetransformation.id'], name=op.f('fk_dis_transformation_id_ist')), + sa.PrimaryKeyConstraint('id', name=op.f('pk_derivedimagestorage')) ) op.create_index('derivedimagestorage_derivative_id', 'derivedimagestorage', ['derivative_id'], unique=False) op.create_index('derivedimagestorage_source_id', 'derivedimagestorage', ['source_id'], unique=False) @@ -38,18 +37,21 @@ def upgrade(tables): op.create_index('derivedimagestorage_transformation_id', 'derivedimagestorage', ['transformation_id'], unique=False) op.drop_index('image_repository_id_docker_image_id', table_name='image') op.create_index('image_repository_id_docker_image_id', 'image', ['repository_id', 'docker_image_id'], unique=True) + op.drop_index('imagestorage_uuid', table_name='imagestorage') + op.create_index('imagestorage_uuid', 'imagestorage', ['uuid'], unique=False) + op.drop_column(u'repository', 'namespace') + op.create_index('repository_namespace_user_id', 'repository', ['namespace_user_id'], unique=False) ### end Alembic commands ### def downgrade(tables): ### commands auto generated by Alembic - please adjust! ### + op.drop_index('repository_namespace_user_id', table_name='repository') + op.add_column(u'repository', sa.Column('namespace', sa.String(length=255), nullable=True)) + op.drop_index('imagestorage_uuid', table_name='imagestorage') + op.create_index('imagestorage_uuid', 'imagestorage', ['uuid'], unique=True) op.drop_index('image_repository_id_docker_image_id', table_name='image') op.create_index('image_repository_id_docker_image_id', 'image', ['repository_id', 'docker_image_id'], unique=False) - op.drop_index('derivedimagestorage_transformation_id', table_name='derivedimagestorage') - op.drop_index('derivedimagestorage_source_id_transformation_id', table_name='derivedimagestorage') - op.drop_index('derivedimagestorage_source_id', table_name='derivedimagestorage') - op.drop_index('derivedimagestorage_derivative_id', table_name='derivedimagestorage') op.drop_table('derivedimagestorage') - op.drop_index('imagestoragetransformation_name', table_name='imagestoragetransformation') op.drop_table('imagestoragetransformation') ### end Alembic commands ### diff --git a/data/model/legacy.py b/data/model/legacy.py index fae1d694a..e930d82d0 100644 --- a/data/model/legacy.py +++ b/data/model/legacy.py @@ -13,7 +13,7 @@ from data.database import (User, Repository, Image, AccessToken, Role, Repositor Notification, ImageStorageLocation, ImageStoragePlacement, ExternalNotificationEvent, ExternalNotificationMethod, RepositoryNotification, RepositoryAuthorizedEmail, TeamMemberInvite, - random_string_generator, db, BUILD_PHASE) + DerivedImageStorage, random_string_generator, db, BUILD_PHASE) from peewee import JOIN_LEFT_OUTER, fn from util.validation import (validate_username, validate_email, validate_password, INVALID_PASSWORD_MESSAGE) diff --git a/test/data/test.db b/test/data/test.db index 4be5d0b90e533ee3120dc04b47e6b0836a596007..16e215a0584c7226266a8b7f273e33e15eae78f7 100644 GIT binary patch delta 43347 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 0: + size_info.size += len(decompressor.decompress(current_data, CHUNK_SIZE)) + current_data = decompressor.unconsumed_tail return size_info, fn diff --git a/tools/uncompressedsize.py b/util/uncompressedsize.py similarity index 84% rename from tools/uncompressedsize.py rename to util/uncompressedsize.py index 53bb74126..7cfa86e2a 100644 --- a/tools/uncompressedsize.py +++ b/util/uncompressedsize.py @@ -1,5 +1,6 @@ import logging import zlib +import sys from data import model from data.database import ImageStorage @@ -15,6 +16,15 @@ CHUNK_SIZE = 5 * 1024 * 1024 def backfill_sizes_from_data(): + logger.setLevel(logging.DEBUG) + logger.debug('Starting uncompressed image size backfill') + + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + + ch = logging.StreamHandler(sys.stdout) + ch.setFormatter(formatter) + logger.addHandler(ch) + while True: # Load the record from the DB. batch_ids = list(ImageStorage @@ -47,7 +57,9 @@ def backfill_sizes_from_data(): if len(current_data) == 0: break - uncompressed_size += len(decompressor.decompress(current_data)) + while current_data: + uncompressed_size += len(decompressor.decompress(current_data, CHUNK_SIZE)) + current_data = decompressor.unconsumed_tail # Write the size to the image storage. We do so under a transaction AFTER checking to # make sure the image storage still exists and has not changed.