# 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. import http.client as http_client from unittest import mock import ddt from oslo_serialization import jsonutils import webob from manila.api.v2 import share_transfer from manila import context from manila import db from manila import exception from manila import quota from manila.share import api as share_api from manila.share import rpcapi as share_rpcapi from manila.share import share_types from manila import test from manila.tests.api import fakes from manila.tests import db_utils from manila.transfer import api as transfer_api SHARE_TRANSFER_VERSION = "2.77" @ddt.ddt class ShareTransferAPITestCase(test.TestCase): """Test Case for transfers V3 API.""" microversion = SHARE_TRANSFER_VERSION def setUp(self): super(ShareTransferAPITestCase, self).setUp() self.share_transfer_api = transfer_api.API() self.v2_controller = share_transfer.ShareTransferController() self.ctxt = context.RequestContext( 'fake_user_id', 'fake_project_id', auth_token=True, is_admin=True) def _create_transfer(self, share_id='fake_share_id', display_name='test_transfer'): transfer = self.share_transfer_api.create(context.get_admin_context(), share_id, display_name) return transfer def _create_share(self, display_name='test_share', display_description='this is a test share', status='available', size=1, project_id='fake_project_id', user_id='fake_user_id', share_network_id=None, mount_point_name=None): """Create a share object.""" share_type = db_utils.create_share_type() if mount_point_name: instance_list = [ db_utils.create_share_instance( status=status, share_id='fake_id', mount_point_name=mount_point_name ) ] share = db_utils.create_share( display_name=display_name, display_description=display_description, status=status, size=size, project_id=project_id, user_id=user_id, share_type_id=share_type['id'], share_network_id=share_network_id, instances=instance_list ) else: share = db_utils.create_share( display_name=display_name, display_description=display_description, status=status, size=size, project_id=project_id, user_id=user_id, share_type_id=share_type['id'], share_network_id=share_network_id, mount_point_name=mount_point_name ) share_id = share['id'] return share_id def test_show_transfer(self): share_id = self._create_share(size=5) transfer = self._create_transfer(share_id) path = '/v2/fake_project_id/share-transfers/%s' % transfer['id'] req = fakes.HTTPRequest.blank(path, version=self.microversion) req.environ['manila.context'] = self.ctxt req.method = 'GET' req.headers['Content-Type'] = 'application/json' res_dict = self.v2_controller.show(req, transfer['id']) self.assertEqual('test_transfer', res_dict['transfer']['name']) self.assertEqual(transfer['id'], res_dict['transfer']['id']) self.assertEqual(share_id, res_dict['transfer']['resource_id']) def test_list_transfers(self): share_id_1 = self._create_share(size=5) share_id_2 = self._create_share(size=5) transfer1 = self._create_transfer(share_id_1) transfer2 = self._create_transfer(share_id_2) path = '/v2/fake_project_id/share-transfers' req = fakes.HTTPRequest.blank(path, version=self.microversion) req.environ['manila.context'] = self.ctxt req.method = 'GET' req.headers['Content-Type'] = 'application/json' res_dict = self.v2_controller.index(req) self.assertEqual(transfer1['id'], res_dict['transfers'][1]['id']) self.assertEqual('test_transfer', res_dict['transfers'][1]['name']) self.assertEqual(transfer2['id'], res_dict['transfers'][0]['id']) self.assertEqual('test_transfer', res_dict['transfers'][0]['name']) def test_list_transfers_with_all_tenants(self): share_id_1 = self._create_share(size=5) share_id_2 = self._create_share(size=5, project_id='fake_project_id2', user_id='fake_user_id2') self._create_transfer(share_id_1) self._create_transfer(share_id_2) path = '/v2/fake_project_id/share-transfers?all_tenants=true' req = fakes.HTTPRequest.blank(path, version=self.microversion) req.environ['manila.context'] = context.get_admin_context() req.method = 'GET' req.headers['Content-Type'] = 'application/json' res_dict = self.v2_controller.index(req) self.assertEqual(2, len(res_dict['transfers'])) def test_list_transfers_with_limit(self): share_id_1 = self._create_share(size=5) share_id_2 = self._create_share(size=5) self._create_transfer(share_id_1) self._create_transfer(share_id_2) path = '/v2/fake_project_id/share-transfers?limit=1' req = fakes.HTTPRequest.blank(path, version=self.microversion) req.environ['manila.context'] = self.ctxt req.method = 'GET' req.headers['Content-Type'] = 'application/json' res_dict = self.v2_controller.index(req) self.assertEqual(1, len(res_dict['transfers'])) @ddt.data("desc", "asc") def test_list_transfers_with_sort(self, sort_dir): share_id_1 = self._create_share(size=5) share_id_2 = self._create_share(size=5) transfer1 = self._create_transfer(share_id_1) transfer2 = self._create_transfer(share_id_2) path = \ '/v2/fake_project_id/share-transfers?sort_key=id&sort_dir=%s' % ( sort_dir) req = fakes.HTTPRequest.blank(path, version=self.microversion) req.environ['manila.context'] = self.ctxt req.method = 'GET' req.headers['Content-Type'] = 'application/json' res_dict = self.v2_controller.index(req) self.assertEqual(2, len(res_dict['transfers'])) order_ids = sorted([transfer1['id'], transfer2['id']]) expect_result = order_ids[1] if sort_dir == "desc" else order_ids[0] self.assertEqual(expect_result, res_dict['transfers'][0]['id']) def test_list_transfers_detail(self): share_id_1 = self._create_share(size=5) share_id_2 = self._create_share(size=5) transfer1 = self._create_transfer(share_id_1) transfer2 = self._create_transfer(share_id_2) path = '/v2/fake_project_id/share-transfers/detail' req = fakes.HTTPRequest.blank(path, version=self.microversion) req.environ['manila.context'] = self.ctxt req.method = 'GET' req.headers['Content-Type'] = 'application/json' req.headers['Accept'] = 'application/json' res_dict = self.v2_controller.detail(req) self.assertEqual('test_transfer', res_dict['transfers'][1]['name']) self.assertEqual(transfer1['id'], res_dict['transfers'][1]['id']) self.assertEqual(share_id_1, res_dict['transfers'][1]['resource_id']) self.assertEqual('test_transfer', res_dict['transfers'][0]['name']) self.assertEqual(transfer2['id'], res_dict['transfers'][0]['id']) self.assertEqual(share_id_2, res_dict['transfers'][0]['resource_id']) def test_create_transfer(self): share_id = self._create_share(status='available', size=5) body = {"transfer": {"name": "transfer1", "share_id": share_id}} path = '/v2/fake_project_id/share-transfers' req = fakes.HTTPRequest.blank(path, version=self.microversion) req.environ['manila.context'] = self.ctxt req.method = 'POST' req.headers['Content-Type'] = 'application/json' req.body = jsonutils.dumps(body).encode("utf-8") res_dict = self.v2_controller.create(req, body) self.assertIn('id', res_dict['transfer']) self.assertIn('auth_key', res_dict['transfer']) self.assertIn('created_at', res_dict['transfer']) self.assertIn('name', res_dict['transfer']) self.assertIn('resource_id', res_dict['transfer']) @ddt.data({}, {"transfer": {"name": "transfer1"}}, {"transfer": {"name": "transfer1", "share_id": "invalid_share_id"}}) def test_create_transfer_with_invalid_body(self, body): path = '/v2/fake_project_id/share-transfers' req = fakes.HTTPRequest.blank(path, version=self.microversion) req.environ['manila.context'] = self.ctxt req.method = 'POST' req.headers['Content-Type'] = 'application/json' req.body = jsonutils.dumps(body).encode("utf-8") self.assertRaises(webob.exc.HTTPBadRequest, self.v2_controller.create, req, body) def test_create_transfer_with_invalid_share_status(self): share_id = self._create_share() body = {"transfer": {"name": "transfer1", "share_id": share_id}} db.share_update(context.get_admin_context(), share_id, {'status': 'error'}) path = '/v2/fake_project_id/share-transfers' req = fakes.HTTPRequest.blank(path, version=self.microversion) req.environ['manila.context'] = self.ctxt req.method = 'POST' req.headers['Content-Type'] = 'application/json' req.body = jsonutils.dumps(body).encode("utf-8") self.assertRaises(webob.exc.HTTPBadRequest, self.v2_controller.create, req, body) def test_create_transfer_with_invalid_mount_point_name(self): share_id = self._create_share( project_id='fake_pid', mount_point_name='fake_pid_mount_point_name') body = {"transfer": {"name": "transfer1", "share_id": share_id}} db.share_update(context.get_admin_context(), share_id, {'status': 'error'}) path = '/v2/fake_project_id/share-transfers' req = fakes.HTTPRequest.blank(path, version=self.microversion) req.environ['manila.context'] = self.ctxt req.method = 'POST' req.headers['Content-Type'] = 'application/json' req.body = jsonutils.dumps(body).encode("utf-8") self.assertRaises(webob.exc.HTTPBadRequest, self.v2_controller.create, req, body) def test_create_transfer_with_project_id_prefix_mount_point_name(self): share_id = self._create_share(project_id='fake', mount_point_name='fake_mp') '''self.share_transfer_api.create(context.get_admin_context(), share_id, 'test_missing_share_type')''' self.assertRaises(exception.Invalid, self.share_transfer_api.create, context.get_admin_context(), share_id, 'test_missing_share_type') def test_create_transfer_share_with_network_id(self): share_id = self._create_share(share_network_id='fake_id') body = {"transfer": {"name": "transfer1", "share_id": share_id}} path = '/v2/fake_project_id/share-transfers' req = fakes.HTTPRequest.blank(path, version=self.microversion) req.environ['manila.context'] = self.ctxt req.method = 'POST' req.headers['Content-Type'] = 'application/json' req.body = jsonutils.dumps(body).encode("utf-8") self.assertRaises(webob.exc.HTTPBadRequest, self.v2_controller.create, req, body) def test_create_transfer_share_with_invalid_snapshot(self): share_id = self._create_share(share_network_id='fake_id') db_utils.create_snapshot(share_id=share_id) body = {"transfer": {"name": "transfer1", "share_id": share_id}} path = '/v2/fake_project_id/share-transfers' req = fakes.HTTPRequest.blank(path, version=self.microversion) req.environ['manila.context'] = self.ctxt req.method = 'POST' req.headers['Content-Type'] = 'application/json' req.body = jsonutils.dumps(body).encode("utf-8") self.assertRaises(webob.exc.HTTPBadRequest, self.v2_controller.create, req, body) def test_delete_transfer_awaiting_transfer(self): share_id = self._create_share() transfer = self.share_transfer_api.create(context.get_admin_context(), share_id, 'test_transfer') path = '/v2/fake_project_id/share-transfers/%s' % transfer['id'] req = fakes.HTTPRequest.blank(path, version=self.microversion) req.environ['manila.context'] = self.ctxt req.method = 'DELETE' req.headers['Content-Type'] = 'application/json' self.v2_controller.delete(req, transfer['id']) # verify transfer has been deleted req = fakes.HTTPRequest.blank(path, version=self.microversion) req.environ['manila.context'] = self.ctxt req.method = 'GET' req.headers['Content-Type'] = 'application/json' res = req.get_response(fakes.app()) self.assertEqual(http_client.NOT_FOUND, res.status_int) self.assertEqual(db.share_get(context.get_admin_context(), share_id)['status'], 'available') def test_delete_transfer_not_awaiting_transfer(self): share_id = self._create_share() transfer = self.share_transfer_api.create(context.get_admin_context(), share_id, 'test_transfer') db.share_update(context.get_admin_context(), share_id, {'status': 'available'}) path = '/v2/fake_project_id/share-transfers/%s' % transfer['id'] req = fakes.HTTPRequest.blank(path, version=self.microversion) req.environ['manila.context'] = self.ctxt req.method = 'DELETE' req.headers['Content-Type'] = 'application/json' self.assertRaises(exception.InvalidShare, self.v2_controller.delete, req, transfer['id']) def test_transfer_accept_share_id_specified(self): share_id = self._create_share() transfer = self.share_transfer_api.create(context.get_admin_context(), share_id, 'test_transfer') self.mock_object(quota.QUOTAS, 'reserve', mock.Mock()) self.mock_object(quota.QUOTAS, 'commit', mock.Mock()) self.mock_object(share_api.API, 'check_is_share_size_within_per_share_quota_limit', mock.Mock()) self.mock_object(share_rpcapi.ShareAPI, 'transfer_accept', mock.Mock()) fake_share_type = {'id': 'fake_id', 'name': 'fake_name', 'is_public': True} self.mock_object(share_types, 'get_share_type', mock.Mock(return_value=fake_share_type)) self.mock_object(db, 'share_snapshot_get_all_for_share', mock.Mock(return_value={})) body = {"accept": {"auth_key": transfer['auth_key']}} path = '/v2/fake_project_id/share-transfers/%s/accept' % transfer['id'] req = fakes.HTTPRequest.blank(path, version=self.microversion) req.environ['manila.context'] = self.ctxt req.method = 'POST' req.headers['Content-Type'] = 'application/json' req.body = jsonutils.dumps(body).encode("utf-8") self.v2_controller.accept(req, transfer['id'], body) def test_transfer_accept_with_not_public_share_type(self): share_id = self._create_share() transfer = self.share_transfer_api.create(context.get_admin_context(), share_id, 'test_transfer') fake_share_type = {'id': 'fake_id', 'name': 'fake_name', 'is_public': False, 'projects': ['project_id1', 'project_id2']} self.mock_object(share_types, 'get_share_type', mock.Mock(return_value=fake_share_type)) body = {"accept": {"auth_key": transfer['auth_key']}} path = '/v2/fake_project_id/share-transfers/%s/accept' % transfer['id'] req = fakes.HTTPRequest.blank(path, version=self.microversion) req.environ['manila.context'] = self.ctxt req.method = 'POST' req.headers['Content-Type'] = 'application/json' req.body = jsonutils.dumps(body).encode("utf-8") self.assertRaises(webob.exc.HTTPBadRequest, self.v2_controller.accept, req, transfer['id'], body) @ddt.data({}, {"accept": {}}, {"accept": {"auth_key": "fake_auth_key", "clear_access_rules": "invalid_bool"}}) def test_transfer_accept_with_invalid_body(self, body): path = '/v2/fake_project_id/share-transfers/fake_transfer_id/accept' req = fakes.HTTPRequest.blank(path, version=self.microversion) req.environ['manila.context'] = self.ctxt req.method = 'POST' req.headers['Content-Type'] = 'application/json' req.body = jsonutils.dumps(body).encode("utf-8") self.assertRaises(webob.exc.HTTPBadRequest, self.v2_controller.accept, req, 'fake_transfer_id', body) def test_transfer_accept_with_invalid_auth_key(self): share_id = self._create_share(size=5) transfer = self._create_transfer(share_id) body = {"accept": {"auth_key": "invalid_auth_key"}} path = '/v2/fake_project_id/share-transfers/%s/accept' % transfer['id'] req = fakes.HTTPRequest.blank(path, version=self.microversion) req.environ['manila.context'] = self.ctxt req.method = 'POST' req.headers['Content-Type'] = 'application/json' req.body = jsonutils.dumps(body).encode("utf-8") self.assertRaises(webob.exc.HTTPBadRequest, self.v2_controller.accept, req, transfer['id'], body) def test_transfer_accept_with_invalid_share_status(self): share_id = self._create_share(size=5) transfer = self._create_transfer(share_id) db.share_update(context.get_admin_context(), share_id, {'status': 'error'}) body = {"accept": {"auth_key": transfer['auth_key']}} path = '/v2/fake_project_id/share-transfers/%s/accept' % transfer['id'] req = fakes.HTTPRequest.blank(path, version=self.microversion) req.environ['manila.context'] = self.ctxt req.method = 'POST' req.headers['Content-Type'] = 'application/json' req.body = jsonutils.dumps(body).encode("utf-8") self.assertRaises(webob.exc.HTTPBadRequest, self.v2_controller.accept, req, transfer['id'], body) @ddt.data({'overs': {'gigabytes': 'fake'}}, {'overs': {'shares': 'fake'}}, {'overs': {'snapshot_gigabytes': 'fake'}}, {'overs': {'snapshots': 'fake'}}) @ddt.unpack def test_accept_share_over_quota(self, overs): share_id = self._create_share() db_utils.create_snapshot(share_id=share_id, status='available') transfer = self.share_transfer_api.create(context.get_admin_context(), share_id, 'test_transfer') usages = {'gigabytes': {'reserved': 5, 'in_use': 5}, 'shares': {'reserved': 10, 'in_use': 10}, 'snapshot_gigabytes': {'reserved': 5, 'in_use': 5}, 'snapshots': {'reserved': 10, 'in_use': 10}} quotas = {'gigabytes': 5, 'shares': 10, 'snapshot_gigabytes': 5, 'snapshots': 10} exc = exception.OverQuota(overs=overs, usages=usages, quotas=quotas) self.mock_object(quota.QUOTAS, 'reserve', mock.Mock(side_effect=exc)) self.mock_object(quota.QUOTAS, 'commit', mock.Mock()) self.mock_object(share_api.API, 'check_is_share_size_within_per_share_quota_limit', mock.Mock()) self.mock_object(share_rpcapi.ShareAPI, 'transfer_accept', mock.Mock()) fake_share_type = {'id': 'fake_id', 'name': 'fake_name', 'is_public': True} self.mock_object(share_types, 'get_share_type', mock.Mock(return_value=fake_share_type)) body = {"accept": {"auth_key": transfer['auth_key']}} path = '/v2/fake_project_id/share-transfers/%s/accept' % transfer['id'] req = fakes.HTTPRequest.blank(path, version=self.microversion) req.environ['manila.context'] = self.ctxt req.method = 'POST' req.headers['Content-Type'] = 'application/json' req.body = jsonutils.dumps(body).encode("utf-8") self.assertRaises(webob.exc.HTTPRequestEntityTooLarge, self.v2_controller.accept, req, transfer['id'], body)