Migrate upgrade checks to oslo.upgradecheck

The common upgrade check code has been moved to oslo.upgradecheck.
This change switches the Nova upgrade checks to use the common code
and removes the tests for functionality that is now the responsibility
of the library.

Change-Id: I0dc2044286dbe78314c650a92c4654f7f50642d2
This commit is contained in:
Ben Nemec 2018-09-18 21:28:22 +00:00
parent f6996903d2
commit c9436c3846
5 changed files with 75 additions and 271 deletions

View File

@ -90,6 +90,7 @@ oslo.reports==1.18.0
oslo.rootwrap==5.8.0 oslo.rootwrap==5.8.0
oslo.serialization==2.18.0 oslo.serialization==2.18.0
oslo.service==1.33.0 oslo.service==1.33.0
oslo.upgradecheck==0.1.1
oslo.utils==3.37.0 oslo.utils==3.37.0
oslo.versionedobjects==1.33.3 oslo.versionedobjects==1.33.3
oslo.vmware==2.17.0 oslo.vmware==2.17.0

View File

@ -19,18 +19,15 @@ CLI interface for nova status commands.
from __future__ import print_function from __future__ import print_function
import collections import collections
# enum comes from the enum34 package if python < 3.4, else it's stdlib
import enum
import functools import functools
import sys import sys
import textwrap
import traceback import traceback
from keystoneauth1 import exceptions as ks_exc from keystoneauth1 import exceptions as ks_exc
from oslo_config import cfg from oslo_config import cfg
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
from oslo_upgradecheck import upgradecheck
import pkg_resources import pkg_resources
import prettytable
from sqlalchemy import func as sqlfunc from sqlalchemy import func as sqlfunc
from sqlalchemy import MetaData, Table, and_, select from sqlalchemy import MetaData, Table, and_, select
from sqlalchemy.sql import false from sqlalchemy.sql import false
@ -59,47 +56,7 @@ PLACEMENT_DOCS_LINK = 'https://docs.openstack.org/nova/latest' \
MIN_PLACEMENT_MICROVERSION = "1.30" MIN_PLACEMENT_MICROVERSION = "1.30"
class UpgradeCheckCode(enum.IntEnum): class UpgradeCommands(upgradecheck.UpgradeCommands):
"""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):
"""Commands related to upgrades. """Commands related to upgrades.
The subcommands here must not rely on the nova object model since they 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 ' 'cell0 and one for your first cell. Run command '
'\'nova-manage cell_v2 simple_cell_setup\' and then ' '\'nova-manage cell_v2 simple_cell_setup\' and then '
'retry.') 'retry.')
return UpgradeCheckResult(UpgradeCheckCode.FAILURE, msg) return upgradecheck.Result(upgradecheck.Code.FAILURE, msg)
count = select([sqlfunc.count()]).select_from(cell_mappings).where( count = select([sqlfunc.count()]).select_from(cell_mappings).where(
cell_mappings.c.uuid == cell_mappings.c.uuid ==
@ -161,7 +118,7 @@ class UpgradeCommands(object):
msg = _('No cell0 mapping found. Run command ' msg = _('No cell0 mapping found. Run command '
'\'nova-manage cell_v2 simple_cell_setup\' and then ' '\'nova-manage cell_v2 simple_cell_setup\' and then '
'retry.') 'retry.')
return UpgradeCheckResult(UpgradeCheckCode.FAILURE, msg) return upgradecheck.Result(upgradecheck.Code.FAILURE, msg)
host_mappings = Table('host_mappings', meta, autoload=True) host_mappings = Table('host_mappings', meta, autoload=True)
count = select([sqlfunc.count()]).select_from(host_mappings).scalar() 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. ' msg = _('No host mappings found but there are compute nodes. '
'Run command \'nova-manage cell_v2 ' 'Run command \'nova-manage cell_v2 '
'simple_cell_setup\' and then retry.') '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 ' msg = _('No host mappings or compute nodes were found. Remember '
'to run command \'nova-manage cell_v2 discover_hosts\' ' 'to run command \'nova-manage cell_v2 discover_hosts\' '
'when new compute hosts are deployed.') '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 @staticmethod
def _placement_get(path): def _placement_get(path):
@ -213,24 +170,24 @@ class UpgradeCommands(object):
msg = (_('Placement API version %(needed)s needed, ' msg = (_('Placement API version %(needed)s needed, '
'you have %(current)s.') % 'you have %(current)s.') %
{'needed': needs_version, 'current': max_version}) {'needed': needs_version, 'current': max_version})
return UpgradeCheckResult(UpgradeCheckCode.FAILURE, msg) return upgradecheck.Result(upgradecheck.Code.FAILURE, msg)
except ks_exc.MissingAuthPlugin: except ks_exc.MissingAuthPlugin:
msg = _('No credentials specified for placement API in nova.conf.') 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: except ks_exc.Unauthorized:
msg = _('Placement service credentials do not work.') msg = _('Placement service credentials do not work.')
return UpgradeCheckResult(UpgradeCheckCode.FAILURE, msg) return upgradecheck.Result(upgradecheck.Code.FAILURE, msg)
except ks_exc.EndpointNotFound: except ks_exc.EndpointNotFound:
msg = _('Placement API endpoint not found.') msg = _('Placement API endpoint not found.')
return UpgradeCheckResult(UpgradeCheckCode.FAILURE, msg) return upgradecheck.Result(upgradecheck.Code.FAILURE, msg)
except ks_exc.DiscoveryFailure: except ks_exc.DiscoveryFailure:
msg = _('Discovery for placement API URI failed.') msg = _('Discovery for placement API URI failed.')
return UpgradeCheckResult(UpgradeCheckCode.FAILURE, msg) return upgradecheck.Result(upgradecheck.Code.FAILURE, msg)
except ks_exc.NotFound: except ks_exc.NotFound:
msg = _('Placement API does not seem to be running.') 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 @staticmethod
def _count_compute_resource_providers(): def _count_compute_resource_providers():
@ -319,7 +276,7 @@ class UpgradeCommands(object):
'%(placement_docs_link)s for more details.') % '%(placement_docs_link)s for more details.') %
{'num_computes': num_computes, {'num_computes': num_computes,
'placement_docs_link': PLACEMENT_DOCS_LINK}) '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 # There are no resource providers and no compute nodes so we
# assume this is a fresh install and move on. We should return a # 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 ' 'report into the Placement service. See '
'%(placement_docs_link)s for more details.') % '%(placement_docs_link)s for more details.') %
{'placement_docs_link': PLACEMENT_DOCS_LINK}) {'placement_docs_link': PLACEMENT_DOCS_LINK})
return UpgradeCheckResult(UpgradeCheckCode.SUCCESS, msg) return upgradecheck.Result(upgradecheck.Code.SUCCESS, msg)
elif num_rps < num_computes: elif num_rps < num_computes:
# There are fewer resource providers than compute nodes, so return # There are fewer resource providers than compute nodes, so return
@ -349,10 +306,10 @@ class UpgradeCommands(object):
{'num_resource_providers': num_rps, {'num_resource_providers': num_rps,
'num_compute_nodes': num_computes, 'num_compute_nodes': num_computes,
'placement_docs_link': PLACEMENT_DOCS_LINK}) 'placement_docs_link': PLACEMENT_DOCS_LINK})
return UpgradeCheckResult(UpgradeCheckCode.WARNING, msg) return upgradecheck.Result(upgradecheck.Code.WARNING, msg)
else: else:
# We have RPs >= CNs which is what we want to see. # We have RPs >= CNs which is what we want to see.
return UpgradeCheckResult(UpgradeCheckCode.SUCCESS) return upgradecheck.Result(upgradecheck.Code.SUCCESS)
@staticmethod @staticmethod
def _is_ironic_instance_migrated(extras, inst): def _is_ironic_instance_migrated(extras, inst):
@ -431,7 +388,7 @@ class UpgradeCommands(object):
# on this. # on this.
msg = (_('Unable to determine ironic flavor migration without ' msg = (_('Unable to determine ironic flavor migration without '
'cell mappings.')) 'cell mappings.'))
return UpgradeCheckResult(UpgradeCheckCode.WARNING, msg) return upgradecheck.Result(upgradecheck.Code.WARNING, msg)
if unmigrated_instance_count_by_cell: if unmigrated_instance_count_by_cell:
# There are unmigrated ironic instances, so we need to fail. # 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]) cell_id, unmigrated_instance_count_by_cell[cell_id])
for cell_id in for cell_id in
sorted(unmigrated_instance_count_by_cell.keys()))) 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 # Either there were no ironic compute nodes or all instances for
# those nodes are already migrated, so there is nothing to do. # 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): def _get_min_service_version(self, context, binary):
meta = MetaData(bind=db_session.get_engine(context=context)) 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 we're using cells v1 then we don't care about this.
if CONF.cells.enable: if CONF.cells.enable:
return UpgradeCheckResult(UpgradeCheckCode.SUCCESS) return upgradecheck.Result(upgradecheck.Code.SUCCESS)
meta = MetaData(bind=db_session.get_api_engine()) meta = MetaData(bind=db_session.get_api_engine())
cell_mappings = Table('cell_mappings', meta, autoload=True) cell_mappings = Table('cell_mappings', meta, autoload=True)
@ -487,7 +444,7 @@ class UpgradeCommands(object):
# on this. # on this.
msg = (_('Unable to determine API service versions without ' msg = (_('Unable to determine API service versions without '
'cell mappings.')) 'cell mappings.'))
return UpgradeCheckResult(UpgradeCheckCode.WARNING, msg) return upgradecheck.Result(upgradecheck.Code.WARNING, msg)
ctxt = nova_context.get_admin_context() ctxt = nova_context.get_admin_context()
cells_with_old_api_services = [] cells_with_old_api_services = []
@ -513,8 +470,8 @@ class UpgradeCommands(object):
"records. See " "records. See "
"https://bugs.launchpad.net/nova/+bug/1759316 for " "https://bugs.launchpad.net/nova/+bug/1759316 for "
"details.") % ', '.join(cells_with_old_api_services)) "details.") % ', '.join(cells_with_old_api_services))
return UpgradeCheckResult(UpgradeCheckCode.WARNING, msg) return upgradecheck.Result(upgradecheck.Code.WARNING, msg)
return UpgradeCheckResult(UpgradeCheckCode.SUCCESS) return upgradecheck.Result(upgradecheck.Code.SUCCESS)
def _check_request_spec_migration(self): def _check_request_spec_migration(self):
"""Checks to make sure request spec migrations are complete. """Checks to make sure request spec migrations are complete.
@ -535,7 +492,7 @@ class UpgradeCommands(object):
# on this. # on this.
msg = (_('Unable to determine request spec migrations without ' msg = (_('Unable to determine request spec migrations without '
'cell mappings.')) 'cell mappings.'))
return UpgradeCheckResult(UpgradeCheckCode.WARNING, msg) return upgradecheck.Result(upgradecheck.Code.WARNING, msg)
request_specs = Table('request_specs', meta, autoload=True) request_specs = Table('request_specs', meta, autoload=True)
ctxt = nova_context.get_admin_context() ctxt = nova_context.get_admin_context()
@ -573,8 +530,8 @@ class UpgradeCommands(object):
"'nova-manage db online_data_migrations' on each cell " "'nova-manage db online_data_migrations' on each cell "
"to create the missing request specs.") % "to create the missing request specs.") %
', '.join(incomplete_cells)) ', '.join(incomplete_cells))
return UpgradeCheckResult(UpgradeCheckCode.FAILURE, msg) return upgradecheck.Result(upgradecheck.Code.FAILURE, msg)
return UpgradeCheckResult(UpgradeCheckCode.SUCCESS) return upgradecheck.Result(upgradecheck.Code.SUCCESS)
def _check_console_auths(self): def _check_console_auths(self):
"""Checks for console usage and warns with info for rolling upgrade. """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 # If the operator has already enabled the workaround, we don't need
# to check anything. # to check anything.
if CONF.cells.enable or CONF.workarounds.enable_consoleauth: 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 # We need to check cell0 for nova-consoleauth service records because
# it's possible a deployment could have services stored in the cell0 # 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 # return a warning. The cellsv2 check would have already failed
# on this. # on this.
msg = (_('Unable to check consoles without cell mappings.')) 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() ctxt = nova_context.get_admin_context()
# If we find a non-deleted, non-disabled nova-consoleauth service in # 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 " "console proxy host if you are performing a "
"rolling upgrade to enable consoles to " "rolling upgrade to enable consoles to "
"function during a partial upgrade.") "function during a partial upgrade.")
return UpgradeCheckResult(UpgradeCheckCode.WARNING, return upgradecheck.Result(upgradecheck.Code.WARNING,
msg) msg)
return UpgradeCheckResult(UpgradeCheckCode.SUCCESS) return upgradecheck.Result(upgradecheck.Code.SUCCESS)
# The format of the check functions is to return an UpgradeCheckResult # The format of the check functions is to return an upgradecheck.Result
# object with the appropriate UpgradeCheckCode and details set. If the # object with the appropriate upgradecheck.Code and details set. If the
# check hits warnings or failures then those should be stored in 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 # 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 # intended to be run in order and build on top of each other so order
# matters. # matters.
@ -681,65 +638,6 @@ class UpgradeCommands(object):
(_('Console Auths'), _check_console_auths), (_('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 = { CATEGORIES = {
'upgrade': UpgradeCommands, 'upgrade': UpgradeCommands,

View File

@ -17,6 +17,7 @@ from six.moves import StringIO
from oslo_config import cfg from oslo_config import cfg
from oslo_config import fixture as config_fixture from oslo_config import fixture as config_fixture
from oslo_upgradecheck import upgradecheck
from oslo_utils.fixture import uuidsentinel as uuids from oslo_utils.fixture import uuidsentinel as uuids
from oslo_utils import uuidutils from oslo_utils import uuidutils
import placement.db_api import placement.db_api
@ -97,7 +98,7 @@ class TestUpgradeCheckResourceProviders(test.NoDBTestCase):
self.useFixture(nova_fixtures.Database()) self.useFixture(nova_fixtures.Database())
result = self.cmd._check_resource_providers() result = self.cmd._check_resource_providers()
# this is assumed to be base install so it's OK but with details # 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 ' self.assertIn('There are no compute resource providers in the '
'Placement service nor are there compute nodes in the ' 'Placement service nor are there compute nodes in the '
'database', 'database',
@ -114,7 +115,7 @@ class TestUpgradeCheckResourceProviders(test.NoDBTestCase):
# install and not a failure. # install and not a failure.
result = self.cmd._check_resource_providers() result = self.cmd._check_resource_providers()
# this is assumed to be base install so it's OK but with details # 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 ' self.assertIn('There are no compute resource providers in the '
'Placement service nor are there compute nodes in the ' 'Placement service nor are there compute nodes in the '
'database', 'database',
@ -141,7 +142,7 @@ class TestUpgradeCheckResourceProviders(test.NoDBTestCase):
cpu_info='{"arch": "x86_64"}') cpu_info='{"arch": "x86_64"}')
cn.create() cn.create()
result = self.cmd._check_resource_providers() 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 ' self.assertIn('There are no compute resource providers in the '
'Placement service but there are 1 compute nodes in the ' 'Placement service but there are 1 compute nodes in the '
'deployment.', result.details) 'deployment.', result.details)
@ -192,7 +193,7 @@ class TestUpgradeCheckResourceProviders(test.NoDBTestCase):
self._create_resource_provider(FAKE_IP_POOL_INVENTORY) self._create_resource_provider(FAKE_IP_POOL_INVENTORY)
result = self.cmd._check_resource_providers() 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 ' self.assertIn('There are no compute resource providers in the '
'Placement service but there are 1 compute nodes in the ' 'Placement service but there are 1 compute nodes in the '
'deployment.', result.details) 'deployment.', result.details)
@ -225,7 +226,7 @@ class TestUpgradeCheckResourceProviders(test.NoDBTestCase):
self._create_resource_provider(FAKE_VCPU_INVENTORY) self._create_resource_provider(FAKE_VCPU_INVENTORY)
result = self.cmd._check_resource_providers() 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 ' self.assertIn('There are 1 compute resource providers and 2 compute '
'nodes in the deployment.', result.details) 'nodes in the deployment.', result.details)
@ -285,5 +286,5 @@ class TestUpgradeCheckResourceProviders(test.NoDBTestCase):
stub_count_compute_nodes) stub_count_compute_nodes)
result = self.cmd._check_resource_providers() result = self.cmd._check_resource_providers()
self.assertEqual(status.UpgradeCheckCode.SUCCESS, result.code) self.assertEqual(upgradecheck.Code.SUCCESS, result.code)
self.assertIsNone(result.details) self.assertIsNone(result.details)

View File

@ -27,6 +27,7 @@ from six.moves import StringIO
from keystoneauth1 import exceptions as ks_exc from keystoneauth1 import exceptions as ks_exc
from keystoneauth1 import loading as keystone from keystoneauth1 import loading as keystone
from keystoneauth1 import session from keystoneauth1 import session
from oslo_upgradecheck import upgradecheck
from oslo_utils.fixture import uuidsentinel as uuids from oslo_utils.fixture import uuidsentinel as uuids
from oslo_utils import uuidutils from oslo_utils import uuidutils
from requests import models from requests import models
@ -124,7 +125,7 @@ class TestPlacementCheck(test.NoDBTestCase):
""" """
auth.side_effect = ks_exc.MissingAuthPlugin() auth.side_effect = ks_exc.MissingAuthPlugin()
res = self.cmd._check_placement() 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) self.assertIn('No credentials specified', res.details)
@mock.patch.object(keystone, "load_auth_from_conf_options") @mock.patch.object(keystone, "load_auth_from_conf_options")
@ -166,7 +167,7 @@ class TestPlacementCheck(test.NoDBTestCase):
""" """
get.side_effect = ks_exc.Unauthorized() get.side_effect = ks_exc.Unauthorized()
res = self.cmd._check_placement() 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) self.assertIn('Placement service credentials do not work', res.details)
@mock.patch.object(status.UpgradeCommands, "_placement_get") @mock.patch.object(status.UpgradeCommands, "_placement_get")
@ -179,7 +180,7 @@ class TestPlacementCheck(test.NoDBTestCase):
""" """
get.side_effect = ks_exc.EndpointNotFound() get.side_effect = ks_exc.EndpointNotFound()
res = self.cmd._check_placement() 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) self.assertIn('Placement API endpoint not found', res.details)
@mock.patch.object(status.UpgradeCommands, "_placement_get") @mock.patch.object(status.UpgradeCommands, "_placement_get")
@ -192,7 +193,7 @@ class TestPlacementCheck(test.NoDBTestCase):
""" """
get.side_effect = ks_exc.DiscoveryFailure() get.side_effect = ks_exc.DiscoveryFailure()
res = self.cmd._check_placement() 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) self.assertIn('Discovery for placement API URI failed.', res.details)
@mock.patch.object(status.UpgradeCommands, "_placement_get") @mock.patch.object(status.UpgradeCommands, "_placement_get")
@ -204,7 +205,7 @@ class TestPlacementCheck(test.NoDBTestCase):
""" """
get.side_effect = ks_exc.NotFound() get.side_effect = ks_exc.NotFound()
res = self.cmd._check_placement() 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) self.assertIn('Placement API does not seem to be running', res.details)
@mock.patch.object(status.UpgradeCommands, "_placement_get") @mock.patch.object(status.UpgradeCommands, "_placement_get")
@ -219,7 +220,7 @@ class TestPlacementCheck(test.NoDBTestCase):
] ]
} }
res = self.cmd._check_placement() 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") @mock.patch.object(status.UpgradeCommands, "_placement_get")
def test_version_comparison_does_not_use_floats(self, get): def test_version_comparison_does_not_use_floats(self, get):
@ -239,7 +240,7 @@ class TestPlacementCheck(test.NoDBTestCase):
] ]
} }
res = self.cmd._check_placement() 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") @mock.patch.object(status.UpgradeCommands, "_placement_get")
def test_invalid_version(self, get): def test_invalid_version(self, get):
@ -253,109 +254,11 @@ class TestPlacementCheck(test.NoDBTestCase):
] ]
} }
res = self.cmd._check_placement() 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' % self.assertIn('Placement API version %s needed, you have 0.9' %
status.MIN_PLACEMENT_MICROVERSION, res.details) 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): class TestUpgradeCheckCellsV2(test.NoDBTestCase):
"""Tests for the nova-status upgrade cells v2 specific check.""" """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. """The cells v2 check should fail because there are no cell mappings.
""" """
result = self.cmd._check_cellsv2() 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', self.assertIn('There needs to be at least two cell mappings',
result.details) result.details)
@ -395,7 +298,7 @@ class TestUpgradeCheckCellsV2(test.NoDBTestCase):
self._create_cell_mapping(uuid) self._create_cell_mapping(uuid)
result = self.cmd._check_cellsv2() 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) self.assertIn('No cell0 mapping found', result.details)
def test_check_no_host_mappings_with_computes(self): def test_check_no_host_mappings_with_computes(self):
@ -418,7 +321,7 @@ class TestUpgradeCheckCellsV2(test.NoDBTestCase):
cn.create() cn.create()
result = self.cmd._check_cellsv2() 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', self.assertIn('No host mappings found but there are compute nodes',
result.details) result.details)
@ -429,7 +332,7 @@ class TestUpgradeCheckCellsV2(test.NoDBTestCase):
self._setup_cells() self._setup_cells()
result = self.cmd._check_cellsv2() 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', self.assertIn('No host mappings or compute nodes were found',
result.details) result.details)
@ -446,7 +349,7 @@ class TestUpgradeCheckCellsV2(test.NoDBTestCase):
hm.create() hm.create()
result = self.cmd._check_cellsv2() result = self.cmd._check_cellsv2()
self.assertEqual(status.UpgradeCheckCode.SUCCESS, result.code) self.assertEqual(upgradecheck.Code.SUCCESS, result.code)
self.assertIsNone(result.details) self.assertIsNone(result.details)
@ -522,7 +425,7 @@ class TestUpgradeCheckIronicFlavorMigration(test.NoDBTestCase):
warning. warning.
""" """
result = self.cmd._check_ironic_flavor_migration() 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 ' self.assertIn('Unable to determine ironic flavor migration without '
'cell mappings', result.details) 'cell mappings', result.details)
@ -533,7 +436,7 @@ class TestUpgradeCheckIronicFlavorMigration(test.NoDBTestCase):
""" """
self._setup_cells() self._setup_cells()
result = self.cmd._check_ironic_flavor_migration() 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): def test_mixed_computes_deleted_ironic_instance(self):
"""Tests the scenario where we have a kvm compute node in one cell """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) ctxt, self.cell_mappings['cell1'], ironic_node, is_deleted=True)
result = self.cmd._check_ironic_flavor_migration() 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): def test_unmigrated_ironic_instances(self):
"""Tests a scenario where we have two cells with only ironic compute """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) ctxt, cell, ironic_node, flavor_migrated=False)
result = self.cmd._check_ironic_flavor_migration() 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 # Check the message - it should point out cell1 has one unmigrated
# instance and cell2 has two unmigrated instances. # instance and cell2 has two unmigrated instances.
unmigrated_instance_count_by_cell = { unmigrated_instance_count_by_cell = {
@ -629,12 +532,12 @@ class TestUpgradeCheckAPIServiceVersion(test.NoDBTestCase):
""" """
self.flags(enable=True, group='cells') self.flags(enable=True, group='cells')
result = self.cmd._check_api_service_version() 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): def test_check_no_cell_mappings_warning(self):
"""Warn when there are no cell mappings.""" """Warn when there are no cell mappings."""
result = self.cmd._check_api_service_version() 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 ' self.assertEqual('Unable to determine API service versions without '
'cell mappings.', result.details) 'cell mappings.', result.details)
@ -684,7 +587,7 @@ class TestUpgradeCheckAPIServiceVersion(test.NoDBTestCase):
binary='nova-compute', version=14) binary='nova-compute', version=14)
result = self.cmd._check_api_service_version() 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. # The only cell in the message should be cell0.
self.assertIn(cell0.uuid, result.details) self.assertIn(cell0.uuid, result.details)
self.assertNotIn(cell1.uuid, result.details) self.assertNotIn(cell1.uuid, result.details)
@ -707,7 +610,7 @@ class TestUpgradeCheckAPIServiceVersion(test.NoDBTestCase):
binary='nova-compute', version=15) binary='nova-compute', version=15)
result = self.cmd._check_api_service_version() 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): class TestUpgradeCheckRequestSpecMigration(test.NoDBTestCase):
@ -758,7 +661,7 @@ class TestUpgradeCheckRequestSpecMigration(test.NoDBTestCase):
warning. warning.
""" """
result = self.cmd._check_request_spec_migration() 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 ' self.assertIn('Unable to determine request spec migrations without '
'cell mappings.', result.details) 'cell mappings.', result.details)
@ -779,7 +682,7 @@ class TestUpgradeCheckRequestSpecMigration(test.NoDBTestCase):
ctxt, self.cell_mappings['cell2'], create_request_spec=True) ctxt, self.cell_mappings['cell2'], create_request_spec=True)
result = self.cmd._check_request_spec_migration() 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): def test_unmigrated_request_spec_instances(self):
"""Tests the scenario that we have a migrated instance in cell1 and """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']) self._create_instance_in_cell(ctxt, self.cell_mappings['cell2'])
result = self.cmd._check_request_spec_migration() 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 " self.assertIn("The following cells have instances which do not have "
"matching request_specs in the API database: %s Run " "matching request_specs in the API database: %s Run "
"'nova-manage db online_data_migrations' on each cell " "'nova-manage db online_data_migrations' on each cell "
@ -856,7 +759,7 @@ class TestUpgradeCheckConsoles(test.NoDBTestCase):
""" """
self.flags(enable=True, group='cells') self.flags(enable=True, group='cells')
result = self.cmd._check_console_auths() 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): def test_check_workaround_enabled(self):
"""This is a 'success' case since the console auths check is """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') self.flags(enable_consoleauth=True, group='workarounds')
result = self.cmd._check_console_auths() 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): def test_deleted_disabled_consoleauth(self):
"""Tests that services other than nova-consoleauth and deleted/disabled """Tests that services other than nova-consoleauth and deleted/disabled
@ -887,7 +790,7 @@ class TestUpgradeCheckConsoles(test.NoDBTestCase):
'nova-consoleauth', disabled=True) 'nova-consoleauth', disabled=True)
result = self.cmd._check_console_auths() 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): def test_consoleauth_with_upgrade_not_started(self):
"""Tests the scenario where the deployment is using consoles but has no """Tests the scenario where the deployment is using consoles but has no
@ -908,7 +811,7 @@ class TestUpgradeCheckConsoles(test.NoDBTestCase):
'nova-compute', version=30) 'nova-compute', version=30)
result = self.cmd._check_console_auths() 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): def test_consoleauth_with_upgrade_complete(self):
"""Tests the scenario where the deployment is using consoles and has """Tests the scenario where the deployment is using consoles and has
@ -939,7 +842,7 @@ class TestUpgradeCheckConsoles(test.NoDBTestCase):
'nova-compute', version=30) 'nova-compute', version=30)
result = self.cmd._check_console_auths() 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): def test_consoleauth_with_upgrade_partial(self):
"""Tests the scenario where the deployment is using consoles and has """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() 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 " self.assertIn("One or more cells were found which have nova-compute "
"services older than Rocky. " "services older than Rocky. "
"Please set the '[workarounds]enable_consoleauth' " "Please set the '[workarounds]enable_consoleauth' "

View File

@ -42,6 +42,7 @@ oslo.context>=2.19.2 # Apache-2.0
oslo.log>=3.36.0 # Apache-2.0 oslo.log>=3.36.0 # Apache-2.0
oslo.reports>=1.18.0 # Apache-2.0 oslo.reports>=1.18.0 # Apache-2.0
oslo.serialization!=2.19.1,>=2.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.utils>=3.37.0 # Apache-2.0
oslo.db>=4.40.0 # Apache-2.0 oslo.db>=4.40.0 # Apache-2.0
oslo.rootwrap>=5.8.0 # Apache-2.0 oslo.rootwrap>=5.8.0 # Apache-2.0