diff --git a/manila_tempest_tests/common/constants.py b/manila_tempest_tests/common/constants.py index 3488bc56..416de566 100644 --- a/manila_tempest_tests/common/constants.py +++ b/manila_tempest_tests/common/constants.py @@ -108,3 +108,6 @@ SERVER_STATE_UNMANAGE_ERROR = 'unmanage_error' SERVER_STATE_UNMANAGE_STARTING = 'unmanage_starting' STATUS_SERVER_MIGRATING = 'server_migrating' STATUS_SERVER_MIGRATING_TO = 'server_migrating_to' + +# Share transfer +SHARE_TRANSFER_VERSION = "2.77" 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 fe3e31c3..f4538d78 100644 --- a/manila_tempest_tests/services/share/v2/json/shares_client.py +++ b/manila_tempest_tests/services/share/v2/json/shares_client.py @@ -372,6 +372,61 @@ class SharesV2Client(shares_client.SharesClient): self.expected_success(202, resp.status) return rest_client.ResponseBody(resp, body) +############### + def create_share_transfer(self, share_id, name=None, + version=LATEST_MICROVERSION): + if name is None: + name = data_utils.rand_name("tempest-created-share-transfer") + post_body = { + "transfer": { + "share_id": share_id, + "name": name + } + } + body = json.dumps(post_body) + resp, body = self.post("share-transfers", body, version=version) + self.expected_success(202, resp.status) + body = json.loads(body) + return rest_client.ResponseBody(resp, body) + + def delete_share_transfer(self, transfer_id, version=LATEST_MICROVERSION): + resp, body = self.delete("share-transfers/%s" % transfer_id, + version=version) + self.expected_success(200, resp.status) + return rest_client.ResponseBody(resp, body) + + def list_share_transfers(self, detailed=False, params=None, + version=LATEST_MICROVERSION): + """Get list of share transfers w/o filters.""" + uri = 'share-transfers/detail' if detailed else 'share-transfers' + uri += '?%s' % parse.urlencode(params) if params else '' + resp, body = self.get(uri, version=version) + self.expected_success(200, resp.status) + body = json.loads(body) + return rest_client.ResponseBody(resp, body) + + def get_share_transfer(self, transfer_id, version=LATEST_MICROVERSION): + resp, body = self.get("share-transfers/%s" % transfer_id, + version=version) + self.expected_success(200, resp.status) + body = json.loads(body) + return rest_client.ResponseBody(resp, body) + + def accept_share_transfer(self, transfer_id, auth_key, + clear_access_rules=False, + version=LATEST_MICROVERSION): + post_body = { + "accept": { + "auth_key": auth_key, + "clear_access_rules": clear_access_rules + } + } + body = json.dumps(post_body) + resp, body = self.post("share-transfers/%s/accept" % transfer_id, + body, version=version) + self.expected_success(202, resp.status) + return rest_client.ResponseBody(resp, body) + ############### def get_instances_of_share(self, share_id, version=LATEST_MICROVERSION): diff --git a/manila_tempest_tests/tests/api/test_share_transfers.py b/manila_tempest_tests/tests/api/test_share_transfers.py new file mode 100644 index 00000000..56f8e0cf --- /dev/null +++ b/manila_tempest_tests/tests/api/test_share_transfers.py @@ -0,0 +1,107 @@ +# Copyright (C) 2022 China Telecom Digital Intelligence. +# 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 decorators +from testtools import testcase as tc + +from manila_tempest_tests.common import constants +from manila_tempest_tests.common import waiters +from manila_tempest_tests.tests.api import base +from manila_tempest_tests import utils + +CONF = config.CONF + + +class ShareTransferTest(base.BaseSharesMixedTest): + + @classmethod + def skip_checks(cls): + super(ShareTransferTest, cls).skip_checks() + utils.check_skip_if_microversion_not_supported( + constants.SHARE_TRANSFER_VERSION) + if CONF.share.multitenancy_enabled: + raise cls.skipException( + 'Only for driver_handles_share_servers = False driver mode.') + + @classmethod + def resource_setup(cls): + super(ShareTransferTest, cls).resource_setup() + # create share_type with dhss=False + extra_specs = cls.add_extra_specs_to_dict() + cls.share_type = cls.create_share_type(extra_specs=extra_specs) + cls.share_type_id = cls.share_type['id'] + + @decorators.idempotent_id('716e71a0-8265-4410-9170-08714095d9e8') + @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) + def test_create_and_delete_share_transfer(self): + # create share + share_name = data_utils.rand_name("tempest-share-name") + share = self.create_share(name=share_name, + share_type_id=self.share_type_id, + cleanup_in_class=False) + + # create share transfer + transfer = self.shares_v2_client.create_share_transfer( + share['id'], name='tempest_share_transfer')['transfer'] + waiters.wait_for_resource_status( + self.shares_client, share['id'], 'awaiting_transfer') + + # check transfer exists and show transfer + transfer_show = self.shares_v2_client.get_share_transfer( + transfer['id'])['transfer'] + self.assertEqual(transfer_show['name'], 'tempest_share_transfer') + + # delete share transfer + self.shares_v2_client.delete_share_transfer(transfer['id']) + waiters.wait_for_resource_status( + self.shares_client, share['id'], 'available') + + # check transfer not in transfer list + transfers = self.shares_v2_client.list_share_transfers()['transfers'] + transfer_ids = [tf['id'] for tf in transfers] + self.assertNotIn(transfer['id'], transfer_ids) + + @decorators.idempotent_id('3c2622ab-3368-4693-afb6-e60bd27e61ef') + @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) + def test_create_and_accept_share_transfer(self): + # create share + share_name = data_utils.rand_name("tempest-share-name") + share = self.create_share(name=share_name, + share_type_id=self.share_type_id) + + # create share transfer + transfer = self.shares_v2_client.create_share_transfer( + share['id'])['transfer'] + waiters.wait_for_resource_status( + self.shares_client, share['id'], 'awaiting_transfer') + + # accept share transfer by alt project + self.alt_shares_v2_client.accept_share_transfer(transfer['id'], + transfer['auth_key']) + waiters.wait_for_resource_status( + self.alt_shares_client, share['id'], 'available') + + # check share in alt project + shares = self.alt_shares_v2_client.list_shares( + detailed=True)['shares'] + share_ids = [sh['id'] for sh in shares] if shares else [] + self.assertIn(share['id'], share_ids) + + # delete the share + self.alt_shares_v2_client.delete_share(share['id']) + self.alt_shares_v2_client.wait_for_resource_deletion( + share_id=share["id"]) diff --git a/manila_tempest_tests/tests/api/test_share_transfers_negative.py b/manila_tempest_tests/tests/api/test_share_transfers_negative.py new file mode 100644 index 00000000..06724595 --- /dev/null +++ b/manila_tempest_tests/tests/api/test_share_transfers_negative.py @@ -0,0 +1,137 @@ +# Copyright (C) 2022 China Telecom Digital Intelligence. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from oslo_utils import uuidutils +from tempest import config +from tempest.lib.common.utils import data_utils +from tempest.lib import decorators +from tempest.lib import exceptions as lib_exc +from testtools import testcase as tc + +from manila_tempest_tests.common import constants +from manila_tempest_tests.common import waiters +from manila_tempest_tests.tests.api import base +from manila_tempest_tests import utils + +CONF = config.CONF + + +class ShareTransferNegativeTest(base.BaseSharesMixedTest): + + @classmethod + def skip_checks(cls): + super(ShareTransferNegativeTest, cls).skip_checks() + utils.check_skip_if_microversion_not_supported( + constants.SHARE_TRANSFER_VERSION) + if CONF.share.multitenancy_enabled: + raise cls.skipException( + 'Only for driver_handles_share_servers = False driver mode.') + + @classmethod + def resource_setup(cls): + super(ShareTransferNegativeTest, cls).resource_setup() + # create share_type with dhss=False + extra_specs = cls.add_extra_specs_to_dict() + cls.share_type = cls.create_share_type(extra_specs=extra_specs) + cls.share_type_id = cls.share_type['id'] + + def _create_share_transfer(self, share): + transfer = self.shares_v2_client.create_share_transfer( + share['id'])['transfer'] + waiters.wait_for_resource_status( + self.shares_client, share['id'], 'awaiting_transfer') + self.addCleanup(waiters.wait_for_resource_status, self.shares_client, + share['id'], 'available') + self.addCleanup(self.shares_v2_client.delete_share_transfer, + transfer['id']) + return transfer + + @decorators.idempotent_id('baf66f62-253e-40dd-a6a9-109bc7613e52') + @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) + def test_show_transfer_of_other_tenants(self): + # create share + share_name = data_utils.rand_name("tempest-share-name") + share = self.create_share( + name=share_name, + share_type_id=self.share_type_id) + + # create share transfer + transfer = self._create_share_transfer(share) + + self.assertRaises(lib_exc.NotFound, + self.alt_shares_v2_client.get_share_transfer, + transfer['id']) + + @decorators.idempotent_id('4b9e75b1-4ac6-4111-b09e-e6dacd0ac2c3') + @tc.attr(base.TAG_NEGATIVE, base.TAG_API) + def test_show_nonexistent_transfer(self): + self.assertRaises(lib_exc.NotFound, + self.shares_v2_client.get_share_transfer, + str(uuidutils.generate_uuid())) + + @decorators.idempotent_id('b3e26356-5eb0-4f73-b5a7-d3594cc2f30e') + @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) + def test_delete_transfer_of_other_tenants(self): + # create share + share_name = data_utils.rand_name("tempest-share-name") + share = self.create_share( + name=share_name, + share_type_id=self.share_type_id) + + # create share transfer + transfer = self._create_share_transfer(share) + + self.assertRaises(lib_exc.NotFound, + self.alt_shares_v2_client.delete_share_transfer, + transfer['id']) + + @decorators.idempotent_id('085d5971-fe6e-4497-93cb-f1eb176a10da') + @tc.attr(base.TAG_NEGATIVE, base.TAG_API) + def test_delete_nonexistent_transfer(self): + self.assertRaises(lib_exc.NotFound, + self.shares_v2_client.delete_share_transfer, + str(uuidutils.generate_uuid())) + + @decorators.idempotent_id('cc7af032-0504-417e-8ab9-73b37bed7f85') + @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) + def test_accept_transfer_without_auth_key(self): + # create share + share_name = data_utils.rand_name("tempest-share-name") + share = self.create_share( + name=share_name, + share_type_id=self.share_type_id) + + # create share transfer + transfer = self._create_share_transfer(share) + + self.assertRaises(lib_exc.BadRequest, + self.alt_shares_v2_client.accept_share_transfer, + transfer['id'], "") + + @decorators.idempotent_id('05a6a345-7609-421f-be21-d79041970674') + @tc.attr(base.TAG_NEGATIVE, base.TAG_API) + def test_accept_transfer_with_incorrect_auth_key(self): + # create share + share_name = data_utils.rand_name("tempest-share-name") + share = self.create_share( + name=share_name, + share_type_id=self.share_type_id) + + # create share transfer + transfer = self._create_share_transfer(share) + + self.assertRaises(lib_exc.BadRequest, + self.alt_shares_v2_client.accept_share_transfer, + transfer['id'], "incorrect_auth_key")