Merge "Add support for API microversions in Tempest tests"
This commit is contained in:
commit
10d00c040f
@ -28,8 +28,13 @@ ADMIN_CREDS = common_creds.get_configured_credentials('identity_admin')
|
|||||||
class Manager(clients.Manager):
|
class Manager(clients.Manager):
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
credentials=ADMIN_CREDS,
|
credentials=ADMIN_CREDS,
|
||||||
service=None,
|
service=None):
|
||||||
api_microversions=None):
|
"""Initialization of Manager class.
|
||||||
|
|
||||||
|
Setup service client and make it available for test cases.
|
||||||
|
:param credentials: type Credentials or TestResources
|
||||||
|
:param service: service name
|
||||||
|
"""
|
||||||
super(Manager, self).__init__(credentials, service)
|
super(Manager, self).__init__(credentials, service)
|
||||||
self.baremetal_client = BaremetalClient(
|
self.baremetal_client = BaremetalClient(
|
||||||
self.auth_provider,
|
self.auth_provider,
|
||||||
|
@ -66,4 +66,20 @@ BaremetalGroup = [
|
|||||||
# help="Timeout for unprovisioning an Ironic node. "
|
# help="Timeout for unprovisioning an Ironic node. "
|
||||||
# "Takes longer since Kilo as Ironic performs an extra "
|
# "Takes longer since Kilo as Ironic performs an extra "
|
||||||
# "step in Node cleaning.")
|
# "step in Node cleaning.")
|
||||||
|
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='latest',
|
||||||
|
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."),
|
||||||
]
|
]
|
||||||
|
@ -15,8 +15,11 @@ import functools
|
|||||||
from oslo_serialization import jsonutils as json
|
from oslo_serialization import jsonutils as json
|
||||||
import six
|
import six
|
||||||
from six.moves.urllib import parse as urllib
|
from six.moves.urllib import parse as urllib
|
||||||
|
from tempest.lib.common import api_version_utils
|
||||||
from tempest.lib.common import rest_client
|
from tempest.lib.common import rest_client
|
||||||
|
|
||||||
|
BAREMETAL_MICROVERSION = None
|
||||||
|
|
||||||
|
|
||||||
def handle_errors(f):
|
def handle_errors(f):
|
||||||
"""A decorator that allows to ignore certain types of errors."""
|
"""A decorator that allows to ignore certain types of errors."""
|
||||||
@ -41,8 +44,27 @@ def handle_errors(f):
|
|||||||
class BaremetalClient(rest_client.RestClient):
|
class BaremetalClient(rest_client.RestClient):
|
||||||
"""Base Tempest REST client for Ironic API."""
|
"""Base Tempest REST client for Ironic API."""
|
||||||
|
|
||||||
|
api_microversion_header_name = 'X-OpenStack-Ironic-API-Version'
|
||||||
uri_prefix = ''
|
uri_prefix = ''
|
||||||
|
|
||||||
|
def get_headers(self):
|
||||||
|
headers = super(BaremetalClient, self).get_headers()
|
||||||
|
if BAREMETAL_MICROVERSION:
|
||||||
|
headers[self.api_microversion_header_name] = BAREMETAL_MICROVERSION
|
||||||
|
return headers
|
||||||
|
|
||||||
|
def request(self, method, url, extra_headers=False, headers=None,
|
||||||
|
body=None):
|
||||||
|
resp, resp_body = super(BaremetalClient, self).request(
|
||||||
|
method, url, extra_headers, headers, body)
|
||||||
|
if (BAREMETAL_MICROVERSION and
|
||||||
|
BAREMETAL_MICROVERSION != api_version_utils.LATEST_MICROVERSION):
|
||||||
|
api_version_utils.assert_version_header_matches_request(
|
||||||
|
self.api_microversion_header_name,
|
||||||
|
BAREMETAL_MICROVERSION,
|
||||||
|
resp)
|
||||||
|
return resp, resp_body
|
||||||
|
|
||||||
def serialize(self, object_dict):
|
def serialize(self, object_dict):
|
||||||
"""Serialize an Ironic object."""
|
"""Serialize an Ironic object."""
|
||||||
|
|
||||||
|
@ -264,6 +264,29 @@ class BaremetalClient(base.BaremetalClient):
|
|||||||
return self._put_request('nodes/%s/states/power' % node_uuid,
|
return self._put_request('nodes/%s/states/power' % node_uuid,
|
||||||
target)
|
target)
|
||||||
|
|
||||||
|
@base.handle_errors
|
||||||
|
def set_node_provision_state(self, node_uuid, state, configdrive=None):
|
||||||
|
"""Set provision state of the specified node.
|
||||||
|
|
||||||
|
:param node_uuid: The unique identifier of the node.
|
||||||
|
:state: desired state to set
|
||||||
|
(active/rebuild/deleted/inspect/manage/provide).
|
||||||
|
:config_drive: A gzipped, base64-encoded configuration drive string.
|
||||||
|
"""
|
||||||
|
data = {'target': state, 'configdrive': configdrive}
|
||||||
|
return self._put_request('nodes/%s/states/provision' % node_uuid,
|
||||||
|
data)
|
||||||
|
|
||||||
|
@base.handle_errors
|
||||||
|
def set_node_raid_config(self, node_uuid, target_raid_config):
|
||||||
|
"""Set raid config of the specified node.
|
||||||
|
|
||||||
|
:param node_uuid: The unique identifier of the node.
|
||||||
|
:target_raid_config: desired RAID configuration of the node.
|
||||||
|
"""
|
||||||
|
return self._put_request('nodes/%s/states/raid' % node_uuid,
|
||||||
|
target_raid_config)
|
||||||
|
|
||||||
@base.handle_errors
|
@base.handle_errors
|
||||||
def validate_driver_interface(self, node_uuid):
|
def validate_driver_interface(self, node_uuid):
|
||||||
"""Get all driver interfaces of a specific node.
|
"""Get all driver interfaces of a specific node.
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
# 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 fixtures
|
||||||
|
|
||||||
|
from ironic_tempest_plugin.services.baremetal import base
|
||||||
|
|
||||||
|
|
||||||
|
class APIMicroversionFixture(fixtures.Fixture):
|
||||||
|
|
||||||
|
def __init__(self, baremetal_microversion):
|
||||||
|
self.baremetal_microversion = baremetal_microversion
|
||||||
|
|
||||||
|
def _setUp(self):
|
||||||
|
super(APIMicroversionFixture, self)._setUp()
|
||||||
|
base.BAREMETAL_MICROVERSION = self.baremetal_microversion
|
||||||
|
self.addCleanup(self._reset_compute_microversion)
|
||||||
|
|
||||||
|
def _reset_compute_microversion(self):
|
||||||
|
base.BAREMETAL_MICROVERSION = None
|
@ -13,11 +13,13 @@
|
|||||||
import functools
|
import functools
|
||||||
|
|
||||||
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 exceptions as lib_exc
|
from tempest.lib import exceptions as lib_exc
|
||||||
from tempest import test
|
from tempest import test
|
||||||
|
|
||||||
from ironic_tempest_plugin import clients
|
from ironic_tempest_plugin import clients
|
||||||
|
from ironic_tempest_plugin.tests.api.admin import api_microversion_fixture
|
||||||
|
|
||||||
CONF = config.CONF
|
CONF = config.CONF
|
||||||
|
|
||||||
@ -50,7 +52,8 @@ def creates(resource):
|
|||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
class BaseBaremetalTest(test.BaseTestCase):
|
class BaseBaremetalTest(api_version_utils.BaseMicroversionTest,
|
||||||
|
test.BaseTestCase):
|
||||||
"""Base class for Baremetal API tests."""
|
"""Base class for Baremetal API tests."""
|
||||||
|
|
||||||
credentials = ['admin']
|
credentials = ['admin']
|
||||||
@ -64,6 +67,23 @@ class BaseBaremetalTest(test.BaseTestCase):
|
|||||||
(cls.__name__, CONF.baremetal.driver))
|
(cls.__name__, CONF.baremetal.driver))
|
||||||
raise cls.skipException(skip_msg)
|
raise cls.skipException(skip_msg)
|
||||||
|
|
||||||
|
cfg_min_version = CONF.baremetal.min_microversion
|
||||||
|
cfg_max_version = CONF.baremetal.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):
|
||||||
|
cls.request_microversion = (
|
||||||
|
api_version_utils.select_request_microversion(
|
||||||
|
cls.min_microversion,
|
||||||
|
CONF.baremetal.min_microversion))
|
||||||
|
cls.services_microversion = {
|
||||||
|
CONF.baremetal.catalog_type: cls.request_microversion}
|
||||||
|
super(BaseBaremetalTest, cls).setup_credentials()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setup_clients(cls):
|
def setup_clients(cls):
|
||||||
super(BaseBaremetalTest, cls).setup_clients()
|
super(BaseBaremetalTest, cls).setup_clients()
|
||||||
@ -72,9 +92,13 @@ class BaseBaremetalTest(test.BaseTestCase):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def resource_setup(cls):
|
def resource_setup(cls):
|
||||||
super(BaseBaremetalTest, cls).resource_setup()
|
super(BaseBaremetalTest, cls).resource_setup()
|
||||||
|
cls.request_microversion = (
|
||||||
|
api_version_utils.select_request_microversion(
|
||||||
|
cls.min_microversion,
|
||||||
|
CONF.baremetal.min_microversion))
|
||||||
cls.driver = CONF.baremetal.driver
|
cls.driver = CONF.baremetal.driver
|
||||||
cls.power_timeout = CONF.baremetal.power_timeout
|
cls.power_timeout = CONF.baremetal.power_timeout
|
||||||
|
cls.unprovision_timeout = CONF.baremetal.unprovision_timeout
|
||||||
cls.created_objects = {}
|
cls.created_objects = {}
|
||||||
for resource in RESOURCE_TYPES:
|
for resource in RESOURCE_TYPES:
|
||||||
cls.created_objects[resource] = set()
|
cls.created_objects[resource] = set()
|
||||||
@ -92,6 +116,11 @@ class BaseBaremetalTest(test.BaseTestCase):
|
|||||||
finally:
|
finally:
|
||||||
super(BaseBaremetalTest, cls).resource_cleanup()
|
super(BaseBaremetalTest, cls).resource_cleanup()
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(BaseBaremetalTest, self).setUp()
|
||||||
|
self.useFixture(api_microversion_fixture.APIMicroversionFixture(
|
||||||
|
self.request_microversion))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@creates('chassis')
|
@creates('chassis')
|
||||||
def create_chassis(cls, description=None, expect_errors=False):
|
def create_chassis(cls, description=None, expect_errors=False):
|
||||||
|
@ -16,17 +16,17 @@ from oslo_utils import timeutils
|
|||||||
from tempest.lib import exceptions
|
from tempest.lib import exceptions
|
||||||
from tempest import test
|
from tempest import test
|
||||||
|
|
||||||
|
from ironic_tempest_plugin.tests.api.admin import api_microversion_fixture
|
||||||
from ironic_tempest_plugin.tests.api.admin import base
|
from ironic_tempest_plugin.tests.api.admin import base
|
||||||
|
|
||||||
|
|
||||||
class TestNodeStates(base.BaseBaremetalTest):
|
class TestNodeStatesMixin(object):
|
||||||
"""Tests for baremetal NodeStates."""
|
"""Mixin for for baremetal node states tests."""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def resource_setup(cls):
|
def resource_setup(cls):
|
||||||
super(TestNodeStates, cls).resource_setup()
|
super(TestNodeStatesMixin, cls).resource_setup()
|
||||||
_, cls.chassis = cls.create_chassis()
|
_, cls.chassis = cls.create_chassis()
|
||||||
_, cls.node = cls.create_node(cls.chassis['uuid'])
|
|
||||||
|
|
||||||
def _validate_power_state(self, node_uuid, power_state):
|
def _validate_power_state(self, node_uuid, power_state):
|
||||||
# Validate that power state is set within timeout
|
# Validate that power state is set within timeout
|
||||||
@ -42,11 +42,26 @@ class TestNodeStates(base.BaseBaremetalTest):
|
|||||||
'the required time: %s sec.' % self.power_timeout)
|
'the required time: %s sec.' % self.power_timeout)
|
||||||
raise exceptions.TimeoutException(message)
|
raise exceptions.TimeoutException(message)
|
||||||
|
|
||||||
|
def _validate_provision_state(self, node_uuid, target_state):
|
||||||
|
# Validate that provision state is set within timeout
|
||||||
|
start = timeutils.utcnow()
|
||||||
|
while timeutils.delta_seconds(
|
||||||
|
start, timeutils.utcnow()) < self.unprovision_timeout:
|
||||||
|
_, node = self.client.show_node(node_uuid)
|
||||||
|
if node['provision_state'] == target_state:
|
||||||
|
return
|
||||||
|
message = ('Failed to set provision state %(state)s within '
|
||||||
|
'the required time: %(timeout)s sec.',
|
||||||
|
{'state': target_state,
|
||||||
|
'timeout': self.unprovision_timeout})
|
||||||
|
raise exceptions.TimeoutException(message)
|
||||||
|
|
||||||
@test.idempotent_id('cd8afa5e-3f57-4e43-8185-beb83d3c9015')
|
@test.idempotent_id('cd8afa5e-3f57-4e43-8185-beb83d3c9015')
|
||||||
def test_list_nodestates(self):
|
def test_list_nodestates(self):
|
||||||
_, nodestates = self.client.list_nodestates(self.node['uuid'])
|
_, node = self.create_node(self.chassis['uuid'])
|
||||||
|
_, nodestates = self.client.list_nodestates(node['uuid'])
|
||||||
for key in nodestates:
|
for key in nodestates:
|
||||||
self.assertEqual(nodestates[key], self.node[key])
|
self.assertEqual(nodestates[key], node[key])
|
||||||
|
|
||||||
@test.idempotent_id('fc5b9320-0c98-4e5a-8848-877fe5a0322c')
|
@test.idempotent_id('fc5b9320-0c98-4e5a-8848-877fe5a0322c')
|
||||||
def test_set_node_power_state(self):
|
def test_set_node_power_state(self):
|
||||||
@ -57,3 +72,122 @@ class TestNodeStates(base.BaseBaremetalTest):
|
|||||||
self.client.set_node_power_state(node['uuid'], state)
|
self.client.set_node_power_state(node['uuid'], state)
|
||||||
# Check power state after state is set
|
# Check power state after state is set
|
||||||
self._validate_power_state(node['uuid'], state)
|
self._validate_power_state(node['uuid'], state)
|
||||||
|
|
||||||
|
|
||||||
|
class TestNodeStatesV1_1(TestNodeStatesMixin, base.BaseBaremetalTest):
|
||||||
|
|
||||||
|
@test.idempotent_id('ccb8fca9-2ba0-480c-a037-34c3bd09dc74')
|
||||||
|
def test_set_node_provision_state(self):
|
||||||
|
_, node = self.create_node(self.chassis['uuid'])
|
||||||
|
# Nodes appear in NONE state by default until v1.1
|
||||||
|
self.assertEqual(None, node['provision_state'])
|
||||||
|
provision_states_list = ['active', 'deleted']
|
||||||
|
target_states_list = ['active', None]
|
||||||
|
for (provision_state, target_state) in zip(provision_states_list,
|
||||||
|
target_states_list):
|
||||||
|
self.client.set_node_provision_state(node['uuid'], provision_state)
|
||||||
|
self._validate_provision_state(node['uuid'], target_state)
|
||||||
|
|
||||||
|
|
||||||
|
class TestNodeStatesV1_2(TestNodeStatesMixin, base.BaseBaremetalTest):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestNodeStatesV1_2, self).setUp()
|
||||||
|
self.useFixture(api_microversion_fixture.APIMicroversionFixture('1.2'))
|
||||||
|
|
||||||
|
@test.idempotent_id('9c414984-f3b6-4b3d-81da-93b60d4662fb')
|
||||||
|
def test_set_node_provision_state(self):
|
||||||
|
_, node = self.create_node(self.chassis['uuid'])
|
||||||
|
# Nodes appear in AVAILABLE state by default from v1.2 to v1.10
|
||||||
|
self.assertEqual('available', node['provision_state'])
|
||||||
|
provision_states_list = ['active', 'deleted']
|
||||||
|
target_states_list = ['active', 'available']
|
||||||
|
for (provision_state, target_state) in zip(provision_states_list,
|
||||||
|
target_states_list):
|
||||||
|
self.client.set_node_provision_state(node['uuid'], provision_state)
|
||||||
|
self._validate_provision_state(node['uuid'], target_state)
|
||||||
|
|
||||||
|
|
||||||
|
class TestNodeStatesV1_4(TestNodeStatesMixin, base.BaseBaremetalTest):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestNodeStatesV1_4, self).setUp()
|
||||||
|
self.useFixture(api_microversion_fixture.APIMicroversionFixture('1.4'))
|
||||||
|
|
||||||
|
@test.idempotent_id('3d606003-05ce-4b5a-964d-bdee382fafe9')
|
||||||
|
def test_set_node_provision_state(self):
|
||||||
|
_, node = self.create_node(self.chassis['uuid'])
|
||||||
|
# Nodes appear in AVAILABLE state by default from v1.2 to v1.10
|
||||||
|
self.assertEqual('available', node['provision_state'])
|
||||||
|
# MANAGEABLE state and PROVIDE transition have been added in v1.4
|
||||||
|
provision_states_list = [
|
||||||
|
'manage', 'provide', 'active', 'deleted']
|
||||||
|
target_states_list = [
|
||||||
|
'manageable', 'available', 'active', 'available']
|
||||||
|
for (provision_state, target_state) in zip(provision_states_list,
|
||||||
|
target_states_list):
|
||||||
|
self.client.set_node_provision_state(node['uuid'], provision_state)
|
||||||
|
self._validate_provision_state(node['uuid'], target_state)
|
||||||
|
|
||||||
|
|
||||||
|
class TestNodeStatesV1_6(TestNodeStatesMixin, base.BaseBaremetalTest):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestNodeStatesV1_6, self).setUp()
|
||||||
|
self.useFixture(api_microversion_fixture.APIMicroversionFixture('1.6'))
|
||||||
|
|
||||||
|
@test.idempotent_id('6c9ce4a3-713b-4c76-91af-18c48d01f1bb')
|
||||||
|
def test_set_node_provision_state(self):
|
||||||
|
_, node = self.create_node(self.chassis['uuid'])
|
||||||
|
# Nodes appear in AVAILABLE state by default from v1.2 to v1.10
|
||||||
|
self.assertEqual('available', node['provision_state'])
|
||||||
|
# INSPECT* states have been added in v1.6
|
||||||
|
provision_states_list = [
|
||||||
|
'manage', 'inspect', 'provide', 'active', 'deleted']
|
||||||
|
target_states_list = [
|
||||||
|
'manageable', 'manageable', 'available', 'active', 'available']
|
||||||
|
for (provision_state, target_state) in zip(provision_states_list,
|
||||||
|
target_states_list):
|
||||||
|
self.client.set_node_provision_state(node['uuid'], provision_state)
|
||||||
|
self._validate_provision_state(node['uuid'], target_state)
|
||||||
|
|
||||||
|
|
||||||
|
class TestNodeStatesV1_11(TestNodeStatesMixin, base.BaseBaremetalTest):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestNodeStatesV1_11, self).setUp()
|
||||||
|
self.useFixture(
|
||||||
|
api_microversion_fixture.APIMicroversionFixture('1.11')
|
||||||
|
)
|
||||||
|
|
||||||
|
@test.idempotent_id('31f53828-b83d-40c7-98e5-843e28a1b6b9')
|
||||||
|
def test_set_node_provision_state(self):
|
||||||
|
_, node = self.create_node(self.chassis['uuid'])
|
||||||
|
# Nodes appear in ENROLL state by default from v1.11
|
||||||
|
self.assertEqual('enroll', node['provision_state'])
|
||||||
|
provision_states_list = [
|
||||||
|
'manage', 'inspect', 'provide', 'active', 'deleted']
|
||||||
|
target_states_list = [
|
||||||
|
'manageable', 'manageable', 'available', 'active', 'available']
|
||||||
|
for (provision_state, target_state) in zip(provision_states_list,
|
||||||
|
target_states_list):
|
||||||
|
self.client.set_node_provision_state(node['uuid'], provision_state)
|
||||||
|
self._validate_provision_state(node['uuid'], target_state)
|
||||||
|
|
||||||
|
|
||||||
|
class TestNodeStatesV1_12(TestNodeStatesMixin, base.BaseBaremetalTest):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestNodeStatesV1_12, self).setUp()
|
||||||
|
self.useFixture(
|
||||||
|
api_microversion_fixture.APIMicroversionFixture('1.12')
|
||||||
|
)
|
||||||
|
|
||||||
|
@test.idempotent_id('4427b1ca-8e79-4139-83d6-77dfac03e61e')
|
||||||
|
def test_set_node_raid_config(self):
|
||||||
|
_, node = self.create_node(self.chassis['uuid'])
|
||||||
|
target_raid_config = {'logical_disks': [{'size_gb': 100,
|
||||||
|
'raid_level': '1'}]}
|
||||||
|
self.client.set_node_raid_config(node['uuid'], target_raid_config)
|
||||||
|
_, ret = self.client.show_node(node['uuid'])
|
||||||
|
self.assertEqual(target_raid_config, ret['target_raid_config'])
|
||||||
|
Loading…
Reference in New Issue
Block a user