Remove quota checks from admin network dashboards

Remove the checks for quota for network, subnetwork, and port create
actions in the admin dashboards. While the checks are a nice feature
in the project daashboard, where they result in only one call, because
only one tenant needs to be checked, on the admin dashboard those
checks are too expensive — there is one for every tenant that has a
network. This is unacceptable on clouds that have hundreds of tenants,
and in addition it exhausts the default limit for memoization (25),
resulting in many other extra calls.

The effect of this is that the create action will no longer be grayed
out when the given user runs out of quota. This doesn't matter for the
admin user. If they try to perform the action, they will get a quota
error anyways.

This patch should significantly speed up the admin dashboards on large
clouds with a lot of tenants.

Change-Id: I67447bad868f29022c5247c2193ec804dc2a0518
This commit is contained in:
Radomir Dopieralski 2023-11-27 12:34:40 +01:00
parent 749f7fd98e
commit ce950e4eb4
5 changed files with 13 additions and 121 deletions

View File

@ -24,7 +24,6 @@ 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):
@ -35,18 +34,6 @@ 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=('port', ))
if usages.get('port', {}).get('available', 1) <= 0:
if "disabled" not in self.classes:
self.classes = list(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

View File

@ -27,7 +27,6 @@ from openstack_dashboard.dashboards.project.networks.subnets \
import tables as proj_tables
from openstack_dashboard.dashboards.project.networks.subnets.tabs \
import SubnetsTab as project_tabs_subnets_tab
from openstack_dashboard.usage import quotas
LOG = logging.getLogger(__name__)
@ -45,20 +44,6 @@ class CreateSubnet(proj_tables.SubnetPolicyTargetMixin, tables.LinkAction):
return reverse(self.url, args=(network_id,))
def allowed(self, request, datum=None):
network = self.table._get_network()
usages = quotas.tenant_quota_usages(
request, tenant_id=network.tenant_id, targets=('subnet', ))
# when Settings.OPENSTACK_NEUTRON_NETWORK['enable_quotas'] = False
# usages["subnet'] is empty
if usages.get('subnet', {}).get('available', 1) <= 0:
if 'disabled' not in self.classes:
self.classes = list(self.classes) + ['disabled']
self.verbose_name = _('Create Subnet (Quota exceeded)')
else:
self.verbose_name = _('Create Subnet')
self.classes = [c for c in self.classes if c != 'disabled']
return True

View File

@ -24,7 +24,6 @@ from horizon.workflows import views
from openstack_dashboard import api
from openstack_dashboard.dashboards.project.networks import tests
from openstack_dashboard.test import helpers as test
from openstack_dashboard.usage import quotas
DETAIL_URL = 'horizon:admin:networks:subnets:detail'
@ -393,12 +392,10 @@ class NetworkSubnetTests(test.BaseAdminViewTests):
@test.create_mocks({api.neutron: ('network_get',
'subnet_list',
'is_extension_supported',
'show_network_ip_availability'),
quotas: ('tenant_quota_usages',)})
'show_network_ip_availability')})
def _test_network_detail_ip_availability_exception(self,
mac_learning=False):
network = self.networks.first()
quota_data = self.neutron_quota_usages.first()
self._stub_is_extension_supported(
{'network-ip-availability': True,
@ -409,7 +406,6 @@ class NetworkSubnetTests(test.BaseAdminViewTests):
self.exceptions.neutron
self.mock_network_get.return_value = network
self.mock_subnet_list.return_value = [self.subnets.first()]
self.mock_tenant_quota_usages.return_value = quota_data
url = parse.unquote(reverse('horizon:admin:networks:subnets_tab',
args=[network.id]))
@ -426,11 +422,7 @@ class NetworkSubnetTests(test.BaseAdminViewTests):
self.mock_show_network_ip_availability.assert_called_once_with(
test.IsHttpRequest(), network.id)
self.assert_mock_multiple_calls_with_same_arguments(
self.mock_network_get, 2,
self.mock_network_get, 1,
mock.call(test.IsHttpRequest(), network.id))
self.mock_subnet_list.assert_called_once_with(test.IsHttpRequest(),
network_id=network.id)
self.assert_mock_multiple_calls_with_same_arguments(
self.mock_tenant_quota_usages, 3,
mock.call(test.IsHttpRequest(), tenant_id=network.tenant_id,
targets=('subnet',)))

View File

@ -25,7 +25,6 @@ from openstack_dashboard import api
from openstack_dashboard.dashboards.project.networks \
import tables as project_tables
from openstack_dashboard import policy
from openstack_dashboard.usage import quotas
LOG = logging.getLogger(__name__)
@ -52,19 +51,6 @@ class CreateSubnet(project_tables.CreateSubnet):
url = "horizon:admin:networks:createsubnet"
def allowed(self, request, datum=None):
usages = quotas.tenant_quota_usages(
request, tenant_id=datum.tenant_id, targets=('subnet', ))
# when Settings.OPENSTACK_NEUTRON_NETWORK['enable_quotas'] = False
# usages["subnet'] is empty
if usages.get('subnet', {}).get('available', 1) <= 0:
if 'disabled' not in self.classes:
self.classes = list(self.classes) + ['disabled']
self.verbose_name = _('Create Subnet (Quota exceeded)')
else:
self.verbose_name = _('Create Subnet')
self.classes = [c for c in self.classes if c != 'disabled']
return True

View File

@ -23,7 +23,6 @@ from horizon import forms
from openstack_dashboard import api
from openstack_dashboard.dashboards.project.networks import tests
from openstack_dashboard.test import helpers as test
from openstack_dashboard import usage
INDEX_TEMPLATE = 'horizon/common/_data_table_view.html'
INDEX_URL = reverse('horizon:admin:networks:index')
@ -47,18 +46,15 @@ class NetworkTests(test.BaseAdminViewTests):
@test.create_mocks({api.neutron: ('network_list',
'list_dhcp_agent_hosting_networks',
'is_extension_supported'),
api.keystone: ('tenant_list',),
usage.quotas: ('tenant_quota_usages',)})
api.keystone: ('tenant_list',)})
def test_index(self):
tenants = self.tenants.list()
quota_data = self.quota_usages.first()
self.mock_network_list.return_value = self.networks.list()
self.mock_tenant_list.return_value = [tenants, False]
self._stub_is_extension_supported(
{'network_availability_zone': True,
'dhcp_agent_scheduler': True})
self.mock_tenant_quota_usages.return_value = quota_data
self.mock_list_dhcp_agent_hosting_networks.return_value = \
self.agents.list()
@ -75,12 +71,6 @@ class NetworkTests(test.BaseAdminViewTests):
self._check_is_extension_supported(
{'network_availability_zone': 1,
'dhcp_agent_scheduler': len(self.networks.list()) + 1})
self.mock_tenant_quota_usages.assert_has_calls(
[mock.call(test.IsHttpRequest(), tenant_id=network.tenant_id,
targets=('subnet', ))
for network in self.networks.list()])
self.assertEqual(len(self.networks.list()),
self.mock_tenant_quota_usages.call_count)
self.mock_list_dhcp_agent_hosting_networks.assert_has_calls(
[mock.call(test.IsHttpRequest(), network.id)
for network in self.networks.list()])
@ -109,14 +99,11 @@ class NetworkTests(test.BaseAdminViewTests):
'dhcp_agent_scheduler': 1})
@test.create_mocks({api.neutron: ('network_get',
'is_extension_supported'),
usage.quotas: ('tenant_quota_usages',)})
'is_extension_supported')})
def test_network_detail_new(self, mac_learning=False):
network = self.networks.first()
quota_data = self.quota_usages.first()
self.mock_network_get.return_value = network
self.mock_tenant_quota_usages.return_value = quota_data
self._stub_is_extension_supported(
{'network-ip-availability': True,
'network_availability_zone': True,
@ -133,12 +120,6 @@ class NetworkTests(test.BaseAdminViewTests):
network.status_label)
self.assertTemplateUsed(res, 'horizon/common/_detail.html')
self.assert_mock_multiple_calls_with_same_arguments(
self.mock_network_get, 2,
mock.call(test.IsHttpRequest(), network.id))
self.mock_tenant_quota_usages.assert_called_once_with(
test.IsHttpRequest(), tenant_id=network.tenant_id,
targets=('subnet',))
self._check_is_extension_supported(
{'network-ip-availability': 1,
'network_availability_zone': 1,
@ -154,12 +135,10 @@ class NetworkTests(test.BaseAdminViewTests):
@test.create_mocks({api.neutron: ('network_get',
'subnet_list',
'show_network_ip_availability',
'is_extension_supported'),
usage.quotas: ('tenant_quota_usages',)})
'is_extension_supported')})
def _test_network_detail_subnets_tab(self, mac_learning=False):
network = self.networks.first()
ip_availability = self.ip_availability.get()
quota_data = self.quota_usages.first()
self.mock_show_network_ip_availability.return_value = ip_availability
self.mock_network_get.return_value = network
@ -169,7 +148,6 @@ class NetworkTests(test.BaseAdminViewTests):
'mac-learning': mac_learning,
'network_availability_zone': True,
'dhcp_agent_scheduler': True})
self.mock_tenant_quota_usages.return_value = quota_data
url = parse.unquote(reverse('horizon:admin:networks:subnets_tab',
args=[network.id]))
@ -182,7 +160,7 @@ class NetworkTests(test.BaseAdminViewTests):
self.mock_show_network_ip_availability.assert_called_once_with(
test.IsHttpRequest(), network.id)
self.assert_mock_multiple_calls_with_same_arguments(
self.mock_network_get, 2,
self.mock_network_get, 1,
mock.call(test.IsHttpRequest(), network.id))
self.mock_subnet_list.assert_called_once_with(test.IsHttpRequest(),
network_id=network.id)
@ -191,22 +169,15 @@ class NetworkTests(test.BaseAdminViewTests):
'mac-learning': 1,
'network_availability_zone': 1,
'dhcp_agent_scheduler': 1})
self.assert_mock_multiple_calls_with_same_arguments(
self.mock_tenant_quota_usages, 3,
mock.call(test.IsHttpRequest(), tenant_id=network.tenant_id,
targets=('subnet',)))
@test.create_mocks({api.neutron: ('network_get',
'port_list',
'is_extension_supported'),
usage.quotas: ('tenant_quota_usages',)})
'is_extension_supported')})
def test_network_detail_ports_tab(self, mac_learning=False):
network = self.networks.first()
quota_data = self.neutron_quota_usages.first()
self.mock_network_get.return_value = network
self.mock_port_list.return_value = [self.ports.first()]
self.mock_tenant_quota_usages.return_value = quota_data
self._stub_is_extension_supported(
{'network-ip-availability': True,
'mac-learning': mac_learning,
@ -222,19 +193,10 @@ class NetworkTests(test.BaseAdminViewTests):
self.assertCountEqual(ports, [self.ports.first()])
self.assert_mock_multiple_calls_with_same_arguments(
self.mock_network_get, 2,
self.mock_network_get, 1,
mock.call(test.IsHttpRequest(), network.id))
self.mock_port_list.assert_called_once_with(test.IsHttpRequest(),
network_id=network.id)
self.assertEqual(3, self.mock_tenant_quota_usages.call_count)
self.mock_tenant_quota_usages.assert_has_calls([
mock.call(test.IsHttpRequest(), tenant_id=network.tenant_id,
targets=('subnet',)),
mock.call(test.IsHttpRequest(), tenant_id=network.tenant_id,
targets=('port',)),
mock.call(test.IsHttpRequest(), tenant_id=network.tenant_id,
targets=('port',)),
])
self._check_is_extension_supported(
{'network-ip-availability': 1,
'mac-learning': 1,
@ -243,11 +205,9 @@ class NetworkTests(test.BaseAdminViewTests):
@test.create_mocks({api.neutron: ('network_get',
'is_extension_supported',
'list_dhcp_agent_hosting_networks',),
usage.quotas: ('tenant_quota_usages',)})
'list_dhcp_agent_hosting_networks',)})
def test_network_detail_agents_tab(self, mac_learning=False):
network = self.networks.first()
quota_data = self.quota_usages.first()
self._stub_is_extension_supported(
{'network-ip-availability': True,
@ -257,7 +217,6 @@ class NetworkTests(test.BaseAdminViewTests):
self.mock_list_dhcp_agent_hosting_networks.return_value = \
self.agents.list()
self.mock_network_get.return_value = network
self.mock_tenant_quota_usages.return_value = quota_data
url = reverse('horizon:admin:networks:agents_tab', args=[network.id])
res = self.client.get(parse.unquote(url))
@ -277,9 +236,6 @@ class NetworkTests(test.BaseAdminViewTests):
test.IsHttpRequest(), network.id)
self.mock_network_get.assert_called_once_with(
test.IsHttpRequest(), network.id)
self.mock_tenant_quota_usages.assert_called_once_with(
test.IsHttpRequest(), tenant_id=network.tenant_id,
targets=('subnet',))
def test_network_detail_subnets_tab_network_exception(self):
self._test_network_detail_subnets_tab_network_exception()
@ -331,12 +287,10 @@ class NetworkTests(test.BaseAdminViewTests):
@test.create_mocks({api.neutron: ('network_get',
'subnet_list',
'show_network_ip_availability',
'is_extension_supported'),
usage.quotas: ('tenant_quota_usages',)})
'is_extension_supported')})
def _test_network_detail_subnets_tab_subnet_exception(self,
mac_learning=False):
network = self.networks.first()
quota_data = self.quota_usages.first()
self.mock_show_network_ip_availability.return_value = \
self.ip_availability.get()
@ -347,7 +301,6 @@ class NetworkTests(test.BaseAdminViewTests):
'mac-learning': mac_learning,
'dhcp_agent_scheduler': True,
'network_availability_zone': True})
self.mock_tenant_quota_usages.return_value = quota_data
url = parse.unquote(reverse('horizon:admin:networks:subnets_tab',
args=[network.id]))
@ -360,14 +313,10 @@ class NetworkTests(test.BaseAdminViewTests):
self.mock_show_network_ip_availability.assert_called_once_with(
test.IsHttpRequest(), network.id)
self.assert_mock_multiple_calls_with_same_arguments(
self.mock_network_get, 2,
self.mock_network_get, 1,
mock.call(test.IsHttpRequest(), network.id))
self.mock_subnet_list.assert_called_once_with(test.IsHttpRequest(),
network_id=network.id)
self.assert_mock_multiple_calls_with_same_arguments(
self.mock_tenant_quota_usages, 3,
mock.call(test.IsHttpRequest(), tenant_id=network.tenant_id,
targets=('subnet',)))
self._stub_is_extension_supported(
{'network-ip-availability': 1,
'mac-learning': 1,
@ -383,13 +332,11 @@ class NetworkTests(test.BaseAdminViewTests):
@test.create_mocks({api.neutron: ('network_get',
'subnet_list',
'is_extension_supported',
'show_network_ip_availability'),
usage.quotas: ('tenant_quota_usages',)})
'show_network_ip_availability')})
def _test_network_detail_subnets_tab_port_exception(self,
mac_learning=False):
network = self.networks.first()
ip_availability = self.ip_availability.get()
quota_data = self.quota_usages.first()
self.mock_show_network_ip_availability.return_value = ip_availability
self.mock_network_get.return_value = network
@ -399,7 +346,6 @@ class NetworkTests(test.BaseAdminViewTests):
'mac-learning': mac_learning,
'network_availability_zone': True,
'dhcp_agent_scheduler': True})
self.mock_tenant_quota_usages.return_value = quota_data
url = parse.unquote(reverse('horizon:admin:networks:subnets_tab',
args=[network.id]))
@ -412,7 +358,7 @@ class NetworkTests(test.BaseAdminViewTests):
self.mock_show_network_ip_availability.assert_called_once_with(
test.IsHttpRequest(), network.id)
self.assert_mock_multiple_calls_with_same_arguments(
self.mock_network_get, 2,
self.mock_network_get, 1,
mock.call(test.IsHttpRequest(), network.id))
self.mock_subnet_list.assert_called_once_with(test.IsHttpRequest(),
network_id=network.id)
@ -421,10 +367,6 @@ class NetworkTests(test.BaseAdminViewTests):
'mac-learning': 1,
'network_availability_zone': 1,
'dhcp_agent_scheduler': 1})
self.assert_mock_multiple_calls_with_same_arguments(
self.mock_tenant_quota_usages, 3,
mock.call(test.IsHttpRequest(), tenant_id=network.tenant_id,
targets=('subnet',)))
@test.create_mocks({api.neutron: ('is_extension_supported',),
api.keystone: ('tenant_list',)})