switching to use limits instead of quotas

Fixes bug 1178694

Change-Id: I6fad061faf84079492338016fd2bada9a2e434f3
This commit is contained in:
Eric Peterson 2013-05-16 09:56:24 -06:00 committed by ericpeterson-l
parent 9254c32527
commit d4b0ab4aa3
18 changed files with 310 additions and 231 deletions

View File

@ -0,0 +1,39 @@
{% load i18n horizon humanize sizeformat %}
<div class="quota-dynamic">
<h3>{% trans "Limit Summary" %}</h3>
<div class="d3_quota_bar">
<div class="d3_pie_chart" data-used="{% widthratio usage.limits.totalInstancesUsed usage.limits.maxTotalInstances 100 %}"></div>
<strong>{% trans "Available Instances" %} <br />
{% blocktrans with used=usage.limits.totalInstancesUsed|intcomma available=usage.limits.maxTotalInstances|intcomma %}Used <span> {{ used }} </span> of <span> {{ available }} </span>{% endblocktrans %}
</strong>
</div>
<div class="d3_quota_bar">
<div class="d3_pie_chart" data-used="{% widthratio usage.limits.totalCoresUsed usage.limits.maxTotalCores 100 %}"></div>
<strong>{% trans "Available VCPUs" %} <br />
{% blocktrans with used=usage.limits.totalCoresUsed|intcomma available=usage.limits.maxTotalCores|intcomma %}Used <span> {{ used }} </span> of <span> {{ available }} </span>{% endblocktrans %}
</strong>
</div>
<div class="d3_quota_bar">
<div class="d3_pie_chart" data-used="{% widthratio usage.limits.totalRAMUsed usage.limits.maxTotalRAMSize 100 %}"></div>
<strong>{% trans "Available RAM" %} <br />
{% blocktrans with used=usage.limits.totalRAMUsed|intcomma available=usage.limits.maxTotalRAMSize|intcomma %}Used <span> {{ used }} MB </span> of <span> {{ available }} MB </span>{% endblocktrans %}
</strong>
</div>
<div class="d3_quota_bar">
<div class="d3_pie_chart" data-used="{% widthratio usage.limits.totalFloatingIpsUsed usage.limits.maxTotalFloatingIps 100 %}"></div>
<strong>{% trans "Available Floating IPs" %} <br />
{% blocktrans with used=usage.limits.totalFloatingIpsUsed|intcomma available=usage.limits.maxTotalFloatingIps|intcomma %}Used <span> {{ used }} </span> of <span> {{ available }} </span>{% endblocktrans %}
</strong>
</div>
<div class="d3_quota_bar">
<div class="d3_pie_chart" data-used="{% widthratio usage.limits.totalSecurityGroupsUsed usage.limits.maxSecurityGroups 100 %}"></div>
<strong>{% trans "Available Security Groups" %} <br />
{% blocktrans with used=usage.limits.totalSecurityGroupsUsed|intcomma available=usage.limits.maxSecurityGroups|intcomma%}Used <span> {{ used }} </span> of <span> {{ available }} </span>{% endblocktrans %}
</strong>
</div>
</div>

View File

@ -1,46 +0,0 @@
{% load i18n horizon humanize sizeformat %}
<div class="quota-dynamic">
<h3>{% trans "Quota Summary" %}</h3>
<div class="d3_quota_bar">
<div class="d3_pie_chart" data-used="{% widthratio usage.quotas.instances.used usage.quotas.instances.quota 100 %}"></div>
<strong>{% trans "Available Instances" %} <br />
{% blocktrans with used=usage.quotas.instances.used|intcomma available=usage.quotas.instances.quota|intcomma %}Used <span> {{ used }} </span> of <span> {{ available }} </span>{% endblocktrans %}
</strong>
</div>
<div class="d3_quota_bar">
<div class="d3_pie_chart" data-used="{% widthratio usage.quotas.cores.used usage.quotas.cores.quota 100 %}"></div>
<strong>{% trans "Available VCPUs" %} <br />
{% blocktrans with used=usage.quotas.cores.used|intcomma available=usage.quotas.cores.quota|intcomma %}Used <span> {{ used }} </span> of <span> {{ available }} </span>{% endblocktrans %}
</strong>
</div>
<div class="d3_quota_bar">
<div class="d3_pie_chart" data-used="{% widthratio usage.quotas.ram.used usage.quotas.ram.quota 100 %}"></div>
<strong>{% trans "Available RAM" %} <br />
{% blocktrans with used=usage.quotas.ram.used|intcomma available=usage.quotas.ram.quota|intcomma %}Used <span> {{ used }} MB </span> of <span> {{ available }} MB </span>{% endblocktrans %}
</strong>
</div>
{% if usage.quotas.volumes %}
<div class="d3_quota_bar">
<div class="d3_pie_chart" data-used="{% widthratio usage.quotas.volumes.used usage.quotas.volumes.quota 100 %}"></div>
<strong>{% trans "Available Volumes" %} <br />
{% blocktrans with used=usage.quotas.volumes.used|intcomma available=usage.quotas.volumes.quota|intcomma %} Used <span> {{ used }} </span> of <span> {{ available }} </span>{% endblocktrans %}
</strong>
</div>
<div class="d3_quota_bar">
<div class="d3_pie_chart" data-used="{% widthratio usage.quotas.snapshots.used usage.quotas.snapshots.quota 100 %}"></div>
<strong>{% trans "Available Snapshots" %} <br />
{% blocktrans with used=usage.quotas.snapshots.used|intcomma available=usage.quotas.snapshots.quota|intcomma %} Used <span> {{ used }} </span> of <span> {{ available }} </span>{% endblocktrans %}
</strong>
</div>
<div class="d3_quota_bar">
<div class="d3_pie_chart" data-used="{% widthratio usage.quotas.gigabytes.used usage.quotas.gigabytes.quota 100 %}"></div>
<strong>{% trans "Available Volume Storage" %} <br />
{% blocktrans with used=usage.quotas.gigabytes.used|intcomma available=usage.quotas.gigabytes.quota|intcomma%}Used <span> {{ used }} GB </span> of <span> {{ available }} GB</span>{% endblocktrans %}
</strong>
</div>
{% endif %}
</div>

View File

@ -144,3 +144,15 @@ def volume_type_create(request, name):
def volume_type_delete(request, volume_type_id): def volume_type_delete(request, volume_type_id):
return cinderclient(request).volume_types.delete(volume_type_id) return cinderclient(request).volume_types.delete(volume_type_id)
def tenant_absolute_limits(request):
limits = cinderclient(request).limits.get().absolute
limits_dict = {}
for limit in limits:
# -1 is used to represent unlimited quotas
if limit.value == -1:
limits_dict[limit.name] = float("inf")
else:
limits_dict[limit.name] = limit.value
return limits_dict

View File

@ -31,27 +31,25 @@ from horizon.templatetags.sizeformat import mbformat
from openstack_dashboard import api from openstack_dashboard import api
from openstack_dashboard import usage from openstack_dashboard import usage
from openstack_dashboard.test import helpers as test from openstack_dashboard.test import helpers as test
from openstack_dashboard.usage import quotas
INDEX_URL = reverse('horizon:project:overview:index') INDEX_URL = reverse('horizon:project:overview:index')
class UsageViewTests(test.BaseAdminViewTests): class UsageViewTests(test.BaseAdminViewTests):
@test.create_stubs({api.nova: ('usage_list',), @test.create_stubs({api.nova: ('usage_list', 'tenant_absolute_limits', ),
quotas: ('tenant_quota_usages',),
api.keystone: ('tenant_list',)}) api.keystone: ('tenant_list',)})
def test_usage(self): def test_usage(self):
now = timezone.now() now = timezone.now()
usage_obj = api.nova.NovaUsage(self.usages.first()) usage_obj = api.nova.NovaUsage(self.usages.first())
quota_data = self.quota_usages.first()
api.keystone.tenant_list(IsA(http.HttpRequest)) \ api.keystone.tenant_list(IsA(http.HttpRequest)) \
.AndReturn(self.tenants.list()) .AndReturn(self.tenants.list())
api.nova.usage_list(IsA(http.HttpRequest), api.nova.usage_list(IsA(http.HttpRequest),
datetime.datetime(now.year, now.month, 1, 0, 0, 0), datetime.datetime(now.year, now.month, 1, 0, 0, 0),
Func(usage.almost_now)) \ Func(usage.almost_now)) \
.AndReturn([usage_obj]) .AndReturn([usage_obj])
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(quota_data) api.nova.tenant_absolute_limits(IsA(http.HttpRequest))\
.AndReturn(self.limits['absolute'])
self.mox.ReplayAll() self.mox.ReplayAll()
res = self.client.get(reverse('horizon:admin:overview:index')) res = self.client.get(reverse('horizon:admin:overview:index'))
self.assertTemplateUsed(res, 'admin/overview/usage.html') self.assertTemplateUsed(res, 'admin/overview/usage.html')
@ -70,20 +68,19 @@ class UsageViewTests(test.BaseAdminViewTests):
usage_obj.vcpu_hours, usage_obj.vcpu_hours,
usage_obj.total_local_gb_usage)) usage_obj.total_local_gb_usage))
@test.create_stubs({api.nova: ('usage_list',), @test.create_stubs({api.nova: ('usage_list', 'tenant_absolute_limits', ),
quotas: ('tenant_quota_usages',),
api.keystone: ('tenant_list',)}) api.keystone: ('tenant_list',)})
def test_usage_csv(self): def test_usage_csv(self):
now = timezone.now() now = timezone.now()
usage_obj = api.nova.NovaUsage(self.usages.first()) usage_obj = api.nova.NovaUsage(self.usages.first())
quota_data = self.quota_usages.first()
api.keystone.tenant_list(IsA(http.HttpRequest)) \ api.keystone.tenant_list(IsA(http.HttpRequest)) \
.AndReturn(self.tenants.list()) .AndReturn(self.tenants.list())
api.nova.usage_list(IsA(http.HttpRequest), api.nova.usage_list(IsA(http.HttpRequest),
datetime.datetime(now.year, now.month, 1, 0, 0, 0), datetime.datetime(now.year, now.month, 1, 0, 0, 0),
Func(usage.almost_now)) \ Func(usage.almost_now)) \
.AndReturn([usage_obj, usage_obj]) .AndReturn([usage_obj, usage_obj])
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(quota_data) api.nova.tenant_absolute_limits(IsA(http.HttpRequest))\
.AndReturn(self.limits['absolute'])
self.mox.ReplayAll() self.mox.ReplayAll()
csv_url = reverse('horizon:admin:overview:index') + "?format=csv" csv_url = reverse('horizon:admin:overview:index') + "?format=csv"
res = self.client.get(csv_url) res = self.client.get(csv_url)

