Skip to content

Commit

Permalink
Show Neutron floating IPs quotas on Overview
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jpichon committed Aug 26, 2013
1 parent eb0d36a commit fde8890
Show file tree
Hide file tree
Showing 9 changed files with 203 additions and 13 deletions.
7 changes: 6 additions & 1 deletion openstack_dashboard/api/base.py
Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions openstack_dashboard/api/neutron.py
Expand Up @@ -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
12 changes: 10 additions & 2 deletions openstack_dashboard/dashboards/admin/overview/tests.py
Expand Up @@ -39,7 +39,8 @@
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())
Expand All @@ -55,7 +56,10 @@ def test_usage(self):
.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))
Expand All @@ -74,7 +78,8 @@ def test_usage(self):
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()]
Expand All @@ -90,7 +95,10 @@ def test_usage_csv(self):
.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')
Expand Down
85 changes: 81 additions & 4 deletions openstack_dashboard/dashboards/project/overview/tests.py
Expand Up @@ -22,6 +22,7 @@

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
Expand All @@ -40,6 +41,7 @@ def test_usage(self):
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,
Expand All @@ -48,20 +50,52 @@ def test_usage(self):
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,
Expand All @@ -72,6 +106,8 @@ def test_unauthorized(self):
.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')
Expand All @@ -85,14 +121,16 @@ def test_usage_csv(self):
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),
self.tenant.id,
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")
Expand All @@ -103,14 +141,16 @@ def test_usage_exception_usage(self):
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),
self.tenant.id,
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'))
Expand All @@ -122,13 +162,16 @@ def test_usage_exception_quota(self):
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),
self.tenant.id,
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'))
Expand All @@ -140,15 +183,49 @@ def test_usage_default_tenant(self):
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),
self.tenant.id,
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)
7 changes: 4 additions & 3 deletions openstack_dashboard/local/local_settings.py.example
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions openstack_dashboard/test/api_tests/neutron_tests.py
Expand Up @@ -270,3 +270,14 @@ def test_router_remove_interface(self):

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'))
3 changes: 2 additions & 1 deletion openstack_dashboard/test/settings.py
Expand Up @@ -83,7 +83,8 @@
}

OPENSTACK_NEUTRON_NETWORK = {
'enable_lb': True
'enable_lb': True,
'enable_quotas': False # Enabled in specific tests only
}

OPENSTACK_HYPERVISOR_FEATURES = {
Expand Down
27 changes: 26 additions & 1 deletion openstack_dashboard/test/test_data/neutron_data.py
Expand Up @@ -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
Expand All @@ -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()
Expand All @@ -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
Expand Down Expand Up @@ -448,3 +450,26 @@ def add_rule_to_group(secgroup, default_only=True):
'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)

0 comments on commit fde8890

Please sign in to comment.