From 7489126d83764779f92ba61e0091d839f1adfbbd Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Tue, 14 May 2019 20:42:45 -0400 Subject: [PATCH] Require nova_client.api_version >= 2.56 The [nova_client]/api_version defaults to 2.56 since change Idd6ebc94f81ad5d65256c80885f2addc1aaeaae1. There is compatibility code for that change but if 2.56 is not available watcher_non_live_migrate_instance will still fail if a destination host is used. Since 2.56 has been available since the Queens version of nova it should be reasonable to require at least that version of nova is running for using Watcher. This adds code which enforces the minimum version along with a release note and "watcher-status upgrade check" check method. Note that it's kind of weird for watcher to have a config option like nova_client.api_version since compute API microversions are per API request even though novaclient is constructed with the single configured version. It should really be something the client (watcher in this case) determines using version discovery and gracefully enables features if the required nova API version is available, but that's a bigger change. Change-Id: Id34938c7bb8a5ca934d997e52cac3b365414c006 --- doc/source/man/watcher-status.rst | 4 ++++ ...-required-nova-train-71f124192d88ae52.yaml | 8 +++++++ watcher/cmd/status.py | 21 +++++++++------- watcher/common/clients.py | 24 +++++++++++++++++++ watcher/conf/nova_client.py | 13 +++++++++- watcher/tests/cmd/test_status.py | 20 ++++++++++++---- watcher/tests/common/test_clients.py | 14 +++++++++-- 7 files changed, 88 insertions(+), 16 deletions(-) create mode 100644 releasenotes/notes/min-required-nova-train-71f124192d88ae52.yaml 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):