diff --git a/api-ref/source/v3/parameters.yaml b/api-ref/source/v3/parameters.yaml index 104f3d20c2e..94edb06febc 100644 --- a/api-ref/source/v3/parameters.yaml +++ b/api-ref/source/v3/parameters.yaml @@ -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. diff --git a/api-ref/source/v3/samples/versions/version-show-response.json b/api-ref/source/v3/samples/versions/version-show-response.json index 6d34b3dfc3e..b1f51dc27cb 100644 --- a/api-ref/source/v3/samples/versions/version-show-response.json +++ b/api-ref/source/v3/samples/versions/version-show-response.json @@ -22,7 +22,7 @@ "min_version": "3.0", "status": "CURRENT", "updated": "2018-07-17T00:00:00Z", - "version": "3.56" + "version": "3.57" } ] } \ No newline at end of file diff --git a/api-ref/source/v3/samples/versions/versions-response.json b/api-ref/source/v3/samples/versions/versions-response.json index e90c6165c14..bafff49f234 100644 --- a/api-ref/source/v3/samples/versions/versions-response.json +++ b/api-ref/source/v3/samples/versions/versions-response.json @@ -46,7 +46,7 @@ "min_version": "3.0", "status": "CURRENT", "updated": "2018-07-17T00:00:00Z", - "version": "3.56" + "version": "3.57" } ] } \ No newline at end of file diff --git a/api-ref/source/v3/samples/volume-transfer-create-response.json b/api-ref/source/v3/samples/volume-transfer-create-response.json index 7f0e9e2c5d4..79714c89313 100644 --- a/api-ref/source/v3/samples/volume-transfer-create-response.json +++ b/api-ref/source/v3/samples/volume-transfer-create-response.json @@ -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", diff --git a/api-ref/source/v3/samples/volume-transfer-show-response.json b/api-ref/source/v3/samples/volume-transfer-show-response.json index 366e5472511..eb8a4be25be 100644 --- a/api-ref/source/v3/samples/volume-transfer-show-response.json +++ b/api-ref/source/v3/samples/volume-transfer-show-response.json @@ -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", diff --git a/api-ref/source/v3/samples/volume-transfers-list-detailed-response.json b/api-ref/source/v3/samples/volume-transfers-list-detailed-response.json index 52ba78bc022..b0daaddc1fd 100644 --- a/api-ref/source/v3/samples/volume-transfers-list-detailed-response.json +++ b/api-ref/source/v3/samples/volume-transfers-list-detailed-response.json @@ -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", diff --git a/api-ref/source/v3/vol-transfer-v3.inc b/api-ref/source/v3/vol-transfer-v3.inc index 716a5dbe52a..b52cd431b61 100644 --- a/api-ref/source/v3/vol-transfer-v3.inc +++ b/api-ref/source/v3/vol-transfer-v3.inc @@ -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 ---------------- diff --git a/cinder/api/microversions.py b/cinder/api/microversions.py index bdbf0966166..fdea162396d 100644 --- a/cinder/api/microversions.py +++ b/cinder/api/microversions.py @@ -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. diff --git a/cinder/api/openstack/api_version_request.py b/cinder/api/openstack/api_version_request.py index 05abf4160f3..e2207de19d2 100644 --- a/cinder/api/openstack/api_version_request.py +++ b/cinder/api/openstack/api_version_request.py @@ -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" diff --git a/cinder/api/openstack/rest_api_version_history.rst b/cinder/api/openstack/rest_api_version_history.rst index a4fa8a5b2ee..3f3b0b86dad 100644 --- a/cinder/api/openstack/rest_api_version_history.rst +++ b/cinder/api/openstack/rest_api_version_history.rst @@ -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. diff --git a/cinder/api/views/transfers.py b/cinder/api/views/transfers.py index 1e8bfdc9400..7daf65e4e27 100644 --- a/cinder/api/views/transfers.py +++ b/cinder/api/views/transfers.py @@ -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): diff --git a/cinder/db/sqlalchemy/api.py b/cinder/db/sqlalchemy/api.py index 91792af6d0e..e6f5b1fe533 100644 --- a/cinder/db/sqlalchemy/api.py +++ b/cinder/db/sqlalchemy/api.py @@ -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})) ############################### diff --git a/cinder/tests/unit/api/v3/test_volume_transfer.py b/cinder/tests/unit/api/v3/test_volume_transfer.py index 06b67642547..587d66ee189 100644 --- a/cinder/tests/unit/api/v3/test_volume_transfer.py +++ b/cinder/tests/unit/api/v3/test_volume_transfer.py @@ -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 diff --git a/cinder/tests/unit/db/test_transfers.py b/cinder/tests/unit/db/test_transfers.py index 90e1daafed7..cfcf75bcaa2 100644 --- a/cinder/tests/unit/db/test_transfers.py +++ b/cinder/tests/unit/db/test_transfers.py @@ -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, diff --git a/cinder/tests/unit/test_volume_transfer.py b/cinder/tests/unit/test_volume_transfer.py index 977913f77a5..5d4084a8844 100644 --- a/cinder/tests/unit/test_volume_transfer.py +++ b/cinder/tests/unit/test_volume_transfer.py @@ -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']) diff --git a/cinder/transfer/api.py b/cinder/transfer/api.py index 380a98a7ee3..81bac43d396 100644 --- a/cinder/transfer/api.py +++ b/cinder/transfer/api.py @@ -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): diff --git a/releasenotes/notes/improve-volume-transfer-records-5599e82ade4d302c.yaml b/releasenotes/notes/improve-volume-transfer-records-5599e82ade4d302c.yaml new file mode 100644 index 00000000000..3d794b5082f --- /dev/null +++ b/releasenotes/notes/improve-volume-transfer-records-5599e82ade4d302c.yaml @@ -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.