diff --git a/openstack_dashboard/api/cinder.py b/openstack_dashboard/api/cinder.py
index d0beae649..d083f00b7 100644
--- a/openstack_dashboard/api/cinder.py
+++ b/openstack_dashboard/api/cinder.py
@@ -77,9 +77,11 @@ def volume_get(request, volume_id):
return volume_data
-def volume_create(request, size, name, description, snapshot_id=None):
+def volume_create(request, size, name, description, volume_type,
+ snapshot_id=None):
return cinderclient(request).volumes.create(size, display_name=name,
- display_description=description, snapshot_id=snapshot_id)
+ display_description=description, volume_type=volume_type,
+ snapshot_id=snapshot_id)
def volume_delete(request, volume_id):
@@ -113,3 +115,15 @@ def tenant_quota_update(request, tenant_id, **kwargs):
def default_quota_get(request, tenant_id):
return QuotaSet(cinderclient(request).quotas.defaults(tenant_id))
+
+
+def volume_type_list(request):
+ return cinderclient(request).volume_types.list()
+
+
+def volume_type_create(request, name):
+ return cinderclient(request).volume_types.create(name)
+
+
+def volume_type_delete(request, volume_type_id):
+ return cinderclient(request).volume_types.delete(volume_type_id)
diff --git a/openstack_dashboard/dashboards/admin/volumes/forms.py b/openstack_dashboard/dashboards/admin/volumes/forms.py
new file mode 100644
index 000000000..9d0886fc3
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/volumes/forms.py
@@ -0,0 +1,44 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+#
+# 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 django.utils.translation import ugettext_lazy as _
+
+from horizon import exceptions
+from horizon import forms
+from horizon import messages
+
+from openstack_dashboard.api import cinder
+
+
+class CreateVolumeType(forms.SelfHandlingForm):
+ name = forms.CharField(max_length="255", label=_("Name"))
+
+ def handle(self, request, data):
+ try:
+ # Remove any new lines in the public key
+ volume_type = cinder.volume_type_create(request,
+ data['name'])
+ messages.success(request, _('Successfully created volume type: %s')
+ % data['name'])
+ return volume_type
+ except:
+ exceptions.handle(request,
+ _('Unable to create volume type.'))
+ return False
diff --git a/openstack_dashboard/dashboards/admin/volumes/tables.py b/openstack_dashboard/dashboards/admin/volumes/tables.py
index 133743695..def5bb2f0 100644
--- a/openstack_dashboard/dashboards/admin/volumes/tables.py
+++ b/openstack_dashboard/dashboards/admin/volumes/tables.py
@@ -1,10 +1,26 @@
from django.utils.translation import ugettext_lazy as _
from horizon import tables
+from openstack_dashboard.api import cinder
from openstack_dashboard.dashboards.project.volumes.tables import (UpdateRow,
VolumesTable as _VolumesTable, DeleteVolume)
+class CreateVolumeType(tables.LinkAction):
+ name = "create"
+ verbose_name = _("Create Volume Type")
+ url = "horizon:admin:volumes:create_type"
+ classes = ("ajax-modal", "btn-create")
+
+
+class DeleteVolumeType(tables.DeleteAction):
+ data_type_singular = _("Volume Type")
+ data_type_plural = _("Volume Types")
+
+ def delete(self, request, obj_id):
+ cinder.volume_type_delete(request, obj_id)
+
+
class VolumesTable(_VolumesTable):
name = tables.Column("display_name",
verbose_name=_("Name"),
@@ -17,3 +33,20 @@ class VolumesTable(_VolumesTable):
row_class = UpdateRow
table_actions = (DeleteVolume,)
row_actions = (DeleteVolume,)
+
+
+class VolumeTypesTable(tables.DataTable):
+ name = tables.Column("name",
+ verbose_name=_("Name"))
+
+ def get_object_display(self, vol_type):
+ return vol_type.name
+
+ def get_object_id(self, vol_type):
+ return str(vol_type.id)
+
+ class Meta:
+ name = "volume_types"
+ verbose_name = _("Volume Types")
+ table_actions = (CreateVolumeType, DeleteVolumeType,)
+ row_actions = (DeleteVolumeType,)
diff --git a/openstack_dashboard/dashboards/admin/volumes/templates/volumes/_create_volume_type.html b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/_create_volume_type.html
new file mode 100644
index 000000000..9a51878e4
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/_create_volume_type.html
@@ -0,0 +1,29 @@
+{% extends "horizon/common/_modal_form.html" %}
+{% load i18n %}
+
+{% block form_id %}{% endblock %}
+{% block form_action %}{% url horizon:admin:volumes:create_type %}{% endblock %}
+
+{% block modal_id %}create_volume_type_modal{% endblock %}
+{% block modal-header %}{% trans "Create Volume Type" %}{% endblock %}
+
+{% block modal-body %}
+
+
+ {% include "horizon/common/_form_fields.html" %}
+
+
+
+
{% trans "Description" %}:
+
{% blocktrans %}
+ The volume type defines the characteristics of a volume.
+ It usually maps to a set of capabilities of the storage back-end driver to be used for this volume.
+ Examples: "Performance", "SSD", "Backup", etc.
+ {% endblocktrans %}
+
+{% endblock %}
+
+{% block modal-footer %}
+
+ {% trans "Cancel" %}
+{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/volumes/templates/volumes/create_volume_type.html b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/create_volume_type.html
new file mode 100644
index 000000000..d60b695d5
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/create_volume_type.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Create Volume Type" %}{% endblock %}
+
+{% block page_header %}
+ {% include "horizon/common/_page_header.html" with title=_("Create a Volume Type") %}
+{% endblock page_header %}
+
+{% block main %}
+ {% include 'admin/volumes/_create_volume_type.html' %}
+{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/volumes/templates/volumes/index.html b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/index.html
index 4079378aa..531571dec 100644
--- a/openstack_dashboard/dashboards/admin/volumes/templates/volumes/index.html
+++ b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/index.html
@@ -7,7 +7,11 @@
{% endblock page_header %}
{% block main %}
- {{ table.render }}
+
+ {{ volumes_table.render }}
+
+
+
+ {{ volume_types_table.render }}
+
{% endblock %}
-
-
diff --git a/openstack_dashboard/dashboards/admin/volumes/tests.py b/openstack_dashboard/dashboards/admin/volumes/tests.py
index fd0dafa58..f9ae005ca 100644
--- a/openstack_dashboard/dashboards/admin/volumes/tests.py
+++ b/openstack_dashboard/dashboards/admin/volumes/tests.py
@@ -19,15 +19,21 @@ from django.core.urlresolvers import reverse
from mox import IsA
from openstack_dashboard import api
+from openstack_dashboard.api import cinder
from openstack_dashboard.test import helpers as test
class VolumeTests(test.BaseAdminViewTests):
- @test.create_stubs({api: ('server_list', 'volume_list',)})
+ @test.create_stubs({api.nova: ('server_list',),
+ cinder: ('volume_list',
+ 'volume_type_list',)})
def test_index(self):
- api.volume_list(IsA(http.HttpRequest), search_opts={
- 'all_tenants': 1}).AndReturn(self.volumes.list())
- api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list())
+ cinder.volume_list(IsA(http.HttpRequest), search_opts={
+ 'all_tenants': 1}).AndReturn(self.volumes.list())
+ api.nova.server_list(IsA(http.HttpRequest)).\
+ AndReturn(self.servers.list())
+ cinder.volume_type_list(IsA(http.HttpRequest)).\
+ AndReturn(self.volume_types.list())
self.mox.ReplayAll()
@@ -37,3 +43,43 @@ class VolumeTests(test.BaseAdminViewTests):
volumes = res.context['volumes_table'].data
self.assertItemsEqual(volumes, self.volumes.list())
+
+ @test.create_stubs({cinder: ('volume_type_create',)})
+ def test_create_volume_type(self):
+ formData = {'name': 'volume type 1'}
+ cinder.volume_type_create(IsA(http.HttpRequest),
+ formData['name']).\
+ AndReturn(self.volume_types.first())
+ self.mox.ReplayAll()
+
+ res = self.client.post(reverse('horizon:admin:volumes:create_type'),
+ formData)
+
+ redirect = reverse('horizon:admin:volumes:index')
+ self.assertNoFormErrors(res)
+ self.assertRedirectsNoFollow(res, redirect)
+
+ @test.create_stubs({api.nova: ('server_list',),
+ cinder: ('volume_list',
+ 'volume_type_list',
+ 'volume_type_delete',)})
+ def test_delete_volume_type(self):
+ volume_type = self.volume_types.first()
+ formData = {'action': 'volume_types__delete__%s' % volume_type.id}
+
+ cinder.volume_list(IsA(http.HttpRequest), search_opts={
+ 'all_tenants': 1}).AndReturn(self.volumes.list())
+ api.nova.server_list(IsA(http.HttpRequest)).\
+ AndReturn(self.servers.list())
+ cinder.volume_type_list(IsA(http.HttpRequest)).\
+ AndReturn(self.volume_types.list())
+ cinder.volume_type_delete(IsA(http.HttpRequest),
+ str(volume_type.id))
+ self.mox.ReplayAll()
+
+ res = self.client.post(reverse('horizon:admin:volumes:index'),
+ formData)
+
+ redirect = reverse('horizon:admin:volumes:index')
+ self.assertNoFormErrors(res)
+ self.assertRedirectsNoFollow(res, redirect)
diff --git a/openstack_dashboard/dashboards/admin/volumes/urls.py b/openstack_dashboard/dashboards/admin/volumes/urls.py
index f42c753f0..5c0280cb5 100644
--- a/openstack_dashboard/dashboards/admin/volumes/urls.py
+++ b/openstack_dashboard/dashboards/admin/volumes/urls.py
@@ -1,8 +1,9 @@
from django.conf.urls.defaults import patterns, url
-from .views import IndexView, DetailView
+from .views import IndexView, DetailView, CreateVolumeTypeView
urlpatterns = patterns('',
url(r'^$', IndexView.as_view(), name='index'),
+ url(r'^create_type$', CreateVolumeTypeView.as_view(), name='create_type'),
url(r'^(?P[^/]+)/$', DetailView.as_view(), name='detail'),
)
diff --git a/openstack_dashboard/dashboards/admin/volumes/views.py b/openstack_dashboard/dashboards/admin/volumes/views.py
index 5682aa8a9..68e844d8e 100644
--- a/openstack_dashboard/dashboards/admin/volumes/views.py
+++ b/openstack_dashboard/dashboards/admin/volumes/views.py
@@ -18,22 +18,48 @@
Admin views for managing volumes.
"""
+from django.utils.translation import ugettext_lazy as _
+from django.core.urlresolvers import reverse
from openstack_dashboard.dashboards.project.volumes.views import \
- IndexView as _IndexView, DetailView as _DetailView
-from .tables import VolumesTable
+ VolumeTableMixIn, DetailView as _DetailView
+from openstack_dashboard.api import cinder
+
+from .tables import VolumesTable, VolumeTypesTable
+from .forms import CreateVolumeType
+from horizon import exceptions
+from horizon import forms
+from horizon import tables
-class IndexView(_IndexView):
- table_class = VolumesTable
+class IndexView(tables.MultiTableView, VolumeTableMixIn):
+ table_classes = (VolumesTable, VolumeTypesTable)
template_name = "admin/volumes/index.html"
- def get_data(self):
+ def get_volumes_data(self):
volumes = self._get_volumes(search_opts={'all_tenants': 1})
instances = self._get_instances()
self._set_id_if_nameless(volumes, instances)
self._set_attachments_string(volumes, instances)
return volumes
+ def get_volume_types_data(self):
+ try:
+ volume_types = cinder.volume_type_list(self.request)
+ except:
+ volume_types = []
+ exceptions.handle(self.request,
+ _("Unable to retrieve volume types"))
+ return volume_types
+
class DetailView(_DetailView):
template_name = "admin/volumes/detail.html"
+
+
+class CreateVolumeTypeView(forms.ModalFormView):
+ form_class = CreateVolumeType
+ template_name = 'admin/volumes/create_volume_type.html'
+ success_url = 'horizon:admin:volumes:index'
+
+ def get_success_url(self):
+ return reverse(self.success_url)
diff --git a/openstack_dashboard/dashboards/project/images_and_snapshots/volume_snapshots/tests.py b/openstack_dashboard/dashboards/project/images_and_snapshots/volume_snapshots/tests.py
index 1cdba5b29..51a331d25 100644
--- a/openstack_dashboard/dashboards/project/images_and_snapshots/volume_snapshots/tests.py
+++ b/openstack_dashboard/dashboards/project/images_and_snapshots/volume_snapshots/tests.py
@@ -22,7 +22,7 @@ from django import http
from django.core.urlresolvers import reverse
from mox import IsA
-from openstack_dashboard import api
+from openstack_dashboard.api import cinder
from openstack_dashboard.test import helpers as test
@@ -38,16 +38,16 @@ class VolumeSnapshotsViewTests(test.TestCase):
self.assertTemplateUsed(res, 'project/volumes/create_snapshot.html')
+ @test.create_stubs({cinder: ('volume_snapshot_create',)})
def test_create_snapshot_post(self):
volume = self.volumes.first()
snapshot = self.volume_snapshots.first()
- self.mox.StubOutWithMock(api, 'volume_snapshot_create')
- api.volume_snapshot_create(IsA(http.HttpRequest),
- volume.id,
- snapshot.display_name,
- snapshot.display_description) \
- .AndReturn(snapshot)
+ cinder.volume_snapshot_create(IsA(http.HttpRequest),
+ volume.id,
+ snapshot.display_name,
+ snapshot.display_description) \
+ .AndReturn(snapshot)
self.mox.ReplayAll()
formData = {'method': 'CreateSnapshotForm',
diff --git a/openstack_dashboard/dashboards/project/volumes/forms.py b/openstack_dashboard/dashboards/project/volumes/forms.py
index c9d0ffd36..3a121fd2d 100644
--- a/openstack_dashboard/dashboards/project/volumes/forms.py
+++ b/openstack_dashboard/dashboards/project/volumes/forms.py
@@ -28,6 +28,8 @@ class CreateForm(forms.SelfHandlingForm):
name = forms.CharField(max_length="255", label=_("Volume Name"))
description = forms.CharField(widget=forms.Textarea,
label=_("Description"), required=False)
+ type = forms.ChoiceField(label=_("Type"),
+ required=False)
size = forms.IntegerField(min_value=1, label=_("Size (GB)"))
snapshot_source = forms.ChoiceField(label=_("Use snapshot as a source"),
widget=SelectWidget(
@@ -40,6 +42,10 @@ class CreateForm(forms.SelfHandlingForm):
def __init__(self, request, *args, **kwargs):
super(CreateForm, self).__init__(request, *args, **kwargs)
+ volume_types = cinder.volume_type_list(request)
+ self.fields['type'].choices = [("", "")] + \
+ [(type.name, type.name)
+ for type in volume_types]
if ("snapshot_id" in request.GET):
try:
snapshot = self.get_snapshot(request,
@@ -48,6 +54,13 @@ class CreateForm(forms.SelfHandlingForm):
self.fields['size'].initial = snapshot.size
self.fields['snapshot_source'].choices = ((snapshot.id,
snapshot),)
+ try:
+ # Set the volume type from the original volume
+ orig_volume = cinder.volume_get(request,
+ snapshot.volume_id)
+ self.fields['type'].initial = orig_volume.volume_type
+ except:
+ pass
self.fields['size'].help_text = _('Volume size must be equal '
'to or greater than the snapshot size (%sGB)'
% snapshot.size)
@@ -56,7 +69,7 @@ class CreateForm(forms.SelfHandlingForm):
_('Unable to load the specified snapshot.'))
else:
try:
- snapshots = api.volume_snapshot_list(request)
+ snapshots = cinder.volume_snapshot_list(request)
if snapshots:
choices = [('', _("Choose a snapshot"))] + \
[(s.id, s) for s in snapshots]
@@ -102,19 +115,22 @@ class CreateForm(forms.SelfHandlingForm):
' volumes.')
raise ValidationError(error_message)
- volume = api.volume_create(request,
- data['size'],
- data['name'],
- data['description'],
- snapshot_id=snapshot_id)
+ volume = cinder.volume_create(request,
+ data['size'],
+ data['name'],
+ data['description'],
+ data['type'],
+ snapshot_id=snapshot_id)
message = 'Creating volume "%s"' % data['name']
messages.info(request, message)
return volume
except ValidationError, e:
- return self.api_error(e.messages[0])
+ self.api_error(e.messages[0])
+ return False
except:
exceptions.handle(request, ignore=True)
- return self.api_error(_("Unable to create volume."))
+ self.api_error(_("Unable to create volume."))
+ return False
@memoized
def get_snapshot(self, request, id):
@@ -176,7 +192,8 @@ class AttachForm(forms.SelfHandlingForm):
data['volume_id'],
data['instance'],
data.get('device', ''))
- vol_name = api.volume_get(request, data['volume_id']).display_name
+ vol_name = cinder.volume_get(request,
+ data['volume_id']).display_name
message = _('Attaching volume %(vol)s to instance '
'%(inst)s on %(dev)s.') % {"vol": vol_name,
@@ -206,10 +223,10 @@ class CreateSnapshotForm(forms.SelfHandlingForm):
def handle(self, request, data):
try:
- snapshot = api.volume_snapshot_create(request,
- data['volume_id'],
- data['name'],
- data['description'])
+ snapshot = cinder.volume_snapshot_create(request,
+ data['volume_id'],
+ data['name'],
+ data['description'])
message = _('Creating volume snapshot "%s"') % data['name']
messages.info(request, message)
diff --git a/openstack_dashboard/dashboards/project/volumes/tables.py b/openstack_dashboard/dashboards/project/volumes/tables.py
index 43349907a..b2ddbbbe5 100644
--- a/openstack_dashboard/dashboards/project/volumes/tables.py
+++ b/openstack_dashboard/dashboards/project/volumes/tables.py
@@ -26,6 +26,7 @@ from horizon import exceptions
from horizon import tables
from openstack_dashboard import api
+from openstack_dashboard.api import cinder
LOG = logging.getLogger(__name__)
@@ -41,7 +42,7 @@ class DeleteVolume(tables.DeleteAction):
obj = self.table.get_object_by_id(obj_id)
name = self.table.get_object_display(obj)
try:
- api.volume_delete(request, obj_id)
+ cinder.volume_delete(request, obj_id)
except:
msg = _('Unable to delete volume "%s". One or more snapshots '
'depend on it.')
@@ -85,7 +86,7 @@ class UpdateRow(tables.Row):
ajax = True
def get_data(self, request, volume_id):
- volume = api.volume_get(request, volume_id)
+ volume = cinder.volume_get(request, volume_id)
return volume
@@ -133,6 +134,10 @@ class AttachmentColumn(tables.Column):
return safestring.mark_safe(", ".join(attachments))
+def get_volume_type(volume):
+ return volume.volume_type if volume.volume_type != "None" else None
+
+
class VolumesTableBase(tables.DataTable):
STATUS_CHOICES = (
("in-use", True),
@@ -163,6 +168,9 @@ class VolumesTable(VolumesTableBase):
name = tables.Column("display_name",
verbose_name=_("Name"),
link="horizon:project:volumes:detail")
+ volume_type = tables.Column(get_volume_type,
+ verbose_name=_("Type"),
+ empty_value="-")
attachments = AttachmentColumn("attachments",
verbose_name=_("Attached To"))
diff --git a/openstack_dashboard/dashboards/project/volumes/tests.py b/openstack_dashboard/dashboards/project/volumes/tests.py
index f1959e8c3..41b2de300 100644
--- a/openstack_dashboard/dashboards/project/volumes/tests.py
+++ b/openstack_dashboard/dashboards/project/volumes/tests.py
@@ -32,25 +32,32 @@ from openstack_dashboard.usage import quotas
class VolumeViewTests(test.TestCase):
- @test.create_stubs({api: ('volume_create',
- 'volume_snapshot_list'),
+ @test.create_stubs({cinder: ('volume_create',
+ 'volume_snapshot_list',
+ 'volume_type_list',),
quotas: ('tenant_quota_usages',)})
def test_create_volume(self):
volume = self.volumes.first()
+ volume_type = self.volume_types.first()
usage = {'gigabytes': {'available': 250}, 'volumes': {'available': 6}}
formData = {'name': u'A Volume I Am Making',
'description': u'This is a volume I am making for a test.',
'method': u'CreateForm',
- 'size': 50, 'snapshot_source': ''}
+ 'type': volume_type.name,
+ 'size': 50,
+ 'snapshot_source': ''}
+ cinder.volume_type_list(IsA(http.HttpRequest)).\
+ AndReturn(self.volume_types.list())
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
- api.volume_snapshot_list(IsA(http.HttpRequest)).\
- AndReturn(self.volume_snapshots.list())
- api.volume_create(IsA(http.HttpRequest),
- formData['size'],
- formData['name'],
- formData['description'],
- snapshot_id=None).AndReturn(volume)
+ cinder.volume_snapshot_list(IsA(http.HttpRequest)).\
+ AndReturn(self.volume_snapshots.list())
+ cinder.volume_create(IsA(http.HttpRequest),
+ formData['size'],
+ formData['name'],
+ formData['description'],
+ formData['type'],
+ snapshot_id=None).AndReturn(volume)
self.mox.ReplayAll()
@@ -60,9 +67,11 @@ class VolumeViewTests(test.TestCase):
redirect_url = reverse('horizon:project:volumes:index')
self.assertRedirectsNoFollow(res, redirect_url)
- @test.create_stubs({api: ('volume_create',
- 'volume_snapshot_list'),
- cinder: ('volume_snapshot_get',),
+ @test.create_stubs({cinder: ('volume_create',
+ 'volume_snapshot_list',
+ 'volume_snapshot_get',
+ 'volume_get',
+ 'volume_type_list',),
quotas: ('tenant_quota_usages',)})
def test_create_volume_from_snapshot(self):
volume = self.volumes.first()
@@ -71,30 +80,40 @@ class VolumeViewTests(test.TestCase):
formData = {'name': u'A Volume I Am Making',
'description': u'This is a volume I am making for a test.',
'method': u'CreateForm',
- 'size': 50, 'snapshot_source': snapshot.id}
+ 'size': 50,
+ 'type': '',
+ 'snapshot_source': snapshot.id}
# first call- with url param
+ cinder.volume_type_list(IsA(http.HttpRequest)).\
+ AndReturn(self.volume_types.list())
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
cinder.volume_snapshot_get(IsA(http.HttpRequest),
str(snapshot.id)).AndReturn(snapshot)
- api.volume_create(IsA(http.HttpRequest),
- formData['size'],
- formData['name'],
- formData['description'],
- snapshot_id=snapshot.id).\
- AndReturn(volume)
+ cinder.volume_get(IsA(http.HttpRequest), snapshot.volume_id).\
+ AndReturn(self.volumes.first())
+ cinder.volume_create(IsA(http.HttpRequest),
+ formData['size'],
+ formData['name'],
+ formData['description'],
+ '',
+ snapshot_id=snapshot.id).\
+ AndReturn(volume)
# second call- with dropdown
+ cinder.volume_type_list(IsA(http.HttpRequest)).\
+ AndReturn(self.volume_types.list())
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
- api.volume_snapshot_list(IsA(http.HttpRequest)).\
+ cinder.volume_snapshot_list(IsA(http.HttpRequest)).\
AndReturn(self.volume_snapshots.list())
cinder.volume_snapshot_get(IsA(http.HttpRequest),
str(snapshot.id)).AndReturn(snapshot)
- api.volume_create(IsA(http.HttpRequest),
- formData['size'],
- formData['name'],
- formData['description'],
- snapshot_id=snapshot.id).\
- AndReturn(volume)
+ cinder.volume_create(IsA(http.HttpRequest),
+ formData['size'],
+ formData['name'],
+ formData['description'],
+ '',
+ snapshot_id=snapshot.id).\
+ AndReturn(volume)
self.mox.ReplayAll()
@@ -114,7 +133,9 @@ class VolumeViewTests(test.TestCase):
redirect_url = reverse('horizon:project:volumes:index')
self.assertRedirectsNoFollow(res, redirect_url)
- @test.create_stubs({cinder: ('volume_snapshot_get',),
+ @test.create_stubs({cinder: ('volume_snapshot_get',
+ 'volume_type_list',
+ 'volume_get',),
quotas: ('tenant_quota_usages',)})
def test_create_volume_from_snapshot_invalid_size(self):
usage = {'gigabytes': {'available': 250}, 'volumes': {'available': 6}}
@@ -124,9 +145,13 @@ class VolumeViewTests(test.TestCase):
'method': u'CreateForm',
'size': 20, 'snapshot_source': snapshot.id}
+ cinder.volume_type_list(IsA(http.HttpRequest)).\
+ AndReturn(self.volume_types.list())
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
cinder.volume_snapshot_get(IsA(http.HttpRequest),
str(snapshot.id)).AndReturn(snapshot)
+ cinder.volume_get(IsA(http.HttpRequest), snapshot.volume_id).\
+ AndReturn(self.volumes.first())
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
self.mox.ReplayAll()
@@ -140,7 +165,7 @@ class VolumeViewTests(test.TestCase):
"The volume size cannot be less than the "
"snapshot size (40GB)")
- @test.create_stubs({api: ('volume_snapshot_list',),
+ @test.create_stubs({cinder: ('volume_snapshot_list', 'volume_type_list',),
quotas: ('tenant_quota_usages',)})
def test_create_volume_gb_used_over_alloted_quota(self):
usage = {'gigabytes': {'available': 100, 'used': 20}}
@@ -149,9 +174,11 @@ class VolumeViewTests(test.TestCase):
'method': u'CreateForm',
'size': 5000}
+ cinder.volume_type_list(IsA(http.HttpRequest)).\
+ AndReturn(self.volume_types.list())
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
- api.volume_snapshot_list(IsA(http.HttpRequest)).\
- AndReturn(self.volume_snapshots.list())
+ cinder.volume_snapshot_list(IsA(http.HttpRequest)).\
+ AndReturn(self.volume_snapshots.list())
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
self.mox.ReplayAll()
@@ -163,7 +190,7 @@ class VolumeViewTests(test.TestCase):
' have 100GB of your quota available.']
self.assertEqual(res.context['form'].errors['__all__'], expected_error)
- @test.create_stubs({api: ('volume_snapshot_list',),
+ @test.create_stubs({cinder: ('volume_snapshot_list', 'volume_type_list',),
quotas: ('tenant_quota_usages',)})
def test_create_volume_number_over_alloted_quota(self):
usage = {'gigabytes': {'available': 100, 'used': 20},
@@ -173,9 +200,11 @@ class VolumeViewTests(test.TestCase):
'method': u'CreateForm',
'size': 10}
+ cinder.volume_type_list(IsA(http.HttpRequest)).\
+ AndReturn(self.volume_types.list())
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
- api.volume_snapshot_list(IsA(http.HttpRequest)).\
- AndReturn(self.volume_snapshots.list())
+ cinder.volume_snapshot_list(IsA(http.HttpRequest)).\
+ AndReturn(self.volume_snapshots.list())
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(usage)
self.mox.ReplayAll()
@@ -187,21 +216,23 @@ class VolumeViewTests(test.TestCase):
' volumes.']
self.assertEqual(res.context['form'].errors['__all__'], expected_error)
- @test.create_stubs({api: ('volume_list',
- 'volume_delete',
- 'server_list')})
+ @test.create_stubs({cinder: ('volume_list',
+ 'volume_delete',),
+ api.nova: ('server_list',)})
def test_delete_volume(self):
volume = self.volumes.first()
formData = {'action':
'volumes__delete__%s' % volume.id}
- api.volume_list(IsA(http.HttpRequest), search_opts=None).\
- AndReturn(self.volumes.list())
- api.volume_delete(IsA(http.HttpRequest), volume.id)
- api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list())
- api.volume_list(IsA(http.HttpRequest), search_opts=None).\
- AndReturn(self.volumes.list())
- api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list())
+ cinder.volume_list(IsA(http.HttpRequest), search_opts=None).\
+ AndReturn(self.volumes.list())
+ cinder.volume_delete(IsA(http.HttpRequest), volume.id)
+ api.nova.server_list(IsA(http.HttpRequest)).\
+ AndReturn(self.servers.list())
+ cinder.volume_list(IsA(http.HttpRequest), search_opts=None).\
+ AndReturn(self.volumes.list())
+ api.nova.server_list(IsA(http.HttpRequest)).\
+ AndReturn(self.servers.list())
self.mox.ReplayAll()
@@ -209,9 +240,9 @@ class VolumeViewTests(test.TestCase):
res = self.client.post(url, formData, follow=True)
self.assertMessageCount(res, count=0)
- @test.create_stubs({api: ('volume_list',
- 'volume_delete',
- 'server_list')})
+ @test.create_stubs({cinder: ('volume_list',
+ 'volume_delete',),
+ api.nova: ('server_list',)})
def test_delete_volume_error_existing_snapshot(self):
volume = self.volumes.first()
formData = {'action':
@@ -219,14 +250,16 @@ class VolumeViewTests(test.TestCase):
exc = self.exceptions.cinder.__class__(400,
"error: dependent snapshots")
- api.volume_list(IsA(http.HttpRequest), search_opts=None).\
- AndReturn(self.volumes.list())
- api.volume_delete(IsA(http.HttpRequest), volume.id). \
- AndRaise(exc)
- api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list())
- api.volume_list(IsA(http.HttpRequest), search_opts=None).\
- AndReturn(self.volumes.list())
- api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list())
+ cinder.volume_list(IsA(http.HttpRequest), search_opts=None).\
+ AndReturn(self.volumes.list())
+ cinder.volume_delete(IsA(http.HttpRequest), volume.id).\
+ AndRaise(exc)
+ api.nova.server_list(IsA(http.HttpRequest)).\
+ AndReturn(self.servers.list())
+ cinder.volume_list(IsA(http.HttpRequest), search_opts=None).\
+ AndReturn(self.volumes.list())
+ api.nova.server_list(IsA(http.HttpRequest)).\
+ AndReturn(self.servers.list())
self.mox.ReplayAll()
@@ -238,12 +271,12 @@ class VolumeViewTests(test.TestCase):
u'One or more snapshots depend on it.' %
volume.display_name)
- @test.create_stubs({api: ('volume_get',), api.nova: ('server_list',)})
+ @test.create_stubs({cinder: ('volume_get',), api.nova: ('server_list',)})
def test_edit_attachments(self):
volume = self.volumes.first()
servers = self.servers.list()
- api.volume_get(IsA(http.HttpRequest), volume.id).AndReturn(volume)
+ cinder.volume_get(IsA(http.HttpRequest), volume.id).AndReturn(volume)
api.nova.server_list(IsA(http.HttpRequest)).AndReturn(servers)
self.mox.ReplayAll()
@@ -258,7 +291,7 @@ class VolumeViewTests(test.TestCase):
self.assertTrue(isinstance(form.fields['device'].widget,
widgets.TextInput))
- @test.create_stubs({api: ('volume_get',), api.nova: ('server_list',)})
+ @test.create_stubs({cinder: ('volume_get',), api.nova: ('server_list',)})
def test_edit_attachments_cannot_set_mount_point(self):
PREV = settings.OPENSTACK_HYPERVISOR_FEATURES['can_set_mount_point']
settings.OPENSTACK_HYPERVISOR_FEATURES['can_set_mount_point'] = False
@@ -266,7 +299,7 @@ class VolumeViewTests(test.TestCase):
volume = self.volumes.first()
servers = self.servers.list()
- api.volume_get(IsA(http.HttpRequest), volume.id).AndReturn(volume)
+ cinder.volume_get(IsA(http.HttpRequest), volume.id).AndReturn(volume)
api.nova.server_list(IsA(http.HttpRequest)).AndReturn(servers)
self.mox.ReplayAll()
@@ -278,14 +311,14 @@ class VolumeViewTests(test.TestCase):
widgets.HiddenInput))
settings.OPENSTACK_HYPERVISOR_FEATURES['can_set_mount_point'] = PREV
- @test.create_stubs({api: ('volume_get',),
+ @test.create_stubs({cinder: ('volume_get',),
api.nova: ('server_get', 'server_list',)})
def test_edit_attachments_attached_volume(self):
server = self.servers.first()
volume = self.volumes.list()[0]
- api.volume_get(IsA(http.HttpRequest), volume.id) \
- .AndReturn(volume)
+ cinder.volume_get(IsA(http.HttpRequest), volume.id) \
+ .AndReturn(volume)
api.nova.server_list(IsA(http.HttpRequest)) \
.AndReturn(self.servers.list())
diff --git a/openstack_dashboard/dashboards/project/volumes/views.py b/openstack_dashboard/dashboards/project/volumes/views.py
index a9516cc06..1c4cb239f 100644
--- a/openstack_dashboard/dashboards/project/volumes/views.py
+++ b/openstack_dashboard/dashboards/project/volumes/views.py
@@ -30,6 +30,7 @@ from horizon import tables
from horizon import tabs
from openstack_dashboard import api
+from openstack_dashboard.api import cinder
from openstack_dashboard.usage import quotas
from .forms import CreateForm, AttachForm, CreateSnapshotForm
from .tables import AttachmentsTable, VolumesTable
@@ -39,22 +40,18 @@ from .tabs import VolumeDetailTabs
LOG = logging.getLogger(__name__)
-class IndexView(tables.DataTableView):
- table_class = VolumesTable
- template_name = 'project/volumes/index.html'
-
+class VolumeTableMixIn(object):
def _get_volumes(self, search_opts=None):
try:
- return api.volume_list(self.request, search_opts=search_opts)
+ return cinder.volume_list(self.request, search_opts=search_opts)
except:
exceptions.handle(self.request,
_('Unable to retrieve volume list.'))
def _get_instances(self):
try:
- return api.server_list(self.request)
+ return api.nova.server_list(self.request)
except:
- instance_list = []
exceptions.handle(self.request,
_("Unable to retrieve volume/instance "
"attachment information"))
@@ -73,6 +70,11 @@ class IndexView(tables.DataTableView):
server_id = att.get('server_id', None)
att['instance'] = instances.get(server_id, None)
+
+class IndexView(tables.DataTableView, VolumeTableMixIn):
+ table_class = VolumesTable
+ template_name = 'project/volumes/index.html'
+
def get_data(self):
volumes = self._get_volumes()
instances = self._get_instances()
@@ -124,7 +126,7 @@ class EditAttachmentsView(tables.DataTableView, forms.ModalFormView):
if not hasattr(self, "_object"):
volume_id = self.kwargs['volume_id']
try:
- self._object = api.volume_get(self.request, volume_id)
+ self._object = cinder.volume_get(self.request, volume_id)
except:
self._object = None
exceptions.handle(self.request,
diff --git a/openstack_dashboard/test/test_data/nova_data.py b/openstack_dashboard/test/test_data/nova_data.py
index 8b8f12842..dbe46f350 100644
--- a/openstack_dashboard/test/test_data/nova_data.py
+++ b/openstack_dashboard/test/test_data/nova_data.py
@@ -14,7 +14,8 @@
import json
-from novaclient.v1_1 import (flavors, keypairs, servers, volumes, quotas,
+from novaclient.v1_1 import (flavors, keypairs, servers, volumes,
+ volume_types, quotas,
floating_ips, usage, certs,
volume_snapshots as vol_snaps,
security_group_rules as rules,
@@ -145,6 +146,7 @@ def data(TEST):
TEST.usages = TestDataContainer()
TEST.certs = TestDataContainer()
TEST.volume_snapshots = TestDataContainer()
+ TEST.volume_types = TestDataContainer()
# Volumes
volume = volumes.Volume(volumes.VolumeManager(None),
@@ -154,6 +156,7 @@ def data(TEST):
size=40,
display_name='Volume name',
created_at='2012-04-01 10:30:00',
+ volume_type=None,
attachments=[]))
nameless_volume = volumes.Volume(volumes.VolumeManager(None),
dict(id="3b189ac8-9166-ac7f-90c9-16c8bf9e01ac",
@@ -164,6 +167,7 @@ def data(TEST):
display_description='',
device="/dev/hda",
created_at='2010-11-21 18:34:25',
+ volume_type='vol_type_1',
attachments=[{"id": "1", "server_id": '1',
"device": "/dev/hda"}]))
attached_volume = volumes.Volume(volumes.VolumeManager(None),
@@ -175,12 +179,21 @@ def data(TEST):
display_description='',
device="/dev/hdk",
created_at='2011-05-01 11:54:33',
+ volume_type='vol_type_2',
attachments=[{"id": "2", "server_id": '1',
"device": "/dev/hdk"}]))
TEST.volumes.add(volume)
TEST.volumes.add(nameless_volume)
TEST.volumes.add(attached_volume)
+ vol_type1 = volume_types.VolumeType(volume_types.VolumeTypeManager(None),
+ {'id': 1,
+ 'name': 'vol_type_1'})
+ vol_type2 = volume_types.VolumeType(volume_types.VolumeTypeManager(None),
+ {'id': 2,
+ 'name': 'vol_type_2'})
+ TEST.volume_types.add(vol_type1, vol_type2)
+
# Flavors
flavor_1 = flavors.Flavor(flavors.FlavorManager(None),
{'id': "1",