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:
Andrew Kerr
2016-04-01 16:01:34 -04:00
parent 777a307b3c
commit fcb0b68e7b
12 changed files with 325 additions and 0 deletions

View 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

View File

@@ -45,6 +45,10 @@ class BaseVolumeTest(tempest.test.BaseTestCase):
if not CONF.volume_feature_enabled.api_v2:
msg = "Volume API v2 is disabled"
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:
msg = ("Invalid Cinder API version (%s)" % cls._api_version)
raise exceptions.InvalidConfiguration(message=msg)

View File

View File

View 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)

View 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

View File

@@ -183,6 +183,7 @@ from tempest.services.volume.v2.json.snapshots_client import \
SnapshotsClient as SnapshotsV2Client
from tempest.services.volume.v2.json.volumes_client import \
VolumesClient as VolumesV2Client
from tempest.services.volume.v3.json.messages_client import MessagesClient
CONF = config.CONF
LOG = logging.getLogger(__name__)
@@ -508,6 +509,8 @@ class Manager(manager.Manager):
self.volumes_v2_client = VolumesV2Client(
self.auth_provider, default_volume_size=CONF.volume.volume_size,
**params)
self.volume_messages_client = MessagesClient(self.auth_provider,
**params)
self.volume_types_client = VolumeTypesClient(self.auth_provider,
**params)
self.volume_types_v2_client = VolumeTypesV2Client(

View File

@@ -682,6 +682,24 @@ VolumeGroup = [
cfg.IntOpt('volume_size',
default=1,
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',
@@ -711,6 +729,9 @@ VolumeFeaturesGroup = [
cfg.BoolOpt('api_v2',
default=True,
help="Is the v2 volume API enabled"),
cfg.BoolOpt('api_v3',
default=False,
help="Is the v3 volume API enabled"),
cfg.BoolOpt('bootable',
default=True,
help='Update bootable status of a volume '

View 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

View File

View 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'