From 1002642674ff4a72949b27eb0892ec4c8590a825 Mon Sep 17 00:00:00 2001 From: Andrew Kerr Date: Wed, 1 Jun 2016 15:32:43 -0400 Subject: [PATCH] [Tempest] Add functional tests for share groups feature Add functional tempest tests to new 'share group' feature [1]. [1] I79a80a62ae4e0015d6161edc2b93fd1f9ba69537 Co-Authored-By: Andrew Kerr Co-Authored-By: Valeriy Ponomaryov Partially-implements-blueprint: manila-share-groups Depends-On: I8e29baed62355fc31caeec9c7a66eaebfcbdf184 Change-Id: I820eb959082995d961b1be992e4b2d1d1a985c1c --- contrib/ci/post_test_hook.sh | 15 +- devstack/plugin.sh | 15 + devstack/settings | 10 + manila_tempest_tests/common/constants.py | 21 + manila_tempest_tests/config.py | 9 +- .../services/share/v2/json/shares_client.py | 411 ++++++++++++------ manila_tempest_tests/share_exceptions.py | 12 +- .../admin/test_consistency_group_actions.py | 151 ------- .../api/admin/test_consistency_groups.py | 99 ----- .../admin/test_consistency_groups_negative.py | 292 ------------- .../tests/api/admin/test_share_group_types.py | 236 ++++++++++ .../admin/test_share_group_types_negative.py | 128 ++++++ .../tests/api/admin/test_share_groups.py | 175 ++++++++ manila_tempest_tests/tests/api/base.py | 111 +++-- .../api/test_consistency_group_actions.py | 377 ---------------- .../tests/api/test_consistency_groups.py | 144 ------ .../api/test_consistency_groups_negative.py | 226 ---------- .../tests/api/test_share_group_actions.py | 395 +++++++++++++++++ .../tests/api/test_share_groups.py | 165 +++++++ .../tests/api/test_share_groups_negative.py | 239 ++++++++++ 20 files changed, 1757 insertions(+), 1474 deletions(-) delete mode 100644 manila_tempest_tests/tests/api/admin/test_consistency_group_actions.py delete mode 100644 manila_tempest_tests/tests/api/admin/test_consistency_groups.py delete mode 100644 manila_tempest_tests/tests/api/admin/test_consistency_groups_negative.py create mode 100644 manila_tempest_tests/tests/api/admin/test_share_group_types.py create mode 100644 manila_tempest_tests/tests/api/admin/test_share_group_types_negative.py create mode 100644 manila_tempest_tests/tests/api/admin/test_share_groups.py delete mode 100644 manila_tempest_tests/tests/api/test_consistency_group_actions.py delete mode 100644 manila_tempest_tests/tests/api/test_consistency_groups.py delete mode 100644 manila_tempest_tests/tests/api/test_consistency_groups_negative.py create mode 100644 manila_tempest_tests/tests/api/test_share_group_actions.py create mode 100644 manila_tempest_tests/tests/api/test_share_groups.py create mode 100644 manila_tempest_tests/tests/api/test_share_groups_negative.py diff --git a/contrib/ci/post_test_hook.sh b/contrib/ci/post_test_hook.sh index d66ea8db89..bd3bd76cf2 100755 --- a/contrib/ci/post_test_hook.sh +++ b/contrib/ci/post_test_hook.sh @@ -70,7 +70,7 @@ RUN_MANILA_QUOTA_TESTS=${RUN_MANILA_QUOTA_TESTS:-True} RUN_MANILA_SHRINK_TESTS=${RUN_MANILA_SHRINK_TESTS:-True} RUN_MANILA_SNAPSHOT_TESTS=${RUN_MANILA_SNAPSHOT_TESTS:-True} RUN_MANILA_REVERT_TO_SNAPSHOT_TESTS=${RUN_MANILA_REVERT_TO_SNAPSHOT_TESTS:-False} -RUN_MANILA_CG_TESTS=${RUN_MANILA_CG_TESTS:-False} +RUN_MANILA_SG_TESTS=${RUN_MANILA_SG_TESTS:-${RUN_MANILA_CG_TESTS:-True}} RUN_MANILA_MANAGE_TESTS=${RUN_MANILA_MANAGE_TESTS:-True} RUN_MANILA_MANAGE_SNAPSHOT_TESTS=${RUN_MANILA_MANAGE_SNAPSHOT_TESTS:-False} RUN_MANILA_REPLICATION_TESTS=${RUN_MANILA_REPLICATION_TESTS:-False} @@ -157,12 +157,13 @@ elif [[ "$DRIVER" == "generic" ]]; then iniset $TEMPEST_CONFIG share enable_protocols nfs fi MANILA_TESTS="(^manila_tempest_tests.tests.api)(?=.*\[.*\bbackend\b.*\])" + RUN_MANILA_SG_TESTS=False fi if [[ "$DRIVER" == "lvm" ]]; then MANILA_TESTS="(^manila_tempest_tests.tests)(?=.*\[.*\bbackend\b.*\])" MANILA_TEMPEST_CONCURRENCY=8 - RUN_MANILA_CG_TESTS=False + RUN_MANILA_SG_TESTS=False RUN_MANILA_MANAGE_TESTS=False RUN_MANILA_HOST_ASSISTED_MIGRATION_TESTS=True RUN_MANILA_SHRINK_TESTS=False @@ -185,7 +186,7 @@ if [[ "$DRIVER" == "lvm" ]]; then elif [[ "$DRIVER" == "zfsonlinux" ]]; then MANILA_TESTS="(^manila_tempest_tests.tests)(?=.*\[.*\bbackend\b.*\])" MANILA_TEMPEST_CONCURRENCY=8 - RUN_MANILA_CG_TESTS=False + RUN_MANILA_SG_TESTS=False RUN_MANILA_MANAGE_TESTS=True RUN_MANILA_MANAGE_SNAPSHOT_TESTS=True RUN_MANILA_DRIVER_ASSISTED_MIGRATION_TESTS=True @@ -207,7 +208,7 @@ elif [[ "$DRIVER" == "zfsonlinux" ]]; then iniset $TEMPEST_CONFIG share capability_snapshot_support True elif [[ "$DRIVER" == "dummy" ]]; then MANILA_TEMPEST_CONCURRENCY=24 - RUN_MANILA_CG_TESTS=False + RUN_MANILA_SG_TESTS=True RUN_MANILA_MANAGE_TESTS=False RUN_MANILA_DRIVER_ASSISTED_MIGRATION_TESTS=True RUN_MANILA_REVERT_TO_SNAPSHOT_TESTS=True @@ -226,7 +227,7 @@ elif [[ "$DRIVER" == "dummy" ]]; then elif [[ "$DRIVER" == "container" ]]; then MANILA_TESTS="(^manila_tempest_tests.tests.api)(?=.*\[.*\bbackend\b.*\])" MANILA_TEMPEST_CONCURRENCY=8 - RUN_MANILA_CG_TESTS=False + RUN_MANILA_SG_TESTS=False RUN_MANILA_MANAGE_TESTS=False RUN_MANILA_QUOTA_TESTS=False RUN_MANILA_SHRINK_TESTS=False @@ -250,8 +251,8 @@ iniset $TEMPEST_CONFIG share run_snapshot_tests $RUN_MANILA_SNAPSHOT_TESTS # Enable revert to snapshot tests iniset $TEMPEST_CONFIG share run_revert_to_snapshot_tests $RUN_MANILA_REVERT_TO_SNAPSHOT_TESTS -# Enable consistency group tests -iniset $TEMPEST_CONFIG share run_consistency_group_tests $RUN_MANILA_CG_TESTS +# Enable share group tests +iniset $TEMPEST_CONFIG share run_share_group_tests $RUN_MANILA_SG_TESTS # Enable manage/unmanage tests iniset $TEMPEST_CONFIG share run_manage_unmanage_tests $RUN_MANILA_MANAGE_TESTS diff --git a/devstack/plugin.sh b/devstack/plugin.sh index 418ae30c6f..aa33c91726 100755 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -181,6 +181,7 @@ function configure_manila { iniset $MANILA_CONF DEFAULT osapi_share_extension manila.api.contrib.standard_extensions iniset $MANILA_CONF DEFAULT state_path $MANILA_STATE_PATH iniset $MANILA_CONF DEFAULT default_share_type $MANILA_DEFAULT_SHARE_TYPE + iniset $MANILA_CONF DEFAULT default_share_group_type $MANILA_DEFAULT_SHARE_GROUP_TYPE if ! [[ -z $MANILA_SHARE_MIGRATION_PERIOD_TASK_INTERVAL ]]; then iniset $MANILA_CONF DEFAULT migration_driver_continue_update_interval $MANILA_SHARE_MIGRATION_PERIOD_TASK_INTERVAL @@ -455,6 +456,17 @@ function create_manila_accounts { "$MANILA_SERVICE_PROTOCOL://$MANILA_SERVICE_HOST:$MANILA_SERVICE_PORT/v2/\$(tenant_id)s" } +# create_default_share_group_type - create share group type that will be set as default. +function create_default_share_group_type { + local type_exists=$( manila share-group-type-list | grep " $MANILA_DEFAULT_SHARE_GROUP_TYPE " ) + if [[ -z $type_exists ]]; then + manila share-group-type-create $MANILA_DEFAULT_SHARE_GROUP_TYPE $MANILA_DEFAULT_SHARE_TYPE + fi + if [[ $MANILA_DEFAULT_SHARE_GROUP_TYPE_SPECS ]]; then + manila share-group-type-key $MANILA_DEFAULT_SHARE_GROUP_TYPE set $MANILA_DEFAULT_SHARE_GROUP_TYPE_SPECS + fi +} + # create_default_share_type - create share type that will be set as default. function create_default_share_type { echo "Waiting for Manila API to start..." @@ -915,6 +927,9 @@ elif [[ "$1" == "stack" && "$2" == "extra" ]]; then echo_summary "Creating Manila default share type" create_default_share_type + echo_summary "Creating Manila default share group type" + create_default_share_group_type + echo_summary "Creating Manila custom share types" create_custom_share_types diff --git a/devstack/settings b/devstack/settings index 04fcff02ee..6b304b908a 100644 --- a/devstack/settings +++ b/devstack/settings @@ -60,6 +60,16 @@ MANILA_DEFAULT_SHARE_TYPE_EXTRA_SPECS=${MANILA_DEFAULT_SHARE_TYPE_EXTRA_SPECS:-' MANILA_DHSS_TRUE_SHARE_TYPE_EXTRA_SPECS=${MANILA_DHSS_TRUE_SHARE_TYPE_EXTRA_SPECS:-$MANILA_DEFAULT_SHARE_TYPE_EXTRA_SPECS} MANILA_DHSS_FALSE_SHARE_TYPE_EXTRA_SPECS=${MANILA_DHSS_FALSE_SHARE_TYPE_EXTRA_SPECS:-$MANILA_DEFAULT_SHARE_TYPE_EXTRA_SPECS} +# Share groups and their specs +MANILA_DEFAULT_SHARE_GROUP_TYPE=${MANILA_DEFAULT_SHARE_GROUP_TYPE:-default} +# MANILA_DEFAULT_SHARE_GROUP_TYPE_SPECS is expected to contain key-value pairs, +# that should be assigned to default share group type. Both - qualified and unqualified specs are supported. +# Pairs are separated by spaces, value is assigned to key using sign of equality. Examples: +# MANILA_DEFAULT_SHARE_GROUP_TYPE_SPECS='foo=bar' +# MANILA_DEFAULT_SHARE_GROUP_TYPE_SPECS='foo=bar quuz=xyzzy' +# MANILA_DEFAULT_SHARE_GROUP_TYPE_SPECS='foo=bar quuz=xyzzy fakeprefix:baz=waldo' +MANILA_DEFAULT_SHARE_GROUP_TYPE_SPECS=${MANILA_DEFAULT_SHARE_GROUP_TYPE_SPECS:-''} + # Public facing bits MANILA_SERVICE_HOST=${MANILA_SERVICE_HOST:-$SERVICE_HOST} MANILA_SERVICE_PORT=${MANILA_SERVICE_PORT:-8786} diff --git a/manila_tempest_tests/common/constants.py b/manila_tempest_tests/common/constants.py index a478487040..4ca87d2737 100644 --- a/manila_tempest_tests/common/constants.py +++ b/manila_tempest_tests/common/constants.py @@ -60,3 +60,24 @@ REVERT_TO_SNAPSHOT_SUPPORT = 'revert_to_snapshot_support' STATUS_RESTORING = 'restoring' STATUS_REVERTING = 'reverting' STATUS_REVERTING_ERROR = 'reverting_error' + +# Share groups +MIN_SHARE_GROUP_MICROVERSION = '2.31' +SHARE_GROUP_SIMPLE_KEYS = { + 'id', 'name', 'links', +} +SHARE_GROUP_DETAIL_REQUIRED_KEYS = { + 'id', 'name', 'description', 'created_at', 'status', 'share_types', + 'project_id', 'host', 'links', 'share_group_type_id', +} +SHARE_GROUP_SNAPSHOT_SIMPLE_KEYS = { + 'id', 'name', 'links', +} +SHARE_GROUP_SNAPSHOT_DETAIL_REQUIRED_KEYS = { + 'id', 'name', 'description', 'created_at', 'status', 'project_id', + 'links', 'share_group_id', 'members', +} + +SHARE_GROUP_TYPE_REQUIRED_KEYS = { + 'id', 'name', 'share_types', 'is_public', 'group_specs', +} diff --git a/manila_tempest_tests/config.py b/manila_tempest_tests/config.py index d25cb8dd68..9af95eb710 100644 --- a/manila_tempest_tests/config.py +++ b/manila_tempest_tests/config.py @@ -170,11 +170,10 @@ ShareGroup = [ help="Defines whether to run tests that revert shares " "to snapshots or not. Enable this feature if used " "driver supports it."), - cfg.BoolOpt("run_consistency_group_tests", - default=False, - help="Defines whether to run consistency group tests or not. " - "Disable this feature if used driver doesn't support " - "it."), + cfg.BoolOpt("run_share_group_tests", + default=True, + deprecated_name="run_consistency_group_tests", + help="Defines whether to run share group tests or not."), cfg.BoolOpt("run_replication_tests", default=False, help="Defines whether to run replication tests or not. " diff --git a/manila_tempest_tests/services/share/v2/json/shares_client.py b/manila_tempest_tests/services/share/v2/json/shares_client.py index aa176cb452..a524051655 100644 --- a/manila_tempest_tests/services/share/v2/json/shares_client.py +++ b/manila_tempest_tests/services/share/v2/json/shares_client.py @@ -173,12 +173,16 @@ class SharesV2Client(shares_client.SharesClient): if "share_instance_id" in kwargs: return self._is_resource_deleted( self.get_share_instance, kwargs.get("share_instance_id")) - elif "cg_id" in kwargs: + elif "share_group_id" in kwargs: return self._is_resource_deleted( - self.get_consistency_group, kwargs.get("cg_id")) - elif "cgsnapshot_id" in kwargs: + self.get_share_group, kwargs.get("share_group_id")) + elif "share_group_snapshot_id" in kwargs: return self._is_resource_deleted( - self.get_cgsnapshot, kwargs.get("cgsnapshot_id")) + self.get_share_group_snapshot, + kwargs.get("share_group_snapshot_id")) + elif "share_group_type_id" in kwargs: + return self._is_resource_deleted( + self.get_share_group_type, kwargs.get("share_group_type_id")) elif "replica_id" in kwargs: return self._is_resource_deleted( self.get_share_replica, kwargs.get("replica_id")) @@ -192,8 +196,9 @@ class SharesV2Client(shares_client.SharesClient): name=None, snapshot_id=None, description=None, metadata=None, share_network_id=None, share_type_id=None, is_public=False, - consistency_group_id=None, availability_zone=None, - version=LATEST_MICROVERSION): + share_group_id=None, availability_zone=None, + version=LATEST_MICROVERSION, experimental=False): + headers = EXPERIMENTAL if experimental else None metadata = metadata or {} if name is None: name = data_utils.rand_name("tempest-created-share") @@ -222,29 +227,37 @@ class SharesV2Client(shares_client.SharesClient): post_body["share"]["share_network_id"] = share_network_id if share_type_id: post_body["share"]["share_type"] = share_type_id - if consistency_group_id: - post_body["share"]["consistency_group_id"] = consistency_group_id + if share_group_id: + post_body["share"]["share_group_id"] = share_group_id body = json.dumps(post_body) - resp, body = self.post("shares", body, version=version) + resp, body = self.post("shares", body, headers=headers, + extra_headers=experimental, version=version) self.expected_success(200, resp.status) return self._parse_resp(body) def list_shares(self, detailed=False, params=None, - version=LATEST_MICROVERSION): + version=LATEST_MICROVERSION, experimental=False): """Get list of shares w/o filters.""" + headers = EXPERIMENTAL if experimental else None uri = 'shares/detail' if detailed else 'shares' uri += '?%s' % urlparse.urlencode(params) if params else '' - resp, body = self.get(uri, version=version) + resp, body = self.get(uri, headers=headers, extra_headers=experimental, + version=version) self.expected_success(200, resp.status) return self._parse_resp(body) def list_shares_with_detail(self, params=None, - version=LATEST_MICROVERSION): + version=LATEST_MICROVERSION, + experimental=False): """Get detailed list of shares w/o filters.""" - return self.list_shares(detailed=True, params=params, version=version) + return self.list_shares(detailed=True, params=params, + version=version, experimental=experimental) - def get_share(self, share_id, version=LATEST_MICROVERSION): - resp, body = self.get("shares/%s" % share_id, version=version) + def get_share(self, share_id, version=LATEST_MICROVERSION, + experimental=False): + headers = EXPERIMENTAL if experimental else None + resp, body = self.get("shares/%s" % share_id, headers=headers, + extra_headers=experimental, version=version) self.expected_success(200, resp.status) return self._parse_resp(body) @@ -881,62 +894,68 @@ class SharesV2Client(shares_client.SharesClient): ############### - def create_consistency_group(self, name=None, description=None, - share_type_ids=(), share_network_id=None, - source_cgsnapshot_id=None, - version=LATEST_MICROVERSION): - """Create a new consistency group.""" - uri = 'consistency-groups' + def create_share_group(self, name=None, description=None, + share_group_type_id=None, share_type_ids=(), + share_network_id=None, + source_share_group_snapshot_id=None, + availability_zone=None, + version=LATEST_MICROVERSION): + """Create a new share group.""" + uri = 'share-groups' post_body = {} if name: post_body['name'] = name if description: post_body['description'] = description + if share_group_type_id: + post_body['share_group_type_id'] = share_group_type_id if share_type_ids: post_body['share_types'] = share_type_ids - if source_cgsnapshot_id: - post_body['source_cgsnapshot_id'] = source_cgsnapshot_id + if source_share_group_snapshot_id: + post_body['source_share_group_snapshot_id'] = ( + source_share_group_snapshot_id) if share_network_id: post_body['share_network_id'] = share_network_id - body = json.dumps({'consistency_group': post_body}) + if availability_zone: + post_body['availability_zone'] = availability_zone + body = json.dumps({'share_group': post_body}) + resp, body = self.post(uri, body, headers=EXPERIMENTAL, extra_headers=True, version=version) + self.expected_success(202, resp.status) return self._parse_resp(body) - def delete_consistency_group(self, consistency_group_id, - version=LATEST_MICROVERSION): - """Delete a consistency group.""" - uri = 'consistency-groups/%s' % consistency_group_id + def delete_share_group(self, share_group_id, version=LATEST_MICROVERSION): + """Delete a share group.""" + uri = 'share-groups/%s' % share_group_id resp, body = self.delete(uri, headers=EXPERIMENTAL, extra_headers=True, version=version) self.expected_success(202, resp.status) - return body + return self._parse_resp(body) - def list_consistency_groups(self, detailed=False, params=None, - version=LATEST_MICROVERSION): - """Get list of consistency groups w/o filters.""" - uri = 'consistency-groups%s' % ('/detail' if detailed else '') + def list_share_groups(self, detailed=False, params=None, + version=LATEST_MICROVERSION): + """Get list of share groups w/o filters.""" + uri = 'share-groups%s' % ('/detail' if detailed else '') uri += '?%s' % (urlparse.urlencode(params) if params else '') resp, body = self.get(uri, headers=EXPERIMENTAL, extra_headers=True, version=version) self.expected_success(200, resp.status) return self._parse_resp(body) - def get_consistency_group(self, consistency_group_id, - version=LATEST_MICROVERSION): - """Get consistency group info.""" - uri = 'consistency-groups/%s' % consistency_group_id + def get_share_group(self, share_group_id, version=LATEST_MICROVERSION): + """Get share group info.""" + uri = 'share-groups/%s' % share_group_id resp, body = self.get(uri, headers=EXPERIMENTAL, extra_headers=True, version=version) self.expected_success(200, resp.status) return self._parse_resp(body) - def update_consistency_group(self, consistency_group_id, name=None, - description=None, - version=LATEST_MICROVERSION, **kwargs): - """Update an existing consistency group.""" - uri = 'consistency-groups/%s' % consistency_group_id + def update_share_group(self, share_group_id, name=None, description=None, + version=LATEST_MICROVERSION, **kwargs): + """Update an existing share group.""" + uri = 'share-groups/%s' % share_group_id post_body = {} if name: post_body['name'] = name @@ -944,147 +963,291 @@ class SharesV2Client(shares_client.SharesClient): post_body['description'] = description if kwargs: post_body.update(kwargs) - body = json.dumps({'consistency_group': post_body}) + body = json.dumps({'share_group': post_body}) + resp, body = self.put(uri, body, headers=EXPERIMENTAL, extra_headers=True, version=version) + self.expected_success(200, resp.status) return self._parse_resp(body) - def consistency_group_reset_state(self, id, status, - version=LATEST_MICROVERSION): - self.reset_state(id, status=status, - s_type='consistency-groups', headers=EXPERIMENTAL, - version=version) + def share_group_reset_state(self, share_group_id, status='error', + version=LATEST_MICROVERSION): + self.reset_state(share_group_id, status=status, s_type='groups', + headers=EXPERIMENTAL, version=version) - def consistency_group_force_delete(self, id, version=LATEST_MICROVERSION): - self.force_delete(id, s_type='consistency-groups', + def share_group_force_delete(self, share_group_id, + version=LATEST_MICROVERSION): + self.force_delete(share_group_id, s_type='share-groups', headers=EXPERIMENTAL, version=version) - 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'] + def wait_for_share_group_status(self, share_group_id, status): + """Waits for a share group to reach a given status.""" + body = self.get_share_group(share_group_id) + sg_name = body['name'] + sg_status = body['status'] start = int(time.time()) - while consistency_group_status != status: + while sg_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) + body = self.get_share_group(share_group_id) + sg_status = body['status'] + if 'error' in sg_status and status != 'error': + raise share_exceptions.ShareGroupBuildErrorException( + share_group_id=share_group_id) if int(time.time()) - start >= self.build_timeout: - consistency_group_name = ( - consistency_group_name if consistency_group_name else - consistency_group_id - ) - message = ('Consistency Group %s failed to reach %s status ' + sg_name = sg_name or share_group_id + message = ('Share Group %s failed to reach %s status ' 'within the required time (%s s). ' 'Current status: %s' % - (consistency_group_name, status, - self.build_timeout, consistency_group_status)) + (sg_name, status, self.build_timeout, sg_status)) raise exceptions.TimeoutException(message) ############### - def create_cgsnapshot(self, consistency_group_id, - name=None, description=None, - version=LATEST_MICROVERSION): - """Create a new cgsnapshot of an existing consistency group.""" - uri = 'cgsnapshots' - post_body = {'consistency_group_id': consistency_group_id} - if name: + def create_share_group_type(self, name=None, share_types=(), + is_public=None, group_specs=None, + version=LATEST_MICROVERSION): + """Create a new share group type.""" + uri = 'share-group-types' + post_body = {} + if isinstance(share_types, (tuple, list)): + share_types = list(share_types) + else: + share_types = [share_types] + if name is not None: post_body['name'] = name - if description: - post_body['description'] = description - body = json.dumps({'cgsnapshot': post_body}) + if share_types: + post_body['share_types'] = share_types + if is_public is not None: + post_body['is_public'] = is_public + if group_specs: + post_body['group_specs'] = group_specs + body = json.dumps({'share_group_type': post_body}) resp, body = self.post(uri, body, headers=EXPERIMENTAL, extra_headers=True, version=version) - self.expected_success(202, resp.status) + self.expected_success(200, resp.status) return self._parse_resp(body) - def delete_cgsnapshot(self, cgsnapshot_id, - version=LATEST_MICROVERSION): - """Delete an existing cgsnapshot.""" - uri = 'cgsnapshots/%s' % cgsnapshot_id - resp, body = self.delete(uri, headers=EXPERIMENTAL, - extra_headers=True, version=version) - self.expected_success(202, resp.status) - return body - - def list_cgsnapshots(self, detailed=False, params=None, - version=LATEST_MICROVERSION): - """Get list of cgsnapshots w/o filters.""" - uri = 'cgsnapshots/detail' if detailed else 'cgsnapshots' + def list_share_group_types(self, detailed=False, params=None, + version=LATEST_MICROVERSION): + """Get list of share group types.""" + uri = 'share-group-types%s' % ('/detail' if detailed else '') uri += '?%s' % (urlparse.urlencode(params) if params else '') resp, body = self.get(uri, headers=EXPERIMENTAL, extra_headers=True, version=version) self.expected_success(200, resp.status) return self._parse_resp(body) - def list_cgsnapshot_members(self, cgsnapshot_id, + def get_share_group_type(self, share_group_type_id, + version=LATEST_MICROVERSION): + """Get share group type info.""" + uri = 'share-group-types/%s' % share_group_type_id + resp, body = self.get(uri, headers=EXPERIMENTAL, extra_headers=True, + version=version) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def get_default_share_group_type(self, version=LATEST_MICROVERSION): + """Get default share group type info.""" + uri = 'share-group-types/default' + resp, body = self.get(uri, headers=EXPERIMENTAL, extra_headers=True, + version=version) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def delete_share_group_type(self, share_group_type_id, version=LATEST_MICROVERSION): - """Get list of members of a cgsnapshots.""" - uri = 'cgsnapshots/%s/members' % cgsnapshot_id + """Delete an existing share group type.""" + uri = 'share-group-types/%s' % share_group_type_id + resp, body = self.delete(uri, headers=EXPERIMENTAL, + extra_headers=True, version=version) + self.expected_success(204, resp.status) + return self._parse_resp(body) + + def add_access_to_share_group_type(self, share_group_type_id, project_id, + version=LATEST_MICROVERSION): + uri = 'share-group-types/%s/action' % share_group_type_id + post_body = {'project': project_id} + post_body = json.dumps({'addProjectAccess': post_body}) + resp, body = self.post(uri, post_body, headers=EXPERIMENTAL, + extra_headers=True, version=version) + self.expected_success(202, resp.status) + return self._parse_resp(body) + + def remove_access_from_share_group_type(self, share_group_type_id, + project_id, + version=LATEST_MICROVERSION): + uri = 'share-group-types/%s/action' % share_group_type_id + post_body = {'project': project_id} + post_body = json.dumps({'removeProjectAccess': post_body}) + resp, body = self.post(uri, post_body, headers=EXPERIMENTAL, + extra_headers=True, version=version) + self.expected_success(202, resp.status) + return self._parse_resp(body) + + def list_access_to_share_group_type(self, share_group_type_id, + version=LATEST_MICROVERSION): + uri = 'share-group-types/%s/access' % share_group_type_id resp, body = self.get(uri, headers=EXPERIMENTAL, extra_headers=True, version=version) self.expected_success(200, resp.status) return self._parse_resp(body) - def get_cgsnapshot(self, cgsnapshot_id, version=LATEST_MICROVERSION): - """Get cgsnapshot info.""" - uri = 'cgsnapshots/%s' % cgsnapshot_id +############### + + def create_share_group_type_specs(self, share_group_type_id, + group_specs_dict, + version=LATEST_MICROVERSION): + url = "share-group-types/%s/group-specs" % share_group_type_id + post_body = json.dumps({'group_specs': group_specs_dict}) + resp, body = self.post(url, post_body, headers=EXPERIMENTAL, + extra_headers=True, version=version) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def get_share_group_type_spec(self, share_group_type_id, group_spec_key, + version=LATEST_MICROVERSION): + uri = "group-types/%s/group_specs/%s" % ( + share_group_type_id, group_spec_key) resp, body = self.get(uri, headers=EXPERIMENTAL, extra_headers=True, version=version) self.expected_success(200, resp.status) return self._parse_resp(body) - def update_cgsnapshot(self, cgsnapshot_id, name=None, description=None, - version=LATEST_MICROVERSION): - """Update an existing cgsnapshot.""" - uri = 'cgsnapshots/%s' % cgsnapshot_id + def list_share_group_type_specs(self, share_group_type_id, params=None, + version=LATEST_MICROVERSION): + uri = "share-group-types/%s/group_specs" % share_group_type_id + if params is not None: + uri += '?%s' % urlparse.urlencode(params) + resp, body = self.get(uri, headers=EXPERIMENTAL, extra_headers=True, + version=version) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def update_share_group_type_spec(self, share_group_type_id, group_spec_key, + group_spec_value, + version=LATEST_MICROVERSION): + uri = "share-group-types/%s/group-specs/%s" % ( + share_group_type_id, group_spec_key) + group_spec = {group_spec_key: group_spec_value} + post_body = json.dumps(group_spec) + resp, body = self.put(uri, post_body, headers=EXPERIMENTAL, + extra_headers=True, version=version) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def update_share_group_type_specs(self, share_group_type_id, + group_specs_dict, + version=LATEST_MICROVERSION): + return self.create_share_group_type_specs( + share_group_type_id, group_specs_dict, version=version) + + def delete_share_group_type_spec(self, share_type_id, group_spec_key, + version=LATEST_MICROVERSION): + uri = "share-group-types/%s/group-specs/%s" % ( + share_type_id, group_spec_key) + resp, body = self.delete(uri, headers=EXPERIMENTAL, extra_headers=True, + version=version) + self.expected_success(204, resp.status) + return body + +############### + + def create_share_group_snapshot(self, share_group_id, name=None, + description=None, + version=LATEST_MICROVERSION): + """Create a new share group snapshot of an existing share group.""" + uri = 'share-group-snapshots' + post_body = {'share_group_id': share_group_id} + if name: + post_body['name'] = name + if description: + post_body['description'] = description + body = json.dumps({'share_group_snapshot': post_body}) + resp, body = self.post(uri, body, headers=EXPERIMENTAL, + extra_headers=True, version=version) + self.expected_success(202, resp.status) + return self._parse_resp(body) + + def delete_share_group_snapshot(self, share_group_snapshot_id, + version=LATEST_MICROVERSION): + """Delete an existing share group snapshot.""" + uri = 'share-group-snapshots/%s' % share_group_snapshot_id + resp, body = self.delete(uri, headers=EXPERIMENTAL, + extra_headers=True, version=version) + self.expected_success(202, resp.status) + return body + + def list_share_group_snapshots(self, detailed=False, params=None, + version=LATEST_MICROVERSION): + """Get list of share group snapshots w/o filters.""" + uri = 'share-group-snapshots%s' % ('/detail' if detailed else '') + uri += '?%s' % (urlparse.urlencode(params) if params else '') + resp, body = self.get(uri, headers=EXPERIMENTAL, extra_headers=True, + version=version) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def get_share_group_snapshot(self, share_group_snapshot_id, + version=LATEST_MICROVERSION): + """Get share group snapshot info.""" + uri = 'share-group-snapshots/%s' % share_group_snapshot_id + resp, body = self.get(uri, headers=EXPERIMENTAL, extra_headers=True, + version=version) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def update_share_group_snapshot(self, share_group_snapshot_id, name=None, + description=None, + version=LATEST_MICROVERSION): + """Update an existing share group snapshot.""" + uri = 'share-group-snapshots/%s' % share_group_snapshot_id post_body = {} if name: post_body['name'] = name if description: post_body['description'] = description - body = json.dumps({'cgsnapshot': post_body}) + body = json.dumps({'share_group_snapshot': post_body}) resp, body = self.put(uri, body, headers=EXPERIMENTAL, extra_headers=True, version=version) self.expected_success(200, resp.status) return self._parse_resp(body) - def cgsnapshot_reset_state(self, id, status, - version=LATEST_MICROVERSION): - self.reset_state(id, status=status, - s_type='cgsnapshots', headers=EXPERIMENTAL, - version=version) + def share_group_snapshot_reset_state(self, share_group_snapshot_id, + status='error', + version=LATEST_MICROVERSION): + self.reset_state( + share_group_snapshot_id, status=status, + s_type='group-snapshots', headers=EXPERIMENTAL, version=version) - def cgsnapshot_force_delete(self, id, version=LATEST_MICROVERSION): - self.force_delete(id, s_type='cgsnapshots', headers=EXPERIMENTAL, - version=version) + def share_group_snapshot_force_delete(self, share_group_snapshot_id, + version=LATEST_MICROVERSION): + self.force_delete( + share_group_snapshot_id, s_type='share-group-snapshots', + headers=EXPERIMENTAL, version=version) - 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'] + def wait_for_share_group_snapshot_status(self, share_group_snapshot_id, + status): + """Waits for a share group snapshot to reach a given status.""" + body = self.get_share_group_snapshot(share_group_snapshot_id) + sg_snapshot_name = body['name'] + sg_snapshot_status = body['status'] start = int(time.time()) - while cgsnapshot_status != status: + while sg_snapshot_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) + body = self.get_share_group_snapshot(share_group_snapshot_id) + sg_snapshot_status = body['status'] + if 'error' in sg_snapshot_status and status != 'error': + raise share_exceptions.ShareGroupSnapshotBuildErrorException( + share_group_snapshot_id=share_group_snapshot_id) if int(time.time()) - start >= self.build_timeout: - message = ('CGSnapshot %s failed to reach %s status ' + message = ('Share Group Snapshot %s failed to reach %s status ' 'within the required time (%s s).' % - (cgsnapshot_name, status, self.build_timeout)) + (sg_snapshot_name, status, self.build_timeout)) raise exceptions.TimeoutException(message) ############### diff --git a/manila_tempest_tests/share_exceptions.py b/manila_tempest_tests/share_exceptions.py index 9b84d02e8c..a309b84256 100644 --- a/manila_tempest_tests/share_exceptions.py +++ b/manila_tempest_tests/share_exceptions.py @@ -24,9 +24,9 @@ class ShareInstanceBuildErrorException(exceptions.TempestException): 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 ShareGroupBuildErrorException(exceptions.TempestException): + message = ("Share group %(share_group_id)s failed to build and " + "is in ERROR status") class AccessRuleBuildErrorException(exceptions.TempestException): @@ -42,9 +42,9 @@ class SnapshotInstanceBuildErrorException(exceptions.TempestException): "ERROR status.") -class CGSnapshotBuildErrorException(exceptions.TempestException): - message = ("CGSnapshot %(cgsnapshot_id)s failed to build and is in ERROR " - "status") +class ShareGroupSnapshotBuildErrorException(exceptions.TempestException): + message = ("Share Group Snapshot %(share_group_snapshot_id)s failed " + "to build and is in ERROR status") class ShareProtocolNotSpecified(exceptions.TempestException): diff --git a/manila_tempest_tests/tests/api/admin/test_consistency_group_actions.py b/manila_tempest_tests/tests/api/admin/test_consistency_group_actions.py deleted file mode 100644 index 8b2a21c560..0000000000 --- a/manila_tempest_tests/tests/api/admin/test_consistency_group_actions.py +++ /dev/null @@ -1,151 +0,0 @@ -# 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.lib.common.utils import data_utils -import testtools -from testtools import testcase as tc - -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_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']]) - cls.consistency_group = cls.shares_v2_client.get_consistency_group( - cls.consistency_group['id']) - - @tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND) - def test_create_cg_from_cgsnapshot_with_multiple_share_types_v2_4(self): - # Create cgsnapshot - cgsnapshot = self.create_cgsnapshot_wait_for_active( - self.consistency_group["id"], - cleanup_in_class=False, - version='2.4', - ) - - new_consistency_group = self.create_consistency_group( - cleanup_in_class=False, - source_cgsnapshot_id=cgsnapshot['id'], - version='2.4', - ) - - # 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)) - - @tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND) - def test_create_cg_from_multi_typed_populated_cgsnapshot_v2_4(self): - share_name = data_utils.rand_name("tempest-share-name") - share_desc = data_utils.rand_name("tempest-share-description") - - shares = self.create_shares([ - {'kwargs': { - 'cleanup_in_class': False, - 'name': share_name, - 'description': share_desc, - 'consistency_group_id': self.consistency_group['id'], - 'share_type_id': st_id, - }} for st_id in (self.share_type['id'], self.share_type2['id']) - ]) - - cg_shares = self.shares_v2_client.list_shares( - detailed=True, - params={'consistency_group_id': self.consistency_group['id']}, - version='2.4', - ) - - cg_share_ids = [s['id'] for s in cg_shares] - for share_id in (shares[0]['id'], shares[1]['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, - version='2.4', - ) - - new_cg = self.create_consistency_group( - cleanup_in_class=False, source_cgsnapshot_id=cgsnapshot['id'], - version='2.4') - new_cg_shares = self.shares_v2_client.list_shares( - detailed=True, - params={'consistency_group_id': new_cg['id']}, - version='2.4') - - # 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))) - - # Ensure that share_server information of the child CG and associated - # shares match with that of the parent CG - self.assertEqual(self.consistency_group['share_network_id'], - new_cg['share_network_id']) - self.assertEqual(self.consistency_group['share_server_id'], - new_cg['share_server_id']) - - for share in new_cg_shares: - msg = ('Share %(share)s has %(attr)s=%(value)s and it does not ' - 'match that of the parent CG where %(attr)s=%(orig)s.') - payload = { - 'share': share['id'], - 'attr': 'share_network_id', - 'value': share['share_network_id'], - 'orig': self.consistency_group['share_network_id'], - } - self.assertEqual(self.consistency_group['share_network_id'], - share['share_network_id'], msg % payload) - - payload.update({'attr': 'share_server_id', - 'value': share['share_server_id'], - 'orig': self.consistency_group['share_server_id'], - }) - self.assertEqual(self.consistency_group['share_server_id'], - share['share_server_id'], msg % payload) diff --git a/manila_tempest_tests/tests/api/admin/test_consistency_groups.py b/manila_tempest_tests/tests/api/admin/test_consistency_groups.py deleted file mode 100644 index 22678e5358..0000000000 --- a/manila_tempest_tests/tests/api/admin/test_consistency_groups.py +++ /dev/null @@ -1,99 +0,0 @@ -# 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.lib.common.utils import data_utils -import testtools -from testtools import testcase as tc - -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_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'] - - @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) - def test_create_cg_with_multiple_share_types_v2_4(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']], - version='2.4', - ) - - 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)) - - @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) - @testtools.skipIf( - not CONF.share.multitenancy_enabled, "Only for multitenancy.") - def test_create_cg_from_cgsnapshot_verify_share_server_information(self): - # Create a consistency group - orig_consistency_group = self.create_consistency_group( - cleanup_in_class=False, - share_type_ids=[self.share_type['id']], - version='2.4') - - # Get latest CG information - orig_consistency_group = self.shares_v2_client.get_consistency_group( - orig_consistency_group['id'], version='2.4') - - # Assert share server information - self.assertIsNotNone(orig_consistency_group['share_network_id']) - self.assertIsNotNone(orig_consistency_group['share_server_id']) - - cg_snapshot = self.create_cgsnapshot_wait_for_active( - orig_consistency_group['id'], cleanup_in_class=False, - version='2.4') - new_consistency_group = self.create_consistency_group( - cleanup_in_class=False, version='2.4', - source_cgsnapshot_id=cg_snapshot['id']) - - # Assert share server information - self.assertEqual(orig_consistency_group['share_network_id'], - new_consistency_group['share_network_id']) - self.assertEqual(orig_consistency_group['share_server_id'], - new_consistency_group['share_server_id']) diff --git a/manila_tempest_tests/tests/api/admin/test_consistency_groups_negative.py b/manila_tempest_tests/tests/api/admin/test_consistency_groups_negative.py deleted file mode 100644 index a85037976a..0000000000 --- a/manila_tempest_tests/tests/api/admin/test_consistency_groups_negative.py +++ /dev/null @@ -1,292 +0,0 @@ -# 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.lib.common.utils import data_utils -from tempest.lib import exceptions -import testtools -from testtools import testcase as tc - -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_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 = cls.create_share( - name=cls.share_name, - description=cls.share_desc, - 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) - - @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) - 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']) - - @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) - def test_create_share_of_unsupported_type_in_cg_v2_4(self): - # Attempt to create share of default type in the cg - self.assertRaises(exceptions.BadRequest, - self.create_share, - size=1, - consistency_group_id=self.consistency_group['id'], - version='2.4') - - @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) - def test_create_share_in_cg_that_is_not_available_v2_4(self): - consistency_group = self.create_consistency_group( - cleanup_in_class=False, version='2.4') - self.addCleanup(self.shares_v2_client.consistency_group_reset_state, - consistency_group['id'], - status='available', - version='2.4') - # creating - self.shares_v2_client.consistency_group_reset_state( - consistency_group['id'], status='creating', version='2.4') - self.shares_v2_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, - consistency_group_id=consistency_group['id'], - cleanup_in_class=False, - version='2.4') - # deleting - self.shares_v2_client.consistency_group_reset_state( - consistency_group['id'], status='deleting', version='2.4') - self.shares_v2_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, - consistency_group_id=consistency_group['id'], - cleanup_in_class=False, - version='2.4') - # error - self.shares_v2_client.consistency_group_reset_state( - consistency_group['id'], status='error', version='2.4') - self.shares_v2_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, - consistency_group_id=consistency_group['id'], - cleanup_in_class=False, - version='2.4') - - @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) - def test_create_cgsnapshot_of_cg_that_is_not_available_v2_4(self): - consistency_group = self.create_consistency_group( - cleanup_in_class=False, version='2.4') - self.addCleanup(self.shares_v2_client.consistency_group_reset_state, - consistency_group['id'], - status='available', - version='2.4') - # creating - self.shares_v2_client.consistency_group_reset_state( - consistency_group['id'], status='creating', version='2.4') - self.shares_v2_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, - version='2.4') - # deleting - self.shares_v2_client.consistency_group_reset_state( - consistency_group['id'], status='deleting', version='2.4') - self.shares_v2_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, - version='2.4') - # error - self.shares_v2_client.consistency_group_reset_state( - consistency_group['id'], status='error', version='2.4') - self.shares_v2_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, - version='2.4') - - @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) - def test_create_cgsnapshot_of_cg_with_share_in_error_state_v2_4(self): - consistency_group = self.create_consistency_group(version='2.4') - share_name = data_utils.rand_name("tempest-share-name") - share_desc = data_utils.rand_name("tempest-share-description") - share = self.create_share( - name=share_name, - description=share_desc, - consistency_group_id=consistency_group['id'], - cleanup_in_class=False, - version='2.4', - ) - 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, - version='2.4') - - @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) - def test_delete_cgsnapshot_not_in_available_or_error_v2_4(self): - cgsnapshot = self.create_cgsnapshot_wait_for_active( - self.consistency_group['id'], - cleanup_in_class=False, - version='2.4', - ) - self.addCleanup(self.shares_v2_client.cgsnapshot_reset_state, - cgsnapshot['id'], - status='available', - version='2.4') - - # creating - self.shares_v2_client.cgsnapshot_reset_state(cgsnapshot['id'], - status='creating', - version='2.4') - self.shares_v2_client.wait_for_cgsnapshot_status(cgsnapshot['id'], - 'creating') - self.assertRaises(exceptions.Conflict, - self.shares_v2_client.delete_cgsnapshot, - cgsnapshot['id'], - version='2.4') - # deleting - self.shares_v2_client.cgsnapshot_reset_state(cgsnapshot['id'], - status='deleting', - version='2.4') - self.shares_v2_client.wait_for_cgsnapshot_status(cgsnapshot['id'], - 'deleting') - self.assertRaises(exceptions.Conflict, - self.shares_v2_client.delete_cgsnapshot, - cgsnapshot['id'], - version='2.4') - - @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) - def test_delete_cg_not_in_available_or_error_v2_4(self): - consistency_group = self.create_consistency_group( - cleanup_in_class=False, version='2.4') - self.addCleanup(self.shares_v2_client.consistency_group_reset_state, - consistency_group['id'], - status='available', - version='2.4') - # creating - self.shares_v2_client.consistency_group_reset_state( - consistency_group['id'], status='creating', version='2.4') - self.shares_v2_client.wait_for_consistency_group_status( - consistency_group['id'], 'creating') - self.assertRaises(exceptions.Conflict, - self.shares_v2_client.delete_consistency_group, - consistency_group['id'], - version='2.4') - # deleting - self.shares_v2_client.consistency_group_reset_state( - consistency_group['id'], status='deleting', version='2.4') - self.shares_v2_client.wait_for_consistency_group_status( - consistency_group['id'], 'deleting') - self.assertRaises(exceptions.Conflict, - self.shares_v2_client.delete_consistency_group, - consistency_group['id'], - version='2.4') - - @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) - def test_create_cg_with_conflicting_share_types_v2_4(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, - version='2.4') - - @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) - def test_create_cg_with_multi_tenant_share_type_and_no_share_network_v2_4( - 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_v2_client.create_consistency_group( - share_type_ids=[multi_tenant_share_type['id']], - version='2.4' - ) - 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) - - @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) - def test_update_cg_share_types(self): - consistency_group = self.create_consistency_group( - cleanup_in_class=False, version='2.4') - - self.assertRaises(exceptions.BadRequest, - self.shares_v2_client.update_consistency_group, - consistency_group['id'], - share_types=[self.share_type['id']], - version='2.4') diff --git a/manila_tempest_tests/tests/api/admin/test_share_group_types.py b/manila_tempest_tests/tests/api/admin/test_share_group_types.py new file mode 100644 index 0000000000..a6bda73401 --- /dev/null +++ b/manila_tempest_tests/tests/api/admin/test_share_group_types.py @@ -0,0 +1,236 @@ +# Copyright 2016 Andrew Kerr +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from tempest import config +from tempest.lib.common.utils import data_utils +import testtools +from testtools import testcase as tc + +from manila_tempest_tests.common import constants +from manila_tempest_tests.tests.api import base + +CONF = config.CONF + + +@testtools.skipUnless( + CONF.share.run_share_group_tests, 'Share Group tests disabled.') +@base.skip_if_microversion_lt(constants.MIN_SHARE_GROUP_MICROVERSION) +class ShareGroupTypesTest(base.BaseSharesAdminTest): + + @classmethod + def resource_setup(cls): + super(ShareGroupTypesTest, cls).resource_setup() + + # Create 2 share_types + name = data_utils.rand_name("tempest-manila") + extra_specs = cls.add_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'] + + @tc.attr(base.TAG_POSITIVE, base.TAG_API) + def test_create_get_delete_share_group_type_min(self): + name = data_utils.rand_name("tempest-manila") + + # Create share group type + sg_type_c = self.create_share_group_type( + name=name, + share_types=self.share_type['id'], + cleanup_in_class=False, + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + self.assertEqual( + [self.share_type['id']], + sg_type_c['share_types'], + 'Share type not applied correctly.') + + # Read share group type + sg_type_r = self.shares_v2_client.get_share_group_type(sg_type_c['id']) + keys = set(sg_type_r.keys()) + self.assertTrue( + constants.SHARE_GROUP_TYPE_REQUIRED_KEYS.issubset(keys), + 'At least one expected key missing from share group type ' + 'response. Expected %s, got %s.' % ( + constants.SHARE_GROUP_TYPE_REQUIRED_KEYS, keys)) + self.assertEqual(sg_type_c['name'], sg_type_r['name']) + + # Delete share group type + self.shares_v2_client.delete_share_group_type( + sg_type_r['id'], version=constants.MIN_SHARE_GROUP_MICROVERSION) + self.shares_v2_client.wait_for_resource_deletion( + share_group_type_id=sg_type_r['id']) + + @tc.attr(base.TAG_POSITIVE, base.TAG_API) + def test_create_share_group_type_multiple_share_types_min(self): + name = data_utils.rand_name("tempest-manila") + + sg_type = self.create_share_group_type( + name=name, + share_types=[self.share_type['id'], self.share_type2['id']], + cleanup_in_class=False, + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + self.assertEqual( + {self.share_type['id'], self.share_type2['id']}, + set(sg_type['share_types']), + 'Share types not applied correctly.') + + @tc.attr(base.TAG_POSITIVE, base.TAG_API) + def test_create_share_group_type_with_one_spec_min(self): + name = data_utils.rand_name("tempest-manila") + group_specs = {'key': 'value'} + + sg_type = self.create_share_group_type( + name=name, + share_types=self.share_type['id'], + group_specs=group_specs, + cleanup_in_class=False, + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + self.assertDictMatch(group_specs, sg_type['group_specs']) + + @tc.attr(base.TAG_POSITIVE, base.TAG_API) + def test_create_share_group_type_with_multiple_specs_min(self): + name = data_utils.rand_name("tempest-manila") + group_specs = {'key1': 'value1', 'key2': 'value2'} + + sg_type = self.create_share_group_type( + name=name, + share_types=self.share_type['id'], + group_specs=group_specs, + cleanup_in_class=False, + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + self.assertDictMatch(group_specs, sg_type['group_specs']) + + @tc.attr(base.TAG_POSITIVE, base.TAG_API) + def test_update_single_share_group_type_spec_min(self): + name = data_utils.rand_name("tempest-manila") + group_specs = {'key1': 'value1', 'key2': 'value2'} + + sg_type = self.create_share_group_type( + name=name, + share_types=self.share_type['id'], + group_specs=group_specs, + cleanup_in_class=False, + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + self.assertDictMatch(group_specs, sg_type['group_specs']) + + group_specs = {'key1': 'value3', 'key2': 'value2'} + + self.shares_v2_client.update_share_group_type_spec( + sg_type['id'], 'key1', 'value3') + sg_type = self.shares_v2_client.get_share_group_type(sg_type['id']) + + self.assertDictMatch(group_specs, sg_type['group_specs']) + + @tc.attr(base.TAG_POSITIVE, base.TAG_API) + def test_update_all_share_group_type_specs_min(self): + name = data_utils.rand_name("tempest-manila") + group_specs = {'key1': 'value1', 'key2': 'value2'} + + sg_type = self.create_share_group_type( + name=name, + share_types=self.share_type['id'], + group_specs=group_specs, + cleanup_in_class=False, + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + self.assertDictMatch(group_specs, sg_type['group_specs']) + + group_specs = {'key1': 'value3', 'key2': 'value4'} + + self.shares_v2_client.update_share_group_type_specs( + sg_type['id'], group_specs) + sg_type = self.shares_v2_client.get_share_group_type(sg_type['id']) + + self.assertDictMatch(group_specs, sg_type['group_specs']) + + @tc.attr(base.TAG_POSITIVE, base.TAG_API) + def test_delete_single_share_group_type_spec_min(self): + name = data_utils.rand_name("tempest-manila") + group_specs = {'key1': 'value1', 'key2': 'value2'} + + sg_type = self.create_share_group_type( + name=name, + share_types=self.share_type['id'], + group_specs=group_specs, + cleanup_in_class=False, + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + self.assertDictMatch(group_specs, sg_type['group_specs']) + + key_to_delete = 'key1' + group_specs.pop(key_to_delete) + + self.shares_v2_client.delete_share_group_type_spec( + sg_type['id'], key_to_delete) + sg_type = self.shares_v2_client.get_share_group_type( + sg_type['id']) + + self.assertDictMatch(group_specs, sg_type['group_specs']) + + @tc.attr(base.TAG_POSITIVE, base.TAG_API) + def test_private_share_group_type_access(self): + name = data_utils.rand_name("tempest-manila") + group_specs = {"key1": "value1", "key2": "value2"} + project_id = self.shares_v2_client.tenant_id + + # Create private share group type + sgt_create = self.create_share_group_type( + name=name, + share_types=[self.share_type['id']], + is_public=False, + group_specs=group_specs, + ) + self.assertEqual(name, sgt_create['name']) + sgt_id = sgt_create["id"] + + # It should not be listed without access + sgt_list = self.shares_v2_client.list_share_group_types() + self.assertFalse(any(sgt_id == sgt["id"] for sgt in sgt_list)) + + # List projects that have access for share group type - none expected + access = self.shares_v2_client.list_access_to_share_group_type(sgt_id) + self.assertEqual([], access) + + # Add project access to share group type + access = self.shares_v2_client.add_access_to_share_group_type( + sgt_id, project_id) + + # Now it should be listed + sgt_list = self.shares_v2_client.list_share_group_types() + self.assertTrue(any(sgt_id == sgt["id"] for sgt in sgt_list)) + + # List projects that have access for share group type - one expected + access = self.shares_v2_client.list_access_to_share_group_type(sgt_id) + expected = [{'share_group_type_id': sgt_id, 'project_id': project_id}] + self.assertEqual(expected, access) + + # Remove project access from share group type + access = self.shares_v2_client.remove_access_from_share_group_type( + sgt_id, project_id) + + # It should not be listed without access + sgt_list = self.shares_v2_client.list_share_group_types() + self.assertFalse(any(sgt_id == sgt["id"] for sgt in sgt_list)) + + # List projects that have access for share group type - none expected + access = self.shares_v2_client.list_access_to_share_group_type(sgt_id) + self.assertEqual([], access) diff --git a/manila_tempest_tests/tests/api/admin/test_share_group_types_negative.py b/manila_tempest_tests/tests/api/admin/test_share_group_types_negative.py new file mode 100644 index 0000000000..e7f682402d --- /dev/null +++ b/manila_tempest_tests/tests/api/admin/test_share_group_types_negative.py @@ -0,0 +1,128 @@ +# 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.lib.common.utils import data_utils +from tempest.lib import exceptions as lib_exc +import testtools +from testtools import testcase as tc + +from manila_tempest_tests.common import constants +from manila_tempest_tests.tests.api import base + +CONF = config.CONF + + +@testtools.skipUnless( + CONF.share.run_share_group_tests, 'Share Group tests disabled.') +@base.skip_if_microversion_lt(constants.MIN_SHARE_GROUP_MICROVERSION) +class ShareGroupTypesAdminNegativeTest(base.BaseSharesMixedTest): + + @classmethod + def resource_setup(cls): + super(ShareGroupTypesAdminNegativeTest, cls).resource_setup() + cls.share_type = cls.create_share_type( + data_utils.rand_name("unique_st_name"), + extra_specs=cls.add_extra_specs_to_dict({"key": "value"}), + client=cls.admin_shares_v2_client) + cls.share_group_type = cls.create_share_group_type( + data_utils.rand_name("unique_sgt_name"), + share_types=[cls.share_type['share_type']['id']], + client=cls.admin_shares_v2_client) + + @tc.attr(base.TAG_NEGATIVE, base.TAG_API) + def test_create_share_ggroup_with_nonexistent_share_type(self): + self.assertRaises( + lib_exc.BadRequest, + self.admin_shares_v2_client.create_share_group_type, + share_types=data_utils.rand_name("fake")) + + @tc.attr(base.TAG_NEGATIVE, base.TAG_API) + def test_create_share_group_type_with_empty_name(self): + self.assertRaises( + lib_exc.BadRequest, + self.create_share_group_type, '', + client=self.admin_shares_v2_client) + + @tc.attr(base.TAG_NEGATIVE, base.TAG_API) + def test_create_share_group_type_with_too_big_name(self): + self.assertRaises( + lib_exc.BadRequest, + self.create_share_group_type, + "x" * 256, client=self.admin_shares_v2_client) + + @tc.attr(base.TAG_NEGATIVE, base.TAG_API) + def test_get_share_group_type_using_nonexistent_id(self): + self.assertRaises( + lib_exc.NotFound, + self.admin_shares_v2_client.get_share_group_type, + data_utils.rand_name("fake")) + + @tc.attr(base.TAG_NEGATIVE, base.TAG_API) + def test_try_delete_share_group_type_using_nonexistent_id(self): + self.assertRaises( + lib_exc.NotFound, + self.admin_shares_v2_client.delete_share_group_type, + data_utils.rand_name("fake")) + + @tc.attr(base.TAG_NEGATIVE, base.TAG_API) + def test_try_create_duplicate_of_share_group_type(self): + unique_name = data_utils.rand_name("unique_sgt_name") + list_of_ids = set() + for step in (1, 2): + sg_type = self.create_share_group_type( + unique_name, + share_types=[self.share_type['share_type']['id']], + client=self.admin_shares_v2_client, + cleanup_in_class=False) + self.assertRaises( + lib_exc.Conflict, + self.create_share_group_type, + unique_name, + share_types=[self.share_type['share_type']['id']], + client=self.admin_shares_v2_client) + list_of_ids.add(sg_type['id']) + self.assertEqual(unique_name, sg_type['name']) + self.admin_shares_v2_client.delete_share_group_type(sg_type['id']) + self.assertEqual(2, len(list_of_ids)) + + @tc.attr(base.TAG_NEGATIVE, base.TAG_API) + def test_add_project_access_to_public_share_group_type(self): + self.assertRaises( + lib_exc.Conflict, + self.admin_shares_v2_client.add_access_to_share_group_type, + self.share_group_type["id"], + self.admin_shares_v2_client.tenant_id) + + @tc.attr(base.TAG_NEGATIVE, base.TAG_API) + def test_remove_project_access_from_public_share_group_type(self): + self.assertRaises( + lib_exc.Conflict, + self.admin_shares_v2_client.remove_access_from_share_group_type, + self.share_group_type["id"], + self.admin_shares_v2_client.tenant_id) + + @tc.attr(base.TAG_NEGATIVE, base.TAG_API) + def test_add_project_access_to_nonexistent_share_group_type(self): + self.assertRaises( + lib_exc.NotFound, + self.admin_shares_v2_client.add_access_to_share_group_type, + data_utils.rand_name("fake"), + self.admin_shares_v2_client.tenant_id) + + @tc.attr(base.TAG_NEGATIVE, base.TAG_API) + def test_remove_project_access_from_nonexistent_share_group_type(self): + self.assertRaises( + lib_exc.NotFound, + self.admin_shares_v2_client.remove_access_from_share_group_type, + data_utils.rand_name("fake"), + self.admin_shares_v2_client.tenant_id) diff --git a/manila_tempest_tests/tests/api/admin/test_share_groups.py b/manila_tempest_tests/tests/api/admin/test_share_groups.py new file mode 100644 index 0000000000..e289005971 --- /dev/null +++ b/manila_tempest_tests/tests/api/admin/test_share_groups.py @@ -0,0 +1,175 @@ +# Copyright 2016 Andrew Kerr +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from tempest import config +from tempest.lib.common.utils import data_utils +import testtools +from testtools import testcase as tc + +from manila_tempest_tests.common import constants +from manila_tempest_tests.tests.api import base + +CONF = config.CONF + + +@testtools.skipUnless( + CONF.share.run_share_group_tests, 'Share Group tests disabled.') +@base.skip_if_microversion_lt(constants.MIN_SHARE_GROUP_MICROVERSION) +class ShareGroupsTest(base.BaseSharesAdminTest): + + @classmethod + def resource_setup(cls): + super(ShareGroupsTest, cls).resource_setup() + # Create 2 share_types + name = data_utils.rand_name("tempest-manila") + extra_specs = cls.add_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'] + + cls.sg_type = cls.create_share_group_type( + name=name, + share_types=[cls.share_type['id'], cls.share_type2['id']], + cleanup_in_class=True, + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + @tc.attr(base.TAG_POSITIVE, base.TAG_API) + def test_create_share_group_with_single_share_type_min(self): + share_group = self.create_share_group( + share_group_type_id=self.sg_type['id'], + cleanup_in_class=False, + share_type_ids=[self.share_type['id']], + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + keys = set(share_group.keys()) + self.assertTrue( + constants.SHARE_GROUP_DETAIL_REQUIRED_KEYS.issubset(keys), + 'At least one expected element missing from share group ' + 'response. Expected %(expected)s, got %(actual)s.' % { + "expected": constants.SHARE_GROUP_DETAIL_REQUIRED_KEYS, + "actual": keys}) + + actual_sg_type = share_group['share_group_type_id'] + expected_sg_type = self.sg_type['id'] + self.assertEqual( + expected_sg_type, actual_sg_type, + 'Incorrect share group type applied to share group ' + '%s. Expected %s, got %s' % ( + share_group['id'], expected_sg_type, actual_sg_type)) + + actual_share_types = share_group['share_types'] + expected_share_types = [self.share_type['id']] + self.assertEqual( + sorted(expected_share_types), + sorted(actual_share_types), + 'Incorrect share types applied to share group %s. ' + 'Expected %s, got %s' % ( + share_group['id'], expected_share_types, actual_share_types)) + + @tc.attr(base.TAG_POSITIVE, base.TAG_API) + def test_create_share_group_with_multiple_share_types_min(self): + share_group = self.create_share_group( + share_group_type_id=self.sg_type['id'], + cleanup_in_class=False, + share_type_ids=[self.share_type['id'], self.share_type2['id']], + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + keys = set(share_group.keys()) + self.assertTrue( + constants.SHARE_GROUP_DETAIL_REQUIRED_KEYS.issubset(keys), + 'At least one expected element missing from share group ' + 'response. Expected %(expected)s, got %(actual)s.' % { + "expected": constants.SHARE_GROUP_DETAIL_REQUIRED_KEYS, + "actual": keys}) + + actual_sg_type = share_group['share_group_type_id'] + expected_sg_type = self.sg_type['id'] + self.assertEqual( + expected_sg_type, actual_sg_type, + 'Incorrect share group type applied to share group %s. ' + 'Expected %s, got %s' % ( + share_group['id'], expected_sg_type, actual_sg_type)) + + actual_share_types = share_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 share group %s. ' + 'Expected %s, got %s' % ( + share_group['id'], expected_share_types, actual_share_types)) + + @tc.attr(base.TAG_POSITIVE, base.TAG_API) + def test_default_share_group_type_applied(self): + default_type = self.shares_v2_client.get_default_share_group_type() + default_share_types = default_type['share_types'] + + share_group = self.create_share_group( + cleanup_in_class=False, + share_type_ids=default_share_types, + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + keys = set(share_group.keys()) + self.assertTrue( + constants.SHARE_GROUP_DETAIL_REQUIRED_KEYS.issubset(keys), + 'At least one expected element missing from share group ' + 'response. Expected %(expected)s, got %(actual)s.' % { + "expected": constants.SHARE_GROUP_DETAIL_REQUIRED_KEYS, + "actual": keys}) + + actual_sg_type = share_group['share_group_type_id'] + expected_sg_type = default_type['id'] + self.assertEqual( + expected_sg_type, actual_sg_type, + 'Incorrect share group type applied to share group %s. ' + 'Expected %s, got %s' % ( + share_group['id'], expected_sg_type, actual_sg_type)) + + @testtools.skipUnless( + CONF.share.multitenancy_enabled, "Only for multitenancy.") + @tc.attr(base.TAG_POSITIVE, base.TAG_API) + def test_create_sg_from_snapshot_verify_share_server_information_min(self): + # Create a share group + orig_sg = self.create_share_group( + share_group_type_id=self.sg_type['id'], + cleanup_in_class=False, + share_type_ids=[self.share_type['id']], + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + # Get latest share group information + orig_sg = self.shares_v2_client.get_share_group( + orig_sg['id'], version=constants.MIN_SHARE_GROUP_MICROVERSION) + + # Assert share server information + self.assertIsNotNone(orig_sg['share_network_id']) + self.assertIsNotNone(orig_sg['share_server_id']) + + sg_snapshot = self.create_share_group_snapshot_wait_for_active( + orig_sg['id'], cleanup_in_class=False, + version=constants.MIN_SHARE_GROUP_MICROVERSION) + new_sg = self.create_share_group( + share_group_type_id=self.sg_type['id'], + cleanup_in_class=False, + version=constants.MIN_SHARE_GROUP_MICROVERSION, + source_share_group_snapshot_id=sg_snapshot['id']) + + # Assert share server information + self.assertEqual( + orig_sg['share_network_id'], new_sg['share_network_id']) + self.assertEqual( + orig_sg['share_server_id'], new_sg['share_server_id']) diff --git a/manila_tempest_tests/tests/api/base.py b/manila_tempest_tests/tests/api/base.py index 48cd3ed626..78371950f5 100644 --- a/manila_tempest_tests/tests/api/base.py +++ b/manila_tempest_tests/tests/api/base.py @@ -392,7 +392,7 @@ class BaseSharesTest(test.BaseTestCase): def _create_share(cls, share_protocol=None, size=None, name=None, snapshot_id=None, description=None, metadata=None, share_network_id=None, share_type_id=None, - consistency_group_id=None, client=None, + share_group_id=None, client=None, cleanup_in_class=True, is_public=False, **kwargs): client = client or cls.shares_v2_client description = description or "Tempest's share" @@ -410,12 +410,12 @@ class BaseSharesTest(test.BaseTestCase): 'share_type_id': share_type_id, 'is_public': is_public, }) - if consistency_group_id: - kwargs['consistency_group_id'] = consistency_group_id + if share_group_id: + kwargs['share_group_id'] = share_group_id share = client.create_share(**kwargs) resource = {"type": "share", "id": share["id"], "client": client, - "consistency_group_id": consistency_group_id} + "share_group_id": share_group_id} cleanup_list = (cls.class_resources if cleanup_in_class else cls.method_resources) cleanup_list.insert(0, resource) @@ -540,41 +540,63 @@ class BaseSharesTest(test.BaseTestCase): return [d["share"] for d in data] @classmethod - def create_consistency_group(cls, client=None, cleanup_in_class=True, - share_network_id=None, **kwargs): + def create_share_group(cls, client=None, cleanup_in_class=True, + share_network_id=None, **kwargs): client = client or cls.shares_v2_client - if kwargs.get('source_cgsnapshot_id') is None: + if kwargs.get('source_share_group_snapshot_id') is None: kwargs['share_network_id'] = (share_network_id or client.share_network_id or None) - consistency_group = client.create_consistency_group(**kwargs) + share_group = client.create_share_group(**kwargs) resource = { - "type": "consistency_group", - "id": consistency_group["id"], - "client": client} + "type": "share_group", + "id": share_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( + if kwargs.get('source_share_group_snapshot_id'): + new_share_group_shares = client.list_shares( detailed=True, - params={'consistency_group_id': consistency_group['id']}) + params={'share_group_id': share_group['id']}, + experimental=True) - for share in new_cg_shares: + for share in new_share_group_shares: resource = {"type": "share", "id": share["id"], "client": client, - "consistency_group_id": share.get( - 'consistency_group_id')} + "share_group_id": share.get("share_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 + client.wait_for_share_group_status(share_group['id'], 'available') + return share_group + + @classmethod + def create_share_group_type(cls, name=None, share_types=(), is_public=None, + group_specs=None, client=None, + cleanup_in_class=True, **kwargs): + client = client or cls.shares_v2_client + share_group_type = client.create_share_group_type( + name=name, + share_types=share_types, + is_public=is_public, + group_specs=group_specs, + **kwargs) + resource = { + "type": "share_group_type", + "id": share_group_type["id"], + "client": client, + } + if cleanup_in_class: + cls.class_resources.insert(0, resource) + else: + cls.method_resources.insert(0, resource) + return share_group_type @classmethod def create_snapshot_wait_for_active(cls, share_id, name=None, @@ -598,28 +620,26 @@ class BaseSharesTest(test.BaseTestCase): return snapshot @classmethod - def create_cgsnapshot_wait_for_active(cls, consistency_group_id, - name=None, description=None, - client=None, cleanup_in_class=True, - **kwargs): + def create_share_group_snapshot_wait_for_active( + cls, share_group_id, name=None, description=None, client=None, + cleanup_in_class=True, **kwargs): client = client or cls.shares_v2_client if description is None: - description = "Tempest's cgsnapshot" - cgsnapshot = client.create_cgsnapshot(consistency_group_id, - name=name, - description=description, - **kwargs) + description = "Tempest's share group snapshot" + sg_snapshot = client.create_share_group_snapshot( + share_group_id, name=name, description=description, **kwargs) resource = { - "type": "cgsnapshot", - "id": cgsnapshot["id"], + "type": "share_group_snapshot", + "id": sg_snapshot["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 + client.wait_for_share_group_snapshot_status( + sg_snapshot["id"], "available") + return sg_snapshot @classmethod def get_availability_zones(cls, client=None): @@ -800,7 +820,6 @@ class BaseSharesTest(test.BaseTestCase): :param resources: dict with keys 'type','id','client' and 'deleted' """ - if resources is None: resources = cls.method_resources for res in resources: @@ -814,9 +833,9 @@ class BaseSharesTest(test.BaseTestCase): with handle_cleanup_exceptions(): if res["type"] is "share": cls.clear_share_replicas(res_id) - cg_id = res.get('consistency_group_id') - if cg_id: - params = {'consistency_group_id': cg_id} + share_group_id = res.get('share_group_id') + if share_group_id: + params = {'share_group_id': share_group_id} client.delete_share(res_id, params=params) else: client.delete_share(res_id) @@ -833,12 +852,18 @@ class BaseSharesTest(test.BaseTestCase): elif res["type"] is "share_type": client.delete_share_type(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) + elif res["type"] is "share_group": + client.delete_share_group(res_id) + client.wait_for_resource_deletion( + share_group_id=res_id) + elif res["type"] is "share_group_type": + client.delete_share_group_type(res_id) + client.wait_for_resource_deletion( + share_group_type_id=res_id) + elif res["type"] is "share_group_snapshot": + client.delete_share_group_snapshot(res_id) + client.wait_for_resource_deletion( + share_group_snapshot_id=res_id) elif res["type"] is "share_replica": client.delete_share_replica(res_id) client.wait_for_resource_deletion(replica_id=res_id) diff --git a/manila_tempest_tests/tests/api/test_consistency_group_actions.py b/manila_tempest_tests/tests/api/test_consistency_group_actions.py deleted file mode 100644 index 7a7c54dfe8..0000000000 --- a/manila_tempest_tests/tests/api/test_consistency_group_actions.py +++ /dev/null @@ -1,377 +0,0 @@ -# -*- 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.lib.common.utils import data_utils -import testtools -from testtools import testcase as tc - -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 first consistency group - cls.cg_name = data_utils.rand_name("tempest-cg-name") - cls.cg_desc = data_utils.rand_name("tempest-cg-description") - cls.cg = cls.create_consistency_group( - name=cls.cg_name, description=cls.cg_desc) - - # Create second consistency group for purposes of sorting and snapshot - # filtering - cls.cg2 = cls.create_consistency_group( - name=cls.cg_name, description=cls.cg_desc) - - # Create 2 shares inside first CG and 1 inside second CG - cls.share_name = data_utils.rand_name("tempest-share-name") - cls.share_desc = data_utils.rand_name("tempest-share-description") - cls.share_size = CONF.share.share_size - cls.share_size2 = cls.share_size + 1 - cls.shares = cls.create_shares([ - {'kwargs': { - 'name': cls.share_name, - 'description': cls.share_desc, - 'size': size, - 'consistency_group_id': cg_id, - }} for size, cg_id in ((cls.share_size, cls.cg['id']), - (cls.share_size2, cls.cg['id']), - (cls.share_size, cls.cg2['id'])) - ]) - - # Create CG snapshots - 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.cg["id"], - name=cls.cgsnap_name, - description=cls.cgsnap_desc) - - cls.cgsnapshot2 = cls.create_cgsnapshot_wait_for_active( - cls.cg2['id'], name=cls.cgsnap_name, description=cls.cgsnap_desc) - - @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) - def test_get_consistency_group_v2_4(self): - - # Get consistency group - consistency_group = self.shares_v2_client.get_consistency_group( - self.cg['id'], version='2.4') - - # 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) - - @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) - def test_get_share_v2_4(self): - - # Get share - share = self.shares_v2_client.get_share(self.shares[0]['id'], - version='2.4') - - # 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.cg["id"], share["consistency_group_id"]) - self.assertEqual(self.cg["id"], share["consistency_group_id"], msg) - - @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) - def test_list_consistency_groups_v2_4(self): - - # List consistency groups - consistency_groups = self.shares_v2_client.list_consistency_groups( - version='2.4') - - # 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.cg["id"], self.cg2["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) - - @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) - def test_list_consistency_groups_with_detail_v2_4(self): - - # List consistency groups - consistency_groups = self.shares_v2_client.list_consistency_groups( - detailed=True, version='2.4') - - # 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.cg["id"], self.cg2["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) - - @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) - def test_filter_shares_by_consistency_group_id_v2_4(self): - - shares = self.shares_v2_client.list_shares( - detailed=True, - params={'consistency_group_id': self.cg['id']}, - version='2.4' - ) - - 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.shares[0]['id'], share_ids, - 'Share %s expected in returned list, but got %s' - % (self.shares[0]['id'], share_ids)) - self.assertIn(self.shares[1]['id'], share_ids, - 'Share %s expected in returned list, but got %s' - % (self.shares[0]['id'], share_ids)) - - @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) - def test_get_cgsnapshot_v2_4(self): - - # Get consistency group - consistency_group = self.shares_v2_client.get_consistency_group( - self.cg['id'], version='2.4') - - # 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) - - @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) - def test_get_cgsnapshot_members_v2_4(self): - - cgsnapshot_members = self.shares_v2_client.list_cgsnapshot_members( - self.cgsnapshot['id'], version='2.4') - 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.shares[0]['id'], self.shares[1]['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.shares[0], self.shares[1]): - 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']) - - @tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND) - def test_create_consistency_group_from_populated_cgsnapshot_v2_4(self): - - cgsnapshot_members = self.shares_v2_client.list_cgsnapshot_members( - self.cgsnapshot['id'], version='2.4') - - new_consistency_group = self.create_consistency_group( - cleanup_in_class=False, - source_cgsnapshot_id=self.cgsnapshot['id'], - version='2.4' - ) - - new_consistency_group = self.shares_v2_client.get_consistency_group( - new_consistency_group['id'], version='2.4') - - # Verify that share_network information matches source CG - self.assertEqual(self.cg['share_network_id'], - new_consistency_group['share_network_id']) - - new_shares = self.shares_v2_client.list_shares( - params={'consistency_group_id': new_consistency_group['id']}, - detailed=True, - version='2.4' - ) - - # 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']) - self.assertEqual(self.cg['share_network_id'], - share['share_network_id']) - - -@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, - ) - - @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) - def test_update_consistency_group_v2_4(self): - - # Get consistency_group - consistency_group = self.shares_v2_client.get_consistency_group( - self.consistency_group['id'], version='2.4') - 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_v2_client.update_consistency_group( - consistency_group["id"], - name=new_name, - description=new_desc, - version='2.4' - ) - self.assertEqual(new_name, updated["name"]) - self.assertEqual(new_desc, updated["description"]) - - # Get consistency_group - consistency_group = self.shares_v2_client.get_consistency_group( - self.consistency_group['id'], version='2.4') - self.assertEqual(new_name, consistency_group["name"]) - self.assertEqual(new_desc, consistency_group["description"]) - - @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) - def test_create_update_read_consistency_group_with_unicode_v2_4(self): - value1 = u'ಠ_ಠ' - value2 = u'ಠ_ರೃ' - # Create consistency_group - consistency_group = self.create_consistency_group( - cleanup_in_class=False, - name=value1, - description=value1, - version='2.4' - ) - self.assertEqual(value1, consistency_group["name"]) - self.assertEqual(value1, consistency_group["description"]) - - # Update consistency_group - updated = self.shares_v2_client.update_consistency_group( - consistency_group["id"], - name=value2, - description=value2, - version='2.4' - ) - self.assertEqual(value2, updated["name"]) - self.assertEqual(value2, updated["description"]) - - # Get consistency_group - consistency_group = self.shares_v2_client.get_consistency_group( - consistency_group['id'], version='2.4') - self.assertEqual(value2, consistency_group["name"]) - self.assertEqual(value2, consistency_group["description"]) diff --git a/manila_tempest_tests/tests/api/test_consistency_groups.py b/manila_tempest_tests/tests/api/test_consistency_groups.py deleted file mode 100644 index 8f141da9d2..0000000000 --- a/manila_tempest_tests/tests/api/test_consistency_groups.py +++ /dev/null @@ -1,144 +0,0 @@ -# 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.lib import exceptions as lib_exc -import testtools -from testtools import testcase as tc - -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.""" - - @tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND) - def test_create_populate_delete_consistency_group_v2_4(self): - # Create a consistency group - consistency_group = self.create_consistency_group( - cleanup_in_class=False, version='2.4') - 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, - version='2.4') - # Delete - params = {"consistency_group_id": consistency_group['id']} - self.shares_v2_client.delete_share(share['id'], params=params, - version='2.4') - self.shares_client.wait_for_resource_deletion(share_id=share['id']) - self.shares_v2_client.delete_consistency_group(consistency_group['id'], - version='2.4') - self.shares_v2_client.wait_for_resource_deletion( - cg_id=consistency_group['id']) - - # Verify - self.assertRaises(lib_exc.NotFound, - self.shares_v2_client.get_consistency_group, - consistency_group['id']) - self.assertRaises(lib_exc.NotFound, - self.shares_client.get_share, - share['id']) - - @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) - def test_create_delete_empty_cgsnapshot_v2_4(self): - # Create base consistency group - consistency_group = self.create_consistency_group( - cleanup_in_class=False, version='2.4') - # Create cgsnapshot - cgsnapshot = self.create_cgsnapshot_wait_for_active( - consistency_group["id"], cleanup_in_class=False, version='2.4') - - 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_v2_client.list_cgsnapshot_members( - cgsnapshot['id'], version='2.4') - - self.assertEmpty(cgsnapshot_members, - 'Expected 0 cgsnapshot members, got %s' % len( - cgsnapshot_members)) - - # delete snapshot - self.shares_v2_client.delete_cgsnapshot(cgsnapshot["id"], - version='2.4') - self.shares_v2_client.wait_for_resource_deletion( - cgsnapshot_id=cgsnapshot["id"]) - self.assertRaises(lib_exc.NotFound, - self.shares_v2_client.get_cgsnapshot, - cgsnapshot['id'], - version='2.4') - - @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) - 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_v2_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 consistency group %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) - - # Assert the share_network information is the same - msg = 'Expected share_network %s as share_network of cg %s' % ( - consistency_group['share_network_id'], - new_consistency_group['share_network_id']) - self.assertEqual(consistency_group['share_network_id'], - new_consistency_group['share_network_id'], - msg) diff --git a/manila_tempest_tests/tests/api/test_consistency_groups_negative.py b/manila_tempest_tests/tests/api/test_consistency_groups_negative.py deleted file mode 100644 index 9c38a2bb9a..0000000000 --- a/manila_tempest_tests/tests/api/test_consistency_groups_negative.py +++ /dev/null @@ -1,226 +0,0 @@ -# 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.lib.common.utils import data_utils -from tempest.lib import exceptions as lib_exc -import testtools -from testtools import testcase as tc - -from manila_tempest_tests.tests.api import base - -CONF = config.CONF - - -@testtools.skipUnless(CONF.share.run_consistency_group_tests, - 'Consistency Group tests disabled.') -class ConsistencyGroupsAPIOnlyNegativeTest(base.BaseSharesTest): - - @tc.attr(base.TAG_NEGATIVE, base.TAG_API) - def test_delete_cg_without_passing_cg_id_v2_4(self): - self.assertRaises(lib_exc.NotFound, - self.shares_v2_client.delete_consistency_group, - '', - version='2.4') - - @tc.attr(base.TAG_NEGATIVE, base.TAG_API) - def test_delete_cg_with_wrong_id_v2_4(self): - self.assertRaises(lib_exc.NotFound, - self.shares_v2_client.delete_consistency_group, - "wrong_consistency_group_id", - version='2.4') - - @tc.attr(base.TAG_NEGATIVE, base.TAG_API) - def test_update_cg_with_wrong_id_v2_4(self): - self.assertRaises(lib_exc.NotFound, - self.shares_v2_client.update_consistency_group, - 'wrong_consistency_group_id', - name='new_name', - description='new_description', - version='2.4') - - @tc.attr(base.TAG_NEGATIVE, base.TAG_API) - def test_get_cg_without_passing_cg_id_v2_4(self): - self.assertRaises(lib_exc.NotFound, - self.shares_v2_client.get_consistency_group, - '', - version='2.4') - - @tc.attr(base.TAG_NEGATIVE, base.TAG_API) - def test_filter_shares_on_invalid_cg_id_v2_4(self): - shares = self.shares_v2_client.list_shares( - detailed=True, - params={'consistency_group_id': 'foobar'}, - version='2.4', - ) - - self.assertEqual(0, len(shares), - 'Incorrect number of shares returned. Expected 0, ' - 'got %s.' % len(shares)) - - @tc.attr(base.TAG_NEGATIVE, base.TAG_API) - def test_create_cgsnapshot_with_invalid_cg_id_value_v2_4(self): - self.assertRaises(lib_exc.BadRequest, - self.create_cgsnapshot_wait_for_active, - 'foobar', - cleanup_in_class=False, - version='2.4') - - @tc.attr(base.TAG_NEGATIVE, base.TAG_API) - def test_create_cg_with_invalid_share_type_id_value_v2_4(self): - self.assertRaises(lib_exc.BadRequest, - self.create_consistency_group, - share_type_ids=['foobar'], - cleanup_in_class=False, - version='2.4') - - @tc.attr(base.TAG_NEGATIVE, base.TAG_API) - def test_create_cg_with_invalid_share_network_id_value_v2_4(self): - self.assertRaises(lib_exc.BadRequest, - self.create_consistency_group, - share_network_id='foobar', - cleanup_in_class=False, - version='2.4') - - @tc.attr(base.TAG_NEGATIVE, base.TAG_API) - def test_create_cg_with_invalid_source_cgsnapshot_id_value_v2_4( - self): - self.assertRaises(lib_exc.BadRequest, - self.create_consistency_group, - source_cgsnapshot_id='foobar', - cleanup_in_class=False, - version='2.4') - - -@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 = cls.create_share( - name=cls.share_name, - description=cls.share_desc, - 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) - - @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) - def test_create_cg_with_nonexistent_source_cgsnapshot_id_value_v2_4(self): - self.assertRaises(lib_exc.BadRequest, - self.create_consistency_group, - source_cgsnapshot_id=self.share['id'], - cleanup_in_class=False, - version='2.4') - - @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) - def test_create_cg_with_nonexistent_share_network_id_value_v2_4(self): - self.assertRaises(lib_exc.BadRequest, - self.create_consistency_group, - share_network_id=self.share['id'], - cleanup_in_class=False, - version='2.4') - - @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) - def test_create_cg_with_nonexistent_share_type_id_value_v2_4(self): - self.assertRaises(lib_exc.BadRequest, - self.create_consistency_group, - share_type_ids=[self.share['id']], - cleanup_in_class=False, - version='2.4') - - @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) - def test_create_cgsnapshot_with_nonexistent_cg_id_value_v2_4(self): - self.assertRaises(lib_exc.BadRequest, - self.create_cgsnapshot_wait_for_active, - self.share['id'], - cleanup_in_class=False, - version='2.4') - - @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) - def test_delete_cg_in_use_by_cgsnapshot_v2_4(self): - # Attempt delete of share type - self.assertRaises(lib_exc.Conflict, - self.shares_v2_client.delete_consistency_group, - self.consistency_group['id'], - version='2.4') - - @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) - def test_delete_share_in_use_by_cgsnapshot_v2_4(self): - # Attempt delete of share type - params = {'consistency_group_id': self.share['consistency_group_id']} - self.assertRaises(lib_exc.Forbidden, - self.shares_v2_client.delete_share, - self.share['id'], - params=params, - version='2.4') - - @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) - def test_delete_cg_containing_a_share_v2_4(self): - self.assertRaises(lib_exc.Conflict, - self.shares_v2_client.delete_consistency_group, - self.consistency_group['id'], - version='2.4') - # Verify consistency group is not put into error state from conflict - cg = self.shares_v2_client.get_consistency_group( - self.consistency_group['id'], version='2.4') - self.assertEqual('available', cg['status']) - - @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) - def test_filter_shares_on_nonexistent_cg_id_v2_4(self): - shares = self.shares_v2_client.list_shares( - detailed=True, - params={'consistency_group_id': self.share['id']}, - version='2.4' - ) - - self.assertEqual(0, len(shares), - 'Incorrect number of shares returned. Expected 0, ' - 'got %s.' % len(shares)) - - @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) - def test_filter_shares_on_empty_cg_id_v2_4(self): - consistency_group = self.create_consistency_group( - name='tempest_cg', - description='tempest_cg_desc', - cleanup_in_class=False, - version='2.4', - ) - shares = self.shares_v2_client.list_shares( - detailed=True, - params={'consistency_group_id': consistency_group['id']}, - version='2.4', - ) - - self.assertEqual(0, len(shares), - 'Incorrect number of shares returned. Expected 0, ' - 'got %s.' % len(shares)) diff --git a/manila_tempest_tests/tests/api/test_share_group_actions.py b/manila_tempest_tests/tests/api/test_share_group_actions.py new file mode 100644 index 0000000000..aa582bfb39 --- /dev/null +++ b/manila_tempest_tests/tests/api/test_share_group_actions.py @@ -0,0 +1,395 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 Andrew Kerr +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from tempest import config +from tempest.lib.common.utils import data_utils +import testtools +from testtools import testcase as tc + +from manila_tempest_tests.common import constants +from manila_tempest_tests.tests.api import base + +CONF = config.CONF + + +@testtools.skipUnless( + CONF.share.run_share_group_tests, 'Share Group tests disabled.') +@base.skip_if_microversion_lt(constants.MIN_SHARE_GROUP_MICROVERSION) +class ShareGroupActionsTest(base.BaseSharesTest): + """Covers share group functionality.""" + + @classmethod + def resource_setup(cls): + super(ShareGroupActionsTest, cls).resource_setup() + + # Create first share group + cls.share_group_name = data_utils.rand_name("tempest-sg-name") + cls.share_group_desc = data_utils.rand_name("tempest-sg-description") + cls.share_group = cls.create_share_group( + name=cls.share_group_name, description=cls.share_group_desc) + + # Create second share group for purposes of sorting and snapshot + # filtering + cls.share_group2 = cls.create_share_group( + name=cls.share_group_name, description=cls.share_group_desc) + + # Create 2 shares - inside first and second share groups + 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_size2 = 2 + cls.shares = cls.create_shares([ + {'kwargs': { + 'name': cls.share_name, + 'description': cls.share_desc, + 'size': size, + 'share_group_id': sg_id, + 'experimental': True, + }} for size, sg_id in ((cls.share_size, cls.share_group['id']), + (cls.share_size2, cls.share_group['id']), + (cls.share_size, cls.share_group2['id'])) + ]) + + # Create share group snapshots + cls.sg_snap_name = data_utils.rand_name("tempest-sg-snap-name") + cls.sg_snap_desc = data_utils.rand_name("tempest-sg-snap-desc") + + cls.sg_snapshot = cls.create_share_group_snapshot_wait_for_active( + cls.share_group["id"], + name=cls.sg_snap_name, + description=cls.sg_snap_desc, + ) + + cls.sg_snapshot2 = cls.create_share_group_snapshot_wait_for_active( + cls.share_group2['id'], + name=cls.sg_snap_name, + description=cls.sg_snap_desc, + ) + + @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) + def test_get_share_group_min_supported_sg_microversion(self): + + # Get share group + share_group = self.shares_v2_client.get_share_group( + self.share_group['id'], + version=constants.MIN_SHARE_GROUP_MICROVERSION, + ) + + # Verify keys + actual_keys = set(share_group.keys()) + self.assertTrue( + constants.SHARE_GROUP_DETAIL_REQUIRED_KEYS.issubset(actual_keys), + 'Not all required keys returned for share group %s. ' + 'Expected at least: %s, found %s' % ( + share_group['id'], + constants.SHARE_GROUP_DETAIL_REQUIRED_KEYS, + actual_keys)) + + # Verify values + self.assertEqual(self.share_group_name, share_group["name"]) + self.assertEqual(self.share_group_desc, share_group["description"]) + + @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) + def test_get_share_min_supported_sg_microversion(self): + + # Get share + share = self.shares_v2_client.get_share( + self.shares[0]['id'], + version=constants.MIN_SHARE_GROUP_MICROVERSION, + experimental=True) + + # Verify keys + expected_keys = { + "status", "description", "links", "availability_zone", + "created_at", "share_proto", "name", "snapshot_id", + "id", "size", "share_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 + self.assertEqual(self.share_name, share["name"]) + self.assertEqual(self.share_desc, share["description"]) + self.assertEqual(self.share_size, int(share["size"])) + self.assertEqual(self.share_group["id"], share["share_group_id"]) + + @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) + def test_list_share_groups_min(self): + + # List share groups + share_groups = self.shares_v2_client.list_share_groups( + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + # Verify keys + self.assertGreater(len(share_groups), 0) + for sg in share_groups: + keys = set(sg.keys()) + self.assertEqual( + constants.SHARE_GROUP_SIMPLE_KEYS, + keys, + 'Incorrect keys returned for share group %s. ' + 'Expected: %s, found %s' % ( + sg['id'], + constants.SHARE_GROUP_SIMPLE_KEYS, + ','.join(keys))) + + # Share group ids are in list exactly once + for sg_id in (self.share_group["id"], self.share_group2["id"]): + gen = [sg["id"] for sg in share_groups if sg["id"] == sg_id] + msg = ("Expected id %s exactly once in share group list" % sg_id) + self.assertEqual(1, len(gen), msg) + + @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) + def test_list_share_groups_with_detail_min(self): + + # List share groups + share_groups = self.shares_v2_client.list_share_groups( + detailed=True, version=constants.MIN_SHARE_GROUP_MICROVERSION) + + # Verify keys + for sg in share_groups: + keys = set(sg.keys()) + self.assertTrue( + constants.SHARE_GROUP_DETAIL_REQUIRED_KEYS.issubset( + keys), + 'Not all required keys returned for share group %s. ' + 'Expected at least: %s, found %s' % ( + sg['id'], + constants.SHARE_GROUP_DETAIL_REQUIRED_KEYS, + ','.join(keys), + ) + ) + + # Share group ids are in list exactly once + for group_id in (self.share_group["id"], self.share_group2["id"]): + gen = [share_group["id"] for share_group in share_groups + if share_group["id"] == group_id] + msg = ("Expected id %s exactly once in share group list" % + group_id) + self.assertEqual(1, len(gen), msg) + + @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) + def test_filter_shares_by_share_group_id_min(self): + shares = self.shares_v2_client.list_shares( + detailed=True, + params={'share_group_id': self.share_group['id']}, + version=constants.MIN_SHARE_GROUP_MICROVERSION, + experimental=True, + ) + + 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.shares[0]['id'], share_ids, + 'Share %s expected in returned list, but got %s' % ( + self.shares[0]['id'], share_ids)) + self.assertIn( + self.shares[1]['id'], share_ids, + 'Share %s expected in returned list, but got %s' % ( + self.shares[0]['id'], share_ids)) + + @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) + def test_get_share_group_snapshot_min(self): + # Get share group snapshot + sg_snapshot = self.shares_v2_client.get_share_group_snapshot( + self.sg_snapshot['id'], + version=constants.MIN_SHARE_GROUP_MICROVERSION, + ) + + # Verify keys + actual_keys = set(sg_snapshot.keys()) + self.assertTrue( + constants.SHARE_GROUP_SNAPSHOT_DETAIL_REQUIRED_KEYS.issubset( + actual_keys), + 'Not all required keys returned for share group %s. ' + 'Expected at least: %s, found %s' % ( + sg_snapshot['id'], + constants.SHARE_GROUP_DETAIL_REQUIRED_KEYS, + actual_keys, + ) + ) + + # Verify values + self.assertEqual(self.sg_snap_name, sg_snapshot["name"]) + self.assertEqual(self.sg_snap_desc, sg_snapshot["description"]) + + @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) + def test_get_share_group_snapshot_members_min(self): + sg_snapshot = self.shares_v2_client.get_share_group_snapshot( + self.sg_snapshot['id'], + version=constants.MIN_SHARE_GROUP_MICROVERSION, + ) + sg_snapshot_members = sg_snapshot['members'] + member_share_ids = [m['share_id'] for m in sg_snapshot_members] + self.assertEqual( + 2, len(sg_snapshot_members), + 'Unexpected number of share group snapshot members. ' + 'Expected 2, got %s.' % len(sg_snapshot_members)) + # Verify each share is represented in the share group snapshot + # appropriately + for share_id in (self.shares[0]['id'], self.shares[1]['id']): + self.assertIn( + share_id, member_share_ids, + 'Share missing %s missing from share group ' + 'snapshot. Found %s.' % (share_id, member_share_ids)) + for share in (self.shares[0], self.shares[1]): + for member in sg_snapshot_members: + if share['id'] == member['share_id']: + self.assertEqual(share['size'], member['size']) + + @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) + def test_create_share_group_from_populated_share_group_snapshot_min(self): + + sg_snapshot = self.shares_v2_client.get_share_group_snapshot( + self.sg_snapshot['id'], + version=constants.MIN_SHARE_GROUP_MICROVERSION, + ) + snapshot_members = sg_snapshot['members'] + + new_share_group = self.create_share_group( + cleanup_in_class=False, + source_share_group_snapshot_id=self.sg_snapshot['id'], + version=constants.MIN_SHARE_GROUP_MICROVERSION, + ) + + new_share_group = self.shares_v2_client.get_share_group( + new_share_group['id'], + version=constants.MIN_SHARE_GROUP_MICROVERSION, + ) + + # Verify that share_network information matches source share group + self.assertEqual( + self.share_group['share_network_id'], + new_share_group['share_network_id']) + + new_shares = self.shares_v2_client.list_shares( + params={'share_group_id': new_share_group['id']}, + detailed=True, + version=constants.MIN_SHARE_GROUP_MICROVERSION, + experimental=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 sgsnapshot member is represented in the new sg + # appropriately + share_source_member_ids = [ + share['source_share_group_snapshot_member_id'] + for share in new_shares] + for member in snapshot_members: + self.assertIn( + member['id'], share_source_member_ids, + 'Share group snapshot member %s not represented by ' + 'share group %s.' % (member['id'], new_share_group['id'])) + for share in new_shares: + if (share['source_share_group_snapshot_member_id'] == ( + member['id'])): + self.assertEqual(member['size'], share['size']) + self.assertEqual( + self.share_group['share_network_id'], + share['share_network_id']) + + +@testtools.skipUnless( + CONF.share.run_share_group_tests, 'Share Group tests disabled.') +@base.skip_if_microversion_lt(constants.MIN_SHARE_GROUP_MICROVERSION) +class ShareGroupRenameTest(base.BaseSharesTest): + + @classmethod + def resource_setup(cls): + super(ShareGroupRenameTest, cls).resource_setup() + + # Create share group + cls.share_group_name = data_utils.rand_name("tempest-sg-name") + cls.share_group_desc = data_utils.rand_name("tempest-sg-description") + cls.share_group = cls.create_share_group( + name=cls.share_group_name, + description=cls.share_group_desc, + ) + + @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) + def test_update_share_group_min(self): + + # Get share_group + share_group = self.shares_v2_client.get_share_group( + self.share_group['id'], + version=constants.MIN_SHARE_GROUP_MICROVERSION + ) + self.assertEqual(self.share_group_name, share_group["name"]) + self.assertEqual(self.share_group_desc, share_group["description"]) + + # Update share_group + new_name = data_utils.rand_name("tempest-new-name") + new_desc = data_utils.rand_name("tempest-new-description") + updated = self.shares_v2_client.update_share_group( + share_group["id"], + name=new_name, + description=new_desc, + version=constants.MIN_SHARE_GROUP_MICROVERSION, + ) + self.assertEqual(new_name, updated["name"]) + self.assertEqual(new_desc, updated["description"]) + + # Get share_group + share_group = self.shares_v2_client.get_share_group( + self.share_group['id'], + version=constants.MIN_SHARE_GROUP_MICROVERSION, + ) + self.assertEqual(new_name, share_group["name"]) + self.assertEqual(new_desc, share_group["description"]) + + @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) + def test_create_update_read_share_group_with_unicode_min(self): + value1 = u'ಠ_ಠ' + value2 = u'ಠ_ರೃ' + + # Create share_group + share_group = self.create_share_group( + cleanup_in_class=False, + name=value1, + description=value1, + version=constants.MIN_SHARE_GROUP_MICROVERSION, + ) + self.assertEqual(value1, share_group["name"]) + self.assertEqual(value1, share_group["description"]) + + # Update share group + updated = self.shares_v2_client.update_share_group( + share_group["id"], + name=value2, + description=value2, + version=constants.MIN_SHARE_GROUP_MICROVERSION, + ) + self.assertEqual(value2, updated["name"]) + self.assertEqual(value2, updated["description"]) + + # Get share group + share_group = self.shares_v2_client.get_share_group( + share_group['id'], version=constants.MIN_SHARE_GROUP_MICROVERSION) + self.assertEqual(value2, share_group["name"]) + self.assertEqual(value2, share_group["description"]) diff --git a/manila_tempest_tests/tests/api/test_share_groups.py b/manila_tempest_tests/tests/api/test_share_groups.py new file mode 100644 index 0000000000..98d29e4e27 --- /dev/null +++ b/manila_tempest_tests/tests/api/test_share_groups.py @@ -0,0 +1,165 @@ +# Copyright 2016 Andrew Kerr +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from tempest import config +from tempest.lib import exceptions as lib_exc +import testtools +from testtools import testcase as tc + +from manila_tempest_tests.common import constants +from manila_tempest_tests.tests.api import base + +CONF = config.CONF + + +@testtools.skipUnless( + CONF.share.run_share_group_tests, 'Share Group tests disabled.') +@base.skip_if_microversion_lt(constants.MIN_SHARE_GROUP_MICROVERSION) +class ShareGroupsTest(base.BaseSharesTest): + """Covers share group functionality.""" + + @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) + def test_create_populate_delete_share_group_min(self): + # Create a share group + share_group = self.create_share_group( + cleanup_in_class=False, + version=constants.MIN_SHARE_GROUP_MICROVERSION) + keys = set(share_group.keys()) + self.assertTrue( + constants.SHARE_GROUP_DETAIL_REQUIRED_KEYS.issubset(keys), + 'At least one expected element missing from share group ' + 'response. Expected %(expected)s, got %(actual)s.' % { + "expected": constants.SHARE_GROUP_DETAIL_REQUIRED_KEYS, + "actual": keys} + ) + # Populate + share = self.create_share( + share_group_id=share_group['id'], + cleanup_in_class=False, + version=constants.MIN_SHARE_GROUP_MICROVERSION, + experimental=True) + + # Delete + params = {"share_group_id": share_group['id']} + self.shares_v2_client.delete_share( + share['id'], + params=params, + version=constants.MIN_SHARE_GROUP_MICROVERSION) + self.shares_client.wait_for_resource_deletion(share_id=share['id']) + self.shares_v2_client.delete_share_group( + share_group['id'], version=constants.MIN_SHARE_GROUP_MICROVERSION) + self.shares_v2_client.wait_for_resource_deletion( + share_group_id=share_group['id']) + + # Verify + self.assertRaises( + lib_exc.NotFound, + self.shares_v2_client.get_share_group, share_group['id']) + self.assertRaises( + lib_exc.NotFound, self.shares_client.get_share, share['id']) + + @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) + def test_create_delete_empty_share_group_snapshot_min(self): + # Create base share group + share_group = self.create_share_group( + cleanup_in_class=False, + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + # Create share group snapshot + sg_snapshot = self.create_share_group_snapshot_wait_for_active( + share_group["id"], + cleanup_in_class=False, + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + keys = set(sg_snapshot.keys()) + self.assertTrue( + constants.SHARE_GROUP_SNAPSHOT_DETAIL_REQUIRED_KEYS.issubset(keys), + 'At least one expected element missing from share group snapshot ' + 'response. Expected %(e)s, got %(a)s.' % { + "e": constants.SHARE_GROUP_SNAPSHOT_DETAIL_REQUIRED_KEYS, + "a": keys}) + + sg_snapshot_members = sg_snapshot['members'] + self.assertEmpty( + sg_snapshot_members, + 'Expected 0 share_group_snapshot members, got %s' % len( + sg_snapshot_members)) + + # Delete snapshot + self.shares_v2_client.delete_share_group_snapshot( + sg_snapshot["id"], version=constants.MIN_SHARE_GROUP_MICROVERSION) + self.shares_v2_client.wait_for_resource_deletion( + share_group_snapshot_id=sg_snapshot["id"]) + self.assertRaises( + lib_exc.NotFound, + self.shares_v2_client.get_share_group_snapshot, + sg_snapshot['id'], + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) + def test_create_share_group_from_empty_share_group_snapshot_min(self): + # Create base share group + share_group = self.create_share_group( + cleanup_in_class=False, + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + # Create share group snapshot + sg_snapshot = self.create_share_group_snapshot_wait_for_active( + share_group["id"], cleanup_in_class=False, + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + snapshot_members = sg_snapshot['members'] + + self.assertEmpty( + snapshot_members, + 'Expected 0 share group snapshot members, got %s' % + len(snapshot_members)) + + new_share_group = self.create_share_group( + cleanup_in_class=False, + source_share_group_snapshot_id=sg_snapshot['id'], + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + new_shares = self.shares_v2_client.list_shares( + params={'share_group_id': new_share_group['id']}, + version=constants.MIN_SHARE_GROUP_MICROVERSION, experimental=True) + + self.assertEmpty( + new_shares, 'Expected 0 new shares, got %s' % len(new_shares)) + + msg = ('Expected source_ishare_group_snapshot_id %s ' + 'as source of share group %s' % ( + sg_snapshot['id'], + new_share_group['source_share_group_snapshot_id'])) + self.assertEqual( + new_share_group['source_share_group_snapshot_id'], + sg_snapshot['id'], + msg) + + msg = ('Unexpected share_types on new share group. Expected ' + '%s, got %s.' % (share_group['share_types'], + new_share_group['share_types'])) + self.assertEqual( + sorted(share_group['share_types']), + sorted(new_share_group['share_types']), msg) + + # Assert the share_network information is the same + msg = 'Expected share_network %s as share_network of cg %s' % ( + share_group['share_network_id'], + new_share_group['share_network_id']) + self.assertEqual( + share_group['share_network_id'], + new_share_group['share_network_id'], + msg) diff --git a/manila_tempest_tests/tests/api/test_share_groups_negative.py b/manila_tempest_tests/tests/api/test_share_groups_negative.py new file mode 100644 index 0000000000..b33eca9215 --- /dev/null +++ b/manila_tempest_tests/tests/api/test_share_groups_negative.py @@ -0,0 +1,239 @@ +# Copyright 2016 Andrew Kerr +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from tempest import config +from tempest.lib.common.utils import data_utils +from tempest.lib import exceptions as lib_exc +import testtools +from testtools import testcase as tc + +from manila_tempest_tests.common import constants +from manila_tempest_tests.tests.api import base + +CONF = config.CONF + + +@testtools.skipUnless( + CONF.share.run_share_group_tests, 'Share Group tests disabled.') +@base.skip_if_microversion_lt(constants.MIN_SHARE_GROUP_MICROVERSION) +class ShareGroupsNegativeTest(base.BaseSharesTest): + + @classmethod + def resource_setup(cls): + super(ShareGroupsNegativeTest, cls).resource_setup() + # Create a share group + cls.share_group_name = data_utils.rand_name("tempest-sg-name") + cls.share_group_desc = data_utils.rand_name("tempest-sg-description") + cls.share_group = cls.create_share_group( + name=cls.share_group_name, + description=cls.share_group_desc + ) + # Create a share in the share 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, + share_group_id=cls.share_group['id'], + experimental=True, + ) + # Create a share group snapshot of the share group + cls.sg_snap_name = data_utils.rand_name("tempest-sg-snap-name") + cls.sg_snap_desc = data_utils.rand_name( + "tempest-group-snap-description") + cls.sg_snapshot = cls.create_share_group_snapshot_wait_for_active( + cls.share_group["id"], + name=cls.sg_snap_name, + description=cls.sg_snap_desc + ) + + @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) + def test_create_sg_with_invalid_source_sg_snapshot_id_value_min(self): + self.assertRaises( + lib_exc.BadRequest, + self.create_share_group, + source_share_group_snapshot_id='foobar', + cleanup_in_class=False, + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) + def test_create_sg_with_nonexistent_source_sg_snapshot_id_value_min(self): + self.assertRaises( + lib_exc.BadRequest, + self.create_share_group, + source_share_group_snapshot_id=self.share['id'], + cleanup_in_class=False, + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) + def test_create_sg_with_invalid_share_network_id_value_min(self): + self.assertRaises( + lib_exc.BadRequest, + self.create_share_group, + share_network_id='foobar', + cleanup_in_class=False, + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) + def test_create_group_with_nonexistent_share_network_id_value_min(self): + self.assertRaises( + lib_exc.BadRequest, + self.create_share_group, + share_network_id=self.share['id'], + cleanup_in_class=False, + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) + def test_create_sg_with_invalid_share_type_id_value_min(self): + self.assertRaises( + lib_exc.BadRequest, + self.create_share_group, + share_type_ids=['foobar'], + cleanup_in_class=False, + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) + def test_create_sg_with_nonexistent_share_type_id_value_min(self): + self.assertRaises( + lib_exc.BadRequest, + self.create_share_group, + share_type_ids=[self.share['id']], + cleanup_in_class=False, + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) + def test_create_sg_snapshot_with_invalid_sg_id_value_min(self): + self.assertRaises( + lib_exc.BadRequest, + self.create_share_group_snapshot_wait_for_active, + 'foobar', + cleanup_in_class=False, + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) + def test_create_sg_snapshot_with_nonexistent_sg_id_value_min(self): + self.assertRaises( + lib_exc.BadRequest, + self.create_share_group_snapshot_wait_for_active, + self.share['id'], + cleanup_in_class=False, + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) + def test_get_sg_with_invalid_id_min(self): + self.assertRaises( + lib_exc.NotFound, + self.shares_v2_client.get_share_group, + "invalid_share_group_id", + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) + def test_get_sg_without_passing_group_id_min(self): + self.assertRaises( + lib_exc.NotFound, + self.shares_v2_client.get_share_group, + '', version=constants.MIN_SHARE_GROUP_MICROVERSION) + + @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) + def test_update_sg_with_invalid_id_min(self): + self.assertRaises( + lib_exc.NotFound, + self.shares_v2_client.update_share_group, + 'invalid_share_group_id', + name='new_name', + description='new_description', + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) + def test_delete_sg_with_invalid_id_min(self): + self.assertRaises( + lib_exc.NotFound, + self.shares_v2_client.delete_share_group, + "invalid_share_group_id", + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) + def test_delete_sg_without_passing_sg_id_min(self): + self.assertRaises( + lib_exc.NotFound, + self.shares_v2_client.delete_share_group, + '', version=constants.MIN_SHARE_GROUP_MICROVERSION) + + @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) + def test_delete_sg_in_use_by_sg_snapshot_min(self): + self.assertRaises( + lib_exc.Conflict, + self.shares_v2_client.delete_share_group, + self.share_group['id'], + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) + def test_delete_share_in_use_by_sg_snapshot_min(self): + params = {'share_group_id': self.share['share_group_id']} + self.assertRaises( + lib_exc.Forbidden, + self.shares_v2_client.delete_share, + self.share['id'], + params=params, + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) + def test_delete_sg_containing_a_share_min(self): + self.assertRaises( + lib_exc.Conflict, + self.shares_v2_client.delete_share_group, + self.share_group['id'], + version=constants.MIN_SHARE_GROUP_MICROVERSION) + + # Verify share group is not put into error state from conflict + sg = self.shares_v2_client.get_share_group( + self.share_group['id'], + version=constants.MIN_SHARE_GROUP_MICROVERSION) + self.assertEqual('available', sg['status']) + + @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) + def test_filter_shares_on_invalid_group_id_min(self): + shares = self.shares_v2_client.list_shares( + detailed=True, + params={'share_group_id': 'foobar'}, + version=constants.MIN_SHARE_GROUP_MICROVERSION, + ) + self.assertEqual(0, len(shares), 'Incorrect number of shares returned') + + @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) + def test_filter_shares_on_nonexistent_group_id_min(self): + shares = self.shares_v2_client.list_shares( + detailed=True, + params={'share_group_id': self.share['id']}, + version=constants.MIN_SHARE_GROUP_MICROVERSION, + ) + self.assertEqual(0, len(shares), 'Incorrect number of shares returned') + + @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) + def test_filter_shares_on_empty_share_group_id_min(self): + share_group = self.create_share_group( + name='tempest_sg', + description='tempest_sg_desc', + cleanup_in_class=False, + version=constants.MIN_SHARE_GROUP_MICROVERSION, + ) + shares = self.shares_v2_client.list_shares( + detailed=True, + params={'share_group_id': share_group['id']}, + version=constants.MIN_SHARE_GROUP_MICROVERSION, + ) + self.assertEqual(0, len(shares), 'Incorrect number of shares returned')