Use "GiB" and "gibibyte" labels in volume panels

Cinder APIs require size values to be in gibibytes (GiB).
Horizon panels show these values in gigabytes (GB), which
confuses storage admins.

Change-Id: I62ab332d3415f35ead237dd1af5f6a11eb193654
Partially-Implements: blueprint gb-to-gib-conversion
Closes-bug: #1511167
This commit is contained in:
Rich Hagarty 2015-11-10 16:15:53 -08:00
parent f37f32ad06
commit 70ade476b8
12 changed files with 47 additions and 38 deletions

View File

@ -47,7 +47,7 @@ def get_quota_name(quota):
'injected_files': _('Injected Files'), 'injected_files': _('Injected Files'),
'volumes': _('Volumes'), 'volumes': _('Volumes'),
'snapshots': _('Volume Snapshots'), 'snapshots': _('Volume Snapshots'),
'gigabytes': _('Total Size of Volumes and Snapshots (GB)'), 'gigabytes': _('Total Size of Volumes and Snapshots (GiB)'),
'ram': _('RAM (MB)'), 'ram': _('RAM (MB)'),
'floating_ips': _('Floating IPs'), 'floating_ips': _('Floating IPs'),
'security_groups': _('Security Groups'), 'security_groups': _('Security Groups'),
@ -57,7 +57,7 @@ def get_quota_name(quota):
'dm-crypt': _('dm-crypt'), 'dm-crypt': _('dm-crypt'),
'server_group_members': _('Server Group Members'), 'server_group_members': _('Server Group Members'),
'server_groups': _('Server Groups'), 'server_groups': _('Server Groups'),
'backup_gigabytes': _('Backup Gigabytes'), 'backup_gigabytes': _('Backup Size (GiB)'),
'backups': _('Backups'), 'backups': _('Backups'),
'per_volume_gigabytes': _('Per Volume Size (GiB)'), 'per_volume_gigabytes': _('Per Volume Size (GiB)'),
} }

View File

@ -49,7 +49,7 @@ class UpdateDefaultQuotasAction(workflows.Action):
label=_("Security Groups")) label=_("Security Groups"))
gigabytes = forms.IntegerField( gigabytes = forms.IntegerField(
min_value=-1, min_value=-1,
label=_("Total Size of Volumes and Snapshots (GB)")) label=_("Total Size of Volumes and Snapshots (GiB)"))
snapshots = forms.IntegerField(min_value=-1, label=_("Volume Snapshots")) snapshots = forms.IntegerField(min_value=-1, label=_("Volume Snapshots"))
volumes = forms.IntegerField(min_value=-1, label=_("Volumes")) volumes = forms.IntegerField(min_value=-1, label=_("Volumes"))

View File

@ -30,7 +30,7 @@
<hr class="header_rule"> <hr class="header_rule">
<dl class="dl-horizontal"> <dl class="dl-horizontal">
<dt>{% trans "Size" %}</dt> <dt>{% trans "Size" %}</dt>
<dd>{{ snapshot.size }} {% trans "GB" %}</dd> <dd>{{ snapshot.size }} {% trans "GiB" %}</dd>
<dt>{% trans "Created" %}</dt> <dt>{% trans "Created" %}</dt>
<dd>{{ snapshot.created_at|parse_date }}</dd> <dd>{{ snapshot.created_at|parse_date }}</dd>
</dl> </dl>

View File

