Merge lp:~rconradharris/glance/registry_db_migration into lp:~glance-coresec/glance/cactus-trunk

Proposed by Rick Harris
Status: Merged
Approved by: Jay Pipes
Approved revision: 74
Merged at revision: 64
Proposed branch: lp:~rconradharris/glance/registry_db_migration
Merge into: lp:~glance-coresec/glance/cactus-trunk
Diff against target: 916 lines (+642/-20)
22 files modified
MANIFEST.in (+1/-0)
bin/glance-manage (+130/-0)
bin/glance-upload (+3/-0)
doc/source/conf.py (+2/-0)
doc/source/glanceapi.rst (+6/-6)
doc/source/man/glanceapi.rst (+1/-1)
doc/source/man/glancemanage.py (+54/-0)
doc/source/man/glanceregistry.rst (+1/-1)
glance/common/config.py (+15/-7)
glance/common/exception.py (+8/-0)
glance/registry/db/migrate_repo/README (+4/-0)
glance/registry/db/migrate_repo/__init__.py (+1/-0)
glance/registry/db/migrate_repo/manage.py (+3/-0)
glance/registry/db/migrate_repo/migrate.cfg (+20/-0)
glance/registry/db/migrate_repo/schema.py (+100/-0)
glance/registry/db/migrate_repo/versions/001_add_images_table.py (+55/-0)
glance/registry/db/migrate_repo/versions/002_add_image_properties_table.py (+63/-0)
glance/registry/db/migrate_repo/versions/__init__.py (+1/-0)
glance/registry/db/migration.py (+117/-0)
glance/registry/db/models.py (+8/-5)
tests/unit/test_migrations.py (+48/-0)
tools/pip-requires (+1/-0)
To merge this branch: bzr merge lp:~rconradharris/glance/registry_db_migration
Reviewer Review Type Date Requested Status
Matt Dietz (community) Approve
Jay Pipes (community) Approve
Review via email: mp+48406@code.launchpad.net

This proposal supersedes a proposal from 2011-02-02.

Description of the change

Adds sqlalchemy migrations.

Also adds a glance-manage utility for managing migrations.

Potentially glance-manage (and glance-upload) could be merged into glance-admin when that lands.

To post a comment you must log in.
Revision history for this message
Jay Pipes (jaypipes) wrote : Posted in a previous version of this proposal

Very nice work, Rick. Any chance we can figure out a way of testing this?

review: Needs Information
Revision history for this message
Rick Harris (rconradharris) wrote : Posted in a previous version of this proposal

I can't think of any meaningful tests as this is just a CLI wrapper around sqlalchemy-migrate (which itself has been tested quite a bit ;-).

Revision history for this message
Jay Pipes (jaypipes) wrote : Posted in a previous version of this proposal

> I can't think of any meaningful tests as this is just a CLI wrapper around
> sqlalchemy-migrate (which itself has been tested quite a bit ;-).

What about something as simple as this?

http://pastie.org/1522390

Revision history for this message
Rick Harris (rconradharris) wrote : Posted in a previous version of this proposal

Honestly, doesn't strike me as super-useful; but that said, it's better to err on the side of caution, so I'll defer to your judgement here.

Test-mode engaged...

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

OK, so I had an additional thought about where the database migration code should go. Since the database migration is only for the reference implementation Glance registry server (bin/glance-registry), I'm thinking that the bin/glance-manage program should be renamed bin/glance-registry-manage. While I understand your comment about having the glance-admin program merge with the glance-manage program in this patch, the two do different things. The database migration commands are particular only to the reference implementation registry server, and not to Glance as a whole, so I think the tool name should reflect that...thoughts?

Note that I think glance-upload can and should be merged into the glance-admin program, just not these specific registry database commands.

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

OK, let's get this merged...we can always address the binary naming stuff later... I want to push on to api-image-format blueprint..

review: Approve
Revision history for this message
Matt Dietz (cerberus) wrote :

