Add the base microversions test part

This is base part of microversions tests.
This patch adds the mechanism for selecting the microversion tests based
on new configration options like the following:

TestClass A: min_microversion = None,  max_microversion = 'latest'
TestClass B: min_microversion = None,  max_microversion = '2.2'
TestClass C: min_microversion = '2.3', max_microversion = 'latest'
TestClass D: min_microversion = '2.5', max_microversion = '2.10'

  +--------------------+-----------------------------------------------------+
  | Configration       | Test classes                                        |
  | (min,    max)      | (Passed microversion)                               |
  +====================+=====================================================+
  | None,     None     | A(Not passed), B(Not passed), C & D - Skipped       |
  +--------------------+-----------------------------------------------------+
  | None,     '2.3'    | A(Not passed), B(Not passed), C('2.3'), D - Skipped |
  +--------------------+-----------------------------------------------------+
  | '2.2',    'latest' | A('2.2'), B('2.2'), C('2.3'), D('2.5')              |
  +--------------------+-----------------------------------------------------+
  | '2.2',    '2.3'    | A('2.2'), B('2.2'), C('2.3'), D - Skipped           |
  +--------------------+-----------------------------------------------------+
  | '2.10',   '2.10'   | A('2.10'), B - Skipped, C('2.10'), D('2.10')        |
  +--------------------+-----------------------------------------------------+
  | None,     'latest' | A(Not passed), B(Not passed), C('2.3'), D('2.5')    |
  +--------------------+-----------------------------------------------------+
  | 'latest', 'latest' | A('latest'), B - Skipped, C('latest'), D - Skipped  |
  +--------------------+-----------------------------------------------------+

After this patch, we need to add tests for each microversion and
these test classes need to contain the range of microversions.

Partially implements blueprint api-microversions-testing-support

Change-Id: I57b78b4c0543b6fb0533b556886a19a03297555e
This commit is contained in:
Ken'ichi Ohmichi 2015-11-05 06:32:33 +00:00 committed by ghanshyam
parent a3a4b1c6a5
commit 4d237e72fe
7 changed files with 586 additions and 1 deletions

View File

@ -18,6 +18,7 @@ import time
from oslo_log import log as logging
from tempest_lib import exceptions as lib_exc
from tempest.common import api_version_utils
from tempest.common import compute
from tempest.common.utils import data_utils
from tempest.common import waiters
@ -29,7 +30,8 @@ CONF = config.CONF
LOG = logging.getLogger(__name__)
class BaseV2ComputeTest(tempest.test.BaseTestCase):
class BaseV2ComputeTest(api_version_utils.BaseMicroversionTest,
tempest.test.BaseTestCase):
"""Base test case class for all Compute API tests."""
force_tenant_isolation = False
@ -43,6 +45,12 @@ class BaseV2ComputeTest(tempest.test.BaseTestCase):
super(BaseV2ComputeTest, cls).skip_checks()
if not CONF.service_available.nova:
raise cls.skipException("Nova is not available")
cfg_min_version = CONF.compute_feature_enabled.min_microversion
cfg_max_version = CONF.compute_feature_enabled.max_microversion
api_version_utils.check_skip_with_microversion(cls.min_microversion,
cls.max_microversion,
cfg_min_version,
cfg_max_version)
@classmethod
def setup_credentials(cls):

View File

