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
This commit is contained in:
Felipe Monteiro 2017-06-21 21:05:07 +01:00
parent ad75393a99
commit 9ff5c280ab
5 changed files with 182 additions and 9 deletions

View File

@ -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.

View File

@ -23,6 +23,7 @@ from tempest.common import utils
from tempest.common.utils.linux import remote_client from tempest.common.utils.linux import remote_client
from tempest.common import waiters from tempest.common import waiters
from tempest import config from tempest import config
from tempest.lib.common import api_version_utils
from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import data_utils
from tempest.lib import decorators from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc from tempest.lib import exceptions as lib_exc
@ -369,7 +370,11 @@ class ServerActionsTestJSON(base.BaseV2ComputeTest):
"been successful as it should have been " "been successful as it should have been "
"deleted during rotation.", oldest_backup) "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) self.addCleanup(_clean_oldest_backup, image1_id)
waiters.wait_for_image_status(glance_client, waiters.wait_for_image_status(glance_client,
image1_id, 'active') image1_id, 'active')
@ -380,7 +385,11 @@ class ServerActionsTestJSON(base.BaseV2ComputeTest):
backup_type='daily', backup_type='daily',
rotation=2, rotation=2,
name=backup2).response 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) self.addCleanup(glance_client.delete_image, image2_id)
waiters.wait_for_image_status(glance_client, waiters.wait_for_image_status(glance_client,
image2_id, 'active') image2_id, 'active')
@ -419,7 +428,11 @@ class ServerActionsTestJSON(base.BaseV2ComputeTest):
backup_type='daily', backup_type='daily',
rotation=2, rotation=2,
name=backup3).response 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) self.addCleanup(glance_client.delete_image, image3_id)
# the first back up should be deleted # the first back up should be deleted
waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE') waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')

View File

@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from oslo_log import log as logging
import testtools import testtools
from tempest.lib.common import api_version_request from tempest.lib.common import api_version_request
@ -19,6 +20,7 @@ from tempest.lib import exceptions
LATEST_MICROVERSION = 'latest' LATEST_MICROVERSION = 'latest'
LOG = logging.getLogger(__name__)
class BaseMicroversionTest(object): class BaseMicroversionTest(object):
@ -120,3 +122,60 @@ def assert_version_header_matches_request(api_microversion_header_name,
api_microversion, api_microversion,
response_header)) response_header))
raise exceptions.InvalidHTTPResponseHeader(msg) 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`` <operation> ``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 <microversion>", 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

View File

@ -276,3 +276,7 @@ class DeleteErrorException(TempestException):
class InvalidTestResource(TempestException): class InvalidTestResource(TempestException):
message = "%(name)s is not a valid %(type)s, or the name is ambiguous" message = "%(name)s is not a valid %(type)s, or the name is ambiguous"
class InvalidParam(TempestException):
message = ("Invalid Parameter passed: %(invalid_param)s")

View File

@ -92,24 +92,106 @@ class TestMicroversionHeaderMatches(base.TestCase):
def test_header_matches(self): def test_header_matches(self):
microversion_header_name = 'x-openstack-xyz-api-version' microversion_header_name = 'x-openstack-xyz-api-version'
request_microversion = '2.1' 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( 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): def test_header_does_not_match(self):
microversion_header_name = 'x-openstack-xyz-api-version' microversion_header_name = 'x-openstack-xyz-api-version'
request_microversion = '2.1' request_microversion = '2.1'
test_respose = {microversion_header_name: '2.2'} test_response = {microversion_header_name: '2.2'}
self.assertRaises( self.assertRaises(
exceptions.InvalidHTTPResponseHeader, exceptions.InvalidHTTPResponseHeader,
api_version_utils.assert_version_header_matches_request, 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): def test_header_not_present(self):
microversion_header_name = 'x-openstack-xyz-api-version' microversion_header_name = 'x-openstack-xyz-api-version'
request_microversion = '2.1' request_microversion = '2.1'
test_respose = {} test_response = {}
self.assertRaises( self.assertRaises(
exceptions.InvalidHTTPResponseHeader, exceptions.InvalidHTTPResponseHeader,
api_version_utils.assert_version_header_matches_request, 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"))