Move Volume Backups out of tabbed panel
Notes on enabling backup: https://github.com/coolsvap/devstack-cinder-backup Change-Id: Ie6bcfad30d04ee35c75d693f5637197297ca84ef Implements: blueprint reorganise-volumes
This commit is contained in:
parent
d7e1adeca0
commit
921f84a7ce
@ -74,7 +74,7 @@ class RestoreBackupForm(forms.SelfHandlingForm):
|
|||||||
volumes = api.cinder.volume_list(request)
|
volumes = api.cinder.volume_list(request)
|
||||||
except Exception:
|
except Exception:
|
||||||
msg = _('Unable to lookup volume or backup information.')
|
msg = _('Unable to lookup volume or backup information.')
|
||||||
redirect = reverse('horizon:project:volumes:index')
|
redirect = reverse('horizon:project:backups:index')
|
||||||
exceptions.handle(request, msg, redirect=redirect)
|
exceptions.handle(request, msg, redirect=redirect)
|
||||||
raise exceptions.Http302(redirect)
|
raise exceptions.Http302(redirect)
|
||||||
|
|
||||||
@ -104,5 +104,5 @@ class RestoreBackupForm(forms.SelfHandlingForm):
|
|||||||
return restore
|
return restore
|
||||||
except Exception:
|
except Exception:
|
||||||
msg = _('Unable to restore backup.')
|
msg = _('Unable to restore backup.')
|
||||||
redirect = reverse('horizon:project:volumes:index')
|
redirect = reverse('horizon:project:backups:index')
|
||||||
exceptions.handle(request, msg, redirect=redirect)
|
exceptions.handle(request, msg, redirect=redirect)
|
26
openstack_dashboard/dashboards/project/backups/panel.py
Normal file
26
openstack_dashboard/dashboards/project/backups/panel.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Copyright 2017 Rackspace, 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 _
|
||||||
|
|
||||||
|
import horizon
|
||||||
|
|
||||||
|
|
||||||
|
class Backups(horizon.Panel):
|
||||||
|
name = _("Backups")
|
||||||
|
slug = 'backups'
|
||||||
|
permissions = (
|
||||||
|
('openstack.services.volume', 'openstack.services.volumev2'),
|
||||||
|
)
|
||||||
|
policy_rules = (("volume", "backup:get_all"),)
|
@ -86,7 +86,7 @@ class RestoreBackup(tables.LinkAction):
|
|||||||
backup_id = datum.id
|
backup_id = datum.id
|
||||||
backup_name = datum.name
|
backup_name = datum.name
|
||||||
volume_id = getattr(datum, 'volume_id', None)
|
volume_id = getattr(datum, 'volume_id', None)
|
||||||
url = reverse("horizon:project:volumes:backups:restore",
|
url = reverse("horizon:project:backups:restore",
|
||||||
args=(backup_id,))
|
args=(backup_id,))
|
||||||
url += '?%s' % http.urlencode({'backup_name': backup_name,
|
url += '?%s' % http.urlencode({'backup_name': backup_name,
|
||||||
'volume_id': volume_id})
|
'volume_id': volume_id})
|
||||||
@ -133,7 +133,7 @@ class BackupsTable(tables.DataTable):
|
|||||||
)
|
)
|
||||||
name = tables.Column("name",
|
name = tables.Column("name",
|
||||||
verbose_name=_("Name"),
|
verbose_name=_("Name"),
|
||||||
link="horizon:project:volumes:backups:detail")
|
link="horizon:project:backups:detail")
|
||||||
description = tables.Column("description",
|
description = tables.Column("description",
|
||||||
verbose_name=_("Description"),
|
verbose_name=_("Description"),
|
||||||
truncate=40)
|
truncate=40)
|
@ -23,8 +23,7 @@ from openstack_dashboard.api import cinder
|
|||||||
class BackupOverviewTab(tabs.Tab):
|
class BackupOverviewTab(tabs.Tab):
|
||||||
name = _("Overview")
|
name = _("Overview")
|
||||||
slug = "overview"
|
slug = "overview"
|
||||||
template_name = ("project/volumes/backups/"
|
template_name = "project/backups/_detail_overview.html"
|
||||||
"_detail_overview.html")
|
|
||||||
|
|
||||||
def get_context_data(self, request):
|
def get_context_data(self, request):
|
||||||
try:
|
try:
|
||||||
@ -36,7 +35,7 @@ class BackupOverviewTab(tabs.Tab):
|
|||||||
return {'backup': backup,
|
return {'backup': backup,
|
||||||
'volume': volume}
|
'volume': volume}
|
||||||
except Exception:
|
except Exception:
|
||||||
redirect = reverse('horizon:project:volumes:index')
|
redirect = reverse('horizon:project:backups:index')
|
||||||
exceptions.handle(self.request,
|
exceptions.handle(self.request,
|
||||||
_('Unable to retrieve backup details.'),
|
_('Unable to retrieve backup details.'),
|
||||||
redirect=redirect)
|
redirect=redirect)
|
@ -10,21 +10,108 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django import http
|
from django import http
|
||||||
|
from django.test.utils import override_settings
|
||||||
from django.utils.http import urlencode
|
from django.utils.http import urlencode
|
||||||
|
from django.utils.http import urlunquote
|
||||||
from mox3.mox import IsA # noqa
|
from mox3.mox import IsA # noqa
|
||||||
|
|
||||||
from openstack_dashboard import api
|
from openstack_dashboard import api
|
||||||
|
from openstack_dashboard.dashboards.project.backups \
|
||||||
|
import tables as backup_tables
|
||||||
from openstack_dashboard.test import helpers as test
|
from openstack_dashboard.test import helpers as test
|
||||||
|
|
||||||
|
|
||||||
INDEX_URL = reverse('horizon:project:volumes:index')
|
INDEX_URL = reverse('horizon:project:backups:index')
|
||||||
VOLUME_BACKUPS_TAB_URL = reverse('horizon:project:volumes:backups_tab')
|
|
||||||
|
|
||||||
|
|
||||||
class VolumeBackupsViewTests(test.TestCase):
|
class VolumeBackupsViewTests(test.TestCase):
|
||||||
|
|
||||||
|
@test.create_stubs({api.cinder: ('tenant_absolute_limits',
|
||||||
|
'volume_backup_list_paged',
|
||||||
|
'volume_list'),
|
||||||
|
api.nova: ('server_list',)})
|
||||||
|
def _test_backups_index_paginated(self, marker, sort_dir, backups, url,
|
||||||
|
has_more, has_prev):
|
||||||
|
api.cinder.volume_backup_list_paged(
|
||||||
|
IsA(http.HttpRequest), marker=marker, sort_dir=sort_dir,
|
||||||
|
paginate=True).AndReturn([backups, has_more, has_prev])
|
||||||
|
api.cinder.volume_list(IsA(http.HttpRequest)).AndReturn(
|
||||||
|
self.cinder_volumes.list())
|
||||||
|
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
|
||||||
|
|
||||||
|
@override_settings(API_RESULT_PAGE_SIZE=1)
|
||||||
|
def test_backups_index_paginated(self):
|
||||||
|
mox_backups = self.cinder_volume_backups.list()
|
||||||
|
size = settings.API_RESULT_PAGE_SIZE
|
||||||
|
base_url = INDEX_URL
|
||||||
|
next = backup_tables.BackupsTable._meta.pagination_param
|
||||||
|
|
||||||
|
# get first page
|
||||||
|
expected_backups = mox_backups[:size]
|
||||||
|
res = self._test_backups_index_paginated(
|
||||||
|
marker=None, sort_dir="desc", backups=expected_backups,
|
||||||
|
url=base_url, has_more=True, has_prev=False)
|
||||||
|
backups = res.context['volume_backups_table'].data
|
||||||
|
self.assertItemsEqual(backups, expected_backups)
|
||||||
|
|
||||||
|
# get second page
|
||||||
|
expected_backups = mox_backups[size:2 * size]
|
||||||
|
marker = expected_backups[0].id
|
||||||
|
|
||||||
|
url = base_url + "?%s=%s" % (next, marker)
|
||||||
|
res = self._test_backups_index_paginated(
|
||||||
|
marker=marker, sort_dir="desc", backups=expected_backups, url=url,
|
||||||
|
has_more=True, has_prev=True)
|
||||||
|
backups = res.context['volume_backups_table'].data
|
||||||
|
self.assertItemsEqual(backups, expected_backups)
|
||||||
|
|
||||||
|
# get last page
|
||||||
|
expected_backups = mox_backups[-size:]
|
||||||
|
marker = expected_backups[0].id
|
||||||
|
url = base_url + "?%s=%s" % (next, marker)
|
||||||
|
res = self._test_backups_index_paginated(
|
||||||
|
marker=marker, sort_dir="desc", backups=expected_backups, url=url,
|
||||||
|
has_more=False, has_prev=True)
|
||||||
|
backups = res.context['volume_backups_table'].data
|
||||||
|
self.assertItemsEqual(backups, expected_backups)
|
||||||
|
|
||||||
|
@override_settings(API_RESULT_PAGE_SIZE=1)
|
||||||
|
def test_backups_index_paginated_prev_page(self):
|
||||||
|
mox_backups = self.cinder_volume_backups.list()
|
||||||
|
size = settings.API_RESULT_PAGE_SIZE
|
||||||
|
base_url = INDEX_URL
|
||||||
|
prev = backup_tables.BackupsTable._meta.prev_pagination_param
|
||||||
|
|
||||||
|
# prev from some page
|
||||||
|
expected_backups = mox_backups[size:2 * size]
|
||||||
|
marker = expected_backups[0].id
|
||||||
|
url = base_url + "?%s=%s" % (prev, marker)
|
||||||
|
res = self._test_backups_index_paginated(
|
||||||
|
marker=marker, sort_dir="asc", backups=expected_backups, url=url,
|
||||||
|
has_more=True, has_prev=True)
|
||||||
|
backups = res.context['volume_backups_table'].data
|
||||||
|
self.assertItemsEqual(backups, expected_backups)
|
||||||
|
|
||||||
|
# back to first page
|
||||||
|
expected_backups = mox_backups[:size]
|
||||||
|
marker = expected_backups[0].id
|
||||||
|
url = base_url + "?%s=%s" % (prev, marker)
|
||||||
|
res = self._test_backups_index_paginated(
|
||||||
|
marker=marker, sort_dir="asc", backups=expected_backups, url=url,
|
||||||
|
has_more=True, has_prev=False)
|
||||||
|
backups = res.context['volume_backups_table'].data
|
||||||
|
self.assertItemsEqual(backups, expected_backups)
|
||||||
|
|
||||||
@test.create_stubs({api.cinder: ('volume_backup_create',)})
|
@test.create_stubs({api.cinder: ('volume_backup_create',)})
|
||||||
def test_create_backup_post(self):
|
def test_create_backup_post(self):
|
||||||
volume = self.volumes.first()
|
volume = self.volumes.first()
|
||||||
@ -50,10 +137,9 @@ class VolumeBackupsViewTests(test.TestCase):
|
|||||||
|
|
||||||
self.assertNoFormErrors(res)
|
self.assertNoFormErrors(res)
|
||||||
self.assertMessageCount(error=0, warning=0)
|
self.assertMessageCount(error=0, warning=0)
|
||||||
self.assertRedirectsNoFollow(res, VOLUME_BACKUPS_TAB_URL)
|
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||||
|
|
||||||
@test.create_stubs({api.cinder: ('volume_list',
|
@test.create_stubs({api.cinder: ('volume_list',
|
||||||
'volume_backup_supported',
|
|
||||||
'volume_backup_list_paged',
|
'volume_backup_list_paged',
|
||||||
'volume_backup_delete')})
|
'volume_backup_delete')})
|
||||||
def test_delete_volume_backup(self):
|
def test_delete_volume_backup(self):
|
||||||
@ -61,8 +147,6 @@ class VolumeBackupsViewTests(test.TestCase):
|
|||||||
volumes = self.cinder_volumes.list()
|
volumes = self.cinder_volumes.list()
|
||||||
backup = self.cinder_volume_backups.first()
|
backup = self.cinder_volume_backups.first()
|
||||||
|
|
||||||
api.cinder.volume_backup_supported(IsA(http.HttpRequest)). \
|
|
||||||
MultipleTimes().AndReturn(True)
|
|
||||||
api.cinder.volume_backup_list_paged(
|
api.cinder.volume_backup_list_paged(
|
||||||
IsA(http.HttpRequest), marker=None, sort_dir='desc',
|
IsA(http.HttpRequest), marker=None, sort_dir='desc',
|
||||||
paginate=True).AndReturn([vol_backups, False, False])
|
paginate=True).AndReturn([vol_backups, False, False])
|
||||||
@ -74,12 +158,9 @@ class VolumeBackupsViewTests(test.TestCase):
|
|||||||
|
|
||||||
formData = {'action':
|
formData = {'action':
|
||||||
'volume_backups__delete__%s' % backup.id}
|
'volume_backups__delete__%s' % backup.id}
|
||||||
res = self.client.post(INDEX_URL +
|
res = self.client.post(INDEX_URL, formData)
|
||||||
"?tab=volumes_and_snapshots__backups_tab",
|
|
||||||
formData)
|
|
||||||
|
|
||||||
self.assertRedirectsNoFollow(res, INDEX_URL +
|
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||||
"?tab=volumes_and_snapshots__backups_tab")
|
|
||||||
self.assertMessageCount(success=1)
|
self.assertMessageCount(success=1)
|
||||||
|
|
||||||
@test.create_stubs({api.cinder: ('volume_backup_get', 'volume_get')})
|
@test.create_stubs({api.cinder: ('volume_backup_get', 'volume_get')})
|
||||||
@ -93,7 +174,7 @@ class VolumeBackupsViewTests(test.TestCase):
|
|||||||
AndReturn(volume)
|
AndReturn(volume)
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
url = reverse('horizon:project:volumes:backups:detail',
|
url = reverse('horizon:project:backups:detail',
|
||||||
args=[backup.id])
|
args=[backup.id])
|
||||||
res = self.client.get(url)
|
res = self.client.get(url)
|
||||||
|
|
||||||
@ -109,7 +190,7 @@ class VolumeBackupsViewTests(test.TestCase):
|
|||||||
AndRaise(self.exceptions.cinder)
|
AndRaise(self.exceptions.cinder)
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
url = reverse('horizon:project:volumes:backups:detail',
|
url = reverse('horizon:project:backups:detail',
|
||||||
args=[backup.id])
|
args=[backup.id])
|
||||||
res = self.client.get(url)
|
res = self.client.get(url)
|
||||||
|
|
||||||
@ -128,7 +209,7 @@ class VolumeBackupsViewTests(test.TestCase):
|
|||||||
AndRaise(self.exceptions.cinder)
|
AndRaise(self.exceptions.cinder)
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
url = reverse('horizon:project:volumes:backups:detail',
|
url = reverse('horizon:project:backups:detail',
|
||||||
args=[backup.id])
|
args=[backup.id])
|
||||||
res = self.client.get(url)
|
res = self.client.get(url)
|
||||||
|
|
||||||
@ -153,7 +234,7 @@ class VolumeBackupsViewTests(test.TestCase):
|
|||||||
'backup_id': backup.id,
|
'backup_id': backup.id,
|
||||||
'backup_name': backup.name,
|
'backup_name': backup.name,
|
||||||
'volume_id': backup.volume_id}
|
'volume_id': backup.volume_id}
|
||||||
url = reverse('horizon:project:volumes:backups:restore',
|
url = reverse('horizon:project:backups:restore',
|
||||||
args=[backup.id])
|
args=[backup.id])
|
||||||
url += '?%s' % urlencode({'backup_name': backup.name,
|
url += '?%s' % urlencode({'backup_name': backup.name,
|
||||||
'volume_id': backup.volume_id})
|
'volume_id': backup.volume_id})
|
||||||
@ -161,4 +242,5 @@ class VolumeBackupsViewTests(test.TestCase):
|
|||||||
|
|
||||||
self.assertNoFormErrors(res)
|
self.assertNoFormErrors(res)
|
||||||
self.assertMessageCount(info=1)
|
self.assertMessageCount(info=1)
|
||||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
self.assertRedirectsNoFollow(res,
|
||||||
|
reverse('horizon:project:volumes:index'))
|
@ -12,10 +12,11 @@
|
|||||||
|
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
|
|
||||||
from openstack_dashboard.dashboards.project.volumes.backups import views
|
from openstack_dashboard.dashboards.project.backups import views
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
url(r'^$', views.BackupsView.as_view(), name='index'),
|
||||||
url(r'^(?P<backup_id>[^/]+)/$',
|
url(r'^(?P<backup_id>[^/]+)/$',
|
||||||
views.BackupDetailView.as_view(),
|
views.BackupDetailView.as_view(),
|
||||||
name='detail'),
|
name='detail'),
|
@ -16,24 +16,53 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
|
|
||||||
from horizon import exceptions
|
from horizon import exceptions
|
||||||
from horizon import forms
|
from horizon import forms
|
||||||
|
from horizon import tables
|
||||||
from horizon import tabs
|
from horizon import tabs
|
||||||
from horizon.utils import memoized
|
from horizon.utils import memoized
|
||||||
|
|
||||||
from openstack_dashboard import api
|
from openstack_dashboard import api
|
||||||
from openstack_dashboard.dashboards.project.volumes.backups \
|
from openstack_dashboard.dashboards.project.backups \
|
||||||
import forms as backup_forms
|
import forms as backup_forms
|
||||||
from openstack_dashboard.dashboards.project.volumes.backups \
|
from openstack_dashboard.dashboards.project.backups \
|
||||||
import tables as backup_tables
|
import tables as backup_tables
|
||||||
from openstack_dashboard.dashboards.project.volumes.backups \
|
from openstack_dashboard.dashboards.project.backups \
|
||||||
import tabs as backup_tabs
|
import tabs as backup_tabs
|
||||||
|
from openstack_dashboard.dashboards.project.volumes \
|
||||||
|
import tabs as volume_tabs
|
||||||
|
|
||||||
|
|
||||||
|
class BackupsView(tables.DataTableView, tables.PagedTableMixin,
|
||||||
|
volume_tabs.VolumeTableMixIn):
|
||||||
|
table_class = backup_tables.BackupsTable
|
||||||
|
page_title = _("Volume Backups")
|
||||||
|
|
||||||
|
def allowed(self, request):
|
||||||
|
return api.cinder.volume_backup_supported(self.request)
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
try:
|
||||||
|
marker, sort_dir = self._get_marker()
|
||||||
|
backups, self._has_more_data, self._has_prev_data = \
|
||||||
|
api.cinder.volume_backup_list_paged(
|
||||||
|
self.request, marker=marker, sort_dir=sort_dir,
|
||||||
|
paginate=True)
|
||||||
|
volumes = api.cinder.volume_list(self.request)
|
||||||
|
volumes = dict((v.id, v) for v in volumes)
|
||||||
|
for backup in backups:
|
||||||
|
backup.volume = volumes.get(backup.volume_id)
|
||||||
|
except Exception:
|
||||||
|
backups = []
|
||||||
|
exceptions.handle(self.request, _("Unable to retrieve "
|
||||||
|
"volume backups."))
|
||||||
|
return backups
|
||||||
|
|
||||||
|
|
||||||
class CreateBackupView(forms.ModalFormView):
|
class CreateBackupView(forms.ModalFormView):
|
||||||
form_class = backup_forms.CreateBackupForm
|
form_class = backup_forms.CreateBackupForm
|
||||||
template_name = 'project/volumes/backups/create_backup.html'
|
template_name = 'project/backups/create_backup.html'
|
||||||
submit_label = _("Create Volume Backup")
|
submit_label = _("Create Volume Backup")
|
||||||
submit_url = "horizon:project:volumes:volumes:create_backup"
|
submit_url = "horizon:project:volumes:volumes:create_backup"
|
||||||
success_url = reverse_lazy("horizon:project:volumes:backups_tab")
|
success_url = reverse_lazy("horizon:project:backups:index")
|
||||||
page_title = _("Create Volume Backup")
|
page_title = _("Create Volume Backup")
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
@ -79,14 +108,14 @@ class BackupDetailView(tabs.TabView):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_redirect_url():
|
def get_redirect_url():
|
||||||
return reverse('horizon:project:volumes:index')
|
return reverse('horizon:project:backups:index')
|
||||||
|
|
||||||
|
|
||||||
class RestoreBackupView(forms.ModalFormView):
|
class RestoreBackupView(forms.ModalFormView):
|
||||||
form_class = backup_forms.RestoreBackupForm
|
form_class = backup_forms.RestoreBackupForm
|
||||||
template_name = 'project/volumes/backups/restore_backup.html'
|
template_name = 'project/backups/restore_backup.html'
|
||||||
submit_label = _("Restore Backup to Volume")
|
submit_label = _("Restore Backup to Volume")
|
||||||
submit_url = "horizon:project:volumes:backups:restore"
|
submit_url = "horizon:project:backups:restore"
|
||||||
success_url = reverse_lazy('horizon:project:volumes:index')
|
success_url = reverse_lazy('horizon:project:volumes:index')
|
||||||
page_title = _("Restore Volume Backup")
|
page_title = _("Restore Volume Backup")
|
||||||
|
|
@ -23,8 +23,6 @@ from horizon import tabs
|
|||||||
from openstack_dashboard import api
|
from openstack_dashboard import api
|
||||||
from openstack_dashboard import policy
|
from openstack_dashboard import policy
|
||||||
|
|
||||||
from openstack_dashboard.dashboards.project.volumes.backups \
|
|
||||||
import tables as backups_tables
|
|
||||||
from openstack_dashboard.dashboards.project.volumes.cg_snapshots \
|
from openstack_dashboard.dashboards.project.volumes.cg_snapshots \
|
||||||
import tables as cg_snapshots_tables
|
import tables as cg_snapshots_tables
|
||||||
from openstack_dashboard.dashboards.project.volumes.cgroups \
|
from openstack_dashboard.dashboards.project.volumes.cgroups \
|
||||||
@ -126,34 +124,6 @@ class VolumeTab(PagedTableMixin, tabs.TableTab, VolumeTableMixIn):
|
|||||||
return volumes
|
return volumes
|
||||||
|
|
||||||
|
|
||||||
class BackupsTab(PagedTableMixin, tabs.TableTab, VolumeTableMixIn):
|
|
||||||
table_classes = (backups_tables.BackupsTable,)
|
|
||||||
name = _("Volume Backups")
|
|
||||||
slug = "backups_tab"
|
|
||||||
template_name = ("horizon/common/_detail_table.html")
|
|
||||||
preload = False
|
|
||||||
|
|
||||||
def allowed(self, request):
|
|
||||||
return api.cinder.volume_backup_supported(self.request)
|
|
||||||
|
|
||||||
def get_volume_backups_data(self):
|
|
||||||
try:
|
|
||||||
marker, sort_dir = self._get_marker()
|
|
||||||
backups, self._has_more_data, self._has_prev_data = \
|
|
||||||
api.cinder.volume_backup_list_paged(
|
|
||||||
self.request, marker=marker, sort_dir=sort_dir,
|
|
||||||
paginate=True)
|
|
||||||
volumes = api.cinder.volume_list(self.request)
|
|
||||||
volumes = dict((v.id, v) for v in volumes)
|
|
||||||
for backup in backups:
|
|
||||||
backup.volume = volumes.get(backup.volume_id)
|
|
||||||
except Exception:
|
|
||||||
backups = []
|
|
||||||
exceptions.handle(self.request, _("Unable to retrieve "
|
|
||||||
"volume backups."))
|
|
||||||
return backups
|
|
||||||
|
|
||||||
|
|
||||||
class CGroupsTab(tabs.TableTab):
|
class CGroupsTab(tabs.TableTab):
|
||||||
table_classes = (cgroup_tables.VolumeCGroupsTable,)
|
table_classes = (cgroup_tables.VolumeCGroupsTable,)
|
||||||
name = _("Consistency Groups")
|
name = _("Consistency Groups")
|
||||||
@ -206,5 +176,5 @@ class CGSnapshotsTab(tabs.TableTab):
|
|||||||
|
|
||||||
class VolumeAndSnapshotTabs(tabs.TabGroup):
|
class VolumeAndSnapshotTabs(tabs.TabGroup):
|
||||||
slug = "volumes_and_snapshots"
|
slug = "volumes_and_snapshots"
|
||||||
tabs = (VolumeTab, BackupsTab, CGroupsTab, CGSnapshotsTab)
|
tabs = (VolumeTab, CGroupsTab, CGSnapshotsTab)
|
||||||
sticky = True
|
sticky = True
|
||||||
|
@ -23,16 +23,12 @@ from django.utils.http import urlunquote
|
|||||||
from mox3.mox import IsA # noqa
|
from mox3.mox import IsA # noqa
|
||||||
|
|
||||||
from openstack_dashboard import api
|
from openstack_dashboard import api
|
||||||
from openstack_dashboard.dashboards.project.volumes.backups \
|
|
||||||
import tables as backup_tables
|
|
||||||
from openstack_dashboard.dashboards.project.volumes.volumes \
|
from openstack_dashboard.dashboards.project.volumes.volumes \
|
||||||
import tables as volume_tables
|
import tables as volume_tables
|
||||||
from openstack_dashboard.test import helpers as test
|
from openstack_dashboard.test import helpers as test
|
||||||
|
|
||||||
|
|
||||||
INDEX_URL = reverse('horizon:project:volumes:index')
|
INDEX_URL = reverse('horizon:project:volumes:index')
|
||||||
VOLUME_BACKUPS_TAB_URL = urlunquote(reverse(
|
|
||||||
'horizon:project:volumes:backups_tab'))
|
|
||||||
|
|
||||||
|
|
||||||
class VolumeAndSnapshotsAndBackupsTests(test.TestCase):
|
class VolumeAndSnapshotsAndBackupsTests(test.TestCase):
|
||||||
@ -44,8 +40,7 @@ class VolumeAndSnapshotsAndBackupsTests(test.TestCase):
|
|||||||
'volume_backup_list_paged',
|
'volume_backup_list_paged',
|
||||||
),
|
),
|
||||||
api.nova: ('server_list',)})
|
api.nova: ('server_list',)})
|
||||||
def _test_index(self, backup_supported=True, instanceless_volumes=False):
|
def test_index(self, instanceless_volumes=False):
|
||||||
vol_backups = self.cinder_volume_backups.list()
|
|
||||||
vol_snaps = self.cinder_volume_snapshots.list()
|
vol_snaps = self.cinder_volume_snapshots.list()
|
||||||
volumes = self.cinder_volumes.list()
|
volumes = self.cinder_volumes.list()
|
||||||
if instanceless_volumes:
|
if instanceless_volumes:
|
||||||
@ -53,7 +48,7 @@ class VolumeAndSnapshotsAndBackupsTests(test.TestCase):
|
|||||||
volume.attachments = []
|
volume.attachments = []
|
||||||
|
|
||||||
api.cinder.volume_backup_supported(IsA(http.HttpRequest)).\
|
api.cinder.volume_backup_supported(IsA(http.HttpRequest)).\
|
||||||
MultipleTimes().AndReturn(backup_supported)
|
MultipleTimes().AndReturn(False)
|
||||||
api.cinder.volume_list_paged(
|
api.cinder.volume_list_paged(
|
||||||
IsA(http.HttpRequest), marker=None, search_opts=None,
|
IsA(http.HttpRequest), marker=None, search_opts=None,
|
||||||
sort_dir='desc', paginate=True).\
|
sort_dir='desc', paginate=True).\
|
||||||
@ -65,11 +60,6 @@ class VolumeAndSnapshotsAndBackupsTests(test.TestCase):
|
|||||||
api.cinder.volume_snapshot_list(IsA(http.HttpRequest)).\
|
api.cinder.volume_snapshot_list(IsA(http.HttpRequest)).\
|
||||||
AndReturn(vol_snaps)
|
AndReturn(vol_snaps)
|
||||||
|
|
||||||
if backup_supported:
|
|
||||||
api.cinder.volume_backup_list_paged(
|
|
||||||
IsA(http.HttpRequest), marker=None, sort_dir='desc',
|
|
||||||
paginate=True).AndReturn([vol_backups, False, False])
|
|
||||||
api.cinder.volume_list(IsA(http.HttpRequest)).AndReturn(volumes)
|
|
||||||
api.cinder.tenant_absolute_limits(IsA(http.HttpRequest)).\
|
api.cinder.tenant_absolute_limits(IsA(http.HttpRequest)).\
|
||||||
MultipleTimes().AndReturn(self.cinder_limits['absolute'])
|
MultipleTimes().AndReturn(self.cinder_limits['absolute'])
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
@ -78,18 +68,8 @@ class VolumeAndSnapshotsAndBackupsTests(test.TestCase):
|
|||||||
self.assertEqual(res.status_code, 200)
|
self.assertEqual(res.status_code, 200)
|
||||||
self.assertTemplateUsed(res, 'project/volumes/index.html')
|
self.assertTemplateUsed(res, 'project/volumes/index.html')
|
||||||
|
|
||||||
if backup_supported:
|
|
||||||
res = self.client.get(VOLUME_BACKUPS_TAB_URL)
|
|
||||||
self.assertTemplateUsed(res, 'project/volumes/index.html')
|
|
||||||
|
|
||||||
def test_index_backup_supported(self):
|
|
||||||
self._test_index(backup_supported=True)
|
|
||||||
|
|
||||||
def test_index_backup_not_supported(self):
|
|
||||||
self._test_index(backup_supported=False)
|
|
||||||
|
|
||||||
def test_index_no_volume_attachments(self):
|
def test_index_no_volume_attachments(self):
|
||||||
self._test_index(instanceless_volumes=True)
|
self.test_index(instanceless_volumes=True)
|
||||||
|
|
||||||
@test.create_stubs({api.cinder: ('tenant_absolute_limits',
|
@test.create_stubs({api.cinder: ('tenant_absolute_limits',
|
||||||
'volume_list_paged',
|
'volume_list_paged',
|
||||||
@ -193,92 +173,3 @@ class VolumeAndSnapshotsAndBackupsTests(test.TestCase):
|
|||||||
has_more=True, has_prev=False)
|
has_more=True, has_prev=False)
|
||||||
volumes = res.context['volumes_table'].data
|
volumes = res.context['volumes_table'].data
|
||||||
self.assertItemsEqual(volumes, expected_volumes)
|
self.assertItemsEqual(volumes, expected_volumes)
|
||||||
|
|
||||||
@test.create_stubs({api.cinder: ('tenant_absolute_limits',
|
|
||||||
'volume_backup_list_paged',
|
|
||||||
'volume_list',
|
|
||||||
'volume_backup_supported',
|
|
||||||
),
|
|
||||||
api.nova: ('server_list',)})
|
|
||||||
def _test_backups_index_paginated(self, marker, sort_dir, backups, url,
|
|
||||||
has_more, has_prev):
|
|
||||||
backup_supported = True
|
|
||||||
|
|
||||||
api.cinder.volume_backup_supported(IsA(http.HttpRequest)).\
|
|
||||||
MultipleTimes().AndReturn(backup_supported)
|
|
||||||
api.cinder.volume_backup_list_paged(
|
|
||||||
IsA(http.HttpRequest), marker=marker, sort_dir=sort_dir,
|
|
||||||
paginate=True).AndReturn([backups, has_more, has_prev])
|
|
||||||
api.cinder.volume_list(IsA(http.HttpRequest)).AndReturn(
|
|
||||||
self.cinder_volumes.list())
|
|
||||||
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
|
|
||||||
|
|
||||||
@override_settings(API_RESULT_PAGE_SIZE=1)
|
|
||||||
def test_backups_index_paginated(self):
|
|
||||||
mox_backups = self.cinder_volume_backups.list()
|
|
||||||
size = settings.API_RESULT_PAGE_SIZE
|
|
||||||
base_url = reverse('horizon:project:volumes:backups_tab')
|
|
||||||
next = backup_tables.BackupsTable._meta.pagination_param
|
|
||||||
|
|
||||||
# get first page
|
|
||||||
expected_backups = mox_backups[:size]
|
|
||||||
res = self._test_backups_index_paginated(
|
|
||||||
marker=None, sort_dir="desc", backups=expected_backups,
|
|
||||||
url=base_url, has_more=True, has_prev=False)
|
|
||||||
backups = res.context['volume_backups_table'].data
|
|
||||||
self.assertItemsEqual(backups, expected_backups)
|
|
||||||
|
|
||||||
# get second page
|
|
||||||
expected_backups = mox_backups[size:2 * size]
|
|
||||||
marker = expected_backups[0].id
|
|
||||||
|
|
||||||
url = "&".join([base_url, "=".join([next, marker])])
|
|
||||||
res = self._test_backups_index_paginated(
|
|
||||||
marker=marker, sort_dir="desc", backups=expected_backups, url=url,
|
|
||||||
has_more=True, has_prev=True)
|
|
||||||
backups = res.context['volume_backups_table'].data
|
|
||||||
self.assertItemsEqual(backups, expected_backups)
|
|
||||||
|
|
||||||
# get last page
|
|
||||||
expected_backups = mox_backups[-size:]
|
|
||||||
marker = expected_backups[0].id
|
|
||||||
url = "&".join([base_url, "=".join([next, marker])])
|
|
||||||
res = self._test_backups_index_paginated(
|
|
||||||
marker=marker, sort_dir="desc", backups=expected_backups, url=url,
|
|
||||||
has_more=False, has_prev=True)
|
|
||||||
backups = res.context['volume_backups_table'].data
|
|
||||||
self.assertItemsEqual(backups, expected_backups)
|
|
||||||
|
|
||||||
@override_settings(API_RESULT_PAGE_SIZE=1)
|
|
||||||
def test_backups_index_paginated_prev_page(self):
|
|
||||||
mox_backups = self.cinder_volume_backups.list()
|
|
||||||
size = settings.API_RESULT_PAGE_SIZE
|
|
||||||
base_url = reverse('horizon:project:volumes:backups_tab')
|
|
||||||
prev = backup_tables.BackupsTable._meta.prev_pagination_param
|
|
||||||
|
|
||||||
# prev from some page
|
|
||||||
expected_backups = mox_backups[size:2 * size]
|
|
||||||
marker = expected_backups[0].id
|
|
||||||
url = "&".join([base_url, "=".join([prev, marker])])
|
|
||||||
res = self._test_backups_index_paginated(
|
|
||||||
marker=marker, sort_dir="asc", backups=expected_backups, url=url,
|
|
||||||
has_more=True, has_prev=True)
|
|
||||||
backups = res.context['volume_backups_table'].data
|
|
||||||
self.assertItemsEqual(backups, expected_backups)
|
|
||||||
|
|
||||||
# back to first page
|
|
||||||
expected_backups = mox_backups[:size]
|
|
||||||
marker = expected_backups[0].id
|
|
||||||
url = "&".join([base_url, "=".join([prev, marker])])
|
|
||||||
res = self._test_backups_index_paginated(
|
|
||||||
marker=marker, sort_dir="asc", backups=expected_backups, url=url,
|
|
||||||
has_more=True, has_prev=False)
|
|
||||||
backups = res.context['volume_backups_table'].data
|
|
||||||
self.assertItemsEqual(backups, expected_backups)
|
|
||||||
|
@ -15,8 +15,6 @@
|
|||||||
from django.conf.urls import include
|
from django.conf.urls import include
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
|
|
||||||
from openstack_dashboard.dashboards.project.volumes.backups \
|
|
||||||
import urls as backups_urls
|
|
||||||
from openstack_dashboard.dashboards.project.volumes.cg_snapshots \
|
from openstack_dashboard.dashboards.project.volumes.cg_snapshots \
|
||||||
import urls as cg_snapshots_urls
|
import urls as cg_snapshots_urls
|
||||||
from openstack_dashboard.dashboards.project.volumes.cgroups \
|
from openstack_dashboard.dashboards.project.volumes.cgroups \
|
||||||
@ -29,8 +27,6 @@ urlpatterns = [
|
|||||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||||
url(r'^\?tab=volumes_and_snapshots__volumes_tab$',
|
url(r'^\?tab=volumes_and_snapshots__volumes_tab$',
|
||||||
views.IndexView.as_view(), name='volumes_tab'),
|
views.IndexView.as_view(), name='volumes_tab'),
|
||||||
url(r'^\?tab=volumes_and_snapshots__backups_tab$',
|
|
||||||
views.IndexView.as_view(), name='backups_tab'),
|
|
||||||
url(r'^\?tab=volumes_and_snapshots__cgroups_tab$',
|
url(r'^\?tab=volumes_and_snapshots__cgroups_tab$',
|
||||||
views.IndexView.as_view(), name='cgroups_tab'),
|
views.IndexView.as_view(), name='cgroups_tab'),
|
||||||
url(r'^\?tab=volumes_and_snapshots__cg_snapshots_tab$',
|
url(r'^\?tab=volumes_and_snapshots__cg_snapshots_tab$',
|
||||||
@ -38,9 +34,6 @@ urlpatterns = [
|
|||||||
url(r'', include(
|
url(r'', include(
|
||||||
volume_urls,
|
volume_urls,
|
||||||
namespace='volumes')),
|
namespace='volumes')),
|
||||||
url(r'backups/', include(
|
|
||||||
backups_urls,
|
|
||||||
namespace='backups')),
|
|
||||||
url(r'cgroups/', include(
|
url(r'cgroups/', include(
|
||||||
cgroup_urls,
|
cgroup_urls,
|
||||||
namespace='cgroups')),
|
namespace='cgroups')),
|
||||||
|
@ -16,7 +16,7 @@ from django.conf.urls import url
|
|||||||
|
|
||||||
from openstack_dashboard.dashboards.project.volumes \
|
from openstack_dashboard.dashboards.project.volumes \
|
||||||
.volumes import views
|
.volumes import views
|
||||||
from openstack_dashboard.dashboards.project.volumes.backups \
|
from openstack_dashboard.dashboards.project.backups \
|
||||||
import views as backup_views
|
import views as backup_views
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
# The slug of the panel to be added to HORIZON_CONFIG. Required.
|
||||||
|
PANEL = 'backups'
|
||||||
|
# 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 = 'volumes'
|
||||||
|
|
||||||
|
# Python panel class of the PANEL to be added.
|
||||||
|
ADD_PANEL = 'openstack_dashboard.dashboards.project.backups.panel.Backups'
|
Loading…
Reference in New Issue
Block a user