diff --git a/openstack_dashboard/dashboards/admin/volumes/snapshots/tables.py b/openstack_dashboard/dashboards/admin/volumes/snapshots/tables.py
index 30855f6ceb..7979e75cf3 100644
--- a/openstack_dashboard/dashboards/admin/volumes/snapshots/tables.py
+++ b/openstack_dashboard/dashboards/admin/volumes/snapshots/tables.py
@@ -20,7 +20,7 @@ from openstack_dashboard.api import keystone
from openstack_dashboard.dashboards.project.snapshots \
import tables as snapshots_tables
-from openstack_dashboard.dashboards.project.volumes.volumes \
+from openstack_dashboard.dashboards.project.volumes \
import tables as volumes_tables
diff --git a/openstack_dashboard/dashboards/admin/volumes/tabs.py b/openstack_dashboard/dashboards/admin/volumes/tabs.py
index 6480741a77..ca1b57ba4c 100644
--- a/openstack_dashboard/dashboards/admin/volumes/tabs.py
+++ b/openstack_dashboard/dashboards/admin/volumes/tabs.py
@@ -30,11 +30,11 @@ from openstack_dashboard.dashboards.admin.volumes.volume_types \
from openstack_dashboard.dashboards.admin.volumes.volumes \
import tables as volumes_tables
from openstack_dashboard.dashboards.project.volumes \
- import tabs as volumes_tabs
+ import views as volumes_views
class VolumeTab(tables.PagedTableMixin, tabs.TableTab,
- volumes_tabs.VolumeTableMixIn, tables.DataTableView):
+ volumes_views.VolumeTableMixIn, tables.DataTableView):
table_classes = (volumes_tables.VolumesTable,)
name = _("Volumes")
slug = "volumes_tab"
@@ -105,7 +105,7 @@ class VolumeTab(tables.PagedTableMixin, tabs.TableTab,
return filters
-class VolumeTypesTab(tabs.TableTab, volumes_tabs.VolumeTableMixIn):
+class VolumeTypesTab(tabs.TableTab, volumes_views.VolumeTableMixIn):
table_classes = (volume_types_tables.VolumeTypesTable,
volume_types_tables.QosSpecsTable)
name = _("Volume Types")
diff --git a/openstack_dashboard/dashboards/admin/volumes/tests.py b/openstack_dashboard/dashboards/admin/volumes/tests.py
index c81619507e..557c45d7e0 100644
--- a/openstack_dashboard/dashboards/admin/volumes/tests.py
+++ b/openstack_dashboard/dashboards/admin/volumes/tests.py
@@ -26,7 +26,7 @@ from openstack_dashboard.api import cinder
from openstack_dashboard.api import keystone
from openstack_dashboard.dashboards.project.snapshots \
import tables as snapshot_tables
-from openstack_dashboard.dashboards.project.volumes.volumes \
+from openstack_dashboard.dashboards.project.volumes \
import tables as volume_tables
from openstack_dashboard.test import helpers as test
@@ -35,8 +35,16 @@ INDEX_URL = reverse('horizon:admin:volumes:index')
class VolumeTests(test.BaseAdminViewTests):
+ def tearDown(self):
+ for volume in self.cinder_volumes.list():
+ # VolumeTableMixIn._set_volume_attributes mutates data
+ # and cinder_volumes.list() doesn't deep copy
+ for att in volume.attachments:
+ if 'instance' in att:
+ del att['instance']
+ super(VolumeTests, self).tearDown()
- @test.create_stubs({api.nova: ('server_list',),
+ @test.create_stubs({api.nova: ('server_list', 'server_get'),
cinder: ('volume_list_paged',
'volume_snapshot_list'),
keystone: ('tenant_list',)})
@@ -45,6 +53,8 @@ class VolumeTests(test.BaseAdminViewTests):
if instanceless_volumes:
for volume in volumes:
volume.attachments = []
+ else:
+ server = self.servers.first()
cinder.volume_list_paged(IsA(http.HttpRequest), sort_dir="desc",
marker=None, paginate=True,
@@ -53,6 +63,8 @@ class VolumeTests(test.BaseAdminViewTests):
cinder.volume_snapshot_list(IsA(http.HttpRequest), search_opts={
'all_tenants': True}).AndReturn([])
if not instanceless_volumes:
+ api.nova.server_get(IsA(http.HttpRequest),
+ server.id).AndReturn(server)
api.nova.server_list(IsA(http.HttpRequest), search_opts={
'all_tenants': True}, detailed=False) \
.AndReturn([self.servers.list(), False])
@@ -72,13 +84,14 @@ class VolumeTests(test.BaseAdminViewTests):
def test_index_with_attachments(self):
self._test_index(instanceless_volumes=False)
- @test.create_stubs({api.nova: ('server_list',),
+ @test.create_stubs({api.nova: ('server_list', 'server_get'),
cinder: ('volume_list_paged',
'volume_snapshot_list'),
keystone: ('tenant_list',)})
def _test_index_paginated(self, marker, sort_dir, volumes, url,
has_more, has_prev):
vol_snaps = self.cinder_volume_snapshots.list()
+ server = self.servers.first()
cinder.volume_list_paged(IsA(http.HttpRequest), sort_dir=sort_dir,
marker=marker, paginate=True,
search_opts={'all_tenants': True}) \
@@ -88,6 +101,8 @@ class VolumeTests(test.BaseAdminViewTests):
api.nova.server_list(IsA(http.HttpRequest), search_opts={
'all_tenants': True}, detailed=False) \
.AndReturn([self.servers.list(), False])
+ api.nova.server_get(IsA(http.HttpRequest),
+ server.id).AndReturn(server)
keystone.tenant_list(IsA(http.HttpRequest)) \
.AndReturn([self.tenants.list(), False])
diff --git a/openstack_dashboard/dashboards/admin/volumes/volumes/forms.py b/openstack_dashboard/dashboards/admin/volumes/volumes/forms.py
index e6bc945885..b3b1962fc5 100644
--- a/openstack_dashboard/dashboards/admin/volumes/volumes/forms.py
+++ b/openstack_dashboard/dashboards/admin/volumes/volumes/forms.py
@@ -27,7 +27,7 @@ from horizon.utils import validators as utils_validators
from openstack_dashboard.api import cinder
from openstack_dashboard.dashboards.admin.volumes.snapshots.forms \
import populate_status_choices
-from openstack_dashboard.dashboards.project.volumes.volumes \
+from openstack_dashboard.dashboards.project.volumes \
import forms as project_forms
diff --git a/openstack_dashboard/dashboards/admin/volumes/volumes/tables.py b/openstack_dashboard/dashboards/admin/volumes/volumes/tables.py
index 36db46f58c..5b5350c03c 100644
--- a/openstack_dashboard/dashboards/admin/volumes/volumes/tables.py
+++ b/openstack_dashboard/dashboards/admin/volumes/volumes/tables.py
@@ -16,7 +16,7 @@ from horizon import exceptions
from horizon import tables
from openstack_dashboard.dashboards.project.volumes \
- .volumes import tables as volumes_tables
+ import tables as volumes_tables
class VolumesFilterAction(tables.FilterAction):
diff --git a/openstack_dashboard/dashboards/admin/volumes/volumes/tests.py b/openstack_dashboard/dashboards/admin/volumes/volumes/tests.py
index 642d44d896..c85ef35556 100644
--- a/openstack_dashboard/dashboards/admin/volumes/volumes/tests.py
+++ b/openstack_dashboard/dashboards/admin/volumes/volumes/tests.py
@@ -24,6 +24,15 @@ INDEX_URL = reverse('horizon:admin:volumes:volumes_tab')
class VolumeViewTests(test.BaseAdminViewTests):
+ def tearDown(self):
+ for volume in self.cinder_volumes.list():
+ # VolumeTableMixIn._set_volume_attributes mutates data
+ # and cinder_volumes.list() doesn't deep copy
+ for att in volume.attachments:
+ if 'instance' in att:
+ del att['instance']
+ super(VolumeViewTests, self).tearDown()
+
@test.create_stubs({cinder: ('volume_reset_state',
'volume_get')})
def test_update_volume_status(self):
diff --git a/openstack_dashboard/dashboards/admin/volumes/volumes/views.py b/openstack_dashboard/dashboards/admin/volumes/volumes/views.py
index 2d9c64f3ba..490b938242 100644
--- a/openstack_dashboard/dashboards/admin/volumes/volumes/views.py
+++ b/openstack_dashboard/dashboards/admin/volumes/volumes/views.py
@@ -23,7 +23,7 @@ from openstack_dashboard.dashboards.admin.volumes.volumes \
import forms as volumes_forms
from openstack_dashboard.dashboards.admin.volumes.volumes \
import tables as volumes_tables
-from openstack_dashboard.dashboards.project.volumes.volumes \
+from openstack_dashboard.dashboards.project.volumes \
import views as volumes_views
diff --git a/openstack_dashboard/dashboards/project/backups/tables.py b/openstack_dashboard/dashboards/project/backups/tables.py
index c35eb908f1..8c7b81c67a 100644
--- a/openstack_dashboard/dashboards/project/backups/tables.py
+++ b/openstack_dashboard/dashboards/project/backups/tables.py
@@ -147,8 +147,7 @@ class BackupsTable(tables.DataTable):
display_choices=STATUS_DISPLAY_CHOICES)
volume_name = BackupVolumeNameColumn("name",
verbose_name=_("Volume Name"),
- link="horizon:project"
- ":volumes:volumes:detail")
+ link="horizon:project:volumes:detail")
class Meta(object):
name = "volume_backups"
diff --git a/openstack_dashboard/dashboards/project/backups/templates/backups/_detail_overview.html b/openstack_dashboard/dashboards/project/backups/templates/backups/_detail_overview.html
index e4e950faac..b95062d7fa 100644
--- a/openstack_dashboard/dashboards/project/backups/templates/backups/_detail_overview.html
+++ b/openstack_dashboard/dashboards/project/backups/templates/backups/_detail_overview.html
@@ -15,7 +15,7 @@
{% if volume %}
{% trans "Volume" %}
-
+
{{ volume.name }}
diff --git a/openstack_dashboard/dashboards/project/backups/tests.py b/openstack_dashboard/dashboards/project/backups/tests.py
index 49c7f9e341..96fa002560 100644
--- a/openstack_dashboard/dashboards/project/backups/tests.py
+++ b/openstack_dashboard/dashboards/project/backups/tests.py
@@ -131,7 +131,7 @@ class VolumeBackupsViewTests(test.TestCase):
'container_name': backup.container_name,
'name': backup.name,
'description': backup.description}
- url = reverse('horizon:project:volumes:volumes:create_backup',
+ url = reverse('horizon:project:volumes:create_backup',
args=[volume.id])
res = self.client.post(url, formData)
diff --git a/openstack_dashboard/dashboards/project/backups/views.py b/openstack_dashboard/dashboards/project/backups/views.py
index ea1a3e55e4..6cb2fbdc6b 100644
--- a/openstack_dashboard/dashboards/project/backups/views.py
+++ b/openstack_dashboard/dashboards/project/backups/views.py
@@ -28,11 +28,11 @@ from openstack_dashboard.dashboards.project.backups \
from openstack_dashboard.dashboards.project.backups \
import tabs as backup_tabs
from openstack_dashboard.dashboards.project.volumes \
- import tabs as volume_tabs
+ import views as volume_views
class BackupsView(tables.DataTableView, tables.PagedTableMixin,
- volume_tabs.VolumeTableMixIn):
+ volume_views.VolumeTableMixIn):
table_class = backup_tables.BackupsTable
page_title = _("Volume Backups")
@@ -61,7 +61,7 @@ class CreateBackupView(forms.ModalFormView):
form_class = backup_forms.CreateBackupForm
template_name = 'project/backups/create_backup.html'
submit_label = _("Create Volume Backup")
- submit_url = "horizon:project:volumes:volumes:create_backup"
+ submit_url = "horizon:project:volumes:create_backup"
success_url = reverse_lazy("horizon:project:backups:index")
page_title = _("Create Volume Backup")
diff --git a/openstack_dashboard/dashboards/project/images/images/tables.py b/openstack_dashboard/dashboards/project/images/images/tables.py
index e5e10b4fd7..030687d4ee 100644
--- a/openstack_dashboard/dashboards/project/images/images/tables.py
+++ b/openstack_dashboard/dashboards/project/images/images/tables.py
@@ -146,7 +146,7 @@ class EditImage(tables.LinkAction):
class CreateVolumeFromImage(tables.LinkAction):
name = "create_volume_from_image"
verbose_name = _("Create Volume")
- url = "horizon:project:volumes:volumes:create"
+ url = "horizon:project:volumes:create"
classes = ("ajax-modal",)
icon = "camera"
policy_rules = (("volume", "volume:create"),)
diff --git a/openstack_dashboard/dashboards/project/instances/views.py b/openstack_dashboard/dashboards/project/instances/views.py
index 844d6075fa..2c7ecf3187 100644
--- a/openstack_dashboard/dashboards/project/instances/views.py
+++ b/openstack_dashboard/dashboards/project/instances/views.py
@@ -306,7 +306,7 @@ class DetailView(tabs.TabView):
redirect_url = 'horizon:project:instances:index'
page_title = "{{ instance.name|default:instance.id }}"
image_url = 'horizon:project:images:images:detail'
- volume_url = 'horizon:project:volumes:volumes:detail'
+ volume_url = 'horizon:project:volumes:detail'
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
diff --git a/openstack_dashboard/dashboards/project/snapshots/tables.py b/openstack_dashboard/dashboards/project/snapshots/tables.py
index f0c7ecfd3a..a8f0c92ad9 100644
--- a/openstack_dashboard/dashboards/project/snapshots/tables.py
+++ b/openstack_dashboard/dashboards/project/snapshots/tables.py
@@ -27,7 +27,7 @@ from openstack_dashboard.api import cinder
from openstack_dashboard import policy
from openstack_dashboard.dashboards.project.volumes \
- .volumes import tables as volume_tables
+ import tables as volume_tables
class LaunchSnapshot(volume_tables.LaunchVolume):
@@ -116,7 +116,7 @@ class EditVolumeSnapshot(policy.PolicyTargetMixin, tables.LinkAction):
class CreateVolumeFromSnapshot(tables.LinkAction):
name = "create_from_snapshot"
verbose_name = _("Create Volume")
- url = "horizon:project:volumes:volumes:create"
+ url = "horizon:project:volumes:create"
classes = ("ajax-modal",)
icon = "camera"
policy_rules = (("volume", "volume:create"),)
@@ -193,7 +193,7 @@ class VolumeSnapshotsTable(volume_tables.VolumesTableBase):
volume_name = SnapshotVolumeNameColumn(
"name",
verbose_name=_("Volume Name"),
- link="horizon:project:volumes:volumes:detail")
+ link="horizon:project:volumes:detail")
class Meta(object):
name = "volume_snapshots"
diff --git a/openstack_dashboard/dashboards/project/snapshots/tests.py b/openstack_dashboard/dashboards/project/snapshots/tests.py
index 1169b92436..2abee07e66 100644
--- a/openstack_dashboard/dashboards/project/snapshots/tests.py
+++ b/openstack_dashboard/dashboards/project/snapshots/tests.py
@@ -137,12 +137,11 @@ class VolumeSnapshotsViewTests(test.TestCase):
AndReturn(usage_limit)
self.mox.ReplayAll()
- url = reverse('horizon:project:volumes:'
- 'volumes:create_snapshot', args=[volume.id])
+ url = reverse('horizon:project:volumes:create_snapshot',
+ args=[volume.id])
res = self.client.get(url)
- self.assertTemplateUsed(res, 'project/volumes/volumes/'
- 'create_snapshot.html')
+ self.assertTemplateUsed(res, 'project/volumes/create_snapshot.html')
@test.create_stubs({cinder: ('volume_get',
'volume_snapshot_create',)})
@@ -165,7 +164,7 @@ class VolumeSnapshotsViewTests(test.TestCase):
'volume_id': volume.id,
'name': snapshot.name,
'description': snapshot.description}
- url = reverse('horizon:project:volumes:volumes:create_snapshot',
+ url = reverse('horizon:project:volumes:create_snapshot',
args=[volume.id])
res = self.client.post(url, formData)
self.assertRedirectsNoFollow(res, INDEX_URL)
@@ -191,7 +190,7 @@ class VolumeSnapshotsViewTests(test.TestCase):
'volume_id': volume.id,
'name': snapshot.name,
'description': snapshot.description}
- url = reverse('horizon:project:volumes:volumes:create_snapshot',
+ url = reverse('horizon:project:volumes:create_snapshot',
args=[volume.id])
res = self.client.post(url, formData)
self.assertRedirectsNoFollow(res, INDEX_URL)
diff --git a/openstack_dashboard/dashboards/project/snapshots/views.py b/openstack_dashboard/dashboards/project/snapshots/views.py
index e0924dfe81..4bc701d7f4 100644
--- a/openstack_dashboard/dashboards/project/snapshots/views.py
+++ b/openstack_dashboard/dashboards/project/snapshots/views.py
@@ -97,7 +97,7 @@ class DetailView(tabs.TabView):
tab_group_class = vol_snapshot_tabs.SnapshotDetailTabs
template_name = 'horizon/common/_detail.html'
page_title = "{{ snapshot.name|default:snapshot.id }}"
- volume_url = 'horizon:project:volumes:volumes:detail'
+ volume_url = 'horizon:project:volumes:detail'
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
diff --git a/openstack_dashboard/dashboards/project/stacks/mappings.py b/openstack_dashboard/dashboards/project/stacks/mappings.py
index 8f03c4bf45..c1c9db16cc 100644
--- a/openstack_dashboard/dashboards/project/stacks/mappings.py
+++ b/openstack_dashboard/dashboards/project/stacks/mappings.py
@@ -44,13 +44,13 @@ resource_urls = {
"AWS::EC2::Subnet": {
'link': 'horizon:project:networks:subnets:detail'},
"AWS::EC2::Volume": {
- 'link': 'horizon:project:volumes:volumes:detail'},
+ 'link': 'horizon:project:volumes:detail'},
"AWS::EC2::VPC": {
'link': 'horizon:project:networks:detail'},
"AWS::S3::Bucket": {
'link': 'horizon:project:containers:index'},
"OS::Cinder::Volume": {
- 'link': 'horizon:project:volumes:volumes:detail'},
+ 'link': 'horizon:project:volumes:detail'},
"OS::Heat::AccessPolicy": {
'link': 'horizon:project:stacks:detail'},
"OS::Heat::AutoScalingGroup": {
diff --git a/openstack_dashboard/dashboards/project/volumes/volumes/forms.py b/openstack_dashboard/dashboards/project/volumes/forms.py
similarity index 99%
rename from openstack_dashboard/dashboards/project/volumes/volumes/forms.py
rename to openstack_dashboard/dashboards/project/volumes/forms.py
index 2a2d588fe4..d7a8e885c5 100644
--- a/openstack_dashboard/dashboards/project/volumes/volumes/forms.py
+++ b/openstack_dashboard/dashboards/project/volumes/forms.py
@@ -564,7 +564,7 @@ class CreateTransferForm(forms.SelfHandlingForm):
msg = _('Created volume transfer: "%s".') % data['name']
messages.success(request, msg)
response = http.HttpResponseRedirect(
- reverse("horizon:project:volumes:volumes:show_transfer",
+ reverse("horizon:project:volumes:show_transfer",
args=(transfer.id, transfer.auth_key)))
return response
except Exception:
diff --git a/openstack_dashboard/dashboards/project/volumes/panel.py b/openstack_dashboard/dashboards/project/volumes/panel.py
index 88c9636096..88380610b1 100644
--- a/openstack_dashboard/dashboards/project/volumes/panel.py
+++ b/openstack_dashboard/dashboards/project/volumes/panel.py
@@ -18,7 +18,6 @@ import horizon
class Volumes(horizon.Panel):
-
name = _("Volumes")
slug = 'volumes'
permissions = (
diff --git a/openstack_dashboard/dashboards/project/volumes/volumes/tables.py b/openstack_dashboard/dashboards/project/volumes/tables.py
similarity index 96%
rename from openstack_dashboard/dashboards/project/volumes/volumes/tables.py
rename to openstack_dashboard/dashboards/project/volumes/tables.py
index 73164445c8..80244ad7e2 100644
--- a/openstack_dashboard/dashboards/project/volumes/volumes/tables.py
+++ b/openstack_dashboard/dashboards/project/volumes/tables.py
@@ -129,7 +129,7 @@ class DeleteVolume(VolumePolicyTargetMixin, tables.DeleteAction):
class CreateVolume(tables.LinkAction):
name = "create"
verbose_name = _("Create Volume")
- url = "horizon:project:volumes:volumes:create"
+ url = "horizon:project:volumes:create"
classes = ("ajax-modal", "btn-create")
icon = "plus"
policy_rules = (("volume", "volume:create"),)
@@ -166,7 +166,7 @@ class CreateVolume(tables.LinkAction):
class ExtendVolume(VolumePolicyTargetMixin, tables.LinkAction):
name = "extend"
verbose_name = _("Extend Volume")
- url = "horizon:project:volumes:volumes:extend"
+ url = "horizon:project:volumes:extend"
classes = ("ajax-modal", "btn-extend")
policy_rules = (("volume", "volume:extend"),)
@@ -177,7 +177,7 @@ class ExtendVolume(VolumePolicyTargetMixin, tables.LinkAction):
class EditAttachments(tables.LinkAction):
name = "attachments"
verbose_name = _("Manage Attachments")
- url = "horizon:project:volumes:volumes:attach"
+ url = "horizon:project:volumes:attach"
classes = ("ajax-modal",)
icon = "pencil"
@@ -204,7 +204,7 @@ class EditAttachments(tables.LinkAction):
class CreateSnapshot(VolumePolicyTargetMixin, tables.LinkAction):
name = "snapshots"
verbose_name = _("Create Snapshot")
- url = "horizon:project:volumes:volumes:create_snapshot"
+ url = "horizon:project:volumes:create_snapshot"
classes = ("ajax-modal",)
icon = "camera"
policy_rules = (("volume", "volume:create_snapshot"),)
@@ -229,7 +229,7 @@ class CreateSnapshot(VolumePolicyTargetMixin, tables.LinkAction):
class CreateTransfer(VolumePolicyTargetMixin, tables.LinkAction):
name = "create_transfer"
verbose_name = _("Create Transfer")
- url = "horizon:project:volumes:volumes:create_transfer"
+ url = "horizon:project:volumes:create_transfer"
classes = ("ajax-modal",)
policy_rules = (("volume", "volume:create_transfer"),)
@@ -240,7 +240,7 @@ class CreateTransfer(VolumePolicyTargetMixin, tables.LinkAction):
class CreateBackup(VolumePolicyTargetMixin, tables.LinkAction):
name = "backups"
verbose_name = _("Create Backup")
- url = "horizon:project:volumes:volumes:create_backup"
+ url = "horizon:project:volumes:create_backup"
classes = ("ajax-modal",)
policy_rules = (("volume", "backup:create"),)
@@ -252,7 +252,7 @@ class CreateBackup(VolumePolicyTargetMixin, tables.LinkAction):
class UploadToImage(VolumePolicyTargetMixin, tables.LinkAction):
name = "upload_to_image"
verbose_name = _("Upload to Image")
- url = "horizon:project:volumes:volumes:upload_to_image"
+ url = "horizon:project:volumes:upload_to_image"
classes = ("ajax-modal",)
icon = "cloud-upload"
policy_rules = (("volume",
@@ -269,7 +269,7 @@ class UploadToImage(VolumePolicyTargetMixin, tables.LinkAction):
class EditVolume(VolumePolicyTargetMixin, tables.LinkAction):
name = "edit"
verbose_name = _("Edit Volume")
- url = "horizon:project:volumes:volumes:update"
+ url = "horizon:project:volumes:update"
classes = ("ajax-modal",)
icon = "pencil"
policy_rules = (("volume", "volume:update"),)
@@ -281,7 +281,7 @@ class EditVolume(VolumePolicyTargetMixin, tables.LinkAction):
class RetypeVolume(VolumePolicyTargetMixin, tables.LinkAction):
name = "retype"
verbose_name = _("Change Volume Type")
- url = "horizon:project:volumes:volumes:retype"
+ url = "horizon:project:volumes:retype"
classes = ("ajax-modal",)
icon = "pencil"
policy_rules = (("volume", "volume:retype"),)
@@ -293,7 +293,7 @@ class RetypeVolume(VolumePolicyTargetMixin, tables.LinkAction):
class AcceptTransfer(tables.LinkAction):
name = "accept_transfer"
verbose_name = _("Accept Transfer")
- url = "horizon:project:volumes:volumes:accept_transfer"
+ url = "horizon:project:volumes:accept_transfer"
classes = ("ajax-modal",)
icon = "exchange"
policy_rules = (("volume", "volume:accept_transfer"),)
@@ -401,7 +401,7 @@ def get_encrypted_value(volume):
def get_encrypted_link(volume):
if hasattr(volume, 'encrypted') and volume.encrypted:
- return reverse("horizon:project:volumes:volumes:encryption_detail",
+ return reverse("horizon:project:volumes:encryption_detail",
kwargs={'volume_id': volume.id})
@@ -444,7 +444,7 @@ class VolumesTableBase(tables.DataTable):
)
name = tables.Column("name",
verbose_name=_("Name"),
- link="horizon:project:volumes:volumes:detail")
+ link="horizon:project:volumes:detail")
description = tables.Column("description",
verbose_name=_("Description"),
truncate=40)
@@ -490,7 +490,7 @@ class UpdateMetadata(tables.LinkAction):
class VolumesTable(VolumesTableBase):
name = tables.WrappingColumn("name",
verbose_name=_("Name"),
- link="horizon:project:volumes:volumes:detail")
+ link="horizon:project:volumes:detail")
volume_type = tables.Column(get_volume_type,
verbose_name=_("Type"))
attachments = AttachmentColumn("attachments",
diff --git a/openstack_dashboard/dashboards/project/volumes/tabs.py b/openstack_dashboard/dashboards/project/volumes/tabs.py
index 4196f23991..5f48ea88b2 100644
--- a/openstack_dashboard/dashboards/project/volumes/tabs.py
+++ b/openstack_dashboard/dashboards/project/volumes/tabs.py
@@ -1,4 +1,4 @@
-# Copyright 2013 Nebula, Inc.
+# 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
@@ -12,114 +12,20 @@
# License for the specific language governing permissions and limitations
# under the License.
-from collections import OrderedDict
-
from django.utils.translation import ugettext_lazy as _
-from horizon import exceptions
-from horizon.tables import PagedTableMixin
from horizon import tabs
-from openstack_dashboard import api
-from openstack_dashboard.dashboards.project.volumes.volumes \
- import tables as volume_tables
+class OverviewTab(tabs.Tab):
+ name = _("Overview")
+ slug = "overview"
+ template_name = ("project/volumes/_detail_overview.html")
+
+ def get_context_data(self, request):
+ return {"volume": self.tab_group.kwargs['volume']}
-class VolumeTableMixIn(object):
- _has_more_data = False
- _has_prev_data = False
-
- def _get_volumes(self, search_opts=None):
- try:
- marker, sort_dir = self._get_marker()
- volumes, self._has_more_data, self._has_prev_data = \
- api.cinder.volume_list_paged(self.request, marker=marker,
- search_opts=search_opts,
- sort_dir=sort_dir, paginate=True)
-
- if sort_dir == "asc":
- volumes.reverse()
-
- return volumes
- except Exception:
- exceptions.handle(self.request,
- _('Unable to retrieve volume list.'))
- return []
-
- def _get_instances(self, search_opts=None, instance_ids=None):
- if not instance_ids:
- return []
- try:
- # TODO(tsufiev): we should pass attached_instance_ids to
- # nova.server_list as soon as Nova API allows for this
- instances, has_more = api.nova.server_list(self.request,
- search_opts=search_opts,
- detailed=False)
- return instances
- except Exception:
- exceptions.handle(self.request,
- _("Unable to retrieve volume/instance "
- "attachment information"))
- return []
-
- def _get_volumes_ids_with_snapshots(self, search_opts=None):
- try:
- volume_ids = []
- snapshots = api.cinder.volume_snapshot_list(
- self.request, search_opts=search_opts)
- if snapshots:
- # extract out the volume ids
- volume_ids = set([(s.volume_id) for s in snapshots])
- except Exception:
- exceptions.handle(self.request,
- _("Unable to retrieve snapshot list."))
-
- return volume_ids
-
- def _get_attached_instance_ids(self, volumes):
- attached_instance_ids = []
- for volume in volumes:
- for att in volume.attachments:
- server_id = att.get('server_id', None)
- if server_id is not None:
- attached_instance_ids.append(server_id)
- return attached_instance_ids
-
- # set attachment string and if volume has snapshots
- def _set_volume_attributes(self,
- volumes,
- instances,
- volume_ids_with_snapshots):
- instances = OrderedDict([(inst.id, inst) for inst in instances])
- for volume in volumes:
- if volume_ids_with_snapshots:
- if volume.id in volume_ids_with_snapshots:
- setattr(volume, 'has_snapshot', True)
- if instances:
- for att in volume.attachments:
- server_id = att.get('server_id', None)
- att['instance'] = instances.get(server_id, None)
-
-
-class VolumeTab(PagedTableMixin, tabs.TableTab, VolumeTableMixIn):
- table_classes = (volume_tables.VolumesTable,)
- name = _("Volumes")
- slug = "volumes_tab"
- template_name = ("horizon/common/_detail_table.html")
- preload = False
-
- def get_volumes_data(self):
- volumes = self._get_volumes()
- attached_instance_ids = self._get_attached_instance_ids(volumes)
- instances = self._get_instances(instance_ids=attached_instance_ids)
- volume_ids_with_snapshots = self._get_volumes_ids_with_snapshots()
- self._set_volume_attributes(
- volumes, instances, volume_ids_with_snapshots)
- return volumes
-
-
-class VolumeAndSnapshotTabs(tabs.TabGroup):
- slug = "volumes_and_snapshots"
- tabs = (VolumeTab, )
- sticky = True
+class VolumeDetailTabs(tabs.TabGroup):
+ slug = "volume_details"
+ tabs = (OverviewTab,)
diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_accept_transfer.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/_accept_transfer.html
similarity index 100%
rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_accept_transfer.html
rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/_accept_transfer.html
diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_attach.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/_attach.html
similarity index 100%
rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_attach.html
rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/_attach.html
diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_create.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/_create.html
similarity index 66%
rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_create.html
rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/_create.html
index 3d08c83b5f..d7cad6ddb7 100644
--- a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_create.html
+++ b/openstack_dashboard/dashboards/project/volumes/templates/volumes/_create.html
@@ -3,6 +3,6 @@
{% block modal-body-right %}
- {% include "project/volumes/volumes/_limits.html" with usages=usages %}
+ {% include "project/volumes/_limits.html" with usages=usages %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_create_snapshot.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/_create_snapshot.html
similarity index 83%
rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_create_snapshot.html
rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/_create_snapshot.html
index dc689c8d2c..abb88b9c5f 100644
--- a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_create_snapshot.html
+++ b/openstack_dashboard/dashboards/project/volumes/templates/volumes/_create_snapshot.html
@@ -3,7 +3,7 @@
{% block modal-body-right %}
- {% include "project/volumes/volumes/_snapshot_limits.html" with usages=usages snapshot_quota=True %}
+ {% include "project/volumes/_snapshot_limits.html" with usages=usages snapshot_quota=True %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_create_transfer.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/_create_transfer.html
similarity index 100%
rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_create_transfer.html
rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/_create_transfer.html
diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_detail_overview.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/_detail_overview.html
similarity index 96%
rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_detail_overview.html
rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/_detail_overview.html
index b057690b08..2358b229ea 100644
--- a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_detail_overview.html
+++ b/openstack_dashboard/dashboards/project/volumes/templates/volumes/_detail_overview.html
@@ -31,7 +31,7 @@
{{ volume.is_bootable|yesno|capfirst }}
{% trans "Encrypted" %}
{% if volume.encrypted %}
- {% trans "Yes" %}
+ {% trans "Yes" %}
{% else %}
{% trans "No" %}
{% endif %}
diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_encryption_detail_overview.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/_encryption_detail_overview.html
similarity index 100%
rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_encryption_detail_overview.html
rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/_encryption_detail_overview.html
diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_extend.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/_extend.html
similarity index 64%
rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_extend.html
rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/_extend.html
index 6a1eeb599f..b41a9f1d17 100644
--- a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_extend.html
+++ b/openstack_dashboard/dashboards/project/volumes/templates/volumes/_extend.html
@@ -3,6 +3,6 @@
{% block modal-body-right %}
- {% include "project/volumes/volumes/_extend_limits.html" with usages=usages %}
+ {% include "project/volumes/_extend_limits.html" with usages=usages %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_extend_limits.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/_extend_limits.html
similarity index 100%
rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_extend_limits.html
rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/_extend_limits.html
diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_limits.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/_limits.html
similarity index 100%
rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_limits.html
rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/_limits.html
diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_retype.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/_retype.html
similarity index 100%
rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_retype.html
rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/_retype.html
diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_show_transfer.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/_show_transfer.html
similarity index 100%
rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_show_transfer.html
rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/_show_transfer.html
diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_snapshot_limits.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/_snapshot_limits.html
similarity index 93%
rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_snapshot_limits.html
rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/_snapshot_limits.html
index 1f3c5323f2..e7cbc48500 100644
--- a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_snapshot_limits.html
+++ b/openstack_dashboard/dashboards/project/volumes/templates/volumes/_snapshot_limits.html
@@ -1,4 +1,4 @@
-{% extends "project/volumes/volumes/_limits.html" %}
+{% extends "project/volumes/_limits.html" %}
{% load i18n horizon humanize %}
{% block title %}
diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_update.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/_update.html
similarity index 100%
rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_update.html
rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/_update.html
diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_upload_to_image.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/_upload_to_image.html
similarity index 100%
rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_upload_to_image.html
rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/_upload_to_image.html
diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/accept_transfer.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/accept_transfer.html
similarity index 68%
rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/accept_transfer.html
rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/accept_transfer.html
index 079f9a1749..1106b99bbf 100644
--- a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/accept_transfer.html
+++ b/openstack_dashboard/dashboards/project/volumes/templates/volumes/accept_transfer.html
@@ -3,5 +3,5 @@
{% block title %}{% trans "Accept Volume Transfer" %}{% endblock %}
{% block main %}
- {% include 'project/volumes/volumes/_accept_transfer.html' %}
+ {% include 'project/volumes/_accept_transfer.html' %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/attach.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/attach.html
similarity index 71%
rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/attach.html
rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/attach.html
index 96d75a75f1..29f263dcc3 100644
--- a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/attach.html
+++ b/openstack_dashboard/dashboards/project/volumes/templates/volumes/attach.html
@@ -3,5 +3,5 @@
{% block title %}{% trans "Manage Volume Attachments" %}{% endblock %}
{% block main %}
- {% include 'project/volumes/volumes/_attach.html' %}
+ {% include 'project/volumes/_attach.html' %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/create.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/create.html
similarity index 70%
rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/create.html
rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/create.html
index 8d3209f4eb..1c7973b4f4 100644
--- a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/create.html
+++ b/openstack_dashboard/dashboards/project/volumes/templates/volumes/create.html
@@ -3,5 +3,5 @@
{% block title %}{% trans "Create Volume" %}{% endblock %}
{% block main %}
- {% include 'project/volumes/volumes/_create.html' %}
+ {% include 'project/volumes/_create.html' %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/create_snapshot.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/create_snapshot.html
similarity index 68%
rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/create_snapshot.html
rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/create_snapshot.html
index 55413883cf..13fe8e312d 100644
--- a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/create_snapshot.html
+++ b/openstack_dashboard/dashboards/project/volumes/templates/volumes/create_snapshot.html
@@ -3,5 +3,5 @@
{% block title %}{% trans "Create Volume Snapshot" %}{% endblock %}
{% block main %}
- {% include 'project/volumes/volumes/_create_snapshot.html' %}
+ {% include 'project/volumes/_create_snapshot.html' %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/create_transfer.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/create_transfer.html
similarity index 68%
rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/create_transfer.html
rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/create_transfer.html
index 6b35292f30..761448f7f0 100644
--- a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/create_transfer.html
+++ b/openstack_dashboard/dashboards/project/volumes/templates/volumes/create_transfer.html
@@ -3,5 +3,5 @@
{% block title %}{% trans "Create Volume Transfer" %}{% endblock %}
{% block main %}
- {% include 'project/volumes/volumes/_create_transfer.html' %}
+ {% include 'project/volumes/_create_transfer.html' %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/encryption_detail.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/encryption_detail.html
similarity index 80%
rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/encryption_detail.html
rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/encryption_detail.html
index 5881a4afcd..e01894ebe6 100644
--- a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/encryption_detail.html
+++ b/openstack_dashboard/dashboards/project/volumes/templates/volumes/encryption_detail.html
@@ -9,7 +9,7 @@
{% block main %}
- {% include "project/volumes/volumes/_encryption_detail_overview.html" %}
+ {% include "project/volumes/_encryption_detail_overview.html" %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/extend.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/extend.html
similarity index 70%
rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/extend.html
rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/extend.html
index 00b29e8441..d5fe67881e 100644
--- a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/extend.html
+++ b/openstack_dashboard/dashboards/project/volumes/templates/volumes/extend.html
@@ -3,5 +3,5 @@
{% block title %}{% trans "Extend Volume" %}{% endblock %}
{% block main %}
- {% include 'project/volumes/volumes/_extend.html' %}
+ {% include 'project/volumes/_extend.html' %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/index.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/index.html
deleted file mode 100644
index 4b518c863d..0000000000
--- a/openstack_dashboard/dashboards/project/volumes/templates/volumes/index.html
+++ /dev/null
@@ -1,11 +0,0 @@
-{% extends 'base.html' %}
-{% load i18n %}
-{% block title %}{% trans "Volumes" %}{% endblock %}
-
-{% block main %}
-
-
- {{ tab_group.render }}
-
-
-{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/retype.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/retype.html
similarity index 70%
rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/retype.html
rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/retype.html
index 3316d9af02..ea58f53ab6 100644
--- a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/retype.html
+++ b/openstack_dashboard/dashboards/project/volumes/templates/volumes/retype.html
@@ -3,5 +3,5 @@
{% block title %}{% trans "Change Volume Type" %}{% endblock %}
{% block main %}
- {% include 'project/volumes/volumes/_retype.html' %}
+ {% include 'project/volumes/_retype.html' %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/show_transfer.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/show_transfer.html
similarity index 69%
rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/show_transfer.html
rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/show_transfer.html
index debf30847a..d0e701cd41 100644
--- a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/show_transfer.html
+++ b/openstack_dashboard/dashboards/project/volumes/templates/volumes/show_transfer.html
@@ -3,5 +3,5 @@
{% block title %}{% trans "Volume Transfer Details" %}{% endblock %}
{% block main %}
- {% include 'project/volumes/volumes/_show_transfer.html' %}
+ {% include 'project/volumes/_show_transfer.html' %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/update.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/update.html
similarity index 69%
rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/update.html
rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/update.html
index 85c573b486..9bae6c483c 100644
--- a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/update.html
+++ b/openstack_dashboard/dashboards/project/volumes/templates/volumes/update.html
@@ -3,5 +3,5 @@
{% block title %}{% trans "Edit Volume" %}{% endblock %}
{% block main %}
- {% include 'project/volumes/volumes/_update.html' %}
+ {% include 'project/volumes/_update.html' %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/upload_to_image.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/upload_to_image.html
similarity index 68%
rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/upload_to_image.html
rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/upload_to_image.html
index 519eec32c7..a405ea1f69 100644
--- a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/upload_to_image.html
+++ b/openstack_dashboard/dashboards/project/volumes/templates/volumes/upload_to_image.html
@@ -3,5 +3,5 @@
{% block title %}{% trans "Upload Volume to Image" %}{% endblock %}
{% block main %}
- {% include 'project/volumes/volumes/_upload_to_image.html' %}
+ {% include 'project/volumes/_upload_to_image.html' %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/volumes/test.py b/openstack_dashboard/dashboards/project/volumes/test.py
deleted file mode 100644
index 9b5f00c837..0000000000
--- a/openstack_dashboard/dashboards/project/volumes/test.py
+++ /dev/null
@@ -1,175 +0,0 @@
-# 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.
-
-import copy
-
-from django.conf import settings
-from django.core.urlresolvers import reverse
-from django import http
-from django.test.utils import override_settings
-from django.utils.http import urlunquote
-
-from mox3.mox import IsA # noqa
-
-from openstack_dashboard import api
-from openstack_dashboard.dashboards.project.volumes.volumes \
- import tables as volume_tables
-from openstack_dashboard.test import helpers as test
-
-
-INDEX_URL = reverse('horizon:project:volumes:index')
-
-
-class VolumeAndSnapshotsAndBackupsTests(test.TestCase):
- @test.create_stubs({api.cinder: ('tenant_absolute_limits',
- 'volume_list',
- 'volume_list_paged',
- 'volume_snapshot_list',
- 'volume_backup_supported',
- 'volume_backup_list_paged',
- ),
- api.nova: ('server_list',)})
- def test_index(self, instanceless_volumes=False):
- vol_snaps = self.cinder_volume_snapshots.list()
- volumes = self.cinder_volumes.list()
- if instanceless_volumes:
- for volume in volumes:
- volume.attachments = []
-
- api.cinder.volume_backup_supported(IsA(http.HttpRequest)).\
- MultipleTimes().AndReturn(False)
- api.cinder.volume_list_paged(
- IsA(http.HttpRequest), marker=None, search_opts=None,
- sort_dir='desc', paginate=True).\
- AndReturn([volumes, False, False])
- if not instanceless_volumes:
- api.nova.server_list(IsA(http.HttpRequest), search_opts=None,
- detailed=False).\
- AndReturn([self.servers.list(), False])
- api.cinder.volume_snapshot_list(IsA(http.HttpRequest)).\
- AndReturn(vol_snaps)
-
- api.cinder.tenant_absolute_limits(IsA(http.HttpRequest)).\
- MultipleTimes().AndReturn(self.cinder_limits['absolute'])
- self.mox.ReplayAll()
-
- res = self.client.get(INDEX_URL)
- self.assertEqual(res.status_code, 200)
- self.assertTemplateUsed(res, 'project/volumes/index.html')
-
- def test_index_no_volume_attachments(self):
- self.test_index(instanceless_volumes=True)
-
- @test.create_stubs({api.cinder: ('tenant_absolute_limits',
- 'volume_list_paged',
- 'volume_backup_supported',
- 'volume_snapshot_list'),
- api.nova: ('server_list',)})
- def _test_index_paginated(self, marker, sort_dir, volumes, url,
- has_more, has_prev):
- backup_supported = True
- vol_snaps = self.cinder_volume_snapshots.list()
-
- api.cinder.volume_backup_supported(IsA(http.HttpRequest)).\
- MultipleTimes().AndReturn(backup_supported)
- api.cinder.volume_list_paged(IsA(http.HttpRequest), marker=marker,
- sort_dir=sort_dir, search_opts=None,
- paginate=True).\
- AndReturn([volumes, has_more, has_prev])
- api.cinder.volume_snapshot_list(
- IsA(http.HttpRequest), search_opts=None).AndReturn(vol_snaps)
- api.nova.server_list(IsA(http.HttpRequest), search_opts=None,
- detailed=False).\
- AndReturn([self.servers.list(), False])
- api.cinder.tenant_absolute_limits(IsA(http.HttpRequest)).MultipleTimes().\
- AndReturn(self.cinder_limits['absolute'])
- self.mox.ReplayAll()
-
- res = self.client.get(urlunquote(url))
- self.assertEqual(res.status_code, 200)
- self.assertTemplateUsed(res, 'project/volumes/index.html')
-
- self.mox.UnsetStubs()
- return res
-
- def ensure_attachments_exist(self, volumes):
- volumes = copy.copy(volumes)
- for volume in volumes:
- if not volume.attachments:
- volume.attachments.append({
- "id": "1", "server_id": '1', "device": "/dev/hda"})
- return volumes
-
- @override_settings(API_RESULT_PAGE_SIZE=2)
- def test_index_paginated(self):
- mox_volumes = self.ensure_attachments_exist(self.cinder_volumes.list())
- size = settings.API_RESULT_PAGE_SIZE
-
- # get first page
- expected_volumes = mox_volumes[:size]
- url = INDEX_URL
- res = self._test_index_paginated(marker=None, sort_dir="desc",
- volumes=expected_volumes, url=url,
- has_more=True, has_prev=False)
- volumes = res.context['volumes_table'].data
- self.assertItemsEqual(volumes, expected_volumes)
-
- # get second page
- expected_volumes = mox_volumes[size:2 * size]
- marker = expected_volumes[0].id
- next = volume_tables.VolumesTable._meta.pagination_param
- url = "?".join([INDEX_URL, "=".join([next, marker])])
- res = self._test_index_paginated(marker=marker, sort_dir="desc",
- volumes=expected_volumes, url=url,
- has_more=True, has_prev=True)
- volumes = res.context['volumes_table'].data
- self.assertItemsEqual(volumes, expected_volumes)
-
- # get last page
- expected_volumes = mox_volumes[-size:]
- marker = expected_volumes[0].id
- next = volume_tables.VolumesTable._meta.pagination_param
- url = "?".join([INDEX_URL, "=".join([next, marker])])
- res = self._test_index_paginated(marker=marker, sort_dir="desc",
- volumes=expected_volumes, url=url,
- has_more=False, has_prev=True)
- volumes = res.context['volumes_table'].data
- self.assertItemsEqual(volumes, expected_volumes)
-
- @override_settings(API_RESULT_PAGE_SIZE=2)
- def test_index_paginated_prev_page(self):
- mox_volumes = self.ensure_attachments_exist(self.cinder_volumes.list())
- size = settings.API_RESULT_PAGE_SIZE
-
- # prev from some page
- expected_volumes = mox_volumes[size:2 * size]
- marker = expected_volumes[0].id
- prev = volume_tables.VolumesTable._meta.prev_pagination_param
- url = "?".join([INDEX_URL, "=".join([prev, marker])])
- res = self._test_index_paginated(marker=marker, sort_dir="asc",
- volumes=expected_volumes, url=url,
- has_more=True, has_prev=True)
- volumes = res.context['volumes_table'].data
- self.assertItemsEqual(volumes, expected_volumes)
-
- # back to first page
- expected_volumes = mox_volumes[:size]
- marker = expected_volumes[0].id
- prev = volume_tables.VolumesTable._meta.prev_pagination_param
- url = "?".join([INDEX_URL, "=".join([prev, marker])])
- res = self._test_index_paginated(marker=marker, sort_dir="asc",
- volumes=expected_volumes, url=url,
- has_more=True, has_prev=False)
- volumes = res.context['volumes_table'].data
- self.assertItemsEqual(volumes, expected_volumes)
diff --git a/openstack_dashboard/dashboards/project/volumes/volumes/tests.py b/openstack_dashboard/dashboards/project/volumes/tests.py
similarity index 87%
rename from openstack_dashboard/dashboards/project/volumes/volumes/tests.py
rename to openstack_dashboard/dashboards/project/volumes/tests.py
index 253a3153e6..c4db43f5c1 100644
--- a/openstack_dashboard/dashboards/project/volumes/volumes/tests.py
+++ b/openstack_dashboard/dashboards/project/volumes/tests.py
@@ -1,7 +1,3 @@
-# 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
@@ -16,7 +12,12 @@
# License for the specific language governing permissions and limitations
# under the License.
+import copy
+import six
+from six import moves
+
import django
+from django.conf import settings
from django.core.urlresolvers import reverse
from django.forms import widgets
from django import http
@@ -25,22 +26,179 @@ from django.test.utils import override_settings
from django.utils.http import urlunquote
from mox3.mox import IsA # noqa
-import six
-from six import moves
from openstack_dashboard import api
from openstack_dashboard.api import cinder
+from openstack_dashboard.dashboards.project.volumes \
+ import tables as volume_tables
from openstack_dashboard.test import helpers as test
from openstack_dashboard.usage import quotas
-VOLUME_INDEX_URL = reverse('horizon:project:volumes:index')
-VOLUME_VOLUMES_TAB_URL = urlunquote(reverse(
- 'horizon:project:volumes:volumes_tab'))
+INDEX_URL = reverse('horizon:project:volumes:index')
SEARCH_OPTS = dict(status=api.cinder.VOLUME_STATE_AVAILABLE)
class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
+ def tearDown(self):
+ for volume in self.cinder_volumes.list():
+ # VolumeTableMixIn._set_volume_attributes mutates data
+ # and cinder_volumes.list() doesn't deep copy
+ for att in volume.attachments:
+ if 'instance' in att:
+ del att['instance']
+ super(VolumeViewTests, self).tearDown()
+
+ @test.create_stubs({api.cinder: ('tenant_absolute_limits',
+ 'volume_list',
+ 'volume_list_paged',
+ 'volume_snapshot_list',
+ 'volume_backup_supported',
+ 'volume_backup_list_paged',
+ ),
+ api.nova: ('server_list', 'server_get')})
+ def test_index(self, with_attachments=True):
+ vol_snaps = self.cinder_volume_snapshots.list()
+ volumes = self.cinder_volumes.list()
+ if with_attachments:
+ server = self.servers.first()
+ else:
+ for volume in volumes:
+ volume.attachments = []
+
+ api.cinder.volume_backup_supported(IsA(http.HttpRequest)).\
+ MultipleTimes().AndReturn(False)
+ api.cinder.volume_list_paged(
+ IsA(http.HttpRequest), marker=None, search_opts=None,
+ sort_dir='desc', paginate=True).\
+ AndReturn([volumes, False, False])
+ if with_attachments:
+ api.nova.server_get(IsA(http.HttpRequest),
+ server.id).AndReturn(server)
+
+ api.nova.server_list(IsA(http.HttpRequest), search_opts=None,
+ detailed=False).\
+ AndReturn([self.servers.list(), False])
+ api.cinder.volume_snapshot_list(IsA(http.HttpRequest)). \
+ AndReturn(vol_snaps)
+
+ api.cinder.tenant_absolute_limits(IsA(http.HttpRequest)).\
+ MultipleTimes().AndReturn(self.cinder_limits['absolute'])
+ self.mox.ReplayAll()
+
+ res = self.client.get(INDEX_URL)
+ self.assertEqual(res.status_code, 200)
+ self.assertTemplateUsed(res, 'horizon/common/_data_table_view.html')
+
+ def test_index_no_volume_attachments(self):
+ self.test_index(with_attachments=False)
+
+ @test.create_stubs({api.cinder: ('tenant_absolute_limits',
+ 'volume_list_paged',
+ 'volume_backup_supported',
+ 'volume_snapshot_list'),
+ api.nova: ('server_list', 'server_get')})
+ def _test_index_paginated(self, marker, sort_dir, volumes, url,
+ has_more, has_prev):
+ backup_supported = True
+ vol_snaps = self.cinder_volume_snapshots.list()
+ server = self.servers.first()
+
+ api.cinder.volume_backup_supported(IsA(http.HttpRequest)).\
+ MultipleTimes().AndReturn(backup_supported)
+ api.cinder.volume_list_paged(IsA(http.HttpRequest), marker=marker,
+ sort_dir=sort_dir, search_opts=None,
+ paginate=True).\
+ AndReturn([volumes, has_more, has_prev])
+ api.cinder.volume_snapshot_list(
+ IsA(http.HttpRequest), search_opts=None).AndReturn(vol_snaps)
+ api.nova.server_list(IsA(http.HttpRequest), search_opts=None,
+ detailed=False).\
+ AndReturn([self.servers.list(), False])
+ api.nova.server_get(IsA(http.HttpRequest),
+ server.id).AndReturn(server)
+ api.cinder.tenant_absolute_limits(IsA(http.HttpRequest)).MultipleTimes().\
+ AndReturn(self.cinder_limits['absolute'])
+ self.mox.ReplayAll()
+
+ res = self.client.get(urlunquote(url))
+ self.assertEqual(res.status_code, 200)
+ self.assertTemplateUsed(res, 'horizon/common/_data_table_view.html')
+
+ self.mox.UnsetStubs()
+ return res
+
+ def ensure_attachments_exist(self, volumes):
+ volumes = copy.copy(volumes)
+ for volume in volumes:
+ if not volume.attachments:
+ volume.attachments.append({
+ "id": "1", "server_id": '1', "device": "/dev/hda"})
+ return volumes
+
+ @override_settings(API_RESULT_PAGE_SIZE=2)
+ def test_index_paginated(self):
+ mox_volumes = self.ensure_attachments_exist(self.cinder_volumes.list())
+ size = settings.API_RESULT_PAGE_SIZE
+
+ # get first page
+ expected_volumes = mox_volumes[:size]
+ url = INDEX_URL
+ res = self._test_index_paginated(marker=None, sort_dir="desc",
+ volumes=expected_volumes, url=url,
+ has_more=True, has_prev=False)
+ volumes = res.context['volumes_table'].data
+ self.assertItemsEqual(volumes, expected_volumes)
+
+ # get second page
+ expected_volumes = mox_volumes[size:2 * size]
+ marker = expected_volumes[0].id
+ next = volume_tables.VolumesTable._meta.pagination_param
+ url = "?".join([INDEX_URL, "=".join([next, marker])])
+ res = self._test_index_paginated(marker=marker, sort_dir="desc",
+ volumes=expected_volumes, url=url,
+ has_more=True, has_prev=True)
+ volumes = res.context['volumes_table'].data
+ self.assertItemsEqual(volumes, expected_volumes)
+
+ # get last page
+ expected_volumes = mox_volumes[-size:]
+ marker = expected_volumes[0].id
+ next = volume_tables.VolumesTable._meta.pagination_param
+ url = "?".join([INDEX_URL, "=".join([next, marker])])
+ res = self._test_index_paginated(marker=marker, sort_dir="desc",
+ volumes=expected_volumes, url=url,
+ has_more=False, has_prev=True)
+ volumes = res.context['volumes_table'].data
+ self.assertItemsEqual(volumes, expected_volumes)
+
+ @override_settings(API_RESULT_PAGE_SIZE=2)
+ def test_index_paginated_prev_page(self):
+ mox_volumes = self.ensure_attachments_exist(self.cinder_volumes.list())
+ size = settings.API_RESULT_PAGE_SIZE
+
+ # prev from some page
+ expected_volumes = mox_volumes[size:2 * size]
+ marker = expected_volumes[0].id
+ prev = volume_tables.VolumesTable._meta.prev_pagination_param
+ url = "?".join([INDEX_URL, "=".join([prev, marker])])
+ res = self._test_index_paginated(marker=marker, sort_dir="asc",
+ volumes=expected_volumes, url=url,
+ has_more=True, has_prev=True)
+ volumes = res.context['volumes_table'].data
+ self.assertItemsEqual(volumes, expected_volumes)
+
+ # back to first page
+ expected_volumes = mox_volumes[:size]
+ marker = expected_volumes[0].id
+ prev = volume_tables.VolumesTable._meta.prev_pagination_param
+ url = "?".join([INDEX_URL, "=".join([prev, marker])])
+ res = self._test_index_paginated(marker=marker, sort_dir="asc",
+ volumes=expected_volumes, url=url,
+ has_more=True, has_prev=False)
+ volumes = res.context['volumes_table'].data
+ self.assertItemsEqual(volumes, expected_volumes)
+
@test.create_stubs({cinder: ('volume_create',
'volume_snapshot_list',
'volume_type_list',
@@ -108,11 +266,11 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.mox.ReplayAll()
- url = reverse('horizon:project:volumes:volumes:create')
+ url = reverse('horizon:project:volumes:create')
res = self.client.post(url, formData)
self.assertNoFormErrors(res)
- redirect_url = VOLUME_VOLUMES_TAB_URL
+ redirect_url = INDEX_URL
self.assertRedirectsNoFollow(res, redirect_url)
@test.create_stubs({cinder: ('volume_create',
@@ -181,10 +339,10 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.mox.ReplayAll()
- url = reverse('horizon:project:volumes:volumes:create')
+ url = reverse('horizon:project:volumes:create')
res = self.client.post(url, formData)
- redirect_url = VOLUME_VOLUMES_TAB_URL
+ redirect_url = INDEX_URL
self.assertRedirectsNoFollow(res, redirect_url)
@test.create_stubs({cinder: ('volume_create',
@@ -251,10 +409,10 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.mox.ReplayAll()
- url = reverse('horizon:project:volumes:volumes:create')
+ url = reverse('horizon:project:volumes:create')
res = self.client.post(url, formData)
- redirect_url = VOLUME_VOLUMES_TAB_URL
+ redirect_url = INDEX_URL
self.assertRedirectsNoFollow(res, redirect_url)
@test.create_stubs({cinder: ('volume_create',
@@ -301,12 +459,12 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.mox.ReplayAll()
# get snapshot from url
- url = reverse('horizon:project:volumes:volumes:create')
+ url = reverse('horizon:project:volumes:create')
res = self.client.post("?".join([url,
"snapshot_id=" + str(snapshot.id)]),
formData)
- redirect_url = VOLUME_VOLUMES_TAB_URL
+ redirect_url = INDEX_URL
self.assertRedirectsNoFollow(res, redirect_url)
@test.create_stubs({cinder: ('volume_create',
@@ -375,8 +533,8 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
source_volid=volume.id).AndReturn(volume)
self.mox.ReplayAll()
- url = reverse('horizon:project:volumes:volumes:create')
- redirect_url = VOLUME_VOLUMES_TAB_URL
+ url = reverse('horizon:project:volumes:create')
+ redirect_url = INDEX_URL
res = self.client.post(url, formData)
self.assertNoFormErrors(res)
self.assertMessageCount(info=1)
@@ -451,10 +609,10 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.mox.ReplayAll()
# get snapshot from dropdown list
- url = reverse('horizon:project:volumes:volumes:create')
+ url = reverse('horizon:project:volumes:create')
res = self.client.post(url, formData)
- redirect_url = VOLUME_VOLUMES_TAB_URL
+ redirect_url = INDEX_URL
self.assertRedirectsNoFollow(res, redirect_url)
@test.create_stubs({cinder: ('volume_snapshot_get',
@@ -497,7 +655,7 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.mox.ReplayAll()
- url = reverse('horizon:project:volumes:volumes:create')
+ url = reverse('horizon:project:volumes:create')
res = self.client.post("?".join([url,
"snapshot_id=" + str(snapshot.id)]),
formData, follow=True)
@@ -555,12 +713,12 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.mox.ReplayAll()
# get image from url
- url = reverse('horizon:project:volumes:volumes:create')
+ url = reverse('horizon:project:volumes:create')
res = self.client.post("?".join([url,
"image_id=" + str(image.id)]),
formData)
- redirect_url = VOLUME_VOLUMES_TAB_URL
+ redirect_url = INDEX_URL
self.assertRedirectsNoFollow(res, redirect_url)
@test.create_stubs({cinder: ('volume_create',
@@ -632,10 +790,10 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.mox.ReplayAll()
# get image from dropdown list
- url = reverse('horizon:project:volumes:volumes:create')
+ url = reverse('horizon:project:volumes:create')
res = self.client.post(url, formData)
- redirect_url = VOLUME_VOLUMES_TAB_URL
+ redirect_url = INDEX_URL
self.assertRedirectsNoFollow(res, redirect_url)
@test.create_stubs({cinder: ('volume_type_list',
@@ -683,7 +841,7 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.mox.ReplayAll()
- url = reverse('horizon:project:volumes:volumes:create')
+ url = reverse('horizon:project:volumes:create')
res = self.client.post("?".join([url,
"image_id=" + str(image.id)]),
formData, follow=True)
@@ -738,7 +896,7 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.mox.ReplayAll()
- url = reverse('horizon:project:volumes:volumes:create')
+ url = reverse('horizon:project:volumes:create')
res = self.client.post("?".join([url,
"image_id=" + str(image.id)]),
formData, follow=True)
@@ -835,7 +993,7 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.mox.ReplayAll()
- url = reverse('horizon:project:volumes:volumes:create')
+ url = reverse('horizon:project:volumes:create')
res = self.client.post(url, formData)
expected_error = [u'A volume of 5000GiB cannot be created as you only'
@@ -914,7 +1072,7 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.mox.ReplayAll()
- url = reverse('horizon:project:volumes:volumes:create')
+ url = reverse('horizon:project:volumes:create')
res = self.client.post(url, formData)
expected_error = [u'You are already using all of your available'
@@ -942,8 +1100,7 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
search_opts=None).\
AndReturn([])
cinder.volume_delete(IsA(http.HttpRequest), volume.id)
- api.nova.server_list(IsA(http.HttpRequest), search_opts=None,
- detailed=False).\
+ api.nova.server_list(IsA(http.HttpRequest), search_opts=None).\
AndReturn([self.servers.list(), False])
cinder.volume_list_paged(
IsA(http.HttpRequest), marker=None, paginate=True, sort_dir='desc',
@@ -951,15 +1108,14 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
cinder.volume_snapshot_list(IsA(http.HttpRequest),
search_opts=None).\
AndReturn([])
- api.nova.server_list(IsA(http.HttpRequest), search_opts=None,
- detailed=False).\
+ api.nova.server_list(IsA(http.HttpRequest), search_opts=None).\
AndReturn([self.servers.list(), False])
cinder.tenant_absolute_limits(IsA(http.HttpRequest)).MultipleTimes().\
AndReturn(self.cinder_limits['absolute'])
self.mox.ReplayAll()
- url = VOLUME_INDEX_URL
+ url = INDEX_URL
res = self.client.post(url, formData, follow=True)
self.assertIn("Scheduled deletion of Volume: Volume name",
[m.message for m in res.context['messages']])
@@ -977,7 +1133,7 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.mox.ReplayAll()
- url = (VOLUME_INDEX_URL +
+ url = (INDEX_URL +
"?action=row_update&table=volumes&obj_id=" + volume.id)
res = self.client.get(url, {}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
@@ -1005,7 +1161,7 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
api.nova.server_list(IsA(http.HttpRequest)).AndReturn([servers, False])
self.mox.ReplayAll()
- url = reverse('horizon:project:volumes:volumes:attach',
+ url = reverse('horizon:project:volumes:attach',
args=[volume.id])
res = self.client.get(url)
msg = 'Volume %s on instance %s' % (volume.name, servers[0].name)
@@ -1038,7 +1194,7 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
api.nova.server_list(IsA(http.HttpRequest)).AndReturn([servers, False])
self.mox.ReplayAll()
- url = reverse('horizon:project:volumes:volumes:attach',
+ url = reverse('horizon:project:volumes:attach',
args=[volume.id])
res = self.client.get(url)
form = res.context['form']
@@ -1057,7 +1213,7 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
api.nova.server_list(IsA(http.HttpRequest)).AndReturn([servers, False])
self.mox.ReplayAll()
- url = reverse('horizon:project:volumes:volumes:attach',
+ url = reverse('horizon:project:volumes:attach',
args=[volume.id])
res = self.client.get(url)
# Assert the device field is hidden.
@@ -1080,7 +1236,7 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.mox.ReplayAll()
- url = reverse('horizon:project:volumes:volumes:attach',
+ url = reverse('horizon:project:volumes:attach',
args=[volume.id])
res = self.client.get(url)
@@ -1120,7 +1276,7 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
cinder.tenant_absolute_limits(IsA(http.HttpRequest)).AndReturn(limits)
self.mox.ReplayAll()
- res_url = (VOLUME_INDEX_URL +
+ res_url = (INDEX_URL +
"?action=row_update&table=volumes&obj_id=" + volume.id)
res = self.client.get(res_url, {},
@@ -1128,7 +1284,7 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
snapshot_action = self._get_volume_row_action_from_ajax(
res, 'snapshots', volume.id)
- self.assertEqual('horizon:project:volumes:volumes:create_snapshot',
+ self.assertEqual('horizon:project:volumes:create_snapshot',
snapshot_action.url)
self.assertEqual(set(['ajax-modal']), set(snapshot_action.classes))
self.assertEqual('Create Snapshot',
@@ -1147,7 +1303,7 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
cinder.tenant_absolute_limits(IsA(http.HttpRequest)).AndReturn(limits)
self.mox.ReplayAll()
- res_url = (VOLUME_INDEX_URL +
+ res_url = (INDEX_URL +
"?action=row_update&table=volumes&obj_id=" + volume.id)
res = self.client.get(res_url, {},
@@ -1177,15 +1333,14 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
cinder.volume_snapshot_list(IsA(http.HttpRequest),
search_opts=None).\
AndReturn([])
- api.nova.server_list(IsA(http.HttpRequest), search_opts=None,
- detailed=False)\
+ api.nova.server_list(IsA(http.HttpRequest), search_opts=None)\
.AndReturn([self.servers.list(), False])
cinder.tenant_absolute_limits(IsA(http.HttpRequest))\
.MultipleTimes().AndReturn(limits)
self.mox.ReplayAll()
- res = self.client.get(VOLUME_INDEX_URL)
- self.assertTemplateUsed(res, 'project/volumes/index.html')
+ res = self.client.get(INDEX_URL)
+ self.assertTemplateUsed(res, 'horizon/common/_data_table_view.html')
volumes = res.context['volumes_table'].data
self.assertItemsEqual(volumes, self.cinder_volumes.list())
@@ -1196,7 +1351,7 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
set(create_action.classes))
self.assertEqual('Create Volume',
six.text_type(create_action.verbose_name))
- self.assertEqual('horizon:project:volumes:volumes:create',
+ self.assertEqual('horizon:project:volumes:create',
create_action.url)
self.assertEqual((('volume', 'volume:create'),),
create_action.policy_rules)
@@ -1219,15 +1374,14 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
cinder.volume_snapshot_list(IsA(http.HttpRequest),
search_opts=None).\
AndReturn([])
- api.nova.server_list(IsA(http.HttpRequest), search_opts=None,
- detailed=False)\
+ api.nova.server_list(IsA(http.HttpRequest), search_opts=None)\
.AndReturn([self.servers.list(), False])
cinder.tenant_absolute_limits(IsA(http.HttpRequest))\
.MultipleTimes().AndReturn(limits)
self.mox.ReplayAll()
- res = self.client.get(VOLUME_INDEX_URL)
- self.assertTemplateUsed(res, 'project/volumes/index.html')
+ res = self.client.get(INDEX_URL)
+ self.assertTemplateUsed(res, 'horizon/common/_data_table_view.html')
volumes = res.context['volumes_table'].data
self.assertItemsEqual(volumes, self.cinder_volumes.list())
@@ -1257,7 +1411,7 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.mox.ReplayAll()
- url = reverse('horizon:project:volumes:volumes:detail',
+ url = reverse('horizon:project:volumes:detail',
args=[volume.id])
res = self.client.get(url)
@@ -1277,7 +1431,7 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.mox.ReplayAll()
- url = reverse('horizon:project:volumes:volumes:encryption_detail',
+ url = reverse('horizon:project:volumes:encryption_detail',
args=[volume.id])
res = self.client.get(url)
@@ -1305,7 +1459,7 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.mox.ReplayAll()
- url = reverse('horizon:project:volumes:volumes:encryption_detail',
+ url = reverse('horizon:project:volumes:encryption_detail',
args=[volume.id])
res = self.client.get(url)
@@ -1329,7 +1483,7 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.mox.ReplayAll()
- url = (VOLUME_INDEX_URL +
+ url = (INDEX_URL +
"?action=row_update&table=volumes&obj_id=" + volume.id)
res = self.client.get(url, {},
@@ -1350,11 +1504,11 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.mox.ReplayAll()
- url = reverse('horizon:project:volumes:volumes:detail',
+ url = reverse('horizon:project:volumes:detail',
args=[volume.id])
res = self.client.get(url)
- self.assertRedirectsNoFollow(res, VOLUME_INDEX_URL)
+ self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({cinder: ('volume_update',
'volume_set_bootable',
@@ -1378,10 +1532,10 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
'description': volume.description,
'bootable': False}
- url = reverse('horizon:project:volumes:volumes:update',
+ url = reverse('horizon:project:volumes:update',
args=[volume.id])
res = self.client.post(url, formData)
- self.assertRedirectsNoFollow(res, VOLUME_INDEX_URL)
+ self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({cinder: ('volume_update',
'volume_set_bootable',
@@ -1405,10 +1559,10 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
'description': volume.description,
'bootable': False}
- url = reverse('horizon:project:volumes:volumes:update',
+ url = reverse('horizon:project:volumes:update',
args=[volume.id])
res = self.client.post(url, formData)
- self.assertRedirectsNoFollow(res, VOLUME_INDEX_URL)
+ self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({cinder: ('volume_update',
'volume_set_bootable',
@@ -1432,10 +1586,10 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
'description': 'update bootable flag',
'bootable': True}
- url = reverse('horizon:project:volumes:volumes:update',
+ url = reverse('horizon:project:volumes:update',
args=[volume.id])
res = self.client.post(url, formData)
- self.assertRedirectsNoFollow(res, VOLUME_INDEX_URL)
+ self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({cinder: ('volume_upload_to_image',
'volume_get')})
@@ -1468,14 +1622,14 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.mox.ReplayAll()
- url = reverse('horizon:project:volumes:volumes:upload_to_image',
+ url = reverse('horizon:project:volumes:upload_to_image',
args=[volume.id])
res = self.client.post(url, form_data)
self.assertNoFormErrors(res)
self.assertMessageCount(info=1)
- redirect_url = VOLUME_INDEX_URL
+ redirect_url = INDEX_URL
self.assertRedirectsNoFollow(res, redirect_url)
@test.create_stubs({cinder: ('volume_get',
@@ -1501,11 +1655,11 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.mox.ReplayAll()
- url = reverse('horizon:project:volumes:volumes:extend',
+ url = reverse('horizon:project:volumes:extend',
args=[volume.id])
res = self.client.post(url, formData)
- redirect_url = VOLUME_INDEX_URL
+ redirect_url = INDEX_URL
self.assertRedirectsNoFollow(res, redirect_url)
@test.create_stubs({cinder: ('volume_get',),
@@ -1527,7 +1681,7 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.mox.ReplayAll()
- url = reverse('horizon:project:volumes:volumes:extend',
+ url = reverse('horizon:project:volumes:extend',
args=[volume.id])
res = self.client.post(url, formData)
self.assertFormErrors(res, 1,
@@ -1546,7 +1700,7 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.mox.ReplayAll()
- url = (VOLUME_INDEX_URL +
+ url = (INDEX_URL +
"?action=row_update&table=volumes&obj_id=" + volume.id)
res = self.client.get(url, {}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
@@ -1582,13 +1736,13 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.mox.ReplayAll()
- url = reverse('horizon:project:volumes:volumes:retype',
+ url = reverse('horizon:project:volumes:retype',
args=[volume.id])
res = self.client.post(url, form_data)
self.assertNoFormErrors(res)
- redirect_url = VOLUME_INDEX_URL
+ redirect_url = INDEX_URL
self.assertRedirectsNoFollow(res, redirect_url)
def test_encryption_false(self):
@@ -1618,15 +1772,14 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
cinder.volume_snapshot_list(IsA(http.HttpRequest),
search_opts=None).\
AndReturn(self.cinder_volume_snapshots.list())
- api.nova.server_list(IsA(http.HttpRequest), search_opts=None,
- detailed=False)\
+ api.nova.server_list(IsA(http.HttpRequest), search_opts=None)\
.AndReturn([self.servers.list(), False])
cinder.tenant_absolute_limits(IsA(http.HttpRequest))\
.MultipleTimes('limits').AndReturn(limits)
self.mox.ReplayAll()
- res = self.client.get(VOLUME_INDEX_URL)
+ res = self.client.get(INDEX_URL)
rows = res.context['volumes_table'].get_rows()
if encryption:
@@ -1656,7 +1809,7 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.mox.ReplayAll()
- url = reverse('horizon:project:volumes:volumes:extend',
+ url = reverse('horizon:project:volumes:extend',
args=[volume.id])
res = self.client.post(url, formData)
self.assertFormError(res, "form", "new_size",
@@ -1680,15 +1833,14 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
cinder.volume_snapshot_list(IsA(http.HttpRequest),
search_opts=None).\
AndReturn([])
- api.nova.server_list(IsA(http.HttpRequest), search_opts=None,
- detailed=False)\
+ api.nova.server_list(IsA(http.HttpRequest), search_opts=None)\
.AndReturn([self.servers.list(), False])
cinder.tenant_absolute_limits(IsA(http.HttpRequest))\
.MultipleTimes().AndReturn(limits)
self.mox.ReplayAll()
- res = self.client.get(VOLUME_INDEX_URL)
+ res = self.client.get(INDEX_URL)
table = res.context['volumes_table']
# Verify that the create transfer action is present if and only if
@@ -1713,7 +1865,7 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.mox.ReplayAll()
# Create a transfer for the first available volume
- url = reverse('horizon:project:volumes:volumes:create_transfer',
+ url = reverse('horizon:project:volumes:create_transfer',
args=[volToTransfer.id])
res = self.client.post(url, formData)
self.assertNoFormErrors(res)
@@ -1747,15 +1899,14 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
search_opts=None).\
AndReturn([])
cinder.transfer_delete(IsA(http.HttpRequest), transfer.id)
- api.nova.server_list(IsA(http.HttpRequest), search_opts=None,
- detailed=False).\
+ api.nova.server_list(IsA(http.HttpRequest), search_opts=None).\
AndReturn([self.servers.list(), False])
cinder.tenant_absolute_limits(IsA(http.HttpRequest)).MultipleTimes().\
AndReturn(self.cinder_limits['absolute'])
self.mox.ReplayAll()
- url = VOLUME_INDEX_URL
+ url = INDEX_URL
res = self.client.post(url, formData, follow=True)
self.assertNoFormErrors(res)
self.assertIn('Successfully deleted volume transfer "test transfer"',
@@ -1770,7 +1921,7 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.mox.ReplayAll()
formData = {'transfer_id': transfer.id, 'auth_key': transfer.auth_key}
- url = reverse('horizon:project:volumes:volumes:accept_transfer')
+ url = reverse('horizon:project:volumes:accept_transfer')
res = self.client.post(url, formData, follow=True)
self.assertNoFormErrors(res)
@@ -1786,7 +1937,7 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
filename = "{}.txt".format(slugify(transfer.id))
- url = reverse('horizon:project:volumes:volumes:'
+ url = reverse('horizon:project:volumes:'
'download_transfer_creds',
kwargs={'transfer_id': transfer.id,
'auth_key': transfer.auth_key})
diff --git a/openstack_dashboard/dashboards/project/volumes/urls.py b/openstack_dashboard/dashboards/project/volumes/urls.py
index ef6a3f9e23..c750d6aa69 100644
--- a/openstack_dashboard/dashboards/project/volumes/urls.py
+++ b/openstack_dashboard/dashboards/project/volumes/urls.py
@@ -12,18 +12,52 @@
# License for the specific language governing permissions and limitations
# under the License.
-from django.conf.urls import include
from django.conf.urls import url
+from openstack_dashboard.dashboards.project.backups \
+ import views as backup_views
from openstack_dashboard.dashboards.project.volumes import views
-from openstack_dashboard.dashboards.project.volumes.volumes \
- import urls as volume_urls
urlpatterns = [
- url(r'^$', views.IndexView.as_view(), name='index'),
- url(r'^\?tab=volumes_and_snapshots__volumes_tab$',
- views.IndexView.as_view(), name='volumes_tab'),
- url(r'', include(
- volume_urls,
- namespace='volumes')),
+ url(r'^$', views.VolumesView.as_view(), name='index'),
+ url(r'^create/$', views.CreateView.as_view(), name='create'),
+ url(r'^(?P[^/]+)/extend/$',
+ views.ExtendView.as_view(),
+ name='extend'),
+ url(r'^(?P[^/]+)/attach/$',
+ views.EditAttachmentsView.as_view(),
+ name='attach'),
+ url(r'^(?P[^/]+)/create_snapshot/$',
+ views.CreateSnapshotView.as_view(),
+ name='create_snapshot'),
+ url(r'^(?P[^/]+)/create_transfer/$',
+ views.CreateTransferView.as_view(),
+ name='create_transfer'),
+ url(r'^accept_transfer/$',
+ views.AcceptTransferView.as_view(),
+ name='accept_transfer'),
+ url(r'^(?P[^/]+)/auth/(?P[^/]+)/$',
+ views.ShowTransferView.as_view(),
+ name='show_transfer'),
+ url(r'^(?P[^/]+)/create_backup/$',
+ backup_views.CreateBackupView.as_view(),
+ name='create_backup'),
+ url(r'^(?P[^/]+)/$',
+ views.DetailView.as_view(),
+ name='detail'),
+ url(r'^(?P[^/]+)/upload_to_image/$',
+ views.UploadToImageView.as_view(),
+ name='upload_to_image'),
+ url(r'^(?P[^/]+)/update/$',
+ views.UpdateView.as_view(),
+ name='update'),
+ url(r'^(?P[^/]+)/retype/$',
+ views.RetypeView.as_view(),
+ name='retype'),
+ url(r'^(?P[^/]+)/encryption_detail/$',
+ views.EncryptionDetailView.as_view(),
+ name='encryption_detail'),
+ url(r'^(?P[^/]+)/download_creds/(?P[^/]+)$',
+ views.DownloadTransferCreds.as_view(),
+ name='download_transfer_creds'),
]
diff --git a/openstack_dashboard/dashboards/project/volumes/views.py b/openstack_dashboard/dashboards/project/volumes/views.py
index 81e9fa732a..777ba29da3 100644
--- a/openstack_dashboard/dashboards/project/volumes/views.py
+++ b/openstack_dashboard/dashboards/project/volumes/views.py
@@ -12,15 +12,626 @@
# License for the specific language governing permissions and limitations
# under the License.
+"""
+Views for managing volumes.
+"""
+
+from collections import OrderedDict
+import json
+
+from django.core.urlresolvers import reverse
+from django.core.urlresolvers import reverse_lazy
+from django import http
+from django.template.defaultfilters import slugify # noqa
+from django.utils.decorators import method_decorator
+from django.utils import encoding
from django.utils.translation import ugettext_lazy as _
+from django.views.decorators.cache import cache_control
+from django.views.decorators.cache import never_cache
+from django.views import generic
+from horizon import exceptions
+from horizon import forms
+from horizon import tables
from horizon import tabs
+from horizon.utils import memoized
+from openstack_dashboard.api import cinder
+from openstack_dashboard.api import nova
+from openstack_dashboard import exceptions as dashboard_exception
+from openstack_dashboard.usage import quotas
+from openstack_dashboard.utils import filters
+
+from openstack_dashboard.dashboards.project.volumes \
+ import forms as volume_forms
+from openstack_dashboard.dashboards.project.volumes \
+ import tables as volume_tables
from openstack_dashboard.dashboards.project.volumes \
import tabs as project_tabs
-class IndexView(tabs.TabbedTableView):
- tab_group_class = project_tabs.VolumeAndSnapshotTabs
- template_name = 'project/volumes/index.html'
+class VolumeTableMixIn(object):
+ _has_more_data = False
+ _has_prev_data = False
+
+ def _get_volumes(self, search_opts=None):
+ try:
+ marker, sort_dir = self._get_marker()
+ volumes, self._has_more_data, self._has_prev_data = \
+ cinder.volume_list_paged(self.request, marker=marker,
+ search_opts=search_opts,
+ sort_dir=sort_dir, paginate=True)
+
+ if sort_dir == "asc":
+ volumes.reverse()
+
+ return volumes
+ except Exception:
+ exceptions.handle(self.request,
+ _('Unable to retrieve volume list.'))
+ return []
+
+ def _get_instances(self, search_opts=None, instance_ids=None):
+ if not instance_ids:
+ return []
+ try:
+ # TODO(tsufiev): we should pass attached_instance_ids to
+ # nova.server_list as soon as Nova API allows for this
+ instances, has_more = nova.server_list(self.request,
+ search_opts=search_opts)
+ return instances
+ except Exception:
+ exceptions.handle(self.request,
+ _("Unable to retrieve volume/instance "
+ "attachment information"))
+ return []
+
+ def _get_volumes_ids_with_snapshots(self, search_opts=None):
+ try:
+ volume_ids = []
+ snapshots = cinder.volume_snapshot_list(
+ self.request, search_opts=search_opts)
+ if snapshots:
+ # extract out the volume ids
+ volume_ids = set([(s.volume_id) for s in snapshots])
+ except Exception:
+ exceptions.handle(self.request,
+ _("Unable to retrieve snapshot list."))
+
+ return volume_ids
+
+ def _get_attached_instance_ids(self, volumes):
+ attached_instance_ids = []
+ for volume in volumes:
+ for att in volume.attachments:
+ server_id = att.get('server_id', None)
+ if server_id is not None:
+ attached_instance_ids.append(server_id)
+ return attached_instance_ids
+
+ # set attachment string and if volume has snapshots
+ def _set_volume_attributes(self,
+ volumes,
+ instances,
+ volume_ids_with_snapshots):
+ instances = OrderedDict([(inst.id, inst) for inst in instances])
+ for volume in volumes:
+ if volume_ids_with_snapshots:
+ if volume.id in volume_ids_with_snapshots:
+ setattr(volume, 'has_snapshot', True)
+ if instances:
+ for att in volume.attachments:
+ server_id = att.get('server_id', None)
+ att['instance'] = instances.get(server_id, None)
+
+
+class VolumesView(tables.PagedTableMixin, VolumeTableMixIn,
+ tables.DataTableView):
+ table_class = volume_tables.VolumesTable
page_title = _("Volumes")
+
+ def get_data(self):
+ volumes = self._get_volumes()
+ attached_instance_ids = self._get_attached_instance_ids(volumes)
+ instances = self._get_instances(instance_ids=attached_instance_ids)
+ volume_ids_with_snapshots = self._get_volumes_ids_with_snapshots()
+ self._set_volume_attributes(
+ volumes, instances, volume_ids_with_snapshots)
+ return volumes
+
+
+class DetailView(tabs.TabView):
+ tab_group_class = project_tabs.VolumeDetailTabs
+ template_name = 'horizon/common/_detail.html'
+ page_title = "{{ volume.name|default:volume.id }}"
+
+ def get_context_data(self, **kwargs):
+ context = super(DetailView, self).get_context_data(**kwargs)
+ volume = self.get_data()
+ table = volume_tables.VolumesTable(self.request)
+ context["volume"] = volume
+ context["url"] = self.get_redirect_url()
+ context["actions"] = table.render_row_actions(volume)
+ choices = volume_tables.VolumesTableBase.STATUS_DISPLAY_CHOICES
+ volume.status_label = filters.get_display_label(choices, volume.status)
+ return context
+
+ @memoized.memoized_method
+ def get_data(self):
+ try:
+ volume_id = self.kwargs['volume_id']
+ volume = cinder.volume_get(self.request, volume_id)
+ snapshots = cinder.volume_snapshot_list(
+ self.request, search_opts={'volume_id': volume.id})
+ if snapshots:
+ setattr(volume, 'has_snapshot', True)
+ for att in volume.attachments:
+ att['instance'] = nova.server_get(self.request,
+ att['server_id'])
+ except Exception:
+ redirect = self.get_redirect_url()
+ exceptions.handle(self.request,
+ _('Unable to retrieve volume details.'),
+ redirect=redirect)
+ return volume
+
+ def get_redirect_url(self):
+ return reverse('horizon:project:volumes:index')
+
+ def get_tabs(self, request, *args, **kwargs):
+ volume = self.get_data()
+ return self.tab_group_class(request, volume=volume, **kwargs)
+
+
+class CreateView(forms.ModalFormView):
+ form_class = volume_forms.CreateForm
+ template_name = 'project/volumes/create.html'
+ submit_label = _("Create Volume")
+ submit_url = reverse_lazy("horizon:project:volumes:create")
+ success_url = reverse_lazy('horizon:project:volumes:index')
+ page_title = _("Create Volume")
+
+ def get_initial(self):
+ initial = super(CreateView, self).get_initial()
+ self.default_vol_type = None
+ try:
+ self.default_vol_type = cinder.volume_type_default(self.request)
+ initial['type'] = self.default_vol_type.name
+ except dashboard_exception.NOT_FOUND:
+ pass
+ return initial
+
+ def get_context_data(self, **kwargs):
+ context = super(CreateView, self).get_context_data(**kwargs)
+ try:
+ context['usages'] = quotas.tenant_limit_usages(self.request)
+ context['volume_types'] = self._get_volume_types()
+ except Exception:
+ exceptions.handle(self.request)
+ return context
+
+ def _get_volume_types(self):
+ volume_types = []
+ try:
+ volume_types = cinder.volume_type_list(self.request)
+ except Exception:
+ exceptions.handle(self.request,
+ _('Unable to retrieve volume type list.'))
+
+ # check if we have default volume type so we can present the
+ # description of no volume type differently
+ no_type_description = None
+ if self.default_vol_type is None:
+ message = \
+ _("If \"No volume type\" is selected, the volume will be "
+ "created without a volume type.")
+
+ no_type_description = encoding.force_text(message)
+
+ type_descriptions = [{'name': '',
+ 'description': no_type_description}] + \
+ [{'name': type.name,
+ 'description': getattr(type, "description", "")}
+ for type in volume_types]
+
+ return json.dumps(type_descriptions)
+
+
+class ExtendView(forms.ModalFormView):
+ form_class = volume_forms.ExtendForm
+ template_name = 'project/volumes/extend.html'
+ submit_label = _("Extend Volume")
+ submit_url = "horizon:project:volumes:extend"
+ success_url = reverse_lazy("horizon:project:volumes:index")
+ page_title = _("Extend Volume")
+
+ def get_object(self):
+ if not hasattr(self, "_object"):
+ volume_id = self.kwargs['volume_id']
+ try:
+ self._object = cinder.volume_get(self.request, volume_id)
+ except Exception:
+ self._object = None
+ exceptions.handle(self.request,
+ _('Unable to retrieve volume information.'))
+ return self._object
+
+ def get_context_data(self, **kwargs):
+ context = super(ExtendView, self).get_context_data(**kwargs)
+ context['volume'] = self.get_object()
+ args = (self.kwargs['volume_id'],)
+ context['submit_url'] = reverse(self.submit_url, args=args)
+ try:
+ usages = quotas.tenant_limit_usages(self.request)
+ usages['gigabytesUsed'] = (usages['gigabytesUsed']
+ - context['volume'].size)
+ context['usages'] = usages
+ except Exception:
+ exceptions.handle(self.request)
+ return context
+
+ def get_initial(self):
+ volume = self.get_object()
+ return {'id': self.kwargs['volume_id'],
+ 'name': volume.name,
+ 'orig_size': volume.size}
+
+
+class CreateSnapshotView(forms.ModalFormView):
+ form_class = volume_forms.CreateSnapshotForm
+ template_name = 'project/volumes/create_snapshot.html'
+ submit_url = "horizon:project:volumes:create_snapshot"
+ success_url = reverse_lazy('horizon:project:snapshots:index')
+ page_title = _("Create Volume Snapshot")
+
+ def get_context_data(self, **kwargs):
+ context = super(CreateSnapshotView, self).get_context_data(**kwargs)
+ context['volume_id'] = self.kwargs['volume_id']
+ args = (self.kwargs['volume_id'],)
+ context['submit_url'] = reverse(self.submit_url, args=args)
+ try:
+ volume = cinder.volume_get(self.request, context['volume_id'])
+ if (volume.status == 'in-use'):
+ context['attached'] = True
+ context['form'].set_warning(_("This volume is currently "
+ "attached to an instance. "
+ "In some cases, creating a "
+ "snapshot from an attached "
+ "volume can result in a "
+ "corrupted snapshot."))
+ context['usages'] = quotas.tenant_limit_usages(self.request)
+ except Exception:
+ exceptions.handle(self.request,
+ _('Unable to retrieve volume information.'))
+ return context
+
+ def get_initial(self):
+ return {'volume_id': self.kwargs["volume_id"]}
+
+
+class UploadToImageView(forms.ModalFormView):
+ form_class = volume_forms.UploadToImageForm
+ template_name = 'project/volumes/upload_to_image.html'
+ submit_label = _("Upload")
+ submit_url = "horizon:project:volumes:upload_to_image"
+ success_url = reverse_lazy("horizon:project:volumes:index")
+ page_title = _("Upload Volume to Image")
+
+ @memoized.memoized_method
+ def get_data(self):
+ try:
+ volume_id = self.kwargs['volume_id']
+ volume = cinder.volume_get(self.request, volume_id)
+ except Exception:
+ error_message = _(
+ 'Unable to retrieve volume information for volume: "%s"') \
+ % volume_id
+ exceptions.handle(self.request,
+ error_message,
+ redirect=self.success_url)
+
+ return volume
+
+ def get_context_data(self, **kwargs):
+ context = super(UploadToImageView, self).get_context_data(**kwargs)
+ context['volume'] = self.get_data()
+ args = (self.kwargs['volume_id'],)
+ context['submit_url'] = reverse(self.submit_url, args=args)
+ return context
+
+ def get_initial(self):
+ volume = self.get_data()
+
+ return {'id': self.kwargs['volume_id'],
+ 'name': volume.name,
+ 'status': volume.status}
+
+
+class CreateTransferView(forms.ModalFormView):
+ form_class = volume_forms.CreateTransferForm
+ template_name = 'project/volumes/create_transfer.html'
+ success_url = reverse_lazy('horizon:project:volumes:index')
+ modal_id = "create_volume_transfer_modal"
+ submit_label = _("Create Volume Transfer")
+ submit_url = "horizon:project:volumes:create_transfer"
+ page_title = _("Create Volume Transfer")
+
+ def get_context_data(self, *args, **kwargs):
+ context = super(CreateTransferView, self).get_context_data(**kwargs)
+ volume_id = self.kwargs['volume_id']
+ context['volume_id'] = volume_id
+ context['submit_url'] = reverse(self.submit_url, args=[volume_id])
+ return context
+
+ def get_initial(self):
+ return {'volume_id': self.kwargs["volume_id"]}
+
+
+class AcceptTransferView(forms.ModalFormView):
+ form_class = volume_forms.AcceptTransferForm
+ template_name = 'project/volumes/accept_transfer.html'
+ success_url = reverse_lazy('horizon:project:volumes:index')
+ modal_id = "accept_volume_transfer_modal"
+ submit_label = _("Accept Volume Transfer")
+ submit_url = reverse_lazy(
+ "horizon:project:volumes:accept_transfer")
+ page_title = _("Accept Volume Transfer")
+
+
+class ShowTransferView(forms.ModalFormView):
+ form_class = volume_forms.ShowTransferForm
+ template_name = 'project/volumes/show_transfer.html'
+ success_url = reverse_lazy('horizon:project:volumes:index')
+ modal_id = "show_volume_transfer_modal"
+ submit_url = "horizon:project:volumes:show_transfer"
+ cancel_label = _("Close")
+ download_label = _("Download transfer credentials")
+ page_title = _("Volume Transfer Details")
+
+ def get_object(self):
+ try:
+ return self._object
+ except AttributeError:
+ transfer_id = self.kwargs['transfer_id']
+ try:
+ self._object = cinder.transfer_get(self.request, transfer_id)
+ return self._object
+ except Exception:
+ exceptions.handle(self.request,
+ _('Unable to retrieve volume transfer.'))
+
+ def get_context_data(self, **kwargs):
+ context = super(ShowTransferView, self).get_context_data(**kwargs)
+ context['transfer_id'] = self.kwargs['transfer_id']
+ context['auth_key'] = self.kwargs['auth_key']
+ context['submit_url'] = reverse(self.submit_url, args=[
+ context['transfer_id'], context['auth_key']])
+ context['download_label'] = self.download_label
+ context['download_url'] = reverse(
+ 'horizon:project:volumes:download_transfer_creds',
+ args=[context['transfer_id'], context['auth_key']]
+ )
+ return context
+
+ def get_initial(self):
+ transfer = self.get_object()
+ return {'id': transfer.id,
+ 'name': transfer.name,
+ 'auth_key': self.kwargs['auth_key']}
+
+
+class UpdateView(forms.ModalFormView):
+ form_class = volume_forms.UpdateForm
+ modal_id = "update_volume_modal"
+ template_name = 'project/volumes/update.html'
+ submit_url = "horizon:project:volumes:update"
+ success_url = reverse_lazy("horizon:project:volumes:index")
+ page_title = _("Edit Volume")
+
+ def get_object(self):
+ if not hasattr(self, "_object"):
+ vol_id = self.kwargs['volume_id']
+ try:
+ self._object = cinder.volume_get(self.request, vol_id)
+ except Exception:
+ msg = _('Unable to retrieve volume.')
+ url = reverse('horizon:project:volumes:index')
+ exceptions.handle(self.request, msg, redirect=url)
+ return self._object
+
+ def get_context_data(self, **kwargs):
+ context = super(UpdateView, self).get_context_data(**kwargs)
+ context['volume'] = self.get_object()
+ args = (self.kwargs['volume_id'],)
+ context['submit_url'] = reverse(self.submit_url, args=args)
+ return context
+
+ def get_initial(self):
+ volume = self.get_object()
+ return {'volume_id': self.kwargs["volume_id"],
+ 'name': volume.name,
+ 'description': volume.description,
+ 'bootable': volume.is_bootable}
+
+
+class EditAttachmentsView(tables.DataTableView, forms.ModalFormView):
+ table_class = volume_tables.AttachmentsTable
+ form_class = volume_forms.AttachForm
+ form_id = "attach_volume_form"
+ modal_id = "attach_volume_modal"
+ template_name = 'project/volumes/attach.html'
+ submit_url = "horizon:project:volumes:attach"
+ success_url = reverse_lazy("horizon:project:volumes:index")
+ page_title = _("Manage Volume Attachments")
+
+ @memoized.memoized_method
+ def get_object(self):
+ volume_id = self.kwargs['volume_id']
+ try:
+ return cinder.volume_get(self.request, volume_id)
+ except Exception:
+ self._object = None
+ exceptions.handle(self.request,
+ _('Unable to retrieve volume information.'))
+
+ def get_data(self):
+ attachments = []
+ volume = self.get_object()
+ if volume is not None:
+ for att in volume.attachments:
+ att['volume_name'] = getattr(volume, 'name', att['device'])
+ attachments.append(att)
+ return attachments
+
+ def get_initial(self):
+ try:
+ instances, has_more = nova.server_list(self.request)
+ except Exception:
+ instances = []
+ exceptions.handle(self.request,
+ _("Unable to retrieve attachment information."))
+ return {'volume': self.get_object(),
+ 'instances': instances}
+
+ @memoized.memoized_method
+ def get_form(self, **kwargs):
+ form_class = kwargs.get('form_class', self.get_form_class())
+ return super(EditAttachmentsView, self).get_form(form_class)
+
+ def get_context_data(self, **kwargs):
+ context = super(EditAttachmentsView, self).get_context_data(**kwargs)
+ context['form'] = self.get_form()
+ volume = self.get_object()
+ args = (self.kwargs['volume_id'],)
+ context['submit_url'] = reverse(self.submit_url, args=args)
+ if volume and volume.status == 'available':
+ context['show_attach'] = True
+ else:
+ context['show_attach'] = False
+ context['volume'] = volume
+ if self.request.is_ajax():
+ context['hide'] = True
+ return context
+
+ def get(self, request, *args, **kwargs):
+ # Table action handling
+ handled = self.construct_tables()
+ if handled:
+ return handled
+ return self.render_to_response(self.get_context_data(**kwargs))
+
+ def post(self, request, *args, **kwargs):
+ form = self.get_form()
+ if form.is_valid():
+ return self.form_valid(form)
+ else:
+ return self.get(request, *args, **kwargs)
+
+
+class RetypeView(forms.ModalFormView):
+ form_class = volume_forms.RetypeForm
+ modal_id = "retype_volume_modal"
+ template_name = 'project/volumes/retype.html'
+ submit_label = _("Change Volume Type")
+ submit_url = "horizon:project:volumes:retype"
+ success_url = reverse_lazy("horizon:project:volumes:index")
+ page_title = _("Change Volume Type")
+
+ @memoized.memoized_method
+ def get_data(self):
+ try:
+ volume_id = self.kwargs['volume_id']
+ volume = cinder.volume_get(self.request, volume_id)
+ except Exception:
+ error_message = _(
+ 'Unable to retrieve volume information for volume: "%s"') \
+ % volume_id
+ exceptions.handle(self.request,
+ error_message,
+ redirect=self.success_url)
+
+ return volume
+
+ def get_context_data(self, **kwargs):
+ context = super(RetypeView, self).get_context_data(**kwargs)
+ context['volume'] = self.get_data()
+ args = (self.kwargs['volume_id'],)
+ context['submit_url'] = reverse(self.submit_url, args=args)
+ return context
+
+ def get_initial(self):
+ volume = self.get_data()
+
+ return {'id': self.kwargs['volume_id'],
+ 'name': volume.name,
+ 'volume_type': volume.volume_type}
+
+
+class EncryptionDetailView(generic.TemplateView):
+ template_name = 'project/volumes/encryption_detail.html'
+ page_title = _("Volume Encryption Details: {{ volume.name }}")
+
+ def get_context_data(self, **kwargs):
+ context = super(EncryptionDetailView, self).get_context_data(**kwargs)
+ volume = self.get_volume_data()
+ context["encryption_metadata"] = self.get_encryption_data()
+ context["volume"] = volume
+ context["page_title"] = _("Volume Encryption Details: "
+ "%(volume_name)s") % {'volume_name':
+ volume.name}
+ return context
+
+ @memoized.memoized_method
+ def get_encryption_data(self):
+ try:
+ volume_id = self.kwargs['volume_id']
+ self._encryption_metadata = \
+ cinder.volume_get_encryption_metadata(self.request,
+ volume_id)
+ except Exception:
+ redirect = self.get_redirect_url()
+ exceptions.handle(self.request,
+ _('Unable to retrieve volume encryption '
+ 'details.'),
+ redirect=redirect)
+ return self._encryption_metadata
+
+ @memoized.memoized_method
+ def get_volume_data(self):
+ try:
+ volume_id = self.kwargs['volume_id']
+ volume = cinder.volume_get(self.request, volume_id)
+ except Exception:
+ redirect = self.get_redirect_url()
+ exceptions.handle(self.request,
+ _('Unable to retrieve volume details.'),
+ redirect=redirect)
+ return volume
+
+ def get_redirect_url(self):
+ return reverse('horizon:project:volumes:index')
+
+
+class DownloadTransferCreds(generic.View):
+ # TODO(Itxaka): Remove cache_control in django >= 1.9
+ # https://code.djangoproject.com/ticket/13008
+ @method_decorator(cache_control(max_age=0, no_cache=True,
+ no_store=True, must_revalidate=True))
+ @method_decorator(never_cache)
+ def get(self, request, transfer_id, auth_key):
+ try:
+ transfer = cinder.transfer_get(self.request, transfer_id)
+ except Exception:
+ transfer = None
+ response = http.HttpResponse(content_type='application/text')
+ response['Content-Disposition'] = \
+ 'attachment; filename=%s.txt' % slugify(transfer_id)
+ response.write('%s: %s\n%s: %s\n%s: %s' % (
+ _("Transfer name"),
+ getattr(transfer, 'name', ''),
+ _("Transfer ID"),
+ transfer_id,
+ _("Authorization Key"),
+ auth_key))
+ response['Content-Length'] = str(len(response.content))
+ return response
diff --git a/openstack_dashboard/dashboards/project/volumes/volumes/__init__.py b/openstack_dashboard/dashboards/project/volumes/volumes/__init__.py
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/openstack_dashboard/dashboards/project/volumes/volumes/tabs.py b/openstack_dashboard/dashboards/project/volumes/volumes/tabs.py
deleted file mode 100644
index 919212e1ec..0000000000
--- a/openstack_dashboard/dashboards/project/volumes/volumes/tabs.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# 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 tabs
-
-
-class OverviewTab(tabs.Tab):
- name = _("Overview")
- slug = "overview"
- template_name = ("project/volumes/volumes/_detail_overview.html")
-
- def get_context_data(self, request):
- return {"volume": self.tab_group.kwargs['volume']}
-
-
-class VolumeDetailTabs(tabs.TabGroup):
- slug = "volume_details"
- tabs = (OverviewTab,)
diff --git a/openstack_dashboard/dashboards/project/volumes/volumes/urls.py b/openstack_dashboard/dashboards/project/volumes/volumes/urls.py
deleted file mode 100644
index 30b96c476b..0000000000
--- a/openstack_dashboard/dashboards/project/volumes/volumes/urls.py
+++ /dev/null
@@ -1,64 +0,0 @@
-# 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.conf.urls import url
-
-from openstack_dashboard.dashboards.project.volumes \
- .volumes import views
-from openstack_dashboard.dashboards.project.backups \
- import views as backup_views
-
-
-urlpatterns = [
- url(r'^create/$', views.CreateView.as_view(), name='create'),
- url(r'^(?P[^/]+)/extend/$',
- views.ExtendView.as_view(),
- name='extend'),
- url(r'^(?P[^/]+)/attach/$',
- views.EditAttachmentsView.as_view(),
- name='attach'),
- url(r'^(?P[^/]+)/create_snapshot/$',
- views.CreateSnapshotView.as_view(),
- name='create_snapshot'),
- url(r'^(?P[^/]+)/create_transfer/$',
- views.CreateTransferView.as_view(),
- name='create_transfer'),
- url(r'^accept_transfer/$',
- views.AcceptTransferView.as_view(),
- name='accept_transfer'),
- url(r'^(?P[^/]+)/auth/(?P[^/]+)/$',
- views.ShowTransferView.as_view(),
- name='show_transfer'),
- url(r'^(?P[^/]+)/create_backup/$',
- backup_views.CreateBackupView.as_view(),
- name='create_backup'),
- url(r'^(?P[^/]+)/$',
- views.DetailView.as_view(),
- name='detail'),
- url(r'^(?P[^/]+)/upload_to_image/$',
- views.UploadToImageView.as_view(),
- name='upload_to_image'),
- url(r'^(?P[^/]+)/update/$',
- views.UpdateView.as_view(),
- name='update'),
- url(r'^(?P[^/]+)/retype/$',
- views.RetypeView.as_view(),
- name='retype'),
- url(r'^(?P[^/]+)/encryption_detail/$',
- views.EncryptionDetailView.as_view(),
- name='encryption_detail'),
- url(r'^(?P[^/]+)/download_creds/(?P[^/]+)$',
- views.DownloadTransferCreds.as_view(),
- name='download_transfer_creds'),
-]
diff --git a/openstack_dashboard/dashboards/project/volumes/volumes/views.py b/openstack_dashboard/dashboards/project/volumes/volumes/views.py
deleted file mode 100644
index 761f94fb47..0000000000
--- a/openstack_dashboard/dashboards/project/volumes/volumes/views.py
+++ /dev/null
@@ -1,547 +0,0 @@
-# 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.
-
-"""
-Views for managing volumes.
-"""
-
-import json
-
-from django.core.urlresolvers import reverse
-from django.core.urlresolvers import reverse_lazy
-from django import http
-from django.template.defaultfilters import slugify # noqa
-from django.utils.decorators import method_decorator
-from django.utils import encoding
-from django.utils.translation import ugettext_lazy as _
-from django.views.decorators.cache import cache_control
-from django.views.decorators.cache import never_cache
-from django.views import generic
-
-from horizon import exceptions
-from horizon import forms
-from horizon import tables
-from horizon import tabs
-from horizon.utils import memoized
-
-from openstack_dashboard import api
-from openstack_dashboard.api import cinder
-from openstack_dashboard import exceptions as dashboard_exception
-from openstack_dashboard.usage import quotas
-from openstack_dashboard.utils import filters
-
-from openstack_dashboard.dashboards.project.volumes \
- .volumes import forms as project_forms
-
-from openstack_dashboard.dashboards.project.volumes \
- .volumes import tables as project_tables
-from openstack_dashboard.dashboards.project.volumes \
- .volumes import tabs as project_tabs
-
-
-class DetailView(tabs.TabView):
- tab_group_class = project_tabs.VolumeDetailTabs
- template_name = 'horizon/common/_detail.html'
- page_title = "{{ volume.name|default:volume.id }}"
-
- def get_context_data(self, **kwargs):
- context = super(DetailView, self).get_context_data(**kwargs)
- volume = self.get_data()
- table = project_tables.VolumesTable(self.request)
- context["volume"] = volume
- context["url"] = self.get_redirect_url()
- context["actions"] = table.render_row_actions(volume)
- choices = project_tables.VolumesTableBase.STATUS_DISPLAY_CHOICES
- volume.status_label = filters.get_display_label(choices, volume.status)
- return context
-
- @memoized.memoized_method
- def get_data(self):
- try:
- volume_id = self.kwargs['volume_id']
- volume = cinder.volume_get(self.request, volume_id)
- snapshots = cinder.volume_snapshot_list(
- self.request, search_opts={'volume_id': volume.id})
- if snapshots:
- setattr(volume, 'has_snapshot', True)
- for att in volume.attachments:
- att['instance'] = api.nova.server_get(self.request,
- att['server_id'])
- except Exception:
- redirect = self.get_redirect_url()
- exceptions.handle(self.request,
- _('Unable to retrieve volume details.'),
- redirect=redirect)
- return volume
-
- def get_redirect_url(self):
- return reverse('horizon:project:volumes:index')
-
- def get_tabs(self, request, *args, **kwargs):
- volume = self.get_data()
- return self.tab_group_class(request, volume=volume, **kwargs)
-
-
-class CreateView(forms.ModalFormView):
- form_class = project_forms.CreateForm
- template_name = 'project/volumes/volumes/create.html'
- submit_label = _("Create Volume")
- submit_url = reverse_lazy("horizon:project:volumes:volumes:create")
- success_url = reverse_lazy('horizon:project:volumes:volumes_tab')
- page_title = _("Create Volume")
-
- def get_initial(self):
- initial = super(CreateView, self).get_initial()
- self.default_vol_type = None
- try:
- self.default_vol_type = cinder.volume_type_default(self.request)
- initial['type'] = self.default_vol_type.name
- except dashboard_exception.NOT_FOUND:
- pass
- return initial
-
- def get_context_data(self, **kwargs):
- context = super(CreateView, self).get_context_data(**kwargs)
- try:
- context['usages'] = quotas.tenant_limit_usages(self.request)
- context['volume_types'] = self._get_volume_types()
- except Exception:
- exceptions.handle(self.request)
- return context
-
- def _get_volume_types(self):
- volume_types = []
- try:
- volume_types = cinder.volume_type_list(self.request)
- except Exception:
- exceptions.handle(self.request,
- _('Unable to retrieve volume type list.'))
-
- # check if we have default volume type so we can present the
- # description of no volume type differently
- no_type_description = None
- if self.default_vol_type is None:
- message = \
- _("If \"No volume type\" is selected, the volume will be "
- "created without a volume type.")
-
- no_type_description = encoding.force_text(message)
-
- type_descriptions = [{'name': '',
- 'description': no_type_description}] + \
- [{'name': type.name,
- 'description': getattr(type, "description", "")}
- for type in volume_types]
-
- return json.dumps(type_descriptions)
-
-
-class ExtendView(forms.ModalFormView):
- form_class = project_forms.ExtendForm
- template_name = 'project/volumes/volumes/extend.html'
- submit_label = _("Extend Volume")
- submit_url = "horizon:project:volumes:volumes:extend"
- success_url = reverse_lazy("horizon:project:volumes:index")
- page_title = _("Extend Volume")
-
- def get_object(self):
- if not hasattr(self, "_object"):
- volume_id = self.kwargs['volume_id']
- try:
- self._object = cinder.volume_get(self.request, volume_id)
- except Exception:
- self._object = None
- exceptions.handle(self.request,
- _('Unable to retrieve volume information.'))
- return self._object
-
- def get_context_data(self, **kwargs):
- context = super(ExtendView, self).get_context_data(**kwargs)
- context['volume'] = self.get_object()
- args = (self.kwargs['volume_id'],)
- context['submit_url'] = reverse(self.submit_url, args=args)
- try:
- usages = quotas.tenant_limit_usages(self.request)
- usages['gigabytesUsed'] = (usages['gigabytesUsed']
- - context['volume'].size)
- context['usages'] = usages
- except Exception:
- exceptions.handle(self.request)
- return context
-
- def get_initial(self):
- volume = self.get_object()
- return {'id': self.kwargs['volume_id'],
- 'name': volume.name,
- 'orig_size': volume.size}
-
-
-class CreateSnapshotView(forms.ModalFormView):
- form_class = project_forms.CreateSnapshotForm
- template_name = 'project/volumes/volumes/create_snapshot.html'
- submit_url = "horizon:project:volumes:volumes:create_snapshot"
- success_url = reverse_lazy('horizon:project:snapshots:index')
- page_title = _("Create Volume Snapshot")
-
- def get_context_data(self, **kwargs):
- context = super(CreateSnapshotView, self).get_context_data(**kwargs)
- context['volume_id'] = self.kwargs['volume_id']
- args = (self.kwargs['volume_id'],)
- context['submit_url'] = reverse(self.submit_url, args=args)
- try:
- volume = cinder.volume_get(self.request, context['volume_id'])
- if (volume.status == 'in-use'):
- context['attached'] = True
- context['form'].set_warning(_("This volume is currently "
- "attached to an instance. "
- "In some cases, creating a "
- "snapshot from an attached "
- "volume can result in a "
- "corrupted snapshot."))
- context['usages'] = quotas.tenant_limit_usages(self.request)
- except Exception:
- exceptions.handle(self.request,
- _('Unable to retrieve volume information.'))
- return context
-
- def get_initial(self):
- return {'volume_id': self.kwargs["volume_id"]}
-
-
-class UploadToImageView(forms.ModalFormView):
- form_class = project_forms.UploadToImageForm
- template_name = 'project/volumes/volumes/upload_to_image.html'
- submit_label = _("Upload")
- submit_url = "horizon:project:volumes:volumes:upload_to_image"
- success_url = reverse_lazy("horizon:project:volumes:index")
- page_title = _("Upload Volume to Image")
-
- @memoized.memoized_method
- def get_data(self):
- try:
- volume_id = self.kwargs['volume_id']
- volume = cinder.volume_get(self.request, volume_id)
- except Exception:
- error_message = _(
- 'Unable to retrieve volume information for volume: "%s"') \
- % volume_id
- exceptions.handle(self.request,
- error_message,
- redirect=self.success_url)
-
- return volume
-
- def get_context_data(self, **kwargs):
- context = super(UploadToImageView, self).get_context_data(**kwargs)
- context['volume'] = self.get_data()
- args = (self.kwargs['volume_id'],)
- context['submit_url'] = reverse(self.submit_url, args=args)
- return context
-
- def get_initial(self):
- volume = self.get_data()
-
- return {'id': self.kwargs['volume_id'],
- 'name': volume.name,
- 'status': volume.status}
-
-
-class CreateTransferView(forms.ModalFormView):
- form_class = project_forms.CreateTransferForm
- template_name = 'project/volumes/volumes/create_transfer.html'
- success_url = reverse_lazy('horizon:project:volumes:volumes_tab')
- modal_id = "create_volume_transfer_modal"
- submit_label = _("Create Volume Transfer")
- submit_url = "horizon:project:volumes:volumes:create_transfer"
- page_title = _("Create Volume Transfer")
-
- def get_context_data(self, *args, **kwargs):
- context = super(CreateTransferView, self).get_context_data(**kwargs)
- volume_id = self.kwargs['volume_id']
- context['volume_id'] = volume_id
- context['submit_url'] = reverse(self.submit_url, args=[volume_id])
- return context
-
- def get_initial(self):
- return {'volume_id': self.kwargs["volume_id"]}
-
-
-class AcceptTransferView(forms.ModalFormView):
- form_class = project_forms.AcceptTransferForm
- template_name = 'project/volumes/volumes/accept_transfer.html'
- success_url = reverse_lazy('horizon:project:volumes:volumes_tab')
- modal_id = "accept_volume_transfer_modal"
- submit_label = _("Accept Volume Transfer")
- submit_url = reverse_lazy(
- "horizon:project:volumes:volumes:accept_transfer")
- page_title = _("Accept Volume Transfer")
-
-
-class ShowTransferView(forms.ModalFormView):
- form_class = project_forms.ShowTransferForm
- template_name = 'project/volumes/volumes/show_transfer.html'
- success_url = reverse_lazy('horizon:project:volumes:volumes_tab')
- modal_id = "show_volume_transfer_modal"
- submit_url = "horizon:project:volumes:volumes:show_transfer"
- cancel_label = _("Close")
- download_label = _("Download transfer credentials")
- page_title = _("Volume Transfer Details")
-
- def get_object(self):
- try:
- return self._object
- except AttributeError:
- transfer_id = self.kwargs['transfer_id']
- try:
- self._object = cinder.transfer_get(self.request, transfer_id)
- return self._object
- except Exception:
- exceptions.handle(self.request,
- _('Unable to retrieve volume transfer.'))
-
- def get_context_data(self, **kwargs):
- context = super(ShowTransferView, self).get_context_data(**kwargs)
- context['transfer_id'] = self.kwargs['transfer_id']
- context['auth_key'] = self.kwargs['auth_key']
- context['submit_url'] = reverse(self.submit_url, args=[
- context['transfer_id'], context['auth_key']])
- context['download_label'] = self.download_label
- context['download_url'] = reverse(
- 'horizon:project:volumes:volumes:download_transfer_creds',
- args=[context['transfer_id'], context['auth_key']]
- )
- return context
-
- def get_initial(self):
- transfer = self.get_object()
- return {'id': transfer.id,
- 'name': transfer.name,
- 'auth_key': self.kwargs['auth_key']}
-
-
-class UpdateView(forms.ModalFormView):
- form_class = project_forms.UpdateForm
- modal_id = "update_volume_modal"
- template_name = 'project/volumes/volumes/update.html'
- submit_url = "horizon:project:volumes:volumes:update"
- success_url = reverse_lazy("horizon:project:volumes:index")
- page_title = _("Edit Volume")
-
- def get_object(self):
- if not hasattr(self, "_object"):
- vol_id = self.kwargs['volume_id']
- try:
- self._object = cinder.volume_get(self.request, vol_id)
- except Exception:
- msg = _('Unable to retrieve volume.')
- url = reverse('horizon:project:volumes:index')
- exceptions.handle(self.request, msg, redirect=url)
- return self._object
-
- def get_context_data(self, **kwargs):
- context = super(UpdateView, self).get_context_data(**kwargs)
- context['volume'] = self.get_object()
- args = (self.kwargs['volume_id'],)
- context['submit_url'] = reverse(self.submit_url, args=args)
- return context
-
- def get_initial(self):
- volume = self.get_object()
- return {'volume_id': self.kwargs["volume_id"],
- 'name': volume.name,
- 'description': volume.description,
- 'bootable': volume.is_bootable}
-
-
-class EditAttachmentsView(tables.DataTableView, forms.ModalFormView):
- table_class = project_tables.AttachmentsTable
- form_class = project_forms.AttachForm
- form_id = "attach_volume_form"
- modal_id = "attach_volume_modal"
- template_name = 'project/volumes/volumes/attach.html'
- submit_url = "horizon:project:volumes:volumes:attach"
- success_url = reverse_lazy("horizon:project:volumes:index")
- page_title = _("Manage Volume Attachments")
-
- @memoized.memoized_method
- def get_object(self):
- volume_id = self.kwargs['volume_id']
- try:
- return cinder.volume_get(self.request, volume_id)
- except Exception:
- self._object = None
- exceptions.handle(self.request,
- _('Unable to retrieve volume information.'))
-
- def get_data(self):
- attachments = []
- volume = self.get_object()
- if volume is not None:
- for att in volume.attachments:
- att['volume_name'] = getattr(volume, 'name', att['device'])
- attachments.append(att)
- return attachments
-
- def get_initial(self):
- try:
- instances, has_more = api.nova.server_list(self.request)
- except Exception:
- instances = []
- exceptions.handle(self.request,
- _("Unable to retrieve attachment information."))
- return {'volume': self.get_object(),
- 'instances': instances}
-
- @memoized.memoized_method
- def get_form(self, **kwargs):
- form_class = kwargs.get('form_class', self.get_form_class())
- return super(EditAttachmentsView, self).get_form(form_class)
-
- def get_context_data(self, **kwargs):
- context = super(EditAttachmentsView, self).get_context_data(**kwargs)
- context['form'] = self.get_form()
- volume = self.get_object()
- args = (self.kwargs['volume_id'],)
- context['submit_url'] = reverse(self.submit_url, args=args)
- if volume and volume.status == 'available':
- context['show_attach'] = True
- else:
- context['show_attach'] = False
- context['volume'] = volume
- if self.request.is_ajax():
- context['hide'] = True
- return context
-
- def get(self, request, *args, **kwargs):
- # Table action handling
- handled = self.construct_tables()
- if handled:
- return handled
- return self.render_to_response(self.get_context_data(**kwargs))
-
- def post(self, request, *args, **kwargs):
- form = self.get_form()
- if form.is_valid():
- return self.form_valid(form)
- else:
- return self.get(request, *args, **kwargs)
-
-
-class RetypeView(forms.ModalFormView):
- form_class = project_forms.RetypeForm
- modal_id = "retype_volume_modal"
- template_name = 'project/volumes/volumes/retype.html'
- submit_label = _("Change Volume Type")
- submit_url = "horizon:project:volumes:volumes:retype"
- success_url = reverse_lazy("horizon:project:volumes:index")
- page_title = _("Change Volume Type")
-
- @memoized.memoized_method
- def get_data(self):
- try:
- volume_id = self.kwargs['volume_id']
- volume = cinder.volume_get(self.request, volume_id)
- except Exception:
- error_message = _(
- 'Unable to retrieve volume information for volume: "%s"') \
- % volume_id
- exceptions.handle(self.request,
- error_message,
- redirect=self.success_url)
-
- return volume
-
- def get_context_data(self, **kwargs):
- context = super(RetypeView, self).get_context_data(**kwargs)
- context['volume'] = self.get_data()
- args = (self.kwargs['volume_id'],)
- context['submit_url'] = reverse(self.submit_url, args=args)
- return context
-
- def get_initial(self):
- volume = self.get_data()
-
- return {'id': self.kwargs['volume_id'],
- 'name': volume.name,
- 'volume_type': volume.volume_type}
-
-
-class EncryptionDetailView(generic.TemplateView):
- template_name = 'project/volumes/volumes/encryption_detail.html'
- page_title = _("Volume Encryption Details: {{ volume.name }}")
-
- def get_context_data(self, **kwargs):
- context = super(EncryptionDetailView, self).get_context_data(**kwargs)
- volume = self.get_volume_data()
- context["encryption_metadata"] = self.get_encryption_data()
- context["volume"] = volume
- context["page_title"] = _("Volume Encryption Details: "
- "%(volume_name)s") % {'volume_name':
- volume.name}
- return context
-
- @memoized.memoized_method
- def get_encryption_data(self):
- try:
- volume_id = self.kwargs['volume_id']
- self._encryption_metadata = \
- cinder.volume_get_encryption_metadata(self.request,
- volume_id)
- except Exception:
- redirect = self.get_redirect_url()
- exceptions.handle(self.request,
- _('Unable to retrieve volume encryption '
- 'details.'),
- redirect=redirect)
- return self._encryption_metadata
-
- @memoized.memoized_method
- def get_volume_data(self):
- try:
- volume_id = self.kwargs['volume_id']
- volume = cinder.volume_get(self.request, volume_id)
- except Exception:
- redirect = self.get_redirect_url()
- exceptions.handle(self.request,
- _('Unable to retrieve volume details.'),
- redirect=redirect)
- return volume
-
- def get_redirect_url(self):
- return reverse('horizon:project:volumes:index')
-
-
-class DownloadTransferCreds(generic.View):
- # TODO(Itxaka): Remove cache_control in django >= 1.9
- # https://code.djangoproject.com/ticket/13008
- @method_decorator(cache_control(max_age=0, no_cache=True,
- no_store=True, must_revalidate=True))
- @method_decorator(never_cache)
- def get(self, request, transfer_id, auth_key):
- try:
- transfer = cinder.transfer_get(self.request, transfer_id)
- except Exception:
- transfer = None
- response = http.HttpResponse(content_type='application/text')
- response['Content-Disposition'] = \
- 'attachment; filename=%s.txt' % slugify(transfer_id)
- response.write('%s: %s\n%s: %s\n%s: %s' % (
- _("Transfer name"),
- getattr(transfer, 'name', ''),
- _("Transfer ID"),
- transfer_id,
- _("Authorization Key"),
- auth_key))
- response['Content-Length'] = str(len(response.content))
- return response
diff --git a/openstack_dashboard/enabled/_1040_project_volumes_panel.py b/openstack_dashboard/enabled/_1320_project_volumes_panel.py
similarity index 93%
rename from openstack_dashboard/enabled/_1040_project_volumes_panel.py
rename to openstack_dashboard/enabled/_1320_project_volumes_panel.py
index 5f92f42a8c..10dd491d71 100644
--- a/openstack_dashboard/enabled/_1040_project_volumes_panel.py
+++ b/openstack_dashboard/enabled/_1320_project_volumes_panel.py
@@ -3,7 +3,7 @@ PANEL = 'volumes'
# The slug of the dashboard the PANEL associated with. Required.
PANEL_DASHBOARD = 'project'
# The slug of the panel group the PANEL is associated with.
-PANEL_GROUP = 'compute'
+PANEL_GROUP = 'volumes'
# Python panel class of the PANEL to be added.
ADD_PANEL = 'openstack_dashboard.dashboards.project.volumes.panel.Volumes'