Merge "Restrict launch fields when restoring from backup"

This commit is contained in:
Zuul 2017-11-02 20:23:43 +00:00 committed by Gerrit Code Review
commit f278d12a57
4 changed files with 128 additions and 2 deletions

View File

@ -0,0 +1,6 @@
---
fixes:
- Adds support for restricting the launch instance datastore field
to the datastore and datastore version that the backup is
relevant to. It also populates the restored backup as the only
option in the advanced step backup field.

View File

@ -12,19 +12,27 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import binascii
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django import http from django import http
from django.utils.translation import ugettext_lazy as _
from mox3.mox import IsA # noqa from mox3.mox import IsA # noqa
import six import six
from openstack_auth import policy from openstack_auth import policy
from openstack_dashboard import api as dash_api
from troveclient import common
from trove_dashboard import api from trove_dashboard import api
from trove_dashboard.content.databases.workflows import create_instance
from trove_dashboard.test import helpers as test from trove_dashboard.test import helpers as test
INDEX_URL = reverse('horizon:project:database_backups:index') INDEX_URL = reverse('horizon:project:database_backups:index')
BACKUP_URL = reverse('horizon:project:database_backups:create') BACKUP_URL = reverse('horizon:project:database_backups:create')
DETAILS_URL = reverse('horizon:project:database_backups:detail', args=['id']) DETAILS_URL = reverse('horizon:project:database_backups:detail', args=['id'])
RESTORE_URL = reverse('horizon:project:databases:launch')
class DatabasesBackupsTests(test.TestCase): class DatabasesBackupsTests(test.TestCase):
@ -191,3 +199,63 @@ class DatabasesBackupsTests(test.TestCase):
args=[incr_backup.id]) args=[incr_backup.id])
res = self.client.get(url) res = self.client.get(url)
self.assertTemplateUsed(res, 'project/database_backups/details.html') self.assertTemplateUsed(res, 'project/database_backups/details.html')
@test.create_stubs({
api.trove: ('backup_get', 'backup_list', 'configuration_list',
'datastore_flavors', 'datastore_list',
'datastore_version_list', 'instance_list'),
dash_api.cinder: ('volume_type_list',),
dash_api.neutron: ('network_list',),
dash_api.nova: ('availability_zone_list',),
policy: ('check',),
})
def test_restore_backup(self):
policy.check((), IsA(http.HttpRequest)).MultipleTimes().AndReturn(True)
backup = self.database_backups.first()
api.trove.backup_get(IsA(http.HttpRequest), IsA(six.text_type)) \
.AndReturn(self.database_backups.first())
api.trove.backup_list(IsA(http.HttpRequest)).AndReturn(
self.database_backups.list())
api.trove.configuration_list(IsA(http.HttpRequest)) \
.AndReturn(self.database_configurations.list())
api.trove.datastore_flavors(IsA(http.HttpRequest),
IsA(six.string_types),
IsA(six.string_types)) \
.AndReturn(self.flavors.list())
api.trove.datastore_list(IsA(http.HttpRequest)) \
.AndReturn(self.datastores.list())
api.trove.datastore_version_list(IsA(http.HttpRequest),
backup.datastore['type']) \
.AndReturn(self.datastore_versions.list())
api.trove.instance_list(IsA(http.HttpRequest), marker=None) \
.AndReturn(common.Paginated(self.databases.list()))
dash_api.cinder.volume_type_list(IsA(http.HttpRequest)).AndReturn([])
dash_api.neutron.network_list(IsA(http.HttpRequest),
tenant_id=self.tenant.id,
shared=False).\
AndReturn(self.networks.list()[:1])
dash_api.nova.availability_zone_list(IsA(http.HttpRequest)) \
.AndReturn(self.availability_zones.list())
self.mox.ReplayAll()
url = RESTORE_URL + '?backup=%s' % self.database_backups.first().id
res = self.client.get(url)
self.assertTemplateUsed(res, 'project/databases/launch.html')
set_instance_detail_step = \
[step for step in res.context_data['workflow'].steps
if isinstance(step, create_instance.SetInstanceDetails)][0]
fields = set_instance_detail_step.action.fields
self.assertTrue(len(fields['datastore'].choices), 1)
text = 'mysql - 5.6'
choice = fields['datastore'].choices[0]
self.assertTrue(choice[0], binascii.hexlify(text))
self.assertTrue(choice[1], text)
advanced_step = [step for step in res.context_data['workflow'].steps
if isinstance(step, create_instance.Advanced)][0]
fields = advanced_step.action.fields
self.assertTrue(len(fields['initial_state'].choices), 1)
choice = fields['initial_state'].choices[0]
self.assertTrue(choice[0], 'backup')
self.assertTrue(choice[1], _('Restore from Backup'))

View File