@ -55,7 +55,7 @@ class ProjectQuotaAction(workflows.Action):
volumes = forms.IntegerField(min_value=-1, label=_("Volumes")) volumes = forms.IntegerField(min_value=-1, label=_("Volumes"))
snapshots = forms.IntegerField(min_value=-1, label=_("Volume Snapshots")) snapshots = forms.IntegerField(min_value=-1, label=_("Volume Snapshots"))
gigabytes = forms.IntegerField( gigabytes = forms.IntegerField(
min_value=-1, label=_("Total Size of Volumes and Snapshots (GB)")) min_value=-1, label=_("Total Size of Volumes and Snapshots (GiB)"))
ram = forms.IntegerField(min_value=-1, label=_("RAM (MB)")) ram = forms.IntegerField(min_value=-1, label=_("RAM (MB)"))
floating_ips = forms.IntegerField(min_value=-1, label=_("Floating IPs")) floating_ips = forms.IntegerField(min_value=-1, label=_("Floating IPs"))
fixed_ips = forms.IntegerField(min_value=-1, label=_("Fixed IPs")) fixed_ips = forms.IntegerField(min_value=-1, label=_("Fixed IPs"))

View File

@ -28,7 +28,7 @@
<hr class="header_rule"> <hr class="header_rule">
<dl class="dl-horizontal"> <dl class="dl-horizontal">
<dt>{% trans "Size" %}</dt> <dt>{% trans "Size" %}</dt>
<dd>{{ snapshot.size }} {% trans "GB" %}</dd> <dd>{{ snapshot.size }} {% trans "GiB" %}</dd>
<dt>{% trans "Created" %}</dt> <dt>{% trans "Created" %}</dt>
<dd>{{ snapshot.created_at|parse_date }}</dd> <dd>{{ snapshot.created_at|parse_date }}</dd>
</dl> </dl>

View File

@ -18,7 +18,7 @@
<hr class="header_rule"> <hr class="header_rule">
<dl class="dl-horizontal"> <dl class="dl-horizontal">
<dt>{% trans "Size" %}</dt> <dt>{% trans "Size" %}</dt>
<dd>{{ volume.size }} {% trans "GB" %}</dd> <dd>{{ volume.size }} {% trans "GiB" %}</dd>
{% if volume.volume_type %} {% if volume.volume_type %}
<dt>{% trans "Type" %}</dt> <dt>{% trans "Type" %}</dt>
<dd>{{ volume.volume_type }}</dd> <dd>{{ volume.volume_type }}</dd>

View File

@ -7,8 +7,8 @@
<h3>{% trans "Volume Limits" %}</h3> <h3>{% trans "Volume Limits" %}</h3>
<div class="quota_title clearfix"> <div class="quota_title clearfix">
<strong>{% trans "Total Gigabytes" %} <span>({{ usages.gigabytesUsed|intcomma }} {% trans "GB" %})</span></strong> <strong>{% trans "Total Gibibytes" %} <span>({{ usages.gigabytesUsed|intcomma }} {% trans "GiB" %})</span></strong>
<p>{{ usages.maxTotalVolumeGigabytes|intcomma|quota:_("GB") }}</p> <p>{{ usages.maxTotalVolumeGigabytes|intcomma|quota:_("GiB") }}</p>
</div> </div>
<div id="quota_size" data-progress-indicator-for="id_new_size" data-quota-limit="{{ usages.maxTotalVolumeGigabytes }}" data-quota-used="{{ usages.gigabytesUsed }}" class="quota_bar"> <div id="quota_size" data-progress-indicator-for="id_new_size" data-quota-limit="{{ usages.maxTotalVolumeGigabytes }}" data-quota-used="{{ usages.gigabytesUsed }}" class="quota_bar">

View File

