placement/placement/cmd/manage.py
Chris Dent da1a588b8d Remove use of oslo.i18n and translation
Nothing is being translated in placement so, for the sake of being clean
and tidy, this patch removes the framework for translation and the
import of oslo.i18n.

Originally the hope was we could remove the dependency on oslo.i18n and
Babel entirely to save some disk space but many other oslo-related libs
depend on oslo.i18n so they are present anyway. [1]

[1] http://lists.openstack.org/pipermail/openstack-discuss/2019-March/004220.html

Change-Id: Ia965d028b6f7c9f04d1f29beb12f4862585631d5
2019-03-25 16:59:21 +00:00

233 lines
8.8 KiB
Python

# 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 collections
import functools
import prettytable
import six
import sys
from oslo_config import cfg
from oslo_log import log as logging
import pbr.version
from placement import conf
from placement import context
from placement.db.sqlalchemy import migration
from placement import db_api
from placement.objects import consumer as consumer_obj
from placement.objects import resource_provider as rp_obj
version_info = pbr.version.VersionInfo('openstack-placement')
LOG = logging.getLogger(__name__)
online_migrations = (
# These functions are called with a DB context and a count, which is the
# maximum batch size requested by the user. They must be idempotent.
# At most $count records should be migrated. The function must return a
# tuple of (found, done). The found value indicates how many
# unmigrated/candidate records existed in the database prior to the
# migration (either total, or up to the $count limit provided), and a
# nonzero found value may tell the user that there is still work to do.
# The done value indicates whether or not any records were actually
# migrated by the function. Thus if both (found, done) are nonzero, work
# was done and some work remains. If found is nonzero and done is zero,
# some records are not migratable, but all migrations that can complete
# have finished.
# Added in Stein
rp_obj.set_root_provider_ids,
# Added in Stein (copied from migration added to Nova in Rocky)
consumer_obj.create_incomplete_consumers,
)
class DbCommands(object):
def __init__(self, config):
self.config = config
def db_sync(self):
# Let exceptions raise for now, they will go to stderr.
migration.upgrade('head')
return 0
def db_version(self):
print(migration.version())
return 0
def db_stamp(self):
migration.stamp(self.config.command.version)
return 0
def db_online_data_migrations(self):
"""Processes online data migration.
:returns: 0 if no (further) updates are possible, 1 if the
``--max-count`` option was used and some updates were
completed successfully (even if others generated errors),
2 if some updates generated errors and no other migrations
were able to take effect in the last batch attempted, or
127 if invalid input is provided.
"""
max_count = self.config.command.max_count
if max_count is not None:
try:
max_count = int(max_count)
except ValueError:
max_count = -1
if max_count < 1:
print('Must supply a positive value for max_count')
return 127
limited = True
else:
max_count = 50
limited = False
print('Running batches of %i until complete' % max_count)
ran = None
migration_info = collections.OrderedDict()
exceptions = False
while ran is None or ran != 0:
migrations, exceptions = self._run_online_migration(max_count)
ran = 0
# For each batch of migration method results, build the cumulative
# set of results.
for name in migrations:
migration_info.setdefault(name, (0, 0))
migration_info[name] = (
migration_info[name][0] + migrations[name][0],
migration_info[name][1] + migrations[name][1],
)
ran += migrations[name][1]
if limited:
break
t = prettytable.PrettyTable(
['Migration', 'Total Found', 'Completed'])
for name, info in migration_info.items():
t.add_row([name, info[0], info[1]])
print(t)
# NOTE(tetsuro): In "limited" case, if some update has been "ran",
# exceptions are not considered fatal because work may still remain
# to be done, and that work may resolve dependencies for the failing
# migrations.
if exceptions and not (limited and ran):
print("Some migrations failed unexpectedly. Check log for "
"details.")
return 2
# TODO(mriedem): Potentially add another return code for
# "there are more migrations, but not completable right now"
return ran and 1 or 0
def _run_online_migration(self, max_count):
ctxt = context.RequestContext(config=self.config)
ran = 0
exceptions = False
migrations = collections.OrderedDict()
for migration_meth in online_migrations:
count = max_count - ran
try:
found, done = migration_meth(ctxt, count)
except Exception:
msg = ("Error attempting to run %(method)s" % dict(
method=migration_meth))
print(msg)
LOG.exception(msg)
exceptions = True
found = done = 0
name = migration_meth.__name__
if found:
print('%(total)i rows matched query %(meth)s, %(done)i '
'migrated' % {'total': found,
'meth': name,
'done': done})
# This is the per-migration method result for this batch, and
# _run_online_migration will either continue on to the next
# migration, or stop if up to this point we've processed max_count
# of records across all migration methods.
migrations[name] = found, done
ran += done
if ran >= max_count:
break
return migrations, exceptions
def add_db_command_parsers(subparsers, config):
command_object = DbCommands(config)
# If we set False here, we avoid having an exit during the parse
# args part of CONF processing and we can thus print out meaningful
# help text.
subparsers.required = False
parser = subparsers.add_parser('db')
# Avoid https://bugs.python.org/issue9351 with cpython < 2.7.9
if not six.PY2:
parser.set_defaults(func=parser.print_help)
db_parser = parser.add_subparsers(description='database commands')
help = 'Sync the datatabse to the current version.'
sync_parser = db_parser.add_parser('sync', help=help, description=help)
sync_parser.set_defaults(func=command_object.db_sync)
help = 'Report the current database version.'
version_parser = db_parser.add_parser(
'version', help=help, description=help)
version_parser.set_defaults(func=command_object.db_version)
help = 'Stamp the revision table with the given version.'
stamp_parser = db_parser.add_parser('stamp', help=help, description=help)
stamp_parser.add_argument('version', help='the version to stamp')
stamp_parser.set_defaults(func=command_object.db_stamp)
help = 'Run the online data migrations.'
online_dm_parser = db_parser.add_parser(
'online_data_migrations', help=help, description=help)
online_dm_parser.add_argument(
'--max-count', metavar='<number>',
help='Maximum number of objects to consider')
online_dm_parser.set_defaults(
func=command_object.db_online_data_migrations)
def setup_commands(config):
# This is a separate method because it facilitates unit testing.
# Use an additional SubCommandOpt and parser for each new sub command.
add_db_cmd_parsers = functools.partial(
add_db_command_parsers, config=config)
command_opt = cfg.SubCommandOpt(
'db', dest='command', title='Command', help='Available DB commands',
handler=add_db_cmd_parsers)
return [command_opt]
def main():
config = cfg.ConfigOpts()
conf.register_opts(config)
command_opts = setup_commands(config)
config.register_cli_opts(command_opts)
config(sys.argv[1:], project='placement',
version=version_info.version_string(),
default_config_files=None)
db_api.configure(config)
try:
func = config.command.func
return_code = func()
# If return_code ends up None we assume 0.
sys.exit(return_code or 0)
except cfg.NoSuchOptError:
config.print_help()
sys.exit(1)