From 302c40a857c1e9cf839eddcf4f891634f76c5520 Mon Sep 17 00:00:00 2001 From: Ben Nemec Date: Wed, 12 Sep 2018 18:20:50 +0000 Subject: [PATCH] Initial implementation --- oslo_upgradecheck/__main__.py | 38 ++++++++ oslo_upgradecheck/upgradecheck.py | 147 ++++++++++++++++++++++++++++++ requirements.txt | 3 + 3 files changed, 188 insertions(+) create mode 100644 oslo_upgradecheck/__main__.py create mode 100644 oslo_upgradecheck/upgradecheck.py diff --git a/oslo_upgradecheck/__main__.py b/oslo_upgradecheck/__main__.py new file mode 100644 index 0000000..80d9c82 --- /dev/null +++ b/oslo_upgradecheck/__main__.py @@ -0,0 +1,38 @@ +# Copyright 2018 Red Hat Inc. +# +# 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. + +"""Example CLI command for running upgrade checks""" + +from oslo_upgradecheck import upgradecheck + +class Checks(upgradecheck.UpgradeCommands): + def success(self): + return upgradecheck.UpgradeCheckResult(upgradecheck.UpgradeCheckCode.SUCCESS, + 'Always succeeds') + + def failure(self): + return upgradecheck.UpgradeCheckResult(upgradecheck.UpgradeCheckCode.FAILURE, + 'Always fails') + + _upgrade_checks = (('always succeeds', success), + ('always fails', failure), + ) + +def main(): + inst = Checks() + upgradecheck.main(inst.check) + + +if __name__ == '__main__': + main() diff --git a/oslo_upgradecheck/upgradecheck.py b/oslo_upgradecheck/upgradecheck.py new file mode 100644 index 0000000..416d191 --- /dev/null +++ b/oslo_upgradecheck/upgradecheck.py @@ -0,0 +1,147 @@ +# 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 functools +import sys +import textwrap + +import enum +from oslo_config import cfg +import prettytable + + +def _(s): + return s + + +class UpgradeCheckCode(enum.IntEnum): + """These are the status codes for the nova-status upgrade check command + and internal check commands. + """ + + # 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 = { + UpgradeCheckCode.SUCCESS: _('Success'), + UpgradeCheckCode.WARNING: _('Warning'), + UpgradeCheckCode.FAILURE: _('Failure'), +} + + +class UpgradeCheckResult(object): + """Class used for 'nova-status upgrade check' results. + The 'code' attribute is an UpgradeCheckCode 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(UpgradeCheckResult, self).__init__() + self.code = code + self.details = details + + + +class UpgradeCommands(object): + _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. These checks also require access to potentially all of the + Nova databases (nova, nova_api, nova_api_cell0) and external services + such as the placement API service. + :returns: UpgradeCheckCode + """ + return_code = UpgradeCheckCode.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. | + # +----------------------------------------------------+ + t = prettytable.PrettyTable([_('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 _add_parsers(subparsers, check_callback): + upgrade_action = subparsers.add_parser('upgrade') + upgrade_action.add_argument('check') + upgrade_action.set_defaults(action_fn=check_callback) + + +def main(check_callback): + add_parsers = functools.partial(_add_parsers, + check_callback=check_callback) + opt = cfg.SubCommandOpt('category', handler=add_parsers) + conf = cfg.ConfigOpts() + conf.register_cli_opt(opt) + conf(sys.argv[1:]) + + conf.category.action_fn() diff --git a/requirements.txt b/requirements.txt index c299e3b..016756a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,6 @@ # process, which may cause wedges in the gate later. Babel>=1.3 +oslo.config +enum34 +prettytable