@ -16,8 +16,8 @@
<h3>{% block head %}{% trans "Volume Limits" %}{% endblock %}</h3> <h3>{% block head %}{% trans "Volume Limits" %}{% endblock %}</h3>
<div class="quota_title clearfix"> <div class="quota_title clearfix">
<strong>{% trans "Total Gigabytes" %} <span>({% block gigabytes_used %}{{ usages.gigabytesUsed|intcomma }}{% endblock %} {% trans "GB" %})</span></strong> <strong>{% trans "Total Gibibytes" %} <span>({% block gigabytes_used %}{{ usages.gigabytesUsed|intcomma }}{% endblock %} {% trans "GiB" %})</span></strong>
<p>{{ usages.maxTotalVolumeGigabytes|intcomma|quota:_("GB") }}</p> <p>{{ usages.maxTotalVolumeGigabytes|intcomma|quota:_("GiB") }}</p>
</div> </div>
<div id="quota_size" data-progress-indicator-for="id_size" data-quota-limit="{{ usages.maxTotalVolumeGigabytes }}" data-quota-used={% block gigabytes_used_progress %}"{{ usages.gigabytesUsed }}"{% endblock %} class="quota_bar"> <div id="quota_size" data-progress-indicator-for="id_size" data-quota-limit="{{ usages.maxTotalVolumeGigabytes }}" data-quota-used={% block gigabytes_used_progress %}"{{ usages.gigabytesUsed }}"{% endblock %} class="quota_bar">

View File

