Coverage for manila/tests/api/v2/test_share_transfer.py: 100%
304 statements
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
1# Copyright (c) 2022 China Telecom Digital Intelligence.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
16import http.client as http_client
17from unittest import mock
19import ddt
20from oslo_serialization import jsonutils
21import webob
23from manila.api.v2 import share_transfer
24from manila import context
25from manila import db
26from manila import exception
27from manila import quota
28from manila.share import api as share_api
29from manila.share import rpcapi as share_rpcapi
30from manila.share import share_types
31from manila import test
32from manila.tests.api import fakes
33from manila.tests import db_utils
34from manila.transfer import api as transfer_api
36SHARE_TRANSFER_VERSION = "2.77"
39@ddt.ddt
40class ShareTransferAPITestCase(test.TestCase):
41 """Test Case for transfers V3 API."""
43 microversion = SHARE_TRANSFER_VERSION
45 def setUp(self):
46 super(ShareTransferAPITestCase, self).setUp()
47 self.share_transfer_api = transfer_api.API()
48 self.v2_controller = share_transfer.ShareTransferController()
49 self.ctxt = context.RequestContext(
50 'fake_user_id', 'fake_project_id', auth_token=True, is_admin=True)
52 def _create_transfer(self, share_id='fake_share_id',
53 display_name='test_transfer'):
54 transfer = self.share_transfer_api.create(context.get_admin_context(),
55 share_id, display_name)
56 return transfer
58 def _create_share(self, display_name='test_share',
59 display_description='this is a test share',
60 status='available',
61 size=1,
62 project_id='fake_project_id',
63 user_id='fake_user_id',
64 share_network_id=None,
65 mount_point_name=None):
66 """Create a share object."""
67 share_type = db_utils.create_share_type()
68 instance_list = []
69 if mount_point_name:
70 instance_list.append(
71 db_utils.create_share_instance(
72 status=status,
73 share_id='fake_id',
74 mount_point_name=mount_point_name
75 )
76 )
78 share = db_utils.create_share(
79 display_name=display_name,
80 display_description=display_description,
81 status=status,
82 size=size,
83 project_id=project_id,
84 user_id=user_id,
85 share_type_id=share_type['id'],
86 share_network_id=share_network_id,
87 instances=instance_list,
88 mount_point_name=mount_point_name
89 )
91 share_id = share['id']
93 return share_id
95 def test_show_transfer(self):
96 share_id = self._create_share(size=5)
97 transfer = self._create_transfer(share_id)
98 path = '/v2/fake_project_id/share-transfers/%s' % transfer['id']
99 req = fakes.HTTPRequest.blank(path, version=self.microversion)
100 req.environ['manila.context'] = self.ctxt
101 req.method = 'GET'
102 req.headers['Content-Type'] = 'application/json'
103 res_dict = self.v2_controller.show(req, transfer['id'])
105 self.assertEqual('test_transfer', res_dict['transfer']['name'])
106 self.assertEqual(transfer['id'], res_dict['transfer']['id'])
107 self.assertEqual(share_id, res_dict['transfer']['resource_id'])
109 def test_list_transfers(self):
110 share_id_1 = self._create_share(size=5)
111 share_id_2 = self._create_share(size=5)
112 transfer1 = self._create_transfer(share_id_1)
113 transfer2 = self._create_transfer(share_id_2)
115 path = '/v2/fake_project_id/share-transfers'
116 req = fakes.HTTPRequest.blank(path, version=self.microversion)
117 req.environ['manila.context'] = self.ctxt
118 req.method = 'GET'
119 req.headers['Content-Type'] = 'application/json'
120 res_dict = self.v2_controller.index(req)
122 self.assertEqual(transfer1['id'], res_dict['transfers'][1]['id'])
123 self.assertEqual('test_transfer', res_dict['transfers'][1]['name'])
124 self.assertEqual(transfer2['id'], res_dict['transfers'][0]['id'])
125 self.assertEqual('test_transfer', res_dict['transfers'][0]['name'])
127 def test_list_transfers_with_all_tenants(self):
128 share_id_1 = self._create_share(size=5)
129 share_id_2 = self._create_share(size=5, project_id='fake_project_id2',
130 user_id='fake_user_id2')
131 self._create_transfer(share_id_1)
132 self._create_transfer(share_id_2)
134 path = '/v2/fake_project_id/share-transfers?all_tenants=true'
135 req = fakes.HTTPRequest.blank(path, version=self.microversion)
136 req.environ['manila.context'] = context.get_admin_context()
137 req.method = 'GET'
138 req.headers['Content-Type'] = 'application/json'
139 res_dict = self.v2_controller.index(req)
141 self.assertEqual(2, len(res_dict['transfers']))
143 def test_list_transfers_with_limit(self):
144 share_id_1 = self._create_share(size=5)
145 share_id_2 = self._create_share(size=5)
146 self._create_transfer(share_id_1)
147 self._create_transfer(share_id_2)
148 path = '/v2/fake_project_id/share-transfers?limit=1'
149 req = fakes.HTTPRequest.blank(path, version=self.microversion)
150 req.environ['manila.context'] = self.ctxt
151 req.method = 'GET'
152 req.headers['Content-Type'] = 'application/json'
153 res_dict = self.v2_controller.index(req)
155 self.assertEqual(1, len(res_dict['transfers']))
157 @ddt.data("desc", "asc")
158 def test_list_transfers_with_sort(self, sort_dir):
159 share_id_1 = self._create_share(size=5)
160 share_id_2 = self._create_share(size=5)
161 transfer1 = self._create_transfer(share_id_1)
162 transfer2 = self._create_transfer(share_id_2)
163 path = \
164 '/v2/fake_project_id/share-transfers?sort_key=id&sort_dir=%s' % (
165 sort_dir)
166 req = fakes.HTTPRequest.blank(path, version=self.microversion)
167 req.environ['manila.context'] = self.ctxt
168 req.method = 'GET'
169 req.headers['Content-Type'] = 'application/json'
170 res_dict = self.v2_controller.index(req)
172 self.assertEqual(2, len(res_dict['transfers']))
173 order_ids = sorted([transfer1['id'],
174 transfer2['id']])
175 expect_result = order_ids[1] if sort_dir == "desc" else order_ids[0]
176 self.assertEqual(expect_result,
177 res_dict['transfers'][0]['id'])
179 def test_list_transfers_detail(self):
180 share_id_1 = self._create_share(size=5)
181 share_id_2 = self._create_share(size=5)
182 transfer1 = self._create_transfer(share_id_1)
183 transfer2 = self._create_transfer(share_id_2)
185 path = '/v2/fake_project_id/share-transfers/detail'
186 req = fakes.HTTPRequest.blank(path, version=self.microversion)
187 req.environ['manila.context'] = self.ctxt
188 req.method = 'GET'
189 req.headers['Content-Type'] = 'application/json'
190 req.headers['Accept'] = 'application/json'
191 res_dict = self.v2_controller.detail(req)
193 self.assertEqual('test_transfer',
194 res_dict['transfers'][1]['name'])
195 self.assertEqual(transfer1['id'], res_dict['transfers'][1]['id'])
196 self.assertEqual(share_id_1, res_dict['transfers'][1]['resource_id'])
198 self.assertEqual('test_transfer',
199 res_dict['transfers'][0]['name'])
200 self.assertEqual(transfer2['id'], res_dict['transfers'][0]['id'])
201 self.assertEqual(share_id_2, res_dict['transfers'][0]['resource_id'])
203 def test_create_transfer(self):
204 share_id = self._create_share(status='available', size=5)
205 body = {"transfer": {"name": "transfer1",
206 "share_id": share_id}}
208 path = '/v2/fake_project_id/share-transfers'
209 req = fakes.HTTPRequest.blank(path, version=self.microversion)
210 req.environ['manila.context'] = self.ctxt
211 req.method = 'POST'
212 req.headers['Content-Type'] = 'application/json'
213 req.body = jsonutils.dumps(body).encode("utf-8")
214 res_dict = self.v2_controller.create(req, body)
216 self.assertIn('id', res_dict['transfer'])
217 self.assertIn('auth_key', res_dict['transfer'])
218 self.assertIn('created_at', res_dict['transfer'])
219 self.assertIn('name', res_dict['transfer'])
220 self.assertIn('resource_id', res_dict['transfer'])
222 @ddt.data({},
223 {"transfer": {"name": "transfer1"}},
224 {"transfer": {"name": "transfer1",
225 "share_id": "invalid_share_id"}})
226 def test_create_transfer_with_invalid_body(self, body):
227 path = '/v2/fake_project_id/share-transfers'
228 req = fakes.HTTPRequest.blank(path, version=self.microversion)
229 req.environ['manila.context'] = self.ctxt
230 req.method = 'POST'
231 req.headers['Content-Type'] = 'application/json'
232 req.body = jsonutils.dumps(body).encode("utf-8")
233 self.assertRaises(webob.exc.HTTPBadRequest,
234 self.v2_controller.create, req, body)
236 def test_create_transfer_with_invalid_share_status(self):
237 share_id = self._create_share()
238 body = {"transfer": {"name": "transfer1",
239 "share_id": share_id}}
240 db.share_update(context.get_admin_context(),
241 share_id, {'status': 'error'})
243 path = '/v2/fake_project_id/share-transfers'
244 req = fakes.HTTPRequest.blank(path, version=self.microversion)
245 req.environ['manila.context'] = self.ctxt
246 req.method = 'POST'
247 req.headers['Content-Type'] = 'application/json'
248 req.body = jsonutils.dumps(body).encode("utf-8")
249 self.assertRaises(webob.exc.HTTPBadRequest,
250 self.v2_controller.create, req, body)
252 def test_create_transfer_with_invalid_mount_point_name(self):
253 share_id = self._create_share(
254 project_id='fake_pid',
255 mount_point_name='fake_pid_mount_point_name')
256 body = {"transfer": {"name": "transfer1",
257 "share_id": share_id}}
258 db.share_update(context.get_admin_context(),
259 share_id, {'status': 'error'})
261 path = '/v2/fake_project_id/share-transfers'
262 req = fakes.HTTPRequest.blank(path, version=self.microversion)
263 req.environ['manila.context'] = self.ctxt
264 req.method = 'POST'
265 req.headers['Content-Type'] = 'application/json'
266 req.body = jsonutils.dumps(body).encode("utf-8")
267 self.assertRaises(webob.exc.HTTPBadRequest,
268 self.v2_controller.create, req, body)
270 def test_create_transfer_with_project_id_prefix_mount_point_name(self):
271 share_id = self._create_share(project_id='fake',
272 mount_point_name='fake_mp')
274 self.assertRaises(exception.Invalid,
275 self.share_transfer_api.create,
276 context.get_admin_context(), share_id,
277 'test_missing_share_type')
279 def test_create_transfer_share_with_network_id(self):
280 share_id = self._create_share(share_network_id='fake_id')
281 body = {"transfer": {"name": "transfer1",
282 "share_id": share_id}}
284 path = '/v2/fake_project_id/share-transfers'
285 req = fakes.HTTPRequest.blank(path, version=self.microversion)
286 req.environ['manila.context'] = self.ctxt
287 req.method = 'POST'
288 req.headers['Content-Type'] = 'application/json'
289 req.body = jsonutils.dumps(body).encode("utf-8")
290 self.assertRaises(webob.exc.HTTPBadRequest,
291 self.v2_controller.create, req, body)
293 def test_create_transfer_share_with_invalid_snapshot(self):
294 share_id = self._create_share(share_network_id='fake_id')
295 db_utils.create_snapshot(share_id=share_id)
296 body = {"transfer": {"name": "transfer1",
297 "share_id": share_id}}
299 path = '/v2/fake_project_id/share-transfers'
300 req = fakes.HTTPRequest.blank(path, version=self.microversion)
301 req.environ['manila.context'] = self.ctxt
302 req.method = 'POST'
303 req.headers['Content-Type'] = 'application/json'
304 req.body = jsonutils.dumps(body).encode("utf-8")
305 self.assertRaises(webob.exc.HTTPBadRequest,
306 self.v2_controller.create, req, body)
308 def test_delete_transfer_awaiting_transfer(self):
309 share_id = self._create_share()
310 transfer = self.share_transfer_api.create(context.get_admin_context(),
311 share_id, 'test_transfer')
312 path = '/v2/fake_project_id/share-transfers/%s' % transfer['id']
313 req = fakes.HTTPRequest.blank(path, version=self.microversion)
314 req.environ['manila.context'] = self.ctxt
315 req.method = 'DELETE'
316 req.headers['Content-Type'] = 'application/json'
317 self.v2_controller.delete(req, transfer['id'])
319 # verify transfer has been deleted
320 req = fakes.HTTPRequest.blank(path, version=self.microversion)
321 req.environ['manila.context'] = self.ctxt
322 req.method = 'GET'
323 req.headers['Content-Type'] = 'application/json'
324 res = req.get_response(fakes.app())
326 self.assertEqual(http_client.NOT_FOUND, res.status_int)
327 self.assertEqual(db.share_get(context.get_admin_context(),
328 share_id)['status'], 'available')
330 def test_delete_transfer_not_awaiting_transfer(self):
331 share_id = self._create_share()
332 transfer = self.share_transfer_api.create(context.get_admin_context(),
333 share_id, 'test_transfer')
334 db.share_update(context.get_admin_context(),
335 share_id, {'status': 'available'})
337 path = '/v2/fake_project_id/share-transfers/%s' % transfer['id']
338 req = fakes.HTTPRequest.blank(path, version=self.microversion)
339 req.environ['manila.context'] = self.ctxt
340 req.method = 'DELETE'
341 req.headers['Content-Type'] = 'application/json'
342 self.assertRaises(exception.InvalidShare,
343 self.v2_controller.delete, req,
344 transfer['id'])
346 def test_transfer_accept_share_id_specified(self):
347 share_id = self._create_share()
348 transfer = self.share_transfer_api.create(context.get_admin_context(),
349 share_id, 'test_transfer')
350 self.mock_object(quota.QUOTAS, 'reserve', mock.Mock())
351 self.mock_object(quota.QUOTAS, 'commit', mock.Mock())
352 self.mock_object(share_api.API,
353 'check_is_share_size_within_per_share_quota_limit',
354 mock.Mock())
355 self.mock_object(share_rpcapi.ShareAPI,
356 'transfer_accept',
357 mock.Mock())
358 fake_share_type = {'id': 'fake_id',
359 'name': 'fake_name',
360 'is_public': True}
361 self.mock_object(share_types, 'get_share_type',
362 mock.Mock(return_value=fake_share_type))
363 self.mock_object(db, 'share_snapshot_get_all_for_share',
364 mock.Mock(return_value={}))
366 body = {"accept": {"auth_key": transfer['auth_key']}}
367 path = '/v2/fake_project_id/share-transfers/%s/accept' % transfer['id']
368 req = fakes.HTTPRequest.blank(path, version=self.microversion)
369 req.environ['manila.context'] = self.ctxt
370 req.method = 'POST'
371 req.headers['Content-Type'] = 'application/json'
372 req.body = jsonutils.dumps(body).encode("utf-8")
373 self.v2_controller.accept(req, transfer['id'], body)
375 def test_transfer_accept_with_not_public_share_type(self):
376 share_id = self._create_share()
377 transfer = self.share_transfer_api.create(context.get_admin_context(),
378 share_id, 'test_transfer')
379 fake_share_type = {'id': 'fake_id',
380 'name': 'fake_name',
381 'is_public': False,
382 'projects': ['project_id1', 'project_id2']}
383 self.mock_object(share_types, 'get_share_type',
384 mock.Mock(return_value=fake_share_type))
386 body = {"accept": {"auth_key": transfer['auth_key']}}
387 path = '/v2/fake_project_id/share-transfers/%s/accept' % transfer['id']
388 req = fakes.HTTPRequest.blank(path, version=self.microversion)
389 req.environ['manila.context'] = self.ctxt
390 req.method = 'POST'
391 req.headers['Content-Type'] = 'application/json'
392 req.body = jsonutils.dumps(body).encode("utf-8")
393 self.assertRaises(webob.exc.HTTPBadRequest,
394 self.v2_controller.accept, req,
395 transfer['id'], body)
397 @ddt.data({},
398 {"accept": {}},
399 {"accept": {"auth_key": "fake_auth_key",
400 "clear_access_rules": "invalid_bool"}})
401 def test_transfer_accept_with_invalid_body(self, body):
402 path = '/v2/fake_project_id/share-transfers/fake_transfer_id/accept'
403 req = fakes.HTTPRequest.blank(path, version=self.microversion)
404 req.environ['manila.context'] = self.ctxt
405 req.method = 'POST'
406 req.headers['Content-Type'] = 'application/json'
407 req.body = jsonutils.dumps(body).encode("utf-8")
408 self.assertRaises(webob.exc.HTTPBadRequest,
409 self.v2_controller.accept, req,
410 'fake_transfer_id', body)
412 def test_transfer_accept_with_invalid_auth_key(self):
413 share_id = self._create_share(size=5)
414 transfer = self._create_transfer(share_id)
415 body = {"accept": {"auth_key": "invalid_auth_key"}}
416 path = '/v2/fake_project_id/share-transfers/%s/accept' % transfer['id']
417 req = fakes.HTTPRequest.blank(path, version=self.microversion)
418 req.environ['manila.context'] = self.ctxt
419 req.method = 'POST'
420 req.headers['Content-Type'] = 'application/json'
421 req.body = jsonutils.dumps(body).encode("utf-8")
422 self.assertRaises(webob.exc.HTTPBadRequest,
423 self.v2_controller.accept, req,
424 transfer['id'], body)
426 def test_transfer_accept_with_invalid_share_status(self):
427 share_id = self._create_share(size=5)
428 transfer = self._create_transfer(share_id)
429 db.share_update(context.get_admin_context(),
430 share_id, {'status': 'error'})
431 body = {"accept": {"auth_key": transfer['auth_key']}}
432 path = '/v2/fake_project_id/share-transfers/%s/accept' % transfer['id']
433 req = fakes.HTTPRequest.blank(path, version=self.microversion)
434 req.environ['manila.context'] = self.ctxt
435 req.method = 'POST'
436 req.headers['Content-Type'] = 'application/json'
437 req.body = jsonutils.dumps(body).encode("utf-8")
438 self.assertRaises(webob.exc.HTTPBadRequest,
439 self.v2_controller.accept, req,
440 transfer['id'], body)
442 @ddt.data({'overs': {'gigabytes': 'fake'}},
443 {'overs': {'shares': 'fake'}},
444 {'overs': {'snapshot_gigabytes': 'fake'}},
445 {'overs': {'snapshots': 'fake'}})
446 @ddt.unpack
447 def test_accept_share_over_quota(self, overs):
448 share_id = self._create_share()
449 db_utils.create_snapshot(share_id=share_id, status='available')
450 transfer = self.share_transfer_api.create(context.get_admin_context(),
451 share_id, 'test_transfer')
453 usages = {'gigabytes': {'reserved': 5, 'in_use': 5},
454 'shares': {'reserved': 10, 'in_use': 10},
455 'snapshot_gigabytes': {'reserved': 5, 'in_use': 5},
456 'snapshots': {'reserved': 10, 'in_use': 10}}
458 quotas = {'gigabytes': 5, 'shares': 10,
459 'snapshot_gigabytes': 5, 'snapshots': 10}
460 exc = exception.OverQuota(overs=overs, usages=usages, quotas=quotas)
461 self.mock_object(quota.QUOTAS, 'reserve', mock.Mock(side_effect=exc))
462 self.mock_object(quota.QUOTAS, 'commit', mock.Mock())
463 self.mock_object(share_api.API,
464 'check_is_share_size_within_per_share_quota_limit',
465 mock.Mock())
466 self.mock_object(share_rpcapi.ShareAPI,
467 'transfer_accept',
468 mock.Mock())
469 fake_share_type = {'id': 'fake_id',
470 'name': 'fake_name',
471 'is_public': True}
472 self.mock_object(share_types, 'get_share_type',
473 mock.Mock(return_value=fake_share_type))
475 body = {"accept": {"auth_key": transfer['auth_key']}}
476 path = '/v2/fake_project_id/share-transfers/%s/accept' % transfer['id']
477 req = fakes.HTTPRequest.blank(path, version=self.microversion)
478 req.environ['manila.context'] = self.ctxt
479 req.method = 'POST'
480 req.headers['Content-Type'] = 'application/json'
481 req.body = jsonutils.dumps(body).encode("utf-8")
482 self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
483 self.v2_controller.accept, req,
484 transfer['id'], body)