Add Tests for Groups Volume APIs - Part 1

This is the 1st patch that adds the tempest tests for generic
volume groups APIs in Cinder. It adds groups and group_types clients.
It tests the following group APIs:

  * create group
  * delete group
  * show group
  * list group

Change-Id: Id1779fe5dce50b062ca0ff841a7ede72cac9dd73
This commit is contained in:
Xing Yang 2015-11-17 22:15:25 -05:00 committed by xing-yang
parent 7172548f30
commit 0ddf83ead7
10 changed files with 477 additions and 9 deletions

View File

@ -0,0 +1,6 @@
---
features:
- |
Add groups and group_types clients for the volume service as library.
Add tempest tests for create group, delete group, show group, and
list group volume APIs.

View File

@ -0,0 +1,109 @@
# Copyright (C) 2017 Dell Inc. or its subsidiaries.
# 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 base
from tempest.common import waiters
from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
CONF = config.CONF
class GroupsTest(base.BaseVolumeAdminTest):
_api_version = 3
min_microversion = '3.14'
max_microversion = 'latest'
def _delete_group(self, grp_id, delete_volumes=True):
self.admin_groups_client.delete_group(grp_id, delete_volumes)
vols = self.admin_volume_client.list_volumes(detail=True)['volumes']
for vol in vols:
if vol['group_id'] == grp_id:
self.admin_volume_client.wait_for_resource_deletion(vol['id'])
self.admin_groups_client.wait_for_resource_deletion(grp_id)
@decorators.idempotent_id('4b111d28-b73d-4908-9bd2-03dc2992e4d4')
def test_group_create_show_list_delete(self):
# Create volume type
volume_type = self.create_volume_type()
# Create group type
group_type = self.create_group_type()
# Create group
grp1_name = data_utils.rand_name('Group1')
grp1 = self.admin_groups_client.create_group(
group_type=group_type['id'],
volume_types=[volume_type['id']],
name=grp1_name)['group']
waiters.wait_for_volume_resource_status(
self.admin_groups_client, grp1['id'], 'available')
grp1_id = grp1['id']
grp2_name = data_utils.rand_name('Group2')
grp2 = self.admin_groups_client.create_group(
group_type=group_type['id'],
volume_types=[volume_type['id']],
name=grp2_name)['group']
waiters.wait_for_volume_resource_status(
self.admin_groups_client, grp2['id'], 'available')
grp2_id = grp2['id']
# Create volume
vol1_name = data_utils.rand_name("volume")
params = {'name': vol1_name,
'volume_type': volume_type['id'],
'group_id': grp1['id'],
'size': CONF.volume.volume_size}
vol1 = self.admin_volume_client.create_volume(**params)['volume']
self.assertEqual(grp1['id'], vol1['group_id'])
waiters.wait_for_volume_resource_status(
self.admin_volume_client, vol1['id'], 'available')
vol1_id = vol1['id']
# Get a given group
grp1 = self.admin_groups_client.show_group(grp1['id'])['group']
self.assertEqual(grp1_name, grp1['name'])
self.assertEqual(grp1_id, grp1['id'])
grp2 = self.admin_groups_client.show_group(grp2['id'])['group']
self.assertEqual(grp2_name, grp2['name'])
self.assertEqual(grp2_id, grp2['id'])
# Get all groups with detail
grps = self.admin_groups_client.list_groups(
detail=True)['groups']
filtered_grps = [g for g in grps if g['id'] in [grp1_id, grp2_id]]
self.assertEqual(2, len(filtered_grps))
for grp in filtered_grps:
self.assertEqual([volume_type['id']], grp['volume_types'])
self.assertEqual(group_type['id'], grp['group_type'])
vols = self.admin_volume_client.list_volumes(
detail=True)['volumes']
filtered_vols = [v for v in vols if v['id'] in [vol1_id]]
self.assertEqual(1, len(filtered_vols))
for vol in filtered_vols:
self.assertEqual(grp1_id, vol['group_id'])
# Delete group
# grp1 has a volume so delete_volumes flag is set to True by default
self._delete_group(grp1_id)
# grp2 is empty so delete_volumes flag can be set to False
self._delete_group(grp2_id, delete_volumes=False)
grps = self.admin_groups_client.list_groups(
detail=True)['groups']
self.assertEmpty(grps)

