174 lines
6.3 KiB
Python
Raw Normal View History

2011-01-12 16:57:04 -08:00
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# 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 os
from migrate import exceptions as versioning_exceptions
from migrate.versioning import api as versioning_api
from migrate.versioning.repository import Repository
from oslo_db.sqlalchemy import utils as db_utils
from oslo_log import log as logging
import sqlalchemy
from sqlalchemy.sql import null
Sync the latest DB code from oslo-incubator This sync contains the following commits from olso-incubator: 7959826 db: move all options into database group dda24eb Introduce mysql_sql_mode option, remove old warning 0b5af67 Introduce a method to set any MySQL session SQL mode 8dccc7b Handle ibm_db_sa DBDuplicateEntry integrity errors 0f24d82 Fix migration.db_version when no tables ac84a40 Update log translation domains c0d357b Add model_query() to db.sqlalchemy.utils module 84254fc Fix a small typo in api.py b8a676c Remove CONF.database.connection default value 86707cd Remove None for dict.get() 0545121 Fix duplicating of SQL queries in logs fcf517d Update oslo log messages with translation domains fa05b7c Restore the ability to load the DB backend lazily 630d395 Don't use cfg.CONF in oslo.db ce69e7f Don't store engine instances in oslo.db 35dc1d7 py3kcompat: remove b4f72b2 Don't raise MySQL 2013 'Lost connection' errors 271adfb Format sql in db.sqlalchemy.session docstring 0334cb3 Handle exception messages with six.text_type eff69ce Drop dependency on log from oslo db code 7a11a04 Automatic retry db.api query if db connection lost 11f2add Clean up docstring in db.sqlalchemy.session 1b5147f Only enable MySQL TRADITIONAL mode if we're running against MySQL 39e1c5c Move db tests base.py to common code 986dafd Fix parsing of UC errors in sqlite 3.7.16+/3.8.2+ bcf6d5e Small edits on help strings ae01e9a Transition from migrate to alembic Due to API changes in oslo.db code we have to change Nova code a bit in order to reuse oslo-incubator changes (oslo.db no longer stores SQLAlchemy Engine and sessionmaker instances globally and it's up to applications to create them). Change-Id: I4aaa7151f66e0292ff66c29330f93d78c6bf78a6
2014-02-24 18:17:02 +02:00
from nova.db.sqlalchemy import api as db_session
from nova import exception
from nova.i18n import _
INIT_VERSION = {}
INIT_VERSION['main'] = 215
INIT_VERSION['api'] = 0
_REPOSITORY = {}
LOG = logging.getLogger(__name__)
def get_engine(database='main'):
if database == 'main':
return db_session.get_engine()
if database == 'api':
return db_session.get_api_engine()
2011-01-12 16:57:04 -08:00
def db_sync(version=None, database='main'):
if version is not None:
try:
version = int(version)
except ValueError:
raise exception.NovaException(_("version should be an integer"))
current_version = db_version(database)
repository = _find_migrate_repo(database)
if version is None or version > current_version:
return versioning_api.upgrade(get_engine(database), repository,
version)
else:
return versioning_api.downgrade(get_engine(database), repository,
version)
2011-01-12 16:57:04 -08:00
def db_version(database='main'):
repository = _find_migrate_repo(database)
2011-01-12 16:57:04 -08:00
try:
return versioning_api.db_version(get_engine(database), repository)
except versioning_exceptions.DatabaseNotControlledError as exc:
2011-01-12 16:57:04 -08:00
meta = sqlalchemy.MetaData()
engine = get_engine(database)
2011-01-12 16:57:04 -08:00
meta.reflect(bind=engine)
tables = meta.tables
if len(tables) == 0:
db_version_control(INIT_VERSION[database], database)
return versioning_api.db_version(get_engine(database), repository)
else:
LOG.exception(exc)
# Some pre-Essex DB's may not be version controlled.
# Require them to upgrade using Essex first.
raise exception.NovaException(
_("Upgrade DB using Essex release first."))
2011-01-12 16:57:04 -08:00
def db_initial_version(database='main'):
return INIT_VERSION[database]
def _process_null_records(table, col_name, check_fkeys, delete=False):
"""Queries the database and optionally deletes the NULL records.
:param table: sqlalchemy.Table object.
:param col_name: The name of the column to check in the table.
:param check_fkeys: If True, check the table for foreign keys back to the
instances table and if not found, return.
:param delete: If true, run a delete operation on the table, else just
query for number of records that match the NULL column.
:returns: The number of records processed for the table and column.
"""
records = 0
if col_name in table.columns:
# NOTE(mriedem): filter out tables that don't have a foreign key back
# to the instances table since they could have stale data even if
# instances.uuid wasn't NULL.
if check_fkeys:
fkey_found = False
fkeys = table.c[col_name].foreign_keys or []
for fkey in fkeys:
if fkey.column.table.name == 'instances':
fkey_found = True
if not fkey_found:
return 0
if delete:
records = table.delete().where(
table.c[col_name] == null()
).execute().rowcount
else:
records = len(list(
table.select().where(table.c[col_name] == null()).execute()
))
return records
def db_null_instance_uuid_scan(delete=False):
"""Scans the database for NULL instance_uuid records.
:param delete: If true, delete NULL instance_uuid records found, else
just query to see if they exist for reporting.
:returns: dict of table name to number of hits for NULL instance_uuid rows.
"""
engine = get_engine()
meta = sqlalchemy.MetaData(bind=engine)
# NOTE(mriedem): We're going to load up all of the tables so we can find
# any with an instance_uuid column since those may be foreign keys back
# to the instances table and we want to cleanup those records first. We
# have to do this explicitly because the foreign keys in nova aren't
# defined with cascading deletes.
meta.reflect(engine)
# Keep track of all of the tables that had hits in the query.
processed = {}
for table in reversed(meta.sorted_tables):
# Ignore the fixed_ips table by design.
if table.name not in ('fixed_ips', 'shadow_fixed_ips'):
processed[table.name] = _process_null_records(
table, 'instance_uuid', check_fkeys=True, delete=delete)
# Now process the *instances tables.
for table_name in ('instances', 'shadow_instances'):
table = db_utils.get_table(engine, table_name)
processed[table.name] = _process_null_records(
table, 'uuid', check_fkeys=False, delete=delete)
return processed
def db_version_control(version=None, database='main'):
repository = _find_migrate_repo(database)
versioning_api.version_control(get_engine(database), repository, version)
2011-01-12 16:57:04 -08:00
return version
def _find_migrate_repo(database='main'):
2011-01-12 16:57:04 -08:00
"""Get the path for the migrate repository."""
global _REPOSITORY
rel_path = 'migrate_repo'
if database == 'api':
rel_path = os.path.join('api_migrations', 'migrate_repo')
2011-01-12 16:57:04 -08:00
path = os.path.join(os.path.abspath(os.path.dirname(__file__)),
rel_path)
2011-01-12 16:57:04 -08:00
assert os.path.exists(path)
if _REPOSITORY.get(database) is None:
_REPOSITORY[database] = Repository(path)
return _REPOSITORY[database]