Restrict launch fields when restoring from backup
When restoring from backup the launch wizard contains all options and performs no customizations based on the information contained in the backup. This patch 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. Added a unit test to validate the fields were populated correctly. Change-Id: Iabfefd34b5f7a24699277104fa92ac77b2c74fe3 Closes-Bug: #1647746
This commit is contained in:
parent
76833761ed
commit
66b826868c
@ -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.
|
@ -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'))
|
||||||
|
@ -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 = []
|
||||||
|
|
||||||
|
@ -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 = {
|
||||||
|
Loading…
Reference in New Issue
Block a user