Volume Types tab with QOS Specs
This feature adds a new "Volume Types" tab to the Admin-Volumes panel, and places QOS Specs on the panel. Future check-ins will add further QOS Spec functionality, such as adding and deleting, etc. Partially Implements: blueprint cinder-qos-specs Closes-Bug: 1284506 Change-Id: I497f3bd00bb13356f5d0734f465cbf6feb02f781
This commit is contained in:
parent
c268f36350
commit
cae34cf1a2
|
@ -122,6 +122,14 @@ class VolTypeExtraSpec(object):
|
|||
self.value = val
|
||||
|
||||
|
||||
class QosSpec(object):
|
||||
def __init__(self, spec_id, key, val):
|
||||
self.spec_id = spec_id
|
||||
self.id = key
|
||||
self.key = key
|
||||
self.value = val
|
||||
|
||||
|
||||
def cinderclient(request):
|
||||
api_version = VERSIONS.get_active_version()
|
||||
|
||||
|
@ -393,6 +401,39 @@ def volume_type_extra_delete(request, type_id, keys):
|
|||
return vol_type.unset_keys([keys])
|
||||
|
||||
|
||||
def qos_spec_list(request):
|
||||
return cinderclient(request).qos_specs.list()
|
||||
|
||||
|
||||
def qos_spec_get(request, qos_spec_id):
|
||||
return cinderclient(request).qos_specs.get(qos_spec_id)
|
||||
|
||||
|
||||
def qos_spec_delete(request, qos_spec_id):
|
||||
return cinderclient(request).qos_specs.delete(qos_spec_id, force=True)
|
||||
|
||||
|
||||
def qos_spec_create(request, name, qos_spec_id):
|
||||
return cinderclient(request).qos_specs.create(name, qos_spec_id)
|
||||
|
||||
|
||||
def qos_spec_get_keys(request, qos_spec_id, raw=False):
|
||||
spec = cinderclient(request).qos_specs.get(qos_spec_id)
|
||||
qos_specs = spec.specs
|
||||
if raw:
|
||||
return spec
|
||||
return [QosSpec(qos_spec_id, key, value) for
|
||||
key, value in qos_specs.items()]
|
||||
|
||||
|
||||
def qos_spec_set_keys(request, qos_spec_id, specs):
|
||||
return cinderclient(request).qos_specs.set_keys(qos_spec_id, specs)
|
||||
|
||||
|
||||
def qos_spec_unset_keys(request, qos_spec_id, specs):
|
||||
return cinderclient(request).qos_specs.unset_keys(qos_spec_id, specs)
|
||||
|
||||
|
||||
@memoized
|
||||
def tenant_absolute_limits(request):
|
||||
limits = cinderclient(request).limits.get().absolute
|
||||
|
|
|
@ -22,6 +22,10 @@ from openstack_dashboard.api import keystone
|
|||
|
||||
from openstack_dashboard.dashboards.admin.volumes.snapshots \
|
||||
import tables as snapshots_tables
|
||||
from openstack_dashboard.dashboards.admin.volumes.volume_types.qos_specs \
|
||||
import tables as qos_tables
|
||||
from openstack_dashboard.dashboards.admin.volumes.volume_types \
|
||||
import tables as volume_types_tables
|
||||
from openstack_dashboard.dashboards.admin.volumes.volumes \
|
||||
import tables as volumes_tables
|
||||
from openstack_dashboard.dashboards.project.volumes \
|
||||
|
@ -29,8 +33,7 @@ from openstack_dashboard.dashboards.project.volumes \
|
|||
|
||||
|
||||
class VolumeTab(tabs.TableTab, volumes_tabs.VolumeTableMixIn):
|
||||
table_classes = (volumes_tables.VolumesTable,
|
||||
volumes_tables.VolumeTypesTable)
|
||||
table_classes = (volumes_tables.VolumesTable,)
|
||||
name = _("Volumes")
|
||||
slug = "volumes_tab"
|
||||
template_name = "admin/volumes/volumes/volumes_tables.html"
|
||||
|
@ -57,6 +60,15 @@ class VolumeTab(tabs.TableTab, volumes_tabs.VolumeTableMixIn):
|
|||
|
||||
return volumes
|
||||
|
||||
|
||||
class VolumeTypesTab(tabs.TableTab, volumes_tabs.VolumeTableMixIn):
|
||||
table_classes = (volume_types_tables.VolumeTypesTable,
|
||||
qos_tables.QosSpecsTable)
|
||||
name = _("Volume Types")
|
||||
slug = "volume_types_tab"
|
||||
template_name = "admin/volumes/volume_types/volume_types_tables.html"
|
||||
preload = False
|
||||
|
||||
def get_volume_types_data(self):
|
||||
try:
|
||||
volume_types = cinder.volume_type_list(self.request)
|
||||
|
@ -66,6 +78,15 @@ class VolumeTab(tabs.TableTab, volumes_tabs.VolumeTableMixIn):
|
|||
_("Unable to retrieve volume types"))
|
||||
return volume_types
|
||||
|
||||
def get_qos_specs_data(self):
|
||||
try:
|
||||
qos_specs = cinder.qos_spec_list(self.request)
|
||||
except Exception:
|
||||
qos_specs = []
|
||||
exceptions.handle(self.request,
|
||||
_("Unable to retrieve QOS specs"))
|
||||
return qos_specs
|
||||
|
||||
|
||||
class SnapshotTab(tabs.TableTab):
|
||||
table_classes = (snapshots_tables.VolumeSnapshotsTable,)
|
||||
|
@ -114,5 +135,5 @@ class SnapshotTab(tabs.TableTab):
|
|||
|
||||
class VolumesGroupTabs(tabs.TabGroup):
|
||||
slug = "volumes_group_tabs"
|
||||
tabs = (VolumeTab, SnapshotTab,)
|
||||
tabs = (VolumeTab, VolumeTypesTab, SnapshotTab)
|
||||
sticky = True
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
{% load url from future %}
|
||||
|
||||
{% block form_id %}{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:admin:volumes:volumes:create_type' %}{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:admin:volumes:volume_types:create_type' %}{% endblock %}
|
||||
|
||||
{% block modal_id %}create_volume_type_modal{% endblock %}
|
||||
{% block modal-header %}{% trans "Create Volume Type" %}{% endblock %}
|
||||
|
@ -31,5 +31,5 @@
|
|||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Create Volume Type" %}" />
|
||||
<a href="{% url 'horizon:admin:volumes:volumes_tab' %}" class="btn btn-default secondary cancel close">{% trans "Cancel" %}</a>
|
||||
<a href="{% url 'horizon:admin:volumes:volume_types_tab' %}" class="btn btn-default secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
|
@ -7,5 +7,5 @@
|
|||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'admin/volumes/volumes/_create_volume_type.html' %}
|
||||
{% include 'admin/volumes/volume_types/_create_volume_type.html' %}
|
||||
{% endblock %}
|
|
@ -3,7 +3,7 @@
|
|||
{% load url from future %}
|
||||
|
||||
{% block form_id %}extra_spec_create_form{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:admin:volumes:volumes:extras:create' vol_type.id %}{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:admin:volumes:volume_types:extras:create' vol_type.id %}{% endblock %}
|
||||
|
||||
|
||||
{% block modal_id %}extra_spec_create_modal{% endblock %}
|
||||
|
@ -23,6 +23,6 @@
|
|||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Create" %}" />
|
||||
<a href="{% url 'horizon:admin:volumes:volumes:extras:index' vol_type.id %}" class="btn btn-default secondary cancel close">{% trans "Cancel" %}</a>
|
||||
<a href="{% url 'horizon:admin:volumes:volume_types:extras:index' vol_type.id %}" class="btn btn-default secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
{% load url from future %}
|
||||
|
||||
{% block form_id %}extra_spec_edit_form{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:admin:volumes:volumes:extras:edit' vol_type.id key %}{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:admin:volumes:volume_types:extras:edit' vol_type.id key %}{% endblock %}
|
||||
|
||||
|
||||
{% block modal_id %}extra_spec_edit_modal{% endblock %}
|
||||
|
@ -23,6 +23,6 @@
|
|||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Save" %}" />
|
||||
<a href="{% url 'horizon:admin:volumes:volumes:extras:index' vol_type.id %}" class="btn btn-default secondary cancel close">{% trans "Cancel" %}</a>
|
||||
<a href="{% url 'horizon:admin:volumes:volume_types:extras:index' vol_type.id %}" class="btn btn-default secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
||||
|
|
@ -8,5 +8,5 @@
|
|||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include "admin/volumes/volumes/extras/_create.html" %}
|
||||
{% include "admin/volumes/volume_types/extras/_create.html" %}
|
||||
{% endblock %}
|
|
@ -8,5 +8,5 @@
|
|||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include "admin/volumes/volumes/extras/_edit.html" %}
|
||||
{% include "admin/volumes/volume_types/extras/_edit.html" %}
|
||||
{% endblock %}
|
|
@ -8,5 +8,5 @@
|
|||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include "admin/volumes/volumes/extras/_index.html" %}
|
||||
{% include "admin/volumes/volume_types/extras/_index.html" %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,9 @@
|
|||
{% block main %}
|
||||
<div id="volumes-type">
|
||||
{{ volume_types_table.render }}
|
||||
</div>
|
||||
|
||||
<div id="qos-specs">
|
||||
{{ qos_specs_table.render }}
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -2,8 +2,4 @@
|
|||
<div id="volumes">
|
||||
{{ volumes_table.render }}
|
||||
</div>
|
||||
|
||||
<div id="volumes-type">
|
||||
{{ volume_types_table.render }}
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -24,75 +24,25 @@ from openstack_dashboard.test import helpers as test
|
|||
|
||||
class VolumeTests(test.BaseAdminViewTests):
|
||||
@test.create_stubs({api.nova: ('server_list',),
|
||||
cinder: ('volume_list',
|
||||
'volume_type_list',),
|
||||
cinder: ('volume_list',),
|
||||
keystone: ('tenant_list',)})
|
||||
def test_index(self):
|
||||
cinder.volume_list(IsA(http.HttpRequest), search_opts={
|
||||
'all_tenants': True}).AndReturn(self.cinder_volumes.list())
|
||||
'all_tenants': True}).\
|
||||
AndReturn(self.cinder_volumes.list())
|
||||
api.nova.server_list(IsA(http.HttpRequest), search_opts={
|
||||
'all_tenants': True}) \
|
||||
.AndReturn([self.servers.list(), False])
|
||||
cinder.volume_type_list(IsA(http.HttpRequest)).\
|
||||
AndReturn(self.volume_types.list())
|
||||
keystone.tenant_list(IsA(http.HttpRequest)). \
|
||||
AndReturn([self.tenants.list(), False])
|
||||
'all_tenants': True}) \
|
||||
.AndReturn([self.servers.list(), False])
|
||||
keystone.tenant_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn([self.tenants.list(), False])
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('horizon:admin:volumes:index'))
|
||||
|
||||
self.assertTemplateUsed(res, 'admin/volumes/index.html')
|
||||
volumes = res.context['volumes_table'].data
|
||||
|
||||
self.assertItemsEqual(volumes, self.cinder_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:volumes:create_type'),
|
||||
formData)
|
||||
|
||||
redirect = reverse('horizon:admin:volumes:volumes_tab')
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertRedirectsNoFollow(res, redirect)
|
||||
|
||||
@test.create_stubs({api.nova: ('server_list',),
|
||||
cinder: ('volume_list',
|
||||
'volume_type_list',
|
||||
'volume_type_delete',),
|
||||
keystone: ('tenant_list',)})
|
||||
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': True}).AndReturn(self.cinder_volumes.list())
|
||||
api.nova.server_list(IsA(http.HttpRequest), search_opts={
|
||||
'all_tenants': True}) \
|
||||
.AndReturn([self.servers.list(), False])
|
||||
cinder.volume_type_list(IsA(http.HttpRequest)).\
|
||||
AndReturn(self.volume_types.list())
|
||||
cinder.volume_type_delete(IsA(http.HttpRequest),
|
||||
str(volume_type.id))
|
||||
keystone.tenant_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn([self.tenants.list(), False])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.post(
|
||||
reverse('horizon:admin:volumes:volumes_tab'),
|
||||
formData)
|
||||
|
||||
redirect = reverse('horizon:admin:volumes:volumes_tab')
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertRedirectsNoFollow(res, redirect)
|
||||
|
||||
@test.create_stubs({cinder: ('volume_reset_state',
|
||||
'volume_get')})
|
||||
def test_update_volume_status(self):
|
||||
|
@ -111,11 +61,30 @@ class VolumeTests(test.BaseAdminViewTests):
|
|||
formData)
|
||||
self.assertNoFormErrors(res)
|
||||
|
||||
@test.create_stubs({api.nova: ('server_list',),
|
||||
cinder: ('volume_list',
|
||||
@test.create_stubs({cinder: ('volume_type_list',
|
||||
'qos_spec_list',)})
|
||||
def test_volume_types_tab(self):
|
||||
cinder.volume_type_list(IsA(http.HttpRequest)).\
|
||||
AndReturn(self.volume_types.list())
|
||||
cinder.qos_spec_list(IsA(http.HttpRequest)).\
|
||||
AndReturn(self.cinder_qos_specs.list())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
res = self.client.get(reverse(
|
||||
'horizon:admin:volumes:volume_types_tab'))
|
||||
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertTemplateUsed(res,
|
||||
'admin/volumes/volume_types/volume_types_tables.html')
|
||||
volume_types = res.context['volume_types_table'].data
|
||||
self.assertItemsEqual(volume_types, self.volume_types.list())
|
||||
qos_specs = res.context['qos_specs_table'].data
|
||||
self.assertItemsEqual(qos_specs, self.cinder_qos_specs.list())
|
||||
|
||||
@test.create_stubs({cinder: ('volume_list',
|
||||
'volume_snapshot_list',),
|
||||
keystone: ('tenant_list',)})
|
||||
def test_snapshot_tab(self):
|
||||
def test_snapshots_tab(self):
|
||||
cinder.volume_snapshot_list(IsA(http.HttpRequest), search_opts={
|
||||
'all_tenants': True}). \
|
||||
AndReturn(self.cinder_volume_snapshots.list())
|
||||
|
@ -124,8 +93,11 @@ class VolumeTests(test.BaseAdminViewTests):
|
|||
AndReturn(self.cinder_volumes.list())
|
||||
keystone.tenant_list(IsA(http.HttpRequest)). \
|
||||
AndReturn([self.tenants.list(), False])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.mox.ReplayAll()
|
||||
res = self.client.get(reverse('horizon:admin:volumes:snapshots_tab'))
|
||||
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertTemplateUsed(res, 'horizon/common/_detail_table.html')
|
||||
snapshots = res.context['volume_snapshots_table'].data
|
||||
self.assertItemsEqual(snapshots, self.cinder_volume_snapshots.list())
|
||||
|
|
|
@ -17,6 +17,8 @@ from django.conf.urls import url # noqa
|
|||
from openstack_dashboard.dashboards.admin.volumes.snapshots \
|
||||
import urls as snapshot_urls
|
||||
from openstack_dashboard.dashboards.admin.volumes import views
|
||||
from openstack_dashboard.dashboards.admin.volumes.volume_types \
|
||||
import urls as volume_types_urls
|
||||
from openstack_dashboard.dashboards.admin.volumes.volumes \
|
||||
import urls as volumes_urls
|
||||
|
||||
|
@ -26,6 +28,9 @@ urlpatterns = patterns('',
|
|||
views.IndexView.as_view(), name='snapshots_tab'),
|
||||
url(r'^\?tab=volumes_group_tabs__volumes_tab$',
|
||||
views.IndexView.as_view(), name='volumes_tab'),
|
||||
url(r'^\?tab=volumes_group_tabs__volume_types_tab$',
|
||||
views.IndexView.as_view(), name='volume_types_tab'),
|
||||
url(r'', include(volumes_urls, namespace='volumes')),
|
||||
url(r'snapshots/', include(snapshot_urls, namespace='snapshots')),
|
||||
url(r'', include(volume_types_urls, namespace='volume_types')),
|
||||
)
|
||||
|
|
|
@ -31,7 +31,7 @@ class ExtraSpecDelete(tables.DeleteAction):
|
|||
class ExtraSpecCreate(tables.LinkAction):
|
||||
name = "create"
|
||||
verbose_name = _("Create")
|
||||
url = "horizon:admin:volumes:volumes:extras:create"
|
||||
url = "horizon:admin:volumes:volume_types:extras:create"
|
||||
classes = ("ajax-modal")
|
||||
icon = "plus"
|
||||
|
||||
|
@ -42,7 +42,7 @@ class ExtraSpecCreate(tables.LinkAction):
|
|||
class ExtraSpecEdit(tables.LinkAction):
|
||||
name = "edit"
|
||||
verbose_name = _("Edit")
|
||||
url = "horizon:admin:volumes:volumes:extras:edit"
|
||||
url = "horizon:admin:volumes:volume_types:extras:edit"
|
||||
classes = ("btn-edit", "ajax-modal")
|
||||
|
||||
def get_link_url(self, extra_spec):
|
|
@ -32,12 +32,12 @@ class VolTypeExtrasTests(test.BaseAdminViewTests):
|
|||
api.cinder.volume_type_extra_get(IsA(http.HttpRequest),
|
||||
vol_type.id).AndReturn(extras)
|
||||
self.mox.ReplayAll()
|
||||
url = reverse('horizon:admin:volumes:volumes:extras:index',
|
||||
url = reverse('horizon:admin:volumes:volume_types:extras:index',
|
||||
args=[vol_type.id])
|
||||
resp = self.client.get(url)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertTemplateUsed(resp,
|
||||
"admin/volumes/volumes/extras/index.html")
|
||||
"admin/volumes/volume_types/extras/index.html")
|
||||
|
||||
@test.create_stubs({api.cinder: ('volume_type_extra_get',
|
||||
'volume_type_get'), })
|
||||
|
@ -50,7 +50,7 @@ class VolTypeExtrasTests(test.BaseAdminViewTests):
|
|||
vol_type.id) \
|
||||
.AndRaise(self.exceptions.cinder)
|
||||
self.mox.ReplayAll()
|
||||
url = reverse('horizon:admin:volumes:volumes:extras:index',
|
||||
url = reverse('horizon:admin:volumes:volume_types:extras:index',
|
||||
args=[vol_type.id])
|
||||
resp = self.client.get(url)
|
||||
self.assertEqual(len(resp.context['extras_table'].data), 0)
|
||||
|
@ -59,10 +59,12 @@ class VolTypeExtrasTests(test.BaseAdminViewTests):
|
|||
@test.create_stubs({api.cinder: ('volume_type_extra_set', ), })
|
||||
def test_extra_create_post(self):
|
||||
vol_type = self.cinder_volume_types.first()
|
||||
create_url = reverse('horizon:admin:volumes:volumes:extras:create',
|
||||
args=[vol_type.id])
|
||||
index_url = reverse('horizon:admin:volumes:volumes:extras:index',
|
||||
args=[vol_type.id])
|
||||
create_url = reverse(
|
||||
'horizon:admin:volumes:volume_types:extras:create',
|
||||
args=[vol_type.id])
|
||||
index_url = reverse(
|
||||
'horizon:admin:volumes:volume_types:extras:index',
|
||||
args=[vol_type.id])
|
||||
|
||||
data = {'key': u'k1',
|
||||
'value': u'v1'}
|
||||
|
@ -80,8 +82,9 @@ class VolTypeExtrasTests(test.BaseAdminViewTests):
|
|||
@test.create_stubs({api.cinder: ('volume_type_get', ), })
|
||||
def test_extra_create_get(self):
|
||||
vol_type = self.cinder_volume_types.first()
|
||||
create_url = reverse('horizon:admin:volumes:volumes:extras:create',
|
||||
args=[vol_type.id])
|
||||
create_url = reverse(
|
||||
'horizon:admin:volumes:volume_types:extras:create',
|
||||
args=[vol_type.id])
|
||||
|
||||
api.cinder.volume_type_get(IsA(http.HttpRequest),
|
||||
vol_type.id).AndReturn(vol_type)
|
||||
|
@ -89,17 +92,17 @@ class VolTypeExtrasTests(test.BaseAdminViewTests):
|
|||
|
||||
resp = self.client.get(create_url)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertTemplateUsed(resp,
|
||||
'admin/volumes/volumes/extras/create.html')
|
||||
self.assertTemplateUsed(
|
||||
resp, 'admin/volumes/volume_types/extras/create.html')
|
||||
|
||||
@test.create_stubs({api.cinder: ('volume_type_extra_get',
|
||||
'volume_type_extra_set',), })
|
||||
def test_extra_edit(self):
|
||||
vol_type = self.cinder_volume_types.first()
|
||||
key = 'foo'
|
||||
edit_url = reverse('horizon:admin:volumes:volumes:extras:edit',
|
||||
args=[vol_type.id, key])
|
||||
index_url = reverse('horizon:admin:volumes:volumes:extras:index',
|
||||
edit_url = reverse('horizon:admin:volumes:volume_types:extras:edit',
|
||||
args=[vol_type.id, key])
|
||||
index_url = reverse('horizon:admin:volumes:volume_types:extras:index',
|
||||
args=[vol_type.id])
|
||||
|
||||
data = {'value': u'v1'}
|
||||
|
@ -124,7 +127,7 @@ class VolTypeExtrasTests(test.BaseAdminViewTests):
|
|||
vol_type = self.cinder_volume_types.first()
|
||||
extras = [api.cinder.VolTypeExtraSpec(vol_type.id, 'k1', 'v1')]
|
||||
formData = {'action': 'extras__delete__k1'}
|
||||
index_url = reverse('horizon:admin:volumes:volumes:extras:index',
|
||||
index_url = reverse('horizon:admin:volumes:volume_types:extras:index',
|
||||
args=[vol_type.id])
|
||||
|
||||
api.cinder.volume_type_extra_get(IsA(http.HttpRequest),
|
|
@ -13,7 +13,7 @@
|
|||
from django.conf.urls import patterns # noqa
|
||||
from django.conf.urls import url # noqa
|
||||
|
||||
from openstack_dashboard.dashboards.admin.volumes.volumes.extras \
|
||||
from openstack_dashboard.dashboards.admin.volumes.volume_types.extras \
|
||||
import views
|
||||
|
||||
urlpatterns = patterns('',
|
|
@ -19,9 +19,9 @@ from horizon import tables
|
|||
|
||||
from openstack_dashboard import api
|
||||
|
||||
from openstack_dashboard.dashboards.admin.volumes.volumes.extras \
|
||||
from openstack_dashboard.dashboards.admin.volumes.volume_types.extras \
|
||||
import forms as project_forms
|
||||
from openstack_dashboard.dashboards.admin.volumes.volumes.extras \
|
||||
from openstack_dashboard.dashboards.admin.volumes.volume_types.extras \
|
||||
import tables as project_tables
|
||||
|
||||
|
||||
|
@ -41,7 +41,7 @@ class ExtraSpecMixin(object):
|
|||
|
||||
class IndexView(ExtraSpecMixin, forms.ModalFormMixin, tables.DataTableView):
|
||||
table_class = project_tables.ExtraSpecsTable
|
||||
template_name = 'admin/volumes/volumes/extras/index.html'
|
||||
template_name = 'admin/volumes/volume_types/extras/index.html'
|
||||
|
||||
def get_data(self):
|
||||
try:
|
||||
|
@ -58,7 +58,7 @@ class IndexView(ExtraSpecMixin, forms.ModalFormMixin, tables.DataTableView):
|
|||
|
||||
class CreateView(ExtraSpecMixin, forms.ModalFormView):
|
||||
form_class = project_forms.CreateExtraSpec
|
||||
template_name = 'admin/volumes/volumes/extras/create.html'
|
||||
template_name = 'admin/volumes/volume_types/extras/create.html'
|
||||
|
||||
def get_initial(self):
|
||||
return {'type_id': self.kwargs['type_id']}
|
||||
|
@ -69,8 +69,8 @@ class CreateView(ExtraSpecMixin, forms.ModalFormView):
|
|||
|
||||
class EditView(ExtraSpecMixin, forms.ModalFormView):
|
||||
form_class = project_forms.EditExtraSpec
|
||||
template_name = 'admin/volumes/volumes/extras/edit.html'
|
||||
success_url = 'horizon:admin:volumes:volumes:extras:index'
|
||||
template_name = 'admin/volumes/volume_types/extras/edit.html'
|
||||
success_url = 'horizon:admin:volumes:volume_types:extras:index'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url,
|
|
@ -0,0 +1,35 @@
|
|||
# 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.template import defaultfilters as filters
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import tables
|
||||
|
||||
|
||||
def render_spec_keys(qos_spec):
|
||||
qos_spec_keys = ["%s=%s" % (key, value)
|
||||
for key, value in qos_spec.specs.items()]
|
||||
return qos_spec_keys
|
||||
|
||||
|
||||
class QosSpecsTable(tables.DataTable):
|
||||
name = tables.Column('name', verbose_name=_('Name'))
|
||||
consumer = tables.Column('consumer', verbose_name=_('Consumer'))
|
||||
specs = tables.Column(render_spec_keys,
|
||||
verbose_name=_('Specs'),
|
||||
wrap_list=True,
|
||||
filters=(filters.unordered_list,))
|
||||
|
||||
class Meta:
|
||||
name = "qos_specs"
|
||||
verbose_name = _("QOS Specs")
|
|
@ -0,0 +1,59 @@
|
|||
# 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 tables
|
||||
from openstack_dashboard.api import cinder
|
||||
|
||||
|
||||
class CreateVolumeType(tables.LinkAction):
|
||||
name = "create"
|
||||
verbose_name = _("Create Volume Type")
|
||||
url = "horizon:admin:volumes:volume_types:create_type"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "plus"
|
||||
policy_rules = (("volume", "volume_extension:types_manage"),)
|
||||
|
||||
|
||||
class ViewVolumeTypeExtras(tables.LinkAction):
|
||||
name = "extras"
|
||||
verbose_name = _("View Extra Specs")
|
||||
url = "horizon:admin:volumes:volume_types:extras:index"
|
||||
classes = ("btn-edit",)
|
||||
policy_rules = (("volume", "volume_extension:types_manage"),)
|
||||
|
||||
|
||||
class DeleteVolumeType(tables.DeleteAction):
|
||||
data_type_singular = _("Volume Type")
|
||||
data_type_plural = _("Volume Types")
|
||||
policy_rules = (("volume", "volume_extension:types_manage"),)
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
cinder.volume_type_delete(request, obj_id)
|
||||
|
||||
|
||||
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 = (ViewVolumeTypeExtras, DeleteVolumeType,)
|
|
@ -0,0 +1,64 @@
|
|||
# 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.core.urlresolvers import reverse
|
||||
from django import http
|
||||
from mox import IsA # noqa
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.api import cinder
|
||||
from openstack_dashboard.api import keystone
|
||||
from openstack_dashboard.test import helpers as test
|
||||
|
||||
|
||||
class VolumeTypeTests(test.BaseAdminViewTests):
|
||||
@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:volume_types:create_type'),
|
||||
formData)
|
||||
|
||||
redirect = reverse('horizon:admin:volumes:volume_types_tab')
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertRedirectsNoFollow(res, redirect)
|
||||
|
||||
@test.create_stubs({api.nova: ('server_list',),
|
||||
cinder: ('volume_list',
|
||||
'volume_type_list',
|
||||
'qos_spec_list',
|
||||
'volume_type_delete',),
|
||||
keystone: ('tenant_list',)})
|
||||
def test_delete_volume_type(self):
|
||||
volume_type = self.volume_types.first()
|
||||
formData = {'action': 'volume_types__delete__%s' % volume_type.id}
|
||||
|
||||
cinder.volume_type_list(IsA(http.HttpRequest)).\
|
||||
AndReturn(self.volume_types.list())
|
||||
cinder.qos_spec_list(IsA(http.HttpRequest)).\
|
||||
AndReturn(self.cinder_qos_specs.list())
|
||||
cinder.volume_type_delete(IsA(http.HttpRequest),
|
||||
str(volume_type.id))
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.post(
|
||||
reverse('horizon:admin:volumes:volumes_tab'),
|
||||
formData)
|
||||
|
||||
redirect = reverse('horizon:admin:volumes:volumes_tab')
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertRedirectsNoFollow(res, redirect)
|
|
@ -0,0 +1,29 @@
|
|||
# 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.conf.urls import include # noqa
|
||||
from django.conf.urls import patterns # noqa
|
||||
from django.conf.urls import url # noqa
|
||||
|
||||
from openstack_dashboard.dashboards.admin.volumes.volume_types.extras \
|
||||
import urls as extras_urls
|
||||
from openstack_dashboard.dashboards.admin.volumes.volume_types \
|
||||
import views
|
||||
|
||||
VIEWS_MOD = ('openstack_dashboard.dashboards.admin.volumes.volume_types.views')
|
||||
|
||||
urlpatterns = patterns('VIEWS_MOD',
|
||||
url(r'^create_type$', views.CreateVolumeTypeView.as_view(),
|
||||
name='create_type'),
|
||||
url(r'^(?P<type_id>[^/]+)/extras/',
|
||||
include(extras_urls, namespace='extras')),
|
||||
)
|
|
@ -0,0 +1,31 @@
|
|||
# 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.
|
||||
|
||||
"""
|
||||
Admin views for managing volumes.
|
||||
"""
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from horizon import forms
|
||||
|
||||
from openstack_dashboard.dashboards.admin.volumes.volumes \
|
||||
import forms as volumes_forms
|
||||
|
||||
|
||||
class CreateVolumeTypeView(forms.ModalFormView):
|
||||
form_class = volumes_forms.CreateVolumeType
|
||||
template_name = 'admin/volumes/volume_types/create_volume_type.html'
|
||||
success_url = 'horizon:admin:volumes:volume_types_tab'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url)
|
|
@ -13,37 +13,10 @@
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import tables
|
||||
from openstack_dashboard.api import cinder
|
||||
from openstack_dashboard.dashboards.project.volumes \
|
||||
.volumes import tables as volumes_tables
|
||||
|
||||
|
||||
class CreateVolumeType(tables.LinkAction):
|
||||
name = "create"
|
||||
verbose_name = _("Create Volume Type")
|
||||
url = "horizon:admin:volumes:volumes:create_type"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "plus"
|
||||
policy_rules = (("volume", "volume_extension:types_manage"),)
|
||||
|
||||
|
||||
class ViewVolumeTypeExtras(tables.LinkAction):
|
||||
name = "extras"
|
||||
verbose_name = _("View Extra Specs")
|
||||
url = "horizon:admin:volumes:volumes:extras:index"
|
||||
classes = ("btn-edit",)
|
||||
policy_rules = (("volume", "volume_extension:types_manage"),)
|
||||
|
||||
|
||||
class DeleteVolumeType(tables.DeleteAction):
|
||||
data_type_singular = _("Volume Type")
|
||||
data_type_plural = _("Volume Types")
|
||||
policy_rules = (("volume", "volume_extension:types_manage"),)
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
cinder.volume_type_delete(request, obj_id)
|
||||
|
||||
|
||||
class VolumesFilterAction(tables.FilterAction):
|
||||
|
||||
def filter(self, table, volumes, filter_string):
|
||||
|
@ -79,20 +52,3 @@ class VolumesTable(volumes_tables.VolumesTable):
|
|||
row_actions = (volumes_tables.DeleteVolume, UpdateVolumeStatusAction)
|
||||
columns = ('tenant', 'host', 'name', 'size', 'status', 'volume_type',
|
||||
'attachments', 'bootable', 'encryption',)
|
||||
|
||||
|
||||
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 = (ViewVolumeTypeExtras, DeleteVolumeType,)
|
||||
|
|
|
@ -14,20 +14,14 @@ from django.conf.urls import include # noqa
|
|||
from django.conf.urls import patterns # noqa
|
||||
from django.conf.urls import url # noqa
|
||||
|
||||
from openstack_dashboard.dashboards.admin.volumes.volumes.extras \
|
||||
import urls as extras_urls
|
||||
from openstack_dashboard.dashboards.admin.volumes.volumes \
|
||||
import views
|
||||
|
||||
VIEWS_MOD = ('openstack_dashboard.dashboards.admin.volumes.volumes.views')
|
||||
|
||||
urlpatterns = patterns(VIEWS_MOD,
|
||||
url(r'^create_type$', views.CreateVolumeTypeView.as_view(),
|
||||
name='create_type'),
|
||||
url(r'^(?P<volume_id>[^/]+)/$', views.DetailView.as_view(),
|
||||
name='detail'),
|
||||
url(r'^(?P<volume_id>[^/]+)/update_status$',
|
||||
views.UpdateStatusView.as_view(), name='update_status'),
|
||||
url(r'^(?P<type_id>[^/]+)/extras/',
|
||||
include(extras_urls, namespace='extras')),
|
||||
)
|
||||
|
|
|
@ -18,6 +18,7 @@ from cinderclient.v1 import services
|
|||
from cinderclient.v1 import volume_snapshots as vol_snaps
|
||||
from cinderclient.v1 import volume_types
|
||||
from cinderclient.v1 import volumes
|
||||
from cinderclient.v2 import qos_specs
|
||||
from cinderclient.v2 import volume_backups as vol_backups
|
||||
from cinderclient.v2 import volume_snapshots as vol_snaps_v2
|
||||
from cinderclient.v2 import volumes as volumes_v2
|
||||
|
@ -33,6 +34,7 @@ def data(TEST):
|
|||
TEST.cinder_volumes = utils.TestDataContainer()
|
||||
TEST.cinder_volume_backups = utils.TestDataContainer()
|
||||
TEST.cinder_volume_types = utils.TestDataContainer()
|
||||
TEST.cinder_qos_specs = utils.TestDataContainer()
|
||||
TEST.cinder_volume_snapshots = utils.TestDataContainer()
|
||||
TEST.cinder_quotas = utils.TestDataContainer()
|
||||
TEST.cinder_quota_usages = utils.TestDataContainer()
|
||||
|
@ -223,3 +225,17 @@ def data(TEST):
|
|||
"maxTotalVolumeGigabytes": 1000,
|
||||
"maxTotalVolumes": 10}}
|
||||
TEST.cinder_limits = limits
|
||||
|
||||
# QOS Specs
|
||||
qos_spec1 = qos_specs.QoSSpecs(qos_specs.QoSSpecsManager(None),
|
||||
{"id": "418db45d-6992-4674-b226-80aacad2073c",
|
||||
"name": "high_iops",
|
||||
"consumer": "back-end",
|
||||
"specs": {"minIOPS": "1000", "maxIOPS": '100000'}})
|
||||
qos_spec2 = qos_specs.QoSSpecs(qos_specs.QoSSpecsManager(None),
|
||||
{"id": "6ed7035f-992e-4075-8ed6-6eff19b3192d",
|
||||
"name": "high_bws",
|
||||
"consumer": "back-end",
|
||||
"specs": {"maxBWS": '5000'}})
|
||||
|
||||
TEST.cinder_qos_specs.add(qos_spec1, qos_spec2)
|
||||
|
|
Loading…
Reference in New Issue