Seems alright to me.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'MANIFEST.in'
--- MANIFEST.in 2011-01-20 10:13:46 +0000
+++ MANIFEST.in 2011-02-03 00:03:10 +0000
@@ -6,5 +6,6 @@
6include tests/test_data.py6include tests/test_data.py
7include tests/utils.py7include tests/utils.py
8include run_tests.py8include run_tests.py
9include glance/registry/db/migrate_repo/migrate.cfg
9graft doc10graft doc
10graft tools11graft tools
1112
=== added file 'bin/glance-manage'
--- bin/glance-manage 1970-01-01 00:00:00 +0000
+++ bin/glance-manage 2011-02-03 00:03:10 +0000
@@ -0,0 +1,130 @@
1#!/usr/bin/env python
2# vim: tabstop=4 shiftwidth=4 softtabstop=4
3
4# Copyright 2010 United States Government as represented by the
5# Administrator of the National Aeronautics and Space Administration.
6# Copyright 2011 OpenStack LLC.
7# All Rights Reserved.
8#
9# Licensed under the Apache License, Version 2.0 (the "License"); you may
10# not use this file except in compliance with the License. You may obtain
11# a copy of the License at
12#
13# http://www.apache.org/licenses/LICENSE-2.0
14#
15# Unless required by applicable law or agreed to in writing, software
16# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
17# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
18# License for the specific language governing permissions and limitations
19# under the License.
20
21"""
22Glance Management Utility
23"""
24
25# FIXME(sirp): When we have glance-admin we can consider merging this into it
26# Perhaps for consistency with Nova, we would then rename glance-admin ->
27# glance-manage (or the other way around)
28
29import optparse
30import os
31import sys
32
33ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
34
35sys.path.append(ROOT_DIR)
36
37from glance import version as glance_version
38from glance.common import config
39from glance.common import exception
40import glance.registry.db
41import glance.registry.db.migration
42
43
44def create_options(parser):
45 """
46 Sets up the CLI and config-file options that may be
47 parsed and program commands.
48
49 :param parser: The option parser
50 """
51 glance.registry.db.add_options(parser)
52 config.add_common_options(parser)
53 config.add_log_options('glance-manage', parser)
54
55
56def do_db_version(options, args):
57 """Print database's current migration level"""
58 print glance.registry.db.migration.db_version(options)
59
60
61def do_upgrade(options, args):
62 """Upgrade the database's migration level"""
63 try:
64 db_version = args[1]
65 except IndexError:
66 db_version = None
67
68 glance.registry.db.migration.upgrade(options, version=db_version)
69
70
71def do_downgrade(options, args):
72 """Downgrade the database's migration level"""
73 try:
74 db_version = args[1]
75 except IndexError:
76 raise exception.MissingArgumentError(
77 "downgrade requires a version argument")
78
79 glance.registry.db.migration.downgrade(options, version=db_version)
80
81
82def do_version_control(options, args):
83 """Place a database under migration control"""
84 glance.registry.db.migration.version_control(options)
85
86
87def do_db_sync(options, args):
88 """Place a database under migration control and upgrade"""
89 try:
90 db_version = args[1]
91 except IndexError:
92 db_version = None
93 glance.registry.db.migration.db_sync(options, version=db_version)
94
95
96def dispatch_cmd(options, args):
97 """Search for do_* cmd in this module and then run it"""
98 cmd = args[0]
99 try:
100 cmd_func = globals()['do_%s' % cmd]
101 except KeyError:
102 sys.exit("ERROR: unrecognized command '%s'" % cmd)
103
104 try:
105 cmd_func(options, args)
106 except exception.Error, e:
107 sys.exit("ERROR: %s" % e)
108
109
110def main():
111 version = '%%prog %s' % glance_version.version_string()
112 usage = "%prog [options] <cmd>"
113 oparser = optparse.OptionParser(usage, version=version)
114 create_options(oparser)
115 (options, args) = config.parse_options(oparser)
116
117 try:
118 config.setup_logging(options)
119 except RuntimeError, e:
120 sys.exit("ERROR: %s" % e)
121
122 if not args:
123 oparser.print_usage()
124 sys.exit(1)
125
126 dispatch_cmd(options, args)
127
128
129if __name__ == '__main__':
130 main()
0131
=== modified file 'bin/glance-upload'
--- bin/glance-upload 2011-01-23 17:18:48 +0000
+++ bin/glance-upload 2011-02-03 00:03:10 +0000
@@ -33,6 +33,9 @@
33 <filename> <name>33 <filename> <name>
3434
35"""35"""
36
37# FIXME(sirp): This can be merged into glance-admin when that becomes
38# available
36import argparse39import argparse
37import pprint40import pprint
38import sys41import sys
3942
=== modified file 'doc/source/conf.py'
--- doc/source/conf.py 2011-01-27 20:01:02 +0000
+++ doc/source/conf.py 2011-02-03 00:03:10 +0000
@@ -129,6 +129,8 @@
129 ('man/glanceapi', 'glance-api', u'Glance API Server',129 ('man/glanceapi', 'glance-api', u'Glance API Server',
130 [u'OpenStack'], 1),130 [u'OpenStack'], 1),
131 ('man/glanceregistry', 'glance-registry', u'Glance Registry Server',131 ('man/glanceregistry', 'glance-registry', u'Glance Registry Server',
132 [u'OpenStack'], 1),
133 ('man/glancemanage', 'glance-manage', u'Glance Management Utility',
132 [u'OpenStack'], 1)134 [u'OpenStack'], 1)
133 ]135 ]
134136
135137
=== modified file 'doc/source/glanceapi.rst'
--- doc/source/glanceapi.rst 2011-01-26 17:26:54 +0000
+++ doc/source/glanceapi.rst 2011-02-03 00:03:10 +0000
@@ -24,7 +24,7 @@
24Server*.24Server*.
2525
26Assume there is a Glance API server running at the URL26Assume there is a Glance API server running at the URL
27``http://glance.example.com``. 27``http://glance.example.com``.
2828
29Let's walk through how a user might request information from this server.29Let's walk through how a user might request information from this server.
3030
@@ -116,7 +116,7 @@
116 x-image-meta-store swift116 x-image-meta-store swift
117 x-image-meta-created_at 2010-02-03 09:34:01117 x-image-meta-created_at 2010-02-03 09:34:01
118 x-image-meta-updated_at 2010-02-03 09:34:01118 x-image-meta-updated_at 2010-02-03 09:34:01
119 x-image-meta-deleted_at 119 x-image-meta-deleted_at
120 x-image-meta-status available120 x-image-meta-status available
121 x-image-meta-is_public True121 x-image-meta-is_public True
122 x-image-meta-property-distro Ubuntu 10.04 LTS122 x-image-meta-property-distro Ubuntu 10.04 LTS
@@ -126,7 +126,7 @@
126 All timestamps returned are in UTC126 All timestamps returned are in UTC
127127
128 The `x-image-meta-updated_at` timestamp is the timestamp when an128 The `x-image-meta-updated_at` timestamp is the timestamp when an
129 image's metadata was last updated, not its image data, as all 129 image's metadata was last updated, not its image data, as all
130 image data is immutable once stored in Glance130 image data is immutable once stored in Glance
131131
132 There may be multiple headers that begin with the prefix132 There may be multiple headers that begin with the prefix
@@ -165,7 +165,7 @@
165 x-image-meta-store swift165 x-image-meta-store swift
166 x-image-meta-created_at 2010-02-03 09:34:01166 x-image-meta-created_at 2010-02-03 09:34:01
167 x-image-meta-updated_at 2010-02-03 09:34:01167 x-image-meta-updated_at 2010-02-03 09:34:01
168 x-image-meta-deleted_at 168 x-image-meta-deleted_at
169 x-image-meta-status available169 x-image-meta-status available
170 x-image-meta-is_public True170 x-image-meta-is_public True
171 x-image-meta-property-distro Ubuntu 10.04 LTS171 x-image-meta-property-distro Ubuntu 10.04 LTS
@@ -175,7 +175,7 @@
175 All timestamps returned are in UTC175 All timestamps returned are in UTC
176176
177 The `x-image-meta-updated_at` timestamp is the timestamp when an177 The `x-image-meta-updated_at` timestamp is the timestamp when an
178 image's metadata was last updated, not its image data, as all 178 image's metadata was last updated, not its image data, as all
179 image data is immutable once stored in Glance179 image data is immutable once stored in Glance
180180
181 There may be multiple headers that begin with the prefix181 There may be multiple headers that begin with the prefix
@@ -232,7 +232,7 @@
232232
233* ``x-image-meta-id``233* ``x-image-meta-id``
234234
235 This header is optional. 235 This header is optional.
236236
237 When present, Glance will use the supplied identifier for the image.237 When present, Glance will use the supplied identifier for the image.
238 If the identifier already exists in that Glance node, then a238 If the identifier already exists in that Glance node, then a
239239
=== modified file 'doc/source/man/glanceapi.rst'
--- doc/source/man/glanceapi.rst 2011-01-19 21:38:39 +0000
+++ doc/source/man/glanceapi.rst 2011-02-03 00:03:10 +0000
@@ -48,7 +48,7 @@
48 running ``glance-api``48 running ``glance-api``
4949
50FILES50FILES
51========51=====
5252
53None53None
5454
5555
=== added file 'doc/source/man/glancemanage.py'
--- doc/source/man/glancemanage.py 1970-01-01 00:00:00 +0000
+++ doc/source/man/glancemanage.py 2011-02-03 00:03:10 +0000
@@ -0,0 +1,54 @@
1=============
2glance-manage
3=============
4
5-------------------------
6Glance Management Utility
7-------------------------
8
9:Author: glance@lists.launchpad.net
10:Date: 2010-11-16
11:Copyright: OpenStack LLC
12:Version: 0.1.2
13:Manual section: 1
14:Manual group: cloud computing
15
16SYNOPSIS
17========
18
19 glance-manage [options]
20
21DESCRIPTION
22===========
23
24glance-manage is a utility for managing and configuring a Glance installation.
25One important use of glance-manage is to setup the database. To do this run::
26
27 glance-manage db_sync
28
29OPTIONS
30=======
31
32 **General options**
33
34 **-v, --verbose**
35 Print more verbose output
36
37 **--sql_connection=CONN_STRING**
38 A proper SQLAlchemy connection string as described
39 `here <http://www.sqlalchemy.org/docs/05/reference/sqlalchemy/connections.html?highlight=engine#sqlalchemy.create_engine>`_
40
41FILES
42=====
43
44None
45
46SEE ALSO
47========
48
49* `OpenStack Glance <http://glance.openstack.org>`__
50
51BUGS
52====
53
54* Glance is sourced in Launchpad so you can view current bugs at `OpenStack Glance <http://glance.openstack.org>`__
055
=== modified file 'doc/source/man/glanceregistry.rst'
--- doc/source/man/glanceregistry.rst 2011-01-19 21:38:39 +0000
+++ doc/source/man/glanceregistry.rst 2011-02-03 00:03:10 +0000
@@ -43,7 +43,7 @@
43 `here <http://www.sqlalchemy.org/docs/05/reference/sqlalchemy/connections.html?highlight=engine#sqlalchemy.create_engine>`_43 `here <http://www.sqlalchemy.org/docs/05/reference/sqlalchemy/connections.html?highlight=engine#sqlalchemy.create_engine>`_
4444
45FILES45FILES
46========46=====
4747
48None48None
4949
5050
=== modified file 'glance/common/config.py'
--- glance/common/config.py 2011-02-02 16:40:57 +0000
+++ glance/common/config.py 2011-02-03 00:03:10 +0000
@@ -27,9 +27,11 @@
27import os27import os
28import sys28import sys
2929
30import glance.common.exception as exception
3031
31DEFAULT_LOG_FORMAT = "%(asctime)s %(levelname)8s [%(name)s] %(message)s"32DEFAULT_LOG_FORMAT = "%(asctime)s %(levelname)8s [%(name)s] %(message)s"
32DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"33DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
34DEFAULT_LOG_HANDLER = 'stream'
33LOGGING_HANDLER_CHOICES = ['syslog', 'file', 'stream']35LOGGING_HANDLER_CHOICES = ['syslog', 'file', 'stream']
3436
3537
@@ -132,7 +134,8 @@
132 "any other logging options specified. Please see "134 "any other logging options specified. Please see "
133 "the Python logging module documentation for "135 "the Python logging module documentation for "
134 "details on logging configuration files.")136 "details on logging configuration files.")
135 group.add_option('--log-handler', default='stream', metavar="HANDLER",137 group.add_option('--log-handler', default=DEFAULT_LOG_HANDLER,
138 metavar="HANDLER",
136 choices=LOGGING_HANDLER_CHOICES,139 choices=LOGGING_HANDLER_CHOICES,
137 help="What logging handler to use? "140 help="What logging handler to use? "
138 "Default: %default")141 "Default: %default")
@@ -159,7 +162,7 @@
159 :param options: Mapping of typed option key/values162 :param options: Mapping of typed option key/values
160 """163 """
161164
162 if options['log_config']:165 if options.get('log_config', None):
163 # Use a logging configuration file for all settings...166 # Use a logging configuration file for all settings...
164 if os.path.exists(options['log_config']):167 if os.path.exists(options['log_config']):
165 logging.config.fileConfig(options['log_config'])168 logging.config.fileConfig(options['log_config'])
@@ -179,14 +182,16 @@
179 root_logger.setLevel(logging.WARNING)182 root_logger.setLevel(logging.WARNING)
180183
181 # Set log configuration from options...184 # Set log configuration from options...
182 formatter = logging.Formatter(options['log_format'],185 log_format = options.get('log_format', DEFAULT_LOG_FORMAT)
183 options['log_date_format'])186 log_date_format = options.get('log_date_format', DEFAULT_LOG_DATE_FORMAT)
187 formatter = logging.Formatter(log_format, log_date_format)
184188
185 if options['log_handler'] == 'syslog':189 log_handler = options.get('log_handler', DEFAULT_LOG_HANDLER)
190 if log_handler == 'syslog':
186 syslog = logging.handlers.SysLogHandler(address='/dev/log')191 syslog = logging.handlers.SysLogHandler(address='/dev/log')
187 syslog.setFormatter(formatter)192 syslog.setFormatter(formatter)
188 root_logger.addHandler(syslog)193 root_logger.addHandler(syslog)
189 elif options['log_handler'] == 'file':194 elif log_handler == 'file':
190 logfile = options['log_file']195 logfile = options['log_file']
191 logdir = options['log_dir']196 logdir = options['log_dir']
192 if logdir:197 if logdir:
@@ -195,10 +200,13 @@
195 logfile.setFormatter(formatter)200 logfile.setFormatter(formatter)
196 logfile.setFormatter(formatter)201 logfile.setFormatter(formatter)
197 root_logger.addHandler(logfile)202 root_logger.addHandler(logfile)
198 else:203 elif log_handler == 'stream':
199 handler = logging.StreamHandler(sys.stdout)204 handler = logging.StreamHandler(sys.stdout)
200 handler.setFormatter(formatter)205 handler.setFormatter(formatter)
201 root_logger.addHandler(handler)206 root_logger.addHandler(handler)
207 else:
208 raise exception.BadInputError(
209 "unrecognized log handler '%(log_handler)s'" % locals())
202210
203 # Log the options used when starting if we're in debug mode...211 # Log the options used when starting if we're in debug mode...
204 if debug:212 if debug:
205213
=== modified file 'glance/common/exception.py'
--- glance/common/exception.py 2011-01-26 17:26:54 +0000
+++ glance/common/exception.py 2011-02-03 00:03:10 +0000
@@ -75,6 +75,14 @@
75 pass75 pass
7676
7777
78class MissingArgumentError(Error):
79 pass
80
81
82class DatabaseMigrationError(Error):
83 pass
84
85
78def wrap_exception(f):86def wrap_exception(f):
79 def _wrap(*args, **kw):87 def _wrap(*args, **kw):
80 try:88 try:
8189
=== added directory 'glance/registry/db/migrate_repo'
=== added file 'glance/registry/db/migrate_repo/README'
--- glance/registry/db/migrate_repo/README 1970-01-01 00:00:00 +0000
+++ glance/registry/db/migrate_repo/README 2011-02-03 00:03:10 +0000
@@ -0,0 +1,4 @@
1This is a database migration repository.
2
3More information at
4http://code.google.com/p/sqlalchemy-migrate/
05
=== added file 'glance/registry/db/migrate_repo/__init__.py'
--- glance/registry/db/migrate_repo/__init__.py 1970-01-01 00:00:00 +0000
+++ glance/registry/db/migrate_repo/__init__.py 2011-02-03 00:03:10 +0000
@@ -0,0 +1,1 @@
1# template repository default module
02
=== added file 'glance/registry/db/migrate_repo/manage.py'
--- glance/registry/db/migrate_repo/manage.py 1970-01-01 00:00:00 +0000
+++ glance/registry/db/migrate_repo/manage.py 2011-02-03 00:03:10 +0000
@@ -0,0 +1,3 @@
1#!/usr/bin/env python
2from migrate.versioning.shell import main
3main(debug='False', repository='.')
04
=== added file 'glance/registry/db/migrate_repo/migrate.cfg'
--- glance/registry/db/migrate_repo/migrate.cfg 1970-01-01 00:00:00 +0000
+++ glance/registry/db/migrate_repo/migrate.cfg 2011-02-03 00:03:10 +0000
@@ -0,0 +1,20 @@
1[db_settings]
2# Used to identify which repository this database is versioned under.
3# You can use the name of your project.
4repository_id=Glance Migrations
5
6# The name of the database table used to track the schema version.
7# This name shouldn't already be used by your project.
8# If this is changed once a database is under version control, you'll need to
9# change the table name in each database too.
10version_table=migrate_version
11
12# When committing a change script, Migrate will attempt to generate the
13# sql for all supported databases; normally, if one of them fails - probably
14# because you don't have that database installed - it is ignored and the
15# commit continues, perhaps ending successfully.
16# Databases in this list MUST compile successfully during a commit, or the
17# entire commit will fail. List the databases your application will actually
18# be using to ensure your updates to that database work properly.
19# This must be a list; example: ['postgres','sqlite']
20required_dbs=[]
021
=== added file 'glance/registry/db/migrate_repo/schema.py'
--- glance/registry/db/migrate_repo/schema.py 1970-01-01 00:00:00 +0000
+++ glance/registry/db/migrate_repo/schema.py 2011-02-03 00:03:10 +0000
@@ -0,0 +1,100 @@
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
18"""
19Various conveniences used for migration scripts
20"""
21
22import logging
23
24import sqlalchemy.types
25from sqlalchemy.schema import MetaData
26
27
28logger = logging.getLogger('glance.registry.db.migrate_repo.schema')
29
30
31String = lambda length: sqlalchemy.types.String(
32 length=length, convert_unicode=False, assert_unicode=None,
33 unicode_error=None, _warn_on_bytestring=False)
34
35
36Text = lambda: sqlalchemy.types.Text(
37 length=None, convert_unicode=False, assert_unicode=None,
38 unicode_error=None, _warn_on_bytestring=False)
39
40
41Boolean = lambda: sqlalchemy.types.Boolean(create_constraint=True, name=None)
42
43
44DateTime = lambda: sqlalchemy.types.DateTime(timezone=False)
45
46
47Integer = lambda: sqlalchemy.types.Integer()
48
49
50def from_migration_import(module_name, fromlist):
51 """Import a migration file and return the module
52
53 :param module_name: name of migration module to import from
54 (ex: 001_add_images_table)
55 :param fromlist: list of items to import (ex: define_images_table)
56 :retval: module object
57
58 This bit of ugliness warrants an explanation:
59
60 As you're writing migrations, you'll frequently want to refer to
61 tables defined in previous migrations.
62
63 In the interest of not repeating yourself, you need a way of importing
64 that table into a 'future' migration.
65
66 However, tables are bound to metadata, so what you need to import is
67 really a table factory, which you can late-bind to your current
68 metadata object.
69
70 Moreover, migrations begin with a number (001...), which means they
71 aren't valid Python identifiers. This means we can't perform a
72 'normal' import on them (the Python lexer will 'splode). Instead, we
73 need to use __import__ magic to bring the table-factory into our
74 namespace.
75
76 Example Usage:
77
78 (define_images_table,) = from_migration_import(
79 '001_add_images_table', ['define_images_table'])
80
81 images = define_images_table(meta)
82
83 # Refer to images table
84
85 """
86 module_path = 'glance.registry.db.migrate_repo.versions.%s' % module_name
87 module = __import__(module_path, globals(), locals(), fromlist, -1)
88 return [getattr(module, item) for item in fromlist]
89
90
91def create_tables(tables):
92 for table in tables:
93 logger.info("creating table %(table)s" % locals())
94 table.create()
95
96
97def drop_tables(tables):
98 for table in tables:
99 logger.info("dropping table %(table)s" % locals())
100 table.drop()
0101
=== added directory 'glance/registry/db/migrate_repo/versions'
=== added file 'glance/registry/db/migrate_repo/versions/001_add_images_table.py'
--- glance/registry/db/migrate_repo/versions/001_add_images_table.py 1970-01-01 00:00:00 +0000
+++ glance/registry/db/migrate_repo/versions/001_add_images_table.py 2011-02-03 00:03:10 +0000
@@ -0,0 +1,55 @@
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 sqlalchemy.schema import (Column, MetaData, Table)
19
20from glance.registry.db.migrate_repo.schema import (
21 Boolean, DateTime, Integer, String, Text, create_tables, drop_tables)
22
23
24def define_images_table(meta):
25 images = Table('images', meta,
26 Column('id', Integer(), primary_key=True, nullable=False),
27 Column('name', String(255)),
28 Column('type', String(30)),
29 Column('size', Integer()),
30 Column('status', String(30), nullable=False),
31 Column('is_public', Boolean(), nullable=False, default=False,
32 index=True),
33 Column('location', Text()),
34 Column('created_at', DateTime(), nullable=False),
35 Column('updated_at', DateTime()),
36 Column('deleted_at', DateTime()),
37 Column('deleted', Boolean(), nullable=False, default=False,
38 index=True),
39 mysql_engine='InnoDB')
40
41 return images
42
43
44def upgrade(migrate_engine):
45 meta = MetaData()
46 meta.bind = migrate_engine
47 tables = [define_images_table(meta)]
48 create_tables(tables)
49
50
51def downgrade(migrate_engine):
52 meta = MetaData()
53 meta.bind = migrate_engine
54 tables = [define_images_table(meta)]
55 drop_tables(tables)
056
=== added file 'glance/registry/db/migrate_repo/versions/002_add_image_properties_table.py'
--- glance/registry/db/migrate_repo/versions/002_add_image_properties_table.py 1970-01-01 00:00:00 +0000
+++ glance/registry/db/migrate_repo/versions/002_add_image_properties_table.py 2011-02-03 00:03:10 +0000
@@ -0,0 +1,63 @@
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 sqlalchemy.schema import (
19 Column, ForeignKey, Index, MetaData, Table, UniqueConstraint)
20
21from glance.registry.db.migrate_repo.schema import (
22 Boolean, DateTime, Integer, String, Text, create_tables, drop_tables,
23 from_migration_import)
24
25
26def define_image_properties_table(meta):
27 (define_images_table,) = from_migration_import(
28 '001_add_images_table', ['define_images_table'])
29
30 images = define_images_table(meta)
31
32 image_properties = Table('image_properties', meta,
33 Column('id', Integer(), primary_key=True, nullable=False),
34 Column('image_id', Integer(), ForeignKey('images.id'), nullable=False,
35 index=True),
36 Column('key', String(255), nullable=False),
37 Column('value', Text()),
38 Column('created_at', DateTime(), nullable=False),
39 Column('updated_at', DateTime()),
40 Column('deleted_at', DateTime()),
41 Column('deleted', Boolean(), nullable=False, default=False,
42 index=True),
43 UniqueConstraint('image_id', 'key'),
44 mysql_engine='InnoDB')
45
46 Index('ix_image_properties_image_id_key', image_properties.c.image_id,
47 image_properties.c.key)
48
49 return image_properties
50
51
52def upgrade(migrate_engine):
53 meta = MetaData()
54 meta.bind = migrate_engine
55 tables = [define_image_properties_table(meta)]
56 create_tables(tables)
57
58
59def downgrade(migrate_engine):
60 meta = MetaData()
61 meta.bind = migrate_engine
62 tables = [define_image_properties_table(meta)]
63 drop_tables(tables)
064
=== added file 'glance/registry/db/migrate_repo/versions/__init__.py'
--- glance/registry/db/migrate_repo/versions/__init__.py 1970-01-01 00:00:00 +0000
+++ glance/registry/db/migrate_repo/versions/__init__.py 2011-02-03 00:03:10 +0000
@@ -0,0 +1,1 @@
1# template repository default versions module
02
=== added file 'glance/registry/db/migration.py'
--- glance/registry/db/migration.py 1970-01-01 00:00:00 +0000
+++ glance/registry/db/migration.py 2011-02-03 00:03:10 +0000
@@ -0,0 +1,117 @@
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
18import logging
19import os
20
21from migrate.versioning import api as versioning_api
22from migrate.versioning import exceptions as versioning_exceptions
23
24from glance.common import exception
25
26
27def db_version(options):
28 """Return the database's current migration number
29
30 :param options: options dict
31 :retval version number
32 """
33 repo_path = _find_migrate_repo()
34 sql_connection = options['sql_connection']
35 try:
36 return versioning_api.db_version(sql_connection, repo_path)
37 except versioning_exceptions.DatabaseNotControlledError, e:
38 msg = ("database '%(sql_connection)s' is not under migration control"
39 % locals())
40 raise exception.DatabaseMigrationError(msg)
41
42
43def upgrade(options, version=None):
44 """Upgrade the database's current migration level
45
46 :param options: options dict
47 :param version: version to upgrade (defaults to latest)
48 :retval version number
49 """
50 db_version(options) # Ensure db is under migration control
51 repo_path = _find_migrate_repo()
52 sql_connection = options['sql_connection']
53 version_str = version or 'latest'
54 logging.info("Upgrading %(sql_connection)s to version %(version_str)s" %
55 locals())
56 return versioning_api.upgrade(sql_connection, repo_path, version)
57
58
59def downgrade(options, version):
60 """Downgrade the database's current migration level
61
62 :param options: options dict
63 :param version: version to downgrade to
64 :retval version number
65 """
66 db_version(options) # Ensure db is under migration control
67 repo_path = _find_migrate_repo()
68 sql_connection = options['sql_connection']
69 logging.info("Downgrading %(sql_connection)s to version %(version)s" %
70 locals())
71 return versioning_api.downgrade(sql_connection, repo_path, version)
72
73
74def version_control(options):
75 """Place a database under migration control
76
77 :param options: options dict
78 """
79 sql_connection = options['sql_connection']
80 try:
81 _version_control(options)
82 except versioning_exceptions.DatabaseAlreadyControlledError, e:
83 msg = ("database '%(sql_connection)s' is already under migration "
84 "control" % locals())
85 raise exception.DatabaseMigrationError(msg)
86
87
88def _version_control(options):
89 """Place a database under migration control
90
91 :param options: options dict
92 """
93 repo_path = _find_migrate_repo()
94 sql_connection = options['sql_connection']
95 return versioning_api.version_control(sql_connection, repo_path)
96
97
98def db_sync(options, version=None):
99 """Place a database under migration control and perform an upgrade
100
101 :param options: options dict
102 :retval version number
103 """
104 try:
105 _version_control(options)
106 except versioning_exceptions.DatabaseAlreadyControlledError, e:
107 pass
108
109 upgrade(options, version=version)
110
111
112def _find_migrate_repo():
113 """Get the path for the migrate repository."""
114 path = os.path.join(os.path.abspath(os.path.dirname(__file__)),
115 'migrate_repo')
116 assert os.path.exists(path)
117 return path
0118
=== modified file 'glance/registry/db/models.py'
--- glance/registry/db/models.py 2011-01-31 19:39:39 +0000
+++ glance/registry/db/models.py 2011-02-03 00:03:10 +0000
@@ -42,10 +42,11 @@
42 __protected_attributes__ = set([42 __protected_attributes__ = set([
43 "created_at", "updated_at", "deleted_at", "deleted"])43 "created_at", "updated_at", "deleted_at", "deleted"])
4444
45 created_at = Column(DateTime, default=datetime.datetime.utcnow)45 created_at = Column(DateTime, default=datetime.datetime.utcnow,
46 nullable=False)
46 updated_at = Column(DateTime, onupdate=datetime.datetime.utcnow)47 updated_at = Column(DateTime, onupdate=datetime.datetime.utcnow)
47 deleted_at = Column(DateTime)48 deleted_at = Column(DateTime)
48 deleted = Column(Boolean, default=False)49 deleted = Column(Boolean, nullable=False, default=False)
4950
50 def save(self, session=None):51 def save(self, session=None):
51 """Save this object"""52 """Save this object"""
@@ -96,8 +97,8 @@
96 name = Column(String(255))97 name = Column(String(255))
97 type = Column(String(30))98 type = Column(String(30))
98 size = Column(Integer)99 size = Column(Integer)
99 status = Column(String(30))100 status = Column(String(30), nullable=False)
100 is_public = Column(Boolean, default=False)101 is_public = Column(Boolean, nullable=False, default=False)
101 location = Column(Text)102 location = Column(Text)
102103
103 @validates('type')104 @validates('type')
@@ -123,5 +124,7 @@
123 image_id = Column(Integer, ForeignKey('images.id'), nullable=False)124 image_id = Column(Integer, ForeignKey('images.id'), nullable=False)
124 image = relationship(Image, backref=backref('properties'))125 image = relationship(Image, backref=backref('properties'))
125126
126 key = Column(String(255), index=True)127 # FIXME(sirp): KEY is a reserved word in SQL, might be a good idea to
128 # rename this column
129 key = Column(String(255), index=True, nullable=False)
127 value = Column(Text)130 value = Column(Text)
128131
=== added file 'tests/unit/test_migrations.py'
--- tests/unit/test_migrations.py 1970-01-01 00:00:00 +0000
+++ tests/unit/test_migrations.py 2011-02-03 00:03:10 +0000
@@ -0,0 +1,48 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2010-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
18import os
19import unittest
20
21import glance.registry.db.migration as migration_api
22import glance.common.config as config
23
24class TestMigrations(unittest.TestCase):
25 """Test sqlalchemy-migrate migrations"""
26
27 def setUp(self):
28 self.db_path = "glance_test_migration.sqlite"
29 self.options = dict(sql_connection="sqlite:///%s" % self.db_path,
30 verbose=False)
31 config.setup_logging(self.options)
32
33 def tearDown(self):
34 if os.path.exists(self.db_path):
35 os.unlink(self.db_path)
36
37 def test_db_sync_downgrade_then_upgrade(self):
38 migration_api.db_sync(self.options)
39
40 latest = migration_api.db_version(self.options)
41
42 migration_api.downgrade(self.options, latest-1)
43 cur_version = migration_api.db_version(self.options)
44 self.assertEqual(cur_version, latest-1)
45
46 migration_api.upgrade(self.options, cur_version+1)
47 cur_version = migration_api.db_version(self.options)
48 self.assertEqual(cur_version, latest)
049
=== modified file 'tools/pip-requires'
--- tools/pip-requires 2011-01-28 21:54:34 +0000
+++ tools/pip-requires 2011-02-03 00:03:10 +0000
@@ -14,3 +14,4 @@
14argparse14argparse
15mox==0.5.015mox==0.5.0
16-f http://pymox.googlecode.com/files/mox-0.5.0.tar.gz16-f http://pymox.googlecode.com/files/mox-0.5.0.tar.gz
17sqlalchemy-migrate>=0.6

Subscribers

People subscribed via source and target branches