@ -0,0 +1,152 @@
# Copyright 2014 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import re
from tempest import exceptions
# Define the minimum and maximum version of the API across all of the
# REST API. The format of the version is:
# X.Y where:
#
# - X will only be changed if a significant backwards incompatible API
# change is made which affects the API as whole. That is, something
# that is only very very rarely incremented.
#
# - Y when you make any change to the API. Note that this includes
# semantic changes which may not affect the input or output formats or
# even originate in the API code layer. We are not distinguishing
# between backwards compatible and backwards incompatible changes in
# the versioning system. It must be made clear in the documentation as
# to what is a backwards compatible change and what is a backwards
# incompatible one.
class APIVersionRequest(object):
"""This class represents an API Version Request.
This class provides convenience methods for manipulation
and comparison of version numbers that we need to do to
implement microversions.
"""
# NOTE: This 'latest' version is a magic number, we assume any
# projects(Nova, etc.) never achieve this number.
latest_ver_major = 99999
latest_ver_minor = 99999
def __init__(self, version_string=None):
"""Create an API version request object.
:param version_string: String representation of APIVersionRequest.
Correct format is 'X.Y', where 'X' and 'Y' are int values.
None value should be used to create Null APIVersionRequest,
which is equal to 0.0
"""
self.ver_major = 0
self.ver_minor = 0
if version_string is not None:
match = re.match(r"^([1-9]\d*)\.([1-9]\d*|0)$",
version_string)
if match:
self.ver_major = int(match.group(1))
self.ver_minor = int(match.group(2))
elif version_string == 'latest':
self.ver_major = self.latest_ver_major
self.ver_minor = self.latest_ver_minor
else:
raise exceptions.InvalidAPIVersionString(
version=version_string)
def __str__(self):
"""Debug/Logging representation of object."""
return ("API Version Request: %s" % self.get_string())
def is_null(self):
return self.ver_major == 0 and self.ver_minor == 0
def _format_type_error(self, other):
return TypeError("'%(other)s' should be an instance of '%(cls)s'" %
{"other": other, "cls": self.__class__})
def __lt__(self, other):
if not isinstance(other, APIVersionRequest):
raise self._format_type_error(other)
return ((self.ver_major, self.ver_minor) <
(other.ver_major, other.ver_minor))
def __eq__(self, other):
if not isinstance(other, APIVersionRequest):
raise self._format_type_error(other)
return ((self.ver_major, self.ver_minor) ==
(other.ver_major, other.ver_minor))
def __gt__(self, other):
if not isinstance(other, APIVersionRequest):
raise self._format_type_error(other)
return ((self.ver_major, self.ver_minor) >
(other.ver_major, other.ver_minor))
def __le__(self, other):
return self < other or self == other
def __ne__(self, other):
return not self.__eq__(other)
def __ge__(self, other):
return self > other or self == other
def matches(self, min_version, max_version):
"""Matches the version object.
Returns whether the version object represents a version
greater than or equal to the minimum version and less than
or equal to the maximum version.
@param min_version: Minimum acceptable version.
@param max_version: Maximum acceptable version.
@returns: boolean
If min_version is null then there is no minimum limit.
If max_version is null then there is no maximum limit.
If self is null then raise ValueError
"""
if self.is_null():
raise ValueError
if max_version.is_null() and min_version.is_null():
return True
elif max_version.is_null():
return min_version <= self
elif min_version.is_null():
return self <= max_version
else:
return min_version <= self <= max_version
def get_string(self):
"""Version string representation.
Converts object to string representation which if used to create
an APIVersionRequest object results in the same version request.
"""
if self.is_null():
return None
if (self.ver_major == self.latest_ver_major and
self.ver_minor == self.latest_ver_minor):
return 'latest'
return "%s.%s" % (self.ver_major, self.ver_minor)

View File

@ -0,0 +1,64 @@
# Copyright 2015 NEC Corporation. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import testtools
from tempest.common import api_version_request
from tempest import exceptions
class BaseMicroversionTest(object):
"""Mixin class for API microversion test class."""
# NOTE: Basically, each microversion is small API change and we
# can use the same tests for most microversions in most cases.
# So it is nice to define the test class as possible to run
# for all microversions. We need to define microversion range
# (min_microversion, max_microversion) on each test class if necessary.
min_microversion = None
max_microversion = 'latest'
def check_skip_with_microversion(test_min_version, test_max_version,
cfg_min_version, cfg_max_version):
min_version = api_version_request.APIVersionRequest(test_min_version)
max_version = api_version_request.APIVersionRequest(test_max_version)
config_min_version = api_version_request.APIVersionRequest(cfg_min_version)
config_max_version = api_version_request.APIVersionRequest(cfg_max_version)
if ((min_version > max_version) or
(config_min_version > config_max_version)):
msg = ("Min version is greater than Max version. Test Class versions "
"[%s - %s]. configration versions [%s - %s]."
% (min_version.get_string(),
max_version.get_string(),
config_min_version.get_string(),
config_max_version.get_string()))
raise exceptions.InvalidConfiguration(msg)
# NOTE: Select tests which are in range of configuration like
# config min config max
# ----------------+--------------------------+----------------
# ...don't-select|
# ...select... ...select... ...select...
# |don't-select...
# ......................select............................
if (max_version < config_min_version or
config_max_version < min_version):
msg = ("The microversion range[%s - %s] of this test is out of the "
"configration range[%s - %s]."
% (min_version.get_string(),
max_version.get_string(),
config_min_version.get_string(),
config_max_version.get_string()))
raise testtools.TestCase.skipException(msg)

View File

