Fix quotas retrieval for shares and snapshots tables
After refactoring manila-ui plugin to adapt to the tabbed quotas interface in horizon, we stopped accesing the usages data structure which (in horizon side) carries quotas information. In this patch set we perform quotas control by querying manilaclient directly. Change-Id: I2896d54fde0d5e3975afbb7274faadf2a1a81d7c Closes-Bug: #1783232 Co-Authored-By: Tom Barron <tpb@dyncloud.net>
This commit is contained in:
parent
e89a96d412
commit
1ed6c85a86
|
@ -21,7 +21,6 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from django.utils.translation import ungettext_lazy
|
||||
from horizon import exceptions
|
||||
from horizon import tables
|
||||
from openstack_dashboard.usage import quotas
|
||||
|
||||
from manila_ui.api import manila
|
||||
|
||||
|
@ -57,8 +56,13 @@ class CreateShareSnapshot(tables.LinkAction):
|
|||
return {"project_id": project_id}
|
||||
|
||||
def allowed(self, request, share=None):
|
||||
usages = quotas.tenant_quota_usages(request)
|
||||
if usages['snapshots']['available'] <= 0:
|
||||
usages = manila.tenant_absolute_limits(request)
|
||||
snapshots_allowed = (usages['maxTotalShareSnapshots'] >
|
||||
usages['totalShareSnapshotsUsed'] and
|
||||
usages['maxTotalSnapshotGigabytes'] >
|
||||
usages['totalSnapshotGigabytesUsed'])
|
||||
|
||||
if not snapshots_allowed:
|
||||
if "disabled" not in self.classes:
|
||||
self.classes = [c for c in self.classes] + ['disabled']
|
||||
self.verbose_name = string_concat(
|
||||
|
@ -145,6 +149,7 @@ class EditShareSnapshot(tables.LinkAction):
|
|||
|
||||
|
||||
class ShareSnapshotShareNameColumn(tables.Column):
|
||||
|
||||
def get_link_url(self, snapshot):
|
||||
return reverse(self.link, args=(snapshot.share_id,))
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@ from django.utils.translation import ungettext_lazy
|
|||
from horizon import exceptions
|
||||
from horizon import messages
|
||||
from horizon import tables
|
||||
from openstack_dashboard.usage import quotas
|
||||
|
||||
from manila_ui.api import manila
|
||||
from manila_ui.dashboards.project.share_snapshots import tables as ss_tables
|
||||
|
@ -87,8 +86,13 @@ class CreateShare(tables.LinkAction):
|
|||
policy_rules = (("share", "share:create"),)
|
||||
|
||||
def allowed(self, request, share=None):
|
||||
usages = quotas.tenant_quota_usages(request)
|
||||
if usages['shares']['available'] <= 0:
|
||||
usages = manila.tenant_absolute_limits(request)
|
||||
shares_allowed = (usages['maxTotalShares'] >
|
||||
usages['totalSharesUsed'] and
|
||||
usages['maxTotalShareGigabytes'] >
|
||||
usages['totalShareGigabytesUsed'])
|
||||
|
||||
if not shares_allowed:
|
||||
if "disabled" not in self.classes:
|
||||
self.classes = [c for c in self.classes] + ['disabled']
|
||||
self.verbose_name = string_concat(self.verbose_name, ' ',
|
||||
|
@ -97,6 +101,7 @@ class CreateShare(tables.LinkAction):
|
|||
self.verbose_name = _("Create Share")
|
||||
classes = [c for c in self.classes if c != "disabled"]
|
||||
self.classes = classes
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@ from django.urls import reverse
|
|||
import mock
|
||||
from openstack_dashboard.api import keystone as api_keystone
|
||||
from openstack_dashboard.api import neutron as api_neutron
|
||||
from openstack_dashboard.usage import quotas
|
||||
|
||||
from manila_ui.api import manila as api_manila
|
||||
from manila_ui.dashboards.admin import utils
|
||||
|
@ -53,9 +52,6 @@ class SharesTests(test.BaseAdminViewTests):
|
|||
api_manila, "share_snapshot_list", mock.Mock(return_value=snaps))
|
||||
self.mock_object(
|
||||
api_neutron, "is_service_enabled", mock.Mock(return_value=[True]))
|
||||
self.mock_object(
|
||||
quotas, "tenant_quota_usages",
|
||||
mock.Mock(return_value=test_data.quota_usage))
|
||||
|
||||
res = self.client.get(INDEX_URL)
|
||||
|
||||
|
|
|
@ -17,7 +17,9 @@ import ddt
|
|||
from django.core.handlers import wsgi
|
||||
import mock
|
||||
|
||||
from manila_ui.api import manila as api_manila
|
||||
from manila_ui.dashboards.project.share_snapshots import tables
|
||||
from manila_ui.tests.dashboards.project import test_data
|
||||
from manila_ui.tests import helpers as base
|
||||
|
||||
|
||||
|
@ -36,23 +38,38 @@ class CreateSnapshotTests(base.APITestCase):
|
|||
return type("Share", (object, ), kwargs)()
|
||||
|
||||
@ddt.data(True, False)
|
||||
@mock.patch('openstack_dashboard.usage.quotas.tenant_quota_usages')
|
||||
def test_allowed_with_snapshot_support_attr(self, snapshot_support,
|
||||
mock_quota_usages):
|
||||
mock_quota_usages.return_value = {'snapshots': {'available': 1}}
|
||||
def test_allowed_with_snapshot_support_attr(self, snapshot_support):
|
||||
self.mock_object(
|
||||
api_manila, "tenant_absolute_limits",
|
||||
mock.Mock(return_value=test_data.limits))
|
||||
|
||||
share = self._get_fake_share(snapshot_support=snapshot_support)
|
||||
|
||||
result = self.create_snapshot.allowed(self.request, share)
|
||||
|
||||
self.assertEqual(snapshot_support, result)
|
||||
mock_quota_usages.assert_called_once_with(self.request)
|
||||
|
||||
@mock.patch('openstack_dashboard.usage.quotas.tenant_quota_usages')
|
||||
def test_allowed_no_snapshot_support_attr(self, mock_quota_usages):
|
||||
mock_quota_usages.return_value = {'snapshots': {'available': 1}}
|
||||
def test_allowed_no_snapshot_support_attr(self):
|
||||
self.mock_object(
|
||||
api_manila, "tenant_absolute_limits",
|
||||
mock.Mock(return_value=test_data.limits))
|
||||
share = self._get_fake_share()
|
||||
|
||||
result = self.create_snapshot.allowed(self.request, share)
|
||||
|
||||
self.assertNotIn('disabled', self.create_snapshot.classes)
|
||||
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_allowed_no_snapshot_support_attr_no_quota(self):
|
||||
self.mock_object(
|
||||
api_manila, "tenant_absolute_limits",
|
||||
mock.Mock(return_value=test_data.limits_negative))
|
||||
|
||||
share = self._get_fake_share()
|
||||
|
||||
result = self.create_snapshot.allowed(self.request, share)
|
||||
|
||||
self.assertIn('disabled', self.create_snapshot.classes)
|
||||
|
||||
self.assertTrue(result)
|
||||
mock_quota_usages.assert_called_once_with(self.request)
|
||||
|
|
|
@ -18,7 +18,6 @@ from django.urls import reverse
|
|||
from horizon import messages as horizon_messages
|
||||
import mock
|
||||
from openstack_dashboard.api import neutron
|
||||
from openstack_dashboard.usage import quotas
|
||||
import six
|
||||
|
||||
from manila_ui.api import manila as api_manila
|
||||
|
@ -55,6 +54,9 @@ class ShareViewTests(test.APITestCase):
|
|||
self.mock_object(horizon_messages, "success")
|
||||
FAKE_ENVIRON = {'REQUEST_METHOD': 'GET', 'wsgi.input': 'fake_input'}
|
||||
self.request = wsgi.WSGIRequest(FAKE_ENVIRON)
|
||||
self.mock_object(
|
||||
api_manila, "tenant_absolute_limits",
|
||||
mock.Mock(return_value=test_data.limits))
|
||||
|
||||
def test_index(self):
|
||||
snaps = [test_data.snapshot, test_data.snapshot_mount_support]
|
||||
|
@ -70,10 +72,10 @@ class ShareViewTests(test.APITestCase):
|
|||
api_manila, "share_network_list",
|
||||
mock.Mock(return_value=share_networks))
|
||||
self.mock_object(
|
||||
neutron, "is_service_enabled", mock.Mock(return_value=[True]))
|
||||
api_manila, "tenant_absolute_limits",
|
||||
mock.Mock(return_value=test_data.limits))
|
||||
self.mock_object(
|
||||
quotas, "tenant_quota_usages",
|
||||
mock.Mock(return_value=test_data.quota_usage))
|
||||
neutron, "is_service_enabled", mock.Mock(return_value=[True]))
|
||||
|
||||
res = self.client.get(INDEX_URL)
|
||||
|
||||
|
@ -83,6 +85,7 @@ class ShareViewTests(test.APITestCase):
|
|||
mock.ANY, detailed=True)
|
||||
api_manila.share_list.assert_called_with(mock.ANY)
|
||||
api_manila.share_network_list.assert_called_with(mock.ANY)
|
||||
api_manila.tenant_absolute_limits.assert_called_with(mock.ANY)
|
||||
|
||||
@mock.patch.object(api_manila, 'availability_zone_list')
|
||||
def test_create_share(self, az_list):
|
||||
|
@ -354,14 +357,7 @@ class ShareViewTests(test.APITestCase):
|
|||
|
||||
def test_extend_share_get(self):
|
||||
share = test_data.share
|
||||
usage_limit = {
|
||||
'maxTotalShareGigabytes': 250,
|
||||
'totalShareGigabytesUsed': 20,
|
||||
}
|
||||
url = reverse('horizon:project:shares:extend', args=[share.id])
|
||||
self.mock_object(
|
||||
api_manila, "tenant_absolute_limits",
|
||||
mock.Mock(return_value=usage_limit))
|
||||
self.mock_object(
|
||||
neutron, "is_service_enabled", mock.Mock(return_value=[True]))
|
||||
|
||||
|
@ -373,15 +369,8 @@ class ShareViewTests(test.APITestCase):
|
|||
|
||||
def test_extend_share_open_form_successfully(self):
|
||||
self.share.size = 5
|
||||
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")
|
||||
self.mock_object(
|
||||
api_manila, 'tenant_absolute_limits',
|
||||
mock.Mock(return_value=usage_limit))
|
||||
|
||||
response = self.client.get(url)
|
||||
|
||||
|
@ -392,15 +381,8 @@ class ShareViewTests(test.APITestCase):
|
|||
api_manila.tenant_absolute_limits.assert_called_once_with(mock.ANY)
|
||||
|
||||
def test_extend_share_get_with_api_exception(self):
|
||||
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")
|
||||
self.mock_object(
|
||||
api_manila, 'tenant_absolute_limits',
|
||||
mock.Mock(return_value=usage_limit))
|
||||
self.mock_object(
|
||||
api_manila, "share_get",
|
||||
mock.Mock(return_value=Exception('Fake share NotFound exception')))
|
||||
|
@ -421,6 +403,7 @@ class ShareViewTests(test.APITestCase):
|
|||
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")
|
||||
|
@ -443,11 +426,11 @@ class ShareViewTests(test.APITestCase):
|
|||
def test_extend_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])
|
||||
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")
|
||||
self.mock_object(
|
||||
api_manila, 'tenant_absolute_limits',
|
||||
|
@ -464,17 +447,10 @@ class ShareViewTests(test.APITestCase):
|
|||
def test_extend_share_post_with_api_exception(self):
|
||||
self.share.size = 5
|
||||
form_data = {'new_size': 30}
|
||||
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",
|
||||
mock.Mock(return_value=Exception('Fake API exception')))
|
||||
self.mock_object(
|
||||
api_manila, 'tenant_absolute_limits',
|
||||
mock.Mock(return_value=usage_limit))
|
||||
|
||||
response = self.client.post(url, form_data)
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
# under the License.
|
||||
import collections
|
||||
|
||||
from manilaclient.v2 import quotas
|
||||
from manilaclient.v2 import security_services
|
||||
from manilaclient.v2 import share_export_locations
|
||||
from manilaclient.v2 import share_group_snapshots
|
||||
|
@ -28,9 +27,6 @@ from manilaclient.v2 import share_snapshots
|
|||
from manilaclient.v2 import share_types
|
||||
from manilaclient.v2 import shares
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.usage import quotas as usage_quotas
|
||||
|
||||
|
||||
class FakeAPIClient(object):
|
||||
client = "fake_client"
|
||||
|
@ -474,24 +470,23 @@ share_group_snapshot_nameless = share_group_snapshots.ShareGroupSnapshot(
|
|||
'members': []}
|
||||
)
|
||||
|
||||
# Quota Sets
|
||||
quota_data = dict(shares='1',
|
||||
share_snapshots='1',
|
||||
share_gigabytes='1000')
|
||||
quota = quotas.QuotaSet(quotas.QuotaSetManager(FakeAPIClient), quota_data)
|
||||
|
||||
# Quota Usages
|
||||
quota_usage_data = {'gigabytes': {'used': 0, 'quota': 1000},
|
||||
'shares': {'used': 0, 'quota': 10},
|
||||
'snapshots': {'used': 0, 'quota': 10},
|
||||
'share_networks': {'used': 0, 'quota': 10}}
|
||||
quota_usage = usage_quotas.QuotaUsage()
|
||||
for k, v in quota_usage_data.items():
|
||||
quota_usage.add_quota(api.base.Quota(k, v['quota']))
|
||||
quota_usage.tally(k, v['used'])
|
||||
|
||||
# Manila Limits
|
||||
limits = {"absolute": {"totalSharesUsed": 1,
|
||||
"totalShareGigabytesUsed": 5,
|
||||
"maxTotalShareGigabytes": 1000,
|
||||
"maxTotalShares": 10}}
|
||||
limits = {"totalSharesUsed": 1,
|
||||
"totalShareSnapshotsUsed": 1,
|
||||
"totalShareGigabytesUsed": 500,
|
||||
"totalSnapshotGigabytesUsed": 500,
|
||||
"maxTotalShares": 10,
|
||||
"maxTotalShareSnapshots": 10,
|
||||
"maxTotalShareGigabytes": 1000,
|
||||
"maxTotalSnapshotGigabytes": 1000,
|
||||
}
|
||||
|
||||
limits_negative = {"totalSharesUsed": 10,
|
||||
"totalShareSnapshotsUsed": 10,
|
||||
"totalShareGigabytesUsed": 1000,
|
||||
"totalSnapshotGigabytesUsed": 1000,
|
||||
"maxTotalShares": 10,
|
||||
"maxTotalShareSnapshots": 10,
|
||||
"maxTotalShareGigabytes": 1000,
|
||||
"maxTotalSnapshotGigabytes": 1000,
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
fixes:
|
||||
- |
|
||||
Fixed missing "Create Share" button on the "Shares" dashboard.
|
Loading…
Reference in New Issue