From cae34cf1a21a0ffd53a42cbcdb5032ad69aa4856 Mon Sep 17 00:00:00 2001 From: Rich Hagarty Date: Fri, 1 Aug 2014 08:45:20 -0700 Subject: [PATCH] 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 --- openstack_dashboard/api/cinder.py | 41 ++++++++ .../dashboards/admin/volumes/tabs.py | 27 +++++- .../_create_volume_type.html | 4 +- .../create_volume_type.html | 2 +- .../extras/_create.html | 4 +- .../extras/_edit.html | 4 +- .../extras/_index.html | 0 .../extras/create.html | 2 +- .../extras/edit.html | 2 +- .../extras/index.html | 2 +- .../volume_types/volume_types_tables.html | 9 ++ .../volumes/volumes/volumes_tables.html | 4 - .../dashboards/admin/volumes/tests.py | 94 +++++++------------ .../dashboards/admin/volumes/urls.py | 5 + .../extras => volume_types}/__init__.py | 0 .../volumes/volume_types/extras/__init__.py | 0 .../{volumes => volume_types}/extras/forms.py | 0 .../extras/tables.py | 4 +- .../{volumes => volume_types}/extras/tests.py | 33 ++++--- .../{volumes => volume_types}/extras/urls.py | 2 +- .../{volumes => volume_types}/extras/views.py | 12 +-- .../volume_types/qos_specs/__init__.py | 0 .../volumes/volume_types/qos_specs/tables.py | 35 +++++++ .../admin/volumes/volume_types/tables.py | 59 ++++++++++++ .../admin/volumes/volume_types/tests.py | 64 +++++++++++++ .../admin/volumes/volume_types/urls.py | 29 ++++++ .../admin/volumes/volume_types/views.py | 31 ++++++ .../admin/volumes/volumes/tables.py | 44 --------- .../dashboards/admin/volumes/volumes/urls.py | 6 -- .../test/test_data/cinder_data.py | 16 ++++ 30 files changed, 383 insertions(+), 152 deletions(-) rename openstack_dashboard/dashboards/admin/volumes/templates/volumes/{volumes => volume_types}/_create_volume_type.html (83%) rename openstack_dashboard/dashboards/admin/volumes/templates/volumes/{volumes => volume_types}/create_volume_type.html (79%) rename openstack_dashboard/dashboards/admin/volumes/templates/volumes/{volumes => volume_types}/extras/_create.html (73%) rename openstack_dashboard/dashboards/admin/volumes/templates/volumes/{volumes => volume_types}/extras/_edit.html (72%) rename openstack_dashboard/dashboards/admin/volumes/templates/volumes/{volumes => volume_types}/extras/_index.html (100%) rename openstack_dashboard/dashboards/admin/volumes/templates/volumes/{volumes => volume_types}/extras/create.html (79%) rename openstack_dashboard/dashboards/admin/volumes/templates/volumes/{volumes => volume_types}/extras/edit.html (79%) rename openstack_dashboard/dashboards/admin/volumes/templates/volumes/{volumes => volume_types}/extras/index.html (79%) create mode 100644 openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/volume_types_tables.html rename openstack_dashboard/dashboards/admin/volumes/{volumes/extras => volume_types}/__init__.py (100%) create mode 100644 openstack_dashboard/dashboards/admin/volumes/volume_types/extras/__init__.py rename openstack_dashboard/dashboards/admin/volumes/{volumes => volume_types}/extras/forms.py (100%) rename openstack_dashboard/dashboards/admin/volumes/{volumes => volume_types}/extras/tables.py (94%) rename openstack_dashboard/dashboards/admin/volumes/{volumes => volume_types}/extras/tests.py (83%) rename openstack_dashboard/dashboards/admin/volumes/{volumes => volume_types}/extras/urls.py (92%) rename openstack_dashboard/dashboards/admin/volumes/{volumes => volume_types}/extras/views.py (88%) create mode 100644 openstack_dashboard/dashboards/admin/volumes/volume_types/qos_specs/__init__.py create mode 100644 openstack_dashboard/dashboards/admin/volumes/volume_types/qos_specs/tables.py create mode 100644 openstack_dashboard/dashboards/admin/volumes/volume_types/tables.py create mode 100644 openstack_dashboard/dashboards/admin/volumes/volume_types/tests.py create mode 100644 openstack_dashboard/dashboards/admin/volumes/volume_types/urls.py create mode 100644 openstack_dashboard/dashboards/admin/volumes/volume_types/views.py diff --git a/openstack_dashboard/api/cinder.py b/openstack_dashboard/api/cinder.py index 15668ea5b6..221157df1d 100644 --- a/openstack_dashboard/api/cinder.py +++ b/openstack_dashboard/api/cinder.py @@ -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 diff --git a/openstack_dashboard/dashboards/admin/volumes/tabs.py b/openstack_dashboard/dashboards/admin/volumes/tabs.py index 21995c23a0..959f38343a 100644 --- a/openstack_dashboard/dashboards/admin/volumes/tabs.py +++ b/openstack_dashboard/dashboards/admin/volumes/tabs.py @@ -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 diff --git a/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/_create_volume_type.html b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/_create_volume_type.html similarity index 83% rename from openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/_create_volume_type.html rename to openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/_create_volume_type.html index 3d29ad64ef..d91e23fee2 100644 --- a/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/_create_volume_type.html +++ b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/_create_volume_type.html @@ -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 %} - {% trans "Cancel" %} + {% trans "Cancel" %} {% endblock %} diff --git a/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/create_volume_type.html b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/create_volume_type.html similarity index 79% rename from openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/create_volume_type.html rename to openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/create_volume_type.html index a8858973bf..db1f341e6e 100644 --- a/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/create_volume_type.html +++ b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/create_volume_type.html @@ -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 %} diff --git a/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/extras/_create.html b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/extras/_create.html similarity index 73% rename from openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/extras/_create.html rename to openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/extras/_create.html index a2aca44d3e..4473d33f7a 100644 --- a/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/extras/_create.html +++ b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/extras/_create.html @@ -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 %} - {% trans "Cancel" %} + {% trans "Cancel" %} {% endblock %} diff --git a/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/extras/_edit.html b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/extras/_edit.html similarity index 72% rename from openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/extras/_edit.html rename to openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/extras/_edit.html index 50af81d4a3..4170c087fb 100644 --- a/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/extras/_edit.html +++ b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/extras/_edit.html @@ -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 %} - {% trans "Cancel" %} + {% trans "Cancel" %} {% endblock %} diff --git a/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/extras/_index.html b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/extras/_index.html similarity index 100% rename from openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/extras/_index.html rename to openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/extras/_index.html diff --git a/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/extras/create.html b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/extras/create.html similarity index 79% rename from openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/extras/create.html rename to openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/extras/create.html index 2e5bdd874f..06c195c7c7 100644 --- a/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/extras/create.html +++ b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/extras/create.html @@ -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 %} diff --git a/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/extras/edit.html b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/extras/edit.html similarity index 79% rename from openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/extras/edit.html rename to openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/extras/edit.html index dfe111b766..9ba781ffc4 100644 --- a/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/extras/edit.html +++ b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/extras/edit.html @@ -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 %} diff --git a/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/extras/index.html b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/extras/index.html similarity index 79% rename from openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/extras/index.html rename to openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/extras/index.html index 73850cae9a..8a43ff67bb 100644 --- a/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/extras/index.html +++ b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/extras/index.html @@ -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 %} diff --git a/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/volume_types_tables.html b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/volume_types_tables.html new file mode 100644 index 0000000000..b50376509d --- /dev/null +++ b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/volume_types_tables.html @@ -0,0 +1,9 @@ +{% block main %} +
+ {{ volume_types_table.render }} +
+ +
+ {{ qos_specs_table.render }} +
+{% endblock %} \ No newline at end of file diff --git a/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/volumes_tables.html b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/volumes_tables.html index 50680723ba..c67ddcfc6e 100644 --- a/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/volumes_tables.html +++ b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes/volumes_tables.html @@ -2,8 +2,4 @@
{{ volumes_table.render }}
- -
- {{ volume_types_table.render }} -
{% endblock %} \ No newline at end of file diff --git a/openstack_dashboard/dashboards/admin/volumes/tests.py b/openstack_dashboard/dashboards/admin/volumes/tests.py index 2e4c50cab7..e337e0405b 100644 --- a/openstack_dashboard/dashboards/admin/volumes/tests.py +++ b/openstack_dashboard/dashboards/admin/volumes/tests.py @@ -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()) diff --git a/openstack_dashboard/dashboards/admin/volumes/urls.py b/openstack_dashboard/dashboards/admin/volumes/urls.py index f8a331230a..43f0f1539c 100644 --- a/openstack_dashboard/dashboards/admin/volumes/urls.py +++ b/openstack_dashboard/dashboards/admin/volumes/urls.py @@ -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')), ) diff --git a/openstack_dashboard/dashboards/admin/volumes/volumes/extras/__init__.py b/openstack_dashboard/dashboards/admin/volumes/volume_types/__init__.py similarity index 100% rename from openstack_dashboard/dashboards/admin/volumes/volumes/extras/__init__.py rename to openstack_dashboard/dashboards/admin/volumes/volume_types/__init__.py diff --git a/openstack_dashboard/dashboards/admin/volumes/volume_types/extras/__init__.py b/openstack_dashboard/dashboards/admin/volumes/volume_types/extras/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openstack_dashboard/dashboards/admin/volumes/volumes/extras/forms.py b/openstack_dashboard/dashboards/admin/volumes/volume_types/extras/forms.py similarity index 100% rename from openstack_dashboard/dashboards/admin/volumes/volumes/extras/forms.py rename to openstack_dashboard/dashboards/admin/volumes/volume_types/extras/forms.py diff --git a/openstack_dashboard/dashboards/admin/volumes/volumes/extras/tables.py b/openstack_dashboard/dashboards/admin/volumes/volume_types/extras/tables.py similarity index 94% rename from openstack_dashboard/dashboards/admin/volumes/volumes/extras/tables.py rename to openstack_dashboard/dashboards/admin/volumes/volume_types/extras/tables.py index 8fe89d4b92..ad5e5b2d13 100644 --- a/openstack_dashboard/dashboards/admin/volumes/volumes/extras/tables.py +++ b/openstack_dashboard/dashboards/admin/volumes/volume_types/extras/tables.py @@ -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): diff --git a/openstack_dashboard/dashboards/admin/volumes/volumes/extras/tests.py b/openstack_dashboard/dashboards/admin/volumes/volume_types/extras/tests.py similarity index 83% rename from openstack_dashboard/dashboards/admin/volumes/volumes/extras/tests.py rename to openstack_dashboard/dashboards/admin/volumes/volume_types/extras/tests.py index 15bbe30a88..11501c92f2 100644 --- a/openstack_dashboard/dashboards/admin/volumes/volumes/extras/tests.py +++ b/openstack_dashboard/dashboards/admin/volumes/volume_types/extras/tests.py @@ -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), diff --git a/openstack_dashboard/dashboards/admin/volumes/volumes/extras/urls.py b/openstack_dashboard/dashboards/admin/volumes/volume_types/extras/urls.py similarity index 92% rename from openstack_dashboard/dashboards/admin/volumes/volumes/extras/urls.py rename to openstack_dashboard/dashboards/admin/volumes/volume_types/extras/urls.py index e18739cd3c..df57ae8e49 100644 --- a/openstack_dashboard/dashboards/admin/volumes/volumes/extras/urls.py +++ b/openstack_dashboard/dashboards/admin/volumes/volume_types/extras/urls.py @@ -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('', diff --git a/openstack_dashboard/dashboards/admin/volumes/volumes/extras/views.py b/openstack_dashboard/dashboards/admin/volumes/volume_types/extras/views.py similarity index 88% rename from openstack_dashboard/dashboards/admin/volumes/volumes/extras/views.py rename to openstack_dashboard/dashboards/admin/volumes/volume_types/extras/views.py index 83ac2bad43..ff4b87ebae 100644 --- a/openstack_dashboard/dashboards/admin/volumes/volumes/extras/views.py +++ b/openstack_dashboard/dashboards/admin/volumes/volume_types/extras/views.py @@ -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, diff --git a/openstack_dashboard/dashboards/admin/volumes/volume_types/qos_specs/__init__.py b/openstack_dashboard/dashboards/admin/volumes/volume_types/qos_specs/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openstack_dashboard/dashboards/admin/volumes/volume_types/qos_specs/tables.py b/openstack_dashboard/dashboards/admin/volumes/volume_types/qos_specs/tables.py new file mode 100644 index 0000000000..11fbb56052 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/volumes/volume_types/qos_specs/tables.py @@ -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") diff --git a/openstack_dashboard/dashboards/admin/volumes/volume_types/tables.py b/openstack_dashboard/dashboards/admin/volumes/volume_types/tables.py new file mode 100644 index 0000000000..94eac9d6a2 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/volumes/volume_types/tables.py @@ -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,) diff --git a/openstack_dashboard/dashboards/admin/volumes/volume_types/tests.py b/openstack_dashboard/dashboards/admin/volumes/volume_types/tests.py new file mode 100644 index 0000000000..fbdcba6f48 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/volumes/volume_types/tests.py @@ -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) diff --git a/openstack_dashboard/dashboards/admin/volumes/volume_types/urls.py b/openstack_dashboard/dashboards/admin/volumes/volume_types/urls.py new file mode 100644 index 0000000000..2a69b2ac00 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/volumes/volume_types/urls.py @@ -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[^/]+)/extras/', + include(extras_urls, namespace='extras')), +) diff --git a/openstack_dashboard/dashboards/admin/volumes/volume_types/views.py b/openstack_dashboard/dashboards/admin/volumes/volume_types/views.py new file mode 100644 index 0000000000..75783dfb46 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/volumes/volume_types/views.py @@ -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) diff --git a/openstack_dashboard/dashboards/admin/volumes/volumes/tables.py b/openstack_dashboard/dashboards/admin/volumes/volumes/tables.py index 94132d68a7..ec5a224d7c 100644 --- a/openstack_dashboard/dashboards/admin/volumes/volumes/tables.py +++ b/openstack_dashboard/dashboards/admin/volumes/volumes/tables.py @@ -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,) diff --git a/openstack_dashboard/dashboards/admin/volumes/volumes/urls.py b/openstack_dashboard/dashboards/admin/volumes/volumes/urls.py index f06f7b9d89..50d8a2face 100644 --- a/openstack_dashboard/dashboards/admin/volumes/volumes/urls.py +++ b/openstack_dashboard/dashboards/admin/volumes/volumes/urls.py @@ -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[^/]+)/$', views.DetailView.as_view(), name='detail'), url(r'^(?P[^/]+)/update_status$', views.UpdateStatusView.as_view(), name='update_status'), - url(r'^(?P[^/]+)/extras/', - include(extras_urls, namespace='extras')), ) diff --git a/openstack_dashboard/test/test_data/cinder_data.py b/openstack_dashboard/test/test_data/cinder_data.py index 40167c03c1..1399c0cd5c 100644 --- a/openstack_dashboard/test/test_data/cinder_data.py +++ b/openstack_dashboard/test/test_data/cinder_data.py @@ -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)