Add resize/extend share actions.
Adds the resize/extend actions from the Share actions API. Includes the resize_share method in the proxy. Change-Id: I9c852360b2e71f6e0a2cfd45c0a77690220379cd
This commit is contained in:
parent
1921a195c6
commit
c3e77fc61e
@ -57,3 +57,16 @@ Force-deletes a share instance.
|
|||||||
|
|
||||||
.. literalinclude:: ../examples/shared_file_system/share_instances.py
|
.. literalinclude:: ../examples/shared_file_system/share_instances.py
|
||||||
:pyobject: delete_share_instance
|
:pyobject: delete_share_instance
|
||||||
|
|
||||||
|
|
||||||
|
Resize Share
|
||||||
|
------------
|
||||||
|
|
||||||
|
Shared File System shares can be resized (extended or shrunk) to a given
|
||||||
|
size. For details on resizing shares, refer to the
|
||||||
|
`Manila docs <https://docs.openstack.org/manila/latest/user/create-and-manage-shares.html#extend-share>`_.
|
||||||
|
|
||||||
|
.. literalinclude:: ../examples/shared_file_system/shares.py
|
||||||
|
:pyobject: resize_share
|
||||||
|
.. literalinclude:: ../examples/shared_file_system/shares.py
|
||||||
|
:pyobject: resize_shares_without_shrink
|
||||||
|
@ -33,7 +33,7 @@ service.
|
|||||||
.. autoclass:: openstack.shared_file_system.v2._proxy.Proxy
|
.. autoclass:: openstack.shared_file_system.v2._proxy.Proxy
|
||||||
:noindex:
|
:noindex:
|
||||||
:members: shares, get_share, delete_share, update_share, create_share,
|
:members: shares, get_share, delete_share, update_share, create_share,
|
||||||
revert_share_to_snapshot
|
revert_share_to_snapshot, resize_share
|
||||||
|
|
||||||
|
|
||||||
Shared File System Storage Pools
|
Shared File System Storage Pools
|
||||||
|
33
examples/shared_file_system/shares.py
Normal file
33
examples/shared_file_system/shares.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
|
def resize_share(conn, share_id, share_size):
|
||||||
|
# Be explicit about not wanting to use force if the share
|
||||||
|
# will be extended.
|
||||||
|
use_force = False
|
||||||
|
print('Resize the share to the given size:')
|
||||||
|
conn.share.resize_share(share_id, share_size, use_force)
|
||||||
|
|
||||||
|
|
||||||
|
def resize_shares_without_shrink(conn, min_size):
|
||||||
|
# Sometimes, extending shares without shrinking
|
||||||
|
# them (effectively setting a min size) is desirable.
|
||||||
|
|
||||||
|
# Get list of shares from the connection.
|
||||||
|
shares = conn.share.shares()
|
||||||
|
|
||||||
|
# Loop over the shares:
|
||||||
|
for share in shares:
|
||||||
|
# Extend shares smaller than min_size to min_size,
|
||||||
|
# but don't shrink shares larger than min_size.
|
||||||
|
conn.share.resize_share(share.id, min_size, no_shrink=True)
|
@ -158,6 +158,41 @@ class Proxy(proxy.Proxy):
|
|||||||
res = self._get(_share.Share, share_id)
|
res = self._get(_share.Share, share_id)
|
||||||
res.revert_to_snapshot(self, snapshot_id)
|
res.revert_to_snapshot(self, snapshot_id)
|
||||||
|
|
||||||
|
def resize_share(
|
||||||
|
self,
|
||||||
|
share_id,
|
||||||
|
new_size,
|
||||||
|
no_shrink=False,
|
||||||
|
no_extend=False,
|
||||||
|
force=False
|
||||||
|
):
|
||||||
|
"""Resizes a share, extending/shrinking the share as needed.
|
||||||
|
|
||||||
|
:param share_id: The ID of the share to resize
|
||||||
|
:param new_size: The new size of the share in GiBs. If new_size is
|
||||||
|
the same as the current size, then nothing is done.
|
||||||
|
:param bool no_shrink: If set to True, the given share is not shrunk,
|
||||||
|
even if shrinking the share is required to get the share to the
|
||||||
|
given size. This could be useful for extending shares to a minimum
|
||||||
|
size, while not shrinking shares to the given size. This defaults
|
||||||
|
to False.
|
||||||
|
:param bool no_extend: If set to True, the given share is not
|
||||||
|
extended, even if extending the share is required to get the share
|
||||||
|
to the given size. This could be useful for shrinking shares to a
|
||||||
|
maximum size, while not extending smaller shares to that maximum
|
||||||
|
size. This defaults to False.
|
||||||
|
:param bool force: Whether or not force should be used,
|
||||||
|
in the case where the share should be extended.
|
||||||
|
:returns: ``None``
|
||||||
|
"""
|
||||||
|
|
||||||
|
res = self._get(_share.Share, share_id)
|
||||||
|
|
||||||
|
if new_size > res.size and no_extend is not True:
|
||||||
|
res.extend_share(self, new_size, force)
|
||||||
|
elif new_size < res.size and no_shrink is not True:
|
||||||
|
res.shrink_share(self, new_size)
|
||||||
|
|
||||||
def wait_for_status(self, res, status='active', failures=None,
|
def wait_for_status(self, res, status='active', failures=None,
|
||||||
interval=2, wait=120):
|
interval=2, wait=120):
|
||||||
"""Wait for a resource to be in a particular status.
|
"""Wait for a resource to be in a particular status.
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from openstack import exceptions
|
||||||
from openstack import resource
|
from openstack import resource
|
||||||
from openstack import utils
|
from openstack import utils
|
||||||
|
|
||||||
@ -102,12 +103,59 @@ class Share(resource.Resource):
|
|||||||
#: Display description for updating description
|
#: Display description for updating description
|
||||||
display_description = resource.Body("display_description", type=str)
|
display_description = resource.Body("display_description", type=str)
|
||||||
|
|
||||||
def _action(self, session, body):
|
def _action(self, session, body, action='patch', microversion=None):
|
||||||
|
"""Perform share instance actions given the message body"""
|
||||||
url = utils.urljoin(self.base_path, self.id, 'action')
|
url = utils.urljoin(self.base_path, self.id, 'action')
|
||||||
headers = {'Accept': ''}
|
headers = {'Accept': ''}
|
||||||
session.post(
|
|
||||||
url, json=body, headers=headers)
|
if microversion is None:
|
||||||
|
microversion = \
|
||||||
|
self._get_microversion(session, action=action)
|
||||||
|
|
||||||
|
response = session.post(
|
||||||
|
url,
|
||||||
|
json=body,
|
||||||
|
headers=headers,
|
||||||
|
microversion=microversion)
|
||||||
|
|
||||||
|
exceptions.raise_from_response(response)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def extend_share(self, session, new_size, force=False):
|
||||||
|
"""Extend the share size.
|
||||||
|
|
||||||
|
:param float new_size: The new size of the share
|
||||||
|
in GiB.
|
||||||
|
:param bool force: Whether or not to use force, bypassing
|
||||||
|
the scheduler. Requires admin privileges. Defaults to False.
|
||||||
|
:returns: The result of the action.
|
||||||
|
:rtype: ``None``
|
||||||
|
"""
|
||||||
|
|
||||||
|
extend_body = {"new_size": new_size}
|
||||||
|
|
||||||
|
if force is True:
|
||||||
|
extend_body['force'] = True
|
||||||
|
|
||||||
|
body = {"extend": extend_body}
|
||||||
|
self._action(session, body)
|
||||||
|
|
||||||
|
def shrink_share(self, session, new_size):
|
||||||
|
"""Shrink the share size.
|
||||||
|
|
||||||
|
:param float new_size: The new size of the share
|
||||||
|
in GiB.
|
||||||
|
:returns: ``None``
|
||||||
|
"""
|
||||||
|
|
||||||
|
body = {"shrink": {'new_size': new_size}}
|
||||||
|
self._action(session, body)
|
||||||
|
|
||||||
def revert_to_snapshot(self, session, snapshot_id):
|
def revert_to_snapshot(self, session, snapshot_id):
|
||||||
|
"""Revert the share to the given snapshot.
|
||||||
|
|
||||||
|
:param str snapshot_id: The id of the snapshot to revert to.
|
||||||
|
:returns: ``None``
|
||||||
|
"""
|
||||||
body = {"revert": {"snapshot_id": snapshot_id}}
|
body = {"revert": {"snapshot_id": snapshot_id}}
|
||||||
self._action(session, body)
|
self._action(session, body)
|
||||||
|
@ -24,6 +24,7 @@ class ShareTest(base.BaseSharedFileSystemTest):
|
|||||||
name=self.SHARE_NAME, size=2, share_type="dhss_false",
|
name=self.SHARE_NAME, size=2, share_type="dhss_false",
|
||||||
share_protocol='NFS', description=None)
|
share_protocol='NFS', description=None)
|
||||||
self.SHARE_ID = my_share.id
|
self.SHARE_ID = my_share.id
|
||||||
|
self.SHARE_SIZE = my_share.size
|
||||||
my_share_snapshot = self.create_share_snapshot(
|
my_share_snapshot = self.create_share_snapshot(
|
||||||
share_id=self.SHARE_ID
|
share_id=self.SHARE_ID
|
||||||
)
|
)
|
||||||
@ -60,3 +61,92 @@ class ShareTest(base.BaseSharedFileSystemTest):
|
|||||||
interval=5,
|
interval=5,
|
||||||
wait=self._wait_for_timeout)
|
wait=self._wait_for_timeout)
|
||||||
self.assertIsNotNone(get_reverted_share.id)
|
self.assertIsNotNone(get_reverted_share.id)
|
||||||
|
|
||||||
|
def test_resize_share_larger(self):
|
||||||
|
larger_size = 3
|
||||||
|
self.user_cloud.share.resize_share(
|
||||||
|
self.SHARE_ID, larger_size)
|
||||||
|
|
||||||
|
get_resized_share = self.user_cloud.share.get_share(
|
||||||
|
self.SHARE_ID)
|
||||||
|
|
||||||
|
self.user_cloud.share.wait_for_status(
|
||||||
|
get_resized_share,
|
||||||
|
status='available',
|
||||||
|
failures=['error'],
|
||||||
|
interval=5,
|
||||||
|
wait=self._wait_for_timeout)
|
||||||
|
self.assertEqual(larger_size, get_resized_share.size)
|
||||||
|
|
||||||
|
def test_resize_share_smaller(self):
|
||||||
|
# Resize to 3 GiB
|
||||||
|
smaller_size = 1
|
||||||
|
|
||||||
|
self.user_cloud.share.resize_share(
|
||||||
|
self.SHARE_ID, smaller_size)
|
||||||
|
|
||||||
|
get_resized_share = self.user_cloud.share.get_share(
|
||||||
|
self.SHARE_ID)
|
||||||
|
|
||||||
|
self.user_cloud.share.wait_for_status(
|
||||||
|
get_resized_share,
|
||||||
|
status='available',
|
||||||
|
failures=['error'],
|
||||||
|
interval=5,
|
||||||
|
wait=self._wait_for_timeout)
|
||||||
|
self.assertEqual(smaller_size, get_resized_share.size)
|
||||||
|
|
||||||
|
def test_resize_share_larger_no_extend(self):
|
||||||
|
larger_size = 3
|
||||||
|
|
||||||
|
self.user_cloud.share.resize_share(
|
||||||
|
self.SHARE_ID, larger_size, no_extend=True)
|
||||||
|
|
||||||
|
get_resized_share = self.user_cloud.share.get_share(
|
||||||
|
self.SHARE_ID)
|
||||||
|
|
||||||
|
self.user_cloud.share.wait_for_status(
|
||||||
|
get_resized_share,
|
||||||
|
status='available',
|
||||||
|
failures=['error'],
|
||||||
|
interval=5,
|
||||||
|
wait=self._wait_for_timeout)
|
||||||
|
|
||||||
|
# Assert that no change was made.
|
||||||
|
self.assertEqual(self.SHARE_SIZE, get_resized_share.size)
|
||||||
|
|
||||||
|
def test_resize_share_smaller_no_shrink(self):
|
||||||
|
smaller_size = 1
|
||||||
|
|
||||||
|
self.user_cloud.share.resize_share(
|
||||||
|
self.SHARE_ID, smaller_size, no_shrink=True)
|
||||||
|
|
||||||
|
get_resized_share = self.user_cloud.share.get_share(
|
||||||
|
self.SHARE_ID)
|
||||||
|
|
||||||
|
self.user_cloud.share.wait_for_status(
|
||||||
|
get_resized_share,
|
||||||
|
status='available',
|
||||||
|
failures=['error'],
|
||||||
|
interval=5,
|
||||||
|
wait=self._wait_for_timeout)
|
||||||
|
|
||||||
|
# Assert that no change was made.
|
||||||
|
self.assertEqual(self.SHARE_SIZE, get_resized_share.size)
|
||||||
|
|
||||||
|
def test_resize_share_with_force(self):
|
||||||
|
# Resize to 3 GiB
|
||||||
|
larger_size = 3
|
||||||
|
self.user_cloud.share.resize_share(
|
||||||
|
self.SHARE_ID, larger_size, force=True)
|
||||||
|
|
||||||
|
get_resized_share = self.user_cloud.share.get_share(
|
||||||
|
self.SHARE_ID)
|
||||||
|
|
||||||
|
self.user_cloud.share.wait_for_status(
|
||||||
|
get_resized_share,
|
||||||
|
status='available',
|
||||||
|
failures=['error'],
|
||||||
|
interval=5,
|
||||||
|
wait=self._wait_for_timeout)
|
||||||
|
self.assertEqual(larger_size, get_resized_share.size)
|
||||||
|
@ -62,6 +62,30 @@ class TestSharedFileSystemShare(TestSharedFileSystemProxy):
|
|||||||
def test_share_update(self):
|
def test_share_update(self):
|
||||||
self.verify_update(self.proxy.update_share, share.Share)
|
self.verify_update(self.proxy.update_share, share.Share)
|
||||||
|
|
||||||
|
def test_share_resize_extend(self):
|
||||||
|
mock_share = share.Share(size=10, id='fakeId')
|
||||||
|
self.proxy._get = mock.Mock(return_value=mock_share)
|
||||||
|
|
||||||
|
self._verify(
|
||||||
|
"openstack.shared_file_system.v2.share."
|
||||||
|
+ "Share.extend_share",
|
||||||
|
self.proxy.resize_share,
|
||||||
|
method_args=['fakeId', 20],
|
||||||
|
expected_args=[self.proxy, 20, False],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_share_resize_shrink(self):
|
||||||
|
mock_share = share.Share(size=30, id='fakeId')
|
||||||
|
self.proxy._get = mock.Mock(return_value=mock_share)
|
||||||
|
|
||||||
|
self._verify(
|
||||||
|
"openstack.shared_file_system.v2.share."
|
||||||
|
+ "Share.shrink_share",
|
||||||
|
self.proxy.resize_share,
|
||||||
|
method_args=['fakeId', 20],
|
||||||
|
expected_args=[self.proxy, 20],
|
||||||
|
)
|
||||||
|
|
||||||
def test_share_instances(self):
|
def test_share_instances(self):
|
||||||
self.verify_list(self.proxy.share_instances,
|
self.verify_list(self.proxy.share_instances,
|
||||||
share_instance.ShareInstance)
|
share_instance.ShareInstance)
|
||||||
|
@ -10,13 +10,18 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from keystoneauth1 import adapter
|
||||||
|
|
||||||
from openstack.shared_file_system.v2 import share
|
from openstack.shared_file_system.v2 import share
|
||||||
from openstack.tests.unit import base
|
from openstack.tests.unit import base
|
||||||
|
|
||||||
|
|
||||||
IDENTIFIER = '08a87d37-5ca2-4308-86c5-cba06d8d796c'
|
IDENTIFIER = '08a87d37-5ca2-4308-86c5-cba06d8d796c'
|
||||||
EXAMPLE = {
|
EXAMPLE = {
|
||||||
"id": IDENTIFIER,
|
"id": IDENTIFIER,
|
||||||
"size": 1,
|
"size": 2,
|
||||||
"availability_zone": "manila-zone-1",
|
"availability_zone": "manila-zone-1",
|
||||||
"created_at": "2021-02-11T17:38:00.000000",
|
"created_at": "2021-02-11T17:38:00.000000",
|
||||||
"status": "available",
|
"status": "available",
|
||||||
@ -109,3 +114,58 @@ class TestShares(base.TestCase):
|
|||||||
self.assertEqual(EXAMPLE['share_server_id'],
|
self.assertEqual(EXAMPLE['share_server_id'],
|
||||||
shares_resource.share_server_id)
|
shares_resource.share_server_id)
|
||||||
self.assertEqual(EXAMPLE['host'], shares_resource.host)
|
self.assertEqual(EXAMPLE['host'], shares_resource.host)
|
||||||
|
|
||||||
|
|
||||||
|
class TestShareActions(TestShares):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.resp = mock.Mock()
|
||||||
|
self.resp.body = None
|
||||||
|
self.resp.status_code = 202
|
||||||
|
self.resp.json = mock.Mock(return_value=self.resp.body)
|
||||||
|
self.sess = mock.Mock(spec=adapter.Adapter)
|
||||||
|
self.sess.default_microversion = '3.0'
|
||||||
|
self.sess.post = mock.Mock(return_value=self.resp)
|
||||||
|
self.sess._get_connection = mock.Mock(return_value=self.cloud)
|
||||||
|
|
||||||
|
def test_shrink_share(self):
|
||||||
|
sot = share.Share(**EXAMPLE)
|
||||||
|
microversion = sot._get_microversion(self.sess, action='patch')
|
||||||
|
|
||||||
|
self.assertIsNone(sot.shrink_share(self.sess, new_size=1))
|
||||||
|
|
||||||
|
url = f'shares/{IDENTIFIER}/action'
|
||||||
|
body = {"shrink": {"new_size": 1}}
|
||||||
|
headers = {'Accept': ''}
|
||||||
|
|
||||||
|
self.sess.post.assert_called_with(
|
||||||
|
url, json=body, headers=headers,
|
||||||
|
microversion=microversion)
|
||||||
|
|
||||||
|
def test_extend_share(self):
|
||||||
|
sot = share.Share(**EXAMPLE)
|
||||||
|
microversion = sot._get_microversion(self.sess, action='patch')
|
||||||
|
|
||||||
|
self.assertIsNone(sot.extend_share(self.sess, new_size=3))
|
||||||
|
|
||||||
|
url = f'shares/{IDENTIFIER}/action'
|
||||||
|
body = {"extend": {"new_size": 3}}
|
||||||
|
headers = {'Accept': ''}
|
||||||
|
|
||||||
|
self.sess.post.assert_called_with(
|
||||||
|
url, json=body, headers=headers,
|
||||||
|
microversion=microversion)
|
||||||
|
|
||||||
|
def test_revert_to_snapshot(self):
|
||||||
|
sot = share.Share(**EXAMPLE)
|
||||||
|
microversion = sot._get_microversion(self.sess, action='patch')
|
||||||
|
|
||||||
|
self.assertIsNone(sot.revert_to_snapshot(self.sess, "fake_id"))
|
||||||
|
|
||||||
|
url = f'shares/{IDENTIFIER}/action'
|
||||||
|
body = {"revert": {"snapshot_id": "fake_id"}}
|
||||||
|
headers = {'Accept': ''}
|
||||||
|
|
||||||
|
self.sess.post.assert_called_with(
|
||||||
|
url, json=body, headers=headers,
|
||||||
|
microversion=microversion)
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Added support for shrink/extend share actions.
|
Loading…
Reference in New Issue
Block a user