@ -17,8 +17,6 @@
Views for managing volumes. Views for managing volumes.
""" """
from oslo_utils import units
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.forms import ValidationError # noqa from django.forms import ValidationError # noqa
@ -92,7 +90,7 @@ class CreateForm(forms.SelfHandlingForm):
widget=forms.SelectWidget( widget=forms.SelectWidget(
attrs={'class': 'snapshot-selector'}, attrs={'class': 'snapshot-selector'},
data_attrs=('size', 'name'), data_attrs=('size', 'name'),
transform=lambda x: "%s (%sGB)" % (x.name, x.size)), transform=lambda x: "%s (%s GiB)" % (x.name, x.size)),
required=False) required=False)
image_source = forms.ChoiceField( image_source = forms.ChoiceField(
label=_("Use image as a source"), label=_("Use image as a source"),
@ -106,8 +104,7 @@ class CreateForm(forms.SelfHandlingForm):
widget=forms.SelectWidget( widget=forms.SelectWidget(
attrs={'class': 'image-selector'}, attrs={'class': 'image-selector'},
data_attrs=('size', 'name'), data_attrs=('size', 'name'),
transform=lambda x: "%s (%s)" % ( transform=lambda x: "%s (%s GiB)" % (x.name, x.size)),
x.name, filesizeformat(x.size * units.Gi))),
required=False) required=False)
type = forms.ChoiceField( type = forms.ChoiceField(
label=_("Type"), label=_("Type"),
@ -117,7 +114,7 @@ class CreateForm(forms.SelfHandlingForm):
'data-switch-on': 'source', 'data-switch-on': 'source',
'data-source-no_source_type': _('Type'), 'data-source-no_source_type': _('Type'),
'data-source-image_source': _('Type')})) 'data-source-image_source': _('Type')}))
size = forms.IntegerField(min_value=1, initial=1, label=_("Size (GB)")) size = forms.IntegerField(min_value=1, initial=1, label=_("Size (GiB)"))
availability_zone = forms.ChoiceField( availability_zone = forms.ChoiceField(
label=_("Availability Zone"), label=_("Availability Zone"),
required=False, required=False,
@ -144,7 +141,7 @@ class CreateForm(forms.SelfHandlingForm):
pass pass
self.fields['size'].help_text = ( self.fields['size'].help_text = (
_('Volume size must be equal to or greater than the ' _('Volume size must be equal to or greater than the '
'snapshot size (%sGB)') % snapshot.size) 'snapshot size (%sGiB)') % snapshot.size)
del self.fields['image_source'] del self.fields['image_source']
del self.fields['volume_source'] del self.fields['volume_source']
del self.fields['volume_source_type'] del self.fields['volume_source_type']
@ -173,7 +170,7 @@ class CreateForm(forms.SelfHandlingForm):
min_vol_size = min_disk_size min_vol_size = min_disk_size
size_help_text = (_('Volume size must be equal to or ' size_help_text = (_('Volume size must be equal to or '
'greater than the image minimum ' 'greater than the image minimum '
'disk size (%sGB)') 'disk size (%sGiB)')
% min_disk_size) % min_disk_size)
self.fields['size'].initial = min_vol_size self.fields['size'].initial = min_vol_size
self.fields['size'].help_text = size_help_text self.fields['size'].help_text = size_help_text
@ -200,8 +197,8 @@ class CreateForm(forms.SelfHandlingForm):
self.fields['description'].initial = volume.description self.fields['description'].initial = volume.description
min_vol_size = volume.size min_vol_size = volume.size
size_help_text = (_('Volume size must be equal to or greater ' size_help_text = (_('Volume size must be equal to or greater '
'than the origin volume size (%s)') 'than the origin volume size (%sGiB)')
% filesizeformat(volume.size)) % volume.size)
self.fields['size'].initial = min_vol_size self.fields['size'].initial = min_vol_size
self.fields['size'].help_text = size_help_text self.fields['size'].help_text = size_help_text
self.fields['volume_source'].choices = ((volume.id, volume),) self.fields['volume_source'].choices = ((volume.id, volume),)
@ -330,7 +327,7 @@ class CreateForm(forms.SelfHandlingForm):
snapshot_id = snapshot.id snapshot_id = snapshot.id
if (data['size'] < snapshot.size): if (data['size'] < snapshot.size):
error_message = (_('The volume size cannot be less than ' error_message = (_('The volume size cannot be less than '
'the snapshot size (%sGB)') 'the snapshot size (%sGiB)')
% snapshot.size) % snapshot.size)
raise ValidationError(error_message) raise ValidationError(error_message)
az = None az = None
@ -351,7 +348,7 @@ class CreateForm(forms.SelfHandlingForm):
properties.get('min_disk', 0)) properties.get('min_disk', 0))
if (min_disk_size > 0 and data['size'] < min_disk_size): if (min_disk_size > 0 and data['size'] < min_disk_size):
error_message = (_('The volume size cannot be less than ' error_message = (_('The volume size cannot be less than '
'the image minimum disk size (%sGB)') 'the image minimum disk size (%sGiB)')
% min_disk_size) % min_disk_size)
raise ValidationError(error_message) raise ValidationError(error_message)
elif (data.get("volume_source", None) and elif (data.get("volume_source", None) and
@ -362,7 +359,7 @@ class CreateForm(forms.SelfHandlingForm):
if data['size'] < volume.size: if data['size'] < volume.size:
error_message = (_('The volume size cannot be less than ' error_message = (_('The volume size cannot be less than '
'the source volume size (%sGB)') 'the source volume size (%sGiB)')
% volume.size) % volume.size)
raise ValidationError(error_message) raise ValidationError(error_message)
else: else:
@ -370,9 +367,9 @@ class CreateForm(forms.SelfHandlingForm):
data['size'] = int(data['size']) data['size'] = int(data['size'])
if availableGB < data['size']: if availableGB < data['size']:
error_message = _('A volume of %(req)iGB cannot be created as ' error_message = _('A volume of %(req)iGiB cannot be created '
'you only have %(avail)iGB of your quota ' 'as you only have %(avail)iGiB of your '
'available.') 'quota available.')
params = {'req': data['size'], params = {'req': data['size'],
'avail': availableGB} 'avail': availableGB}
raise ValidationError(error_message % params) raise ValidationError(error_message % params)
@ -707,11 +704,11 @@ class ExtendForm(forms.SelfHandlingForm):
required=False, required=False,
) )
orig_size = forms.IntegerField( orig_size = forms.IntegerField(
label=_("Current Size (GB)"), label=_("Current Size (GiB)"),
widget=forms.TextInput(attrs={'readonly': 'readonly'}), widget=forms.TextInput(attrs={'readonly': 'readonly'}),
required=False, required=False,
) )
new_size = forms.IntegerField(label=_("New Size (GB)")) new_size = forms.IntegerField(label=_("New Size (GiB)"))
def clean(self): def clean(self):
cleaned_data = super(ExtendForm, self).clean() cleaned_data = super(ExtendForm, self).clean()
@ -726,8 +723,8 @@ class ExtendForm(forms.SelfHandlingForm):
availableGB = usages['maxTotalVolumeGigabytes'] - \ availableGB = usages['maxTotalVolumeGigabytes'] - \
usages['gigabytesUsed'] usages['gigabytesUsed']
if availableGB < (new_size - orig_size): if availableGB < (new_size - orig_size):
message = _('Volume cannot be extended to %(req)iGB as ' message = _('Volume cannot be extended to %(req)iGiB as '
'you only have %(avail)iGB of your quota ' 'you only have %(avail)iGiB of your quota '
'available.') 'available.')
params = {'req': new_size, 'avail': availableGB} params = {'req': new_size, 'avail': availableGB}
self._errors["new_size"] = self.error_class([message % params]) self._errors["new_size"] = self.error_class([message % params])