View File

@ -71,6 +71,8 @@ class BaseVolumeTest(api_version_utils.BaseMicroversionTest,
cls.snapshots_client = cls.os_primary.snapshots_v2_client cls.snapshots_client = cls.os_primary.snapshots_v2_client
cls.volumes_client = cls.os_primary.volumes_v2_client cls.volumes_client = cls.os_primary.volumes_v2_client
if cls._api_version == 3:
cls.volumes_client = cls.os_primary.volumes_v3_client
cls.backups_client = cls.os_primary.backups_v2_client cls.backups_client = cls.os_primary.backups_v2_client
cls.volumes_extension_client =\ cls.volumes_extension_client =\
cls.os_primary.volumes_v2_extension_client cls.os_primary.volumes_v2_extension_client
@ -79,9 +81,7 @@ class BaseVolumeTest(api_version_utils.BaseMicroversionTest,
cls.volume_limits_client = cls.os_primary.volume_v2_limits_client cls.volume_limits_client = cls.os_primary.volume_v2_limits_client
cls.messages_client = cls.os_primary.volume_v3_messages_client cls.messages_client = cls.os_primary.volume_v3_messages_client
cls.versions_client = cls.os_primary.volume_v3_versions_client cls.versions_client = cls.os_primary.volume_v3_versions_client
cls.groups_client = cls.os_primary.groups_v3_client
if cls._api_version == 3:
cls.volumes_client = cls.os_primary.volumes_v3_client
def setUp(self): def setUp(self):
super(BaseVolumeTest, self).setUp() super(BaseVolumeTest, self).setUp()
@ -253,6 +253,8 @@ class BaseVolumeAdminTest(BaseVolumeTest):
cls.admin_volume_types_client = cls.os_admin.volume_types_v2_client cls.admin_volume_types_client = cls.os_admin.volume_types_v2_client
cls.admin_volume_manage_client = cls.os_admin.volume_manage_v2_client cls.admin_volume_manage_client = cls.os_admin.volume_manage_v2_client
cls.admin_volume_client = cls.os_admin.volumes_v2_client cls.admin_volume_client = cls.os_admin.volumes_v2_client
if cls._api_version == 3:
cls.admin_volume_client = cls.os_admin.volumes_v3_client
cls.admin_hosts_client = cls.os_admin.volume_hosts_v2_client cls.admin_hosts_client = cls.os_admin.volume_hosts_v2_client
cls.admin_snapshot_manage_client = \ cls.admin_snapshot_manage_client = \
cls.os_admin.snapshot_manage_v2_client cls.os_admin.snapshot_manage_v2_client
@ -269,9 +271,8 @@ class BaseVolumeAdminTest(BaseVolumeTest):
cls.admin_scheduler_stats_client = \ cls.admin_scheduler_stats_client = \
cls.os_admin.volume_scheduler_stats_v2_client cls.os_admin.volume_scheduler_stats_v2_client
cls.admin_messages_client = cls.os_admin.volume_v3_messages_client cls.admin_messages_client = cls.os_admin.volume_v3_messages_client
cls.admin_groups_client = cls.os_admin.groups_v3_client
if cls._api_version == 3: cls.admin_group_types_client = cls.os_admin.group_types_v3_client
cls.admin_volume_client = cls.os_admin.volumes_v3_client
@classmethod @classmethod
def resource_setup(cls): def resource_setup(cls):
@ -305,6 +306,16 @@ class BaseVolumeAdminTest(BaseVolumeTest):
cls.volume_types.append(volume_type['id']) cls.volume_types.append(volume_type['id'])
return volume_type return volume_type
def create_group_type(self, name=None, **kwargs):
"""Create a test group-type"""
name = name or data_utils.rand_name(
self.__class__.__name__ + '-group-type')
group_type = self.admin_group_types_client.create_group_type(
name=name, **kwargs)['group_type']
self.addCleanup(self.admin_group_types_client.delete_group_type,
group_type['id'])
return group_type
@classmethod @classmethod
def clear_qos_specs(cls): def clear_qos_specs(cls):
for qos_id in cls.qos_specs: for qos_id in cls.qos_specs:

View File

@ -255,6 +255,8 @@ class Manager(clients.ServiceClients):
self.volume_v2.QuotaClassesClient() self.volume_v2.QuotaClassesClient()
self.volumes_extension_client = self.volume_v1.ExtensionsClient() self.volumes_extension_client = self.volume_v1.ExtensionsClient()
self.volumes_v2_extension_client = self.volume_v2.ExtensionsClient() self.volumes_v2_extension_client = self.volume_v2.ExtensionsClient()
self.groups_v3_client = self.volume_v3.GroupsClient()
self.group_types_v3_client = self.volume_v3.GroupTypesClient()
self.volume_availability_zone_client = \ self.volume_availability_zone_client = \
self.volume_v1.AvailabilityZoneClient() self.volume_v1.AvailabilityZoneClient()
self.volume_v2_availability_zone_client = \ self.volume_v2_availability_zone_client = \

View File

@ -186,8 +186,9 @@ def wait_for_volume_resource_status(client, resource_id, status):
resources. The function extracts the name of the desired resource from resources. The function extracts the name of the desired resource from
the client class name of the resource. the client class name of the resource.
""" """
resource_name = re.findall(r'(Volume|Snapshot|Backup)', resource_name = re.findall(
client.__class__.__name__)[0].lower() r'(Volume|Snapshot|Backup|Group)',
client.__class__.__name__)[0].lower()
show_resource = getattr(client, 'show_' + resource_name) show_resource = getattr(client, 'show_' + resource_name)
resource_status = show_resource(resource_id)[resource_name]['status'] resource_status = show_resource(resource_id)[resource_name]['status']
start = int(time.time()) start = int(time.time())

View File

@ -13,8 +13,11 @@
# the License. # the License.
from tempest.lib.services.volume.v3.base_client import BaseClient from tempest.lib.services.volume.v3.base_client import BaseClient
from tempest.lib.services.volume.v3.group_types_client import GroupTypesClient
from tempest.lib.services.volume.v3.groups_client import GroupsClient
from tempest.lib.services.volume.v3.messages_client import MessagesClient from tempest.lib.services.volume.v3.messages_client import MessagesClient
from tempest.lib.services.volume.v3.versions_client import VersionsClient from tempest.lib.services.volume.v3.versions_client import VersionsClient
from tempest.lib.services.volume.v3.volumes_client import VolumesClient from tempest.lib.services.volume.v3.volumes_client import VolumesClient
__all__ = ['MessagesClient', 'BaseClient', 'VersionsClient', 'VolumesClient'] __all__ = ['BaseClient', 'GroupsClient', 'GroupTypesClient',
'MessagesClient', 'VersionsClient', 'VolumesClient']

View File

@ -0,0 +1,47 @@
# Copyright (C) 2017 Dell Inc. or its subsidiaries.
# 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.services.volume.v3 import base_client
class GroupTypesClient(base_client.BaseClient):
"""Client class to send CRUD Volume V3 Group Types API requests"""
@property
def resource_type(self):
"""Returns the primary type of resource this client works with."""
return 'group-type'
def create_group_type(self, **kwargs):
"""Create group_type.
For a full list of available parameters, please refer to the official
API reference:
https://developer.openstack.org/api-ref/block-storage/v3/#create-group-type
"""
post_body = json.dumps({'group_type': kwargs})
resp, body = self.post('group_types', post_body)
body = json.loads(body)
self.expected_success(202, resp.status)
return rest_client.ResponseBody(resp, body)
def delete_group_type(self, group_type_id):
"""Deletes the specified group_type."""
resp, body = self.delete("group_types/%s" % group_type_id)
self.expected_success(202, resp.status)
return rest_client.ResponseBody(resp, body)

View File

@ -0,0 +1,96 @@
# Copyright (C) 2017 Dell Inc. or its subsidiaries.
# 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 six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
from tempest.lib import exceptions as lib_exc
from tempest.lib.services.volume.v3 import base_client
class GroupsClient(base_client.BaseClient):
"""Client class to send CRUD Volume Group API requests"""
def create_group(self, **kwargs):
"""Creates a group.
group_type and volume_types are required parameters in kwargs.
For a full list of available parameters, please refer to the official
API reference:
https://developer.openstack.org/api-ref/block-storage/v3/#create-group
"""
post_body = json.dumps({'group': kwargs})
resp, body = self.post('groups', post_body)
body = json.loads(body)
self.expected_success(202, resp.status)
return rest_client.ResponseBody(resp, body)
def delete_group(self, group_id, delete_volumes=True):
"""Deletes a group.
For a full list of available parameters, please refer to the official
API reference:
https://developer.openstack.org/api-ref/block-storage/v3/#delete-group
"""
post_body = {'delete-volumes': delete_volumes}
post_body = json.dumps({'delete': post_body})
resp, body = self.post('groups/%s/action' % group_id,
post_body)
self.expected_success(202, resp.status)
return rest_client.ResponseBody(resp, body)
def show_group(self, group_id):
"""Returns the details of a single group.
For a full list of available parameters, please refer to the official
API reference:
https://developer.openstack.org/api-ref/block-storage/v3/#show-group-details
"""
url = "groups/%s" % str(group_id)
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
def list_groups(self, detail=False, **params):
"""Lists information for all the tenant's groups.
For a full list of available parameters, please refer to the official
API reference:
https://developer.openstack.org/api-ref/block-storage/v3/#list-groups
https://developer.openstack.org/api-ref/block-storage/v3/#list-groups-with-details
"""
url = "groups"
if detail:
url += "/detail"
if params:
url += '?%s' % urllib.urlencode(params)
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
def is_resource_deleted(self, id):
try:
self.show_group(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 'group'

View File

@ -0,0 +1,57 @@
# Copyright (C) 2017 Dell Inc. or its subsidiaries.
#
# 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.services.volume.v3 import group_types_client
from tempest.tests.lib import fake_auth_provider
from tempest.tests.lib.services import base
class TestGroupTypesClient(base.BaseServiceTest):
FAKE_CREATE_GROUP_TYPE = {
"group_type": {
"name": "group-type-001",
"description": "Test group type 1",
"group_specs": {},
"is_public": True,
}
}
def setUp(self):
super(TestGroupTypesClient, self).setUp()
fake_auth = fake_auth_provider.FakeAuthProvider()
self.client = group_types_client.GroupTypesClient(fake_auth,
'volume',
'regionOne')
def _test_create_group_type(self, bytes_body=False):
self.check_service_client_function(
self.client.create_group_type,
'tempest.lib.common.rest_client.RestClient.post',
self.FAKE_CREATE_GROUP_TYPE,
bytes_body,
status=202)
def test_create_group_type_with_str_body(self):
self._test_create_group_type()
def test_create_group_type_with_bytes_body(self):
self._test_create_group_type(bytes_body=True)
def test_delete_group_type(self):
self.check_service_client_function(
self.client.delete_group_type,
'tempest.lib.common.rest_client.RestClient.delete',
{},
group_type_id='0e58433f-d108-4bf3-a22c-34e6b71ef86b',
status=202)

View File

@ -0,0 +1,136 @@
# Copyright (C) 2017 Dell Inc. or its subsidiaries.
#
# 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.services.volume.v3 import groups_client
from tempest.tests.lib import fake_auth_provider
from tempest.tests.lib.services import base
class TestGroupsClient(base.BaseServiceTest):
FAKE_CREATE_GROUP = {
"group": {
"name": "group-001",
"description": "Test group 1",
"group_type": "0e58433f-d108-4bf3-a22c-34e6b71ef86b",
"volume_types": ["2103099d-7cc3-4e52-a2f1-23a5284416f3"],
"availability_zone": "az1",
}
}
FAKE_INFO_GROUP = {
"group": {
"id": "0e701ab8-1bec-4b9f-b026-a7ba4af13578",
"name": "group-001",
"description": "Test group 1",
"group_type": "0e58433f-d108-4bf3-a22c-34e6b71ef86b",
"volume_types": ["2103099d-7cc3-4e52-a2f1-23a5284416f3"],
"status": "available",
"availability_zone": "az1",
"created_at": "20127-06-20T03:50:07Z"
}
}
FAKE_LIST_GROUPS = {
"groups": [
{
"id": "0e701ab8-1bec-4b9f-b026-a7ba4af13578",
"name": "group-001",
"description": "Test group 1",
"group_type": "0e58433f-d108-4bf3-a22c-34e6b71ef86b",
"volume_types": ["2103099d-7cc3-4e52-a2f1-23a5284416f3"],
"status": "available",
"availability_zone": "az1",
"created_at": "2017-06-20T03:50:07Z",
},
{
"id": "e479997c-650b-40a4-9dfe-77655818b0d2",
"name": "group-002",
"description": "Test group 2",
"group_snapshot_id": "79c9afdb-7e46-4d71-9249-1f022886963c",
"group_type": "0e58433f-d108-4bf3-a22c-34e6b71ef86b",
"volume_types": ["2103099d-7cc3-4e52-a2f1-23a5284416f3"],
"status": "available",
"availability_zone": "az1",
"created_at": "2017-06-19T01:52:47Z",
},
{
"id": "c5c4769e-213c-40a6-a568-8e797bb691d4",
"name": "group-003",
"description": "Test group 3",
"source_group_id": "e92f9dc7-0b20-492d-8ab2-3ad8fdac270e",
"group_type": "0e58433f-d108-4bf3-a22c-34e6b71ef86b",
"volume_types": ["2103099d-7cc3-4e52-a2f1-23a5284416f3"],
"status": "available",
"availability_zone": "az1",
"created_at": "2017-06-18T06:34:32Z",
}
]
}
def setUp(self):
super(TestGroupsClient, self).setUp()
fake_auth = fake_auth_provider.FakeAuthProvider()
self.client = groups_client.GroupsClient(fake_auth,
'volume',
'regionOne')
def _test_create_group(self, bytes_body=False):
self.check_service_client_function(
self.client.create_group,
'tempest.lib.common.rest_client.RestClient.post',
self.FAKE_CREATE_GROUP,
bytes_body,
status=202)
def _test_show_group(self, bytes_body=False):
self.check_service_client_function(
self.client.show_group,
'tempest.lib.common.rest_client.RestClient.get',
self.FAKE_INFO_GROUP,
bytes_body,
group_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5")
def _test_list_groups(self, bytes_body=False):
self.check_service_client_function(
self.client.list_groups,
'tempest.lib.common.rest_client.RestClient.get',
self.FAKE_LIST_GROUPS,
bytes_body,
detail=True)
def test_create_group_with_str_body(self):
self._test_create_group()
def test_create_group_with_bytes_body(self):
self._test_create_group(bytes_body=True)
def test_show_group_with_str_body(self):
self._test_show_group()
def test_show_group_with_bytes_body(self):
self._test_show_group(bytes_body=True)
def test_list_groups_with_str_body(self):
self._test_list_groups()
def test_list_groups_with_bytes_body(self):
self._test_list_groups(bytes_body=True)
def test_delete_group(self):
self.check_service_client_function(
self.client.delete_group,
'tempest.lib.common.rest_client.RestClient.post',
{},
group_id='0e701ab8-1bec-4b9f-b026-a7ba4af13578',
status=202)