diff --git a/lower-constraints.txt b/lower-constraints.txt index c62d875c076a..2d919f5268d5 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -90,6 +90,7 @@ oslo.reports==1.18.0 oslo.rootwrap==5.8.0 oslo.serialization==2.18.0 oslo.service==1.33.0 +oslo.upgradecheck==0.1.1 oslo.utils==3.37.0 oslo.versionedobjects==1.33.3 oslo.vmware==2.17.0 diff --git a/nova/cmd/status.py b/nova/cmd/status.py index c5e4f43fbc16..f9c9cead6850 100644 --- a/nova/cmd/status.py +++ b/nova/cmd/status.py @@ -19,18 +19,15 @@ CLI interface for nova status commands. from __future__ import print_function import collections -# enum comes from the enum34 package if python < 3.4, else it's stdlib -import enum import functools import sys -import textwrap import traceback from keystoneauth1 import exceptions as ks_exc from oslo_config import cfg from oslo_serialization import jsonutils +from oslo_upgradecheck import upgradecheck import pkg_resources -import prettytable from sqlalchemy import func as sqlfunc from sqlalchemy import MetaData, Table, and_, select from sqlalchemy.sql import false @@ -59,47 +56,7 @@ PLACEMENT_DOCS_LINK = 'https://docs.openstack.org/nova/latest' \ MIN_PLACEMENT_MICROVERSION = "1.30" -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): +class UpgradeCommands(upgradecheck.UpgradeCommands): """Commands related to upgrades. The subcommands here must not rely on the nova object model since they @@ -152,7 +109,7 @@ class UpgradeCommands(object): 'cell0 and one for your first cell. Run command ' '\'nova-manage cell_v2 simple_cell_setup\' and then ' 'retry.') - return UpgradeCheckResult(UpgradeCheckCode.FAILURE, msg) + return upgradecheck.Result(upgradecheck.Code.FAILURE, msg) count = select([sqlfunc.count()]).select_from(cell_mappings).where( cell_mappings.c.uuid == @@ -161,7 +118,7 @@ class UpgradeCommands(object): msg = _('No cell0 mapping found. Run command ' '\'nova-manage cell_v2 simple_cell_setup\' and then ' 'retry.') - return UpgradeCheckResult(UpgradeCheckCode.FAILURE, msg) + return upgradecheck.Result(upgradecheck.Code.FAILURE, msg) host_mappings = Table('host_mappings', meta, autoload=True) count = select([sqlfunc.count()]).select_from(host_mappings).scalar() @@ -176,14 +133,14 @@ class UpgradeCommands(object): msg = _('No host mappings found but there are compute nodes. ' 'Run command \'nova-manage cell_v2 ' 'simple_cell_setup\' and then retry.') - return UpgradeCheckResult(UpgradeCheckCode.FAILURE, msg) + return upgradecheck.Result(upgradecheck.Code.FAILURE, msg) msg = _('No host mappings or compute nodes were found. Remember ' 'to run command \'nova-manage cell_v2 discover_hosts\' ' 'when new compute hosts are deployed.') - return UpgradeCheckResult(UpgradeCheckCode.SUCCESS, msg) + return upgradecheck.Result(upgradecheck.Code.SUCCESS, msg) - return UpgradeCheckResult(UpgradeCheckCode.SUCCESS) + return upgradecheck.Result(upgradecheck.Code.SUCCESS) @staticmethod def _placement_get(path): @@ -213,24 +170,24 @@ class UpgradeCommands(object): msg = (_('Placement API version %(needed)s needed, ' 'you have %(current)s.') % {'needed': needs_version, 'current': max_version}) - return UpgradeCheckResult(UpgradeCheckCode.FAILURE, msg) + return upgradecheck.Result(upgradecheck.Code.FAILURE, msg) except ks_exc.MissingAuthPlugin: msg = _('No credentials specified for placement API in nova.conf.') - return UpgradeCheckResult(UpgradeCheckCode.FAILURE, msg) + return upgradecheck.Result(upgradecheck.Code.FAILURE, msg) except ks_exc.Unauthorized: msg = _('Placement service credentials do not work.') - return UpgradeCheckResult(UpgradeCheckCode.FAILURE, msg) + return upgradecheck.Result(upgradecheck.Code.FAILURE, msg) except ks_exc.EndpointNotFound: msg = _('Placement API endpoint not found.') - return UpgradeCheckResult(UpgradeCheckCode.FAILURE, msg) + return upgradecheck.Result(upgradecheck.Code.FAILURE, msg) except ks_exc.DiscoveryFailure: msg = _('Discovery for placement API URI failed.') - return UpgradeCheckResult(UpgradeCheckCode.FAILURE, msg) + return upgradecheck.Result(upgradecheck.Code.FAILURE, msg) except ks_exc.NotFound: msg = _('Placement API does not seem to be running.') - return UpgradeCheckResult(UpgradeCheckCode.FAILURE, msg) + return upgradecheck.Result(upgradecheck.Code.FAILURE, msg) - return UpgradeCheckResult(UpgradeCheckCode.SUCCESS) + return upgradecheck.Result(upgradecheck.Code.SUCCESS) @staticmethod def _count_compute_resource_providers(): @@ -319,7 +276,7 @@ class UpgradeCommands(object): '%(placement_docs_link)s for more details.') % {'num_computes': num_computes, 'placement_docs_link': PLACEMENT_DOCS_LINK}) - return UpgradeCheckResult(UpgradeCheckCode.WARNING, msg) + return upgradecheck.Result(upgradecheck.Code.WARNING, msg) # There are no resource providers and no compute nodes so we # assume this is a fresh install and move on. We should return a @@ -330,7 +287,7 @@ class UpgradeCommands(object): 'report into the Placement service. See ' '%(placement_docs_link)s for more details.') % {'placement_docs_link': PLACEMENT_DOCS_LINK}) - return UpgradeCheckResult(UpgradeCheckCode.SUCCESS, msg) + return upgradecheck.Result(upgradecheck.Code.SUCCESS, msg) elif num_rps < num_computes: # There are fewer resource providers than compute nodes, so return @@ -349,10 +306,10 @@ class UpgradeCommands(object): {'num_resource_providers': num_rps, 'num_compute_nodes': num_computes, 'placement_docs_link': PLACEMENT_DOCS_LINK}) - return UpgradeCheckResult(UpgradeCheckCode.WARNING, msg) + return upgradecheck.Result(upgradecheck.Code.WARNING, msg) else: # We have RPs >= CNs which is what we want to see. - return UpgradeCheckResult(UpgradeCheckCode.SUCCESS) + return upgradecheck.Result(upgradecheck.Code.SUCCESS) @staticmethod def _is_ironic_instance_migrated(extras, inst): @@ -431,7 +388,7 @@ class UpgradeCommands(object): # on this. msg = (_('Unable to determine ironic flavor migration without ' 'cell mappings.')) - return UpgradeCheckResult(UpgradeCheckCode.WARNING, msg) + return upgradecheck.Result(upgradecheck.Code.WARNING, msg) if unmigrated_instance_count_by_cell: # There are unmigrated ironic instances, so we need to fail. @@ -442,11 +399,11 @@ class UpgradeCommands(object): cell_id, unmigrated_instance_count_by_cell[cell_id]) for cell_id in sorted(unmigrated_instance_count_by_cell.keys()))) - return UpgradeCheckResult(UpgradeCheckCode.FAILURE, msg) + return upgradecheck.Result(upgradecheck.Code.FAILURE, msg) # Either there were no ironic compute nodes or all instances for # those nodes are already migrated, so there is nothing to do. - return UpgradeCheckResult(UpgradeCheckCode.SUCCESS) + return upgradecheck.Result(upgradecheck.Code.SUCCESS) def _get_min_service_version(self, context, binary): meta = MetaData(bind=db_session.get_engine(context=context)) @@ -475,7 +432,7 @@ class UpgradeCommands(object): """ # If we're using cells v1 then we don't care about this. if CONF.cells.enable: - return UpgradeCheckResult(UpgradeCheckCode.SUCCESS) + return upgradecheck.Result(upgradecheck.Code.SUCCESS) meta = MetaData(bind=db_session.get_api_engine()) cell_mappings = Table('cell_mappings', meta, autoload=True) @@ -487,7 +444,7 @@ class UpgradeCommands(object): # on this. msg = (_('Unable to determine API service versions without ' 'cell mappings.')) - return UpgradeCheckResult(UpgradeCheckCode.WARNING, msg) + return upgradecheck.Result(upgradecheck.Code.WARNING, msg) ctxt = nova_context.get_admin_context() cells_with_old_api_services = [] @@ -513,8 +470,8 @@ class UpgradeCommands(object): "records. See " "https://bugs.launchpad.net/nova/+bug/1759316 for " "details.") % ', '.join(cells_with_old_api_services)) - return UpgradeCheckResult(UpgradeCheckCode.WARNING, msg) - return UpgradeCheckResult(UpgradeCheckCode.SUCCESS) + return upgradecheck.Result(upgradecheck.Code.WARNING, msg) + return upgradecheck.Result(upgradecheck.Code.SUCCESS) def _check_request_spec_migration(self): """Checks to make sure request spec migrations are complete. @@ -535,7 +492,7 @@ class UpgradeCommands(object): # on this. msg = (_('Unable to determine request spec migrations without ' 'cell mappings.')) - return UpgradeCheckResult(UpgradeCheckCode.WARNING, msg) + return upgradecheck.Result(upgradecheck.Code.WARNING, msg) request_specs = Table('request_specs', meta, autoload=True) ctxt = nova_context.get_admin_context() @@ -573,8 +530,8 @@ class UpgradeCommands(object): "'nova-manage db online_data_migrations' on each cell " "to create the missing request specs.") % ', '.join(incomplete_cells)) - return UpgradeCheckResult(UpgradeCheckCode.FAILURE, msg) - return UpgradeCheckResult(UpgradeCheckCode.SUCCESS) + return upgradecheck.Result(upgradecheck.Code.FAILURE, msg) + return upgradecheck.Result(upgradecheck.Code.SUCCESS) def _check_console_auths(self): """Checks for console usage and warns with info for rolling upgrade. @@ -591,7 +548,7 @@ class UpgradeCommands(object): # If the operator has already enabled the workaround, we don't need # to check anything. if CONF.cells.enable or CONF.workarounds.enable_consoleauth: - return UpgradeCheckResult(UpgradeCheckCode.SUCCESS) + return upgradecheck.Result(upgradecheck.Code.SUCCESS) # We need to check cell0 for nova-consoleauth service records because # it's possible a deployment could have services stored in the cell0 @@ -606,7 +563,7 @@ class UpgradeCommands(object): # return a warning. The cellsv2 check would have already failed # on this. msg = (_('Unable to check consoles without cell mappings.')) - return UpgradeCheckResult(UpgradeCheckCode.WARNING, msg) + return upgradecheck.Result(upgradecheck.Code.WARNING, msg) ctxt = nova_context.get_admin_context() # If we find a non-deleted, non-disabled nova-consoleauth service in @@ -652,15 +609,15 @@ class UpgradeCommands(object): "console proxy host if you are performing a " "rolling upgrade to enable consoles to " "function during a partial upgrade.") - return UpgradeCheckResult(UpgradeCheckCode.WARNING, + return upgradecheck.Result(upgradecheck.Code.WARNING, msg) - return UpgradeCheckResult(UpgradeCheckCode.SUCCESS) + return upgradecheck.Result(upgradecheck.Code.SUCCESS) - # The format of the check functions is to return an UpgradeCheckResult - # object with the appropriate UpgradeCheckCode and details set. If the + # The format of the check functions is to return an upgradecheck.Result + # object with the appropriate upgradecheck.Code and details set. If the # check hits warnings or failures then those should be stored in the - # returned UpgradeCheckResult's "details" attribute. The summary will + # returned upgradecheck.Result's "details" attribute. The summary will # be rolled up at the end of the check() function. These functions are # intended to be run in order and build on top of each other so order # matters. @@ -681,65 +638,6 @@ class UpgradeCommands(object): (_('Console Auths'), _check_console_auths), ) - 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 - - # 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 - CATEGORIES = { 'upgrade': UpgradeCommands, diff --git a/nova/tests/functional/test_nova_status.py b/nova/tests/functional/test_nova_status.py index 69889fb9f02a..0aa4337e7953 100644 --- a/nova/tests/functional/test_nova_status.py +++ b/nova/tests/functional/test_nova_status.py @@ -17,6 +17,7 @@ from six.moves import StringIO from oslo_config import cfg from oslo_config import fixture as config_fixture +from oslo_upgradecheck import upgradecheck from oslo_utils.fixture import uuidsentinel as uuids from oslo_utils import uuidutils import placement.db_api @@ -97,7 +98,7 @@ class TestUpgradeCheckResourceProviders(test.NoDBTestCase): self.useFixture(nova_fixtures.Database()) result = self.cmd._check_resource_providers() # this is assumed to be base install so it's OK but with details - self.assertEqual(status.UpgradeCheckCode.SUCCESS, result.code) + self.assertEqual(upgradecheck.Code.SUCCESS, result.code) self.assertIn('There are no compute resource providers in the ' 'Placement service nor are there compute nodes in the ' 'database', @@ -114,7 +115,7 @@ class TestUpgradeCheckResourceProviders(test.NoDBTestCase): # install and not a failure. result = self.cmd._check_resource_providers() # this is assumed to be base install so it's OK but with details - self.assertEqual(status.UpgradeCheckCode.SUCCESS, result.code) + self.assertEqual(upgradecheck.Code.SUCCESS, result.code) self.assertIn('There are no compute resource providers in the ' 'Placement service nor are there compute nodes in the ' 'database', @@ -141,7 +142,7 @@ class TestUpgradeCheckResourceProviders(test.NoDBTestCase): cpu_info='{"arch": "x86_64"}') cn.create() result = self.cmd._check_resource_providers() - self.assertEqual(status.UpgradeCheckCode.WARNING, result.code) + self.assertEqual(upgradecheck.Code.WARNING, result.code) self.assertIn('There are no compute resource providers in the ' 'Placement service but there are 1 compute nodes in the ' 'deployment.', result.details) @@ -192,7 +193,7 @@ class TestUpgradeCheckResourceProviders(test.NoDBTestCase): self._create_resource_provider(FAKE_IP_POOL_INVENTORY) result = self.cmd._check_resource_providers() - self.assertEqual(status.UpgradeCheckCode.WARNING, result.code) + self.assertEqual(upgradecheck.Code.WARNING, result.code) self.assertIn('There are no compute resource providers in the ' 'Placement service but there are 1 compute nodes in the ' 'deployment.', result.details) @@ -225,7 +226,7 @@ class TestUpgradeCheckResourceProviders(test.NoDBTestCase): self._create_resource_provider(FAKE_VCPU_INVENTORY) result = self.cmd._check_resource_providers() - self.assertEqual(status.UpgradeCheckCode.WARNING, result.code) + self.assertEqual(upgradecheck.Code.WARNING, result.code) self.assertIn('There are 1 compute resource providers and 2 compute ' 'nodes in the deployment.', result.details) @@ -285,5 +286,5 @@ class TestUpgradeCheckResourceProviders(test.NoDBTestCase): stub_count_compute_nodes) result = self.cmd._check_resource_providers() - self.assertEqual(status.UpgradeCheckCode.SUCCESS, result.code) + self.assertEqual(upgradecheck.Code.SUCCESS, result.code) self.assertIsNone(result.details) diff --git a/nova/tests/unit/cmd/test_status.py b/nova/tests/unit/cmd/test_status.py index c263a4fdf4dc..8d7410775e43 100644 --- a/nova/tests/unit/cmd/test_status.py +++ b/nova/tests/unit/cmd/test_status.py @@ -27,6 +27,7 @@ from six.moves import StringIO from keystoneauth1 import exceptions as ks_exc from keystoneauth1 import loading as keystone from keystoneauth1 import session +from oslo_upgradecheck import upgradecheck from oslo_utils.fixture import uuidsentinel as uuids from oslo_utils import uuidutils from requests import models @@ -124,7 +125,7 @@ class TestPlacementCheck(test.NoDBTestCase): """ auth.side_effect = ks_exc.MissingAuthPlugin() res = self.cmd._check_placement() - self.assertEqual(status.UpgradeCheckCode.FAILURE, res.code) + self.assertEqual(upgradecheck.Code.FAILURE, res.code) self.assertIn('No credentials specified', res.details) @mock.patch.object(keystone, "load_auth_from_conf_options") @@ -166,7 +167,7 @@ class TestPlacementCheck(test.NoDBTestCase): """ get.side_effect = ks_exc.Unauthorized() res = self.cmd._check_placement() - self.assertEqual(status.UpgradeCheckCode.FAILURE, res.code) + self.assertEqual(upgradecheck.Code.FAILURE, res.code) self.assertIn('Placement service credentials do not work', res.details) @mock.patch.object(status.UpgradeCommands, "_placement_get") @@ -179,7 +180,7 @@ class TestPlacementCheck(test.NoDBTestCase): """ get.side_effect = ks_exc.EndpointNotFound() res = self.cmd._check_placement() - self.assertEqual(status.UpgradeCheckCode.FAILURE, res.code) + self.assertEqual(upgradecheck.Code.FAILURE, res.code) self.assertIn('Placement API endpoint not found', res.details) @mock.patch.object(status.UpgradeCommands, "_placement_get") @@ -192,7 +193,7 @@ class TestPlacementCheck(test.NoDBTestCase): """ get.side_effect = ks_exc.DiscoveryFailure() res = self.cmd._check_placement() - self.assertEqual(status.UpgradeCheckCode.FAILURE, res.code) + self.assertEqual(upgradecheck.Code.FAILURE, res.code) self.assertIn('Discovery for placement API URI failed.', res.details) @mock.patch.object(status.UpgradeCommands, "_placement_get") @@ -204,7 +205,7 @@ class TestPlacementCheck(test.NoDBTestCase): """ get.side_effect = ks_exc.NotFound() res = self.cmd._check_placement() - self.assertEqual(status.UpgradeCheckCode.FAILURE, res.code) + self.assertEqual(upgradecheck.Code.FAILURE, res.code) self.assertIn('Placement API does not seem to be running', res.details) @mock.patch.object(status.UpgradeCommands, "_placement_get") @@ -219,7 +220,7 @@ class TestPlacementCheck(test.NoDBTestCase): ] } res = self.cmd._check_placement() - self.assertEqual(status.UpgradeCheckCode.SUCCESS, res.code) + self.assertEqual(upgradecheck.Code.SUCCESS, res.code) @mock.patch.object(status.UpgradeCommands, "_placement_get") def test_version_comparison_does_not_use_floats(self, get): @@ -239,7 +240,7 @@ class TestPlacementCheck(test.NoDBTestCase): ] } res = self.cmd._check_placement() - self.assertEqual(status.UpgradeCheckCode.SUCCESS, res.code) + self.assertEqual(upgradecheck.Code.SUCCESS, res.code) @mock.patch.object(status.UpgradeCommands, "_placement_get") def test_invalid_version(self, get): @@ -253,109 +254,11 @@ class TestPlacementCheck(test.NoDBTestCase): ] } res = self.cmd._check_placement() - self.assertEqual(status.UpgradeCheckCode.FAILURE, res.code) + self.assertEqual(upgradecheck.Code.FAILURE, res.code) self.assertIn('Placement API version %s needed, you have 0.9' % status.MIN_PLACEMENT_MICROVERSION, res.details) -class TestUpgradeCheckBasic(test.NoDBTestCase): - """Tests for the nova-status upgrade check command. - - The tests in this class should just test basic logic and use mock. Real - checks which require more elaborate fixtures or the database should be done - in separate test classes as they are more or less specific to a particular - release and may be removed in a later release after they are no longer - needed. - """ - - def setUp(self): - super(TestUpgradeCheckBasic, self).setUp() - self.output = StringIO() - self.useFixture(fixtures.MonkeyPatch('sys.stdout', self.output)) - self.cmd = status.UpgradeCommands() - - def test_check_success(self): - fake_checks = ( - ('good', mock.Mock(return_value=status.UpgradeCheckResult( - status.UpgradeCheckCode.SUCCESS - ))), - ) - with mock.patch.object(self.cmd, '_upgrade_checks', fake_checks): - self.assertEqual(status.UpgradeCheckCode.SUCCESS, self.cmd.check()) - expected = """\ -+-----------------------+ -| Upgrade Check Results | -+-----------------------+ -| Check: good | -| Result: Success | -| Details: None | -+-----------------------+ -""" - self.assertEqual(expected, self.output.getvalue()) - - def test_check_warning(self): - fake_checks = ( - ('good', mock.Mock(return_value=status.UpgradeCheckResult( - status.UpgradeCheckCode.SUCCESS - ))), - ('warn', mock.Mock(return_value=status.UpgradeCheckResult( - status.UpgradeCheckCode.WARNING, 'there might be a problem' - ))), - ) - with mock.patch.object(self.cmd, '_upgrade_checks', fake_checks): - self.assertEqual(status.UpgradeCheckCode.WARNING, self.cmd.check()) - expected = """\ -+-----------------------------------+ -| Upgrade Check Results | -+-----------------------------------+ -| Check: good | -| Result: Success | -| Details: None | -+-----------------------------------+ -| Check: warn | -| Result: Warning | -| Details: there might be a problem | -+-----------------------------------+ -""" - self.assertEqual(expected, self.output.getvalue()) - - def test_check_failure(self): - # make the error details over 60 characters so we test the wrapping - error_details = 'go back to bed' + '!' * 60 - fake_checks = ( - ('good', mock.Mock(return_value=status.UpgradeCheckResult( - status.UpgradeCheckCode.SUCCESS - ))), - ('warn', mock.Mock(return_value=status.UpgradeCheckResult( - status.UpgradeCheckCode.WARNING, 'there might be a problem' - ))), - ('fail', mock.Mock(return_value=status.UpgradeCheckResult( - status.UpgradeCheckCode.FAILURE, error_details - ))), - ) - with mock.patch.object(self.cmd, '_upgrade_checks', fake_checks): - self.assertEqual(status.UpgradeCheckCode.FAILURE, self.cmd.check()) - expected = """\ -+-----------------------------------------------------------------------+ -| Upgrade Check Results | -+-----------------------------------------------------------------------+ -| Check: good | -| Result: Success | -| Details: None | -+-----------------------------------------------------------------------+ -| Check: warn | -| Result: Warning | -| Details: there might be a problem | -+-----------------------------------------------------------------------+ -| Check: fail | -| Result: Failure | -| Details: go back to bed!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | -| !!!!!!!!!!!!!! | -+-----------------------------------------------------------------------+ -""" - self.assertEqual(expected, self.output.getvalue()) - - class TestUpgradeCheckCellsV2(test.NoDBTestCase): """Tests for the nova-status upgrade cells v2 specific check.""" @@ -374,7 +277,7 @@ class TestUpgradeCheckCellsV2(test.NoDBTestCase): """The cells v2 check should fail because there are no cell mappings. """ result = self.cmd._check_cellsv2() - self.assertEqual(status.UpgradeCheckCode.FAILURE, result.code) + self.assertEqual(upgradecheck.Code.FAILURE, result.code) self.assertIn('There needs to be at least two cell mappings', result.details) @@ -395,7 +298,7 @@ class TestUpgradeCheckCellsV2(test.NoDBTestCase): self._create_cell_mapping(uuid) result = self.cmd._check_cellsv2() - self.assertEqual(status.UpgradeCheckCode.FAILURE, result.code) + self.assertEqual(upgradecheck.Code.FAILURE, result.code) self.assertIn('No cell0 mapping found', result.details) def test_check_no_host_mappings_with_computes(self): @@ -418,7 +321,7 @@ class TestUpgradeCheckCellsV2(test.NoDBTestCase): cn.create() result = self.cmd._check_cellsv2() - self.assertEqual(status.UpgradeCheckCode.FAILURE, result.code) + self.assertEqual(upgradecheck.Code.FAILURE, result.code) self.assertIn('No host mappings found but there are compute nodes', result.details) @@ -429,7 +332,7 @@ class TestUpgradeCheckCellsV2(test.NoDBTestCase): self._setup_cells() result = self.cmd._check_cellsv2() - self.assertEqual(status.UpgradeCheckCode.SUCCESS, result.code) + self.assertEqual(upgradecheck.Code.SUCCESS, result.code) self.assertIn('No host mappings or compute nodes were found', result.details) @@ -446,7 +349,7 @@ class TestUpgradeCheckCellsV2(test.NoDBTestCase): hm.create() result = self.cmd._check_cellsv2() - self.assertEqual(status.UpgradeCheckCode.SUCCESS, result.code) + self.assertEqual(upgradecheck.Code.SUCCESS, result.code) self.assertIsNone(result.details) @@ -522,7 +425,7 @@ class TestUpgradeCheckIronicFlavorMigration(test.NoDBTestCase): warning. """ result = self.cmd._check_ironic_flavor_migration() - self.assertEqual(status.UpgradeCheckCode.WARNING, result.code) + self.assertEqual(upgradecheck.Code.WARNING, result.code) self.assertIn('Unable to determine ironic flavor migration without ' 'cell mappings', result.details) @@ -533,7 +436,7 @@ class TestUpgradeCheckIronicFlavorMigration(test.NoDBTestCase): """ self._setup_cells() result = self.cmd._check_ironic_flavor_migration() - self.assertEqual(status.UpgradeCheckCode.SUCCESS, result.code) + self.assertEqual(upgradecheck.Code.SUCCESS, result.code) def test_mixed_computes_deleted_ironic_instance(self): """Tests the scenario where we have a kvm compute node in one cell @@ -557,7 +460,7 @@ class TestUpgradeCheckIronicFlavorMigration(test.NoDBTestCase): ctxt, self.cell_mappings['cell1'], ironic_node, is_deleted=True) result = self.cmd._check_ironic_flavor_migration() - self.assertEqual(status.UpgradeCheckCode.SUCCESS, result.code) + self.assertEqual(upgradecheck.Code.SUCCESS, result.code) def test_unmigrated_ironic_instances(self): """Tests a scenario where we have two cells with only ironic compute @@ -590,7 +493,7 @@ class TestUpgradeCheckIronicFlavorMigration(test.NoDBTestCase): ctxt, cell, ironic_node, flavor_migrated=False) result = self.cmd._check_ironic_flavor_migration() - self.assertEqual(status.UpgradeCheckCode.FAILURE, result.code) + self.assertEqual(upgradecheck.Code.FAILURE, result.code) # Check the message - it should point out cell1 has one unmigrated # instance and cell2 has two unmigrated instances. unmigrated_instance_count_by_cell = { @@ -629,12 +532,12 @@ class TestUpgradeCheckAPIServiceVersion(test.NoDBTestCase): """ self.flags(enable=True, group='cells') result = self.cmd._check_api_service_version() - self.assertEqual(status.UpgradeCheckCode.SUCCESS, result.code) + self.assertEqual(upgradecheck.Code.SUCCESS, result.code) def test_check_no_cell_mappings_warning(self): """Warn when there are no cell mappings.""" result = self.cmd._check_api_service_version() - self.assertEqual(status.UpgradeCheckCode.WARNING, result.code) + self.assertEqual(upgradecheck.Code.WARNING, result.code) self.assertEqual('Unable to determine API service versions without ' 'cell mappings.', result.details) @@ -684,7 +587,7 @@ class TestUpgradeCheckAPIServiceVersion(test.NoDBTestCase): binary='nova-compute', version=14) result = self.cmd._check_api_service_version() - self.assertEqual(status.UpgradeCheckCode.WARNING, result.code) + self.assertEqual(upgradecheck.Code.WARNING, result.code) # The only cell in the message should be cell0. self.assertIn(cell0.uuid, result.details) self.assertNotIn(cell1.uuid, result.details) @@ -707,7 +610,7 @@ class TestUpgradeCheckAPIServiceVersion(test.NoDBTestCase): binary='nova-compute', version=15) result = self.cmd._check_api_service_version() - self.assertEqual(status.UpgradeCheckCode.SUCCESS, result.code) + self.assertEqual(upgradecheck.Code.SUCCESS, result.code) class TestUpgradeCheckRequestSpecMigration(test.NoDBTestCase): @@ -758,7 +661,7 @@ class TestUpgradeCheckRequestSpecMigration(test.NoDBTestCase): warning. """ result = self.cmd._check_request_spec_migration() - self.assertEqual(status.UpgradeCheckCode.WARNING, result.code) + self.assertEqual(upgradecheck.Code.WARNING, result.code) self.assertIn('Unable to determine request spec migrations without ' 'cell mappings.', result.details) @@ -779,7 +682,7 @@ class TestUpgradeCheckRequestSpecMigration(test.NoDBTestCase): ctxt, self.cell_mappings['cell2'], create_request_spec=True) result = self.cmd._check_request_spec_migration() - self.assertEqual(status.UpgradeCheckCode.SUCCESS, result.code) + self.assertEqual(upgradecheck.Code.SUCCESS, result.code) def test_unmigrated_request_spec_instances(self): """Tests the scenario that we have a migrated instance in cell1 and @@ -796,7 +699,7 @@ class TestUpgradeCheckRequestSpecMigration(test.NoDBTestCase): self._create_instance_in_cell(ctxt, self.cell_mappings['cell2']) result = self.cmd._check_request_spec_migration() - self.assertEqual(status.UpgradeCheckCode.FAILURE, result.code) + self.assertEqual(upgradecheck.Code.FAILURE, result.code) self.assertIn("The following cells have instances which do not have " "matching request_specs in the API database: %s Run " "'nova-manage db online_data_migrations' on each cell " @@ -856,7 +759,7 @@ class TestUpgradeCheckConsoles(test.NoDBTestCase): """ self.flags(enable=True, group='cells') result = self.cmd._check_console_auths() - self.assertEqual(status.UpgradeCheckCode.SUCCESS, result.code) + self.assertEqual(upgradecheck.Code.SUCCESS, result.code) def test_check_workaround_enabled(self): """This is a 'success' case since the console auths check is @@ -864,7 +767,7 @@ class TestUpgradeCheckConsoles(test.NoDBTestCase): """ self.flags(enable_consoleauth=True, group='workarounds') result = self.cmd._check_console_auths() - self.assertEqual(status.UpgradeCheckCode.SUCCESS, result.code) + self.assertEqual(upgradecheck.Code.SUCCESS, result.code) def test_deleted_disabled_consoleauth(self): """Tests that services other than nova-consoleauth and deleted/disabled @@ -887,7 +790,7 @@ class TestUpgradeCheckConsoles(test.NoDBTestCase): 'nova-consoleauth', disabled=True) result = self.cmd._check_console_auths() - self.assertEqual(status.UpgradeCheckCode.SUCCESS, result.code) + self.assertEqual(upgradecheck.Code.SUCCESS, result.code) def test_consoleauth_with_upgrade_not_started(self): """Tests the scenario where the deployment is using consoles but has no @@ -908,7 +811,7 @@ class TestUpgradeCheckConsoles(test.NoDBTestCase): 'nova-compute', version=30) result = self.cmd._check_console_auths() - self.assertEqual(status.UpgradeCheckCode.WARNING, result.code) + self.assertEqual(upgradecheck.Code.WARNING, result.code) def test_consoleauth_with_upgrade_complete(self): """Tests the scenario where the deployment is using consoles and has @@ -939,7 +842,7 @@ class TestUpgradeCheckConsoles(test.NoDBTestCase): 'nova-compute', version=30) result = self.cmd._check_console_auths() - self.assertEqual(status.UpgradeCheckCode.SUCCESS, result.code) + self.assertEqual(upgradecheck.Code.SUCCESS, result.code) def test_consoleauth_with_upgrade_partial(self): """Tests the scenario where the deployment is using consoles and has @@ -968,7 +871,7 @@ class TestUpgradeCheckConsoles(test.NoDBTestCase): result = self.cmd._check_console_auths() - self.assertEqual(status.UpgradeCheckCode.WARNING, result.code) + self.assertEqual(upgradecheck.Code.WARNING, result.code) self.assertIn("One or more cells were found which have nova-compute " "services older than Rocky. " "Please set the '[workarounds]enable_consoleauth' " diff --git a/requirements.txt b/requirements.txt index 9ce1b74e386a..422dd7b1ef54 100644 --- a/requirements.txt +++ b/requirements.txt @@ -42,6 +42,7 @@ oslo.context>=2.19.2 # Apache-2.0 oslo.log>=3.36.0 # Apache-2.0 oslo.reports>=1.18.0 # Apache-2.0 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0 +oslo.upgradecheck>=0.1.1 oslo.utils>=3.37.0 # Apache-2.0 oslo.db>=4.40.0 # Apache-2.0 oslo.rootwrap>=5.8.0 # Apache-2.0