View File

@ -16,26 +16,26 @@
</table> </table>
<div class="quota-dynamic"> <div class="quota-dynamic">
<h4>{% trans "Project Quotas" %}</h4> <h4>{% trans "Project Limits" %}</h4>
<div class="quota_title clearfix"> <div class="quota_title clearfix">
<strong>{% trans "Number of Instances" %}</strong> <strong>{% trans "Number of Instances" %}</strong>
{% blocktrans with used=usages.instances.used|intcomma quota=usages.instances.quota|intcomma %}<p>{{ used }} of {{ quota }} Used</p>{% endblocktrans %} {% blocktrans with used=usages.totalInstancesUsed|intcomma quota=usages.maxTotalInstances|intcomma %}<p>{{ used }} of {{ quota }} Used</p>{% endblocktrans %}
</div> </div>
<div id="quota_instances" class="quota_bar" data-progress-indicator-flavor data-quota-limit="{{ usages.instances.quota }}" data-quota-used="{{ usages.instances.used }}"> <div id="quota_instances" class="quota_bar" data-progress-indicator-flavor data-quota-limit="{{ usages.maxTotalInstances }}" data-quota-used="{{ usages.totalInstancesUsed }}">
</div> </div>
<div class="quota_title clearfix"> <div class="quota_title clearfix">
<strong>{% trans "Number of VCPUs" %}</strong> <strong>{% trans "Number of VCPUs" %}</strong>
{% blocktrans with used=usages.cores.used|intcomma quota=usages.cores.quota|intcomma %}<p>{{ used }} of {{ quota }} Used</p>{% endblocktrans %} {% blocktrans with used=usages.totalCoresUsed|intcomma quota=usages.maxTotalCores|intcomma %}<p>{{ used }} of {{ quota }} Used</p>{% endblocktrans %}
</div> </div>
<div id="quota_vcpus" class="quota_bar" data-progress-indicator-flavor data-quota-limit="{{ usages.cores.quota }}" data-quota-used="{{ usages.cores.used }}"> <div id="quota_vcpus" class="quota_bar" data-progress-indicator-flavor data-quota-limit="{{ usages.maxTotalCores }}" data-quota-used="{{ usages.totalCoresUsed }}">
</div> </div>
<div class="quota_title clearfix"> <div class="quota_title clearfix">
<strong>{% trans "Total RAM" %}</strong> <strong>{% trans "Total RAM" %}</strong>
{% blocktrans with used=usages.ram.used|intcomma quota=usages.ram.quota|intcomma %}<p>{{ used }} of {{ quota }} MB Used</p>{% endblocktrans %} {% blocktrans with used=usages.totalRAMUsed|intcomma quota=usages.maxTotalRAMSize|intcomma %}<p>{{ used }} of {{ quota }} MB Used</p>{% endblocktrans %}
</div> </div>
<div id="quota_ram" data-progress-indicator-flavor data-quota-limit="{{ usages.ram.quota }}" data-quota-used="{{ usages.ram.used }}" class="quota_bar"> <div id="quota_ram" data-progress-indicator-flavor data-quota-limit="{{ usages.maxTotalRAMSize }}" data-quota-used="{{ usages.totalRAMUsed }}" class="quota_bar">
</div> </div>
</div> </div>

View File

