diff --git a/doc/source/man/watcher-status.rst b/doc/source/man/watcher-status.rst index 6486e24c4..8b0eeea9d 100644 --- a/doc/source/man/watcher-status.rst +++ b/doc/source/man/watcher-status.rst @@ -81,3 +81,7 @@ Upgrade **2.0.0 (Stein)** * Sample check to be filled in with checks as they are added in Stein. + + **3.0.0 (Train)** + + * A check was added to enforce the minimum required version of nova API used. diff --git a/releasenotes/notes/min-required-nova-train-71f124192d88ae52.yaml b/releasenotes/notes/min-required-nova-train-71f124192d88ae52.yaml new file mode 100644 index 000000000..5e1540791 --- /dev/null +++ b/releasenotes/notes/min-required-nova-train-71f124192d88ae52.yaml @@ -0,0 +1,8 @@ +--- +upgrade: + - | + The minimum required version of the ``[nova_client]/api_version`` value + is now enforced to be ``2.56`` which is available since the Queens version + of the nova compute service. + + A ``watcher-status upgrade check`` has been added for this. diff --git a/watcher/cmd/status.py b/watcher/cmd/status.py index acdd106fc..76a3275bd 100644 --- a/watcher/cmd/status.py +++ b/watcher/cmd/status.py @@ -15,8 +15,10 @@ import sys from oslo_upgradecheck import upgradecheck +import six from watcher._i18n import _ +from watcher.common import clients from watcher import conf CONF = conf.CONF @@ -30,17 +32,18 @@ class Checks(upgradecheck.UpgradeCommands): and added to _upgrade_checks tuple. """ - def _sample_check(self): - """This is sample check added to test the upgrade check framework - - It needs to be removed after adding any real upgrade check - """ - return upgradecheck.Result(upgradecheck.Code.SUCCESS, 'Sample detail') + def _minimum_nova_api_version(self): + """Checks the minimum required version of nova_client.api_version""" + try: + clients.check_min_nova_api_version(CONF.nova_client.api_version) + except ValueError as e: + return upgradecheck.Result( + upgradecheck.Code.FAILURE, six.text_type(e)) + return upgradecheck.Result(upgradecheck.Code.SUCCESS) _upgrade_checks = ( - # Sample check added for now. - # Whereas in future real checks must be added here in tuple - (_('Sample Check'), _sample_check), + # Added in Train. + (_('Minimum Nova API Version'), _minimum_nova_api_version), ) diff --git a/watcher/common/clients.py b/watcher/common/clients.py index bfe351ba8..c84a5cfe9 100755 --- a/watcher/common/clients.py +++ b/watcher/common/clients.py @@ -20,6 +20,7 @@ from keystoneauth1 import loading as ka_loading from keystoneclient import client as keyclient from monascaclient import client as monclient from neutronclient.neutron import client as netclient +from novaclient import api_versions as nova_api_versions from novaclient import client as nvclient from watcher.common import exception @@ -34,6 +35,26 @@ CONF = cfg.CONF _CLIENTS_AUTH_GROUP = 'watcher_clients_auth' +# NOTE(mriedem): This is the minimum required version of the nova API for +# watcher features to work. If new features are added which require new +# versions, they should perform version discovery and be backward compatible +# for at least one release before raising the minimum required version. +MIN_NOVA_API_VERSION = '2.56' + + +def check_min_nova_api_version(config_version): + """Validates the minimum required nova API version. + + :param config_version: The configured [nova_client]/api_version value + :raises: ValueError if the configured version is less than the required + minimum + """ + min_required = nova_api_versions.APIVersion(MIN_NOVA_API_VERSION) + if nova_api_versions.APIVersion(config_version) < min_required: + raise ValueError('Invalid nova_client.api_version %s. %s or ' + 'greater is required.' % (config_version, + MIN_NOVA_API_VERSION)) + class OpenStackClients(object): """Convenience class to create and cache client instances.""" @@ -87,6 +108,9 @@ class OpenStackClients(object): return self._nova novaclient_version = self._get_client_option('nova', 'api_version') + + check_min_nova_api_version(novaclient_version) + nova_endpoint_type = self._get_client_option('nova', 'endpoint_type') nova_region_name = self._get_client_option('nova', 'region_name') self._nova = nvclient.Client(novaclient_version, diff --git a/watcher/conf/nova_client.py b/watcher/conf/nova_client.py index e5ae3910c..952130e25 100755 --- a/watcher/conf/nova_client.py +++ b/watcher/conf/nova_client.py @@ -18,13 +18,24 @@ from oslo_config import cfg +from watcher.common import clients + nova_client = cfg.OptGroup(name='nova_client', title='Configuration Options for Nova') NOVA_CLIENT_OPTS = [ cfg.StrOpt('api_version', default='2.56', - help='Version of Nova API to use in novaclient.'), + help=""" +Version of Nova API to use in novaclient. + +Minimum required version: %s + +Certain Watcher features depend on a minimum version of the compute +API being available which is enforced with this option. See +https://docs.openstack.org/nova/latest/reference/api-microversion-history.html +for the compute API microversion history. +""" % clients.MIN_NOVA_API_VERSION), cfg.StrOpt('endpoint_type', default='publicURL', help='Type of endpoint to use in novaclient. ' diff --git a/watcher/tests/cmd/test_status.py b/watcher/tests/cmd/test_status.py index c85f5ac36..633e85502 100644 --- a/watcher/tests/cmd/test_status.py +++ b/watcher/tests/cmd/test_status.py @@ -15,8 +15,11 @@ from oslo_upgradecheck.upgradecheck import Code from watcher.cmd import status +from watcher import conf from watcher.tests import base +CONF = conf.CONF + class TestUpgradeChecks(base.TestCase): @@ -24,7 +27,16 @@ class TestUpgradeChecks(base.TestCase): super(TestUpgradeChecks, self).setUp() self.cmd = status.Checks() - def test__sample_check(self): - check_result = self.cmd._sample_check() - self.assertEqual( - Code.SUCCESS, check_result.code) + def test_minimum_nova_api_version_ok(self): + # Tests that the default [nova_client]/api_version meets the minimum + # required version. + result = self.cmd._minimum_nova_api_version() + self.assertEqual(Code.SUCCESS, result.code) + + def test_minimum_nova_api_version_fail(self): + # Tests the scenario that [nova_client]/api_version is less than the + # minimum required version. + CONF.set_override('api_version', '2.47', group='nova_client') + result = self.cmd._minimum_nova_api_version() + self.assertEqual(Code.FAILURE, result.code) + self.assertIn('Invalid nova_client.api_version 2.47.', result.details) diff --git a/watcher/tests/common/test_clients.py b/watcher/tests/common/test_clients.py index 0ef1e0b1f..091b325d4 100755 --- a/watcher/tests/common/test_clients.py +++ b/watcher/tests/common/test_clients.py @@ -26,6 +26,7 @@ from monascaclient.v2_0 import client as monclient_v2 from neutronclient.neutron import client as netclient from neutronclient.v2_0 import client as netclient_v2 from novaclient import client as nvclient +import six from watcher.common import clients from watcher import conf @@ -125,11 +126,20 @@ class TestClients(base.TestCase): @mock.patch.object(clients.OpenStackClients, 'session') def test_clients_nova_diff_vers(self, mock_session): - CONF.set_override('api_version', '2.3', group='nova_client') + CONF.set_override('api_version', '2.60', group='nova_client') osc = clients.OpenStackClients() osc._nova = None osc.nova() - self.assertEqual('2.3', osc.nova().api_version.get_string()) + self.assertEqual('2.60', osc.nova().api_version.get_string()) + + @mock.patch.object(clients.OpenStackClients, 'session') + def test_clients_nova_bad_min_version(self, mock_session): + CONF.set_override('api_version', '2.47', group='nova_client') + osc = clients.OpenStackClients() + osc._nova = None + ex = self.assertRaises(ValueError, osc.nova) + self.assertIn('Invalid nova_client.api_version 2.47', + six.text_type(ex)) @mock.patch.object(clients.OpenStackClients, 'session') def test_clients_nova_diff_endpoint(self, mock_session):