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:
Reynaldo Bontje 2023-03-29 14:36:54 -05:00 committed by Reynaldo
parent 1921a195c6
commit c3e77fc61e
9 changed files with 312 additions and 5 deletions

View File

@ -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

View File

@ -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

View 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)

View File

@ -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.

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -0,0 +1,4 @@
---
features:
- |
Added support for shrink/extend share actions.