View File

@ -303,7 +303,7 @@ class UpdateRow(tables.Row):
def get_size(volume): def get_size(volume):
return _("%sGB") % volume.size return _("%sGiB") % volume.size
def get_attachment_name(request, attachment): def get_attachment_name(request, attachment):

View File

@ -494,7 +494,7 @@ class VolumeViewTests(test.TestCase):
self.assertEqual(res.redirect_chain, []) self.assertEqual(res.redirect_chain, [])
self.assertFormError(res, 'form', None, self.assertFormError(res, 'form', None,
"The volume size cannot be less than the " "The volume size cannot be less than the "
"snapshot size (40GB)") "snapshot size (40GiB)")
@test.create_stubs({cinder: ('volume_create', @test.create_stubs({cinder: ('volume_create',
'volume_type_list', 'volume_type_list',
@ -725,7 +725,7 @@ class VolumeViewTests(test.TestCase):
self.assertEqual(res.redirect_chain, []) self.assertEqual(res.redirect_chain, [])
self.assertFormError(res, 'form', None, self.assertFormError(res, 'form', None,
"The volume size cannot be less than the " "The volume size cannot be less than the "
"image minimum disk size (30GB)") "image minimum disk size (30GiB)")
def test_create_volume_from_image_under_image_min_disk_size(self): def test_create_volume_from_image_under_image_min_disk_size(self):
image = self.images.get(name="protected_images") image = self.images.get(name="protected_images")
@ -791,8 +791,8 @@ class VolumeViewTests(test.TestCase):
url = reverse('horizon:project:volumes:volumes:create') url = reverse('horizon:project:volumes:volumes:create')
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 5000GiB cannot be created as you only'
' have 20GB of your quota available.'] ' have 20GiB 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', @test.create_stubs({cinder: ('volume_snapshot_list',
@ -1578,8 +1578,8 @@ class VolumeViewTests(test.TestCase):
args=[volume.id]) args=[volume.id])
res = self.client.post(url, formData) res = self.client.post(url, formData)
self.assertFormError(res, "form", "new_size", self.assertFormError(res, "form", "new_size",
"Volume cannot be extended to 1000GB as you only " "Volume cannot be extended to 1000GiB as you "
"have 80GB of your quota available.") "only have 80GiB of your quota available.")
@test.create_stubs({cinder: ('volume_backup_supported', @test.create_stubs({cinder: ('volume_backup_supported',
'volume_list', 'volume_list',

View File

@ -0,0 +1,12 @@
---
prelude: >
Cinder defines storage size in gibibytes (GiB), which is inconsistent with Horizon panels that
show/request storage size in gigabytes (GB).
features:
- All Volume related panels in Horizon that previously used the term "GB" and "gigabyte" have been
replaced with 'GiB' and 'gibibyte'.
issues:
- There are also some Nova related panels (e.g. "Instances") that reference storage size in "GB".
These panels will be addressed in subsequent patches.
fixes:
- blueprint gb-to-gib-conversion <https://blueprints.launchpad.net/horizon/+spec/gb-to-gib-conversion/>