Improve volume transfer records
Add a new microversion API to add source_project_id, destination_project_id, accepted fields to the response of follow APIs: - Create a volume transfer - Show volume transfer detail - List volume transfer and detail And the source_project_id will be recorded when a transfer is created, the destination_project_id and accepted will be recorded when a transfer is accepted. Part of blueprint: improve-volume-transfer-records Change-Id: I3d79f9a67a9aed7272871969e7c0942a1f396ea5
This commit is contained in:
parent
39e78e44e0
commit
3e16dfff8e
@ -410,6 +410,13 @@ absolute:
|
||||
in: body
|
||||
required: true
|
||||
type: object
|
||||
accepted:
|
||||
description: |
|
||||
Records if this transfer was accepted or not.
|
||||
in: body
|
||||
required: false
|
||||
type: boolean
|
||||
min_version: 3.57
|
||||
active_backend_id:
|
||||
description: |
|
||||
The ID of active storage backend. Only in ``cinder-volume`` service.
|
||||
@ -967,6 +974,13 @@ description_volume_type_required:
|
||||
in: body
|
||||
required: true
|
||||
type: string
|
||||
destination_project_id:
|
||||
description: |
|
||||
Records the destination project_id after volume transfer.
|
||||
in: body
|
||||
required: false
|
||||
type: string
|
||||
min_version: 3.57
|
||||
detached_at:
|
||||
description: |
|
||||
The time when attachment is detached.
|
||||
@ -2571,6 +2585,13 @@ source_group_id_req:
|
||||
in: body
|
||||
required: true
|
||||
type: string
|
||||
source_project_id:
|
||||
description: |
|
||||
Records the source project_id before volume transfer.
|
||||
in: body
|
||||
required: false
|
||||
type: string
|
||||
min_version: 3.57
|
||||
source_reference:
|
||||
description: |
|
||||
The snapshot's origin volume information.
|
||||
|
@ -22,7 +22,7 @@
|
||||
"min_version": "3.0",
|
||||
"status": "CURRENT",
|
||||
"updated": "2018-07-17T00:00:00Z",
|
||||
"version": "3.56"
|
||||
"version": "3.57"
|
||||
}
|
||||
]
|
||||
}
|
@ -46,7 +46,7 @@
|
||||
"min_version": "3.0",
|
||||
"status": "CURRENT",
|
||||
"updated": "2018-07-17T00:00:00Z",
|
||||
"version": "3.56"
|
||||
"version": "3.57"
|
||||
}
|
||||
]
|
||||
}
|
@ -5,6 +5,9 @@
|
||||
"name": "first volume",
|
||||
"volume_id": "c86b9af4-151d-4ead-b62c-5fb967af0e37",
|
||||
"auth_key": "9266c59563c84664",
|
||||
"source_project_id": "10f92548903841278443fcf3da935b22",
|
||||
"destination_project_id": null,
|
||||
"accepted": false,
|
||||
"links": [
|
||||
{
|
||||
"href": "http://localhost/v3/firstproject/volumes/3",
|
||||
|
@ -4,6 +4,9 @@
|
||||
"created_at": "2015-02-25T03:56:53.081642",
|
||||
"name": "first volume transfer",
|
||||
"volume_id": "894623a6-e901-4312-aa06-4275e6321cce",
|
||||
"source_project_id": "10f92548903841278443fcf3da935b22",
|
||||
"destination_project_id": null,
|
||||
"accepted": false,
|
||||
"links": [
|
||||
{
|
||||
"href": "http://localhost/v3/firstproject/volumes/1",
|
||||
|
@ -5,6 +5,9 @@
|
||||
"created_at": "2015-02-25T03:56:53.081642",
|
||||
"name": "first volume transfer",
|
||||
"volume_id": "894623a6-e901-4312-aa06-4275e6321cce",
|
||||
"source_project_id": "10f92548903841278443fcf3da935b22",
|
||||
"destination_project_id": null,
|
||||
"accepted": false,
|
||||
"links": [
|
||||
{
|
||||
"href": "http://localhost/v3/firstproject/volumes/1",
|
||||
@ -21,6 +24,9 @@
|
||||
"created_at": "2015-03-25T03:56:53.081642",
|
||||
"name": "second volume transfer",
|
||||
"volume_id": "673db275-379f-41af-8371-e1652132b4c1",
|
||||
"source_project_id": "10f92548903841278443fcf3da935b22",
|
||||
"destination_project_id": null,
|
||||
"accepted": false,
|
||||
"links": [
|
||||
{
|
||||
"href": "http://localhost/v3/firstproject/volumes/2",
|
||||
|
@ -114,6 +114,9 @@ Response Parameters
|
||||
- volume_id: volume_id
|
||||
- id: id
|
||||
- name: name
|
||||
- destination_project_id: destination_project_id
|
||||
- source_project_id: source_project_id
|
||||
- accepted: accepted
|
||||
|
||||
Response Example
|
||||
----------------
|
||||
@ -198,6 +201,9 @@ Response Parameters
|
||||
- id: id
|
||||
- links: links
|
||||
- name: name
|
||||
- destination_project_id: destination_project_id
|
||||
- source_project_id: source_project_id
|
||||
- accepted: accepted
|
||||
|
||||
|
||||
Response Example
|
||||
@ -264,6 +270,9 @@ Response Parameters
|
||||
- id: id
|
||||
- links: links
|
||||
- name: name
|
||||
- destination_project_id: destination_project_id
|
||||
- source_project_id: source_project_id
|
||||
- accepted: accepted
|
||||
|
||||
Response Example
|
||||
----------------
|
||||
|
@ -153,6 +153,8 @@ TRANSFER_WITH_SNAPSHOTS = '3.55'
|
||||
|
||||
BACKUP_PROJECT_USER_ID = '3.56'
|
||||
|
||||
TRANSFER_WITH_HISTORY = '3.57'
|
||||
|
||||
|
||||
def get_mv_header(version):
|
||||
"""Gets a formatted HTTP microversion header.
|
||||
|
@ -129,6 +129,8 @@ REST_API_VERSION_HISTORY = """
|
||||
* 3.55 - Support transfer volume with snapshots
|
||||
* 3.56 - Add ``user_id`` attribute to response body of list backup with
|
||||
detail and show backup detail APIs.
|
||||
* 3.57 - Add 'source_project_id', 'destination_project_id', 'accepted' to
|
||||
transfer.
|
||||
"""
|
||||
|
||||
# The minimum and maximum versions of the API supported
|
||||
@ -136,7 +138,7 @@ REST_API_VERSION_HISTORY = """
|
||||
# minimum version of the API supported.
|
||||
# Explicitly using /v2 endpoints will still work
|
||||
_MIN_API_VERSION = "3.0"
|
||||
_MAX_API_VERSION = "3.56"
|
||||
_MAX_API_VERSION = "3.57"
|
||||
_LEGACY_API_VERSION2 = "2.0"
|
||||
UPDATED = "2018-07-17T00:00:00Z"
|
||||
|
||||
|
@ -446,3 +446,9 @@ Support ability to transfer snapshots along with their parent volume.
|
||||
----
|
||||
Add ``user_id`` attribute to response body of list backup with detail and show
|
||||
backup detail APIs.
|
||||
|
||||
3.57
|
||||
----
|
||||
Expanded volume transfer record details by adding ``source_project_id``,
|
||||
``destination_project_id`` and ``accepted`` fields to ``transfer`` table and
|
||||
related api (create/show/list detail transfer APIs) responses.
|
||||
|
@ -63,6 +63,13 @@ class ViewBuilder(common.ViewBuilder):
|
||||
if req_version.matches(mv.TRANSFER_WITH_SNAPSHOTS):
|
||||
detail_body['transfer'].update({'no_snapshots':
|
||||
transfer.get('no_snapshots')})
|
||||
if req_version.matches(mv.TRANSFER_WITH_HISTORY):
|
||||
transfer_history = {
|
||||
'destination_project_id': transfer['destination_project_id'],
|
||||
'source_project_id': transfer['source_project_id'],
|
||||
'accepted': transfer['accepted']
|
||||
}
|
||||
detail_body['transfer'].update(transfer_history)
|
||||
return detail_body
|
||||
|
||||
def create(self, request, transfer):
|
||||
@ -81,6 +88,13 @@ class ViewBuilder(common.ViewBuilder):
|
||||
if req_version.matches(mv.TRANSFER_WITH_SNAPSHOTS):
|
||||
create_body['transfer'].update({'no_snapshots':
|
||||
transfer.get('no_snapshots')})
|
||||
if req_version.matches(mv.TRANSFER_WITH_HISTORY):
|
||||
transfer_history = {
|
||||
'destination_project_id': transfer['destination_project_id'],
|
||||
'source_project_id': transfer['source_project_id'],
|
||||
'accepted': transfer['accepted']
|
||||
}
|
||||
create_body['transfer'].update(transfer_history)
|
||||
return create_body
|
||||
|
||||
def _list_view(self, func, request, transfers, origin_transfer_count):
|
||||
|
@ -5426,7 +5426,8 @@ def transfer_get(context, transfer_id):
|
||||
|
||||
def _translate_transfers(transfers):
|
||||
fields = ('id', 'volume_id', 'display_name', 'created_at', 'deleted',
|
||||
'no_snapshots')
|
||||
'no_snapshots', 'source_project_id', 'destination_project_id',
|
||||
'accepted')
|
||||
return [{k: transfer[k] for k in fields} for transfer in transfers]
|
||||
|
||||
|
||||
@ -5578,7 +5579,9 @@ def transfer_accept(context, transfer_id, user_id, project_id,
|
||||
.filter_by(id=transfer_id)
|
||||
.update({'deleted': True,
|
||||
'deleted_at': timeutils.utcnow(),
|
||||
'updated_at': literal_column('updated_at')}))
|
||||
'updated_at': literal_column('updated_at'),
|
||||
'destination_project_id': project_id,
|
||||
'accepted': True}))
|
||||
|
||||
|
||||
###############################
|
||||
|
@ -35,6 +35,11 @@ import cinder.transfer
|
||||
class VolumeTransferAPITestCase(test.TestCase):
|
||||
"""Test Case for transfers V3 API."""
|
||||
|
||||
microversion = mv.TRANSFER_WITH_SNAPSHOTS
|
||||
expect_transfer_history = False
|
||||
DETAIL_LEN = 6
|
||||
SUMMARY_LEN = 4
|
||||
|
||||
def setUp(self):
|
||||
super(VolumeTransferAPITestCase, self).setUp()
|
||||
self.volume_transfer_api = cinder.transfer.API()
|
||||
@ -73,13 +78,24 @@ class VolumeTransferAPITestCase(test.TestCase):
|
||||
volume_id)
|
||||
return volume_id
|
||||
|
||||
def _check_history_in_res(self, transfer_dict):
|
||||
tx_history_keys = ['source_project_id',
|
||||
'destination_project_id',
|
||||
'accepted']
|
||||
if self.expect_transfer_history:
|
||||
for key in tx_history_keys:
|
||||
self.assertIn(key, transfer_dict)
|
||||
else:
|
||||
for key in tx_history_keys:
|
||||
self.assertNotIn(key, transfer_dict)
|
||||
|
||||
def test_show_transfer(self):
|
||||
volume_id = self._create_volume(size=5)
|
||||
transfer = self._create_transfer(volume_id)
|
||||
req = webob.Request.blank('/v3/%s/volume-transfers/%s' % (
|
||||
fake.PROJECT_ID, transfer['id']))
|
||||
req.method = 'GET'
|
||||
req.headers = mv.get_mv_header(mv.TRANSFER_WITH_SNAPSHOTS)
|
||||
req.headers = mv.get_mv_header(self.microversion)
|
||||
req.headers['Content-Type'] = 'application/json'
|
||||
res = req.get_response(fakes.wsgi_app(
|
||||
fake_auth_context=self.user_ctxt))
|
||||
@ -98,17 +114,17 @@ class VolumeTransferAPITestCase(test.TestCase):
|
||||
req = webob.Request.blank('/v3/%s/volume-transfers' %
|
||||
fake.PROJECT_ID)
|
||||
req.method = 'GET'
|
||||
req.headers = mv.get_mv_header(mv.TRANSFER_WITH_SNAPSHOTS)
|
||||
req.headers = mv.get_mv_header(self.microversion)
|
||||
req.headers['Content-Type'] = 'application/json'
|
||||
res = req.get_response(fakes.wsgi_app(
|
||||
fake_auth_context=self.user_ctxt))
|
||||
res_dict = jsonutils.loads(res.body)
|
||||
|
||||
self.assertEqual(http_client.OK, res.status_int)
|
||||
self.assertEqual(4, len(res_dict['transfers'][0]))
|
||||
self.assertEqual(self.SUMMARY_LEN, len(res_dict['transfers'][0]))
|
||||
self.assertEqual(transfer1['id'], res_dict['transfers'][0]['id'])
|
||||
self.assertEqual('test_transfer', res_dict['transfers'][0]['name'])
|
||||
self.assertEqual(4, len(res_dict['transfers'][1]))
|
||||
self.assertEqual(self.SUMMARY_LEN, len(res_dict['transfers'][1]))
|
||||
self.assertEqual(transfer2['id'], res_dict['transfers'][1]['id'])
|
||||
self.assertEqual('test_transfer', res_dict['transfers'][1]['name'])
|
||||
|
||||
@ -121,7 +137,7 @@ class VolumeTransferAPITestCase(test.TestCase):
|
||||
req = webob.Request.blank('/v3/%s/volume-transfers/detail' %
|
||||
fake.PROJECT_ID)
|
||||
req.method = 'GET'
|
||||
req.headers = mv.get_mv_header(mv.TRANSFER_WITH_SNAPSHOTS)
|
||||
req.headers = mv.get_mv_header(self.microversion)
|
||||
req.headers['Content-Type'] = 'application/json'
|
||||
req.headers['Accept'] = 'application/json'
|
||||
res = req.get_response(fakes.wsgi_app(
|
||||
@ -129,17 +145,19 @@ class VolumeTransferAPITestCase(test.TestCase):
|
||||
res_dict = jsonutils.loads(res.body)
|
||||
|
||||
self.assertEqual(http_client.OK, res.status_int)
|
||||
self.assertEqual(6, len(res_dict['transfers'][0]))
|
||||
self.assertEqual(self.DETAIL_LEN, len(res_dict['transfers'][0]))
|
||||
self.assertEqual('test_transfer',
|
||||
res_dict['transfers'][0]['name'])
|
||||
self.assertEqual(transfer1['id'], res_dict['transfers'][0]['id'])
|
||||
self.assertEqual(volume_id_1, res_dict['transfers'][0]['volume_id'])
|
||||
self._check_history_in_res(res_dict['transfers'][0])
|
||||
|
||||
self.assertEqual(6, len(res_dict['transfers'][1]))
|
||||
self.assertEqual(self.DETAIL_LEN, len(res_dict['transfers'][1]))
|
||||
self.assertEqual('test_transfer',
|
||||
res_dict['transfers'][1]['name'])
|
||||
self.assertEqual(transfer2['id'], res_dict['transfers'][1]['id'])
|
||||
self.assertEqual(volume_id_2, res_dict['transfers'][1]['volume_id'])
|
||||
self._check_history_in_res(res_dict['transfers'][1])
|
||||
|
||||
def test_list_transfers_detail_with_no_snapshots(self):
|
||||
volume_id_1 = self._create_volume(size=5)
|
||||
@ -150,7 +168,7 @@ class VolumeTransferAPITestCase(test.TestCase):
|
||||
req = webob.Request.blank('/v3/%s/volume-transfers/detail' %
|
||||
fake.PROJECT_ID)
|
||||
req.method = 'GET'
|
||||
req.headers = mv.get_mv_header(mv.TRANSFER_WITH_SNAPSHOTS)
|
||||
req.headers = mv.get_mv_header(self.microversion)
|
||||
req.headers['Content-Type'] = 'application/json'
|
||||
req.headers['Accept'] = 'application/json'
|
||||
res = req.get_response(fakes.wsgi_app(
|
||||
@ -158,14 +176,14 @@ class VolumeTransferAPITestCase(test.TestCase):
|
||||
res_dict = jsonutils.loads(res.body)
|
||||
|
||||
self.assertEqual(http_client.OK, res.status_int)
|
||||
self.assertEqual(6, len(res_dict['transfers'][0]))
|
||||
self.assertEqual(self.DETAIL_LEN, len(res_dict['transfers'][0]))
|
||||
self.assertEqual('test_transfer',
|
||||
res_dict['transfers'][0]['name'])
|
||||
self.assertEqual(transfer1['id'], res_dict['transfers'][0]['id'])
|
||||
self.assertEqual(volume_id_1, res_dict['transfers'][0]['volume_id'])
|
||||
self.assertEqual(False, res_dict['transfers'][0]['no_snapshots'])
|
||||
|
||||
self.assertEqual(6, len(res_dict['transfers'][1]))
|
||||
self.assertEqual(self.DETAIL_LEN, len(res_dict['transfers'][1]))
|
||||
self.assertEqual('test_transfer',
|
||||
res_dict['transfers'][1]['name'])
|
||||
self.assertEqual(transfer2['id'], res_dict['transfers'][1]['id'])
|
||||
@ -180,7 +198,7 @@ class VolumeTransferAPITestCase(test.TestCase):
|
||||
req = webob.Request.blank('/v3/%s/volume-transfers' %
|
||||
fake.PROJECT_ID)
|
||||
req.method = 'POST'
|
||||
req.headers = mv.get_mv_header(mv.TRANSFER_WITH_SNAPSHOTS)
|
||||
req.headers = mv.get_mv_header(self.microversion)
|
||||
req.headers['Content-Type'] = 'application/json'
|
||||
req.body = jsonutils.dump_as_bytes(body)
|
||||
res = req.get_response(fakes.wsgi_app(
|
||||
@ -194,6 +212,7 @@ class VolumeTransferAPITestCase(test.TestCase):
|
||||
self.assertIn('created_at', res_dict['transfer'])
|
||||
self.assertIn('name', res_dict['transfer'])
|
||||
self.assertIn('volume_id', res_dict['transfer'])
|
||||
self._check_history_in_res(res_dict['transfer'])
|
||||
|
||||
def test_create_transfer_with_no_snapshots(self):
|
||||
volume_id = self._create_volume(status='available', size=5)
|
||||
@ -204,7 +223,7 @@ class VolumeTransferAPITestCase(test.TestCase):
|
||||
req = webob.Request.blank('/v3/%s/volume-transfers' %
|
||||
fake.PROJECT_ID)
|
||||
req.method = 'POST'
|
||||
req.headers = mv.get_mv_header(mv.TRANSFER_WITH_SNAPSHOTS)
|
||||
req.headers = mv.get_mv_header(self.microversion)
|
||||
req.headers['Content-Type'] = 'application/json'
|
||||
req.body = jsonutils.dump_as_bytes(body)
|
||||
res = req.get_response(fakes.wsgi_app(
|
||||
@ -219,6 +238,7 @@ class VolumeTransferAPITestCase(test.TestCase):
|
||||
self.assertIn('name', res_dict['transfer'])
|
||||
self.assertIn('volume_id', res_dict['transfer'])
|
||||
self.assertIn('no_snapshots', res_dict['transfer'])
|
||||
self._check_history_in_res(res_dict['transfer'])
|
||||
|
||||
def test_delete_transfer_awaiting_transfer(self):
|
||||
volume_id = self._create_volume()
|
||||
@ -227,7 +247,7 @@ class VolumeTransferAPITestCase(test.TestCase):
|
||||
req = webob.Request.blank('/v3/%s/volume-transfers/%s' % (
|
||||
fake.PROJECT_ID, transfer['id']))
|
||||
req.method = 'DELETE'
|
||||
req.headers = mv.get_mv_header(mv.TRANSFER_WITH_SNAPSHOTS)
|
||||
req.headers = mv.get_mv_header(self.microversion)
|
||||
req.headers['Content-Type'] = 'application/json'
|
||||
res = req.get_response(fakes.wsgi_app(
|
||||
fake_auth_context=self.user_ctxt))
|
||||
@ -261,7 +281,7 @@ class VolumeTransferAPITestCase(test.TestCase):
|
||||
req = webob.Request.blank('/v3/%s/volume-transfers/%s/accept' % (
|
||||
fake.PROJECT_ID, transfer['id']))
|
||||
req.method = 'POST'
|
||||
req.headers = mv.get_mv_header(mv.TRANSFER_WITH_SNAPSHOTS)
|
||||
req.headers = mv.get_mv_header(self.microversion)
|
||||
req.headers['Content-Type'] = 'application/json'
|
||||
req.body = jsonutils.dump_as_bytes(body)
|
||||
res = req.get_response(fakes.wsgi_app(
|
||||
@ -273,3 +293,10 @@ class VolumeTransferAPITestCase(test.TestCase):
|
||||
self.assertEqual(volume_id, res_dict['transfer']['volume_id'])
|
||||
# cleanup
|
||||
svc.stop()
|
||||
|
||||
|
||||
class VolumeTransferAPITestCase357(VolumeTransferAPITestCase):
|
||||
|
||||
microversion = mv.TRANSFER_WITH_HISTORY
|
||||
DETAIL_LEN = 9
|
||||
expect_transfer_history = True
|
||||
|
@ -17,6 +17,8 @@
|
||||
|
||||
from cinder import context
|
||||
from cinder import db
|
||||
from cinder.db.sqlalchemy import api as db_api
|
||||
from cinder.db.sqlalchemy import models
|
||||
from cinder import exception
|
||||
from cinder import test
|
||||
from cinder.tests.unit import fake_constants as fake
|
||||
@ -31,13 +33,15 @@ class TransfersTableTestCase(test.TestCase):
|
||||
self.ctxt = context.RequestContext(user_id=fake.USER_ID,
|
||||
project_id=fake.PROJECT_ID)
|
||||
|
||||
def _create_transfer(self, volume_id=None):
|
||||
def _create_transfer(self, volume_id=None, source_project_id=None):
|
||||
"""Create a transfer object."""
|
||||
transfer = {'display_name': 'display_name',
|
||||
'salt': 'salt',
|
||||
'crypt_hash': 'crypt_hash'}
|
||||
if volume_id is not None:
|
||||
transfer['volume_id'] = volume_id
|
||||
if source_project_id is not None:
|
||||
transfer['source_project_id'] = source_project_id
|
||||
return db.transfer_create(self.ctxt, transfer)['id']
|
||||
|
||||
def test_transfer_create(self):
|
||||
@ -119,6 +123,26 @@ class TransfersTableTestCase(test.TestCase):
|
||||
xfer = db.transfer_get_all(context.get_admin_context())
|
||||
self.assertEqual(0, len(xfer), "Unexpected number of transfer records")
|
||||
|
||||
def test_transfer_accept(self):
|
||||
volume = utils.create_volume(self.ctxt)
|
||||
xfer_id = self._create_transfer(volume['id'], volume['project_id'])
|
||||
nctxt = context.RequestContext(user_id=fake.USER2_ID,
|
||||
project_id=fake.PROJECT2_ID)
|
||||
xfer = db.transfer_get(nctxt.elevated(), xfer_id)
|
||||
self.assertEqual(volume.project_id, xfer['source_project_id'])
|
||||
self.assertFalse(xfer['accepted'])
|
||||
self.assertIsNone(xfer['destination_project_id'])
|
||||
db.transfer_accept(nctxt.elevated(), xfer_id, fake.USER2_ID,
|
||||
fake.PROJECT2_ID)
|
||||
|
||||
xfer = db_api.model_query(
|
||||
nctxt.elevated(), models.Transfer, read_deleted='yes'
|
||||
).filter_by(id=xfer_id).first()
|
||||
|
||||
self.assertEqual(volume.project_id, xfer['source_project_id'])
|
||||
self.assertTrue(xfer['accepted'])
|
||||
self.assertEqual(fake.PROJECT2_ID, xfer['destination_project_id'])
|
||||
|
||||
def test_transfer_accept_with_snapshots(self):
|
||||
volume_id = utils.create_volume(self.ctxt)['id']
|
||||
snapshot_id1 = utils.create_snapshot(self.ctxt, volume_id,
|
||||
|
@ -18,6 +18,8 @@ from oslo_utils import timeutils
|
||||
|
||||
from cinder import context
|
||||
from cinder import db
|
||||
from cinder.db.sqlalchemy import api as db_api
|
||||
from cinder.db.sqlalchemy import models
|
||||
from cinder import exception
|
||||
from cinder import objects
|
||||
from cinder import quota
|
||||
@ -361,3 +363,28 @@ class VolumeTransferTestCase(test.TestCase):
|
||||
utils.create_snapshot(self.ctxt, volume.id, status='deleting')
|
||||
self.assertRaises(exception.InvalidSnapshot,
|
||||
tx_api.create, self.ctxt, volume.id, 'Description')
|
||||
|
||||
@mock.patch('cinder.volume.utils.notify_about_volume_usage')
|
||||
def test_transfer_accept_with_detail_records(self, mock_notify):
|
||||
svc = self.start_service('volume', host='test_host')
|
||||
self.addCleanup(svc.stop)
|
||||
tx_api = transfer_api.API()
|
||||
volume = utils.create_volume(self.ctxt, updated_at=self.updated_at)
|
||||
|
||||
transfer = tx_api.create(self.ctxt, volume.id, 'Description')
|
||||
self.assertEqual(volume.project_id, transfer['source_project_id'])
|
||||
self.assertIsNone(transfer['destination_project_id'])
|
||||
self.assertFalse(transfer['accepted'])
|
||||
|
||||
# Get volume and snapshot quota before accept
|
||||
self.ctxt.user_id = fake.USER2_ID
|
||||
self.ctxt.project_id = fake.PROJECT2_ID
|
||||
|
||||
tx_api.accept(self.ctxt, transfer['id'], transfer['auth_key'])
|
||||
|
||||
xfer = db_api.model_query(self.ctxt, models.Transfer,
|
||||
read_deleted='yes'
|
||||
).filter_by(id=transfer['id']).first()
|
||||
self.assertEqual(volume.project_id, xfer['source_project_id'])
|
||||
self.assertTrue(xfer['accepted'])
|
||||
self.assertEqual(fake.PROJECT2_ID, xfer['destination_project_id'])
|
||||
|
@ -150,7 +150,8 @@ class API(base.Base):
|
||||
'salt': salt,
|
||||
'crypt_hash': crypt_hash,
|
||||
'expires_at': None,
|
||||
'no_snapshots': no_snapshots}
|
||||
'no_snapshots': no_snapshots,
|
||||
'source_project_id': volume_ref['project_id']}
|
||||
|
||||
try:
|
||||
transfer = self.db.transfer_create(context, transfer_rec)
|
||||
@ -164,7 +165,10 @@ class API(base.Base):
|
||||
'display_name': transfer['display_name'],
|
||||
'auth_key': auth_key,
|
||||
'created_at': transfer['created_at'],
|
||||
'no_snapshots': transfer['no_snapshots']}
|
||||
'no_snapshots': transfer['no_snapshots'],
|
||||
'source_project_id': transfer['source_project_id'],
|
||||
'destination_project_id': transfer['destination_project_id'],
|
||||
'accepted': transfer['accepted']}
|
||||
|
||||
def _handle_snapshot_quota(self, context, snapshots, volume_type_id,
|
||||
donor_id):
|
||||
|
@ -0,0 +1,7 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Expanded volume transfer information. Starting with microversion 3.57,
|
||||
``source_project_id``, ``destination_project_id``, and ``accepted`` fields
|
||||
will be returned in the response of the volume transfer create, show, and
|
||||
list calls.
|
Loading…
Reference in New Issue
Block a user