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:
parent
7235634f74
commit
0b71363dd6
|
@ -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
|
||||
|
|
|
@ -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:
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
)
|
|
@ -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
|
||||
)
|
|
@ -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},
|
||||
)
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
Added support for volume transfer create, find
|
||||
and delete.
|
Loading…
Reference in New Issue