@ -64,6 +64,11 @@ class SetInstanceDetailsAction(workflows.Action):
})) }))
def __init__(self, request, *args, **kwargs): def __init__(self, request, *args, **kwargs):
if args:
self.backup_id = args[0].get('backup', None)
else:
self.backup_id = None
super(SetInstanceDetailsAction, self).__init__(request, super(SetInstanceDetailsAction, self).__init__(request,
*args, *args,
**kwargs) **kwargs)
@ -184,11 +189,24 @@ class SetInstanceDetailsAction(workflows.Action):
LOG.exception("Exception while obtaining datastore version list") LOG.exception("Exception while obtaining datastore version list")
self._datastore_versions = [] self._datastore_versions = []
@memoized.memoized_method
def get_backup(self, request, backup_id):
try:
return api.trove.backup_get(request, backup_id)
except Exception:
LOG.exception("Exception while obtaining backup information")
return None
def populate_datastore_choices(self, request, context): def populate_datastore_choices(self, request, context):
choices = () choices = ()
datastores = self.datastores(request) datastores = self.datastores(request)
if datastores is not None: if datastores is not None:
if self.backup_id:
backup = self.get_backup(request, self.backup_id)
for ds in datastores: for ds in datastores:
if self.backup_id:
if ds.name != backup.datastore['type']:
continue
versions = self.datastore_versions(request, ds.name) versions = self.datastore_versions(request, ds.name)
if versions: if versions:
# only add to choices if datastore has at least one version # only add to choices if datastore has at least one version
@ -196,6 +214,9 @@ class SetInstanceDetailsAction(workflows.Action):
for v in versions: for v in versions:
if hasattr(v, 'active') and not v.active: if hasattr(v, 'active') and not v.active:
continue continue
if self.backup_id:
if v.id != backup.datastore['version_id']:
continue
selection_text = self._build_datastore_display_text( selection_text = self._build_datastore_display_text(
ds.name, v.name) ds.name, v.name)
widget_text = self._build_widget_field_name( widget_text = self._build_widget_field_name(
@ -350,6 +371,18 @@ class AdvancedAction(workflows.Action):
'data-initial_state-master': _('Replica Count') 'data-initial_state-master': _('Replica Count')
})) }))
def __init__(self, request, *args, **kwargs):
if args[0]:
self.backup_id = args[0].get('backup', None)
else:
self.backup_id = None
super(AdvancedAction, self).__init__(request, *args, **kwargs)
if self.backup_id:
self.fields['initial_state'].choices = [('backup',
_('Restore from Backup'))]
class Meta(object): class Meta(object):
name = _("Advanced") name = _("Advanced")
help_text_template = "project/databases/_launch_advanced_help.html" help_text_template = "project/databases/_launch_advanced_help.html"
@ -374,9 +407,13 @@ class AdvancedAction(workflows.Action):
def populate_backup_choices(self, request, context): def populate_backup_choices(self, request, context):
try: try:
choices = []
backups = api.trove.backup_list(request) backups = api.trove.backup_list(request)
choices = [(b.id, b.name) for b in backups for b in backups:
if b.status == 'COMPLETED'] if self.backup_id and b.id != self.backup_id:
continue
if b.status == 'COMPLETED':
choices.append((b.id, b.name))
except Exception: except Exception:
choices = [] choices = []

View File

@ -229,6 +229,11 @@ BACKUP_ONE = {
"size": 0.13, "size": 0.13,
"id": "0edb3c14-8919-4583-9add-00df9e524081", "id": "0edb3c14-8919-4583-9add-00df9e524081",
"description": "Long description of backup", "description": "Long description of backup",
"datastore": {
"type": "mysql",
"version": "5.6",
"version_id": "500a6d52-8347-4e00-8e4c-f4fa9cf96ae9"
},
} }
BACKUP_TWO = { BACKUP_TWO = {
@ -241,6 +246,11 @@ BACKUP_TWO = {
"size": 0.13, "size": 0.13,
"id": "e4602a3c-2bca-478f-b059-b6c215510fb4", "id": "e4602a3c-2bca-478f-b059-b6c215510fb4",
"description": "Longer description of backup", "description": "Longer description of backup",
"datastore": {
"type": "mysql",
"version": "5.6",
"version_id": "500a6d52-8347-4e00-8e4c-f4fa9cf96ae9"
},
} }
BACKUP_TWO_INC = { BACKUP_TWO_INC = {
@ -254,6 +264,11 @@ BACKUP_TWO_INC = {
"id": "e4602a3c-2bca-478f-b059-b6c215510fb5", "id": "e4602a3c-2bca-478f-b059-b6c215510fb5",
"description": "Longer description of backup", "description": "Longer description of backup",
"parent_id": "e4602a3c-2bca-478f-b059-b6c215510fb4", "parent_id": "e4602a3c-2bca-478f-b059-b6c215510fb4",
"datastore": {
"type": "mysql",
"version": "5.6",
"version_id": "500a6d52-8347-4e00-8e4c-f4fa9cf96ae9"
},
} }
CONFIG_ONE = { CONFIG_ONE = {