Add tests for Cinder user messages v3 API
This commit adds basic tests for the list/show/delete v3 APIs for Cinder's new user messages. Depends-On: Id8a4a700c1159be24b15056f401a2ea77804d0a0 Depends-On: I97caa6bfababf7d1cc714296ae66f77d22bf24ab Depends-On: I80c6a0c46c667291c6f7fe2a036717504c110314 Change-Id: I3d9b3fe288333721bf3b2c6c988949f2f253bfcc
This commit is contained in:
30
tempest/api/volume/api_microversion_fixture.py
Normal file
30
tempest/api/volume/api_microversion_fixture.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#
|
||||||
|
# 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 tempest.services.volume.base import base_v3_client
|
||||||
|
|
||||||
|
|
||||||
|
class APIMicroversionFixture(fixtures.Fixture):
|
||||||
|
|
||||||
|
def __init__(self, volume_microversion):
|
||||||
|
self.volume_microversion = volume_microversion
|
||||||
|
|
||||||
|
def _setUp(self):
|
||||||
|
super(APIMicroversionFixture, self)._setUp()
|
||||||
|
base_v3_client.VOLUME_MICROVERSION = self.volume_microversion
|
||||||
|
self.addCleanup(self._reset_volume_microversion)
|
||||||
|
|
||||||
|
def _reset_volume_microversion(self):
|
||||||
|
base_v3_client.VOLUME_MICROVERSION = None
|
||||||
@@ -45,6 +45,10 @@ class BaseVolumeTest(tempest.test.BaseTestCase):
|
|||||||
if not CONF.volume_feature_enabled.api_v2:
|
if not CONF.volume_feature_enabled.api_v2:
|
||||||
msg = "Volume API v2 is disabled"
|
msg = "Volume API v2 is disabled"
|
||||||
raise cls.skipException(msg)
|
raise cls.skipException(msg)
|
||||||
|
elif cls._api_version == 3:
|
||||||
|
if not CONF.volume_feature_enabled.api_v3:
|
||||||
|
msg = "Volume API v3 is disabled"
|
||||||
|
raise cls.skipException(msg)
|
||||||
else:
|
else:
|
||||||
msg = ("Invalid Cinder API version (%s)" % cls._api_version)
|
msg = ("Invalid Cinder API version (%s)" % cls._api_version)
|
||||||
raise exceptions.InvalidConfiguration(message=msg)
|
raise exceptions.InvalidConfiguration(message=msg)
|
||||||
|
|||||||
0
tempest/api/volume/v3/__init__.py
Normal file
0
tempest/api/volume/v3/__init__.py
Normal file
0
tempest/api/volume/v3/admin/__init__.py
Normal file
0
tempest/api/volume/v3/admin/__init__.py
Normal file
98
tempest/api/volume/v3/admin/test_user_messages.py
Normal file
98
tempest/api/volume/v3/admin/test_user_messages.py
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
# Copyright 2016 Andrew Kerr
|
||||||
|
# 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 tempest.api.volume.v3 import base
|
||||||
|
from tempest.common.utils import data_utils
|
||||||
|
from tempest.common import waiters
|
||||||
|
from tempest import exceptions
|
||||||
|
from tempest import test
|
||||||
|
|
||||||
|
MESSAGE_KEYS = [
|
||||||
|
'created_at',
|
||||||
|
'event_id',
|
||||||
|
'guaranteed_until',
|
||||||
|
'id',
|
||||||
|
'message_level',
|
||||||
|
'request_id',
|
||||||
|
'resource_type',
|
||||||
|
'resource_uuid',
|
||||||
|
'user_message',
|
||||||
|
'links']
|
||||||
|
|
||||||
|
|
||||||
|
class UserMessagesTest(base.VolumesV3AdminTest):
|
||||||
|
min_microversion = '3.3'
|
||||||
|
max_microversion = 'latest'
|
||||||
|
|
||||||
|
def _delete_volume(self, volume_id):
|
||||||
|
self.volumes_client.delete_volume(volume_id)
|
||||||
|
self.volumes_client.wait_for_resource_deletion(volume_id)
|
||||||
|
|
||||||
|
def _create_user_message(self):
|
||||||
|
"""Trigger a 'no valid host' situation to generate a message."""
|
||||||
|
bad_protocol = data_utils.rand_name('storage_protocol')
|
||||||
|
bad_vendor = data_utils.rand_name('vendor_name')
|
||||||
|
extra_specs = {'storage_protocol': bad_protocol,
|
||||||
|
'vendor_name': bad_vendor}
|
||||||
|
vol_type_name = data_utils.rand_name('volume-type')
|
||||||
|
bogus_type = self.admin_volume_types_client.create_volume_type(
|
||||||
|
name=vol_type_name,
|
||||||
|
extra_specs=extra_specs)['volume_type']
|
||||||
|
self.addCleanup(self.admin_volume_types_client.delete_volume_type,
|
||||||
|
bogus_type['id'])
|
||||||
|
params = {'volume_type': bogus_type['id']}
|
||||||
|
volume = self.volumes_client.create_volume(**params)['volume']
|
||||||
|
self.addCleanup(self._delete_volume, volume['id'])
|
||||||
|
try:
|
||||||
|
waiters.wait_for_volume_status(self.volumes_client, volume['id'],
|
||||||
|
'error')
|
||||||
|
except exceptions.VolumeBuildErrorException:
|
||||||
|
# Error state is expected and desired
|
||||||
|
pass
|
||||||
|
messages = self.messages_client.list_messages()['messages']
|
||||||
|
message_id = None
|
||||||
|
for message in messages:
|
||||||
|
if message['resource_uuid'] == volume['id']:
|
||||||
|
message_id = message['id']
|
||||||
|
break
|
||||||
|
self.assertIsNotNone(message_id, 'No user message generated for '
|
||||||
|
'volume %s' % volume['id'])
|
||||||
|
return message_id
|
||||||
|
|
||||||
|
@test.idempotent_id('50f29e6e-f363-42e1-8ad1-f67ae7fd4d5a')
|
||||||
|
def test_list_messages(self):
|
||||||
|
self._create_user_message()
|
||||||
|
messages = self.messages_client.list_messages()['messages']
|
||||||
|
self.assertIsInstance(messages, list)
|
||||||
|
for message in messages:
|
||||||
|
for key in MESSAGE_KEYS:
|
||||||
|
self.assertIn(key, message.keys(),
|
||||||
|
'Missing expected key %s' % key)
|
||||||
|
|
||||||
|
@test.idempotent_id('55a4a61e-c7b2-4ba0-a05d-b914bdef3070')
|
||||||
|
def test_show_message(self):
|
||||||
|
message_id = self._create_user_message()
|
||||||
|
self.addCleanup(self.messages_client.delete_message, message_id)
|
||||||
|
|
||||||
|
message = self.messages_client.show_message(message_id)['message']
|
||||||
|
|
||||||
|
for key in MESSAGE_KEYS:
|
||||||
|
self.assertIn(key, message.keys(), 'Missing expected key %s' % key)
|
||||||
|
|
||||||
|
@test.idempotent_id('c6eb6901-cdcc-490f-b735-4fe251842aed')
|
||||||
|
def test_delete_message(self):
|
||||||
|
message_id = self._create_user_message()
|
||||||
|
self.messages_client.delete_message(message_id)
|
||||||
|
self.messages_client.wait_for_resource_deletion(message_id)
|
||||||
64
tempest/api/volume/v3/base.py
Normal file
64
tempest/api/volume/v3/base.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# Copyright 2016 Andrew Kerr
|
||||||
|
# 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 tempest.api.volume import api_microversion_fixture
|
||||||
|
from tempest.api.volume import base
|
||||||
|
from tempest import config
|
||||||
|
from tempest.lib.common import api_version_utils
|
||||||
|
|
||||||
|
CONF = config.CONF
|
||||||
|
|
||||||
|
|
||||||
|
class VolumesV3Test(api_version_utils.BaseMicroversionTest,
|
||||||
|
base.BaseVolumeTest):
|
||||||
|
"""Base test case class for all v3 Cinder API tests."""
|
||||||
|
|
||||||
|
_api_version = 3
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def skip_checks(cls):
|
||||||
|
super(VolumesV3Test, cls).skip_checks()
|
||||||
|
api_version_utils.check_skip_with_microversion(
|
||||||
|
cls.min_microversion, cls.max_microversion,
|
||||||
|
CONF.volume.min_microversion, CONF.volume.max_microversion)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resource_setup(cls):
|
||||||
|
super(VolumesV3Test, cls).resource_setup()
|
||||||
|
cls.request_microversion = (
|
||||||
|
api_version_utils.select_request_microversion(
|
||||||
|
cls.min_microversion,
|
||||||
|
CONF.volume.min_microversion))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setup_clients(cls):
|
||||||
|
super(VolumesV3Test, cls).setup_clients()
|
||||||
|
cls.messages_client = cls.os.volume_messages_client
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(VolumesV3Test, self).setUp()
|
||||||
|
self.useFixture(api_microversion_fixture.APIMicroversionFixture(
|
||||||
|
self.request_microversion))
|
||||||
|
|
||||||
|
|
||||||
|
class VolumesV3AdminTest(VolumesV3Test):
|
||||||
|
"""Base test case class for all v3 Volume Admin API tests."""
|
||||||
|
|
||||||
|
credentials = ['primary', 'admin']
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setup_clients(cls):
|
||||||
|
super(VolumesV3AdminTest, cls).setup_clients()
|
||||||
|
cls.admin_messages_client = cls.os_adm.volume_messages_client
|
||||||
|
cls.admin_volume_types_client = cls.os_adm.volume_types_v2_client
|
||||||
@@ -183,6 +183,7 @@ from tempest.services.volume.v2.json.snapshots_client import \
|
|||||||
SnapshotsClient as SnapshotsV2Client
|
SnapshotsClient as SnapshotsV2Client
|
||||||
from tempest.services.volume.v2.json.volumes_client import \
|
from tempest.services.volume.v2.json.volumes_client import \
|
||||||
VolumesClient as VolumesV2Client
|
VolumesClient as VolumesV2Client
|
||||||
|
from tempest.services.volume.v3.json.messages_client import MessagesClient
|
||||||
|
|
||||||
CONF = config.CONF
|
CONF = config.CONF
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@@ -508,6 +509,8 @@ class Manager(manager.Manager):
|
|||||||
self.volumes_v2_client = VolumesV2Client(
|
self.volumes_v2_client = VolumesV2Client(
|
||||||
self.auth_provider, default_volume_size=CONF.volume.volume_size,
|
self.auth_provider, default_volume_size=CONF.volume.volume_size,
|
||||||
**params)
|
**params)
|
||||||
|
self.volume_messages_client = MessagesClient(self.auth_provider,
|
||||||
|
**params)
|
||||||
self.volume_types_client = VolumeTypesClient(self.auth_provider,
|
self.volume_types_client = VolumeTypesClient(self.auth_provider,
|
||||||
**params)
|
**params)
|
||||||
self.volume_types_v2_client = VolumeTypesV2Client(
|
self.volume_types_v2_client = VolumeTypesV2Client(
|
||||||
|
|||||||
@@ -682,6 +682,24 @@ VolumeGroup = [
|
|||||||
cfg.IntOpt('volume_size',
|
cfg.IntOpt('volume_size',
|
||||||
default=1,
|
default=1,
|
||||||
help='Default size in GB for volumes created by volumes tests'),
|
help='Default size in GB for volumes created by volumes tests'),
|
||||||
|
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 not specified, Tempest avoids tests "
|
||||||
|
"which require a microversion. Valid values are string "
|
||||||
|
"with format 'X.Y' or string 'latest'",),
|
||||||
|
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 not specified, Tempest avoids tests "
|
||||||
|
"which require a microversion. Valid values are string "
|
||||||
|
"with format 'X.Y' or string 'latest'",),
|
||||||
]
|
]
|
||||||
|
|
||||||
volume_feature_group = cfg.OptGroup(name='volume-feature-enabled',
|
volume_feature_group = cfg.OptGroup(name='volume-feature-enabled',
|
||||||
@@ -711,6 +729,9 @@ VolumeFeaturesGroup = [
|
|||||||
cfg.BoolOpt('api_v2',
|
cfg.BoolOpt('api_v2',
|
||||||
default=True,
|
default=True,
|
||||||
help="Is the v2 volume API enabled"),
|
help="Is the v2 volume API enabled"),
|
||||||
|
cfg.BoolOpt('api_v3',
|
||||||
|
default=False,
|
||||||
|
help="Is the v3 volume API enabled"),
|
||||||
cfg.BoolOpt('bootable',
|
cfg.BoolOpt('bootable',
|
||||||
default=True,
|
default=True,
|
||||||
help='Update bootable status of a volume '
|
help='Update bootable status of a volume '
|
||||||
|
|||||||
46
tempest/services/volume/base/base_v3_client.py
Normal file
46
tempest/services/volume/base/base_v3_client.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# Copyright 2016 Andrew Kerr
|
||||||
|
# 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 tempest.lib.common import api_version_utils
|
||||||
|
from tempest.lib.common import rest_client
|
||||||
|
|
||||||
|
VOLUME_MICROVERSION = None
|
||||||
|
|
||||||
|
|
||||||
|
class BaseV3Client(rest_client.RestClient):
|
||||||
|
"""Base class to handle Cinder v3 client microversion support."""
|
||||||
|
api_version = 'v3'
|
||||||
|
api_microversion_header_name = 'Openstack-Api-Version'
|
||||||
|
|
||||||
|
def get_headers(self, accept_type=None, send_type=None):
|
||||||
|
headers = super(BaseV3Client, self).get_headers(
|
||||||
|
accept_type=accept_type, send_type=send_type)
|
||||||
|
if VOLUME_MICROVERSION:
|
||||||
|
headers[self.api_microversion_header_name] = ('volume %s' %
|
||||||
|
VOLUME_MICROVERSION)
|
||||||
|
return headers
|
||||||
|
|
||||||
|
def request(self, method, url, extra_headers=False, headers=None,
|
||||||
|
body=None, chunked=False):
|
||||||
|
|
||||||
|
resp, resp_body = super(BaseV3Client, self).request(
|
||||||
|
method, url, extra_headers, headers, body, chunked)
|
||||||
|
if (VOLUME_MICROVERSION and
|
||||||
|
VOLUME_MICROVERSION != api_version_utils.LATEST_MICROVERSION):
|
||||||
|
api_version_utils.assert_version_header_matches_request(
|
||||||
|
self.api_microversion_header_name,
|
||||||
|
'volume %s' % VOLUME_MICROVERSION,
|
||||||
|
resp)
|
||||||
|
return resp, resp_body
|
||||||
0
tempest/services/volume/v3/__init__.py
Normal file
0
tempest/services/volume/v3/__init__.py
Normal file
0
tempest/services/volume/v3/json/__init__.py
Normal file
0
tempest/services/volume/v3/json/__init__.py
Normal file
59
tempest/services/volume/v3/json/messages_client.py
Normal file
59
tempest/services/volume/v3/json/messages_client.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# Copyright 2016 Andrew Kerr
|
||||||
|
# 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_serialization import jsonutils as json
|
||||||
|
|
||||||
|
from tempest.lib.common import rest_client
|
||||||
|
from tempest.lib import exceptions as lib_exc
|
||||||
|
from tempest.services.volume.base import base_v3_client
|
||||||
|
|
||||||
|
|
||||||
|
class MessagesClient(base_v3_client.BaseV3Client):
|
||||||
|
"""Client class to send user messages API requests."""
|
||||||
|
|
||||||
|
def show_message(self, message_id):
|
||||||
|
"""Show details for a single message."""
|
||||||
|
url = 'messages/%s' % str(message_id)
|
||||||
|
resp, body = self.get(url)
|
||||||
|
body = json.loads(body)
|
||||||
|
self.expected_success(200, resp.status)
|
||||||
|
return rest_client.ResponseBody(resp, body)
|
||||||
|
|
||||||
|
def list_messages(self):
|
||||||
|
"""List all messages."""
|
||||||
|
url = 'messages'
|
||||||
|
resp, body = self.get(url)
|
||||||
|
body = json.loads(body)
|
||||||
|
self.expected_success(200, resp.status)
|
||||||
|
return rest_client.ResponseBody(resp, body)
|
||||||
|
|
||||||
|
def delete_message(self, message_id):
|
||||||
|
"""Delete a single message."""
|
||||||
|
url = 'messages/%s' % str(message_id)
|
||||||
|
resp, body = self.delete(url)
|
||||||
|
self.expected_success(204, resp.status)
|
||||||
|
return rest_client.ResponseBody(resp, body)
|
||||||
|
|
||||||
|
def is_resource_deleted(self, id):
|
||||||
|
try:
|
||||||
|
self.show_message(id)
|
||||||
|
except lib_exc.NotFound:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def resource_type(self):
|
||||||
|
"""Returns the primary type of resource this client works with."""
|
||||||
|
return 'message'
|
||||||
Reference in New Issue
Block a user