Merge "Addition of share shrinking feature to Manila-UI"
This commit is contained in:
commit
0c39bd8436
|
@ -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.
|
||||
|
||||
|
|
|
@ -194,7 +194,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)
|
||||
|
||||
|
|
|
@ -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.
|
Loading…
Reference in New Issue