Disable 'Create Port' button if ports quota is exceeded
Ports has quota management in a project, if quota is exceeded, will create failure and API returns "Recoverable error: Quota exceeded for resources: ['port']". So, it should be like creating a network and subnet to perform quota checks on ports, and if the quota is exceeded, add text descriptions to the create port button and disable it. Change-Id: I31bd8f93c312179b86115544ba0fadc9a9ffec63 Closes-Bug:#1712556
This commit is contained in:
parent
233680f541
commit
0b65dbc913
@ -12,14 +12,19 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tables
|
||||
from horizon.utils import memoized
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.dashboards.project.networks.ports import \
|
||||
tables as project_tables
|
||||
from openstack_dashboard.dashboards.project.networks.ports.tabs \
|
||||
import PortsTab as project_port_tab
|
||||
from openstack_dashboard.usage import quotas
|
||||
|
||||
|
||||
class DeletePort(project_tables.DeletePort):
|
||||
@ -29,6 +34,21 @@ class DeletePort(project_tables.DeletePort):
|
||||
class CreatePort(project_tables.CreatePort):
|
||||
url = "horizon:admin:networks:addport"
|
||||
|
||||
def allowed(self, request, datum=None):
|
||||
network = self.table._get_network()
|
||||
tenant_id = network.tenant_id
|
||||
usages = quotas.tenant_quota_usages(
|
||||
request, tenant_id=tenant_id, targets=('ports', ))
|
||||
if usages.get('ports', {}).get('available', 1) <= 0:
|
||||
if "disabled" not in self.classes:
|
||||
self.classes = [c for c in self.classes] + ["disabled"]
|
||||
self.verbose_name = _("Create Port (Quota exceeded)")
|
||||
else:
|
||||
self.verbose_name = _("Create Port")
|
||||
self.classes = [c for c in self.classes if c != "disabled"]
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class UpdatePort(project_tables.UpdatePort):
|
||||
url = "horizon:admin:networks:editport"
|
||||
@ -38,6 +58,19 @@ class PortsTable(project_tables.PortsTable):
|
||||
name = tables.WrappingColumn("name_or_id",
|
||||
verbose_name=_("Name"),
|
||||
link="horizon:admin:networks:ports:detail")
|
||||
failure_url = reverse_lazy('horizon:admin:networks:index')
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_network(self):
|
||||
try:
|
||||
network_id = self.kwargs['network_id']
|
||||
network = api.neutron.network_get(self.request, network_id)
|
||||
network.set_id_as_name_if_empty(length=0)
|
||||
except Exception:
|
||||
msg = _('Unable to retrieve details for network "%s".') \
|
||||
% (network_id)
|
||||
exceptions.handle(self.request, msg, redirect=self.failure_url)
|
||||
return network
|
||||
|
||||
class Meta(object):
|
||||
name = "ports"
|
||||
|
@ -25,6 +25,7 @@ from horizon import tables
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard import policy
|
||||
from openstack_dashboard.usage import quotas
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
@ -82,6 +83,23 @@ class CreatePort(tables.LinkAction):
|
||||
network_id = self.table.kwargs['network_id']
|
||||
return reverse(self.url, args=(network_id,))
|
||||
|
||||
def allowed(self, request, datum=None):
|
||||
usages = quotas.tenant_quota_usages(request, targets=('ports', ))
|
||||
# when Settings.OPENSTACK_NEUTRON_NETWORK['enable_quotas'] = False
|
||||
# usages["ports"] is empty
|
||||
if usages.get('ports', {}).get('available', 1) <= 0:
|
||||
if "disabled" not in self.classes:
|
||||
self.classes = [c for c in self.classes] + ["disabled"]
|
||||
self.verbose_name = _("Create Port (Quota exceeded)")
|
||||
else:
|
||||
# If the port is deleted, the usage of port will less than
|
||||
# the quota again, so we need to redefine the status of the
|
||||
# button.
|
||||
self.verbose_name = _("Create Port")
|
||||
self.classes = [c for c in self.classes if c != "disabled"]
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class DeletePort(policy.PolicyTargetMixin, tables.DeleteAction):
|
||||
@staticmethod
|
||||
|
@ -1162,3 +1162,66 @@ class NetworkViewTests(test.TestCase, NetworkStubMixin):
|
||||
six.text_type(create_action.verbose_name))
|
||||
self.assertEqual((('network', 'create_subnet'),),
|
||||
create_action.policy_rules)
|
||||
|
||||
@test.create_stubs({api.neutron: ('network_get',
|
||||
'port_list',
|
||||
'is_extension_supported',),
|
||||
quotas: ('tenant_quota_usages',)})
|
||||
def _test_port_create_button(self, quota_data):
|
||||
network_id = self.networks.first().id
|
||||
|
||||
api.neutron.network_get(
|
||||
IsA(http.HttpRequest), network_id) \
|
||||
.MultipleTimes().AndReturn(self.networks.first())
|
||||
api.neutron.port_list(
|
||||
IsA(http.HttpRequest), network_id=network_id) \
|
||||
.AndReturn(self.ports.list())
|
||||
api.neutron.is_extension_supported(
|
||||
IsA(http.HttpRequest), 'mac-learning') \
|
||||
.AndReturn(False)
|
||||
quotas.tenant_quota_usages(
|
||||
IsA(http.HttpRequest), targets=('subnets', )) \
|
||||
.MultipleTimes().AndReturn(quota_data)
|
||||
quotas.tenant_quota_usages(
|
||||
IsA(http.HttpRequest), targets=('ports',)) \
|
||||
.MultipleTimes().AndReturn(quota_data)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = urlunquote(reverse('horizon:project:networks:ports_tab',
|
||||
args=[network_id]))
|
||||
res = self.client.get(url)
|
||||
self.assertTemplateUsed(res, 'horizon/common/_detail.html')
|
||||
|
||||
ports = res.context['ports_table'].data
|
||||
self.assertItemsEqual(ports, self.ports.list())
|
||||
|
||||
return self.getAndAssertTableAction(res, 'ports', 'create')
|
||||
|
||||
def test_port_create_button_disabled_when_quota_exceeded(self):
|
||||
quota_data = self.neutron_quota_usages.first()
|
||||
quota_data['ports']['available'] = 0
|
||||
create_action = self._test_port_create_button(quota_data)
|
||||
self.assertIn('disabled', create_action.classes,
|
||||
'The create button should be disabled')
|
||||
|
||||
def test_port_create_button_enabled_when_quota_disabled(self):
|
||||
# In case of enable_quotas False, neutron related items
|
||||
# are not set in a response from tenant_quota_usages.
|
||||
quota_data = {}
|
||||
create_action = self._test_port_create_button(quota_data)
|
||||
self.assertNotIn('disabled', create_action.classes,
|
||||
'The create button should be enabled')
|
||||
|
||||
def test_create_port_button_attributes(self):
|
||||
quota_data = self.neutron_quota_usages.first()
|
||||
quota_data['ports']['available'] = 1
|
||||
create_action = self._test_port_create_button(quota_data)
|
||||
|
||||
self.assertEqual(set(['ajax-modal']), set(create_action.classes))
|
||||
self.assertEqual('horizon:project:networks:addport',
|
||||
create_action.url)
|
||||
self.assertEqual('Create Port',
|
||||
six.text_type(create_action.verbose_name))
|
||||
self.assertEqual((('network', 'create_port'),),
|
||||
create_action.policy_rules)
|
||||
|
@ -572,6 +572,7 @@ def data(TEST):
|
||||
# Quota Usages
|
||||
quota_usage_data = {'networks': {'used': 0, 'quota': 5},
|
||||
'subnets': {'used': 0, 'quota': 5},
|
||||
'ports': {'used': 0, 'quota': 5},
|
||||
'routers': {'used': 0, 'quota': 5},
|
||||
}
|
||||
quota_usage = usage_quotas.QuotaUsage()
|
||||
|
@ -229,6 +229,10 @@ def get_tenant_quota_data(request, disabled_quotas=None, tenant_id=None):
|
||||
net_quota = neutron_quotas.get('subnet').limit
|
||||
qs.add(base.QuotaSet({'subnets': net_quota}))
|
||||
|
||||
if 'port' not in disabled_quotas:
|
||||
net_quota = neutron_quotas.get('port').limit
|
||||
qs.add(base.QuotaSet({'ports': net_quota}))
|
||||
|
||||
if 'router' not in disabled_quotas:
|
||||
router_quota = neutron_quotas.get('router').limit
|
||||
qs.add(base.QuotaSet({'routers': router_quota}))
|
||||
@ -364,6 +368,10 @@ def _get_tenant_network_usages(request, usages, disabled_quotas, tenant_id):
|
||||
subnets = neutron.subnet_list(request, tenant_id=tenant_id)
|
||||
usages.tally('subnets', len(subnets))
|
||||
|
||||
if 'port' not in disabled_quotas:
|
||||
ports = neutron.port_list(request, tenant_id=tenant_id)
|
||||
usages.tally('ports', len(ports))
|
||||
|
||||
if 'router' not in disabled_quotas:
|
||||
routers = neutron.router_list(request, tenant_id=tenant_id)
|
||||
usages.tally('routers', len(routers))
|
||||
|
Loading…
x
Reference in New Issue
Block a user