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:
Julie Pichon 2013-08-26 16:32:37 +01:00
parent eb0d36a106
commit fde88906b3
9 changed files with 203 additions and 13 deletions

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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)

View File

@ -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

View File

@ -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'))

View File

@ -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 = {

View File

@ -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)

View File

@ -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: