Add functional tests for Manila consistency groups
This commit adds tempest functional tests for Manila consistency groups and cgsnapshot objects and actions. By default these tests are enabled; 3rd party CI systems should disabled these tests through the RUN_MANILA_CG_TESTS environment variable if their driver does not support consistency groups. Partially implements bp manila-consistency-groups Change-Id: I3297e02ad53c328f0bfe5245fefdb6af80552b4a
This commit is contained in:
parent
8666460e8c
commit
333474047c
@ -41,6 +41,10 @@ iniset $BASE/new/tempest/etc/tempest.conf share share_creation_retry_number 2
|
|||||||
SUPPRESS_ERRORS=${SUPPRESS_ERRORS_IN_CLEANUP:-True}
|
SUPPRESS_ERRORS=${SUPPRESS_ERRORS_IN_CLEANUP:-True}
|
||||||
iniset $BASE/new/tempest/etc/tempest.conf share suppress_errors_in_cleanup $SUPPRESS_ERRORS
|
iniset $BASE/new/tempest/etc/tempest.conf share suppress_errors_in_cleanup $SUPPRESS_ERRORS
|
||||||
|
|
||||||
|
# Enable consistency group tests
|
||||||
|
RUN_MANILA_CG_TESTS=${RUN_MANILA_CG_TESTS:-True}
|
||||||
|
iniset $BASE/new/tempest/etc/tempest.conf share run_consistency_group_tests $RUN_MANILA_CG_TESTS
|
||||||
|
|
||||||
# Enable manage/unmanage tests
|
# Enable manage/unmanage tests
|
||||||
RUN_MANILA_MANAGE_TESTS=${RUN_MANILA_MANAGE_TESTS:-True}
|
RUN_MANILA_MANAGE_TESTS=${RUN_MANILA_MANAGE_TESTS:-True}
|
||||||
iniset $BASE/new/tempest/etc/tempest.conf share run_manage_unmanage_tests $RUN_MANILA_MANAGE_TESTS
|
iniset $BASE/new/tempest/etc/tempest.conf share run_manage_unmanage_tests $RUN_MANILA_MANAGE_TESTS
|
||||||
|
@ -25,6 +25,7 @@ echo "DEVSTACK_GATE_TEMPEST_ALLOW_TENANT_ISOLATION=1" >> $localrc_path
|
|||||||
echo "API_RATE_LIMIT=False" >> $localrc_path
|
echo "API_RATE_LIMIT=False" >> $localrc_path
|
||||||
echo "TEMPEST_SERVICES+=,manila" >> $localrc_path
|
echo "TEMPEST_SERVICES+=,manila" >> $localrc_path
|
||||||
echo "VOLUME_BACKING_FILE_SIZE=22G" >> $localrc_path
|
echo "VOLUME_BACKING_FILE_SIZE=22G" >> $localrc_path
|
||||||
|
echo "CINDER_LVM_TYPE=thin" >> $localrc_path
|
||||||
|
|
||||||
echo "MANILA_BACKEND1_CONFIG_GROUP_NAME=london" >> $localrc_path
|
echo "MANILA_BACKEND1_CONFIG_GROUP_NAME=london" >> $localrc_path
|
||||||
echo "MANILA_BACKEND2_CONFIG_GROUP_NAME=paris" >> $localrc_path
|
echo "MANILA_BACKEND2_CONFIG_GROUP_NAME=paris" >> $localrc_path
|
||||||
|
@ -139,6 +139,11 @@ ShareGroup = [
|
|||||||
help="Defines whether to run tests that use share snapshots "
|
help="Defines whether to run tests that use share snapshots "
|
||||||
"or not. Disable this feature if used driver doesn't "
|
"or not. Disable this feature if used driver doesn't "
|
||||||
"support it."),
|
"support it."),
|
||||||
|
cfg.BoolOpt("run_consistency_group_tests",
|
||||||
|
default=True,
|
||||||
|
help="Defines whether to run consistency group tests or not. "
|
||||||
|
"Disable this feature if used driver doesn't support "
|
||||||
|
"it."),
|
||||||
cfg.StrOpt("image_with_share_tools",
|
cfg.StrOpt("image_with_share_tools",
|
||||||
default="manila-service-image",
|
default="manila-service-image",
|
||||||
help="Image name for vm booting with nfs/smb clients tool."),
|
help="Image name for vm booting with nfs/smb clients tool."),
|
||||||
|
@ -26,6 +26,13 @@ from tempest_lib import exceptions
|
|||||||
from manila_tempest_tests import share_exceptions
|
from manila_tempest_tests import share_exceptions
|
||||||
|
|
||||||
CONF = config.CONF
|
CONF = config.CONF
|
||||||
|
LATEST_MICRO_API = {
|
||||||
|
'X-OpenStack-Manila-API-Version': CONF.share.max_api_microversion,
|
||||||
|
}
|
||||||
|
EXPERIMENTAL = {
|
||||||
|
'X-OpenStack-Manila-API-Experimental': 'True',
|
||||||
|
'X-OpenStack-Manila-API-Version': CONF.share.max_api_microversion,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class SharesClient(rest_client.RestClient):
|
class SharesClient(rest_client.RestClient):
|
||||||
@ -86,7 +93,8 @@ class SharesClient(rest_client.RestClient):
|
|||||||
def create_share(self, share_protocol=None, size=1,
|
def create_share(self, share_protocol=None, size=1,
|
||||||
name=None, snapshot_id=None, description=None,
|
name=None, snapshot_id=None, description=None,
|
||||||
metadata=None, share_network_id=None,
|
metadata=None, share_network_id=None,
|
||||||
share_type_id=None, is_public=False):
|
share_type_id=None, is_public=False,
|
||||||
|
consistency_group_id=None):
|
||||||
metadata = metadata or {}
|
metadata = metadata or {}
|
||||||
if name is None:
|
if name is None:
|
||||||
name = data_utils.rand_name("tempest-created-share")
|
name = data_utils.rand_name("tempest-created-share")
|
||||||
@ -111,13 +119,18 @@ class SharesClient(rest_client.RestClient):
|
|||||||
post_body["share"]["share_network_id"] = share_network_id
|
post_body["share"]["share_network_id"] = share_network_id
|
||||||
if share_type_id:
|
if share_type_id:
|
||||||
post_body["share"]["share_type"] = share_type_id
|
post_body["share"]["share_type"] = share_type_id
|
||||||
|
if consistency_group_id:
|
||||||
|
post_body["share"]["consistency_group_id"] = consistency_group_id
|
||||||
body = json.dumps(post_body)
|
body = json.dumps(post_body)
|
||||||
resp, body = self.post("shares", body)
|
resp, body = self.post("shares", body, headers=LATEST_MICRO_API,
|
||||||
|
extra_headers=True)
|
||||||
self.expected_success(200, resp.status)
|
self.expected_success(200, resp.status)
|
||||||
return self._parse_resp(body)
|
return self._parse_resp(body)
|
||||||
|
|
||||||
def delete_share(self, share_id):
|
def delete_share(self, share_id, params=None):
|
||||||
resp, body = self.delete("shares/%s" % share_id)
|
uri = "shares/%s" % share_id
|
||||||
|
uri += '?%s' % (urllib.urlencode(params) if params else '')
|
||||||
|
resp, body = self.delete(uri)
|
||||||
self.expected_success(202, resp.status)
|
self.expected_success(202, resp.status)
|
||||||
return body
|
return body
|
||||||
|
|
||||||
@ -148,7 +161,8 @@ class SharesClient(rest_client.RestClient):
|
|||||||
"""Get list of shares w/o filters."""
|
"""Get list of shares w/o filters."""
|
||||||
uri = 'shares/detail' if detailed else 'shares'
|
uri = 'shares/detail' if detailed else 'shares'
|
||||||
uri += '?%s' % urllib.urlencode(params) if params else ''
|
uri += '?%s' % urllib.urlencode(params) if params else ''
|
||||||
resp, body = self.get(uri)
|
resp, body = self.get(uri, headers=LATEST_MICRO_API,
|
||||||
|
extra_headers=True)
|
||||||
self.expected_success(200, resp.status)
|
self.expected_success(200, resp.status)
|
||||||
return self._parse_resp(body)
|
return self._parse_resp(body)
|
||||||
|
|
||||||
@ -157,7 +171,8 @@ class SharesClient(rest_client.RestClient):
|
|||||||
return self.list_shares(detailed=True, params=params)
|
return self.list_shares(detailed=True, params=params)
|
||||||
|
|
||||||
def get_share(self, share_id):
|
def get_share(self, share_id):
|
||||||
resp, body = self.get("shares/%s" % share_id)
|
resp, body = self.get("shares/%s" % share_id, headers=LATEST_MICRO_API,
|
||||||
|
extra_headers=True)
|
||||||
self.expected_success(200, resp.status)
|
self.expected_success(200, resp.status)
|
||||||
return self._parse_resp(body)
|
return self._parse_resp(body)
|
||||||
|
|
||||||
@ -367,6 +382,49 @@ class SharesClient(rest_client.RestClient):
|
|||||||
(snapshot_name, status, self.build_timeout))
|
(snapshot_name, status, self.build_timeout))
|
||||||
raise exceptions.TimeoutException(message)
|
raise exceptions.TimeoutException(message)
|
||||||
|
|
||||||
|
def wait_for_consistency_group_status(self, consistency_group_id, status):
|
||||||
|
"""Waits for a consistency group to reach a given status."""
|
||||||
|
body = self.get_consistency_group(consistency_group_id)
|
||||||
|
consistency_group_name = body['name']
|
||||||
|
consistency_group_status = body['status']
|
||||||
|
start = int(time.time())
|
||||||
|
|
||||||
|
while consistency_group_status != status:
|
||||||
|
time.sleep(self.build_interval)
|
||||||
|
body = self.get_consistency_group(consistency_group_id)
|
||||||
|
consistency_group_status = body['status']
|
||||||
|
if 'error' in consistency_group_status and status != 'error':
|
||||||
|
raise share_exceptions.ConsistencyGroupBuildErrorException(
|
||||||
|
consistency_group_id=consistency_group_id)
|
||||||
|
|
||||||
|
if int(time.time()) - start >= self.build_timeout:
|
||||||
|
message = ('Consistency Group %s failed to reach %s status '
|
||||||
|
'within the required time (%s s).' %
|
||||||
|
(consistency_group_name, status,
|
||||||
|
self.build_timeout))
|
||||||
|
raise exceptions.TimeoutException(message)
|
||||||
|
|
||||||
|
def wait_for_cgsnapshot_status(self, cgsnapshot_id, status):
|
||||||
|
"""Waits for a cgsnapshot to reach a given status."""
|
||||||
|
body = self.get_cgsnapshot(cgsnapshot_id)
|
||||||
|
cgsnapshot_name = body['name']
|
||||||
|
cgsnapshot_status = body['status']
|
||||||
|
start = int(time.time())
|
||||||
|
|
||||||
|
while cgsnapshot_status != status:
|
||||||
|
time.sleep(self.build_interval)
|
||||||
|
body = self.get_cgsnapshot(cgsnapshot_id)
|
||||||
|
cgsnapshot_status = body['status']
|
||||||
|
if 'error' in cgsnapshot_status and status != 'error':
|
||||||
|
raise share_exceptions.CGSnapshotBuildErrorException(
|
||||||
|
cgsnapshot_id=cgsnapshot_id)
|
||||||
|
|
||||||
|
if int(time.time()) - start >= self.build_timeout:
|
||||||
|
message = ('CGSnapshot %s failed to reach %s status '
|
||||||
|
'within the required time (%s s).' %
|
||||||
|
(cgsnapshot_name, status, self.build_timeout))
|
||||||
|
raise exceptions.TimeoutException(message)
|
||||||
|
|
||||||
def wait_for_access_rule_status(self, share_id, rule_id, status):
|
def wait_for_access_rule_status(self, share_id, rule_id, status):
|
||||||
"""Waits for an access rule to reach a given status."""
|
"""Waits for an access rule to reach a given status."""
|
||||||
rule_status = "new"
|
rule_status = "new"
|
||||||
@ -444,7 +502,8 @@ class SharesClient(rest_client.RestClient):
|
|||||||
"""Verifies whether provided resource deleted or not.
|
"""Verifies whether provided resource deleted or not.
|
||||||
|
|
||||||
:param kwargs: dict with expected keys 'share_id', 'snapshot_id',
|
:param kwargs: dict with expected keys 'share_id', 'snapshot_id',
|
||||||
:param kwargs: 'sn_id', 'ss_id', 'vt_id' and 'server_id'
|
:param kwargs: 'sn_id', 'ss_id', 'vt_id', 'server_id', 'cg_id',
|
||||||
|
:param kwargs: and 'cgsnapshot_id'
|
||||||
:raises share_exceptions.InvalidResource
|
:raises share_exceptions.InvalidResource
|
||||||
"""
|
"""
|
||||||
if "share_id" in kwargs:
|
if "share_id" in kwargs:
|
||||||
@ -480,6 +539,12 @@ class SharesClient(rest_client.RestClient):
|
|||||||
elif "server_id" in kwargs:
|
elif "server_id" in kwargs:
|
||||||
return self._is_resource_deleted(
|
return self._is_resource_deleted(
|
||||||
self.show_share_server, kwargs.get("server_id"))
|
self.show_share_server, kwargs.get("server_id"))
|
||||||
|
elif "cg_id" in kwargs:
|
||||||
|
return self._is_resource_deleted(
|
||||||
|
self.get_consistency_group, kwargs.get("cg_id"))
|
||||||
|
elif "cgsnapshot_id" in kwargs:
|
||||||
|
return self._is_resource_deleted(
|
||||||
|
self.get_cgsnapshot, kwargs.get("cgsnapshot_id"))
|
||||||
else:
|
else:
|
||||||
raise share_exceptions.InvalidResource(
|
raise share_exceptions.InvalidResource(
|
||||||
message=six.text_type(kwargs))
|
message=six.text_type(kwargs))
|
||||||
@ -489,7 +554,7 @@ class SharesClient(rest_client.RestClient):
|
|||||||
res = func(res_id)
|
res = func(res_id)
|
||||||
except exceptions.NotFound:
|
except exceptions.NotFound:
|
||||||
return True
|
return True
|
||||||
if res.get('status') == 'error_deleting':
|
if res.get('status') in ['error_deleting', 'error']:
|
||||||
# Resource has "error_deleting" status and can not be deleted.
|
# Resource has "error_deleting" status and can not be deleted.
|
||||||
resource_type = func.__name__.split('_', 1)[-1]
|
resource_type = func.__name__.split('_', 1)[-1]
|
||||||
raise share_exceptions.ResourceReleaseFailed(
|
raise share_exceptions.ResourceReleaseFailed(
|
||||||
@ -533,26 +598,29 @@ class SharesClient(rest_client.RestClient):
|
|||||||
self.expected_success(200, resp.status)
|
self.expected_success(200, resp.status)
|
||||||
return self._parse_resp(body)
|
return self._parse_resp(body)
|
||||||
|
|
||||||
def reset_state(self, s_id, status="error", s_type="shares"):
|
def reset_state(self, s_id, status="error", s_type="shares",
|
||||||
"""Resets the state of a share or a snapshot.
|
headers=None):
|
||||||
|
"""Resets the state of a share, snapshot, cg, or a cgsnapshot.
|
||||||
|
|
||||||
status: available, error, creating, deleting, error_deleting
|
status: available, error, creating, deleting, error_deleting
|
||||||
s_type: shares, snapshots
|
s_type: shares, snapshots, consistency-groups, cgsnapshots
|
||||||
"""
|
"""
|
||||||
body = {"os-reset_status": {"status": status}}
|
body = {"os-reset_status": {"status": status}}
|
||||||
body = json.dumps(body)
|
body = json.dumps(body)
|
||||||
resp, body = self.post("%s/%s/action" % (s_type, s_id), body)
|
resp, body = self.post("%s/%s/action" % (s_type, s_id), body,
|
||||||
|
headers=headers, extra_headers=True)
|
||||||
self.expected_success(202, resp.status)
|
self.expected_success(202, resp.status)
|
||||||
return body
|
return body
|
||||||
|
|
||||||
def force_delete(self, s_id, s_type="shares"):
|
def force_delete(self, s_id, s_type="shares", headers=None):
|
||||||
"""Force delete share or snapshot.
|
"""Force delete share or snapshot.
|
||||||
|
|
||||||
s_type: shares, snapshots
|
s_type: shares, snapshots
|
||||||
"""
|
"""
|
||||||
body = {"os-force_delete": None}
|
body = {"os-force_delete": None}
|
||||||
body = json.dumps(body)
|
body = json.dumps(body)
|
||||||
resp, body = self.post("%s/%s/action" % (s_type, s_id), body)
|
resp, body = self.post("%s/%s/action" % (s_type, s_id), body,
|
||||||
|
headers=headers, extra_headers=True)
|
||||||
self.expected_success(202, resp.status)
|
self.expected_success(202, resp.status)
|
||||||
return body
|
return body
|
||||||
|
|
||||||
@ -857,3 +925,143 @@ class SharesClient(rest_client.RestClient):
|
|||||||
resp, body = self.get(uri)
|
resp, body = self.get(uri)
|
||||||
self.expected_success(200, resp.status)
|
self.expected_success(200, resp.status)
|
||||||
return json.loads(body)
|
return json.loads(body)
|
||||||
|
|
||||||
|
###############
|
||||||
|
|
||||||
|
def create_consistency_group(self, name=None, description=None,
|
||||||
|
share_type_ids=(), share_network_id=None,
|
||||||
|
source_cgsnapshot_id=None):
|
||||||
|
"""Create a new consistency group."""
|
||||||
|
uri = 'consistency-groups'
|
||||||
|
post_body = {}
|
||||||
|
if name:
|
||||||
|
post_body['name'] = name
|
||||||
|
if description:
|
||||||
|
post_body['description'] = description
|
||||||
|
if share_type_ids:
|
||||||
|
post_body['share_types'] = share_type_ids
|
||||||
|
if source_cgsnapshot_id:
|
||||||
|
post_body['source_cgsnapshot_id'] = source_cgsnapshot_id
|
||||||
|
if share_network_id:
|
||||||
|
post_body['share_network_id'] = share_network_id
|
||||||
|
body = json.dumps({'consistency_group': post_body})
|
||||||
|
resp, body = self.post(uri, body, headers=EXPERIMENTAL,
|
||||||
|
extra_headers=True)
|
||||||
|
self.expected_success(202, resp.status)
|
||||||
|
return self._parse_resp(body)
|
||||||
|
|
||||||
|
def delete_consistency_group(self, consistency_group_id):
|
||||||
|
"""Delete a consistency group."""
|
||||||
|
uri = 'consistency-groups/%s' % consistency_group_id
|
||||||
|
resp, body = self.delete(uri, headers=EXPERIMENTAL,
|
||||||
|
extra_headers=True)
|
||||||
|
self.expected_success(202, resp.status)
|
||||||
|
return body
|
||||||
|
|
||||||
|
def list_consistency_groups(self, detailed=False, params=None):
|
||||||
|
"""Get list of consistency groups w/o filters."""
|
||||||
|
uri = 'consistency-groups%s' % ('/detail' if detailed else '')
|
||||||
|
uri += '?%s' % (urllib.urlencode(params) if params else '')
|
||||||
|
resp, body = self.get(uri, headers=EXPERIMENTAL, extra_headers=True)
|
||||||
|
self.expected_success(200, resp.status)
|
||||||
|
return self._parse_resp(body)
|
||||||
|
|
||||||
|
def get_consistency_group(self, consistency_group_id):
|
||||||
|
"""Get consistency group info."""
|
||||||
|
uri = 'consistency-groups/%s' % consistency_group_id
|
||||||
|
resp, body = self.get(uri, headers=EXPERIMENTAL, extra_headers=True)
|
||||||
|
self.expected_success(200, resp.status)
|
||||||
|
return self._parse_resp(body)
|
||||||
|
|
||||||
|
def update_consistency_group(self, consistency_group_id, name=None,
|
||||||
|
description=None, **kwargs):
|
||||||
|
"""Update an existing consistency group."""
|
||||||
|
uri = 'consistency-groups/%s' % consistency_group_id
|
||||||
|
post_body = {}
|
||||||
|
if name:
|
||||||
|
post_body['name'] = name
|
||||||
|
if description:
|
||||||
|
post_body['description'] = description
|
||||||
|
if kwargs:
|
||||||
|
post_body.update(kwargs)
|
||||||
|
body = json.dumps({'consistency_group': post_body})
|
||||||
|
resp, body = self.put(uri, body, headers=EXPERIMENTAL,
|
||||||
|
extra_headers=True)
|
||||||
|
self.expected_success(200, resp.status)
|
||||||
|
return self._parse_resp(body)
|
||||||
|
|
||||||
|
def consistency_group_reset_state(self, id, status):
|
||||||
|
self.reset_state(id, status=status,
|
||||||
|
s_type='consistency-groups', headers=EXPERIMENTAL)
|
||||||
|
|
||||||
|
def consistency_group_force_delete(self, id, status):
|
||||||
|
self.force_delete(id, status=status,
|
||||||
|
s_type='consistency-groups', headers=EXPERIMENTAL)
|
||||||
|
|
||||||
|
###############
|
||||||
|
|
||||||
|
def create_cgsnapshot(self, consistency_group_id,
|
||||||
|
name=None, description=None):
|
||||||
|
"""Create a new cgsnapshot of an existing consistency group."""
|
||||||
|
uri = 'cgsnapshots'
|
||||||
|
post_body = {'consistency_group_id': consistency_group_id}
|
||||||
|
if name:
|
||||||
|
post_body['name'] = name
|
||||||
|
if description:
|
||||||
|
post_body['description'] = description
|
||||||
|
body = json.dumps({'cgsnapshot': post_body})
|
||||||
|
resp, body = self.post(uri, body, headers=EXPERIMENTAL,
|
||||||
|
extra_headers=True)
|
||||||
|
self.expected_success(202, resp.status)
|
||||||
|
return self._parse_resp(body)
|
||||||
|
|
||||||
|
def delete_cgsnapshot(self, cgsnapshot_id):
|
||||||
|
"""Delete an existing cgsnapshot."""
|
||||||
|
uri = 'cgsnapshots/%s' % cgsnapshot_id
|
||||||
|
resp, body = self.delete(uri, headers=EXPERIMENTAL, extra_headers=True)
|
||||||
|
self.expected_success(202, resp.status)
|
||||||
|
return body
|
||||||
|
|
||||||
|
def list_cgsnapshots(self, detailed=False, params=None):
|
||||||
|
"""Get list of cgsnapshots w/o filters."""
|
||||||
|
uri = 'cgsnapshots/detail' if detailed else 'cgsnapshots'
|
||||||
|
uri += '?%s' % (urllib.urlencode(params) if params else '')
|
||||||
|
resp, body = self.get(uri, headers=EXPERIMENTAL, extra_headers=True)
|
||||||
|
self.expected_success(200, resp.status)
|
||||||
|
return self._parse_resp(body)
|
||||||
|
|
||||||
|
def list_cgsnapshot_members(self, cgsnapshot_id):
|
||||||
|
"""Get list of members of a cgsnapshots."""
|
||||||
|
uri = 'cgsnapshots/%s/members' % cgsnapshot_id
|
||||||
|
resp, body = self.get(uri, headers=EXPERIMENTAL, extra_headers=True)
|
||||||
|
self.expected_success(200, resp.status)
|
||||||
|
return self._parse_resp(body)
|
||||||
|
|
||||||
|
def get_cgsnapshot(self, cgsnapshot_id):
|
||||||
|
"""Get cgsnapshot info."""
|
||||||
|
uri = 'cgsnapshots/%s' % cgsnapshot_id
|
||||||
|
resp, body = self.get(uri, headers=EXPERIMENTAL, extra_headers=True)
|
||||||
|
self.expected_success(200, resp.status)
|
||||||
|
return self._parse_resp(body)
|
||||||
|
|
||||||
|
def update_cgsnapshot(self, cgsnapshot_id, name=None, description=None):
|
||||||
|
"""Update an existing cgsnapshot."""
|
||||||
|
uri = 'cgsnapshots/%s' % cgsnapshot_id
|
||||||
|
post_body = {}
|
||||||
|
if name:
|
||||||
|
post_body['name'] = name
|
||||||
|
if description:
|
||||||
|
post_body['description'] = description
|
||||||
|
body = json.dumps({'cgsnapshot': post_body})
|
||||||
|
resp, body = self.put(uri, body, headers=EXPERIMENTAL,
|
||||||
|
extra_headers=True)
|
||||||
|
self.expected_success(200, resp.status)
|
||||||
|
return self._parse_resp(body)
|
||||||
|
|
||||||
|
def cgsnapshot_reset_state(self, id, status):
|
||||||
|
self.reset_state(id, status=status,
|
||||||
|
s_type='cgsnapshots', headers=EXPERIMENTAL)
|
||||||
|
|
||||||
|
def cgsnapshot_force_delete(self, id, status):
|
||||||
|
self.force_delete(id, status=status,
|
||||||
|
s_type='cgsnapshots', headers=EXPERIMENTAL)
|
||||||
|
@ -24,6 +24,11 @@ class ShareInstanceBuildErrorException(exceptions.TempestException):
|
|||||||
message = "Share instance %(id)s failed to build and is in ERROR status"
|
message = "Share instance %(id)s failed to build and is in ERROR status"
|
||||||
|
|
||||||
|
|
||||||
|
class ConsistencyGroupBuildErrorException(exceptions.TempestException):
|
||||||
|
message = ("Consistency group %(consistency_group_id)s failed to build "
|
||||||
|
"and is in ERROR status")
|
||||||
|
|
||||||
|
|
||||||
class AccessRuleBuildErrorException(exceptions.TempestException):
|
class AccessRuleBuildErrorException(exceptions.TempestException):
|
||||||
message = "Share's rule with id %(rule_id)s is in ERROR status"
|
message = "Share's rule with id %(rule_id)s is in ERROR status"
|
||||||
|
|
||||||
@ -32,6 +37,11 @@ class SnapshotBuildErrorException(exceptions.TempestException):
|
|||||||
message = "Snapshot %(snapshot_id)s failed to build and is in ERROR status"
|
message = "Snapshot %(snapshot_id)s failed to build and is in ERROR status"
|
||||||
|
|
||||||
|
|
||||||
|
class CGSnapshotBuildErrorException(exceptions.TempestException):
|
||||||
|
message = ("CGSnapshot %(cgsnapshot_id)s failed to build and is in ERROR "
|
||||||
|
"status")
|
||||||
|
|
||||||
|
|
||||||
class ShareProtocolNotSpecified(exceptions.TempestException):
|
class ShareProtocolNotSpecified(exceptions.TempestException):
|
||||||
message = "Share can not be created, share protocol is not specified"
|
message = "Share can not be created, share protocol is not specified"
|
||||||
|
|
||||||
|
@ -0,0 +1,118 @@
|
|||||||
|
# Copyright 2015 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 import config
|
||||||
|
from tempest import test
|
||||||
|
from tempest_lib.common.utils import data_utils
|
||||||
|
import testtools
|
||||||
|
|
||||||
|
from manila_tempest_tests.tests.api import base
|
||||||
|
|
||||||
|
CONF = config.CONF
|
||||||
|
|
||||||
|
|
||||||
|
@testtools.skipUnless(CONF.share.run_consistency_group_tests,
|
||||||
|
'Consistency Group tests disabled.')
|
||||||
|
class ConsistencyGroupActionsTest(base.BaseSharesAdminTest):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resource_setup(cls):
|
||||||
|
super(ConsistencyGroupActionsTest, cls).resource_setup()
|
||||||
|
# Create 2 share_types
|
||||||
|
name = data_utils.rand_name("tempest-manila")
|
||||||
|
extra_specs = cls.add_required_extra_specs_to_dict()
|
||||||
|
share_type = cls.create_share_type(name, extra_specs=extra_specs)
|
||||||
|
cls.share_type = share_type['share_type']
|
||||||
|
|
||||||
|
name = data_utils.rand_name("tempest-manila")
|
||||||
|
share_type = cls.create_share_type(name, extra_specs=extra_specs)
|
||||||
|
cls.share_type2 = share_type['share_type']
|
||||||
|
|
||||||
|
# Create a consistency group
|
||||||
|
cls.consistency_group = cls.create_consistency_group(
|
||||||
|
share_type_ids=[cls.share_type['id'], cls.share_type2['id']])
|
||||||
|
|
||||||
|
@test.attr(type=["gate", ])
|
||||||
|
def test_create_cg_from_cgsnapshot_with_multiple_share_types(self):
|
||||||
|
# Create cgsnapshot
|
||||||
|
cgsnapshot = self.create_cgsnapshot_wait_for_active(
|
||||||
|
self.consistency_group["id"], cleanup_in_class=False)
|
||||||
|
|
||||||
|
new_consistency_group = self.create_consistency_group(
|
||||||
|
cleanup_in_class=False, source_cgsnapshot_id=cgsnapshot['id'])
|
||||||
|
|
||||||
|
# Verify share_types are the same
|
||||||
|
expected_types = sorted(self.consistency_group['share_types'])
|
||||||
|
actual_types = sorted(new_consistency_group['share_types'])
|
||||||
|
self.assertEqual(expected_types, actual_types,
|
||||||
|
'Expected share types of %s, but got %s.' % (
|
||||||
|
expected_types, actual_types))
|
||||||
|
|
||||||
|
@test.attr(type=["gate", ])
|
||||||
|
def test_create_cg_from_multi_typed_populated_cgsnapshot(self):
|
||||||
|
share_name = data_utils.rand_name("tempest-share-name")
|
||||||
|
share_desc = data_utils.rand_name("tempest-share-description")
|
||||||
|
share_size = 1
|
||||||
|
share = self.create_share(
|
||||||
|
cleanup_in_class=False,
|
||||||
|
name=share_name,
|
||||||
|
description=share_desc,
|
||||||
|
size=share_size,
|
||||||
|
consistency_group_id=self.consistency_group['id'],
|
||||||
|
share_type_id=self.share_type['id']
|
||||||
|
)
|
||||||
|
|
||||||
|
share_name2 = data_utils.rand_name("tempest-share-name")
|
||||||
|
share_desc2 = data_utils.rand_name("tempest-share-description")
|
||||||
|
share_size2 = 1
|
||||||
|
share2 = self.create_share(
|
||||||
|
cleanup_in_class=False,
|
||||||
|
name=share_name2,
|
||||||
|
description=share_desc2,
|
||||||
|
size=share_size2,
|
||||||
|
consistency_group_id=self.consistency_group['id'],
|
||||||
|
share_type_id=self.share_type2['id']
|
||||||
|
)
|
||||||
|
|
||||||
|
cg_shares = self.shares_client.list_shares(detailed=True, params={
|
||||||
|
'consistency_group_id': self.consistency_group['id']})
|
||||||
|
|
||||||
|
cg_share_ids = [s['id'] for s in cg_shares]
|
||||||
|
for share_id in [share['id'], share2['id']]:
|
||||||
|
self.assertIn(share_id, cg_share_ids, 'Share %s not in '
|
||||||
|
'consistency group %s.' %
|
||||||
|
(share_id, self.consistency_group['id']))
|
||||||
|
|
||||||
|
cgsnap_name = data_utils.rand_name("tempest-cgsnap-name")
|
||||||
|
cgsnap_desc = data_utils.rand_name("tempest-cgsnap-description")
|
||||||
|
cgsnapshot = self.create_cgsnapshot_wait_for_active(
|
||||||
|
self.consistency_group["id"],
|
||||||
|
name=cgsnap_name,
|
||||||
|
description=cgsnap_desc,
|
||||||
|
cleanup_in_class=False)
|
||||||
|
|
||||||
|
self.create_consistency_group(
|
||||||
|
cleanup_in_class=False, source_cgsnapshot_id=cgsnapshot['id'])
|
||||||
|
|
||||||
|
# TODO(akerr): Skip until bug 1483886 is resolved
|
||||||
|
# Verify that the new shares correspond to correct share types
|
||||||
|
# expected_share_types = [self.share_type['id'], self.share_type2[
|
||||||
|
# 'id']]
|
||||||
|
# actual_share_types = [s['share_type'] for s in new_cg_shares]
|
||||||
|
# self.assertEqual(sorted(expected_share_types),
|
||||||
|
# sorted(actual_share_types),
|
||||||
|
# 'Expected shares of types %s, got %s.' % (
|
||||||
|
# sorted(expected_share_types),
|
||||||
|
# sorted(actual_share_types)))
|
@ -0,0 +1,66 @@
|
|||||||
|
# Copyright 2015 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 import config
|
||||||
|
from tempest import test
|
||||||
|
from tempest_lib.common.utils import data_utils
|
||||||
|
import testtools
|
||||||
|
|
||||||
|
from manila_tempest_tests.tests.api import base
|
||||||
|
|
||||||
|
CONF = config.CONF
|
||||||
|
CG_REQUIRED_ELEMENTS = {"id", "name", "description", "created_at", "status",
|
||||||
|
"share_types", "project_id", "host", "links"}
|
||||||
|
|
||||||
|
|
||||||
|
@testtools.skipUnless(CONF.share.run_consistency_group_tests,
|
||||||
|
'Consistency Group tests disabled.')
|
||||||
|
class ConsistencyGroupsTest(base.BaseSharesAdminTest):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resource_setup(cls):
|
||||||
|
super(ConsistencyGroupsTest, cls).resource_setup()
|
||||||
|
# Create 2 share_types
|
||||||
|
name = data_utils.rand_name("tempest-manila")
|
||||||
|
extra_specs = cls.add_required_extra_specs_to_dict()
|
||||||
|
share_type = cls.create_share_type(name, extra_specs=extra_specs)
|
||||||
|
cls.share_type = share_type['share_type']
|
||||||
|
|
||||||
|
name = data_utils.rand_name("tempest-manila")
|
||||||
|
share_type = cls.create_share_type(name, extra_specs=extra_specs)
|
||||||
|
cls.share_type2 = share_type['share_type']
|
||||||
|
|
||||||
|
@test.attr(type=["gate", ])
|
||||||
|
def test_create_cg_with_multiple_share_types(self):
|
||||||
|
# Create a consistency group
|
||||||
|
consistency_group = self.create_consistency_group(
|
||||||
|
cleanup_in_class=False, share_type_ids=[self.share_type['id'],
|
||||||
|
self.share_type2['id']])
|
||||||
|
|
||||||
|
self.assertTrue(CG_REQUIRED_ELEMENTS.issubset(
|
||||||
|
consistency_group.keys()),
|
||||||
|
'At least one expected element missing from consistency group '
|
||||||
|
'response. Expected %(expected)s, got %(actual)s.' % {
|
||||||
|
"expected": CG_REQUIRED_ELEMENTS,
|
||||||
|
"actual": consistency_group.keys()})
|
||||||
|
|
||||||
|
actual_share_types = consistency_group['share_types']
|
||||||
|
expected_share_types = [self.share_type['id'], self.share_type2['id']]
|
||||||
|
self.assertEqual(sorted(expected_share_types),
|
||||||
|
sorted(actual_share_types),
|
||||||
|
'Incorrect share types applied to consistency group '
|
||||||
|
'%s. Expected %s, got %s' % (consistency_group['id'],
|
||||||
|
expected_share_types,
|
||||||
|
actual_share_types))
|
@ -0,0 +1,270 @@
|
|||||||
|
# Copyright 2015 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 import config
|
||||||
|
from tempest import test
|
||||||
|
from tempest_lib.common.utils import data_utils
|
||||||
|
from tempest_lib import exceptions
|
||||||
|
import testtools
|
||||||
|
|
||||||
|
from manila_tempest_tests.tests.api import base
|
||||||
|
|
||||||
|
CONF = config.CONF
|
||||||
|
|
||||||
|
|
||||||
|
@testtools.skipUnless(CONF.share.run_consistency_group_tests,
|
||||||
|
'Consistency Group tests disabled.')
|
||||||
|
class ConsistencyGroupsNegativeTest(base.BaseSharesAdminTest):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resource_setup(cls):
|
||||||
|
super(ConsistencyGroupsNegativeTest, cls).resource_setup()
|
||||||
|
# Create share_type
|
||||||
|
name = data_utils.rand_name("tempest-manila")
|
||||||
|
extra_specs = cls.add_required_extra_specs_to_dict()
|
||||||
|
share_type = cls.create_share_type(name, extra_specs=extra_specs)
|
||||||
|
cls.share_type = share_type['share_type']
|
||||||
|
|
||||||
|
# Create a consistency group
|
||||||
|
cls.consistency_group = cls.create_consistency_group(
|
||||||
|
share_type_ids=[cls.share_type['id']])
|
||||||
|
|
||||||
|
# Create share inside consistency group
|
||||||
|
cls.share_name = data_utils.rand_name("tempest-share-name")
|
||||||
|
cls.share_desc = data_utils.rand_name("tempest-share-description")
|
||||||
|
cls.share_size = 1
|
||||||
|
cls.share = cls.create_share(
|
||||||
|
name=cls.share_name,
|
||||||
|
description=cls.share_desc,
|
||||||
|
size=cls.share_size,
|
||||||
|
consistency_group_id=cls.consistency_group['id'],
|
||||||
|
share_type_id=cls.share_type['id'],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create a cgsnapshot of the consistency group
|
||||||
|
cls.cgsnap_name = data_utils.rand_name("tempest-cgsnap-name")
|
||||||
|
cls.cgsnap_desc = data_utils.rand_name("tempest-cgsnap-description")
|
||||||
|
cls.cgsnapshot = cls.create_cgsnapshot_wait_for_active(
|
||||||
|
cls.consistency_group["id"],
|
||||||
|
name=cls.cgsnap_name,
|
||||||
|
description=cls.cgsnap_desc)
|
||||||
|
|
||||||
|
@test.attr(type=["negative", "gate", ])
|
||||||
|
def test_delete_share_type_in_use_by_cg(self):
|
||||||
|
# Attempt delete of share type
|
||||||
|
self.assertRaises(exceptions.BadRequest,
|
||||||
|
self.shares_client.delete_share_type,
|
||||||
|
self.share_type['id'])
|
||||||
|
|
||||||
|
@test.attr(type=["negative", "gate", ])
|
||||||
|
def test_create_share_of_unsupported_type_in_cg(self):
|
||||||
|
# Attempt to create share of default type in the cg
|
||||||
|
self.assertRaises(exceptions.BadRequest,
|
||||||
|
self.shares_client.create_share, size=1,
|
||||||
|
consistency_group_id=self.consistency_group['id'])
|
||||||
|
|
||||||
|
@test.attr(type=["negative", "gate", ])
|
||||||
|
def test_create_share_in_cg_that_is_not_available(self):
|
||||||
|
consistency_group = self.create_consistency_group(
|
||||||
|
cleanup_in_class=False)
|
||||||
|
self.addCleanup(self.shares_client.consistency_group_reset_state,
|
||||||
|
consistency_group['id'],
|
||||||
|
status='available')
|
||||||
|
# creating
|
||||||
|
self.shares_client.consistency_group_reset_state(
|
||||||
|
consistency_group['id'], status='creating')
|
||||||
|
self.shares_client.wait_for_consistency_group_status(
|
||||||
|
consistency_group['id'], 'creating')
|
||||||
|
self.assertRaises(exceptions.BadRequest, self.create_share,
|
||||||
|
name=self.share_name,
|
||||||
|
description=self.share_desc,
|
||||||
|
size=self.share_size,
|
||||||
|
consistency_group_id=consistency_group['id'],
|
||||||
|
cleanup_in_class=False)
|
||||||
|
# deleting
|
||||||
|
self.shares_client.consistency_group_reset_state(
|
||||||
|
consistency_group['id'], status='deleting')
|
||||||
|
self.shares_client.wait_for_consistency_group_status(
|
||||||
|
consistency_group['id'], 'deleting')
|
||||||
|
self.assertRaises(exceptions.BadRequest, self.create_share,
|
||||||
|
name=self.share_name,
|
||||||
|
description=self.share_desc,
|
||||||
|
size=self.share_size,
|
||||||
|
consistency_group_id=consistency_group['id'],
|
||||||
|
cleanup_in_class=False)
|
||||||
|
# error
|
||||||
|
self.shares_client.consistency_group_reset_state(
|
||||||
|
consistency_group['id'], status='error')
|
||||||
|
self.shares_client.wait_for_consistency_group_status(
|
||||||
|
consistency_group['id'], 'error')
|
||||||
|
self.assertRaises(exceptions.BadRequest, self.create_share,
|
||||||
|
name=self.share_name,
|
||||||
|
description=self.share_desc,
|
||||||
|
size=self.share_size,
|
||||||
|
consistency_group_id=consistency_group['id'],
|
||||||
|
cleanup_in_class=False)
|
||||||
|
|
||||||
|
@test.attr(type=["negative", "gate", ])
|
||||||
|
def test_create_cgsnapshot_of_cg_that_is_not_available(self):
|
||||||
|
consistency_group = self.create_consistency_group(
|
||||||
|
cleanup_in_class=False)
|
||||||
|
self.addCleanup(self.shares_client.consistency_group_reset_state,
|
||||||
|
consistency_group['id'],
|
||||||
|
status='available')
|
||||||
|
# creating
|
||||||
|
self.shares_client.consistency_group_reset_state(
|
||||||
|
consistency_group['id'], status='creating')
|
||||||
|
self.shares_client.wait_for_consistency_group_status(
|
||||||
|
consistency_group['id'], 'creating')
|
||||||
|
self.assertRaises(exceptions.Conflict,
|
||||||
|
self.create_cgsnapshot_wait_for_active,
|
||||||
|
consistency_group['id'],
|
||||||
|
cleanup_in_class=False)
|
||||||
|
# deleting
|
||||||
|
self.shares_client.consistency_group_reset_state(
|
||||||
|
consistency_group['id'], status='deleting')
|
||||||
|
self.shares_client.wait_for_consistency_group_status(
|
||||||
|
consistency_group['id'], 'deleting')
|
||||||
|
self.assertRaises(exceptions.Conflict,
|
||||||
|
self.create_cgsnapshot_wait_for_active,
|
||||||
|
consistency_group['id'],
|
||||||
|
cleanup_in_class=False)
|
||||||
|
# error
|
||||||
|
self.shares_client.consistency_group_reset_state(
|
||||||
|
consistency_group['id'], status='error')
|
||||||
|
self.shares_client.wait_for_consistency_group_status(
|
||||||
|
consistency_group['id'], 'error')
|
||||||
|
self.assertRaises(exceptions.Conflict,
|
||||||
|
self.create_cgsnapshot_wait_for_active,
|
||||||
|
consistency_group['id'],
|
||||||
|
cleanup_in_class=False)
|
||||||
|
|
||||||
|
@test.attr(type=["negative", "gate", ])
|
||||||
|
def test_create_cgsnapshot_of_cg_with_share_in_error_state(self):
|
||||||
|
consistency_group = self.create_consistency_group()
|
||||||
|
share_name = data_utils.rand_name("tempest-share-name")
|
||||||
|
share_desc = data_utils.rand_name("tempest-share-description")
|
||||||
|
share_size = 1
|
||||||
|
share = self.create_share(
|
||||||
|
name=share_name,
|
||||||
|
description=share_desc,
|
||||||
|
size=share_size,
|
||||||
|
consistency_group_id=consistency_group['id'],
|
||||||
|
cleanup_in_class=False,
|
||||||
|
)
|
||||||
|
self.shares_client.reset_state(s_id=share['id'])
|
||||||
|
self.shares_client.wait_for_share_status(share['id'], 'error')
|
||||||
|
self.assertRaises(exceptions.Conflict,
|
||||||
|
self.create_cgsnapshot_wait_for_active,
|
||||||
|
consistency_group['id'],
|
||||||
|
cleanup_in_class=False)
|
||||||
|
|
||||||
|
@test.attr(type=["negative", "gate", ])
|
||||||
|
def test_delete_cgsnapshot_not_in_available_or_error(self):
|
||||||
|
cgsnapshot = self.create_cgsnapshot_wait_for_active(
|
||||||
|
self.consistency_group['id'], cleanup_in_class=False)
|
||||||
|
self.addCleanup(self.shares_client.cgsnapshot_reset_state,
|
||||||
|
cgsnapshot['id'],
|
||||||
|
status='available')
|
||||||
|
|
||||||
|
# creating
|
||||||
|
self.shares_client.cgsnapshot_reset_state(cgsnapshot['id'],
|
||||||
|
status='creating')
|
||||||
|
self.shares_client.wait_for_cgsnapshot_status(cgsnapshot['id'],
|
||||||
|
'creating')
|
||||||
|
self.assertRaises(exceptions.Conflict,
|
||||||
|
self.shares_client.delete_cgsnapshot,
|
||||||
|
cgsnapshot['id'])
|
||||||
|
# deleting
|
||||||
|
self.shares_client.cgsnapshot_reset_state(cgsnapshot['id'],
|
||||||
|
status='deleting')
|
||||||
|
self.shares_client.wait_for_cgsnapshot_status(cgsnapshot['id'],
|
||||||
|
'deleting')
|
||||||
|
self.assertRaises(exceptions.Conflict,
|
||||||
|
self.shares_client.delete_cgsnapshot,
|
||||||
|
cgsnapshot['id'])
|
||||||
|
|
||||||
|
@test.attr(type=["negative", "gate", ])
|
||||||
|
def test_delete_cg_not_in_available_or_error(self):
|
||||||
|
consistency_group = self.create_consistency_group(
|
||||||
|
cleanup_in_class=False)
|
||||||
|
self.addCleanup(self.shares_client.consistency_group_reset_state,
|
||||||
|
consistency_group['id'],
|
||||||
|
status='available')
|
||||||
|
# creating
|
||||||
|
self.shares_client.consistency_group_reset_state(
|
||||||
|
consistency_group['id'], status='creating')
|
||||||
|
self.shares_client.wait_for_consistency_group_status(
|
||||||
|
consistency_group['id'], 'creating')
|
||||||
|
self.assertRaises(exceptions.Conflict,
|
||||||
|
self.shares_client.delete_consistency_group,
|
||||||
|
consistency_group['id'])
|
||||||
|
# deleting
|
||||||
|
self.shares_client.consistency_group_reset_state(
|
||||||
|
consistency_group['id'], status='deleting')
|
||||||
|
self.shares_client.wait_for_consistency_group_status(
|
||||||
|
consistency_group['id'], 'deleting')
|
||||||
|
self.assertRaises(exceptions.Conflict,
|
||||||
|
self.shares_client.delete_consistency_group,
|
||||||
|
consistency_group['id'])
|
||||||
|
|
||||||
|
@test.attr(type=["negative", "gate", ])
|
||||||
|
def test_create_cg_with_conflicting_share_types(self):
|
||||||
|
# Create conflicting share types
|
||||||
|
name = data_utils.rand_name("tempest-manila")
|
||||||
|
extra_specs = {"driver_handles_share_servers": False}
|
||||||
|
share_type = self.create_share_type(name, extra_specs=extra_specs)
|
||||||
|
single_tenant_share_type = share_type['share_type']
|
||||||
|
|
||||||
|
name = data_utils.rand_name("tempest-manila")
|
||||||
|
extra_specs = {"driver_handles_share_servers": True}
|
||||||
|
share_type = self.create_share_type(name, extra_specs=extra_specs)
|
||||||
|
multi_tenant_share_type = share_type['share_type']
|
||||||
|
|
||||||
|
self.assertRaises(exceptions.BadRequest,
|
||||||
|
self.create_consistency_group,
|
||||||
|
share_type_ids=[single_tenant_share_type['id'],
|
||||||
|
multi_tenant_share_type['id']],
|
||||||
|
cleanup_in_class=False)
|
||||||
|
|
||||||
|
@test.attr(type=["negative", "gate", ])
|
||||||
|
def test_create_cg_with_multi_tenant_share_type_and_no_share_network(self):
|
||||||
|
# Create multi tenant share type
|
||||||
|
name = data_utils.rand_name("tempest-manila")
|
||||||
|
extra_specs = {"driver_handles_share_servers": True}
|
||||||
|
share_type = self.create_share_type(name, extra_specs=extra_specs)
|
||||||
|
multi_tenant_share_type = share_type['share_type']
|
||||||
|
|
||||||
|
def create_cg():
|
||||||
|
cg = self.shares_client.create_consistency_group(
|
||||||
|
share_type_ids=[multi_tenant_share_type['id']])
|
||||||
|
resource = {
|
||||||
|
"type": "consistency_group",
|
||||||
|
"id": cg["id"],
|
||||||
|
"client": self.shares_client}
|
||||||
|
self.method_resources.insert(0, resource)
|
||||||
|
return cg
|
||||||
|
|
||||||
|
self.assertRaises(exceptions.BadRequest, create_cg)
|
||||||
|
|
||||||
|
@test.attr(type=["negative", "gate", ])
|
||||||
|
def test_update_cg_share_types(self):
|
||||||
|
consistency_group = self.create_consistency_group(
|
||||||
|
cleanup_in_class=False)
|
||||||
|
|
||||||
|
self.assertRaises(exceptions.BadRequest,
|
||||||
|
self.shares_client.update_consistency_group,
|
||||||
|
consistency_group['id'],
|
||||||
|
share_types=[self.share_type['id']])
|
@ -280,7 +280,8 @@ class BaseSharesTest(test.BaseTestCase):
|
|||||||
def _create_share(cls, share_protocol=None, size=1, name=None,
|
def _create_share(cls, share_protocol=None, size=1, name=None,
|
||||||
snapshot_id=None, description=None, metadata=None,
|
snapshot_id=None, description=None, metadata=None,
|
||||||
share_network_id=None, share_type_id=None,
|
share_network_id=None, share_type_id=None,
|
||||||
client=None, cleanup_in_class=True, is_public=False):
|
consistency_group_id=None, client=None,
|
||||||
|
cleanup_in_class=True, is_public=False):
|
||||||
client = client or cls.shares_client
|
client = client or cls.shares_client
|
||||||
description = description or "Tempest's share"
|
description = description or "Tempest's share"
|
||||||
share_network_id = share_network_id or client.share_network_id or None
|
share_network_id = share_network_id or client.share_network_id or None
|
||||||
@ -296,8 +297,12 @@ class BaseSharesTest(test.BaseTestCase):
|
|||||||
'share_type_id': share_type_id,
|
'share_type_id': share_type_id,
|
||||||
'is_public': is_public,
|
'is_public': is_public,
|
||||||
}
|
}
|
||||||
|
if consistency_group_id:
|
||||||
|
kwargs['consistency_group_id'] = consistency_group_id
|
||||||
|
|
||||||
share = client.create_share(**kwargs)
|
share = client.create_share(**kwargs)
|
||||||
resource = {"type": "share", "id": share["id"], "client": client}
|
resource = {"type": "share", "id": share["id"], "client": client,
|
||||||
|
"consistency_group_id": consistency_group_id}
|
||||||
cleanup_list = (cls.class_resources if cleanup_in_class else
|
cleanup_list = (cls.class_resources if cleanup_in_class else
|
||||||
cls.method_resources)
|
cls.method_resources)
|
||||||
cleanup_list.insert(0, resource)
|
cleanup_list.insert(0, resource)
|
||||||
@ -375,6 +380,42 @@ class BaseSharesTest(test.BaseTestCase):
|
|||||||
|
|
||||||
return [d["share"] for d in data]
|
return [d["share"] for d in data]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_consistency_group(cls, client=None, cleanup_in_class=True,
|
||||||
|
share_network_id=None, **kwargs):
|
||||||
|
client = client or cls.shares_client
|
||||||
|
kwargs['share_network_id'] = (share_network_id or
|
||||||
|
client.share_network_id or None)
|
||||||
|
consistency_group = client.create_consistency_group(**kwargs)
|
||||||
|
resource = {
|
||||||
|
"type": "consistency_group",
|
||||||
|
"id": consistency_group["id"],
|
||||||
|
"client": client}
|
||||||
|
if cleanup_in_class:
|
||||||
|
cls.class_resources.insert(0, resource)
|
||||||
|
else:
|
||||||
|
cls.method_resources.insert(0, resource)
|
||||||
|
|
||||||
|
if kwargs.get('source_cgsnapshot_id'):
|
||||||
|
new_cg_shares = client.list_shares(
|
||||||
|
detailed=True,
|
||||||
|
params={'consistency_group_id': consistency_group['id']})
|
||||||
|
|
||||||
|
for share in new_cg_shares:
|
||||||
|
resource = {"type": "share",
|
||||||
|
"id": share["id"],
|
||||||
|
"client": client,
|
||||||
|
"consistency_group_id": share.get(
|
||||||
|
'consistency_group_id')}
|
||||||
|
if cleanup_in_class:
|
||||||
|
cls.class_resources.insert(0, resource)
|
||||||
|
else:
|
||||||
|
cls.method_resources.insert(0, resource)
|
||||||
|
|
||||||
|
client.wait_for_consistency_group_status(consistency_group['id'],
|
||||||
|
'available')
|
||||||
|
return consistency_group
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_snapshot_wait_for_active(cls, share_id, name=None,
|
def create_snapshot_wait_for_active(cls, share_id, name=None,
|
||||||
description=None, force=False,
|
description=None, force=False,
|
||||||
@ -396,6 +437,27 @@ class BaseSharesTest(test.BaseTestCase):
|
|||||||
client.wait_for_snapshot_status(snapshot["id"], "available")
|
client.wait_for_snapshot_status(snapshot["id"], "available")
|
||||||
return snapshot
|
return snapshot
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_cgsnapshot_wait_for_active(cls, consistency_group_id,
|
||||||
|
name=None, description=None,
|
||||||
|
client=None, cleanup_in_class=True):
|
||||||
|
client = client or cls.shares_client
|
||||||
|
if description is None:
|
||||||
|
description = "Tempest's cgsnapshot"
|
||||||
|
cgsnapshot = client.create_cgsnapshot(consistency_group_id, name=name,
|
||||||
|
description=description)
|
||||||
|
resource = {
|
||||||
|
"type": "cgsnapshot",
|
||||||
|
"id": cgsnapshot["id"],
|
||||||
|
"client": client,
|
||||||
|
}
|
||||||
|
if cleanup_in_class:
|
||||||
|
cls.class_resources.insert(0, resource)
|
||||||
|
else:
|
||||||
|
cls.method_resources.insert(0, resource)
|
||||||
|
client.wait_for_cgsnapshot_status(cgsnapshot["id"], "available")
|
||||||
|
return cgsnapshot
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_share_network(cls, client=None,
|
def create_share_network(cls, client=None,
|
||||||
cleanup_in_class=False, **kwargs):
|
cleanup_in_class=False, **kwargs):
|
||||||
@ -494,7 +556,11 @@ class BaseSharesTest(test.BaseTestCase):
|
|||||||
client = res["client"]
|
client = res["client"]
|
||||||
with handle_cleanup_exceptions():
|
with handle_cleanup_exceptions():
|
||||||
if res["type"] is "share":
|
if res["type"] is "share":
|
||||||
client.delete_share(res_id)
|
params = None
|
||||||
|
cg_id = res.get('consistency_group_id')
|
||||||
|
if cg_id:
|
||||||
|
params = {'consistency_group_id': cg_id}
|
||||||
|
client.delete_share(res_id, params=params)
|
||||||
client.wait_for_resource_deletion(share_id=res_id)
|
client.wait_for_resource_deletion(share_id=res_id)
|
||||||
elif res["type"] is "snapshot":
|
elif res["type"] is "snapshot":
|
||||||
client.delete_snapshot(res_id)
|
client.delete_snapshot(res_id)
|
||||||
@ -508,6 +574,12 @@ class BaseSharesTest(test.BaseTestCase):
|
|||||||
elif res["type"] is "share_type":
|
elif res["type"] is "share_type":
|
||||||
client.delete_share_type(res_id)
|
client.delete_share_type(res_id)
|
||||||
client.wait_for_resource_deletion(st_id=res_id)
|
client.wait_for_resource_deletion(st_id=res_id)
|
||||||
|
elif res["type"] is "consistency_group":
|
||||||
|
client.delete_consistency_group(res_id)
|
||||||
|
client.wait_for_resource_deletion(cg_id=res_id)
|
||||||
|
elif res["type"] is "cgsnapshot":
|
||||||
|
client.delete_cgsnapshot(res_id)
|
||||||
|
client.wait_for_resource_deletion(cgsnapshot_id=res_id)
|
||||||
else:
|
else:
|
||||||
LOG.warn("Provided unsupported resource type for "
|
LOG.warn("Provided unsupported resource type for "
|
||||||
"cleanup '%s'. Skipping." % res["type"])
|
"cleanup '%s'. Skipping." % res["type"])
|
||||||
|
371
manila_tempest_tests/tests/api/test_consistency_group_actions.py
Normal file
371
manila_tempest_tests/tests/api/test_consistency_group_actions.py
Normal file
@ -0,0 +1,371 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2015 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 import config
|
||||||
|
from tempest import test
|
||||||
|
from tempest_lib.common.utils import data_utils
|
||||||
|
import testtools
|
||||||
|
|
||||||
|
from manila_tempest_tests.tests.api import base
|
||||||
|
|
||||||
|
CONF = config.CONF
|
||||||
|
|
||||||
|
CG_SIMPLE_KEYS = {"id", "name", "links"}
|
||||||
|
CG_DETAIL_REQUIRED_KEYS = {"id", "name", "description", "created_at", "status",
|
||||||
|
"project_id", "host", "links"}
|
||||||
|
CGSNAPSHOT_SIMPLE_KEYS = {"id", "name", "links"}
|
||||||
|
CGSNAPSHOT_DETAIL_REQUIRED_KEYS = {"id", "name", "description", "created_at",
|
||||||
|
"status", "project_id", "links"}
|
||||||
|
|
||||||
|
|
||||||
|
@testtools.skipUnless(CONF.share.run_consistency_group_tests,
|
||||||
|
'Consistency Group tests disabled.')
|
||||||
|
class ConsistencyGroupActionsTest(base.BaseSharesTest):
|
||||||
|
"""Covers consistency group functionality."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resource_setup(cls):
|
||||||
|
super(ConsistencyGroupActionsTest, cls).resource_setup()
|
||||||
|
# Create consistency group
|
||||||
|
cls.cg_name = data_utils.rand_name("tempest-cg-name")
|
||||||
|
cls.cg_desc = data_utils.rand_name("tempest-cg-description")
|
||||||
|
cls.consistency_group = cls.create_consistency_group(
|
||||||
|
name=cls.cg_name,
|
||||||
|
description=cls.cg_desc,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create 2 shares inside consistency group
|
||||||
|
cls.share_name = data_utils.rand_name("tempest-share-name")
|
||||||
|
cls.share_desc = data_utils.rand_name("tempest-share-description")
|
||||||
|
cls.share_size = 1
|
||||||
|
cls.share = cls.create_share(
|
||||||
|
name=cls.share_name,
|
||||||
|
description=cls.share_desc,
|
||||||
|
size=cls.share_size,
|
||||||
|
consistency_group_id=cls.consistency_group['id'],
|
||||||
|
metadata={'key': 'value'},
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.share_name2 = data_utils.rand_name("tempest-share-name")
|
||||||
|
cls.share_desc2 = data_utils.rand_name("tempest-share-description")
|
||||||
|
cls.share_size2 = 2
|
||||||
|
cls.share2 = cls.create_share(
|
||||||
|
name=cls.share_name2,
|
||||||
|
description=cls.share_desc2,
|
||||||
|
size=cls.share_size2,
|
||||||
|
consistency_group_id=cls.consistency_group['id'],
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.cgsnap_name = data_utils.rand_name("tempest-cgsnap-name")
|
||||||
|
cls.cgsnap_desc = data_utils.rand_name("tempest-cgsnap-description")
|
||||||
|
cls.cgsnapshot = cls.create_cgsnapshot_wait_for_active(
|
||||||
|
cls.consistency_group["id"],
|
||||||
|
name=cls.cgsnap_name,
|
||||||
|
description=cls.cgsnap_desc)
|
||||||
|
|
||||||
|
# Create second consistency group for purposes of sorting and snapshot
|
||||||
|
# filtering
|
||||||
|
cls.cg_name2 = data_utils.rand_name("tempest-cg-name")
|
||||||
|
cls.cg_desc2 = data_utils.rand_name("tempest-cg-description")
|
||||||
|
cls.consistency_group2 = cls.create_consistency_group(
|
||||||
|
name=cls.cg_name2,
|
||||||
|
description=cls.cg_desc2,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create 1 share in second consistency group
|
||||||
|
cls.share_name3 = data_utils.rand_name("tempest-share-name")
|
||||||
|
cls.share_desc3 = data_utils.rand_name("tempest-share-description")
|
||||||
|
cls.share3 = cls.create_share(
|
||||||
|
name=cls.share_name3,
|
||||||
|
description=cls.share_desc3,
|
||||||
|
size=cls.share_size,
|
||||||
|
consistency_group_id=cls.consistency_group2['id'],
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.cgsnap_name2 = data_utils.rand_name("tempest-cgsnap-name")
|
||||||
|
cls.cgsnap_desc2 = data_utils.rand_name("tempest-cgsnap-description")
|
||||||
|
cls.cgsnapshot2 = cls.create_cgsnapshot_wait_for_active(
|
||||||
|
cls.consistency_group2['id'],
|
||||||
|
name=cls.cgsnap_name2,
|
||||||
|
description=cls.cgsnap_desc2)
|
||||||
|
|
||||||
|
@test.attr(type=["gate", ])
|
||||||
|
def test_get_consistency_group(self):
|
||||||
|
|
||||||
|
# Get consistency group
|
||||||
|
consistency_group = self.shares_client.get_consistency_group(
|
||||||
|
self.consistency_group['id'])
|
||||||
|
|
||||||
|
# Verify keys
|
||||||
|
actual_keys = set(consistency_group.keys())
|
||||||
|
self.assertTrue(CG_DETAIL_REQUIRED_KEYS.issubset(actual_keys),
|
||||||
|
'Not all required keys returned for consistency '
|
||||||
|
'group %s. Expected at least: %s, found %s' % (
|
||||||
|
consistency_group['id'],
|
||||||
|
CG_DETAIL_REQUIRED_KEYS,
|
||||||
|
actual_keys))
|
||||||
|
|
||||||
|
# Verify values
|
||||||
|
msg = "Expected name: '%s', actual name: '%s'" % (
|
||||||
|
self.cg_name, consistency_group["name"])
|
||||||
|
self.assertEqual(self.cg_name, str(consistency_group["name"]), msg)
|
||||||
|
|
||||||
|
msg = "Expected description: '%s', actual description: '%s'" % (
|
||||||
|
self.cg_desc, consistency_group["description"])
|
||||||
|
self.assertEqual(self.cg_desc, str(consistency_group["description"]),
|
||||||
|
msg)
|
||||||
|
|
||||||
|
@test.attr(type=["gate", ])
|
||||||
|
def test_get_share(self):
|
||||||
|
|
||||||
|
# Get share
|
||||||
|
share = self.shares_client.get_share(self.share['id'])
|
||||||
|
|
||||||
|
# Verify keys
|
||||||
|
expected_keys = {"status", "description", "links", "availability_zone",
|
||||||
|
"created_at", "export_location", "share_proto",
|
||||||
|
"name", "snapshot_id", "id", "size",
|
||||||
|
"consistency_group_id"}
|
||||||
|
actual_keys = set(share.keys())
|
||||||
|
self.assertTrue(expected_keys.issubset(actual_keys),
|
||||||
|
'Not all required keys returned for share %s. '
|
||||||
|
'Expected at least: %s, found %s' % (share['id'],
|
||||||
|
expected_keys,
|
||||||
|
actual_keys))
|
||||||
|
|
||||||
|
# Verify values
|
||||||
|
msg = "Expected name: '%s', actual name: '%s'" % (self.share_name,
|
||||||
|
share["name"])
|
||||||
|
self.assertEqual(self.share_name, str(share["name"]), msg)
|
||||||
|
|
||||||
|
msg = "Expected description: '%s', actual description: '%s'" % (
|
||||||
|
self.share_desc, share["description"])
|
||||||
|
self.assertEqual(self.share_desc, str(share["description"]), msg)
|
||||||
|
|
||||||
|
msg = "Expected size: '%s', actual size: '%s'" % (self.share_size,
|
||||||
|
share["size"])
|
||||||
|
self.assertEqual(self.share_size, int(share["size"]), msg)
|
||||||
|
|
||||||
|
msg = "Expected consistency_group_id: '%s', actual value: '%s'" % (
|
||||||
|
self.consistency_group["id"], share["consistency_group_id"])
|
||||||
|
self.assertEqual(
|
||||||
|
self.consistency_group["id"], share["consistency_group_id"], msg)
|
||||||
|
|
||||||
|
@test.attr(type=["gate", ])
|
||||||
|
def test_list_consistency_groups(self):
|
||||||
|
|
||||||
|
# List consistency groups
|
||||||
|
consistency_groups = self.shares_client.list_consistency_groups()
|
||||||
|
|
||||||
|
# Verify keys
|
||||||
|
[self.assertEqual(CG_SIMPLE_KEYS, set(cg.keys())) for cg in
|
||||||
|
consistency_groups]
|
||||||
|
|
||||||
|
# Consistency group ids are in list exactly once
|
||||||
|
for cg_id in [self.consistency_group["id"],
|
||||||
|
self.consistency_group2["id"]]:
|
||||||
|
gen = [cgid["id"] for cgid in consistency_groups
|
||||||
|
if cgid["id"] == cg_id]
|
||||||
|
msg = ("Expected id %s exactly once in consistency group list" %
|
||||||
|
cg_id)
|
||||||
|
self.assertEqual(1, len(gen), msg)
|
||||||
|
|
||||||
|
@test.attr(type=["gate", ])
|
||||||
|
def test_list_consistency_groups_with_detail(self):
|
||||||
|
|
||||||
|
# List consistency groups
|
||||||
|
consistency_groups = self.shares_client.list_consistency_groups(
|
||||||
|
detailed=True)
|
||||||
|
|
||||||
|
# Verify keys
|
||||||
|
[self.assertTrue(CG_DETAIL_REQUIRED_KEYS.issubset(set(cg.keys())))
|
||||||
|
for cg in consistency_groups]
|
||||||
|
|
||||||
|
# Consistency group ids are in list exactly once
|
||||||
|
for cg_id in [self.consistency_group["id"],
|
||||||
|
self.consistency_group2["id"]]:
|
||||||
|
gen = [cgid["id"] for cgid in consistency_groups
|
||||||
|
if cgid["id"] == cg_id]
|
||||||
|
msg = ("Expected id %s exactly once in consistency group list" %
|
||||||
|
cg_id)
|
||||||
|
self.assertEqual(1, len(gen), msg)
|
||||||
|
|
||||||
|
@test.attr(type=["gate", ])
|
||||||
|
def test_filter_shares_by_consistency_group_id(self):
|
||||||
|
|
||||||
|
shares = self.shares_client.list_shares(detailed=True, params={
|
||||||
|
'consistency_group_id': self.consistency_group['id']})
|
||||||
|
|
||||||
|
share_ids = [share['id'] for share in shares]
|
||||||
|
|
||||||
|
self.assertEqual(2, len(shares),
|
||||||
|
'Incorrect number of shares returned. Expected 2, '
|
||||||
|
'got %s' % len(shares))
|
||||||
|
self.assertIn(self.share['id'], share_ids,
|
||||||
|
'Share %s expected in returned list, but got %s'
|
||||||
|
% (self.share['id'], share_ids))
|
||||||
|
self.assertIn(self.share2['id'], share_ids,
|
||||||
|
'Share %s expected in returned list, but got %s'
|
||||||
|
% (self.share['id'], share_ids))
|
||||||
|
|
||||||
|
@test.attr(type=["gate", ])
|
||||||
|
def test_get_cgsnapshot(self):
|
||||||
|
# Get consistency group
|
||||||
|
consistency_group = self.shares_client.get_consistency_group(
|
||||||
|
self.consistency_group['id'])
|
||||||
|
|
||||||
|
# Verify keys
|
||||||
|
actual_keys = set(consistency_group.keys())
|
||||||
|
self.assertTrue(CG_DETAIL_REQUIRED_KEYS.issubset(actual_keys),
|
||||||
|
'Not all required keys returned for consistency '
|
||||||
|
'group %s. Expected at least: %s, found %s' % (
|
||||||
|
consistency_group['id'],
|
||||||
|
CG_DETAIL_REQUIRED_KEYS,
|
||||||
|
actual_keys))
|
||||||
|
|
||||||
|
# Verify values
|
||||||
|
msg = "Expected name: '%s', actual name: '%s'" % (
|
||||||
|
self.cg_name, consistency_group["name"])
|
||||||
|
self.assertEqual(self.cg_name, str(consistency_group["name"]), msg)
|
||||||
|
|
||||||
|
msg = "Expected description: '%s', actual description: '%s'" % (
|
||||||
|
self.cg_desc, consistency_group["description"])
|
||||||
|
self.assertEqual(self.cg_desc, str(consistency_group["description"]),
|
||||||
|
msg)
|
||||||
|
|
||||||
|
@test.attr(type=["gate", ])
|
||||||
|
def test_get_cgsnapshot_members(self):
|
||||||
|
|
||||||
|
cgsnapshot_members = self.shares_client.list_cgsnapshot_members(
|
||||||
|
self.cgsnapshot['id'])
|
||||||
|
member_share_ids = [member['share_id'] for member in
|
||||||
|
cgsnapshot_members]
|
||||||
|
self.assertEqual(2, len(cgsnapshot_members),
|
||||||
|
'Unexpected number of cgsnapshot members. Expected '
|
||||||
|
'2, got %s.' % len(cgsnapshot_members))
|
||||||
|
# Verify each share is represented in the cgsnapshot appropriately
|
||||||
|
for share_id in [self.share['id'], self.share2['id']]:
|
||||||
|
self.assertIn(share_id, member_share_ids,
|
||||||
|
'Share missing %s missing from cgsnapshot. Found %s.'
|
||||||
|
% (share_id, member_share_ids))
|
||||||
|
for share in [self.share, self.share2]:
|
||||||
|
for member in cgsnapshot_members:
|
||||||
|
if share['id'] == member['share_id']:
|
||||||
|
self.assertEqual(share['size'], member['size'])
|
||||||
|
self.assertEqual(share['share_proto'],
|
||||||
|
member['share_protocol'])
|
||||||
|
# TODO(akerr): Add back assert when bug 1483886 is fixed
|
||||||
|
# self.assertEqual(share['share_type'],
|
||||||
|
# member['share_type_id'])
|
||||||
|
|
||||||
|
@test.attr(type=["gate", "smoke", ])
|
||||||
|
def test_create_consistency_group_from_populated_cgsnapshot(self):
|
||||||
|
|
||||||
|
cgsnapshot_members = self.shares_client.list_cgsnapshot_members(
|
||||||
|
self.cgsnapshot['id'])
|
||||||
|
|
||||||
|
new_consistency_group = self.create_consistency_group(
|
||||||
|
cleanup_in_class=False, source_cgsnapshot_id=self.cgsnapshot['id'])
|
||||||
|
|
||||||
|
new_shares = self.shares_client.list_shares(
|
||||||
|
params={'consistency_group_id': new_consistency_group['id']},
|
||||||
|
detailed=True)
|
||||||
|
|
||||||
|
# Verify each new share is available
|
||||||
|
for share in new_shares:
|
||||||
|
self.assertEqual('available', share['status'],
|
||||||
|
'Share %s is not in available status.'
|
||||||
|
% share['id'])
|
||||||
|
|
||||||
|
# Verify each cgsnapshot member is represented in the new cg
|
||||||
|
# appropriately
|
||||||
|
share_source_member_ids = [share['source_cgsnapshot_member_id'] for
|
||||||
|
share in new_shares]
|
||||||
|
for member in cgsnapshot_members:
|
||||||
|
self.assertIn(member['id'], share_source_member_ids,
|
||||||
|
'cgsnapshot member %s not represented by '
|
||||||
|
'consistency group %s.' % (
|
||||||
|
member['id'], new_consistency_group['id']))
|
||||||
|
for share in new_shares:
|
||||||
|
if share['source_cgsnapshot_member_id'] == member['id']:
|
||||||
|
self.assertEqual(member['size'], share['size'])
|
||||||
|
self.assertEqual(member['share_protocol'],
|
||||||
|
share['share_proto'])
|
||||||
|
# TODO(akerr): Add back assert when bug 1483886 is fixed
|
||||||
|
# self.assertEqual(member['share_type_id'],
|
||||||
|
# share['share_type'])
|
||||||
|
|
||||||
|
|
||||||
|
@testtools.skipUnless(CONF.share.run_consistency_group_tests,
|
||||||
|
'Consistency Group tests disabled.')
|
||||||
|
class ConsistencyGroupRenameTest(base.BaseSharesTest):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resource_setup(cls):
|
||||||
|
super(ConsistencyGroupRenameTest, cls).resource_setup()
|
||||||
|
|
||||||
|
# Create consistency group
|
||||||
|
cls.cg_name = data_utils.rand_name("tempest-cg-name")
|
||||||
|
cls.cg_desc = data_utils.rand_name("tempest-cg-description")
|
||||||
|
cls.consistency_group = cls.create_consistency_group(
|
||||||
|
name=cls.cg_name,
|
||||||
|
description=cls.cg_desc,
|
||||||
|
)
|
||||||
|
|
||||||
|
@test.attr(type=["gate", ])
|
||||||
|
def test_update_consistency_group(self):
|
||||||
|
|
||||||
|
# Get consistency_group
|
||||||
|
consistency_group = self.shares_client.get_consistency_group(
|
||||||
|
self.consistency_group['id'])
|
||||||
|
self.assertEqual(self.cg_name, consistency_group["name"])
|
||||||
|
self.assertEqual(self.cg_desc, consistency_group["description"])
|
||||||
|
|
||||||
|
# Update consistency_group
|
||||||
|
new_name = data_utils.rand_name("tempest-new-name")
|
||||||
|
new_desc = data_utils.rand_name("tempest-new-description")
|
||||||
|
updated = self.shares_client.update_consistency_group(
|
||||||
|
consistency_group["id"], name=new_name, description=new_desc)
|
||||||
|
self.assertEqual(new_name, updated["name"])
|
||||||
|
self.assertEqual(new_desc, updated["description"])
|
||||||
|
|
||||||
|
# Get consistency_group
|
||||||
|
consistency_group = self.shares_client.get_consistency_group(
|
||||||
|
self.consistency_group['id'])
|
||||||
|
self.assertEqual(new_name, consistency_group["name"])
|
||||||
|
self.assertEqual(new_desc, consistency_group["description"])
|
||||||
|
|
||||||
|
@test.attr(type=["gate", ])
|
||||||
|
def test_create_update_read_consistency_group_with_unicode(self):
|
||||||
|
value1 = u'ಠ_ಠ'
|
||||||
|
value2 = u'ಠ_ರೃ'
|
||||||
|
# Create consistency_group
|
||||||
|
consistency_group = self.create_consistency_group(
|
||||||
|
cleanup_in_class=False, name=value1, description=value1)
|
||||||
|
self.assertEqual(value1, consistency_group["name"])
|
||||||
|
self.assertEqual(value1, consistency_group["description"])
|
||||||
|
|
||||||
|
# Update consistency_group
|
||||||
|
updated = self.shares_client.update_consistency_group(
|
||||||
|
consistency_group["id"], name=value2, description=value2)
|
||||||
|
self.assertEqual(value2, updated["name"])
|
||||||
|
self.assertEqual(value2, updated["description"])
|
||||||
|
|
||||||
|
# Get consistency_group
|
||||||
|
consistency_group = self.shares_client.get_consistency_group(
|
||||||
|
consistency_group['id'])
|
||||||
|
self.assertEqual(value2, consistency_group["name"])
|
||||||
|
self.assertEqual(value2, consistency_group["description"])
|
130
manila_tempest_tests/tests/api/test_consistency_groups.py
Normal file
130
manila_tempest_tests/tests/api/test_consistency_groups.py
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
# Copyright 2015 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 import config # noqa
|
||||||
|
from tempest import test # noqa
|
||||||
|
from tempest_lib import exceptions as lib_exc # noqa
|
||||||
|
import testtools # noqa
|
||||||
|
|
||||||
|
from manila_tempest_tests.tests.api import base
|
||||||
|
|
||||||
|
CONF = config.CONF
|
||||||
|
CG_REQUIRED_ELEMENTS = {"id", "name", "description", "created_at", "status",
|
||||||
|
"share_types", "project_id", "host", "links"}
|
||||||
|
CGSNAPSHOT_REQUIRED_ELEMENTS = {"id", "name", "description", "created_at",
|
||||||
|
"status", "project_id", "links"}
|
||||||
|
|
||||||
|
|
||||||
|
@testtools.skipUnless(CONF.share.run_consistency_group_tests,
|
||||||
|
'Consistency Group tests disabled.')
|
||||||
|
class ConsistencyGroupsTest(base.BaseSharesTest):
|
||||||
|
"""Covers consistency group functionality."""
|
||||||
|
|
||||||
|
@test.attr(type=["gate", ])
|
||||||
|
def test_create_populate_delete_consistency_group(self):
|
||||||
|
# Create a consistency group
|
||||||
|
consistency_group = self.create_consistency_group(
|
||||||
|
cleanup_in_class=False)
|
||||||
|
self.assertTrue(CG_REQUIRED_ELEMENTS.issubset(
|
||||||
|
consistency_group.keys()),
|
||||||
|
'At least one expected element missing from consistency group '
|
||||||
|
'response. Expected %(expected)s, got %(actual)s.' % {
|
||||||
|
"expected": CG_REQUIRED_ELEMENTS,
|
||||||
|
"actual": consistency_group.keys()})
|
||||||
|
# Populate
|
||||||
|
share = self.create_share(consistency_group_id=consistency_group['id'],
|
||||||
|
cleanup_in_class=False)
|
||||||
|
# Delete
|
||||||
|
params = {"consistency_group_id": consistency_group['id']}
|
||||||
|
self.shares_client.delete_share(share['id'], params=params)
|
||||||
|
self.shares_client.wait_for_resource_deletion(share_id=share['id'])
|
||||||
|
self.shares_client.delete_consistency_group(consistency_group['id'])
|
||||||
|
self.shares_client.wait_for_resource_deletion(
|
||||||
|
cg_id=consistency_group['id'])
|
||||||
|
|
||||||
|
# Verify
|
||||||
|
self.assertRaises(lib_exc.NotFound,
|
||||||
|
self.shares_client.get_consistency_group,
|
||||||
|
consistency_group['id'])
|
||||||
|
self.assertRaises(lib_exc.NotFound,
|
||||||
|
self.shares_client.get_share,
|
||||||
|
share['id'])
|
||||||
|
|
||||||
|
@test.attr(type=["gate", ])
|
||||||
|
def test_create_delete_empty_cgsnapshot(self):
|
||||||
|
# Create base consistency group
|
||||||
|
consistency_group = self.create_consistency_group(
|
||||||
|
cleanup_in_class=False)
|
||||||
|
# Create cgsnapshot
|
||||||
|
cgsnapshot = self.create_cgsnapshot_wait_for_active(
|
||||||
|
consistency_group["id"], cleanup_in_class=False)
|
||||||
|
|
||||||
|
self.assertTrue(CGSNAPSHOT_REQUIRED_ELEMENTS.issubset(
|
||||||
|
cgsnapshot.keys()),
|
||||||
|
'At least one expected element missing from cgsnapshot response. '
|
||||||
|
'Expected %(expected)s, got %(actual)s.' % {
|
||||||
|
"expected": CGSNAPSHOT_REQUIRED_ELEMENTS,
|
||||||
|
"actual": cgsnapshot.keys()})
|
||||||
|
|
||||||
|
cgsnapshot_members = self.shares_client.list_cgsnapshot_members(
|
||||||
|
cgsnapshot['id'])
|
||||||
|
|
||||||
|
self.assertEmpty(cgsnapshot_members,
|
||||||
|
'Expected 0 cgsnapshot members, got %s' % len(
|
||||||
|
cgsnapshot_members))
|
||||||
|
|
||||||
|
# delete snapshot
|
||||||
|
self.shares_client.delete_cgsnapshot(cgsnapshot["id"])
|
||||||
|
self.shares_client.wait_for_resource_deletion(
|
||||||
|
cgsnapshot_id=cgsnapshot["id"])
|
||||||
|
self.assertRaises(lib_exc.NotFound,
|
||||||
|
self.shares_client.get_cgsnapshot, cgsnapshot['id'])
|
||||||
|
|
||||||
|
@test.attr(type=["gate", "smoke", ])
|
||||||
|
def test_create_consistency_group_from_empty_cgsnapshot(self):
|
||||||
|
# Create base consistency group
|
||||||
|
consistency_group = self.create_consistency_group(
|
||||||
|
cleanup_in_class=False)
|
||||||
|
|
||||||
|
# Create cgsnapshot
|
||||||
|
cgsnapshot = self.create_cgsnapshot_wait_for_active(
|
||||||
|
consistency_group["id"], cleanup_in_class=False)
|
||||||
|
|
||||||
|
cgsnapshot_members = self.shares_client.list_cgsnapshot_members(
|
||||||
|
cgsnapshot['id'])
|
||||||
|
|
||||||
|
self.assertEmpty(cgsnapshot_members,
|
||||||
|
'Expected 0 cgsnapshot members, got %s' % len(
|
||||||
|
cgsnapshot_members))
|
||||||
|
|
||||||
|
new_consistency_group = self.create_consistency_group(
|
||||||
|
cleanup_in_class=False, source_cgsnapshot_id=cgsnapshot['id'])
|
||||||
|
|
||||||
|
new_shares = self.shares_client.list_shares(
|
||||||
|
params={'consistency_group_id': new_consistency_group['id']})
|
||||||
|
|
||||||
|
self.assertEmpty(new_shares,
|
||||||
|
'Expected 0 new shares, got %s' % len(new_shares))
|
||||||
|
|
||||||
|
msg = 'Expected cgsnapshot_id %s as source of share %s' % (
|
||||||
|
cgsnapshot['id'], new_consistency_group['source_cgsnapshot_id'])
|
||||||
|
self.assertEqual(new_consistency_group['source_cgsnapshot_id'],
|
||||||
|
cgsnapshot['id'], msg)
|
||||||
|
|
||||||
|
msg = 'Unexpected share_types on new consistency group. Expected %s, ' \
|
||||||
|
'got %s.' % (consistency_group['share_types'],
|
||||||
|
new_consistency_group['share_types'])
|
||||||
|
self.assertEqual(sorted(consistency_group['share_types']),
|
||||||
|
sorted(new_consistency_group['share_types']), msg)
|
@ -0,0 +1,205 @@
|
|||||||
|
# Copyright 2015 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 import config
|
||||||
|
from tempest import test
|
||||||
|
from tempest_lib.common.utils import data_utils
|
||||||
|
from tempest_lib import exceptions as lib_exc
|
||||||
|
import testtools
|
||||||
|
|
||||||
|
from manila_tempest_tests.tests.api import base
|
||||||
|
|
||||||
|
CONF = config.CONF
|
||||||
|
|
||||||
|
|
||||||
|
@testtools.skipUnless(CONF.share.run_consistency_group_tests,
|
||||||
|
'Consistency Group tests disabled.')
|
||||||
|
class ConsistencyGroupsNegativeTest(base.BaseSharesTest):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resource_setup(cls):
|
||||||
|
super(ConsistencyGroupsNegativeTest, cls).resource_setup()
|
||||||
|
# Create a consistency group
|
||||||
|
cls.cg_name = data_utils.rand_name("tempest-cg-name")
|
||||||
|
cls.cg_desc = data_utils.rand_name("tempest-cg-description")
|
||||||
|
cls.consistency_group = cls.create_consistency_group(
|
||||||
|
name=cls.cg_name,
|
||||||
|
description=cls.cg_desc
|
||||||
|
)
|
||||||
|
# Create a share in the consistency group
|
||||||
|
cls.share_name = data_utils.rand_name("tempest-share-name")
|
||||||
|
cls.share_desc = data_utils.rand_name("tempest-share-description")
|
||||||
|
cls.share_size = 1
|
||||||
|
cls.share = cls.create_share(
|
||||||
|
name=cls.share_name,
|
||||||
|
description=cls.share_desc,
|
||||||
|
size=cls.share_size,
|
||||||
|
consistency_group_id=cls.consistency_group['id']
|
||||||
|
)
|
||||||
|
# Create a cgsnapshot of the consistency group
|
||||||
|
cls.cgsnap_name = data_utils.rand_name("tempest-cgsnap-name")
|
||||||
|
cls.cgsnap_desc = data_utils.rand_name("tempest-cgsnap-description")
|
||||||
|
cls.cgsnapshot = cls.create_cgsnapshot_wait_for_active(
|
||||||
|
cls.consistency_group["id"],
|
||||||
|
name=cls.cgsnap_name,
|
||||||
|
description=cls.cgsnap_desc)
|
||||||
|
|
||||||
|
@test.attr(type=["negative", "smoke", "gate", ])
|
||||||
|
def test_create_cg_with_invalid_source_cgsnapshot_id_value(
|
||||||
|
self):
|
||||||
|
self.assertRaises(lib_exc.BadRequest,
|
||||||
|
self.create_consistency_group,
|
||||||
|
source_cgsnapshot_id='foobar',
|
||||||
|
cleanup_in_class=False)
|
||||||
|
|
||||||
|
@test.attr(type=["negative", "smoke", "gate", ])
|
||||||
|
def test_create_cg_with_nonexistent_source_cgsnapshot_id_value(self):
|
||||||
|
self.assertRaises(lib_exc.BadRequest,
|
||||||
|
self.create_consistency_group,
|
||||||
|
source_cgsnapshot_id=self.share['id'],
|
||||||
|
cleanup_in_class=False)
|
||||||
|
|
||||||
|
@test.attr(type=["negative", "smoke", "gate", ])
|
||||||
|
def test_create_cg_with_invalid_share_network_id_value(
|
||||||
|
self):
|
||||||
|
self.assertRaises(lib_exc.BadRequest,
|
||||||
|
self.create_consistency_group,
|
||||||
|
share_network_id='foobar',
|
||||||
|
cleanup_in_class=False)
|
||||||
|
|
||||||
|
@test.attr(type=["negative", "smoke", "gate", ])
|
||||||
|
def test_create_cg_with_nonexistent_share_network_id_value(self):
|
||||||
|
self.assertRaises(lib_exc.BadRequest,
|
||||||
|
self.create_consistency_group,
|
||||||
|
share_network_id=self.share['id'],
|
||||||
|
cleanup_in_class=False)
|
||||||
|
|
||||||
|
@test.attr(type=["negative", "smoke", "gate", ])
|
||||||
|
def test_create_cg_with_invalid_share_type_id_value(
|
||||||
|
self):
|
||||||
|
self.assertRaises(lib_exc.BadRequest,
|
||||||
|
self.create_consistency_group,
|
||||||
|
share_type_ids=['foobar'],
|
||||||
|
cleanup_in_class=False)
|
||||||
|
|
||||||
|
@test.attr(type=["negative", "smoke", "gate", ])
|
||||||
|
def test_create_cg_with_nonexistent_share_type_id_value(self):
|
||||||
|
self.assertRaises(lib_exc.BadRequest,
|
||||||
|
self.create_consistency_group,
|
||||||
|
share_type_ids=[self.share['id']],
|
||||||
|
cleanup_in_class=False)
|
||||||
|
|
||||||
|
@test.attr(type=["negative", "smoke", "gate", ])
|
||||||
|
def test_create_cgsnapshot_with_invalid_cg_id_value(
|
||||||
|
self):
|
||||||
|
self.assertRaises(lib_exc.BadRequest,
|
||||||
|
self.create_cgsnapshot_wait_for_active,
|
||||||
|
'foobar',
|
||||||
|
cleanup_in_class=False)
|
||||||
|
|
||||||
|
@test.attr(type=["negative", "smoke", "gate", ])
|
||||||
|
def test_create_cgsnapshot_with_nonexistent_cg_id_value(self):
|
||||||
|
self.assertRaises(lib_exc.BadRequest,
|
||||||
|
self.create_cgsnapshot_wait_for_active,
|
||||||
|
self.share['id'],
|
||||||
|
cleanup_in_class=False)
|
||||||
|
|
||||||
|
@test.attr(type=["negative", "smoke", "gate", ])
|
||||||
|
def test_get_cg_with_wrong_id(self):
|
||||||
|
self.assertRaises(lib_exc.NotFound,
|
||||||
|
self.shares_client.get_consistency_group,
|
||||||
|
"wrong_consistency_group_id")
|
||||||
|
|
||||||
|
@test.attr(type=["negative", "smoke", "gate", ])
|
||||||
|
def test_get_cg_without_passing_cg_id(self):
|
||||||
|
self.assertRaises(lib_exc.NotFound,
|
||||||
|
self.shares_client.get_consistency_group, '')
|
||||||
|
|
||||||
|
@test.attr(type=["negative", "smoke", "gate", ])
|
||||||
|
def test_update_cg_with_wrong_id(self):
|
||||||
|
self.assertRaises(lib_exc.NotFound,
|
||||||
|
self.shares_client.update_consistency_group,
|
||||||
|
'wrong_consistency_group_id',
|
||||||
|
name='new_name',
|
||||||
|
description='new_description')
|
||||||
|
|
||||||
|
@test.attr(type=["negative", "smoke", "gate", ])
|
||||||
|
def test_delete_cg_with_wrong_id(self):
|
||||||
|
self.assertRaises(lib_exc.NotFound,
|
||||||
|
self.shares_client.delete_consistency_group,
|
||||||
|
"wrong_consistency_group_id")
|
||||||
|
|
||||||
|
@test.attr(type=["negative", "smoke", "gate", ])
|
||||||
|
def test_delete_cg_without_passing_cg_id(self):
|
||||||
|
self.assertRaises(lib_exc.NotFound,
|
||||||
|
self.shares_client.delete_consistency_group, '')
|
||||||
|
|
||||||
|
@test.attr(type=["negative", "gate", ])
|
||||||
|
def test_delete_cg_in_use_by_cgsnapshot(self):
|
||||||
|
# Attempt delete of share type
|
||||||
|
self.assertRaises(lib_exc.Conflict,
|
||||||
|
self.shares_client.delete_consistency_group,
|
||||||
|
self.consistency_group['id'])
|
||||||
|
|
||||||
|
@test.attr(type=["negative", "gate", ])
|
||||||
|
def test_delete_share_in_use_by_cgsnapshot(self):
|
||||||
|
# Attempt delete of share type
|
||||||
|
params = {'consistency_group_id': self.share['consistency_group_id']}
|
||||||
|
self.assertRaises(lib_exc.Forbidden,
|
||||||
|
self.shares_client.delete_share,
|
||||||
|
self.share['id'],
|
||||||
|
params=params)
|
||||||
|
|
||||||
|
@test.attr(type=["negative", "smoke", "gate", ])
|
||||||
|
def test_delete_cg_containing_a_share(self):
|
||||||
|
self.assertRaises(lib_exc.Conflict,
|
||||||
|
self.shares_client.delete_consistency_group,
|
||||||
|
self.consistency_group['id'])
|
||||||
|
# Verify consistency group is not put into error state from conflict
|
||||||
|
cg = self.shares_client.get_consistency_group(
|
||||||
|
self.consistency_group['id'])
|
||||||
|
self.assertEqual('available', cg['status'])
|
||||||
|
|
||||||
|
@test.attr(type=["negative", "smoke", "gate", ])
|
||||||
|
def test_filter_shares_on_invalid_cg_id(self):
|
||||||
|
shares = self.shares_client.list_shares(detailed=True, params={
|
||||||
|
'consistency_group_id': 'foobar'})
|
||||||
|
|
||||||
|
self.assertEqual(0, len(shares), 'Incorrect number of shares '
|
||||||
|
'returned. Expected 0, got %s.' %
|
||||||
|
len(shares))
|
||||||
|
|
||||||
|
@test.attr(type=["negative", "smoke", "gate", ])
|
||||||
|
def test_filter_shares_on_nonexistent_cg_id(self):
|
||||||
|
shares = self.shares_client.list_shares(detailed=True, params={
|
||||||
|
'consistency_group_id': self.share['id']})
|
||||||
|
|
||||||
|
self.assertEqual(0, len(shares), 'Incorrect number of shares '
|
||||||
|
'returned. Expected 0, got %s.' %
|
||||||
|
len(shares))
|
||||||
|
|
||||||
|
@test.attr(type=["negative", "smoke", "gate", ])
|
||||||
|
def test_filter_shares_on_empty_cg_id(self):
|
||||||
|
consistency_group = self.create_consistency_group(
|
||||||
|
name='tempest_cg',
|
||||||
|
description='tempest_cg_desc',
|
||||||
|
cleanup_in_class=False,
|
||||||
|
)
|
||||||
|
shares = self.shares_client.list_shares(detailed=True, params={
|
||||||
|
'consistency_group_id': consistency_group['id']})
|
||||||
|
|
||||||
|
self.assertEqual(0, len(shares), 'Incorrect number of shares '
|
||||||
|
'returned. Expected 0, got %s.' %
|
||||||
|
len(shares))
|
Loading…
Reference in New Issue
Block a user