From 9ff5c280ab55eaaa5a5e2b5b8bfa46bafebbfed2 Mon Sep 17 00:00:00 2001 From: Felipe Monteiro Date: Wed, 21 Jun 2017 21:05:07 +0100 Subject: [PATCH] Add compare header version function to tempest.lib The motivation for this commit is that some API responses like backing up a server image return the location of the image_id in either the response body or the response header depending on the microversion, e.g. [0]. In the case of server backup action, image_id is added to response body in microversion 2.45. Add `compare_version_header_to_request` to `api_version_utils` to accept a new kwarg called `operation`. At run time, 'eq' is translated to the __eq__ attribute of `APIVersionRequest`. The other operations include le, lt, gt, ge, and ne. This makes it possible to do for example: if api_version_utils.compare_version_header_to_response( "X-OpenStack-Nova-API-Version", "2.45", resp, "lt"): image1_id = resp['image_id'] else: image1_id = data_utils.parse_image_id(resp['location']) Which means that if "2.45" < "microversion in resp" then we can grab the image_id from the response body -- else we have to grab it from resp.response['location']. This commit: - adds compare_version_header_to_response to api_version_utils allowing to compare the request's header microversion to an expected microversion - modifies test_server_actions to use the new function in tests that always assume that the image_id attribute is in the resp header (not true across all microversions) -- this can be done to other tests in follow-up patch - adds related unit tests for all scenarios [0] https://developer.openstack.org/api-ref/compute/#create-server-back-up-createbackup-action Change-Id: Ib97e65cca468a09bbeaf68fcfe0e8192674a481e --- ...-header-version-func-de5139b2161b3627.yaml | 15 +++ .../compute/servers/test_server_actions.py | 19 +++- tempest/lib/common/api_version_utils.py | 59 ++++++++++++ tempest/lib/exceptions.py | 4 + .../lib/common/test_api_version_utils.py | 94 +++++++++++++++++-- 5 files changed, 182 insertions(+), 9 deletions(-) create mode 100644 releasenotes/notes/compare-header-version-func-de5139b2161b3627.yaml diff --git a/releasenotes/notes/compare-header-version-func-de5139b2161b3627.yaml b/releasenotes/notes/compare-header-version-func-de5139b2161b3627.yaml new file mode 100644 index 0000000000..305e7561d6 --- /dev/null +++ b/releasenotes/notes/compare-header-version-func-de5139b2161b3627.yaml @@ -0,0 +1,15 @@ +--- +features: + - | + Add a new function called ``compare_version_header_to_response`` to + ``tempest.lib.common.api_version_utils``, which compares the API + micoversion in the response header to another microversion using the + comparators defined in + ``tempest.lib.common.api_version_request.APIVersionRequest``. + + It is now possible to determine how to retrieve an attribute from a + response body of an API call, depending on the returned microversion. + + Add a new exception type called ``InvalidParam`` to + ``tempest.lib.exceptions``, allowing the possibility of raising an + exception if an invalid parameter is passed to a library function. diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py index 4870a3d051..b5fc39c4aa 100644 --- a/tempest/api/compute/servers/test_server_actions.py +++ b/tempest/api/compute/servers/test_server_actions.py @@ -23,6 +23,7 @@ from tempest.common import utils from tempest.common.utils.linux import remote_client from tempest.common import waiters from tempest import config +from tempest.lib.common import api_version_utils from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc @@ -369,7 +370,11 @@ class ServerActionsTestJSON(base.BaseV2ComputeTest): "been successful as it should have been " "deleted during rotation.", oldest_backup) - image1_id = data_utils.parse_image_id(resp['location']) + if api_version_utils.compare_version_header_to_response( + "OpenStack-API-Version", "compute 2.45", resp, "lt"): + image1_id = resp['image_id'] + else: + image1_id = data_utils.parse_image_id(resp['location']) self.addCleanup(_clean_oldest_backup, image1_id) waiters.wait_for_image_status(glance_client, image1_id, 'active') @@ -380,7 +385,11 @@ class ServerActionsTestJSON(base.BaseV2ComputeTest): backup_type='daily', rotation=2, name=backup2).response - image2_id = data_utils.parse_image_id(resp['location']) + if api_version_utils.compare_version_header_to_response( + "OpenStack-API-Version", "compute 2.45", resp, "lt"): + image2_id = resp['image_id'] + else: + image2_id = data_utils.parse_image_id(resp['location']) self.addCleanup(glance_client.delete_image, image2_id) waiters.wait_for_image_status(glance_client, image2_id, 'active') @@ -419,7 +428,11 @@ class ServerActionsTestJSON(base.BaseV2ComputeTest): backup_type='daily', rotation=2, name=backup3).response - image3_id = data_utils.parse_image_id(resp['location']) + if api_version_utils.compare_version_header_to_response( + "OpenStack-API-Version", "compute 2.45", resp, "lt"): + image3_id = resp['image_id'] + else: + image3_id = data_utils.parse_image_id(resp['location']) self.addCleanup(glance_client.delete_image, image3_id) # the first back up should be deleted waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE') diff --git a/tempest/lib/common/api_version_utils.py b/tempest/lib/common/api_version_utils.py index 1371b3c667..98f174d1ef 100644 --- a/tempest/lib/common/api_version_utils.py +++ b/tempest/lib/common/api_version_utils.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. +from oslo_log import log as logging import testtools from tempest.lib.common import api_version_request @@ -19,6 +20,7 @@ from tempest.lib import exceptions LATEST_MICROVERSION = 'latest' +LOG = logging.getLogger(__name__) class BaseMicroversionTest(object): @@ -120,3 +122,60 @@ def assert_version_header_matches_request(api_microversion_header_name, api_microversion, response_header)) raise exceptions.InvalidHTTPResponseHeader(msg) + + +def compare_version_header_to_response(api_microversion_header_name, + api_microversion, + response_header, + operation='eq'): + """Compares API microversion in response header to ``api_microversion``. + + Compare the ``api_microversion`` value in response header if microversion + header is present in response, otherwise return false. + + To make this function work for APIs which do not return microversion + header in response (example compute v2.0), this function does *not* raise + InvalidHTTPResponseHeader. + + :param api_microversion_header_name: Microversion header name. Example: + 'Openstack-Api-Version'. + :param api_microversion: Microversion number. Example: + + * '2.10' for the old-style header name, 'X-OpenStack-Nova-API-Version' + * 'Compute 2.10' for the new-style header name, 'Openstack-Api-Version' + + :param response_header: Response header where microversion is + expected to be present. + :param operation: The boolean operation to use to compare the + ``api_microversion`` to the microversion in ``response_header``. + Can be 'lt', 'eq', 'gt', 'le', 'ne', 'ge'. Default is 'eq'. The + operation type should be based on the order of the arguments: + ``api_microversion`` ``response_header`` microversion. + :returns: True if the comparison is logically true, else False if the + comparison is logically false or if ``api_microversion_header_name`` is + missing in the ``response_header``. + :raises InvalidParam: If the operation is not lt, eq, gt, le, ne or ge. + """ + api_microversion_header_name = api_microversion_header_name.lower() + if api_microversion_header_name not in response_header: + return False + + op = getattr(api_version_request.APIVersionRequest, + '__%s__' % operation, None) + + if op is None: + msg = ("Operation %s is invalid. Valid options include: lt, eq, gt, " + "le, ne, ge." % operation) + LOG.debug(msg) + raise exceptions.InvalidParam(invalid_param=msg) + + # Remove "volume" from "volume ", for example, so that the + # microversion can be converted to `APIVersionRequest`. + api_version = api_microversion.split(' ')[-1] + resp_version = response_header[api_microversion_header_name].split(' ')[-1] + if not op( + api_version_request.APIVersionRequest(api_version), + api_version_request.APIVersionRequest(resp_version)): + return False + + return True diff --git a/tempest/lib/exceptions.py b/tempest/lib/exceptions.py index c538c7242c..9b2e87e764 100644 --- a/tempest/lib/exceptions.py +++ b/tempest/lib/exceptions.py @@ -276,3 +276,7 @@ class DeleteErrorException(TempestException): class InvalidTestResource(TempestException): message = "%(name)s is not a valid %(type)s, or the name is ambiguous" + + +class InvalidParam(TempestException): + message = ("Invalid Parameter passed: %(invalid_param)s") diff --git a/tempest/tests/lib/common/test_api_version_utils.py b/tempest/tests/lib/common/test_api_version_utils.py index c063556f80..b99e8d41e0 100644 --- a/tempest/tests/lib/common/test_api_version_utils.py +++ b/tempest/tests/lib/common/test_api_version_utils.py @@ -92,24 +92,106 @@ class TestMicroversionHeaderMatches(base.TestCase): def test_header_matches(self): microversion_header_name = 'x-openstack-xyz-api-version' request_microversion = '2.1' - test_respose = {microversion_header_name: request_microversion} + test_response = {microversion_header_name: request_microversion} api_version_utils.assert_version_header_matches_request( - microversion_header_name, request_microversion, test_respose) + microversion_header_name, request_microversion, test_response) def test_header_does_not_match(self): microversion_header_name = 'x-openstack-xyz-api-version' request_microversion = '2.1' - test_respose = {microversion_header_name: '2.2'} + test_response = {microversion_header_name: '2.2'} self.assertRaises( exceptions.InvalidHTTPResponseHeader, api_version_utils.assert_version_header_matches_request, - microversion_header_name, request_microversion, test_respose) + microversion_header_name, request_microversion, test_response) def test_header_not_present(self): microversion_header_name = 'x-openstack-xyz-api-version' request_microversion = '2.1' - test_respose = {} + test_response = {} self.assertRaises( exceptions.InvalidHTTPResponseHeader, api_version_utils.assert_version_header_matches_request, - microversion_header_name, request_microversion, test_respose) + microversion_header_name, request_microversion, test_response) + + def test_compare_versions_less_than(self): + microversion_header_name = 'x-openstack-xyz-api-version' + request_microversion = '2.2' + test_response = {microversion_header_name: '2.1'} + self.assertFalse( + api_version_utils.compare_version_header_to_response( + microversion_header_name, request_microversion, test_response, + "lt")) + + def test_compare_versions_less_than_equal(self): + microversion_header_name = 'x-openstack-xyz-api-version' + request_microversion = '2.2' + test_response = {microversion_header_name: '2.1'} + self.assertFalse( + api_version_utils.compare_version_header_to_response( + microversion_header_name, request_microversion, test_response, + "le")) + + def test_compare_versions_greater_than_equal(self): + microversion_header_name = 'x-openstack-xyz-api-version' + request_microversion = '2.1' + test_response = {microversion_header_name: '2.2'} + self.assertFalse( + api_version_utils.compare_version_header_to_response( + microversion_header_name, request_microversion, test_response, + "ge")) + + def test_compare_versions_greater_than(self): + microversion_header_name = 'x-openstack-xyz-api-version' + request_microversion = '2.1' + test_response = {microversion_header_name: '2.2'} + self.assertFalse( + api_version_utils.compare_version_header_to_response( + microversion_header_name, request_microversion, test_response, + "gt")) + + def test_compare_versions_equal(self): + microversion_header_name = 'x-openstack-xyz-api-version' + request_microversion = '2.11' + test_response = {microversion_header_name: '2.1'} + self.assertFalse( + api_version_utils.compare_version_header_to_response( + microversion_header_name, request_microversion, test_response, + "eq")) + + def test_compare_versions_not_equal(self): + microversion_header_name = 'x-openstack-xyz-api-version' + request_microversion = '2.1' + test_response = {microversion_header_name: '2.1'} + self.assertFalse( + api_version_utils.compare_version_header_to_response( + microversion_header_name, request_microversion, test_response, + "ne")) + + def test_compare_versions_with_name_in_microversion(self): + microversion_header_name = 'x-openstack-xyz-api-version' + request_microversion = 'volume 3.1' + test_response = {microversion_header_name: 'volume 3.1'} + self.assertTrue( + api_version_utils.compare_version_header_to_response( + microversion_header_name, request_microversion, test_response, + "eq")) + + def test_compare_versions_invalid_operation(self): + microversion_header_name = 'x-openstack-xyz-api-version' + request_microversion = '2.1' + test_response = {microversion_header_name: '2.1'} + self.assertRaises( + exceptions.InvalidParam, + api_version_utils.compare_version_header_to_response, + microversion_header_name, request_microversion, test_response, + "foo") + + def test_compare_versions_header_not_present(self): + microversion_header_name = 'x-openstack-xyz-api-version' + request_microversion = '2.1' + test_response = {} + self.assertFalse( + api_version_utils.compare_version_header_to_response( + microversion_header_name, request_microversion, test_response, + "eq"))