Addition of share shrinking feature to Manila-UI
Currently, share shrinking feature in Manila-UI is not supported. Only "Extend Share" feature exists. "Extend Share" feature was renamed as "Resize Share" and share shrinking feature was added. Now it works both ways, if value in "New Size" field is less than original size, it will shrink, and if value is more it will extend. Also, minimal value was added to "New Size" field to ensure the value doesn't decrease to zero. Implements: blueprint share-shrinking Change-Id: I8553bed01b529c1b3a57b1e900d171db1d5f28d2
This commit is contained in:
@@ -112,17 +112,22 @@ Edit share
|
||||
|
||||
A message indicates whether the action was successful.
|
||||
|
||||
Extend share
|
||||
Resize share
|
||||
------------
|
||||
|
||||
#. Log in to the dashboard, choose a project, and click :guilabel:`Shares`.
|
||||
|
||||
#. Go to the share that you want to edit and choose :guilabel:`Extend Share`
|
||||
#. Go to the share that you want to edit and choose :guilabel:`Resize Share`
|
||||
from Actions.
|
||||
|
||||
#. :guilabel:`New Size (GB)`: Enter new size.
|
||||
#. :guilabel:`New Size (GB)`: Enter new size. It can be increased or decreased
|
||||
from the original size. The size of the share cannot be lower than the size
|
||||
of the data stored in the share.
|
||||
|
||||
#. Click :guilabel:`Extend Share`.
|
||||
If increased, the size of the share will be extended.
|
||||
If decreased, the size of the share will be shrinked.
|
||||
|
||||
#. Click :guilabel:`Resize Share`.
|
||||
|
||||
A message indicates whether the action was successful.
|
||||
|
||||
|
@@ -189,7 +189,10 @@ def share_unmanage(request, share):
|
||||
return manilaclient(request).shares.unmanage(share)
|
||||
|
||||
|
||||
def share_extend(request, share_id, new_size):
|
||||
def share_resize(request, share_id, new_size, orig_size):
|
||||
if orig_size > new_size:
|
||||
return manilaclient(request).shares.shrink(share_id, new_size)
|
||||
else:
|
||||
return manilaclient(request).shares.extend(share_id, new_size)
|
||||
|
||||
|
||||
|
@@ -26,6 +26,7 @@ from manila_ui.api import manila
|
||||
from manila_ui.dashboards import utils
|
||||
from manila_ui import features
|
||||
from manilaclient.common.apiclient import exceptions as m_exceptions
|
||||
import time
|
||||
|
||||
|
||||
class CreateForm(forms.SelfHandlingForm):
|
||||
@@ -346,7 +347,7 @@ class AddRule(forms.SelfHandlingForm):
|
||||
request, _('Unable to add rule.'), redirect=redirect)
|
||||
|
||||
|
||||
class ExtendForm(forms.SelfHandlingForm):
|
||||
class ResizeForm(forms.SelfHandlingForm):
|
||||
name = forms.CharField(
|
||||
max_length="255", label=_("Share Name"),
|
||||
widget=forms.TextInput(attrs={'readonly': 'readonly'}),
|
||||
@@ -360,16 +361,21 @@ class ExtendForm(forms.SelfHandlingForm):
|
||||
)
|
||||
|
||||
new_size = forms.IntegerField(
|
||||
label=_("New Size (GiB)"),
|
||||
label=_("New Size (GiB)")
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(ExtendForm, self).clean()
|
||||
cleaned_data = super(ResizeForm, self).clean()
|
||||
new_size = cleaned_data.get('new_size')
|
||||
orig_size = self.initial['orig_size']
|
||||
|
||||
if new_size <= orig_size:
|
||||
message = _("New size must be greater than current size.")
|
||||
if new_size == orig_size:
|
||||
message = _("New size must be different than the existing size")
|
||||
self._errors["new_size"] = self.error_class([message])
|
||||
return cleaned_data
|
||||
|
||||
if new_size <= 0:
|
||||
message = _("New size should not be less than or equal to zero")
|
||||
self._errors["new_size"] = self.error_class([message])
|
||||
return cleaned_data
|
||||
|
||||
@@ -386,16 +392,33 @@ class ExtendForm(forms.SelfHandlingForm):
|
||||
|
||||
def handle(self, request, data):
|
||||
share_id = self.initial['share_id']
|
||||
new_size = data['new_size']
|
||||
orig_size = data['orig_size']
|
||||
try:
|
||||
manila.share_extend(request, share_id, data['new_size'])
|
||||
message = _('Extend share "%s"') % data['name']
|
||||
messages.success(request, message)
|
||||
return True
|
||||
manila.share_resize(request, share_id, new_size, orig_size)
|
||||
return self.check_size(request, share_id, new_size)
|
||||
except Exception:
|
||||
redirect = reverse("horizon:project:shares:index")
|
||||
exceptions.handle(request,
|
||||
_('Unable to extend share.'),
|
||||
redirect=redirect)
|
||||
exceptions.handle(request, _(
|
||||
'Unable to resize share.'), redirect=redirect)
|
||||
|
||||
def check_size(self, request, share_id, new_size):
|
||||
share = manila.share_get(request, share_id)
|
||||
timeout = 30
|
||||
interval = 0.35
|
||||
time_elapsed = 0
|
||||
while share.status != 'available':
|
||||
time.sleep(interval)
|
||||
time_elapsed += interval
|
||||
share = manila.share_get(request, share_id)
|
||||
if time_elapsed > timeout:
|
||||
break
|
||||
|
||||
if share.size == new_size:
|
||||
message = _('Resized share "%s"') % share.name
|
||||
messages.success(request, message)
|
||||
return True
|
||||
raise Exception
|
||||
|
||||
|
||||
class RevertForm(forms.SelfHandlingForm):
|
||||
|
@@ -142,12 +142,12 @@ class EditShareMetadata(tables.LinkAction):
|
||||
return share.status in ("available", "in-use")
|
||||
|
||||
|
||||
class ExtendShare(tables.LinkAction):
|
||||
name = "extend_share"
|
||||
verbose_name = _("Extend Share")
|
||||
url = "horizon:project:shares:extend"
|
||||
class ResizeShare(tables.LinkAction):
|
||||
name = "resize_share"
|
||||
verbose_name = _("Resize Share")
|
||||
url = "horizon:project:shares:resize"
|
||||
classes = ("ajax-modal", "btn-create")
|
||||
policy_rules = (("share", "share:extend"),)
|
||||
policy_rules = (("share", "share:resize"),)
|
||||
|
||||
def get_policy_target(self, request, datum=None):
|
||||
project_id = None
|
||||
@@ -213,6 +213,8 @@ class SharesTableBase(tables.DataTable):
|
||||
("MANAGE_ERROR", False),
|
||||
("UNMANAGE_ERROR", False),
|
||||
("extending_error", False),
|
||||
("shrinking_error", False),
|
||||
("shrinking_possible_data_loss_error", False),
|
||||
("reverting_error", False),
|
||||
)
|
||||
STATUS_DISPLAY_CHOICES = (
|
||||
@@ -237,6 +239,10 @@ class SharesTableBase(tables.DataTable):
|
||||
u"Unmanage Error")),
|
||||
("extending_error", pgettext_lazy("Current status of share",
|
||||
u"Extending Error")),
|
||||
("shrinking_error", pgettext_lazy("Current status of share",
|
||||
u"Shrinking Error")),
|
||||
("shrinking_possible_data_loss_error", pgettext_lazy(
|
||||
"Current status of share", u"Shrinking Error")),
|
||||
("reverting_error", pgettext_lazy("Current status of share",
|
||||
u"Reverting Error")),
|
||||
)
|
||||
@@ -411,7 +417,7 @@ class SharesTable(SharesTableBase):
|
||||
DeleteShare)
|
||||
row_actions = (
|
||||
EditShare,
|
||||
ExtendShare,
|
||||
ResizeShare,
|
||||
RevertShare,
|
||||
ss_tables.CreateShareSnapshot,
|
||||
ManageRules,
|
||||
|
@@ -2,6 +2,6 @@
|
||||
{% load i18n %}
|
||||
{% block modal-body-right %}
|
||||
<div class="quota-dynamic">
|
||||
{% include "project/shares/_extend_limits.html" with usages=usages %}
|
||||
{% include "project/shares/_resize_limits.html" with usages=usages %}
|
||||
</div>
|
||||
{% endblock %}
|
@@ -2,7 +2,7 @@
|
||||
|
||||
<h3>{% trans "Description" %}:</h3>
|
||||
|
||||
<p>{% trans "Extend the size of a share. " %}</p>
|
||||
<p>{% trans "Resize the size of a share. " %}</p>
|
||||
|
||||
<h3>{% trans "Share Limits" %}</h3>
|
||||
|
@@ -1,7 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Extend Share" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'project/shares/_extend.html' %}
|
||||
{% endblock %}
|
@@ -0,0 +1,7 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Resize Share" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'project/shares/_resize.html' %}
|
||||
{% endblock %}
|
@@ -49,9 +49,9 @@ urlpatterns = [
|
||||
shares_views.UpdateMetadataView.as_view(),
|
||||
name='update_metadata'),
|
||||
urls.url(
|
||||
r'^(?P<share_id>[^/]+)/extend/$',
|
||||
shares_views.ExtendView.as_view(),
|
||||
name='extend'),
|
||||
r'^(?P<share_id>[^/]+)/resize/$',
|
||||
shares_views.ResizeView.as_view(),
|
||||
name='resize'),
|
||||
urls.url(
|
||||
r'^(?P<share_id>[^/]+)/revert/$',
|
||||
shares_views.RevertView.as_view(),
|
||||
|
@@ -276,16 +276,16 @@ class ManageRulesView(tables.DataTableView):
|
||||
return rules
|
||||
|
||||
|
||||
class ExtendView(forms.ModalFormView):
|
||||
form_class = share_form.ExtendForm
|
||||
form_id = "extend_share"
|
||||
template_name = 'project/shares/extend.html'
|
||||
modal_header = _("Extend Share")
|
||||
modal_id = "extend_share_modal"
|
||||
submit_label = _("Extend")
|
||||
submit_url = "horizon:project:shares:extend"
|
||||
class ResizeView(forms.ModalFormView):
|
||||
form_class = share_form.ResizeForm
|
||||
form_id = "resize_share"
|
||||
template_name = 'project/shares/resize.html'
|
||||
modal_header = _("Resize Share")
|
||||
modal_id = "resize_share_modal"
|
||||
submit_label = _("Resize")
|
||||
submit_url = "horizon:project:shares:resize"
|
||||
success_url = reverse_lazy("horizon:project:shares:index")
|
||||
page_title = _('Extend Share')
|
||||
page_title = _('Resize Share')
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_object(self):
|
||||
@@ -295,7 +295,7 @@ class ExtendView(forms.ModalFormView):
|
||||
exceptions.handle(self.request, _('Unable to retrieve share.'))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ExtendView, self).get_context_data(**kwargs)
|
||||
context = super(ResizeView, self).get_context_data(**kwargs)
|
||||
args = (self.get_object().id,)
|
||||
context['submit_url'] = reverse(self.submit_url, args=args)
|
||||
try:
|
||||
@@ -315,7 +315,7 @@ class ExtendView(forms.ModalFormView):
|
||||
'share_id': self.kwargs["share_id"],
|
||||
'name': share.name or share.id,
|
||||
'orig_size': share.size,
|
||||
'new_size': int(share.size) + 1,
|
||||
'new_size': int(share.size),
|
||||
}
|
||||
|
||||
|
||||
|
@@ -210,15 +210,18 @@ class ManilaApiTests(base.APITestCase):
|
||||
assert_called_once_with('fake_share'))
|
||||
|
||||
# Share resize tests
|
||||
|
||||
def test_share_extend(self):
|
||||
new_size = "123"
|
||||
|
||||
api.share_extend(self.request, self.id, new_size)
|
||||
|
||||
@ddt.data(("123", "78"), ("2", "5"),
|
||||
("75", "21"), ("0", "2"),
|
||||
("18", "62"), ("-1", "3"))
|
||||
@ddt.unpack
|
||||
def test_share_resize(self, new_size, orig_size):
|
||||
api.share_resize(self.request, self.id, new_size, orig_size)
|
||||
if orig_size > new_size:
|
||||
self.manilaclient.shares.shrink.assert_called_once_with(
|
||||
self.id, new_size)
|
||||
else:
|
||||
self.manilaclient.shares.extend.assert_called_once_with(
|
||||
self.id, new_size
|
||||
)
|
||||
self.id, new_size)
|
||||
|
||||
# Share snapshots tests
|
||||
|
||||
|
@@ -354,9 +354,9 @@ class ShareViewTests(test.APITestCase):
|
||||
mock.ANY, self.share.id, rule.id)
|
||||
api_manila.share_rules_list.assert_called_with(mock.ANY, self.share.id)
|
||||
|
||||
def test_extend_share_get(self):
|
||||
def test_resize_share_get(self):
|
||||
share = test_data.share
|
||||
url = reverse('horizon:project:shares:extend', args=[share.id])
|
||||
url = reverse('horizon:project:shares:resize', args=[share.id])
|
||||
self.mock_object(
|
||||
neutron, "is_service_enabled", mock.Mock(return_value=[True]))
|
||||
|
||||
@@ -364,24 +364,24 @@ class ShareViewTests(test.APITestCase):
|
||||
|
||||
api_manila.share_get.assert_called_once_with(mock.ANY, share.id)
|
||||
self.assertNoMessages()
|
||||
self.assertTemplateUsed(res, 'project/shares/extend.html')
|
||||
self.assertTemplateUsed(res, 'project/shares/resize.html')
|
||||
|
||||
def test_extend_share_open_form_successfully(self):
|
||||
def test_resize_share_open_form_successfully(self):
|
||||
self.share.size = 5
|
||||
url = reverse('horizon:project:shares:extend', args=[self.share.id])
|
||||
self.mock_object(api_manila, "share_extend")
|
||||
url = reverse('horizon:project:shares:resize', args=[self.share.id])
|
||||
self.mock_object(api_manila, "share_resize")
|
||||
|
||||
response = self.client.get(url)
|
||||
|
||||
self.assertEqual(200, response.status_code)
|
||||
self.assertTemplateUsed(response, 'project/shares/extend.html')
|
||||
self.assertTemplateUsed(response, 'project/shares/resize.html')
|
||||
api_manila.share_get.assert_called_once_with(mock.ANY, self.share.id)
|
||||
self.assertFalse(api_manila.share_extend.called)
|
||||
self.assertFalse(api_manila.share_resize.called)
|
||||
api_manila.tenant_absolute_limits.assert_called_once_with(mock.ANY)
|
||||
|
||||
def test_extend_share_get_with_api_exception(self):
|
||||
url = reverse('horizon:project:shares:extend', args=[self.share.id])
|
||||
self.mock_object(api_manila, "share_extend")
|
||||
def test_resize_share_get_with_api_exception(self):
|
||||
url = reverse('horizon:project:shares:resize', args=[self.share.id])
|
||||
self.mock_object(api_manila, "share_resize")
|
||||
self.mock_object(
|
||||
api_manila, "share_get",
|
||||
mock.Mock(return_value=Exception('Fake share NotFound exception')))
|
||||
@@ -390,22 +390,22 @@ class ShareViewTests(test.APITestCase):
|
||||
|
||||
self.assertEqual(404, response.status_code)
|
||||
self.assertTemplateNotUsed(
|
||||
response, 'project/shares/shares/extend.html')
|
||||
self.assertFalse(api_manila.share_extend.called)
|
||||
response, 'project/shares/shares/resize.html')
|
||||
self.assertFalse(api_manila.share_resize.called)
|
||||
api_manila.share_get.assert_called_once_with(mock.ANY, self.share.id)
|
||||
self.assertFalse(api_manila.tenant_absolute_limits.called)
|
||||
|
||||
@ddt.data(6, 54, 55)
|
||||
def test_extend_share_post_successfully(self, new_size):
|
||||
@ddt.data(6, 54, 1, 2, 21)
|
||||
def test_resize_share_post_successfully(self, new_size):
|
||||
self.share.size = 5
|
||||
form_data = {'new_size': new_size}
|
||||
form_data = {'new_size': new_size, 'orig_size': self.share.size}
|
||||
usage_limit = {
|
||||
'maxTotalShareGigabytes': self.share.size + 50,
|
||||
'totalShareGigabytesUsed': self.share.size,
|
||||
|
||||
}
|
||||
url = reverse('horizon:project:shares:extend', args=[self.share.id])
|
||||
self.mock_object(api_manila, "share_extend")
|
||||
url = reverse('horizon:project:shares:resize', args=[self.share.id])
|
||||
self.mock_object(api_manila, "share_resize")
|
||||
self.mock_object(
|
||||
api_manila, 'tenant_absolute_limits',
|
||||
mock.Mock(return_value=usage_limit))
|
||||
@@ -414,23 +414,28 @@ class ShareViewTests(test.APITestCase):
|
||||
|
||||
self.assertEqual(302, response.status_code)
|
||||
self.assertTemplateNotUsed(
|
||||
response, 'project/shares/extend.html')
|
||||
api_manila.share_get.assert_called_once_with(mock.ANY, self.share.id)
|
||||
api_manila.share_extend.assert_called_once_with(
|
||||
mock.ANY, self.share.id, form_data['new_size'])
|
||||
response, 'project/shares/resize.html')
|
||||
calls = [
|
||||
mock.call(mock.ANY, self.share.id),
|
||||
mock.call(mock.ANY, self.share.id)]
|
||||
api_manila.share_get.assert_has_calls(calls)
|
||||
api_manila.share_resize.assert_called_once_with(
|
||||
mock.ANY, self.share.id, form_data['new_size'],
|
||||
form_data['orig_size'])
|
||||
api_manila.tenant_absolute_limits.assert_called_once_with(mock.ANY)
|
||||
self.assertRedirectsNoFollow(response, INDEX_URL)
|
||||
|
||||
@ddt.data(0, 5, 56)
|
||||
def test_extend_share_post_with_invalid_value(self, new_size):
|
||||
@ddt.data(5, 56, 0, -1)
|
||||
def test_resize_share_post_with_invalid_value(self, new_size):
|
||||
self.share.size = 5
|
||||
form_data = {'new_size': new_size}
|
||||
url = reverse('horizon:project:shares:extend', args=[self.share.id])
|
||||
form_data = {'new_size': new_size, 'orig_size': self.share.size}
|
||||
|
||||
url = reverse('horizon:project:shares:resize', args=[self.share.id])
|
||||
usage_limit = {
|
||||
'maxTotalShareGigabytes': self.share.size + 50,
|
||||
'totalShareGigabytesUsed': self.share.size,
|
||||
}
|
||||
self.mock_object(api_manila, "share_extend")
|
||||
self.mock_object(api_manila, "share_resize")
|
||||
self.mock_object(
|
||||
api_manila, 'tenant_absolute_limits',
|
||||
mock.Mock(return_value=usage_limit))
|
||||
@@ -438,27 +443,31 @@ class ShareViewTests(test.APITestCase):
|
||||
response = self.client.post(url, form_data)
|
||||
|
||||
self.assertEqual(200, response.status_code)
|
||||
self.assertTemplateUsed(response, 'project/shares/extend.html')
|
||||
self.assertFalse(api_manila.share_extend.called)
|
||||
self.assertTemplateUsed(response, 'project/shares/resize.html')
|
||||
self.assertFalse(api_manila.share_resize.called)
|
||||
api_manila.share_get.assert_called_once_with(mock.ANY, self.share.id)
|
||||
api_manila.tenant_absolute_limits.assert_called_with(mock.ANY)
|
||||
|
||||
def test_extend_share_post_with_api_exception(self):
|
||||
def test_resize_share_post_with_api_exception(self):
|
||||
self.share.size = 5
|
||||
form_data = {'new_size': 30}
|
||||
url = reverse('horizon:project:shares:extend', args=[self.share.id])
|
||||
form_data = {'new_size': 30, 'orig_size': self.share.size}
|
||||
url = reverse('horizon:project:shares:resize', args=[self.share.id])
|
||||
self.mock_object(
|
||||
api_manila, "share_extend",
|
||||
api_manila, "share_resize",
|
||||
mock.Mock(return_value=Exception('Fake API exception')))
|
||||
|
||||
response = self.client.post(url, form_data)
|
||||
|
||||
self.assertEqual(302, response.status_code)
|
||||
self.assertTemplateNotUsed(
|
||||
response, 'project/shares/extend.html')
|
||||
api_manila.share_extend.assert_called_once_with(
|
||||
mock.ANY, self.share.id, form_data['new_size'])
|
||||
api_manila.share_get.assert_called_once_with(mock.ANY, self.share.id)
|
||||
response, 'project/shares/resize.html')
|
||||
api_manila.share_resize.assert_called_once_with(
|
||||
mock.ANY, self.share.id, form_data['new_size'],
|
||||
form_data['orig_size'])
|
||||
calls = [
|
||||
mock.call(mock.ANY, self.share.id),
|
||||
mock.call(mock.ANY, self.share.id)]
|
||||
api_manila.share_get.assert_has_calls(calls)
|
||||
api_manila.tenant_absolute_limits.assert_called_once_with(mock.ANY)
|
||||
self.assertRedirectsNoFollow(response, INDEX_URL)
|
||||
|
||||
|
6
releasenotes/notes/share-shrinking-c1d46fc1c3ce29b7.yaml
Normal file
6
releasenotes/notes/share-shrinking-c1d46fc1c3ce29b7.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Existing "Extend Share" feature in Manila-UI was renamed
|
||||
"Resize Share" and share shrinking feature was added. Now
|
||||
users can both extend and shrink shares.
|
Reference in New Issue
Block a user