Show Neutron floating IPs quotas on Overview
Display the correct limits and usage when Neutron is in use and the quotas extension is enabled. If Neutron is enabled but the quotas extensions is not supported, assume the floating IPs quota is unlimited (a floating IP quota is expected to exist in other places, e.g. Security and Access panel) Because quotas may not be configured or enabled even if the extension is available, add an 'enable_quotas' setting. Partial-Bug: #1109140 Change-Id: Id6345f4700f0ff45be8ce8acb69cca0d4e05e14a
This commit is contained in:
parent
eb0d36a106
commit
fde88906b3
|
@ -158,7 +158,12 @@ class QuotaSet(Sequence):
|
|||
def __init__(self, apiresource=None):
|
||||
self.items = []
|
||||
if apiresource:
|
||||
for k, v in apiresource._info.items():
|
||||
if hasattr(apiresource, '_info'):
|
||||
items = apiresource._info.items()
|
||||
else:
|
||||
items = apiresource.items()
|
||||
|
||||
for k, v in items:
|
||||
if k == 'id':
|
||||
continue
|
||||
self[k] = v
|
||||
|
|
|
@ -602,3 +602,25 @@ def router_add_gateway(request, router_id, network_id):
|
|||
|
||||
def router_remove_gateway(request, router_id):
|
||||
neutronclient(request).remove_gateway_router(router_id)
|
||||
|
||||
|
||||
def tenant_quota_get(request, tenant_id):
|
||||
return base.QuotaSet(neutronclient(request).show_quota(tenant_id)['quota'])
|
||||
|
||||
|
||||
def list_extensions(request):
|
||||
extensions_list = neutronclient(request).list_extensions()
|
||||
if 'extensions' in extensions_list:
|
||||
return extensions_list['extensions']
|
||||
else:
|
||||
return {}
|
||||
|
||||
|
||||
def is_extension_supported(request, extension_alias):
|
||||
extensions = list_extensions(request)
|
||||
|
||||
for extension in extensions:
|
||||
if extension['alias'] == extension_alias:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
|
|
@ -39,7 +39,8 @@ INDEX_URL = reverse('horizon:project:overview:index')
|
|||
class UsageViewTests(test.BaseAdminViewTests):
|
||||
|
||||
@test.create_stubs({api.nova: ('usage_list', 'tenant_absolute_limits', ),
|
||||
api.keystone: ('tenant_list',)})
|
||||
api.keystone: ('tenant_list',),
|
||||
api.network: ('tenant_floating_ip_list',)})
|
||||
def test_usage(self):
|
||||
now = timezone.now()
|
||||
usage_obj = api.nova.NovaUsage(self.usages.first())
|
||||
|
@ -55,7 +56,10 @@ class UsageViewTests(test.BaseAdminViewTests):
|
|||
.AndReturn([usage_obj])
|
||||
api.nova.tenant_absolute_limits(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.limits['absolute'])
|
||||
api.network.tenant_floating_ip_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.floating_ips.list())
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('horizon:admin:overview:index'))
|
||||
self.assertTemplateUsed(res, 'admin/overview/usage.html')
|
||||
self.assertTrue(isinstance(res.context['usage'], usage.GlobalUsage))
|
||||
|
@ -74,7 +78,8 @@ class UsageViewTests(test.BaseAdminViewTests):
|
|||
usage_obj.total_local_gb_usage))
|
||||
|
||||
@test.create_stubs({api.nova: ('usage_list', 'tenant_absolute_limits', ),
|
||||
api.keystone: ('tenant_list',)})
|
||||
api.keystone: ('tenant_list',),
|
||||
api.network: ('tenant_floating_ip_list',)})
|
||||
def test_usage_csv(self):
|
||||
now = timezone.now()
|
||||
usage_obj = [api.nova.NovaUsage(u) for u in self.usages.list()]
|
||||
|
@ -90,7 +95,10 @@ class UsageViewTests(test.BaseAdminViewTests):
|
|||
.AndReturn(usage_obj)
|
||||
api.nova.tenant_absolute_limits(IsA(http.HttpRequest))\
|
||||
.AndReturn(self.limits['absolute'])
|
||||
api.network.tenant_floating_ip_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.floating_ips.list())
|
||||
self.mox.ReplayAll()
|
||||
|
||||
csv_url = reverse('horizon:admin:overview:index') + "?format=csv"
|
||||
res = self.client.get(csv_url)
|
||||
self.assertTemplateUsed(res, 'admin/overview/usage.csv')
|
||||
|
|
|
@ -22,6 +22,7 @@ import datetime
|
|||
|
||||
from django.core.urlresolvers import reverse # noqa
|
||||
from django import http
|
||||
from django.test.utils import override_settings # noqa
|
||||
from django.utils import timezone
|
||||
|
||||
from mox import IsA # noqa
|
||||
|
@ -40,6 +41,7 @@ class UsageViewTests(test.TestCase):
|
|||
usage_obj = api.nova.NovaUsage(self.usages.first())
|
||||
self.mox.StubOutWithMock(api.nova, 'usage_get')
|
||||
self.mox.StubOutWithMock(api.nova, 'tenant_absolute_limits')
|
||||
self.mox.StubOutWithMock(api.network, 'tenant_floating_ip_list')
|
||||
api.nova.usage_get(IsA(http.HttpRequest), self.tenant.id,
|
||||
datetime.datetime(now.year,
|
||||
now.month,
|
||||
|
@ -48,20 +50,52 @@ class UsageViewTests(test.TestCase):
|
|||
now.month,
|
||||
now.day, 23, 59, 59, 0)) \
|
||||
.AndReturn(usage_obj)
|
||||
api.nova.tenant_absolute_limits(IsA(http.HttpRequest))\
|
||||
api.nova.tenant_absolute_limits(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.limits['absolute'])
|
||||
api.network.tenant_floating_ip_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.floating_ips.list())
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('horizon:project:overview:index'))
|
||||
usages = res.context['usage']
|
||||
self.assertTemplateUsed(res, 'project/overview/usage.html')
|
||||
self.assertTrue(isinstance(res.context['usage'], usage.ProjectUsage))
|
||||
self.assertTrue(isinstance(usages, usage.ProjectUsage))
|
||||
self.assertContains(res, 'form-horizontal')
|
||||
self.assertEqual(usages.limits['maxTotalFloatingIps'], float("inf"))
|
||||
|
||||
def test_usage_nova_network(self):
|
||||
now = timezone.now()
|
||||
usage_obj = api.nova.NovaUsage(self.usages.first())
|
||||
self.mox.StubOutWithMock(api.nova, 'usage_get')
|
||||
self.mox.StubOutWithMock(api.nova, 'tenant_absolute_limits')
|
||||
self.mox.StubOutWithMock(api.base, 'is_service_enabled')
|
||||
api.nova.usage_get(IsA(http.HttpRequest), self.tenant.id,
|
||||
datetime.datetime(now.year,
|
||||
now.month,
|
||||
now.day, 0, 0, 0, 0),
|
||||
datetime.datetime(now.year,
|
||||
now.month,
|
||||
now.day, 23, 59, 59, 0)) \
|
||||
.AndReturn(usage_obj)
|
||||
api.nova.tenant_absolute_limits(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.limits['absolute'])
|
||||
api.base.is_service_enabled(IsA(http.HttpRequest), 'network') \
|
||||
.AndReturn(False)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('horizon:project:overview:index'))
|
||||
usages = res.context['usage']
|
||||
self.assertTemplateUsed(res, 'project/overview/usage.html')
|
||||
self.assertTrue(isinstance(usages, usage.ProjectUsage))
|
||||
self.assertContains(res, 'form-horizontal')
|
||||
self.assertEqual(usages.limits['maxTotalFloatingIps'], 10)
|
||||
|
||||
def test_unauthorized(self):
|
||||
exc = self.exceptions.nova_unauthorized
|
||||
now = timezone.now()
|
||||
self.mox.StubOutWithMock(api.nova, 'usage_get')
|
||||
self.mox.StubOutWithMock(api.nova, 'tenant_absolute_limits')
|
||||
self.mox.StubOutWithMock(api.network, 'tenant_floating_ip_list')
|
||||
api.nova.usage_get(IsA(http.HttpRequest), self.tenant.id,
|
||||
datetime.datetime(now.year,
|
||||
now.month,
|
||||
|
@ -72,6 +106,8 @@ class UsageViewTests(test.TestCase):
|
|||
.AndRaise(exc)
|
||||
api.nova.tenant_absolute_limits(IsA(http.HttpRequest))\
|
||||
.AndReturn(self.limits['absolute'])
|
||||
api.network.tenant_floating_ip_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.floating_ips.list())
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:project:overview:index')
|
||||
|
@ -85,6 +121,7 @@ class UsageViewTests(test.TestCase):
|
|||
usage_obj = api.nova.NovaUsage(self.usages.first())
|
||||
self.mox.StubOutWithMock(api.nova, 'usage_get')
|
||||
self.mox.StubOutWithMock(api.nova, 'tenant_absolute_limits')
|
||||
self.mox.StubOutWithMock(api.network, 'tenant_floating_ip_list')
|
||||
start = datetime.datetime(now.year, now.month, now.day, 0, 0, 0, 0)
|
||||
end = datetime.datetime(now.year, now.month, now.day, 23, 59, 59, 0)
|
||||
api.nova.usage_get(IsA(http.HttpRequest),
|
||||
|
@ -92,7 +129,8 @@ class UsageViewTests(test.TestCase):
|
|||
start, end).AndReturn(usage_obj)
|
||||
api.nova.tenant_absolute_limits(IsA(http.HttpRequest))\
|
||||
.AndReturn(self.limits['absolute'])
|
||||
|
||||
api.network.tenant_floating_ip_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.floating_ips.list())
|
||||
self.mox.ReplayAll()
|
||||
res = self.client.get(reverse('horizon:project:overview:index') +
|
||||
"?format=csv")
|
||||
|
@ -103,6 +141,7 @@ class UsageViewTests(test.TestCase):
|
|||
now = timezone.now()
|
||||
self.mox.StubOutWithMock(api.nova, 'usage_get')
|
||||
self.mox.StubOutWithMock(api.nova, 'tenant_absolute_limits')
|
||||
self.mox.StubOutWithMock(api.network, 'tenant_floating_ip_list')
|
||||
start = datetime.datetime(now.year, now.month, now.day, 0, 0, 0, 0)
|
||||
end = datetime.datetime(now.year, now.month, now.day, 23, 59, 59, 0)
|
||||
api.nova.usage_get(IsA(http.HttpRequest),
|
||||
|
@ -110,7 +149,8 @@ class UsageViewTests(test.TestCase):
|
|||
start, end).AndRaise(self.exceptions.nova)
|
||||
api.nova.tenant_absolute_limits(IsA(http.HttpRequest))\
|
||||
.AndReturn(self.limits['absolute'])
|
||||
|
||||
api.network.tenant_floating_ip_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.floating_ips.list())
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('horizon:project:overview:index'))
|
||||
|
@ -122,6 +162,7 @@ class UsageViewTests(test.TestCase):
|
|||
usage_obj = api.nova.NovaUsage(self.usages.first())
|
||||
self.mox.StubOutWithMock(api.nova, 'usage_get')
|
||||
self.mox.StubOutWithMock(api.nova, 'tenant_absolute_limits')
|
||||
self.mox.StubOutWithMock(api.network, 'tenant_floating_ip_list')
|
||||
start = datetime.datetime(now.year, now.month, now.day, 0, 0, 0, 0)
|
||||
end = datetime.datetime(now.year, now.month, now.day, 23, 59, 59, 0)
|
||||
api.nova.usage_get(IsA(http.HttpRequest),
|
||||
|
@ -129,6 +170,8 @@ class UsageViewTests(test.TestCase):
|
|||
start, end).AndReturn(usage_obj)
|
||||
api.nova.tenant_absolute_limits(IsA(http.HttpRequest))\
|
||||
.AndRaise(self.exceptions.nova)
|
||||
api.network.tenant_floating_ip_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.floating_ips.list())
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('horizon:project:overview:index'))
|
||||
|
@ -140,6 +183,7 @@ class UsageViewTests(test.TestCase):
|
|||
usage_obj = api.nova.NovaUsage(self.usages.first())
|
||||
self.mox.StubOutWithMock(api.nova, 'usage_get')
|
||||
self.mox.StubOutWithMock(api.nova, 'tenant_absolute_limits')
|
||||
self.mox.StubOutWithMock(api.network, 'tenant_floating_ip_list')
|
||||
start = datetime.datetime(now.year, now.month, now.day, 0, 0, 0, 0)
|
||||
end = datetime.datetime(now.year, now.month, now.day, 23, 59, 59, 0)
|
||||
api.nova.usage_get(IsA(http.HttpRequest),
|
||||
|
@ -147,8 +191,41 @@ class UsageViewTests(test.TestCase):
|
|||
start, end).AndReturn(usage_obj)
|
||||
api.nova.tenant_absolute_limits(IsA(http.HttpRequest))\
|
||||
.AndReturn(self.limits['absolute'])
|
||||
api.network.tenant_floating_ip_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.floating_ips.list())
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('horizon:project:overview:index'))
|
||||
self.assertTemplateUsed(res, 'project/overview/usage.html')
|
||||
self.assertTrue(isinstance(res.context['usage'], usage.ProjectUsage))
|
||||
|
||||
@override_settings(OPENSTACK_NEUTRON_NETWORK={'enable_quotas': True})
|
||||
def test_usage_with_neutron_floating_ips(self):
|
||||
now = timezone.now()
|
||||
usage_obj = api.nova.NovaUsage(self.usages.first())
|
||||
self.mox.StubOutWithMock(api.nova, 'usage_get')
|
||||
self.mox.StubOutWithMock(api.nova, 'tenant_absolute_limits')
|
||||
self.mox.StubOutWithMock(api.neutron, 'is_extension_supported')
|
||||
self.mox.StubOutWithMock(api.neutron, 'tenant_quota_get')
|
||||
self.mox.StubOutWithMock(api.network, 'tenant_floating_ip_list')
|
||||
start = datetime.datetime(now.year, now.month, now.day, 0, 0, 0, 0)
|
||||
end = datetime.datetime(now.year, now.month, now.day, 23, 59, 59, 0)
|
||||
api.nova.usage_get(IsA(http.HttpRequest),
|
||||
self.tenant.id,
|
||||
start, end).AndReturn(usage_obj)
|
||||
api.nova.tenant_absolute_limits(IsA(http.HttpRequest))\
|
||||
.AndReturn(self.limits['absolute'])
|
||||
api.neutron.is_extension_supported(IsA(http.HttpRequest), 'quotas') \
|
||||
.AndReturn(True)
|
||||
api.neutron.tenant_quota_get(IsA(http.HttpRequest), self.tenant.id) \
|
||||
.AndReturn(self.neutron_quotas.first())
|
||||
api.network.tenant_floating_ip_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.floating_ips.list())
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('horizon:project:overview:index'))
|
||||
self.assertContains(res, 'Floating IPs')
|
||||
|
||||
# Make sure the floating IPs limit comes from Neutron (50 vs. 10)
|
||||
max_floating_ips = res.context['usage'].limits['maxTotalFloatingIps']
|
||||
self.assertEqual(max_floating_ips, 50)
|
||||
|
|
|
@ -155,11 +155,12 @@ OPENSTACK_HYPERVISOR_FEATURES = {
|
|||
}
|
||||
|
||||
# The OPENSTACK_NEUTRON_NETWORK settings can be used to enable optional
|
||||
# services provided by neutron. Currently only the load balancer service
|
||||
# is available.
|
||||
# services provided by neutron. Options currenly available are load
|
||||
# balancer service, security groups, quotas.
|
||||
OPENSTACK_NEUTRON_NETWORK = {
|
||||
'enable_security_group': True,
|
||||
'enable_lb': False,
|
||||
'enable_quotas': True,
|
||||
'enable_security_group': True,
|
||||
}
|
||||
|
||||
# OPENSTACK_ENDPOINT_TYPE specifies the endpoint type to use for the endpoints
|
||||
|
|
|
@ -270,3 +270,14 @@ class NeutronApiTests(test.APITestCase):
|
|||
|
||||
api.neutron.router_remove_interface(
|
||||
self.request, router_id, port_id=fake_port)
|
||||
|
||||
def test_is_extension_supported(self):
|
||||
neutronclient = self.stub_neutronclient()
|
||||
neutronclient.list_extensions().MultipleTimes() \
|
||||
.AndReturn(self.api_extensions.first())
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.assertTrue(
|
||||
api.neutron.is_extension_supported(self.request, 'quotas'))
|
||||
self.assertFalse(
|
||||
api.neutron.is_extension_supported(self.request, 'doesntexist'))
|
||||
|
|
|
@ -83,7 +83,8 @@ OPENSTACK_KEYSTONE_BACKEND = {
|
|||
}
|
||||
|
||||
OPENSTACK_NEUTRON_NETWORK = {
|
||||
'enable_lb': True
|
||||
'enable_lb': True,
|
||||
'enable_quotas': False # Enabled in specific tests only
|
||||
}
|
||||
|
||||
OPENSTACK_HYPERVISOR_FEATURES = {
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
import copy
|
||||
import uuid
|
||||
|
||||
from openstack_dashboard.api import base
|
||||
from openstack_dashboard.api import lbaas
|
||||
|
||||
from openstack_dashboard.api import neutron
|
||||
|
||||
from openstack_dashboard.test.test_data import utils
|
||||
|
@ -35,6 +35,7 @@ def data(TEST):
|
|||
TEST.vips = utils.TestDataContainer()
|
||||
TEST.members = utils.TestDataContainer()
|
||||
TEST.monitors = utils.TestDataContainer()
|
||||
TEST.neutron_quotas = utils.TestDataContainer()
|
||||
|
||||
# data return by neutronclient
|
||||
TEST.api_networks = utils.TestDataContainer()
|
||||
|
@ -48,6 +49,7 @@ def data(TEST):
|
|||
TEST.api_vips = utils.TestDataContainer()
|
||||
TEST.api_members = utils.TestDataContainer()
|
||||
TEST.api_monitors = utils.TestDataContainer()
|
||||
TEST.api_extensions = utils.TestDataContainer()
|
||||
|
||||
#------------------------------------------------------------
|
||||
# 1st network
|
||||
|
@ -448,3 +450,26 @@ def data(TEST):
|
|||
'admin_state_up': True}
|
||||
TEST.api_monitors.add(monitor_dict)
|
||||
TEST.monitors.add(lbaas.PoolMonitor(monitor_dict))
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Quotas
|
||||
quota_data = dict(floatingip='50',
|
||||
network='10',
|
||||
port='50',
|
||||
router='10',
|
||||
security_groups='10',
|
||||
security_group_rules='100',
|
||||
subnet='10')
|
||||
TEST.neutron_quotas.add(base.QuotaSet(quota_data))
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Extensions
|
||||
extension_1 = {"name": "security-group",
|
||||
"alias": "security-group",
|
||||
"description": "The security groups extension."}
|
||||
extension_2 = {"name": "Quota management support",
|
||||
"alias": "quotas",
|
||||
"description": "Expose functions for quotas management"}
|
||||
extensions = {}
|
||||
extensions['extensions'] = [extension_1, extension_2]
|
||||
TEST.api_extensions.add(extensions)
|
||||
|
|
|
@ -7,6 +7,7 @@ import datetime
|
|||
import logging
|
||||
from StringIO import StringIO # noqa
|
||||
|
||||
from django.conf import settings # noqa
|
||||
from django.http import HttpResponse # noqa
|
||||
from django import template as django_template
|
||||
from django.utils import timezone
|
||||
|
@ -102,6 +103,43 @@ class BaseUsage(object):
|
|||
'end': init[1]})
|
||||
return self.form
|
||||
|
||||
def get_neutron_limits(self):
|
||||
if not api.base.is_service_enabled(self.request, 'network'):
|
||||
return
|
||||
|
||||
# Retrieve number of floating IPs currently allocated
|
||||
try:
|
||||
floating_ips_current = len(
|
||||
api.network.tenant_floating_ip_list(self.request))
|
||||
except Exception:
|
||||
floating_ips_current = 0
|
||||
msg = _('Unable to retrieve floating IP addresses.')
|
||||
exceptions.handle(self.request, msg)
|
||||
|
||||
self.limits['totalFloatingIpsUsed'] = floating_ips_current
|
||||
|
||||
# Quotas are an optional extension in Neutron. If it isn't
|
||||
# enabled, assume the floating IP limit is infinite.
|
||||
network_config = getattr(settings, 'OPENSTACK_NEUTRON_NETWORK', {})
|
||||
if not network_config.get('enable_quotas', False) or \
|
||||
not api.neutron.is_extension_supported(self.request, 'quotas'):
|
||||
self.limits['maxTotalFloatingIps'] = float("inf")
|
||||
return
|
||||
|
||||
try:
|
||||
neutron_quotas = api.neutron.tenant_quota_get(self.request,
|
||||
self.project_id)
|
||||
floating_ips_max = getattr(neutron_quotas.get('floatingip'),
|
||||
'limit', float("inf"))
|
||||
if floating_ips_max == -1:
|
||||
floating_ips_max = float("inf")
|
||||
except Exception:
|
||||
floating_ips_max = float("inf")
|
||||
msg = _('Unable to retrieve network quota information.')
|
||||
exceptions.handle(self.request, msg)
|
||||
|
||||
self.limits['maxTotalFloatingIps'] = floating_ips_max
|
||||
|
||||
def get_limits(self):
|
||||
try:
|
||||
self.limits = api.nova.tenant_absolute_limits(self.request)
|
||||
|
@ -109,8 +147,10 @@ class BaseUsage(object):
|
|||
exceptions.handle(self.request,
|
||||
_("Unable to retrieve limit information."))
|
||||
|
||||
self.get_neutron_limits()
|
||||
|
||||
def get_usage_list(self, start, end):
|
||||
raise NotImplementedError("You must define a get_usage method.")
|
||||
raise NotImplementedError("You must define a get_usage_list method.")
|
||||
|
||||
def summarize(self, start, end):
|
||||
if start <= end and start <= self.today:
|
||||
|
|
Loading…
Reference in New Issue