Merge "Add share transfer test."
diff --git a/manila_tempest_tests/common/constants.py b/manila_tempest_tests/common/constants.py
index 3488bc5..416de56 100644
--- a/manila_tempest_tests/common/constants.py
+++ b/manila_tempest_tests/common/constants.py
@@ -108,3 +108,6 @@
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 fe3e31c..f4538d7 100644
--- a/manila_tempest_tests/services/share/v2/json/shares_client.py
+++ b/manila_tempest_tests/services/share/v2/json/shares_client.py
@@ -373,6 +373,61 @@
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):
resp, body = self.get("shares/%s/instances" % share_id,
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 0000000..56f8e0c
--- /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 0000000..0672459
--- /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")