From 4393d9052423851a530b1a9df056d31aeec0597a Mon Sep 17 00:00:00 2001 From: Andrey Kurilin Date: Mon, 23 Nov 2015 15:16:06 +0200 Subject: [PATCH] Check response headers for microversions novaclient provides version negotiation on CLI layer. In case of usage novaclient as a lib, user can specify api_version with microversion part and can try communicate with Nova endpoint which doesn't support microversions. Nova endpoint will not check X-OpenStack-Nova-API-Version header and will execute request(it can be correct or not). It this case we should warn user about his mistake and about "incorrect" response. Change-Id: I7a5ab964b7b2b2a49cc80c22bf67ad5548afb30b --- novaclient/api_versions.py | 12 +++++++++- novaclient/client.py | 3 +++ novaclient/tests/unit/test_api_versions.py | 26 ++++++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/novaclient/api_versions.py b/novaclient/api_versions.py index 06fcc52bd..047a047db 100644 --- a/novaclient/api_versions.py +++ b/novaclient/api_versions.py @@ -30,6 +30,7 @@ if not LOG.handlers: LOG.addHandler(logging.StreamHandler()) +HEADER_NAME = "X-OpenStack-Nova-API-Version" # key is a deprecated version and value is an alternative version. DEPRECATED_VERSIONS = {"1.1": "2"} @@ -306,7 +307,16 @@ def update_headers(headers, api_version): """Set 'X-OpenStack-Nova-API-Version' header if api_version is not null""" if not api_version.is_null() and api_version.ver_minor != 0: - headers["X-OpenStack-Nova-API-Version"] = api_version.get_string() + headers[HEADER_NAME] = api_version.get_string() + + +def check_headers(response, api_version): + """Checks that 'X-OpenStack-Nova-API-Version' header in response.""" + if api_version.ver_minor > 0 and HEADER_NAME not in response.headers: + LOG.warning(_LW( + "Your request was processed by a Nova API which does not support " + "microversions (%s header is missing from response). " + "Warning: Response may be incorrect."), HEADER_NAME) def add_substitution(versioned_method): diff --git a/novaclient/client.py b/novaclient/client.py index 399d98e22..0fc998389 100644 --- a/novaclient/client.py +++ b/novaclient/client.py @@ -89,6 +89,7 @@ class SessionClient(adapter.LegacyJsonAdapter): method, raise_exc=False, **kwargs) + api_versions.check_headers(resp, self.api_version) if raise_exc and resp.status_code >= 400: raise exceptions.from_response(resp, body, url, method) @@ -365,6 +366,8 @@ class HTTPClient(object): url, **kwargs) + api_versions.check_headers(resp, self.api_version) + self.http_log_resp(resp) if resp.text: diff --git a/novaclient/tests/unit/test_api_versions.py b/novaclient/tests/unit/test_api_versions.py index 0159d198b..da35fdf99 100644 --- a/novaclient/tests/unit/test_api_versions.py +++ b/novaclient/tests/unit/test_api_versions.py @@ -145,6 +145,32 @@ class UpdateHeadersTestCase(utils.TestCase): headers) +class CheckHeadersTestCase(utils.TestCase): + def setUp(self): + super(CheckHeadersTestCase, self).setUp() + mock_log_patch = mock.patch("novaclient.api_versions.LOG") + self.mock_log = mock_log_patch.start() + self.addCleanup(mock_log_patch.stop) + + def test_microversion_is_specified(self): + response = mock.MagicMock(headers={api_versions.HEADER_NAME: ""}) + api_versions.check_headers(response, api_versions.APIVersion("2.2")) + self.assertFalse(self.mock_log.warning.called) + + response = mock.MagicMock(headers={}) + api_versions.check_headers(response, api_versions.APIVersion("2.2")) + self.assertTrue(self.mock_log.warning.called) + + def test_microversion_is_not_specified(self): + response = mock.MagicMock(headers={api_versions.HEADER_NAME: ""}) + api_versions.check_headers(response, api_versions.APIVersion("2.2")) + self.assertFalse(self.mock_log.warning.called) + + response = mock.MagicMock(headers={}) + api_versions.check_headers(response, api_versions.APIVersion("2.0")) + self.assertFalse(self.mock_log.warning.called) + + class GetAPIVersionTestCase(utils.TestCase): def test_get_available_client_versions(self): output = api_versions.get_available_major_versions()