206 lines
7.4 KiB
Python
206 lines
7.4 KiB
Python
# Copyright 2018 Red Hat Inc.
|
|
# Copyright 2016 IBM Corp.
|
|
#
|
|
# 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 sys
|
|
import textwrap
|
|
import traceback
|
|
|
|
import enum
|
|
from oslo_config import cfg
|
|
import prettytable
|
|
import six
|
|
|
|
from oslo_upgradecheck._i18n import _
|
|
|
|
|
|
class Code(enum.IntEnum):
|
|
"""Status codes for the upgrade check command"""
|
|
|
|
# All upgrade readiness checks passed successfully and there is
|
|
# nothing to do.
|
|
SUCCESS = 0
|
|
|
|
# At least one check encountered an issue and requires further
|
|
# investigation. This is considered a warning but the upgrade may be OK.
|
|
WARNING = 1
|
|
|
|
# There was an upgrade status check failure that needs to be
|
|
# investigated. This should be considered something that stops an upgrade.
|
|
FAILURE = 2
|
|
|
|
|
|
UPGRADE_CHECK_MSG_MAP = {
|
|
Code.SUCCESS: _('Success'),
|
|
Code.WARNING: _('Warning'),
|
|
Code.FAILURE: _('Failure'),
|
|
}
|
|
|
|
|
|
class Result(object):
|
|
"""Class used for 'nova-status upgrade check' results.
|
|
|
|
The 'code' attribute is a Code enum.
|
|
The 'details' attribute is a translated message generally only used for
|
|
checks that result in a warning or failure code. The details should provide
|
|
information on what issue was discovered along with any remediation.
|
|
"""
|
|
|
|
def __init__(self, code, details=None):
|
|
super(Result, self).__init__()
|
|
self.code = code
|
|
self.details = details
|
|
|
|
|
|
class UpgradeCommands(object):
|
|
"""Base class for upgrade checks
|
|
|
|
This class should be inherited by a class in each project that provides
|
|
the actual checks. Those checks should be added to the _upgrade_checks
|
|
class member so that they are run when the ``check`` method is called.
|
|
|
|
The subcommands here must not rely on the service object model since they
|
|
should be able to run on n-1 data. Any queries to the database should be
|
|
done through the sqlalchemy query language directly like the database
|
|
schema migrations.
|
|
"""
|
|
_upgrade_checks = ()
|
|
|
|
def _get_details(self, upgrade_check_result):
|
|
if upgrade_check_result.details is not None:
|
|
# wrap the text on the details to 60 characters
|
|
return '\n'.join(textwrap.wrap(upgrade_check_result.details, 60,
|
|
subsequent_indent=' '))
|
|
|
|
def check(self):
|
|
"""Performs checks to see if the deployment is ready for upgrade.
|
|
|
|
These checks are expected to be run BEFORE services are restarted with
|
|
new code.
|
|
|
|
:returns: Code
|
|
"""
|
|
return_code = Code.SUCCESS
|
|
# This is a list if 2-item tuples for the check name and it's results.
|
|
check_results = []
|
|
for name, func in self._upgrade_checks:
|
|
result = func(self)
|
|
# store the result of the check for the summary table
|
|
check_results.append((name, result))
|
|
# we want to end up with the highest level code of all checks
|
|
if result.code > return_code:
|
|
return_code = result.code
|
|
|
|
# TODO(bnemec): Consider using cliff for this so we can output in
|
|
# different formats like JSON or CSV.
|
|
# We're going to build a summary table that looks like:
|
|
# +----------------------------------------------------+
|
|
# | Upgrade Check Results |
|
|
# +----------------------------------------------------+
|
|
# | Check: Cells v2 |
|
|
# | Result: Success |
|
|
# | Details: None |
|
|
# +----------------------------------------------------+
|
|
# | Check: Placement API |
|
|
# | Result: Failure |
|
|
# | Details: There is no placement-api endpoint in the |
|
|
# | service catalog. |
|
|
# +----------------------------------------------------+
|
|
# NOTE(bnemec): We use six.text_type on the translated string to
|
|
# force immediate translation if lazy translation is in use.
|
|
# See lp1801761 for details.
|
|
t = prettytable.PrettyTable([six.text_type(_('Upgrade Check Results'))
|
|
],
|
|
hrules=prettytable.ALL)
|
|
t.align = 'l'
|
|
for name, result in check_results:
|
|
cell = (
|
|
_('Check: %(name)s\n'
|
|
'Result: %(result)s\n'
|
|
'Details: %(details)s') %
|
|
{
|
|
'name': name,
|
|
'result': UPGRADE_CHECK_MSG_MAP[result.code],
|
|
'details': self._get_details(result),
|
|
}
|
|
)
|
|
t.add_row([cell])
|
|
print(t)
|
|
|
|
return return_code
|
|
|
|
|
|
def register_cli_options(conf, upgrade_command):
|
|
"""Set up the command line options.
|
|
|
|
Adds a subcommand to support 'upgrade check' on the command line.
|
|
|
|
:param conf: An oslo.confg ConfigOpts instance on which to register the
|
|
upgrade check arguments.
|
|
:param upgrade_command: The UpgradeCommands instance.
|
|
"""
|
|
def add_parsers(subparsers):
|
|
upgrade_action = subparsers.add_parser('upgrade')
|
|
upgrade_action.add_argument('check')
|
|
upgrade_action.set_defaults(action_fn=upgrade_command.check)
|
|
|
|
opt = cfg.SubCommandOpt('command', handler=add_parsers)
|
|
conf.register_cli_opt(opt)
|
|
|
|
|
|
def run(conf):
|
|
"""Run the requested command.
|
|
|
|
:param conf: An oslo.confg ConfigOpts instance on which the upgrade
|
|
commands have been previously registered.
|
|
"""
|
|
try:
|
|
return conf.command.action_fn()
|
|
except Exception:
|
|
print(_('Error:\n%s') % traceback.format_exc())
|
|
# This is 255 so it's not confused with the upgrade check exit codes.
|
|
return 255
|
|
|
|
|
|
def main(conf, project, upgrade_command,
|
|
argv=sys.argv[1:],
|
|
default_config_files=None):
|
|
"""Simple implementation of main for upgrade checks
|
|
|
|
This can be used in upgrade check commands to provide the minimum
|
|
necessary parameter handling and logic.
|
|
|
|
:param conf: An oslo.confg ConfigOpts instance on which to register the
|
|
upgrade check arguments.
|
|
:param project: The name of the project, to be used as an argument
|
|
to the oslo_config.ConfigOpts instance to find
|
|
configuration files.
|
|
:param upgrade_command: The UpgradeCommands instance.
|
|
:param argv: The command line arguments to parse. Defaults to sys.argv[1:].
|
|
:param default_config_files: The configuration files to load. For projects
|
|
that use non-standard default locations for
|
|
the configuration files, use this to override
|
|
the search behavior in oslo.config.
|
|
|
|
"""
|
|
register_cli_options(conf, upgrade_command)
|
|
|
|
conf(
|
|
args=argv,
|
|
project=project,
|
|
default_config_files=default_config_files,
|
|
)
|
|
|
|
return run(conf)
|