diff --git a/MANIFEST.in b/MANIFEST.in index f6b341a0a2..6039e28da7 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -6,5 +6,6 @@ include tests/stubs.py include tests/test_data.py include tests/utils.py include run_tests.py +include glance/registry/db/migrate_repo/migrate.cfg graft doc graft tools diff --git a/bin/glance-manage b/bin/glance-manage new file mode 100755 index 0000000000..b5cdcc10d1 --- /dev/null +++ b/bin/glance-manage @@ -0,0 +1,126 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# Copyright 2011 OpenStack LLC. +# 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. + +""" +Glance Management Utility +""" + +# FIXME(sirp): When we have glance-admin we can consider merging this into it +# Perhaps for consistency with Nova, we would then rename glance-admin -> +# glance-manage (or the other way around) + +import optparse +import os +import sys + +ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +sys.path.append(ROOT_DIR) + +from glance import version as glance_version +from glance.common import config +from glance.common import exception +import glance.registry.db +import glance.registry.db.migration + + +def create_options(parser): + """ + Sets up the CLI and config-file options that may be + parsed and program commands. + + :param parser: The option parser + """ + parser.add_option('-v', '--verbose', default=False, dest="verbose", + action="store_true", + help="Print more verbose output") + parser.add_option('-d', '--debug', default=False, dest="debug", + action="store_true", + help="Print debugging output") + config.add_log_options('glance-manage', parser) + glance.registry.db.add_options(parser) + + +def do_db_version(options, args): + """Print database's current migration level""" + print glance.registry.db.migration.db_version(options) + + +def do_upgrade(options, args): + """Upgrade the database's migration level""" + try: + db_version = args[1] + except IndexError: + db_version = None + + glance.registry.db.migration.upgrade(options, version=db_version) + + +def do_downgrade(options, args): + """Downgrade the database's migration level""" + try: + db_version = args[1] + except IndexError: + raise exception.MissingArgumentError( + "downgrade requires a version argument") + + glance.registry.db.migration.downgrade(options, version=db_version) + + +def do_version_control(options, args): + """Place a database under migration control""" + glance.registry.db.migration.version_control(options) + + +def dispatch_cmd(options, args): + """Search for do_* cmd in this module and then run it""" + cmd = args[0] + try: + cmd_func = globals()['do_%s' % cmd] + except KeyError: + sys.exit("ERROR: unrecognized command '%s'" % cmd) + + try: + cmd_func(options, args) + except exception.Error, e: + sys.exit("ERROR: %s" % e) + + +def main(): + version = '%%prog %s' % glance_version.version_string() + usage = "%prog [options] " + oparser = optparse.OptionParser(usage, version=version) + create_options(oparser) + (options, args) = config.parse_options(oparser) + + try: + config.setup_logging(options) + except RuntimeError, e: + sys.exit("ERROR: %s" % e) + + if not args: + oparser.print_usage() + sys.exit(1) + + dispatch_cmd(options, args) + + +if __name__ == '__main__': + main() diff --git a/bin/glance-upload b/bin/glance-upload index de202580fa..9c0414ec05 100755 --- a/bin/glance-upload +++ b/bin/glance-upload @@ -33,6 +33,9 @@ Kernel-outside: """ + +# FIXME(sirp): This can be merged into glance-admin when that becomes +# available import argparse import pprint import sys diff --git a/glance/common/exception.py b/glance/common/exception.py index b95541df41..2ea1fd834f 100644 --- a/glance/common/exception.py +++ b/glance/common/exception.py @@ -75,6 +75,14 @@ class BadInputError(Exception): pass +class MissingArgumentError(Error): + pass + + +class DatabaseMigrationError(Error): + pass + + def wrap_exception(f): def _wrap(*args, **kw): try: diff --git a/glance/registry/db/migrate_repo/README b/glance/registry/db/migrate_repo/README new file mode 100644 index 0000000000..6218f8cac4 --- /dev/null +++ b/glance/registry/db/migrate_repo/README @@ -0,0 +1,4 @@ +This is a database migration repository. + +More information at +http://code.google.com/p/sqlalchemy-migrate/ diff --git a/glance/registry/db/migrate_repo/__init__.py b/glance/registry/db/migrate_repo/__init__.py new file mode 100644 index 0000000000..2f288d3cf1 --- /dev/null +++ b/glance/registry/db/migrate_repo/__init__.py @@ -0,0 +1 @@ +# template repository default module diff --git a/glance/registry/db/migrate_repo/manage.py b/glance/registry/db/migrate_repo/manage.py new file mode 100644 index 0000000000..2a928c84c9 --- /dev/null +++ b/glance/registry/db/migrate_repo/manage.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from migrate.versioning.shell import main +main(debug='False') diff --git a/glance/registry/db/migrate_repo/migrate.cfg b/glance/registry/db/migrate_repo/migrate.cfg new file mode 100644 index 0000000000..6761c45997 --- /dev/null +++ b/glance/registry/db/migrate_repo/migrate.cfg @@ -0,0 +1,20 @@ +[db_settings] +# Used to identify which repository this database is versioned under. +# You can use the name of your project. +repository_id=Glance Migrations + +# The name of the database table used to track the schema version. +# This name shouldn't already be used by your project. +# If this is changed once a database is under version control, you'll need to +# change the table name in each database too. +version_table=migrate_version + +# When committing a change script, Migrate will attempt to generate the +# sql for all supported databases; normally, if one of them fails - probably +# because you don't have that database installed - it is ignored and the +# commit continues, perhaps ending successfully. +# Databases in this list MUST compile successfully during a commit, or the +# entire commit will fail. List the databases your application will actually +# be using to ensure your updates to that database work properly. +# This must be a list; example: ['postgres','sqlite'] +required_dbs=[] diff --git a/glance/registry/db/migrate_repo/versions/__init__.py b/glance/registry/db/migrate_repo/versions/__init__.py new file mode 100644 index 0000000000..507b5ff6bd --- /dev/null +++ b/glance/registry/db/migrate_repo/versions/__init__.py @@ -0,0 +1 @@ +# template repository default versions module diff --git a/glance/registry/db/migration.py b/glance/registry/db/migration.py new file mode 100644 index 0000000000..63814a7ce5 --- /dev/null +++ b/glance/registry/db/migration.py @@ -0,0 +1,94 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# 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 logging +import os + +from migrate.versioning import api as versioning_api +from migrate.versioning import exceptions as versioning_exceptions + +from glance.common import exception + + +def db_version(options): + """Return the database's current migration number + + :param options: options dict + :retval version number + """ + repo_path = _find_migrate_repo() + sql_connection = options['sql_connection'] + try: + return versioning_api.db_version(sql_connection, repo_path) + except versioning_exceptions.DatabaseNotControlledError, e: + msg = ("database '%(sql_connection)s' is not under migration control" + % locals()) + raise exception.DatabaseMigrationError(msg) + + +def upgrade(options, version=None): + """Upgrade the database's current migration level + + :param options: options dict + :param version: version to upgrade (defaults to latest) + :retval version number + """ + db_version(options) # Ensure db is under migration control + repo_path = _find_migrate_repo() + sql_connection = options['sql_connection'] + version_str = version or 'latest' + logging.info("Upgrading %(sql_connection)s to version %(version_str)s" % + locals()) + return versioning_api.upgrade(sql_connection, repo_path, version) + + +def downgrade(options, version): + """Downgrade the database's current migration level + + :param options: options dict + :param version: version to downgrade to + :retval version number + """ + db_version(options) # Ensure db is under migration control + repo_path = _find_migrate_repo() + sql_connection = options['sql_connection'] + logging.info("Downgrading %(sql_connection)s to version %(version)s" % + locals()) + return versioning_api.downgrade(sql_connection, repo_path, version) + + +def version_control(options): + """Place a database under migration control + + :param options: options dict + """ + repo_path = _find_migrate_repo() + sql_connection = options['sql_connection'] + try: + versioning_api.version_control(sql_connection, repo_path) + except versioning_exceptions.DatabaseAlreadyControlledError, e: + msg = ("database '%(sql_connection)s' is already under migration " + "control" % locals()) + raise exception.DatabaseMigrationError(msg) + + +def _find_migrate_repo(): + """Get the path for the migrate repository.""" + path = os.path.join(os.path.abspath(os.path.dirname(__file__)), + 'migrate_repo') + assert os.path.exists(path) + return path diff --git a/tools/pip-requires b/tools/pip-requires index 9d779c27bf..57f61415f1 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -14,3 +14,4 @@ sphinx argparse mox==0.5.0 -f http://pymox.googlecode.com/files/mox-0.5.0.tar.gz +sqlalchemy-migrate>=0.6