Add volume transfer support [1/2]

This patch adds support for volume transfer create, find and delete.

Change-Id: I759a583d94d2de508efe76ed38ae4d2ba0e85a48
This commit is contained in:
whoami-rajat 2023-06-27 17:42:49 +05:30 committed by Stephen Finucane
parent 7235634f74
commit 0b71363dd6
7 changed files with 411 additions and 0 deletions

View File

@ -156,3 +156,10 @@ Attachments
:noindex:
:members: create_attachment, get_attachment, attachments,
delete_attachment, update_attachment, complete_attachment
Transfer Operations
^^^^^^^^^^^^^^^^^^^
.. autoclass:: openstack.block_storage.v3._proxy.Proxy
:noindex:
:members: create_transfer, delete_transfer, find_transfer

View File

@ -0,0 +1,13 @@
openstack.block_storage.v3.transfer
===================================
.. automodule:: openstack.block_storage.v3.transfer
The Volume Transfer Class
-------------------------
The ``Volume Transfer`` class inherits from
:class:`~openstack.resource.Resource`.
.. autoclass:: openstack.block_storage.v3.transfer.Transfer
:members:

View File

@ -28,6 +28,7 @@ from openstack.block_storage.v3 import resource_filter as _resource_filter
from openstack.block_storage.v3 import service as _service
from openstack.block_storage.v3 import snapshot as _snapshot
from openstack.block_storage.v3 import stats as _stats
from openstack.block_storage.v3 import transfer as _transfer
from openstack.block_storage.v3 import type as _type
from openstack.block_storage.v3 import volume as _volume
from openstack import exceptions
@ -51,6 +52,7 @@ class Proxy(_base_proxy.BaseBlockStorageProxy):
"snapshot": _snapshot.Snapshot,
"stats_pools": _stats.Pools,
"summary": _summary.BlockStorageSummary,
"transfer": _transfer.Transfer,
"type": _type.Type,
"volume": _volume.Volume,
}
@ -1861,6 +1863,60 @@ class Proxy(_base_proxy.BaseBlockStorageProxy):
"""
return self._list(_extension.Extension)
# ===== TRANFERS =====
def create_transfer(self, **attrs):
"""Create a new Transfer record
:param volume_id: The value is ID of the volume.
:param name: The value is name of the transfer
:param dict attrs: Keyword arguments which will be used to create
a :class:`~openstack.block_storage.v3.transfer.Transfer`
comprised of the properties on the Transfer class.
:returns: The results of Transfer creation
:rtype: :class:`~openstack.block_storage.v3.transfer.Transfer`
"""
return self._create(_transfer.Transfer, **attrs)
def delete_transfer(self, transfer, ignore_missing=True):
"""Delete a volume transfer
:param transfer: The value can be either the ID of a transfer or a
:class:`~openstack.block_storage.v3.transfer.Transfer`` instance.
:param bool ignore_missing: When set to ``False``
:class:`~openstack.exceptions.ResourceNotFound` will be
raised when the transfer does not exist.
When set to ``True``, no exception will be set when
attempting to delete a nonexistent transfer.
:returns: ``None``
"""
self._delete(
_transfer.Transfer,
transfer,
ignore_missing=ignore_missing,
)
def find_transfer(self, name_or_id, ignore_missing=True):
"""Find a single transfer
:param name_or_id: The name or ID a transfer
:param bool ignore_missing: When set to ``False``
:class:`~openstack.exceptions.ResourceNotFound` will be raised
when the volume transfer does not exist.
:returns: One :class:`~openstack.block_storage.v3.transfer.Transfer`
:raises: :class:`~openstack.exceptions.ResourceNotFound`
when no resource can be found.
:raises: :class:`~openstack.exceptions.DuplicateResource` when multiple
resources are found.
"""
return self._find(
_transfer.Transfer,
name_or_id,
ignore_missing=ignore_missing,
)
# ====== UTILS ======
def wait_for_status(
self,

View File

@ -0,0 +1,173 @@
# 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 openstack import resource
from openstack import utils
class Transfer(resource.Resource):
resource_key = "transfer"
resources_key = "transfers"
base_path = "/volume-transfers"
# capabilities
allow_create = True
allow_delete = True
allow_fetch = True
# Properties
#: UUID of the transfer.
id = resource.Body("id")
#: The date and time when the resource was created.
created_at = resource.Body("created_at")
#: Name of the volume to transfer.
name = resource.Body("name")
#: ID of the volume to transfer.
volume_id = resource.Body("volume_id")
#: Auth key for the transfer.
auth_key = resource.Body("auth_key")
#: A list of links associated with this volume. *Type: list*
links = resource.Body("links")
#: Whether to transfer snapshots or not
no_snapshots = resource.Body("no_snapshots")
_max_microversion = "3.55"
def create(
self,
session,
prepend_key=True,
base_path=None,
*,
resource_request_key=None,
resource_response_key=None,
microversion=None,
**params,
):
"""Create a volume transfer.
:param session: The session to use for making this request.
:type session: :class:`~keystoneauth1.adapter.Adapter`
:param prepend_key: A boolean indicating whether the resource_key
should be prepended in a resource creation request. Default to
True.
:param str base_path: Base part of the URI for creating resources, if
different from :data:`~openstack.resource.Resource.base_path`.
:param str resource_request_key: Overrides the usage of
self.resource_key when prepending a key to the request body.
Ignored if `prepend_key` is false.
:param str resource_response_key: Overrides the usage of
self.resource_key when processing response bodies.
Ignored if `prepend_key` is false.
:param str microversion: API version to override the negotiated one.
:param dict params: Additional params to pass.
:return: This :class:`Resource` instance.
:raises: :exc:`~openstack.exceptions.MethodNotSupported` if
:data:`Resource.allow_create` is not set to ``True``.
"""
# With MV 3.55 we introduced new API for volume transfer
# (/volume-transfers). Prior to that (MV < 3.55), we use
# the old API (/os-volume-transfer)
if not utils.supports_microversion(session, '3.55'):
base_path = '/os-volume-transfer'
# With MV 3.55, we also introduce the ability to transfer
# snapshot along with the volume. If MV < 3.55, we should
# not send 'no_snapshots' parameter in the request.
if 'no_snapshots' in params:
params.pop('no_snapshots')
return super().create(
session,
prepend_key=prepend_key,
base_path=base_path,
resource_request_key=resource_request_key,
resource_response_key=resource_response_key,
microversion=microversion,
**params,
)
def fetch(
self,
session,
requires_id=True,
base_path=None,
error_message=None,
skip_cache=False,
*,
resource_response_key=None,
microversion=None,
**params,
):
"""Get volume transfer.
:param session: The session to use for making this request.
:type session: :class:`~keystoneauth1.adapter.Adapter`
:param boolean requires_id: A boolean indicating whether resource ID
should be part of the requested URI.
:param str base_path: Base part of the URI for fetching resources, if
different from :data:`~openstack.resource.Resource.base_path`.
:param str error_message: An Error message to be returned if
requested object does not exist.
:param bool skip_cache: A boolean indicating whether optional API
cache should be skipped for this invocation.
:param str resource_response_key: Overrides the usage of
self.resource_key when processing the response body.
:param str microversion: API version to override the negotiated one.
:param dict params: Additional parameters that can be consumed.
:return: This :class:`Resource` instance.
:raises: :exc:`~openstack.exceptions.MethodNotSupported` if
:data:`Resource.allow_fetch` is not set to ``True``.
:raises: :exc:`~openstack.exceptions.ResourceNotFound` if
the resource was not found.
"""
if not utils.supports_microversion(session, '3.55'):
base_path = '/os-volume-transfer'
return super().fetch(
session,
requires_id=requires_id,
base_path=base_path,
error_message=error_message,
skip_cache=skip_cache,
resource_response_key=resource_response_key,
microversion=microversion,
**params,
)
def delete(
self, session, error_message=None, *, microversion=None, **kwargs
):
"""Delete a volume transfer.
:param session: The session to use for making this request.
:type session: :class:`~keystoneauth1.adapter.Adapter`
:param str microversion: API version to override the negotiated one.
:param dict kwargs: Parameters that will be passed to
_prepare_request()
:return: This :class:`Resource` instance.
:raises: :exc:`~openstack.exceptions.MethodNotSupported` if
:data:`Resource.allow_commit` is not set to ``True``.
:raises: :exc:`~openstack.exceptions.ResourceNotFound` if
the resource was not found.
"""
if not utils.supports_microversion(session, '3.55'):
kwargs['base_path'] = '/os-volume-transfer'
return super().delete(
session,
error_message=error_message,
microversion=microversion,
**kwargs,
)

View File

@ -0,0 +1,58 @@
# 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 openstack.tests.functional.block_storage.v3 import base
from openstack import utils
class TestTransfer(base.BaseBlockStorageTest):
def setUp(self):
super().setUp()
self.VOLUME_NAME = self.getUniqueString()
self.volume = self.user_cloud.block_storage.create_volume(
name=self.VOLUME_NAME,
size=1,
)
self.user_cloud.block_storage.wait_for_status(
self.volume,
status='available',
failures=['error'],
interval=2,
wait=self._wait_for_timeout,
)
self.VOLUME_ID = self.volume.id
def tearDown(self):
sot = self.user_cloud.block_storage.delete_volume(
self.VOLUME_ID, ignore_missing=False
)
self.assertIsNone(sot)
super().tearDown()
def test_transfer(self):
if not utils.supports_microversion(self.conn.block_storage, "3.55"):
self.skipTest("Cannot test new transfer API if MV < 3.55")
sot = self.conn.block_storage.create_transfer(
volume_id=self.VOLUME_ID,
name=self.VOLUME_NAME,
)
self.assertIn('auth_key', sot)
self.assertIn('created_at', sot)
self.assertIn('id', sot)
self.assertIn('name', sot)
self.assertIn('volume_id', sot)
sot = self.user_cloud.block_storage.delete_transfer(
sot.id, ignore_missing=False
)

View File

@ -0,0 +1,99 @@
# 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 unittest import mock
from keystoneauth1 import adapter
from openstack.block_storage.v3 import transfer
from openstack import resource
from openstack.tests.unit import base
FAKE_ID = "09d18b36-9e8d-4438-a4da-3f5eff5e1130"
FAKE_VOL_ID = "390de1bc-19d1-41e7-ba67-c492bb36cae5"
FAKE_VOL_NAME = "test-volume"
TRANSFER = {
"auth_key": "95bc670c0068821d",
"created_at": "2023-06-27T08:47:23.035010",
"id": FAKE_ID,
"name": FAKE_VOL_NAME,
"volume_id": FAKE_VOL_ID,
}
class TestTransfer(base.TestCase):
def setUp(self):
super().setUp()
self.resp = mock.Mock()
self.resp.body = None
self.resp.json = mock.Mock(return_value=self.resp.body)
self.resp.headers = {}
self.resp.status_code = 202
self.sess = mock.Mock(spec=adapter.Adapter)
self.sess.post = mock.Mock(return_value=self.resp)
self.sess.default_microversion = "3.55"
def test_basic(self):
tr = transfer.Transfer(TRANSFER)
self.assertEqual("transfer", tr.resource_key)
self.assertEqual("transfers", tr.resources_key)
self.assertEqual("/volume-transfers", tr.base_path)
self.assertTrue(tr.allow_create)
self.assertIsNotNone(tr._max_microversion)
self.assertDictEqual(
{
"limit": "limit",
"marker": "marker",
},
tr._query_mapping._mapping,
)
@mock.patch(
'openstack.utils.supports_microversion',
autospec=True,
return_value=True,
)
@mock.patch.object(resource.Resource, '_translate_response')
def test_create(self, mock_mv, mock_translate):
sot = transfer.Transfer()
sot.create(self.sess, volume_id=FAKE_VOL_ID, name=FAKE_VOL_NAME)
self.sess.post.assert_called_with(
'/volume-transfers',
json={'transfer': {}},
microversion="3.55",
headers={},
params={'volume_id': FAKE_VOL_ID, 'name': FAKE_VOL_NAME},
)
@mock.patch(
'openstack.utils.supports_microversion',
autospec=True,
return_value=False,
)
@mock.patch.object(resource.Resource, '_translate_response')
def test_create_pre_v355(self, mock_mv, mock_translate):
self.sess.default_microversion = "3.0"
sot = transfer.Transfer()
sot.create(self.sess, volume_id=FAKE_VOL_ID, name=FAKE_VOL_NAME)
self.sess.post.assert_called_with(
'/os-volume-transfer',
json={'transfer': {}},
microversion="3.0",
headers={},
params={'volume_id': FAKE_VOL_ID, 'name': FAKE_VOL_NAME},
)

View File

@ -0,0 +1,5 @@
---
features:
- |
Added support for volume transfer create, find
and delete.