@ -30,7 +30,6 @@ from mox import IsA, IgnoreArg
from openstack_dashboard import api from openstack_dashboard import api
from openstack_dashboard.api import cinder from openstack_dashboard.api import cinder
from openstack_dashboard.test import helpers as test from openstack_dashboard.test import helpers as test
from openstack_dashboard.usage import quotas
from .tables import LaunchLink from .tables import LaunchLink
from .tabs import InstanceDetailTabs from .tabs import InstanceDetailTabs
from .workflows import LaunchInstance from .workflows import LaunchInstance
@ -798,14 +797,13 @@ class InstanceTests(test.TestCase):
@test.create_stubs({api.nova: ('flavor_list', @test.create_stubs({api.nova: ('flavor_list',
'keypair_list', 'keypair_list',
'security_group_list', 'security_group_list',
'tenant_absolute_limits',
'availability_zone_list',), 'availability_zone_list',),
cinder: ('volume_snapshot_list', cinder: ('volume_snapshot_list',
'volume_list',), 'volume_list',),
quotas: ('tenant_quota_usages',),
api.quantum: ('network_list',), api.quantum: ('network_list',),
api.glance: ('image_list_detailed',)}) api.glance: ('image_list_detailed',)})
def test_launch_instance_get(self): def test_launch_instance_get(self):
quota_usages = self.quota_usages.first()
image = self.images.first() image = self.images.first()
cinder.volume_list(IsA(http.HttpRequest)) \ cinder.volume_list(IsA(http.HttpRequest)) \
@ -827,8 +825,8 @@ class InstanceTests(test.TestCase):
api.quantum.network_list(IsA(http.HttpRequest), api.quantum.network_list(IsA(http.HttpRequest),
shared=True) \ shared=True) \
.AndReturn(self.networks.list()[1:]) .AndReturn(self.networks.list()[1:])
quotas.tenant_quota_usages(IsA(http.HttpRequest)) \ api.nova.tenant_absolute_limits(IsA(http.HttpRequest))\
.AndReturn(quota_usages) .AndReturn(self.limits['absolute'])
api.nova.flavor_list(IsA(http.HttpRequest)) \ api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list()) .AndReturn(self.flavors.list())
api.nova.flavor_list(IsA(http.HttpRequest)) \ api.nova.flavor_list(IsA(http.HttpRequest)) \
@ -861,7 +859,6 @@ class InstanceTests(test.TestCase):
@test.create_stubs({api.glance: ('image_list_detailed',), @test.create_stubs({api.glance: ('image_list_detailed',),
api.quantum: ('network_list',), api.quantum: ('network_list',),
quotas: ('tenant_quota_usages',),
api.nova: ('flavor_list', api.nova: ('flavor_list',
'keypair_list', 'keypair_list',
'security_group_list', 'security_group_list',
@ -941,8 +938,8 @@ class InstanceTests(test.TestCase):
@test.create_stubs({api.glance: ('image_list_detailed',), @test.create_stubs({api.glance: ('image_list_detailed',),
api.quantum: ('network_list',), api.quantum: ('network_list',),
quotas: ('tenant_quota_usages',),
api.nova: ('flavor_list', api.nova: ('flavor_list',
'tenant_absolute_limits',
'keypair_list', 'keypair_list',
'security_group_list', 'security_group_list',
'availability_zone_list', 'availability_zone_list',
@ -963,7 +960,8 @@ class InstanceTests(test.TestCase):
api.nova.flavor_list(IsA(http.HttpRequest)) \ api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list()) .AndReturn(self.flavors.list())
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn({}) api.nova.tenant_absolute_limits(IsA(http.HttpRequest)) \
.AndReturn(self.limits['absolute'])
api.glance.image_list_detailed(IsA(http.HttpRequest), api.glance.image_list_detailed(IsA(http.HttpRequest),
filters={'is_public': True, filters={'is_public': True,
'status': 'active'}) \ 'status': 'active'}) \
@ -1021,7 +1019,6 @@ class InstanceTests(test.TestCase):
@test.create_stubs({api.glance: ('image_list_detailed',), @test.create_stubs({api.glance: ('image_list_detailed',),
api.quantum: ('network_list',), api.quantum: ('network_list',),
quotas: ('tenant_quota_usages',),
api.nova: ('flavor_list', api.nova: ('flavor_list',
'keypair_list', 'keypair_list',
'security_group_list', 'security_group_list',
@ -1105,7 +1102,6 @@ class InstanceTests(test.TestCase):
@test.create_stubs({api.glance: ('image_list_detailed',), @test.create_stubs({api.glance: ('image_list_detailed',),
api.quantum: ('network_list',), api.quantum: ('network_list',),
quotas: ('tenant_quota_usages',),
api.nova: ('server_create', api.nova: ('server_create',
'flavor_list', 'flavor_list',
'keypair_list', 'keypair_list',
@ -1191,11 +1187,11 @@ class InstanceTests(test.TestCase):
@test.create_stubs({api.glance: ('image_list_detailed',), @test.create_stubs({api.glance: ('image_list_detailed',),
api.quantum: ('network_list',), api.quantum: ('network_list',),
quotas: ('tenant_quota_usages',),
api.nova: ('flavor_list', api.nova: ('flavor_list',
'keypair_list', 'keypair_list',
'availability_zone_list', 'availability_zone_list',
'security_group_list',), 'security_group_list',
'tenant_absolute_limits',),
cinder: ('volume_list', cinder: ('volume_list',
'volume_snapshot_list',)}) 'volume_snapshot_list',)})
def test_launch_instance_post_no_images_available(self): def test_launch_instance_post_no_images_available(self):
@ -1208,7 +1204,8 @@ class InstanceTests(test.TestCase):
api.nova.flavor_list(IsA(http.HttpRequest)) \ api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list()) .AndReturn(self.flavors.list())
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn({}) api.nova.tenant_absolute_limits(IsA(http.HttpRequest)) \
.AndReturn(self.limits['absolute'])
api.glance.image_list_detailed(IsA(http.HttpRequest), api.glance.image_list_detailed(IsA(http.HttpRequest),
filters={'is_public': True, filters={'is_public': True,
'status': 'active'}) \ 'status': 'active'}) \
@ -1262,12 +1259,12 @@ class InstanceTests(test.TestCase):
@test.create_stubs({api.glance: ('image_list_detailed',), @test.create_stubs({api.glance: ('image_list_detailed',),
api.quantum: ('network_list',), api.quantum: ('network_list',),
quotas: ('tenant_quota_usages',),
cinder: ('volume_list', cinder: ('volume_list',
'volume_snapshot_list',), 'volume_snapshot_list',),
api.nova: ('flavor_list', api.nova: ('flavor_list',
'keypair_list', 'keypair_list',
'security_group_list', 'security_group_list',
'tenant_absolute_limits',
'availability_zone_list',)}) 'availability_zone_list',)})
def test_launch_flavorlist_error(self): def test_launch_flavorlist_error(self):
cinder.volume_list(IsA(http.HttpRequest)) \ cinder.volume_list(IsA(http.HttpRequest)) \
@ -1289,8 +1286,8 @@ class InstanceTests(test.TestCase):
api.quantum.network_list(IsA(http.HttpRequest), api.quantum.network_list(IsA(http.HttpRequest),
shared=True) \ shared=True) \
.AndReturn(self.networks.list()[1:]) .AndReturn(self.networks.list()[1:])
quotas.tenant_quota_usages(IsA(http.HttpRequest)) \ api.nova.tenant_absolute_limits(IsA(http.HttpRequest)) \
.AndReturn(self.quota_usages.first()) .AndReturn(self.limits['absolute'])
api.nova.flavor_list(IsA(http.HttpRequest)) \ api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndRaise(self.exceptions.nova) .AndRaise(self.exceptions.nova)
api.nova.flavor_list(IsA(http.HttpRequest)) \ api.nova.flavor_list(IsA(http.HttpRequest)) \
@ -1390,10 +1387,10 @@ class InstanceTests(test.TestCase):
@test.create_stubs({api.glance: ('image_list_detailed',), @test.create_stubs({api.glance: ('image_list_detailed',),
api.quantum: ('network_list',), api.quantum: ('network_list',),
quotas: ('tenant_quota_usages',),
api.nova: ('flavor_list', api.nova: ('flavor_list',
'keypair_list', 'keypair_list',
'security_group_list', 'security_group_list',
'tenant_absolute_limits',
'availability_zone_list',), 'availability_zone_list',),
cinder: ('volume_list', cinder: ('volume_list',
'volume_snapshot_list',)}) 'volume_snapshot_list',)})
@ -1438,8 +1435,8 @@ class InstanceTests(test.TestCase):
api.nova.flavor_list(IsA(http.HttpRequest)) \ api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list()) .AndReturn(self.flavors.list())
quotas.tenant_quota_usages(IsA(http.HttpRequest)) \ api.nova.tenant_absolute_limits(IsA(http.HttpRequest)) \
.AndReturn(self.quota_usages.first()) .AndReturn(self.limits['absolute'])
self.mox.ReplayAll() self.mox.ReplayAll()
@ -1514,15 +1511,14 @@ class InstanceTests(test.TestCase):
@test.create_stubs({api.nova: ('flavor_list', @test.create_stubs({api.nova: ('flavor_list',
'keypair_list', 'keypair_list',
'security_group_list', 'security_group_list',
'availability_zone_list',), 'availability_zone_list',
'tenant_absolute_limits',),
cinder: ('volume_snapshot_list', cinder: ('volume_snapshot_list',
'volume_list',), 'volume_list',),
quotas: ('tenant_quota_usages',),
api.quantum: ('network_list',), api.quantum: ('network_list',),
api.glance: ('image_list_detailed',)}) api.glance: ('image_list_detailed',)})
def test_select_default_keypair_if_only_one(self): def test_select_default_keypair_if_only_one(self):
keypair = self.keypairs.first() keypair = self.keypairs.first()
quota_usages = self.quota_usages.first()
image = self.images.first() image = self.images.first()
cinder.volume_list(IsA(http.HttpRequest)) \ cinder.volume_list(IsA(http.HttpRequest)) \
@ -1544,8 +1540,8 @@ class InstanceTests(test.TestCase):
api.quantum.network_list(IsA(http.HttpRequest), api.quantum.network_list(IsA(http.HttpRequest),
shared=True) \ shared=True) \
.AndReturn(self.networks.list()[1:]) .AndReturn(self.networks.list()[1:])
quotas.tenant_quota_usages(IsA(http.HttpRequest)) \ api.nova.tenant_absolute_limits(IsA(http.HttpRequest)) \
.AndReturn(quota_usages) .AndReturn(self.limits['absolute'])
api.nova.flavor_list(IsA(http.HttpRequest)) \ api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list()) .AndReturn(self.flavors.list())
api.nova.flavor_list(IsA(http.HttpRequest)) \ api.nova.flavor_list(IsA(http.HttpRequest)) \
@ -1627,19 +1623,18 @@ class InstanceTests(test.TestCase):
self.assertRedirectsNoFollow(res, INDEX_URL) self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api.nova: ('server_get', @test.create_stubs({api.nova: ('server_get',
'flavor_list',), 'flavor_list',
quotas: ('tenant_quota_usages',)}) 'tenant_absolute_limits')})
def test_instance_resize_get(self): def test_instance_resize_get(self):
server = self.servers.first() server = self.servers.first()
api.nova.server_get(IsA(http.HttpRequest), server.id) \ api.nova.server_get(IsA(http.HttpRequest), server.id) \
.AndReturn(server) .AndReturn(server)
api.nova.flavor_list(IsA(http.HttpRequest)) \ api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list()) .AndReturn(self.flavors.list())
api.nova.flavor_list(IsA(http.HttpRequest)) \ api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list()) .AndReturn(self.flavors.list())
quotas.tenant_quota_usages(IsA(http.HttpRequest)) \ api.nova.tenant_absolute_limits(IsA(http.HttpRequest)) \
.AndReturn(self.quota_usages.first()) .AndReturn(self.limits['absolute'])
self.mox.ReplayAll() self.mox.ReplayAll()

View File

@ -32,7 +32,6 @@ from horizon.utils import validators
from openstack_dashboard import api from openstack_dashboard import api
from openstack_dashboard.api import cinder from openstack_dashboard.api import cinder
from openstack_dashboard.usage import quotas
from ...images_and_snapshots.utils import get_available_images from ...images_and_snapshots.utils import get_available_images
@ -293,7 +292,7 @@ class SetInstanceDetailsAction(workflows.Action):
def get_help_text(self): def get_help_text(self):
extra = {} extra = {}
try: try:
extra['usages'] = quotas.tenant_quota_usages(self.request) extra['usages'] = api.nova.tenant_absolute_limits(self.request)
extra['usages_json'] = json.dumps(extra['usages']) extra['usages_json'] = json.dumps(extra['usages'])
flavors = json.dumps([f._info for f in flavors = json.dumps([f._info for f in
api.nova.flavor_list(self.request)]) api.nova.flavor_list(self.request)])

View File

@ -27,7 +27,6 @@ from horizon import workflows
from horizon import forms from horizon import forms
from openstack_dashboard import api from openstack_dashboard import api
from openstack_dashboard.usage import quotas
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -72,7 +71,7 @@ class SetFlavorChoiceAction(workflows.Action):
def get_help_text(self): def get_help_text(self):
extra = {} extra = {}
try: try:
extra['usages'] = quotas.tenant_quota_usages(self.request) extra['usages'] = api.nova.tenant_absolute_limits(self.request)
extra['usages_json'] = json.dumps(extra['usages']) extra['usages_json'] = json.dumps(extra['usages'])
flavors = json.dumps([f._info for f in flavors = json.dumps([f._info for f in
api.nova.flavor_list(self.request)]) api.nova.flavor_list(self.request)])

View File

@ -7,7 +7,7 @@
{% endblock page_header %} {% endblock page_header %}
{% block main %} {% block main %}
{% include "horizon/common/_quota_summary.html" %} {% include "horizon/common/_limit_summary.html" %}
{% include "horizon/common/_usage_summary.html" %} {% include "horizon/common/_usage_summary.html" %}
{{ table.render }} {{ table.render }}
{% endblock %} {% endblock %}

View File

@ -29,7 +29,6 @@ from mox import IsA, Func
from openstack_dashboard import api from openstack_dashboard import api
from openstack_dashboard import usage from openstack_dashboard import usage
from openstack_dashboard.test import helpers as test from openstack_dashboard.test import helpers as test
from openstack_dashboard.usage import quotas
INDEX_URL = reverse('horizon:project:overview:index') INDEX_URL = reverse('horizon:project:overview:index')
@ -39,14 +38,14 @@ class UsageViewTests(test.TestCase):
def test_usage(self): def test_usage(self):
now = timezone.now() now = timezone.now()
usage_obj = api.nova.NovaUsage(self.usages.first()) usage_obj = api.nova.NovaUsage(self.usages.first())
quota_data = self.quota_usages.first()
self.mox.StubOutWithMock(api.nova, 'usage_get') self.mox.StubOutWithMock(api.nova, 'usage_get')
self.mox.StubOutWithMock(quotas, 'tenant_quota_usages') self.mox.StubOutWithMock(api.nova, 'tenant_absolute_limits')
api.nova.usage_get(IsA(http.HttpRequest), self.tenant.id, api.nova.usage_get(IsA(http.HttpRequest), self.tenant.id,
datetime.datetime(now.year, now.month, 1, 0, 0, 0), datetime.datetime(now.year, now.month, 1, 0, 0, 0),
Func(usage.almost_now)) \ Func(usage.almost_now)) \
.AndReturn(usage_obj) .AndReturn(usage_obj)
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(quota_data) api.nova.tenant_absolute_limits(IsA(http.HttpRequest))\
.AndReturn(self.limits['absolute'])
self.mox.ReplayAll() self.mox.ReplayAll()
res = self.client.get(reverse('horizon:project:overview:index')) res = self.client.get(reverse('horizon:project:overview:index'))
@ -57,14 +56,14 @@ class UsageViewTests(test.TestCase):
def test_unauthorized(self): def test_unauthorized(self):
exc = self.exceptions.nova_unauthorized exc = self.exceptions.nova_unauthorized
now = timezone.now() now = timezone.now()
quota_data = self.quota_usages.first()
self.mox.StubOutWithMock(api.nova, 'usage_get') self.mox.StubOutWithMock(api.nova, 'usage_get')
self.mox.StubOutWithMock(quotas, 'tenant_quota_usages') self.mox.StubOutWithMock(api.nova, 'tenant_absolute_limits')
api.nova.usage_get(IsA(http.HttpRequest), self.tenant.id, api.nova.usage_get(IsA(http.HttpRequest), self.tenant.id,
datetime.datetime(now.year, now.month, 1, 0, 0, 0), datetime.datetime(now.year, now.month, 1, 0, 0, 0),
Func(usage.almost_now)) \ Func(usage.almost_now)) \
.AndRaise(exc) .AndRaise(exc)
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(quota_data) api.nova.tenant_absolute_limits(IsA(http.HttpRequest))\
.AndReturn(self.limits['absolute'])
self.mox.ReplayAll() self.mox.ReplayAll()
url = reverse('horizon:project:overview:index') url = reverse('horizon:project:overview:index')
@ -76,16 +75,16 @@ class UsageViewTests(test.TestCase):
def test_usage_csv(self): def test_usage_csv(self):
now = timezone.now() now = timezone.now()
usage_obj = api.nova.NovaUsage(self.usages.first()) usage_obj = api.nova.NovaUsage(self.usages.first())
quota_data = self.quota_usages.first()
self.mox.StubOutWithMock(api.nova, 'usage_get') self.mox.StubOutWithMock(api.nova, 'usage_get')
self.mox.StubOutWithMock(quotas, 'tenant_quota_usages') self.mox.StubOutWithMock(api.nova, 'tenant_absolute_limits')
timestamp = datetime.datetime(now.year, now.month, 1, 0, 0, 0) timestamp = datetime.datetime(now.year, now.month, 1, 0, 0, 0)
api.nova.usage_get(IsA(http.HttpRequest), api.nova.usage_get(IsA(http.HttpRequest),
self.tenant.id, self.tenant.id,
timestamp, timestamp,
Func(usage.almost_now)) \ Func(usage.almost_now)) \
.AndReturn(usage_obj) .AndReturn(usage_obj)
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(quota_data) api.nova.tenant_absolute_limits(IsA(http.HttpRequest))\
.AndReturn(self.limits['absolute'])
self.mox.ReplayAll() self.mox.ReplayAll()
res = self.client.get(reverse('horizon:project:overview:index') + res = self.client.get(reverse('horizon:project:overview:index') +
@ -95,16 +94,16 @@ class UsageViewTests(test.TestCase):
def test_usage_exception_usage(self): def test_usage_exception_usage(self):
now = timezone.now() now = timezone.now()
quota_data = self.quota_usages.first()
self.mox.StubOutWithMock(api.nova, 'usage_get') self.mox.StubOutWithMock(api.nova, 'usage_get')
self.mox.StubOutWithMock(quotas, 'tenant_quota_usages') self.mox.StubOutWithMock(api.nova, 'tenant_absolute_limits')
timestamp = datetime.datetime(now.year, now.month, 1, 0, 0, 0) timestamp = datetime.datetime(now.year, now.month, 1, 0, 0, 0)
api.nova.usage_get(IsA(http.HttpRequest), api.nova.usage_get(IsA(http.HttpRequest),
self.tenant.id, self.tenant.id,
timestamp, timestamp,
Func(usage.almost_now)) \ Func(usage.almost_now)) \
.AndRaise(self.exceptions.nova) .AndRaise(self.exceptions.nova)
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(quota_data) api.nova.tenant_absolute_limits(IsA(http.HttpRequest))\
.AndReturn(self.limits['absolute'])
self.mox.ReplayAll() self.mox.ReplayAll()
res = self.client.get(reverse('horizon:project:overview:index')) res = self.client.get(reverse('horizon:project:overview:index'))
@ -115,15 +114,15 @@ class UsageViewTests(test.TestCase):
now = timezone.now() now = timezone.now()
usage_obj = api.nova.NovaUsage(self.usages.first()) usage_obj = api.nova.NovaUsage(self.usages.first())
self.mox.StubOutWithMock(api.nova, 'usage_get') self.mox.StubOutWithMock(api.nova, 'usage_get')
self.mox.StubOutWithMock(quotas, 'tenant_quota_usages') self.mox.StubOutWithMock(api.nova, 'tenant_absolute_limits')
timestamp = datetime.datetime(now.year, now.month, 1, 0, 0, 0) timestamp = datetime.datetime(now.year, now.month, 1, 0, 0, 0)
api.nova.usage_get(IsA(http.HttpRequest), api.nova.usage_get(IsA(http.HttpRequest),
self.tenant.id, self.tenant.id,
timestamp, timestamp,
Func(usage.almost_now)) \ Func(usage.almost_now)) \
.AndReturn(usage_obj) .AndReturn(usage_obj)
quotas.tenant_quota_usages(IsA(http.HttpRequest))\ api.nova.tenant_absolute_limits(IsA(http.HttpRequest))\
.AndRaise(self.exceptions.nova) .AndRaise(self.exceptions.nova)
self.mox.ReplayAll() self.mox.ReplayAll()
res = self.client.get(reverse('horizon:project:overview:index')) res = self.client.get(reverse('horizon:project:overview:index'))
@ -133,16 +132,16 @@ class UsageViewTests(test.TestCase):
def test_usage_default_tenant(self): def test_usage_default_tenant(self):
now = timezone.now() now = timezone.now()
usage_obj = api.nova.NovaUsage(self.usages.first()) usage_obj = api.nova.NovaUsage(self.usages.first())
quota_data = self.quota_usages.first()
self.mox.StubOutWithMock(api.nova, 'usage_get') self.mox.StubOutWithMock(api.nova, 'usage_get')
self.mox.StubOutWithMock(quotas, 'tenant_quota_usages') self.mox.StubOutWithMock(api.nova, 'tenant_absolute_limits')
timestamp = datetime.datetime(now.year, now.month, 1, 0, 0, 0) timestamp = datetime.datetime(now.year, now.month, 1, 0, 0, 0)
api.nova.usage_get(IsA(http.HttpRequest), api.nova.usage_get(IsA(http.HttpRequest),
self.tenant.id, self.tenant.id,
timestamp, timestamp,
Func(usage.almost_now)) \ Func(usage.almost_now)) \
.AndReturn(usage_obj) .AndReturn(usage_obj)
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(quota_data) api.nova.tenant_absolute_limits(IsA(http.HttpRequest))\
.AndReturn(self.limits['absolute'])
self.mox.ReplayAll() self.mox.ReplayAll()
res = self.client.get(reverse('horizon:project:overview:index')) res = self.client.get(reverse('horizon:project:overview:index'))

View File

@ -164,7 +164,15 @@ class CreateForm(forms.SelfHandlingForm):
# error message when the quota is exceeded when trying to create # error message when the quota is exceeded when trying to create
# a volume, so we need to check for that scenario here before we # a volume, so we need to check for that scenario here before we
# send it off to try and create. # send it off to try and create.
usages = quotas.tenant_quota_usages(request) usages = cinder.tenant_absolute_limits(self.request)
volumes = cinder.volume_list(self.request)
total_size = sum([getattr(volume, 'size', 0) for volume
in volumes])
usages['gigabytesUsed'] = total_size
usages['volumesUsed'] = len(volumes)
availableGB = usages['maxTotalVolumeGigabytes'] -\
usages['gigabytesUsed']
availableVol = usages['maxTotalVolumes'] - usages['volumesUsed']
snapshot_id = None snapshot_id = None
image_id = None image_id = None
@ -196,14 +204,14 @@ class CreateForm(forms.SelfHandlingForm):
if type(data['size']) is str: if type(data['size']) is str:
data['size'] = int(data['size']) data['size'] = int(data['size'])
if usages['gigabytes']['available'] < data['size']: if availableGB < data['size']:
error_message = _('A volume of %(req)iGB cannot be created as ' error_message = _('A volume of %(req)iGB cannot be created as '
'you only have %(avail)iGB of your quota ' 'you only have %(avail)iGB of your quota '
'available.') 'available.')
params = {'req': data['size'], params = {'req': data['size'],
'avail': usages['gigabytes']['available']} 'avail': availableGB}
raise ValidationError(error_message % params) raise ValidationError(error_message % params)
elif usages['volumes']['available'] <= 0: elif availableVol <= 0:
error_message = _('You are already using all of your available' error_message = _('You are already using all of your available'
' volumes.') ' volumes.')
raise ValidationError(error_message) raise ValidationError(error_message)

View File

@ -16,7 +16,7 @@
</div> </div>
<div class="right quota-dynamic"> <div class="right quota-dynamic">
{% include "project/volumes/_quota.html" with usages=usages %} {% include "project/volumes/_limits.html" with usages=usages %}
</div> </div>
{% endblock %} {% endblock %}

View File

@ -0,0 +1,34 @@
{% load i18n horizon humanize %}
<h3>{% trans "Description" %}:</h3>
<p>{% trans "Volumes are block devices that can be attached to instances." %}</p>
<h3>{% trans "Volume Limits" %}</h3>
<div class="quota_title clearfix">
<strong>{% trans "Total Gigabytes" %} <span>({{ usages.gigabytesUsed|intcomma }} {% trans "GB" %})</span></strong>
<p>{{ usages.maxTotalVolumeGigabytes|quota:_("GB")|intcomma }}</p>
</div>
<div id="quota_size" data-progress-indicator-for="id_size" data-quota-limit="{{ usages.maxTotalVolumeGigabytes }}" data-quota-used="{{ usages.gigabytesUsed }}" class="quota_bar">
</div>
<div class="quota_title clearfix">
<strong>{% trans "Number of Volumes" %} <span>({{ usages.volumesUsed|intcomma }})</span></strong>
<p>{{ usages.maxTotalVolumes|quota|intcomma }}</p>
</div>
<div id="quota_volumes" data-progress-indicator-step-by="1" data-quota-limit="{{ usages.maxTotalVolumes}}" data-quota-used="{{ usages.volumesUsed }}" class="quota_bar">
</div>
<script type="text/javascript" charset="utf-8">
if(typeof horizon.Quota !== 'undefined') {
horizon.Quota.init();
} else {
addHorizonLoadEvent(function() {
horizon.Quota.init();
});
}
</script>

View File

@ -1,43 +0,0 @@
{% load i18n horizon humanize %}
<h3>{% trans "Description" %}:</h3>
<p>{% trans "Volumes are block devices that can be attached to instances." %}</p>
<h3>{% trans "Volume Quotas" %}</h3>
<div class="quota_title clearfix">
<strong>{% trans "Total Gigabytes" %} <span>({{ usages.gigabytes.used|intcomma }} {% trans "GB" %})</span></strong>
<p>{{ usages.gigabytes.available|quota:_("GB")|intcomma }}</p>
</div>
<div id="quota_size" data-progress-indicator-for="id_size" data-quota-limit="{{ usages.gigabytes.quota }}" data-quota-used="{{ usages.gigabytes.used }}" class="quota_bar">
</div>
{% if snapshot_quota %}
<div class="quota_title clearfix">
<strong>{% trans "Number of Snapshots" %} <span>({{ usages.snapshots.used|intcomma }})</span></strong>
<p>{{ usages.snapshots.available|quota|intcomma }}</p>
</div>
<div id="quota_snapshots" data-progress-indicator-step-by="1" data-quota-limit="{{ usages.snapshots.quota }}" data-quota-used="{{ usages.snapshots.used }}" class="quota_bar">
</div>
{% else %}
<div class="quota_title clearfix">
<strong>{% trans "Number of Volumes" %} <span>({{ usages.volumes.used|intcomma }})</span></strong>
<p>{{ usages.volumes.available|quota|intcomma }}</p>
</div>
<div id="quota_volumes" data-progress-indicator-step-by="1" data-quota-limit="{{ usages.volumes.quota }}" data-quota-used="{{ usages.volumes.used }}" class="quota_bar">
</div>
{% endif %}
<script type="text/javascript" charset="utf-8">
if(typeof horizon.Quota !== 'undefined') {
horizon.Quota.init();
} else {
addHorizonLoadEvent(function() {
horizon.Quota.init();
});
}
</script>

View File

@ -23,7 +23,7 @@ from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.forms import widgets from django.forms import widgets
from mox import IsA from mox import IsA, IgnoreArg
from openstack_dashboard import api from openstack_dashboard import api
from openstack_dashboard.api import cinder from openstack_dashboard.api import cinder
@ -34,13 +34,16 @@ from openstack_dashboard.usage import quotas
class VolumeViewTests(test.TestCase): class VolumeViewTests(test.TestCase):
@test.create_stubs({cinder: ('volume_create', @test.create_stubs({cinder: ('volume_create',
'volume_snapshot_list', 'volume_snapshot_list',
'volume_type_list',), 'volume_type_list',
api.glance: ('image_list_detailed',), 'tenant_absolute_limits',
quotas: ('tenant_quota_usages',)}) 'volume_list',),
api.glance: ('image_list_detailed',)})
def test_create_volume(self): def test_create_volume(self):
volume = self.volumes.first() volume = self.volumes.first()
volume_type = self.volume_types.first() volume_type = self.volume_types.first()
usage = {'gigabytes': {'available': 250}, 'volumes': {'available': 6}} usage_limit = {'maxTotalVolumeGigabytes': 250,
'gigabytesUsed': 20,
'maxTotalVolumes': 6}
formData = {'name': u'A Volume I Am Making', formData = {'name': u'A Volume I Am Making',
'description': u'This is a volume I am making for a test.', 'description': u'This is a volume I am making for a test.',
'method': u'CreateForm', 'method': u'CreateForm',
@ -50,7 +53,10 @@ class VolumeViewTests(test.TestCase):
cinder.volume_type_list(IsA(http.HttpRequest)).\ cinder.volume_type_list(IsA(http.HttpRequest)).\
AndReturn(self.volume_types.list()) AndReturn(self.volume_types.list())
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage) cinder.tenant_absolute_limits(IsA(http.HttpRequest)).\
AndReturn(usage_limit)
cinder.volume_list(IsA(http.HttpRequest)).\
AndReturn(self.volumes.list())
cinder.volume_snapshot_list(IsA(http.HttpRequest)).\ cinder.volume_snapshot_list(IsA(http.HttpRequest)).\
AndReturn(self.volume_snapshots.list()) AndReturn(self.volume_snapshots.list())
api.glance.image_list_detailed(IsA(http.HttpRequest), api.glance.image_list_detailed(IsA(http.HttpRequest),
@ -80,12 +86,14 @@ class VolumeViewTests(test.TestCase):
@test.create_stubs({cinder: ('volume_create', @test.create_stubs({cinder: ('volume_create',
'volume_snapshot_list', 'volume_snapshot_list',
'volume_type_list',), 'volume_type_list',
api.glance: ('image_list_detailed',), 'tenant_absolute_limits',
quotas: ('tenant_quota_usages',)}) 'volume_list',),
api.glance: ('image_list_detailed',)})
def test_create_volume_dropdown(self): def test_create_volume_dropdown(self):
volume = self.volumes.first() volume = self.volumes.first()
usage = {'gigabytes': {'available': 250}, 'volumes': {'available': 6}} usage_limit = {'maxTotalVolumeGigabytes': 250,
'maxTotalVolumes': 6}
formData = {'name': u'A Volume I Am Making', formData = {'name': u'A Volume I Am Making',
'description': u'This is a volume I am making for a test.', 'description': u'This is a volume I am making for a test.',
'method': u'CreateForm', 'method': u'CreateForm',
@ -107,7 +115,10 @@ class VolumeViewTests(test.TestCase):
filters={'property-owner_id': self.tenant.id, filters={'property-owner_id': self.tenant.id,
'status': 'active'}) \ 'status': 'active'}) \
.AndReturn([[], False]) .AndReturn([[], False])
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage) cinder.tenant_absolute_limits(IsA(http.HttpRequest)).\
AndReturn(usage_limit)
cinder.volume_list(IsA(http.HttpRequest)).\
AndReturn(self.volumes.list())
cinder.volume_create(IsA(http.HttpRequest), cinder.volume_create(IsA(http.HttpRequest),
formData['size'], formData['size'],
formData['name'], formData['name'],
@ -129,11 +140,13 @@ class VolumeViewTests(test.TestCase):
@test.create_stubs({cinder: ('volume_create', @test.create_stubs({cinder: ('volume_create',
'volume_snapshot_get', 'volume_snapshot_get',
'volume_get', 'volume_get',
'volume_type_list',), 'volume_type_list',
quotas: ('tenant_quota_usages',)}) 'tenant_absolute_limits',
'volume_list',)})
def test_create_volume_from_snapshot(self): def test_create_volume_from_snapshot(self):
volume = self.volumes.first() volume = self.volumes.first()
usage = {'gigabytes': {'available': 250}, 'volumes': {'available': 6}} usage_limit = {'maxTotalVolumeGigabytes': 250,
'maxTotalVolumes': 6}
snapshot = self.volume_snapshots.first() snapshot = self.volume_snapshots.first()
formData = {'name': u'A Volume I Am Making', formData = {'name': u'A Volume I Am Making',
'description': u'This is a volume I am making for a test.', 'description': u'This is a volume I am making for a test.',
@ -144,7 +157,10 @@ class VolumeViewTests(test.TestCase):
cinder.volume_type_list(IsA(http.HttpRequest)).\ cinder.volume_type_list(IsA(http.HttpRequest)).\
AndReturn(self.volume_types.list()) AndReturn(self.volume_types.list())
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage) cinder.tenant_absolute_limits(IsA(http.HttpRequest)).\
AndReturn(usage_limit)
cinder.volume_list(IsA(http.HttpRequest)).\
AndReturn(self.volumes.list())
cinder.volume_snapshot_get(IsA(http.HttpRequest), cinder.volume_snapshot_get(IsA(http.HttpRequest),
str(snapshot.id)).AndReturn(snapshot) str(snapshot.id)).AndReturn(snapshot)
cinder.volume_get(IsA(http.HttpRequest), snapshot.volume_id).\ cinder.volume_get(IsA(http.HttpRequest), snapshot.volume_id).\
@ -173,12 +189,14 @@ class VolumeViewTests(test.TestCase):
'volume_snapshot_list', 'volume_snapshot_list',
'volume_snapshot_get', 'volume_snapshot_get',
'volume_get', 'volume_get',
'volume_type_list',), 'volume_type_list',
api.glance: ('image_list_detailed',), 'tenant_absolute_limits',
quotas: ('tenant_quota_usages',)}) 'volume_list',),
api.glance: ('image_list_detailed',)})
def test_create_volume_from_snapshot_dropdown(self): def test_create_volume_from_snapshot_dropdown(self):
volume = self.volumes.first() volume = self.volumes.first()
usage = {'gigabytes': {'available': 250}, 'volumes': {'available': 6}} usage_limit = {'maxTotalVolumeGigabytes': 250,
'maxTotalVolumes': 6}
snapshot = self.volume_snapshots.first() snapshot = self.volume_snapshots.first()
formData = {'name': u'A Volume I Am Making', formData = {'name': u'A Volume I Am Making',
'description': u'This is a volume I am making for a test.', 'description': u'This is a volume I am making for a test.',
@ -200,7 +218,10 @@ class VolumeViewTests(test.TestCase):
filters={'property-owner_id': self.tenant.id, filters={'property-owner_id': self.tenant.id,
'status': 'active'}) \ 'status': 'active'}) \
.AndReturn([[], False]) .AndReturn([[], False])
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage) cinder.tenant_absolute_limits(IsA(http.HttpRequest)).\
AndReturn(usage_limit)
cinder.volume_list(IsA(http.HttpRequest)).\
AndReturn(self.volumes.list())
cinder.volume_snapshot_get(IsA(http.HttpRequest), cinder.volume_snapshot_get(IsA(http.HttpRequest),
str(snapshot.id)).AndReturn(snapshot) str(snapshot.id)).AndReturn(snapshot)
cinder.volume_create(IsA(http.HttpRequest), cinder.volume_create(IsA(http.HttpRequest),
@ -224,11 +245,13 @@ class VolumeViewTests(test.TestCase):
@test.create_stubs({cinder: ('volume_snapshot_get', @test.create_stubs({cinder: ('volume_snapshot_get',
'volume_type_list', 'volume_type_list',
'volume_get',), 'volume_get',
api.glance: ('image_list_detailed',), 'tenant_absolute_limits',
quotas: ('tenant_quota_usages',)}) 'volume_list',),
api.glance: ('image_list_detailed',)})
def test_create_volume_from_snapshot_invalid_size(self): def test_create_volume_from_snapshot_invalid_size(self):
usage = {'gigabytes': {'available': 250}, 'volumes': {'available': 6}} usage_limit = {'maxTotalVolumeGigabytes': 100,
'maxTotalVolumes': 6}
snapshot = self.volume_snapshots.first() snapshot = self.volume_snapshots.first()
formData = {'name': u'A Volume I Am Making', formData = {'name': u'A Volume I Am Making',
'description': u'This is a volume I am making for a test.', 'description': u'This is a volume I am making for a test.',
@ -237,12 +260,18 @@ class VolumeViewTests(test.TestCase):
cinder.volume_type_list(IsA(http.HttpRequest)).\ cinder.volume_type_list(IsA(http.HttpRequest)).\
AndReturn(self.volume_types.list()) AndReturn(self.volume_types.list())
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage) cinder.tenant_absolute_limits(IsA(http.HttpRequest)).\
AndReturn(usage_limit)
cinder.volume_list(IsA(http.HttpRequest)).\
AndReturn(self.volumes.list())
cinder.volume_snapshot_get(IsA(http.HttpRequest), cinder.volume_snapshot_get(IsA(http.HttpRequest),
str(snapshot.id)).AndReturn(snapshot) str(snapshot.id)).AndReturn(snapshot)
cinder.volume_get(IsA(http.HttpRequest), snapshot.volume_id).\ cinder.volume_get(IsA(http.HttpRequest), snapshot.volume_id).\
AndReturn(self.volumes.first()) AndReturn(self.volumes.first())
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage) cinder.tenant_absolute_limits(IsA(http.HttpRequest)).\
AndReturn(usage_limit)
cinder.volume_list(IsA(http.HttpRequest)).\
AndReturn(self.volumes.list())
self.mox.ReplayAll() self.mox.ReplayAll()
@ -256,12 +285,14 @@ class VolumeViewTests(test.TestCase):
"snapshot size (40GB)") "snapshot size (40GB)")
@test.create_stubs({cinder: ('volume_create', @test.create_stubs({cinder: ('volume_create',
'volume_type_list',), 'volume_type_list',
api.glance: ('image_get',), 'tenant_absolute_limits',
quotas: ('tenant_quota_usages',)}) 'volume_list',),
api.glance: ('image_get',)})
def test_create_volume_from_image(self): def test_create_volume_from_image(self):
volume = self.volumes.first() volume = self.volumes.first()
usage = {'gigabytes': {'available': 250}, 'volumes': {'available': 6}} usage_limit = {'maxTotalVolumeGigabytes': 200,
'maxTotalVolumes': 6}
image = self.images.first() image = self.images.first()
formData = {'name': u'A Volume I Am Making', formData = {'name': u'A Volume I Am Making',
'description': u'This is a volume I am making for a test.', 'description': u'This is a volume I am making for a test.',
@ -272,7 +303,10 @@ class VolumeViewTests(test.TestCase):
cinder.volume_type_list(IsA(http.HttpRequest)).\ cinder.volume_type_list(IsA(http.HttpRequest)).\
AndReturn(self.volume_types.list()) AndReturn(self.volume_types.list())
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage) cinder.tenant_absolute_limits(IsA(http.HttpRequest)).\
AndReturn(usage_limit)
cinder.volume_list(IsA(http.HttpRequest)).\
AndReturn(self.volumes.list())
api.glance.image_get(IsA(http.HttpRequest), api.glance.image_get(IsA(http.HttpRequest),
str(image.id)).AndReturn(image) str(image.id)).AndReturn(image)
cinder.volume_create(IsA(http.HttpRequest), cinder.volume_create(IsA(http.HttpRequest),
@ -298,13 +332,15 @@ class VolumeViewTests(test.TestCase):
@test.create_stubs({cinder: ('volume_create', @test.create_stubs({cinder: ('volume_create',
'volume_type_list', 'volume_type_list',
'volume_snapshot_list',), 'volume_snapshot_list',
'tenant_absolute_limits',
'volume_list',),
api.glance: ('image_get', api.glance: ('image_get',
'image_list_detailed'), 'image_list_detailed')})
quotas: ('tenant_quota_usages',)})
def test_create_volume_from_image_dropdown(self): def test_create_volume_from_image_dropdown(self):
volume = self.volumes.first() volume = self.volumes.first()
usage = {'gigabytes': {'available': 250}, 'volumes': {'available': 6}} usage_limit = {'maxTotalVolumeGigabytes': 200,
'maxTotalVolumes': 6}
image = self.images.first() image = self.images.first()
formData = {'name': u'A Volume I Am Making', formData = {'name': u'A Volume I Am Making',
'description': u'This is a volume I am making for a test.', 'description': u'This is a volume I am making for a test.',
@ -327,7 +363,10 @@ class VolumeViewTests(test.TestCase):
filters={'property-owner_id': self.tenant.id, filters={'property-owner_id': self.tenant.id,
'status': 'active'}) \ 'status': 'active'}) \
.AndReturn([[], False]) .AndReturn([[], False])
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage) cinder.tenant_absolute_limits(IsA(http.HttpRequest)) \
.AndReturn(usage_limit)
cinder.volume_list(IsA(http.HttpRequest)) \
.AndReturn(self.volumes.list())
api.glance.image_get(IsA(http.HttpRequest), api.glance.image_get(IsA(http.HttpRequest),
str(image.id)).AndReturn(image) str(image.id)).AndReturn(image)
cinder.volume_create(IsA(http.HttpRequest), cinder.volume_create(IsA(http.HttpRequest),
@ -349,12 +388,13 @@ class VolumeViewTests(test.TestCase):
redirect_url = reverse('horizon:project:volumes:index') redirect_url = reverse('horizon:project:volumes:index')
self.assertRedirectsNoFollow(res, redirect_url) self.assertRedirectsNoFollow(res, redirect_url)
@test.create_stubs({cinder: ('volume_type_list',), @test.create_stubs({cinder: ('volume_type_list', 'tenant_absolute_limits',
'volume_list',),
api.glance: ('image_get', api.glance: ('image_get',
'image_list_detailed'), 'image_list_detailed')})
quotas: ('tenant_quota_usages',)})
def test_create_volume_from_image_invalid_size(self): def test_create_volume_from_image_invalid_size(self):
usage = {'gigabytes': {'available': 250}, 'volumes': {'available': 6}} usage_limit = {'maxTotalVolumeGigabytes': 100,
'maxTotalVolumes': 6}
image = self.images.first() image = self.images.first()
formData = {'name': u'A Volume I Am Making', formData = {'name': u'A Volume I Am Making',
'description': u'This is a volume I am making for a test.', 'description': u'This is a volume I am making for a test.',
@ -363,10 +403,16 @@ class VolumeViewTests(test.TestCase):
cinder.volume_type_list(IsA(http.HttpRequest)).\ cinder.volume_type_list(IsA(http.HttpRequest)).\
AndReturn(self.volume_types.list()) AndReturn(self.volume_types.list())
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage) cinder.tenant_absolute_limits(IsA(http.HttpRequest)).\
AndReturn(usage_limit)
cinder.volume_list(IsA(http.HttpRequest)).\
AndReturn(self.volumes.list())
api.glance.image_get(IsA(http.HttpRequest), api.glance.image_get(IsA(http.HttpRequest),
str(image.id)).AndReturn(image) str(image.id)).AndReturn(image)
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage) cinder.tenant_absolute_limits(IsA(http.HttpRequest)).\
AndReturn(usage_limit)
cinder.volume_list(IsA(http.HttpRequest)).\
AndReturn(self.volumes.list())
self.mox.ReplayAll() self.mox.ReplayAll()
@ -379,11 +425,12 @@ class VolumeViewTests(test.TestCase):
"The volume size cannot be less than the " "The volume size cannot be less than the "
"image size (20.0 GB)") "image size (20.0 GB)")
@test.create_stubs({cinder: ('volume_snapshot_list', 'volume_type_list',), @test.create_stubs({cinder: ('volume_snapshot_list', 'volume_type_list',
api.glance: ('image_list_detailed',), 'tenant_absolute_limits', 'volume_list',),
quotas: ('tenant_quota_usages',)}) api.glance: ('image_list_detailed',)})
def test_create_volume_gb_used_over_alloted_quota(self): def test_create_volume_gb_used_over_alloted_quota(self):
usage = {'gigabytes': {'available': 100, 'used': 20}} usage_limit = {'maxTotalVolumeGigabytes': 100,
'maxTotalVolumes': 6}
formData = {'name': u'This Volume Is Huge!', formData = {'name': u'This Volume Is Huge!',
'description': u'This is a volume that is just too big!', 'description': u'This is a volume that is just too big!',
'method': u'CreateForm', 'method': u'CreateForm',
@ -391,7 +438,10 @@ class VolumeViewTests(test.TestCase):
cinder.volume_type_list(IsA(http.HttpRequest)).\ cinder.volume_type_list(IsA(http.HttpRequest)).\
AndReturn(self.volume_types.list()) AndReturn(self.volume_types.list())
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage) cinder.tenant_absolute_limits(IsA(http.HttpRequest)).\
AndReturn(usage_limit)
cinder.volume_list(IsA(http.HttpRequest)).\
AndReturn(self.volumes.list())
cinder.volume_snapshot_list(IsA(http.HttpRequest)).\ cinder.volume_snapshot_list(IsA(http.HttpRequest)).\
AndReturn(self.volume_snapshots.list()) AndReturn(self.volume_snapshots.list())
api.glance.image_list_detailed(IsA(http.HttpRequest), api.glance.image_list_detailed(IsA(http.HttpRequest),
@ -402,7 +452,10 @@ class VolumeViewTests(test.TestCase):
filters={'property-owner_id': self.tenant.id, filters={'property-owner_id': self.tenant.id,
'status': 'active'}) \ 'status': 'active'}) \
.AndReturn([[], False]) .AndReturn([[], False])
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage) cinder.tenant_absolute_limits(IsA(http.HttpRequest)).\
AndReturn(usage_limit)
cinder.volume_list(IsA(http.HttpRequest)).\
AndReturn(self.volumes.list())
self.mox.ReplayAll() self.mox.ReplayAll()
@ -410,15 +463,15 @@ class VolumeViewTests(test.TestCase):
res = self.client.post(url, formData) res = self.client.post(url, formData)
expected_error = [u'A volume of 5000GB cannot be created as you only' expected_error = [u'A volume of 5000GB cannot be created as you only'
' have 100GB of your quota available.'] ' have 20GB of your quota available.']
self.assertEqual(res.context['form'].errors['__all__'], expected_error) self.assertEqual(res.context['form'].errors['__all__'], expected_error)
@test.create_stubs({cinder: ('volume_snapshot_list', 'volume_type_list',), @test.create_stubs({cinder: ('volume_snapshot_list', 'volume_type_list',
api.glance: ('image_list_detailed',), 'tenant_absolute_limits', 'volume_list',),
quotas: ('tenant_quota_usages',)}) api.glance: ('image_list_detailed',)})
def test_create_volume_number_over_alloted_quota(self): def test_create_volume_number_over_alloted_quota(self):
usage = {'gigabytes': {'available': 100, 'used': 20}, usage_limit = {'maxTotalVolumeGigabytes': 100,
'volumes': {'available': 0}} 'maxTotalVolumes': len(self.volumes.list())}
formData = {'name': u'Too Many...', formData = {'name': u'Too Many...',
'description': u'We have no volumes left!', 'description': u'We have no volumes left!',
'method': u'CreateForm', 'method': u'CreateForm',
@ -426,7 +479,10 @@ class VolumeViewTests(test.TestCase):
cinder.volume_type_list(IsA(http.HttpRequest)).\ cinder.volume_type_list(IsA(http.HttpRequest)).\
AndReturn(self.volume_types.list()) AndReturn(self.volume_types.list())
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage) cinder.tenant_absolute_limits(IsA(http.HttpRequest)).\
AndReturn(usage_limit)
cinder.volume_list(IsA(http.HttpRequest)).\
AndReturn(self.volumes.list())
cinder.volume_snapshot_list(IsA(http.HttpRequest)).\ cinder.volume_snapshot_list(IsA(http.HttpRequest)).\
AndReturn(self.volume_snapshots.list()) AndReturn(self.volume_snapshots.list())
api.glance.image_list_detailed(IsA(http.HttpRequest), api.glance.image_list_detailed(IsA(http.HttpRequest),
@ -437,7 +493,10 @@ class VolumeViewTests(test.TestCase):
filters={'property-owner_id': self.tenant.id, filters={'property-owner_id': self.tenant.id,
'status': 'active'}) \ 'status': 'active'}) \
.AndReturn([[], False]) .AndReturn([[], False])
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage) cinder.tenant_absolute_limits(IsA(http.HttpRequest)).\
AndReturn(usage_limit)
cinder.volume_list(IsA(http.HttpRequest)).\
AndReturn(self.volumes.list())
self.mox.ReplayAll() self.mox.ReplayAll()
@ -450,13 +509,16 @@ class VolumeViewTests(test.TestCase):
@test.create_stubs({cinder: ('volume_create', @test.create_stubs({cinder: ('volume_create',
'volume_snapshot_list', 'volume_snapshot_list',
'volume_type_list',), 'volume_type_list',
api.glance: ('image_list_detailed',), 'tenant_absolute_limits',
quotas: ('tenant_quota_usages',)}) 'volume_list',),
api.glance: ('image_list_detailed',)})
def test_create_volume_encrypted(self): def test_create_volume_encrypted(self):
volume = self.volumes.first() volume = self.volumes.first()
volume_type = self.volume_types.first() volume_type = self.volume_types.first()
usage = {'gigabytes': {'available': 250}, 'volumes': {'available': 6}} usage_limit = {'maxTotalVolumeGigabytes': 250,
'gigabytesUsed': 20,
'maxTotalVolumes': 6}
formData = {'name': u'An Encrypted Volume', formData = {'name': u'An Encrypted Volume',
'description': u'This volume has metadata for encryption.', 'description': u'This volume has metadata for encryption.',
'method': u'CreateForm', 'method': u'CreateForm',
@ -471,7 +533,10 @@ class VolumeViewTests(test.TestCase):
cinder.volume_type_list(IsA(http.HttpRequest)).\ cinder.volume_type_list(IsA(http.HttpRequest)).\
AndReturn(self.volume_types.list()) AndReturn(self.volume_types.list())
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage) cinder.tenant_absolute_limits(IsA(http.HttpRequest)).\
AndReturn(usage_limit)
cinder.volume_list(IsA(http.HttpRequest)).\
AndReturn(self.volumes.list())
cinder.volume_snapshot_list(IsA(http.HttpRequest)).\ cinder.volume_snapshot_list(IsA(http.HttpRequest)).\
AndReturn(self.volume_snapshots.list()) AndReturn(self.volume_snapshots.list())
api.glance.image_list_detailed(IsA(http.HttpRequest), api.glance.image_list_detailed(IsA(http.HttpRequest),
@ -501,9 +566,9 @@ class VolumeViewTests(test.TestCase):
settings.OPENSTACK_HYPERVISOR_FEATURES['can_encrypt_volumes'] = PREV settings.OPENSTACK_HYPERVISOR_FEATURES['can_encrypt_volumes'] = PREV
@test.create_stubs({cinder: ('volume_snapshot_list', 'volume_type_list',), @test.create_stubs({cinder: ('volume_snapshot_list', 'volume_type_list',
api.glance: ('image_list_detailed',), 'tenant_absolute_limits', 'volume_list',),
quotas: ('tenant_quota_usages',)}) api.glance: ('image_list_detailed',)})
def test_create_volume_cannot_encrypt(self): def test_create_volume_cannot_encrypt(self):
volume = self.volumes.first() volume = self.volumes.first()
volume_type = self.volume_types.first() volume_type = self.volume_types.first()
@ -522,11 +587,16 @@ class VolumeViewTests(test.TestCase):
volume = self.volumes.first() volume = self.volumes.first()
volume_type = self.volume_types.first() volume_type = self.volume_types.first()
usage = {'gigabytes': {'available': 250}, 'volumes': {'available': 6}} usage_limit = {'maxTotalVolumeGigabytes': 250,
'gigabytesUsed': 20,
'maxTotalVolumes': 6}
cinder.volume_type_list(IsA(http.HttpRequest)).\ cinder.volume_type_list(IsA(http.HttpRequest)).\
AndReturn(self.volume_types.list()) AndReturn(self.volume_types.list())
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage) cinder.tenant_absolute_limits(IsA(http.HttpRequest)).\
AndReturn(usage_limit)
cinder.volume_list(IsA(http.HttpRequest)).\
AndReturn(self.volumes.list())
cinder.volume_snapshot_list(IsA(http.HttpRequest)).\ cinder.volume_snapshot_list(IsA(http.HttpRequest)).\
AndReturn(self.volume_snapshots.list()) AndReturn(self.volume_snapshots.list())
api.glance.image_list_detailed(IsA(http.HttpRequest), api.glance.image_list_detailed(IsA(http.HttpRequest),

View File

@ -99,7 +99,15 @@ class CreateView(forms.ModalFormView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(CreateView, self).get_context_data(**kwargs) context = super(CreateView, self).get_context_data(**kwargs)
try: try:
context['usages'] = quotas.tenant_quota_usages(self.request) tenant_id = self.kwargs.get('tenant_id',
self.request.user.tenant_id)
context['usages'] = cinder.tenant_absolute_limits(self.request)
volumes = cinder.volume_list(self.request)
total_size = sum([getattr(volume, 'size', 0) for volume
in volumes])
context['usages']['gigabytesUsed'] = total_size
context['usages']['volumesUsed'] = len(volumes)
except: except:
exceptions.handle(self.request) exceptions.handle(self.request)
return context return context

View File

@ -32,6 +32,7 @@ class BaseUsage(object):
self.request = request self.request = request
self.summary = {} self.summary = {}
self.usage_list = [] self.usage_list = []
self.limits = {}
self.quotas = {} self.quotas = {}
@property @property
@ -82,6 +83,13 @@ class BaseUsage(object):
'year': self.today.year}) 'year': self.today.year})
return self.form return self.form
def get_limits(self):
try:
self.limits = api.nova.tenant_absolute_limits(self.request)
except:
exceptions.handle(self.request,
_("Unable to retrieve limit information."))
def get_usage_list(self, start, end): def get_usage_list(self, start, end):
raise NotImplementedError("You must define a get_usage method.") raise NotImplementedError("You must define a get_usage method.")

View File

@ -31,7 +31,7 @@ class UsageView(tables.DataTableView):
tenant_id = self.kwargs.get('tenant_id', self.request.user.tenant_id) tenant_id = self.kwargs.get('tenant_id', self.request.user.tenant_id)
self.usage = self.usage_class(self.request, tenant_id) self.usage = self.usage_class(self.request, tenant_id)
self.usage.summarize(*self.usage.get_date_range()) self.usage.summarize(*self.usage.get_date_range())
self.usage.get_quotas() self.usage.get_limits()
self.kwargs['usage'] = self.usage self.kwargs['usage'] = self.usage
return self.usage.usage_list return self.usage.usage_list