@ -341,6 +341,22 @@ compute_features_group = cfg.OptGroup(name='compute-feature-enabled',
title="Enabled Compute Service Features")
ComputeFeaturesGroup = [
cfg.StrOpt('min_microversion',
default=None,
help="Lower version of the test target microversion range. "
"The format is 'X.Y', where 'X' and 'Y' are int values. "
"Tempest selects tests based on the range between "
"min_microversion and max_microversion. "
"If both values are None, Tempest avoids tests which "
"require a microversion."),
cfg.StrOpt('max_microversion',
default=None,
help="Upper version of the test target microversion range. "
"The format is 'X.Y', where 'X' and 'Y' are int values. "
"Tempest selects tests based on the range between "
"min_microversion and max_microversion. "
"If both values are None, Tempest avoids tests which "
"require a microversion."),
cfg.BoolOpt('disk_config',
default=True,
help="If false, skip disk config tests"),

View File

@ -176,6 +176,11 @@ class InvalidStructure(TempestException):
message = "Invalid structure of table with details"
class InvalidAPIVersionString(TempestException):
msg_fmt = ("API Version String %(version)s is of invalid format. Must "
"be of format MajorNum.MinorNum.")
class CommandFailed(Exception):
def __init__(self, returncode, cmd, output, stderr):
super(CommandFailed, self).__init__()

View File

@ -0,0 +1,146 @@
# Copyright 2014 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from tempest.common import api_version_request
from tempest import exceptions
from tempest.tests import base
class APIVersionRequestTests(base.TestCase):
def test_valid_version_strings(self):
def _test_string(version, exp_major, exp_minor):
v = api_version_request.APIVersionRequest(version)
self.assertEqual(v.ver_major, exp_major)
self.assertEqual(v.ver_minor, exp_minor)
_test_string("1.1", 1, 1)
_test_string("2.10", 2, 10)
_test_string("5.234", 5, 234)
_test_string("12.5", 12, 5)
_test_string("2.0", 2, 0)
_test_string("2.200", 2, 200)
def test_null_version(self):
v = api_version_request.APIVersionRequest()
self.assertTrue(v.is_null())
def test_invalid_version_strings(self):
self.assertRaises(exceptions.InvalidAPIVersionString,
api_version_request.APIVersionRequest, "2")
self.assertRaises(exceptions.InvalidAPIVersionString,
api_version_request.APIVersionRequest, "200")
self.assertRaises(exceptions.InvalidAPIVersionString,
api_version_request.APIVersionRequest, "2.1.4")
self.assertRaises(exceptions.InvalidAPIVersionString,
api_version_request.APIVersionRequest, "200.23.66.3")
self.assertRaises(exceptions.InvalidAPIVersionString,
api_version_request.APIVersionRequest, "5 .3")
self.assertRaises(exceptions.InvalidAPIVersionString,
api_version_request.APIVersionRequest, "5. 3")
self.assertRaises(exceptions.InvalidAPIVersionString,
api_version_request.APIVersionRequest, "5.03")
self.assertRaises(exceptions.InvalidAPIVersionString,
api_version_request.APIVersionRequest, "02.1")
self.assertRaises(exceptions.InvalidAPIVersionString,
api_version_request.APIVersionRequest, "2.001")
self.assertRaises(exceptions.InvalidAPIVersionString,
api_version_request.APIVersionRequest, "")
self.assertRaises(exceptions.InvalidAPIVersionString,
api_version_request.APIVersionRequest, " 2.1")
self.assertRaises(exceptions.InvalidAPIVersionString,
api_version_request.APIVersionRequest, "2.1 ")
def test_version_comparisons(self):
vers2_0 = api_version_request.APIVersionRequest("2.0")
vers2_5 = api_version_request.APIVersionRequest("2.5")
vers5_23 = api_version_request.APIVersionRequest("5.23")
v_null = api_version_request.APIVersionRequest()
v_latest = api_version_request.APIVersionRequest('latest')
self.assertTrue(v_null < vers2_5)
self.assertTrue(vers2_0 < vers2_5)
self.assertTrue(vers2_0 <= vers2_5)
self.assertTrue(vers2_0 <= vers2_0)
self.assertTrue(vers2_5 > v_null)
self.assertTrue(vers5_23 > vers2_5)
self.assertTrue(vers2_0 >= vers2_0)
self.assertTrue(vers5_23 >= vers2_5)
self.assertTrue(vers2_0 != vers2_5)
self.assertTrue(vers2_0 == vers2_0)
self.assertTrue(vers2_0 != v_null)
self.assertTrue(v_null == v_null)
self.assertTrue(vers2_0 <= v_latest)
self.assertTrue(vers2_0 != v_latest)
self.assertTrue(v_latest == v_latest)
self.assertRaises(TypeError, vers2_0.__lt__, "2.1")
def test_version_matches(self):
vers2_0 = api_version_request.APIVersionRequest("2.0")
vers2_5 = api_version_request.APIVersionRequest("2.5")
vers2_45 = api_version_request.APIVersionRequest("2.45")
vers3_3 = api_version_request.APIVersionRequest("3.3")
vers3_23 = api_version_request.APIVersionRequest("3.23")
vers4_0 = api_version_request.APIVersionRequest("4.0")
v_null = api_version_request.APIVersionRequest()
v_latest = api_version_request.APIVersionRequest('latest')
def _check_version_matches(version, version1, version2, check=True):
if check:
msg = "Version %s does not matches with [%s - %s] range"
self.assertTrue(version.matches(version1, version2),
msg % (version.get_string(),
version1.get_string(),
version2.get_string()))
else:
msg = "Version %s matches with [%s - %s] range"
self.assertFalse(version.matches(version1, version2),
msg % (version.get_string(),
version1.get_string(),
version2.get_string()))
_check_version_matches(vers2_5, vers2_0, vers2_45)
_check_version_matches(vers2_5, vers2_0, v_null)
_check_version_matches(vers2_0, vers2_0, vers2_5)
_check_version_matches(vers3_3, vers2_5, vers3_3)
_check_version_matches(vers3_3, v_null, vers3_3)
_check_version_matches(vers3_3, v_null, vers4_0)
_check_version_matches(vers2_0, vers2_5, vers2_45, False)
_check_version_matches(vers3_23, vers2_5, vers3_3, False)
_check_version_matches(vers2_5, vers2_45, vers2_0, False)
_check_version_matches(vers2_5, vers2_0, v_latest)
_check_version_matches(v_latest, v_latest, v_latest)
_check_version_matches(vers2_5, v_latest, v_latest, False)
_check_version_matches(v_latest, vers2_0, vers4_0, False)
self.assertRaises(ValueError, v_null.matches, vers2_0, vers2_45)
def test_get_string(self):
vers_string = ["3.23", "latest"]
for ver in vers_string:
ver_obj = api_version_request.APIVersionRequest(ver)
self.assertEqual(ver, ver_obj.get_string())
self.assertIsNotNone(
api_version_request.APIVersionRequest().get_string)

View File

@ -0,0 +1,194 @@
# Copyright 2015 NEC Corporation. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_config import cfg
import testtools
from tempest.api.compute import base as compute_base
from tempest.common import api_version_utils
from tempest import config
from tempest import exceptions
from tempest.tests import base
from tempest.tests import fake_config
class VersionTestNoneTolatest(compute_base.BaseV2ComputeTest):
min_microversion = None
max_microversion = 'latest'
class VersionTestNoneTo2_2(compute_base.BaseV2ComputeTest):
min_microversion = None
max_microversion = '2.2'
class VersionTest2_3ToLatest(compute_base.BaseV2ComputeTest):
min_microversion = '2.3'
max_microversion = 'latest'
class VersionTest2_5To2_10(compute_base.BaseV2ComputeTest):
min_microversion = '2.5'
max_microversion = '2.10'
class VersionTest2_10To2_10(compute_base.BaseV2ComputeTest):
min_microversion = '2.10'
max_microversion = '2.10'
class InvalidVersionTest(compute_base.BaseV2ComputeTest):
min_microversion = '2.11'
max_microversion = '2.1'
class TestMicroversionsTestsClass(base.TestCase):
def setUp(self):
super(TestMicroversionsTestsClass, self).setUp()
self.useFixture(fake_config.ConfigFixture())
self.stubs.Set(config, 'TempestConfigPrivate',
fake_config.FakePrivate)
def _test_version(self, cfg_min, cfg_max,
expected_pass_tests,
expected_skip_tests):
cfg.CONF.set_default('min_microversion',
cfg_min, group='compute-feature-enabled')
cfg.CONF.set_default('max_microversion',
cfg_max, group='compute-feature-enabled')
try:
for test_class in expected_pass_tests:
test_class.skip_checks()
for test_class in expected_skip_tests:
self.assertRaises(testtools.TestCase.skipException,
test_class.skip_checks)
except testtools.TestCase.skipException as e:
raise testtools.TestCase.failureException(e.message)
def test_config_version_none_none(self):
expected_pass_tests = [VersionTestNoneTolatest, VersionTestNoneTo2_2]
expected_skip_tests = [VersionTest2_3ToLatest, VersionTest2_5To2_10,
VersionTest2_10To2_10]
self._test_version(None, None,
expected_pass_tests,
expected_skip_tests)
def test_config_version_none_23(self):
expected_pass_tests = [VersionTestNoneTolatest, VersionTestNoneTo2_2,
VersionTest2_3ToLatest]
expected_skip_tests = [VersionTest2_5To2_10, VersionTest2_10To2_10]
self._test_version(None, '2.3',
expected_pass_tests,
expected_skip_tests)
def test_config_version_22_latest(self):
expected_pass_tests = [VersionTestNoneTolatest, VersionTestNoneTo2_2,
VersionTest2_3ToLatest, VersionTest2_5To2_10,
VersionTest2_10To2_10]
expected_skip_tests = []
self._test_version('2.2', 'latest',
expected_pass_tests,
expected_skip_tests)
def test_config_version_22_23(self):
expected_pass_tests = [VersionTestNoneTolatest, VersionTestNoneTo2_2,
VersionTest2_3ToLatest]
expected_skip_tests = [VersionTest2_5To2_10, VersionTest2_10To2_10]
self._test_version('2.2', '2.3',
expected_pass_tests,
expected_skip_tests)
def test_config_version_210_210(self):
expected_pass_tests = [VersionTestNoneTolatest,
VersionTest2_3ToLatest,
VersionTest2_5To2_10,
VersionTest2_10To2_10]
expected_skip_tests = [VersionTestNoneTo2_2]
self._test_version('2.10', '2.10',
expected_pass_tests,
expected_skip_tests)
def test_config_version_none_latest(self):
expected_pass_tests = [VersionTestNoneTolatest, VersionTestNoneTo2_2,
VersionTest2_3ToLatest, VersionTest2_5To2_10,
VersionTest2_10To2_10]
expected_skip_tests = []
self._test_version(None, 'latest',
expected_pass_tests,
expected_skip_tests)
def test_config_version_latest_latest(self):
expected_pass_tests = [VersionTestNoneTolatest, VersionTest2_3ToLatest]
expected_skip_tests = [VersionTestNoneTo2_2, VersionTest2_5To2_10,
VersionTest2_10To2_10]
self._test_version('latest', 'latest',
expected_pass_tests,
expected_skip_tests)
def test_config_invalid_version(self):
cfg.CONF.set_default('min_microversion',
'2.5', group='compute-feature-enabled')
cfg.CONF.set_default('max_microversion',
'2.1', group='compute-feature-enabled')
self.assertRaises(exceptions.InvalidConfiguration,
VersionTestNoneTolatest.skip_checks)
def test_config_version_invalid_test_version(self):
cfg.CONF.set_default('min_microversion',
None, group='compute-feature-enabled')
cfg.CONF.set_default('max_microversion',
'2.13', group='compute-feature-enabled')
self.assertRaises(exceptions.InvalidConfiguration,
InvalidVersionTest.skip_checks)
class TestVersionSkipLogic(base.TestCase):
def _test_version(self, test_min_version, test_max_version,
cfg_min_version, cfg_max_version, expected_skip=False):
try:
api_version_utils.check_skip_with_microversion(test_min_version,
test_max_version,
cfg_min_version,
cfg_max_version)
except testtools.TestCase.skipException as e:
if not expected_skip:
raise testtools.TestCase.failureException(e.message)
def test_version_min_in_range(self):
self._test_version('2.2', '2.10', '2.1', '2.7')
def test_version_max_in_range(self):
self._test_version('2.1', '2.3', '2.2', '2.7')
def test_version_cfg_in_range(self):
self._test_version('2.2', '2.9', '2.3', '2.7')
def test_version_equal(self):
self._test_version('2.2', '2.2', '2.2', '2.2')
def test_version_below_cfg_min(self):
self._test_version('2.2', '2.4', '2.5', '2.7', expected_skip=True)
def test_version_above_cfg_max(self):
self._test_version('2.8', '2.9', '2.3', '2.7', expected_skip=True)
def test_version_min_greater_than_max(self):
self.assertRaises(exceptions.InvalidConfiguration,
self._test_version, '2.8', '2.7', '2.3', '2.7')
def test_cfg_version_min_greater_than_max(self):
self.assertRaises(exceptions.InvalidConfiguration,
self._test_version, '2.2', '2.7', '2.9', '2.7')