Handle the case no SimpleTenantUsage Nova extension

When the SimpleTenantUsage Nova extension is not enabled,
panels that include usages produce an error and
the usage table is useless.

Three panels use the SimpleTenantUsage extension:
- openstack_dashboard.dashboards.admin.overview
- openstack_dashboard.dashboards.admin.projects
- openstack_dashboard.dashboards.project.overview

This fix avoids errors and does not show the usage
table in such a case.

A context variable 'simple_tenant_usage_enabled' is now
available in templates rendered by usage.UsageView subclasses.

Unit tests now mock nova 'extension_supported' API.

Change-Id: Ib306846bf6c947572ba0e7c773125d03b3dbf68b
Closes-Bug: #1211470
This commit is contained in:
Romain Hardouin
2013-09-30 16:12:12 +02:00
parent 9c08d883ea
commit d43e49ae46
7 changed files with 198 additions and 91 deletions

View File

@@ -17,6 +17,9 @@
</ul>
</div>
{% endif %}
{% include "horizon/common/_usage_summary.html" %}
{{ table.render }}
{% if simple_tenant_usage_enabled %}
{% include "horizon/common/_usage_summary.html" %}
{{ table.render }}
{% endif %}
{% endblock %}

View File

@@ -38,24 +38,44 @@ 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.neutron: ('is_extension_supported',),
api.network: ('tenant_floating_ip_list',
'security_group_list')})
def _stub_nova_api_calls(self, nova_stu_enabled):
self.mox.StubOutWithMock(api.nova, 'usage_list')
self.mox.StubOutWithMock(api.nova, 'tenant_absolute_limits')
self.mox.StubOutWithMock(api.nova, 'extension_supported')
self.mox.StubOutWithMock(api.keystone, 'tenant_list')
self.mox.StubOutWithMock(api.neutron, 'is_extension_supported')
self.mox.StubOutWithMock(api.network, 'tenant_floating_ip_list')
self.mox.StubOutWithMock(api.network, 'security_group_list')
api.nova.extension_supported(
'SimpleTenantUsage', IsA(http.HttpRequest)) \
.AndReturn(nova_stu_enabled)
def test_usage(self):
self._test_usage(nova_stu_enabled=True)
def test_usage_disabled(self):
self._test_usage(nova_stu_enabled=False)
def _test_usage(self, nova_stu_enabled=True):
self._stub_nova_api_calls(nova_stu_enabled)
api.nova.extension_supported(
'SimpleTenantUsage', IsA(http.HttpRequest)) \
.AndReturn(nova_stu_enabled)
now = timezone.now()
usage_obj = api.nova.NovaUsage(self.usages.first())
api.keystone.tenant_list(IsA(http.HttpRequest)) \
.AndReturn([self.tenants.list(), False])
api.nova.usage_list(IsA(http.HttpRequest),
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])
if nova_stu_enabled:
api.nova.usage_list(IsA(http.HttpRequest),
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.neutron.is_extension_supported(IsA(http.HttpRequest),
@@ -69,38 +89,50 @@ class UsageViewTests(test.BaseAdminViewTests):
res = self.client.get(reverse('horizon:admin:overview:index'))
self.assertTemplateUsed(res, 'admin/overview/usage.html')
self.assertTrue(isinstance(res.context['usage'], usage.GlobalUsage))
self.assertContains(res,
'<td class="sortable normal_column">test_tenant'
'</td>'
'<td class="sortable normal_column">%s</td>'
'<td class="sortable normal_column">%s</td>'
'<td class="sortable normal_column">%s</td>'
'<td class="sortable normal_column">%.2f</td>'
'<td class="sortable normal_column">%.2f</td>' %
(usage_obj.vcpus,
usage_obj.disk_gb_hours,
sizeformat.mbformat(usage_obj.memory_mb),
usage_obj.vcpu_hours,
usage_obj.total_local_gb_usage))
self.assertEqual(nova_stu_enabled,
res.context['simple_tenant_usage_enabled'])
usage_table = '<td class="sortable normal_column">test_tenant</td>' \
'<td class="sortable normal_column">%s</td>' \
'<td class="sortable normal_column">%s</td>' \
'<td class="sortable normal_column">%s</td>' \
'<td class="sortable normal_column">%.2f</td>' \
'<td class="sortable normal_column">%.2f</td>' % \
(usage_obj.vcpus,
usage_obj.disk_gb_hours,
sizeformat.mbformat(usage_obj.memory_mb),
usage_obj.vcpu_hours,
usage_obj.total_local_gb_usage)
if nova_stu_enabled:
self.assertContains(res, usage_table)
else:
self.assertNotContains(res, usage_table)
@test.create_stubs({api.nova: ('usage_list', 'tenant_absolute_limits', ),
api.keystone: ('tenant_list',),
api.neutron: ('is_extension_supported',),
api.network: ('tenant_floating_ip_list',
'security_group_list')})
def test_usage_csv(self):
self._test_usage_csv(nova_stu_enabled=True)
def test_usage_csv_disabled(self):
self._test_usage_csv(nova_stu_enabled=False)
def _test_usage_csv(self, nova_stu_enabled=True):
self._stub_nova_api_calls(nova_stu_enabled)
api.nova.extension_supported(
'SimpleTenantUsage', IsA(http.HttpRequest)) \
.AndReturn(nova_stu_enabled)
now = timezone.now()
usage_obj = [api.nova.NovaUsage(u) for u in self.usages.list()]
api.keystone.tenant_list(IsA(http.HttpRequest)) \
.AndReturn([self.tenants.list(), False])
api.nova.usage_list(IsA(http.HttpRequest),
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)
if nova_stu_enabled:
api.nova.usage_list(IsA(http.HttpRequest),
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.neutron.is_extension_supported(IsA(http.HttpRequest),
@@ -116,11 +148,13 @@ class UsageViewTests(test.BaseAdminViewTests):
self.assertTemplateUsed(res, 'admin/overview/usage.csv')
self.assertTrue(isinstance(res.context['usage'], usage.GlobalUsage))
hdr = 'Project Name,VCPUs,Ram (MB),Disk (GB),Usage (Hours)'
self.assertContains(res, '%s\r\n' % (hdr))
for obj in usage_obj:
row = u'{0},{1},{2},{3},{4:.2f}\r\n'.format(obj.project_name,
obj.vcpus,
obj.memory_mb,
obj.disk_gb_hours,
obj.vcpu_hours)
self.assertContains(res, row)
self.assertContains(res, '%s\r\n' % hdr)
if nova_stu_enabled:
for obj in usage_obj:
row = u'{0},{1},{2},{3},{4:.2f}\r\n'.format(obj.project_name,
obj.vcpus,
obj.memory_mb,
obj.disk_gb_hours,
obj.vcpu_hours)
self.assertContains(res, row)

View File

@@ -7,6 +7,8 @@
{% endblock %}
{% block main %}
{% include "horizon/common/_usage_summary.html" %}
{{ table.render }}
{% if simple_tenant_usage_enabled %}
{% include "horizon/common/_usage_summary.html" %}
{{ table.render }}
{% endif %}
{% endblock %}

View File

@@ -8,6 +8,9 @@
{% block main %}
{% include "horizon/common/_limit_summary.html" %}
{% include "horizon/common/_usage_summary.html" %}
{{ table.render }}
{% if simple_tenant_usage_enabled %}
{% include "horizon/common/_usage_summary.html" %}
{{ table.render }}
{% endif %}
{% endblock %}

View File

@@ -36,6 +36,15 @@ INDEX_URL = reverse('horizon:project:overview:index')
class UsageViewTests(test.TestCase):
def _stub_nova_api_calls(self, nova_stu_enabled=True):
self.mox.StubOutWithMock(api.nova, 'usage_get')
self.mox.StubOutWithMock(api.nova, 'tenant_absolute_limits')
self.mox.StubOutWithMock(api.nova, 'extension_supported')
api.nova.extension_supported(
'SimpleTenantUsage', IsA(http.HttpRequest)) \
.AndReturn(nova_stu_enabled)
def _stub_neutron_api_calls(self, neutron_sg_enabled=True):
self.mox.StubOutWithMock(api.neutron, 'is_extension_supported')
self.mox.StubOutWithMock(api.network, 'tenant_floating_ip_list')
@@ -51,20 +60,30 @@ class UsageViewTests(test.TestCase):
.AndReturn(self.q_secgroups.list())
def test_usage(self):
self._test_usage(nova_stu_enabled=True)
def test_usage_disabled(self):
self._test_usage(nova_stu_enabled=False)
def _test_usage(self, nova_stu_enabled):
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')
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)
self._stub_nova_api_calls(nova_stu_enabled)
api.nova.extension_supported(
'SimpleTenantUsage', IsA(http.HttpRequest)) \
.AndReturn(nova_stu_enabled)
if nova_stu_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'])
.AndReturn(self.limits['absolute'])
self._stub_neutron_api_calls()
self.mox.ReplayAll()
@@ -72,23 +91,38 @@ class UsageViewTests(test.TestCase):
usages = res.context['usage']
self.assertTemplateUsed(res, 'project/overview/usage.html')
self.assertTrue(isinstance(usages, usage.ProjectUsage))
self.assertContains(res, 'form-horizontal')
self.assertEqual(nova_stu_enabled,
res.context['simple_tenant_usage_enabled'])
if nova_stu_enabled:
self.assertContains(res, 'form-horizontal')
else:
self.assertNotContains(res, 'form-horizontal')
self.assertEqual(usages.limits['maxTotalFloatingIps'], float("inf"))
def test_usage_nova_network(self):
self._test_usage_nova_network(nova_stu_enabled=True)
def test_usage_nova_network_disabled(self):
self._test_usage_nova_network(nova_stu_enabled=False)
def _test_usage_nova_network(self, nova_stu_enabled):
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)
self._stub_nova_api_calls(nova_stu_enabled)
api.nova.extension_supported(
'SimpleTenantUsage', IsA(http.HttpRequest)) \
.AndReturn(nova_stu_enabled)
if nova_stu_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') \
@@ -99,14 +133,21 @@ class UsageViewTests(test.TestCase):
usages = res.context['usage']
self.assertTemplateUsed(res, 'project/overview/usage.html')
self.assertTrue(isinstance(usages, usage.ProjectUsage))
self.assertContains(res, 'form-horizontal')
self.assertEqual(nova_stu_enabled,
res.context['simple_tenant_usage_enabled'])
if nova_stu_enabled:
self.assertContains(res, 'form-horizontal')
else:
self.assertNotContains(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._stub_nova_api_calls()
api.nova.extension_supported(
'SimpleTenantUsage', IsA(http.HttpRequest)) \
.AndReturn(True)
api.nova.usage_get(IsA(http.HttpRequest), self.tenant.id,
datetime.datetime(now.year,
now.month,
@@ -127,15 +168,25 @@ class UsageViewTests(test.TestCase):
self.assertContains(res, 'Unauthorized:')
def test_usage_csv(self):
self._test_usage_csv(nova_stu_enabled=True)
def test_usage_csv_disabled(self):
self._test_usage_csv(nova_stu_enabled=False)
def _test_usage_csv(self, nova_stu_enabled=True):
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._stub_nova_api_calls(nova_stu_enabled)
api.nova.extension_supported(
'SimpleTenantUsage', IsA(http.HttpRequest)) \
.AndReturn(nova_stu_enabled)
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)
if nova_stu_enabled:
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'])
self._stub_neutron_api_calls()
@@ -147,10 +198,12 @@ class UsageViewTests(test.TestCase):
def test_usage_exception_usage(self):
now = timezone.now()
self.mox.StubOutWithMock(api.nova, 'usage_get')
self.mox.StubOutWithMock(api.nova, 'tenant_absolute_limits')
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)
self._stub_nova_api_calls()
api.nova.extension_supported(
'SimpleTenantUsage', IsA(http.HttpRequest)) \
.AndReturn(True)
api.nova.usage_get(IsA(http.HttpRequest),
self.tenant.id,
start, end).AndRaise(self.exceptions.nova)
@@ -166,8 +219,10 @@ class UsageViewTests(test.TestCase):
def test_usage_exception_quota(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._stub_nova_api_calls()
api.nova.extension_supported(
'SimpleTenantUsage', IsA(http.HttpRequest)) \
.AndReturn(True)
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),
@@ -185,8 +240,10 @@ class UsageViewTests(test.TestCase):
def test_usage_default_tenant(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._stub_nova_api_calls()
api.nova.extension_supported(
'SimpleTenantUsage', IsA(http.HttpRequest)) \
.AndReturn(True)
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),
@@ -212,8 +269,10 @@ class UsageViewTests(test.TestCase):
def _test_usage_with_neutron(self, neutron_sg_enabled=True):
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._stub_nova_api_calls()
api.nova.extension_supported(
'SimpleTenantUsage', IsA(http.HttpRequest)) \
.AndReturn(True)
self.mox.StubOutWithMock(api.neutron, 'tenant_quota_get')
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)

View File

@@ -181,6 +181,9 @@ class BaseUsage(object):
raise NotImplementedError("You must define a get_usage_list method.")
def summarize(self, start, end):
if not api.nova.extension_supported('SimpleTenantUsage', self.request):
return
if start <= end and start <= self.today:
# The API can't handle timezone aware datetime, so convert back
# to naive UTC just for this last step.

View File

@@ -1,4 +1,5 @@
from horizon import tables
from openstack_dashboard import api
from openstack_dashboard.usage import base
@@ -35,6 +36,8 @@ class UsageView(tables.DataTableView):
context['table'].kwargs['usage'] = self.usage
context['form'] = self.usage.form
context['usage'] = self.usage
context['simple_tenant_usage_enabled'] = \
api.nova.extension_supported('SimpleTenantUsage', self.request)
return context
def render_to_response(self, context, **response_kwargs):