Merge "Adding and removing bytes_body param from unit tests"
diff --git a/releasenotes/notes/add-volume-groups-tempest-tests-dd7b2abfe2b48427.yaml b/releasenotes/notes/add-volume-groups-tempest-tests-dd7b2abfe2b48427.yaml
new file mode 100644
index 0000000..898d366
--- /dev/null
+++ b/releasenotes/notes/add-volume-groups-tempest-tests-dd7b2abfe2b48427.yaml
@@ -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.
diff --git a/releasenotes/notes/set-cinder-api-v3-option-true-1b3e61e3129b7c00.yaml b/releasenotes/notes/set-cinder-api-v3-option-true-1b3e61e3129b7c00.yaml
new file mode 100644
index 0000000..6959ca7
--- /dev/null
+++ b/releasenotes/notes/set-cinder-api-v3-option-true-1b3e61e3129b7c00.yaml
@@ -0,0 +1,5 @@
+---
+upgrade:
+ - |
+ The volume config option 'api_v3' default is changed to
+ ``True`` because the volume v3 API is CURRENT.
diff --git a/tempest/api/compute/servers/test_device_tagging.py b/tempest/api/compute/servers/test_device_tagging.py
index 9ab508d..7ee1b02 100644
--- a/tempest/api/compute/servers/test_device_tagging.py
+++ b/tempest/api/compute/servers/test_device_tagging.py
@@ -84,10 +84,14 @@
if d['mac'] == self.net_2_200_mac:
self.assertEqual(d['tags'], ['net-2-200'])
- found_devices = [d['tags'][0] for d in md_dict['devices']]
- self.assertItemsEqual(found_devices, ['port-1', 'port-2', 'net-1',
- 'net-2-100', 'net-2-200',
- 'boot', 'other'])
+ # A hypervisor may present multiple paths to a tagged disk, so
+ # there may be duplicated tags in the metadata, use set() to
+ # remove duplicated tags.
+ found_devices = [d['tags'][0] for d in md_dict['devices']]
+ self.assertEqual(set(found_devices), set(['port-1', 'port-2',
+ 'net-1', 'net-2-100',
+ 'net-2-200', 'boot',
+ 'other']))
@decorators.idempotent_id('a2e65a6c-66f1-4442-aaa8-498c31778d96')
@test.services('network', 'volume', 'image')
diff --git a/tempest/api/volume/admin/test_groups.py b/tempest/api/volume/admin/test_groups.py
new file mode 100644
index 0000000..8609bdb
--- /dev/null
+++ b/tempest/api/volume/admin/test_groups.py
@@ -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)
diff --git a/tempest/api/volume/admin/test_snapshot_manage.py b/tempest/api/volume/admin/test_snapshot_manage.py
index 6c09042..9ff7160 100644
--- a/tempest/api/volume/admin/test_snapshot_manage.py
+++ b/tempest/api/volume/admin/test_snapshot_manage.py
@@ -18,6 +18,7 @@
from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
+from tempest.lib import exceptions
CONF = config.CONF
@@ -38,8 +39,9 @@
raise cls.skipException("Manage snapshot tests are disabled")
if len(CONF.volume.manage_snapshot_ref) != 2:
- raise cls.skipException("Manage snapshot ref is not correctly "
- "configured")
+ msg = ("Manage snapshot ref is not correctly configured, "
+ "it should be a list of two elements")
+ raise exceptions.InvalidConfiguration(msg)
@decorators.idempotent_id('0132f42d-0147-4b45-8501-cc504bbf7810')
def test_unmanage_manage_snapshot(self):
diff --git a/tempest/api/volume/admin/test_volume_manage.py b/tempest/api/volume/admin/test_volume_manage.py
index a039085..4b352e0 100644
--- a/tempest/api/volume/admin/test_volume_manage.py
+++ b/tempest/api/volume/admin/test_volume_manage.py
@@ -18,6 +18,7 @@
from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
+from tempest.lib import exceptions
CONF = config.CONF
@@ -32,8 +33,9 @@
raise cls.skipException("Manage volume tests are disabled")
if len(CONF.volume.manage_volume_ref) != 2:
- raise cls.skipException("Manage volume ref is not correctly "
- "configured")
+ msg = ("Manage volume ref is not correctly configured, "
+ "it should be a list of two elements")
+ raise exceptions.InvalidConfiguration(msg)
@decorators.idempotent_id('70076c71-0ce1-4208-a8ff-36a66e65cc1e')
def test_unmanage_manage_volume(self):
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 8d66156..394c453 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -71,6 +71,8 @@
cls.snapshots_client = cls.os_primary.snapshots_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.volumes_extension_client =\
cls.os_primary.volumes_v2_extension_client
@@ -79,9 +81,7 @@
cls.volume_limits_client = cls.os_primary.volume_v2_limits_client
cls.messages_client = cls.os_primary.volume_v3_messages_client
cls.versions_client = cls.os_primary.volume_v3_versions_client
-
- if cls._api_version == 3:
- cls.volumes_client = cls.os_primary.volumes_v3_client
+ cls.groups_client = cls.os_primary.groups_v3_client
def setUp(self):
super(BaseVolumeTest, self).setUp()
@@ -253,6 +253,8 @@
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_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_snapshot_manage_client = \
cls.os_admin.snapshot_manage_v2_client
@@ -269,9 +271,8 @@
cls.admin_scheduler_stats_client = \
cls.os_admin.volume_scheduler_stats_v2_client
cls.admin_messages_client = cls.os_admin.volume_v3_messages_client
-
- if cls._api_version == 3:
- cls.admin_volume_client = cls.os_admin.volumes_v3_client
+ cls.admin_groups_client = cls.os_admin.groups_v3_client
+ cls.admin_group_types_client = cls.os_admin.group_types_v3_client
@classmethod
def resource_setup(cls):
@@ -305,6 +306,16 @@
cls.volume_types.append(volume_type['id'])
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
def clear_qos_specs(cls):
for qos_id in cls.qos_specs:
diff --git a/tempest/clients.py b/tempest/clients.py
index 8ad6e92..d29bef9 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -255,6 +255,8 @@
self.volume_v2.QuotaClassesClient()
self.volumes_extension_client = self.volume_v1.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_v1.AvailabilityZoneClient()
self.volume_v2_availability_zone_client = \
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index 9110c4a..9f467fe 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -204,6 +204,19 @@
except Exception:
LOG.exception('Deleting server %s failed',
server['id'])
+ for server in servers:
+ # NOTE(artom) If the servers were booted with volumes
+ # and with delete_on_termination=False we need to wait
+ # for the servers to go away before proceeding with
+ # cleanup, otherwise we'll attempt to delete the
+ # volumes while they're still attached to servers that
+ # are in the process of being deleted.
+ try:
+ waiters.wait_for_server_termination(
+ clients.servers_client, server['id'])
+ except Exception:
+ LOG.exception('Server %s failed to delete in time',
+ server['id'])
return body, servers
@@ -252,16 +265,34 @@
def __init__(self, client_socket, url):
"""Contructor for the WebSocket wrapper to the socket."""
self._socket = client_socket
+ # cached stream for early frames.
+ self.cached_stream = b''
# Upgrade the HTTP connection to a WebSocket
self._upgrade(url)
+ def _recv(self, recv_size):
+ """Wrapper to receive data from the cached stream or socket."""
+ if recv_size <= 0:
+ return None
+
+ data_from_cached = b''
+ data_from_socket = b''
+ if len(self.cached_stream) > 0:
+ read_from_cached = min(len(self.cached_stream), recv_size)
+ data_from_cached += self.cached_stream[:read_from_cached]
+ self.cached_stream = self.cached_stream[read_from_cached:]
+ recv_size -= read_from_cached
+ if recv_size > 0:
+ data_from_socket = self._socket.recv(recv_size)
+ return data_from_cached + data_from_socket
+
def receive_frame(self):
"""Wrapper for receiving data to parse the WebSocket frame format"""
# We need to loop until we either get some bytes back in the frame
# or no data was received (meaning the socket was closed). This is
# done to handle the case where we get back some empty frames
while True:
- header = self._socket.recv(2)
+ header = self._recv(2)
# If we didn't receive any data, just return None
if not header:
return None
@@ -270,7 +301,7 @@
# that only the 2nd byte contains the length, and since the
# server doesn't do masking, we can just read the data length
if ord_func(header[1]) & 127 > 0:
- return self._socket.recv(ord_func(header[1]) & 127)
+ return self._recv(ord_func(header[1]) & 127)
def send_frame(self, data):
"""Wrapper for sending data to add in the WebSocket frame format."""
@@ -318,6 +349,15 @@
self._socket.sendall(reqdata.encode('utf8'))
self.response = data = self._socket.recv(4096)
# Loop through & concatenate all of the data in the response body
- while data and self.response.find(b'\r\n\r\n') < 0:
+ end_loc = self.response.find(b'\r\n\r\n')
+ while data and end_loc < 0:
data = self._socket.recv(4096)
self.response += data
+ end_loc = self.response.find(b'\r\n\r\n')
+
+ if len(self.response) > end_loc + 4:
+ # In case some frames (e.g. the first RFP negotiation) have
+ # arrived, cache it for next reading.
+ self.cached_stream = self.response[end_loc + 4:]
+ # ensure response ends with '\r\n\r\n'.
+ self.response = self.response[:end_loc + 4]
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index 9c83c99..93e6fbf 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -186,8 +186,9 @@
resources. The function extracts the name of the desired resource from
the client class name of the resource.
"""
- resource_name = re.findall(r'(Volume|Snapshot|Backup)',
- client.__class__.__name__)[0].lower()
+ resource_name = re.findall(
+ r'(Volume|Snapshot|Backup|Group)',
+ client.__class__.__name__)[0].lower()
show_resource = getattr(client, 'show_' + resource_name)
resource_status = show_resource(resource_id)[resource_name]['status']
start = int(time.time())
diff --git a/tempest/config.py b/tempest/config.py
index fbe0a1b..7b96281 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -832,7 +832,7 @@
default=True,
help="Is the v2 volume API enabled"),
cfg.BoolOpt('api_v3',
- default=False,
+ default=True,
help="Is the v3 volume API enabled")
]
diff --git a/tempest/lib/services/network/base.py b/tempest/lib/services/network/base.py
index b6f9c91..fe8b244 100644
--- a/tempest/lib/services/network/base.py
+++ b/tempest/lib/services/network/base.py
@@ -54,7 +54,8 @@
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
- def create_resource(self, uri, post_data, expect_empty_body=False):
+ def create_resource(self, uri, post_data, expect_empty_body=False,
+ expect_response_code=201):
req_uri = self.uri_prefix + uri
req_post_data = json.dumps(post_data)
resp, body = self.post(req_uri, req_post_data)
@@ -65,10 +66,11 @@
body = json.loads(body)
else:
body = None
- self.expected_success(201, resp.status)
+ self.expected_success(expect_response_code, resp.status)
return rest_client.ResponseBody(resp, body)
- def update_resource(self, uri, post_data, expect_empty_body=False):
+ def update_resource(self, uri, post_data, expect_empty_body=False,
+ expect_response_code=200):
req_uri = self.uri_prefix + uri
req_post_data = json.dumps(post_data)
resp, body = self.put(req_uri, req_post_data)
@@ -79,5 +81,5 @@
body = json.loads(body)
else:
body = None
- self.expected_success(200, resp.status)
+ self.expected_success(expect_response_code, resp.status)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/network/tags_client.py b/tempest/lib/services/network/tags_client.py
index 20c2c11..5d49a79 100644
--- a/tempest/lib/services/network/tags_client.py
+++ b/tempest/lib/services/network/tags_client.py
@@ -27,14 +27,10 @@
For more information, please refer to the official API reference:
http://developer.openstack.org/api-ref/networking/v2/index.html#add-a-tag
"""
- # NOTE(felipemonteiro): Cannot use ``update_resource`` method because
- # this API requires self.put but returns 201 instead of 200 expected
- # by ``update_resource``.
- uri = '%s/%s/%s/tags/%s' % (
- self.uri_prefix, resource_type, resource_id, tag)
- resp, _ = self.put(uri, json.dumps({}))
- self.expected_success(201, resp.status)
- return rest_client.ResponseBody(resp)
+ uri = '/%s/%s/tags/%s' % (resource_type, resource_id, tag)
+ return self.update_resource(
+ uri, json.dumps({}), expect_response_code=201,
+ expect_empty_body=True)
def check_tag_existence(self, resource_type, resource_id, tag):
"""Confirm that a given tag is set on the resource.
diff --git a/tempest/lib/services/volume/v2/encryption_types_client.py b/tempest/lib/services/volume/v2/encryption_types_client.py
index eeff537..20f3356 100755
--- a/tempest/lib/services/volume/v2/encryption_types_client.py
+++ b/tempest/lib/services/volume/v2/encryption_types_client.py
@@ -50,9 +50,9 @@
def create_encryption_type(self, volume_type_id, **kwargs):
"""Create encryption type.
- TODO: Current api-site doesn't contain this API description.
- After fixing the api-site, we need to fix here also for putting
- the link to api-site.
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v2/#create-an-encryption-type-for-v2
"""
url = "/types/%s/encryption" % volume_type_id
post_body = json.dumps({'encryption': kwargs})
@@ -71,9 +71,9 @@
def update_encryption_type(self, volume_type_id, **kwargs):
"""Update an encryption type for an existing volume type.
- TODO: Current api-site doesn't contain this API description.
- After fixing the api-site, we need to fix here also for putting
- the link to api-site.
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v2/#update-an-encryption-type-for-v2
"""
url = "/types/%s/encryption/provider" % volume_type_id
put_body = json.dumps({'encryption': kwargs})
diff --git a/tempest/lib/services/volume/v2/hosts_client.py b/tempest/lib/services/volume/v2/hosts_client.py
index 8fcf4d0..f44bda3 100644
--- a/tempest/lib/services/volume/v2/hosts_client.py
+++ b/tempest/lib/services/volume/v2/hosts_client.py
@@ -24,8 +24,12 @@
api_version = "v2"
def list_hosts(self, **params):
- """Lists all hosts."""
+ """Lists all hosts.
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v2/#list-all-hosts
+ """
url = 'os-hosts'
if params:
url += '?%s' % urllib.urlencode(params)
diff --git a/tempest/lib/services/volume/v2/snapshots_client.py b/tempest/lib/services/volume/v2/snapshots_client.py
index 983ed89..5f4e7de 100644
--- a/tempest/lib/services/volume/v2/snapshots_client.py
+++ b/tempest/lib/services/volume/v2/snapshots_client.py
@@ -124,7 +124,12 @@
return rest_client.ResponseBody(resp, body)
def create_snapshot_metadata(self, snapshot_id, metadata):
- """Create metadata for the snapshot."""
+ """Create metadata for the snapshot.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#create-snapshot-metadata
+ """
put_body = json.dumps({'metadata': metadata})
url = "snapshots/%s/metadata" % snapshot_id
resp, body = self.post(url, put_body)
diff --git a/tempest/lib/services/volume/v2/volumes_client.py b/tempest/lib/services/volume/v2/volumes_client.py
index f4e7c6a..86e3836 100644
--- a/tempest/lib/services/volume/v2/volumes_client.py
+++ b/tempest/lib/services/volume/v2/volumes_client.py
@@ -72,6 +72,10 @@
"""List all the volumes created.
Params can be a string (must be urlencoded) or a dictionary.
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#list-volumes-with-details
+ http://developer.openstack.org/api-ref/block-storage/v2/#list-volumes
"""
url = 'volumes'
if detail:
@@ -155,7 +159,12 @@
return rest_client.ResponseBody(resp, body)
def set_bootable_volume(self, volume_id, **kwargs):
- """set a bootable flag for a volume - true or false."""
+ """Set a bootable flag for a volume - true or false.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#update-volume-bootable-status
+ """
post_body = json.dumps({'os-set_bootable': kwargs})
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
@@ -239,7 +248,12 @@
return rest_client.ResponseBody(resp, body)
def create_volume_metadata(self, volume_id, metadata):
- """Create metadata for the volume."""
+ """Create metadata for the volume.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#create-volume-metadata
+ """
put_body = json.dumps({'metadata': metadata})
url = "volumes/%s/metadata" % volume_id
resp, body = self.post(url, put_body)
@@ -256,7 +270,12 @@
return rest_client.ResponseBody(resp, body)
def update_volume_metadata(self, volume_id, metadata):
- """Update metadata for the volume."""
+ """Update metadata for the volume.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#update-volume-metadata
+ """
put_body = json.dumps({'metadata': metadata})
url = "volumes/%s/metadata" % volume_id
resp, body = self.put(url, put_body)
@@ -281,7 +300,12 @@
return rest_client.ResponseBody(resp, body)
def retype_volume(self, volume_id, **kwargs):
- """Updates volume with new volume type."""
+ """Updates volume with new volume type.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v2/#retype-volume
+ """
post_body = json.dumps({'os-retype': kwargs})
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
self.expected_success(202, resp.status)
diff --git a/tempest/lib/services/volume/v3/__init__.py b/tempest/lib/services/volume/v3/__init__.py
index 07ae917..a351d61 100644
--- a/tempest/lib/services/volume/v3/__init__.py
+++ b/tempest/lib/services/volume/v3/__init__.py
@@ -13,8 +13,11 @@
# the License.
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.versions_client import VersionsClient
from tempest.lib.services.volume.v3.volumes_client import VolumesClient
-__all__ = ['MessagesClient', 'BaseClient', 'VersionsClient', 'VolumesClient']
+__all__ = ['BaseClient', 'GroupsClient', 'GroupTypesClient',
+ 'MessagesClient', 'VersionsClient', 'VolumesClient']
diff --git a/tempest/lib/services/volume/v3/group_types_client.py b/tempest/lib/services/volume/v3/group_types_client.py
new file mode 100644
index 0000000..390d44d
--- /dev/null
+++ b/tempest/lib/services/volume/v3/group_types_client.py
@@ -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)
diff --git a/tempest/lib/services/volume/v3/groups_client.py b/tempest/lib/services/volume/v3/groups_client.py
new file mode 100644
index 0000000..c06997a
--- /dev/null
+++ b/tempest/lib/services/volume/v3/groups_client.py
@@ -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'
diff --git a/tempest/tests/cmd/test_verify_tempest_config.py b/tempest/tests/cmd/test_verify_tempest_config.py
index 640dcd4..b0e74fb 100644
--- a/tempest/tests/cmd/test_verify_tempest_config.py
+++ b/tempest/tests/cmd/test_verify_tempest_config.py
@@ -199,7 +199,9 @@
with mock.patch.object(verify_tempest_config,
'print_and_or_update') as print_mock:
verify_tempest_config.verify_cinder_api_versions(fake_os, True)
- print_mock.assert_not_called()
+ print_mock.assert_any_call('api_v3', 'volume-feature-enabled',
+ False, True)
+ self.assertEqual(1, print_mock.call_count)
@mock.patch('tempest.lib.common.http.ClosingHttp.request')
def test_verify_cinder_api_versions_no_v2(self, mock_request):
@@ -215,9 +217,7 @@
verify_tempest_config.verify_cinder_api_versions(fake_os, True)
print_mock.assert_any_call('api_v2', 'volume-feature-enabled',
False, True)
- print_mock.assert_any_call('api_v3', 'volume-feature-enabled',
- True, True)
- self.assertEqual(2, print_mock.call_count)
+ self.assertEqual(1, print_mock.call_count)
@mock.patch('tempest.lib.common.http.ClosingHttp.request')
def test_verify_cinder_api_versions_no_v1(self, mock_request):
@@ -231,9 +231,7 @@
with mock.patch.object(verify_tempest_config,
'print_and_or_update') as print_mock:
verify_tempest_config.verify_cinder_api_versions(fake_os, True)
- print_mock.assert_any_call('api_v3', 'volume-feature-enabled',
- True, True)
- self.assertEqual(1, print_mock.call_count)
+ print_mock.assert_not_called()
def test_verify_glance_version_no_v2_with_v1_1(self):
def fake_get_versions():
diff --git a/tempest/tests/common/test_compute.py b/tempest/tests/common/test_compute.py
new file mode 100644
index 0000000..c108be9
--- /dev/null
+++ b/tempest/tests/common/test_compute.py
@@ -0,0 +1,106 @@
+# Copyright 2017 Citrix Systems
+# 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 six.moves.urllib import parse as urlparse
+
+import mock
+
+from tempest.common import compute
+from tempest.tests import base
+
+
+class TestCompute(base.TestCase):
+ def setUp(self):
+ super(TestCompute, self).setUp()
+ self.client_sock = mock.Mock()
+ self.url = urlparse.urlparse("http://www.fake.com:80")
+
+ def test_rfp_frame_not_cached(self):
+ # rfp negotiation frame arrived separately after upgrade
+ # response, so it's not cached.
+ RFP_VERSION = b'RFB.003.003\x0a'
+ rfp_frame_header = b'\x82\x0c'
+
+ self.client_sock.recv.side_effect = [
+ b'fake response start\r\n',
+ b'fake response end\r\n\r\n',
+ rfp_frame_header,
+ RFP_VERSION]
+ expect_response = b'fake response start\r\nfake response end\r\n\r\n'
+
+ webSocket = compute._WebSocket(self.client_sock, self.url)
+
+ self.assertEqual(webSocket.response, expect_response)
+ # no cache
+ self.assertEqual(webSocket.cached_stream, b'')
+ self.client_sock.recv.assert_has_calls([mock.call(4096),
+ mock.call(4096)])
+
+ self.client_sock.recv.reset_mock()
+ recv_version = webSocket.receive_frame()
+
+ self.assertEqual(recv_version, RFP_VERSION)
+ self.client_sock.recv.assert_has_calls([mock.call(2),
+ mock.call(12)])
+
+ def test_rfp_frame_fully_cached(self):
+ RFP_VERSION = b'RFB.003.003\x0a'
+ rfp_version_frame = b'\x82\x0c%s' % RFP_VERSION
+
+ self.client_sock.recv.side_effect = [
+ b'fake response start\r\n',
+ b'fake response end\r\n\r\n%s' % rfp_version_frame]
+ expect_response = b'fake response start\r\nfake response end\r\n\r\n'
+ webSocket = compute._WebSocket(self.client_sock, self.url)
+
+ self.client_sock.recv.assert_has_calls([mock.call(4096),
+ mock.call(4096)])
+ self.assertEqual(webSocket.response, expect_response)
+ self.assertEqual(webSocket.cached_stream, rfp_version_frame)
+
+ self.client_sock.recv.reset_mock()
+ recv_version = webSocket.receive_frame()
+
+ self.client_sock.recv.assert_not_called()
+ self.assertEqual(recv_version, RFP_VERSION)
+ # cached_stream should be empty in the end.
+ self.assertEqual(webSocket.cached_stream, b'')
+
+ def test_rfp_frame_partially_cached(self):
+ RFP_VERSION = b'RFB.003.003\x0a'
+ rfp_version_frame = b'\x82\x0c%s' % RFP_VERSION
+ frame_part1 = rfp_version_frame[:6]
+ frame_part2 = rfp_version_frame[6:]
+
+ self.client_sock.recv.side_effect = [
+ b'fake response start\r\n',
+ b'fake response end\r\n\r\n%s' % frame_part1,
+ frame_part2]
+ expect_response = b'fake response start\r\nfake response end\r\n\r\n'
+ webSocket = compute._WebSocket(self.client_sock, self.url)
+
+ self.client_sock.recv.assert_has_calls([mock.call(4096),
+ mock.call(4096)])
+ self.assertEqual(webSocket.response, expect_response)
+ self.assertEqual(webSocket.cached_stream, frame_part1)
+
+ self.client_sock.recv.reset_mock()
+
+ recv_version = webSocket.receive_frame()
+
+ self.client_sock.recv.assert_called_once_with(len(frame_part2))
+ self.assertEqual(recv_version, RFP_VERSION)
+ # cached_stream should be empty in the end.
+ self.assertEqual(webSocket.cached_stream, b'')
diff --git a/tempest/tests/lib/services/network/test_base_network_client.py b/tempest/tests/lib/services/network/test_base_network_client.py
new file mode 100644
index 0000000..e121cec
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_base_network_client.py
@@ -0,0 +1,96 @@
+# Copyright 2017 AT&T 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 mock
+
+from tempest.lib.services.network import base as base_network_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib import fake_http
+from tempest.tests.lib.services import base
+
+
+class TestBaseNetworkClient(base.BaseServiceTest):
+
+ def setUp(self):
+ super(TestBaseNetworkClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = base_network_client.BaseNetworkClient(
+ fake_auth, 'compute', 'regionOne')
+
+ self.mock_expected_success = mock.patch.object(
+ self.client, 'expected_success').start()
+
+ def _assert_empty(self, resp):
+ self.assertEqual([], list(resp.keys()))
+
+ @mock.patch('tempest.lib.common.rest_client.RestClient.post')
+ def test_create_resource(self, mock_post):
+ response = fake_http.fake_http_response(headers=None, status=201)
+ mock_post.return_value = response, '{"baz": "qux"}'
+
+ post_data = {'foo': 'bar'}
+ resp = self.client.create_resource('/fake_url', post_data)
+
+ self.assertEqual({'status': '201'}, resp.response)
+ self.assertEqual("qux", resp["baz"])
+ mock_post.assert_called_once_with('v2.0/fake_url', '{"foo": "bar"}')
+ self.mock_expected_success.assert_called_once_with(
+ 201, 201)
+
+ @mock.patch('tempest.lib.common.rest_client.RestClient.post')
+ def test_create_resource_expect_different_values(self, mock_post):
+ response = fake_http.fake_http_response(headers=None, status=200)
+ mock_post.return_value = response, '{}'
+
+ post_data = {'foo': 'bar'}
+ resp = self.client.create_resource('/fake_url', post_data,
+ expect_response_code=200,
+ expect_empty_body=True)
+
+ self.assertEqual({'status': '200'}, resp.response)
+ self._assert_empty(resp)
+ mock_post.assert_called_once_with('v2.0/fake_url', '{"foo": "bar"}')
+ self.mock_expected_success.assert_called_once_with(
+ 200, 200)
+
+ @mock.patch('tempest.lib.common.rest_client.RestClient.put')
+ def test_update_resource(self, mock_put):
+ response = fake_http.fake_http_response(headers=None, status=200)
+ mock_put.return_value = response, '{"baz": "qux"}'
+
+ put_data = {'foo': 'bar'}
+ resp = self.client.update_resource('/fake_url', put_data)
+
+ self.assertEqual({'status': '200'}, resp.response)
+ self.assertEqual("qux", resp["baz"])
+ mock_put.assert_called_once_with('v2.0/fake_url', '{"foo": "bar"}')
+ self.mock_expected_success.assert_called_once_with(
+ 200, 200)
+
+ @mock.patch('tempest.lib.common.rest_client.RestClient.put')
+ def test_update_resource_expect_different_values(self, mock_put):
+ response = fake_http.fake_http_response(headers=None, status=201)
+ mock_put.return_value = response, '{}'
+
+ put_data = {'foo': 'bar'}
+ resp = self.client.update_resource('/fake_url', put_data,
+ expect_response_code=201,
+ expect_empty_body=True)
+
+ self.assertEqual({'status': '201'}, resp.response)
+ self._assert_empty(resp)
+ mock_put.assert_called_once_with('v2.0/fake_url', '{"foo": "bar"}')
+ self.mock_expected_success.assert_called_once_with(
+ 201, 201)
diff --git a/tempest/tests/lib/services/volume/v3/test_group_types_client.py b/tempest/tests/lib/services/volume/v3/test_group_types_client.py
new file mode 100644
index 0000000..95498c7
--- /dev/null
+++ b/tempest/tests/lib/services/volume/v3/test_group_types_client.py
@@ -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)
diff --git a/tempest/tests/lib/services/volume/v3/test_groups_client.py b/tempest/tests/lib/services/volume/v3/test_groups_client.py
new file mode 100644
index 0000000..00db5b4
--- /dev/null
+++ b/tempest/tests/lib/services/volume/v3/test_groups_client.py
@@ -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)