Merge "Enable snapshot quota setting"

This commit is contained in:
Jenkins 2013-05-29 07:11:43 +00:00 committed by Gerrit Code Review
commit 144fda877f
13 changed files with 143 additions and 48 deletions

View File

@ -30,6 +30,12 @@
{% 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 />

View File

@ -31,7 +31,7 @@ class ServicesViewTests(test.BaseAdminViewTests):
api.nova.default_quota_get(IsA(http.HttpRequest),
self.tenant.id).AndReturn(self.quotas.nova)
api.cinder.default_quota_get(IsA(http.HttpRequest), self.tenant.id) \
.AndReturn(self.quotas.nova)
.AndReturn(self.cinder_quotas.first())
self.mox.ReplayAll()
@ -60,6 +60,7 @@ class ServicesViewTests(test.BaseAdminViewTests):
'<Quota: (floating_ips, 1)>',
'<Quota: (fixed_ips, 10)>',
'<Quota: (instances, 10)>',
'<Quota: (snapshots, 1)>',
'<Quota: (volumes, 1)>',
'<Quota: (cores, 10)>',
'<Quota: (security_groups, 10)>',

View File

@ -58,9 +58,12 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
return project_info
def _get_quota_info(self, quota):
cinder_quota = self.cinder_quotas.first()
quota_data = {}
for field in quotas.QUOTA_FIELDS:
for field in quotas.NOVA_QUOTA_FIELDS:
quota_data[field] = int(quota.get(field).limit)
for field in quotas.CINDER_QUOTA_FIELDS:
quota_data[field] = int(cinder_quota.get(field).limit)
return quota_data
def _get_workflow_data(self, project, quota):
@ -403,9 +406,12 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
def _get_quota_info(self, quota):
cinder_quota = self.cinder_quotas.first()
quota_data = {}
for field in quotas.QUOTA_FIELDS:
for field in quotas.NOVA_QUOTA_FIELDS:
quota_data[field] = int(quota.get(field).limit)
for field in quotas.CINDER_QUOTA_FIELDS:
quota_data[field] = int(cinder_quota.get(field).limit)
return quota_data
@test.create_stubs({api.keystone: ('get_default_role',

View File

@ -51,6 +51,7 @@ class UpdateProjectQuotaAction(workflows.Action):
injected_file_content_bytes = forms.IntegerField(min_value=-1,
label=ifcb_label)
volumes = forms.IntegerField(min_value=-1, label=_("Volumes"))
snapshots = forms.IntegerField(min_value=-1, label=_("Snapshots"))
gigabytes = forms.IntegerField(min_value=-1, label=_("Gigabytes"))
ram = forms.IntegerField(min_value=-1, label=_("RAM (MB)"))
floating_ips = forms.IntegerField(min_value=-1, label=_("Floating IPs"))

View File

@ -25,14 +25,21 @@ from mox import IsA
from openstack_dashboard import api
from openstack_dashboard.api import cinder
from openstack_dashboard.test import helpers as test
from openstack_dashboard.usage import quotas
INDEX_URL = reverse('horizon:project:images_and_snapshots:index')
class VolumeSnapshotsViewTests(test.TestCase):
@test.create_stubs({quotas: ('tenant_quota_usages',)})
def test_create_snapshot_get(self):
volume = self.volumes.first()
usage = {'gigabytes': {'available': 250},
'snapshots': {'available': 6}}
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
self.mox.ReplayAll()
url = reverse('horizon:project:volumes:create_snapshot',
args=[volume.id])
res = self.client.get(url)

View File

@ -1,5 +1,5 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n horizon humanize %}
{% load i18n %}
{% load url from future %}
{% block form_id %}{% endblock %}
@ -16,38 +16,8 @@
</div>
<div class="right quota-dynamic">
<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>
<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>
{% include "project/volumes/_quota.html" with usages=usages %}
</div>
<script type="text/javascript" charset="utf-8">
if(typeof horizon.Quota !== 'undefined') {
horizon.Quota.init();
} else {
addHorizonLoadEvent(function() {
horizon.Quota.init();
});
}
</script>
{% endblock %}
{% block modal-footer %}

View File

@ -9,15 +9,14 @@
{% block modal-header %}{% trans "Create Volume Snapshot" %}{% endblock %}
{% block modal-body %}
<div class="left">
<fieldset>
{% include "horizon/common/_form_fields.html" %}
</fieldset>
</div>
<div class="right">
<h3>{% trans "Description" %}:</h3>
<p>{% trans "Volumes are block devices that can be attached to instances." %}</p>
</div>
<div class="left">
<fieldset>
{% include "horizon/common/_form_fields.html" %}
</fieldset>
</div>
<div class="right quota-dynamic">
{% include "project/volumes/_quota.html" with usages=usages snapshot_quota=True %}
</div>
{% endblock %}
{% block modal-footer %}

View File

@ -0,0 +1,43 @@
{% 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

@ -113,6 +113,10 @@ class CreateSnapshotView(forms.ModalFormView):
def get_context_data(self, **kwargs):
context = super(CreateSnapshotView, self).get_context_data(**kwargs)
context['volume_id'] = self.kwargs['volume_id']
try:
context['usages'] = quotas.tenant_quota_usages(self.request)
except:
exceptions.handle(self.request)
return context
def get_initial(self):

View File

@ -0,0 +1,45 @@
# Copyright 2012 Nebula, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.v1 import quotas
from openstack_dashboard.api.base import Quota, QuotaSet as QuotaSetWrapper
from openstack_dashboard.usage.quotas import QuotaUsage
from .utils import TestDataContainer
def data(TEST):
TEST.cinder_quotas = TestDataContainer()
TEST.cinder_quota_usages = TestDataContainer()
# Quota Sets
quota_data = dict(volumes='1',
snapshots='1',
gigabytes='1000')
quota = quotas.QuotaSet(quotas.QuotaSetManager(None), quota_data)
#TEST.quotas.cinder = QuotaSetWrapper(quota)
TEST.cinder_quotas.add(QuotaSetWrapper(quota))
# Quota Usages
quota_usage_data = {'gigabytes': {'used': 0,
'quota': 1000},
'instances': {'used': 0,
'quota': 10},
'snapshots': {'used': 0,
'quota': 10}}
quota_usage = QuotaUsage()
for k, v in quota_usage_data.items():
quota_usage.add_quota(Quota(k, v['quota']))
quota_usage.tally(k, v['used'])
TEST.cinder_quota_usages.add(quota_usage)

View File

@ -18,6 +18,7 @@ def load_test_data(load_onto=None):
from . import glance_data
from . import keystone_data
from . import nova_data
from . import cinder_data
from . import quantum_data
from . import swift_data
from . import heat_data
@ -27,6 +28,7 @@ def load_test_data(load_onto=None):
keystone_data.data,
glance_data.data,
nova_data.data,
cinder_data.data,
quantum_data.data,
swift_data.data,
heat_data.data)

View File

@ -45,6 +45,8 @@ class QuotaTests(test.APITestCase):
'cores': {'available': 8, 'used': 2, 'quota': 10}}
if with_volume:
quotas.update({'volumes': {'available': 0, 'used': 3, 'quota': 1},
'snapshots': {'available': 0, 'used': 3,
'quota': 1},
'gigabytes': {'available': 920, 'used': 80,
'quota': 1000}})
return quotas
@ -54,7 +56,8 @@ class QuotaTests(test.APITestCase):
'tenant_quota_get',),
api.network: ('tenant_floating_ip_list',),
quotas: ('is_service_enabled',),
cinder: ('volume_list', 'tenant_quota_get',)})
cinder: ('volume_list', 'volume_snapshot_list',
'tenant_quota_get',)})
def test_tenant_quota_usages(self):
quotas.is_service_enabled(IsA(http.HttpRequest),
'volume').AndReturn(True)
@ -68,8 +71,10 @@ class QuotaTests(test.APITestCase):
.AndReturn([self.servers.list(), False])
cinder.volume_list(IsA(http.HttpRequest)) \
.AndReturn(self.volumes.list())
cinder.volume_snapshot_list(IsA(http.HttpRequest)) \
.AndReturn(self.snapshots.list())
cinder.tenant_quota_get(IsA(http.HttpRequest), '1') \
.AndReturn(self.quotas.first())
.AndReturn(self.cinder_quotas.first())
self.mox.ReplayAll()
@ -139,7 +144,8 @@ class QuotaTests(test.APITestCase):
'tenant_quota_get',),
api.network: ('tenant_floating_ip_list',),
quotas: ('is_service_enabled',),
cinder: ('volume_list', 'tenant_quota_get',)})
cinder: ('volume_list', 'volume_snapshot_list',
'tenant_quota_get',)})
def test_tenant_quota_usages_unlimited_quota(self):
inf_quota = self.quotas.first()
inf_quota['ram'] = -1
@ -156,8 +162,10 @@ class QuotaTests(test.APITestCase):
.AndReturn([self.servers.list(), False])
cinder.volume_list(IsA(http.HttpRequest)) \
.AndReturn(self.volumes.list())
cinder.volume_snapshot_list(IsA(http.HttpRequest)) \
.AndReturn(self.snapshots.list())
cinder.tenant_quota_get(IsA(http.HttpRequest), '1') \
.AndReturn(inf_quota)
.AndReturn(self.cinder_quotas.first())
self.mox.ReplayAll()

View File

@ -19,6 +19,7 @@ NOVA_QUOTA_FIELDS = ("metadata_items",
"security_group_rules",)
CINDER_QUOTA_FIELDS = ("volumes",
"snapshots",
"gigabytes",)
QUOTA_FIELDS = NOVA_QUOTA_FIELDS + CINDER_QUOTA_FIELDS
@ -136,8 +137,10 @@ def tenant_quota_usages(request):
if 'volumes' not in disabled_quotas:
volumes = cinder.volume_list(request)
snapshots = cinder.volume_snapshot_list(request)
usages.tally('gigabytes', sum([int(v.size) for v in volumes]))
usages.tally('volumes', len(volumes))
usages.tally('snapshots', len(snapshots))
# Sum our usage based on the flavors of the instances.
for flavor in [flavors[instance.flavor['id']] for instance in instances]: