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
|
||||
: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
|
||||
:noindex:
|
||||
: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
|
||||
|
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.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,
|
||||
interval=2, wait=120):
|
||||
"""Wait for a resource to be in a particular status.
|
||||
|
@ -10,6 +10,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from openstack import exceptions
|
||||
from openstack import resource
|
||||
from openstack import utils
|
||||
|
||||
@ -102,12 +103,59 @@ class Share(resource.Resource):
|
||||
#: Display description for updating description
|
||||
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')
|
||||
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):
|
||||
"""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}}
|
||||
self._action(session, body)
|
||||
|
@ -24,6 +24,7 @@ class ShareTest(base.BaseSharedFileSystemTest):
|
||||
name=self.SHARE_NAME, size=2, share_type="dhss_false",
|
||||
share_protocol='NFS', description=None)
|
||||
self.SHARE_ID = my_share.id
|
||||
self.SHARE_SIZE = my_share.size
|
||||
my_share_snapshot = self.create_share_snapshot(
|
||||
share_id=self.SHARE_ID
|
||||
)
|
||||
@ -60,3 +61,92 @@ class ShareTest(base.BaseSharedFileSystemTest):
|
||||
interval=5,
|
||||
wait=self._wait_for_timeout)
|
||||
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):
|
||||
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):
|
||||
self.verify_list(self.proxy.share_instances,
|
||||
share_instance.ShareInstance)
|
||||
|
@ -10,13 +10,18 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from unittest import mock
|
||||
|
||||
from keystoneauth1 import adapter
|
||||
|
||||
from openstack.shared_file_system.v2 import share
|
||||
from openstack.tests.unit import base
|
||||
|
||||
|
||||
IDENTIFIER = '08a87d37-5ca2-4308-86c5-cba06d8d796c'
|
||||
EXAMPLE = {
|
||||
"id": IDENTIFIER,
|
||||
"size": 1,
|
||||
"size": 2,
|
||||
"availability_zone": "manila-zone-1",
|
||||
"created_at": "2021-02-11T17:38:00.000000",
|
||||
"status": "available",
|
||||
@ -109,3 +114,58 @@ class TestShares(base.TestCase):
|
||||
self.assertEqual(EXAMPLE['share_server_id'],
|
||||
shares_resource.share_server_id)
|
||||
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