summaryrefslogtreecommitdiff
path: root/openstackclient
diff options
context:
space:
mode:
Diffstat (limited to 'openstackclient')
-rw-r--r--openstackclient/tests/unit/volume/v2/fakes.py29
-rw-r--r--openstackclient/tests/unit/volume/v2/test_backup_record.py114
-rw-r--r--openstackclient/volume/v2/backup_record.py82
3 files changed, 225 insertions, 0 deletions
diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py
index 59b08d0b..c245cbf6 100644
--- a/openstackclient/tests/unit/volume/v2/fakes.py
+++ b/openstackclient/tests/unit/volume/v2/fakes.py
@@ -596,6 +596,35 @@ class FakeBackup(object):
return mock.Mock(side_effect=backups)
+ @staticmethod
+ def create_backup_record():
+ """Gets a fake backup record for a given backup.
+
+ :return: An "exported" backup record.
+ """
+
+ return {
+ 'backup_service': 'cinder.backup.drivers.swift.SwiftBackupDriver',
+ 'backup_url': 'eyJzdGF0dXMiOiAiYXZh',
+ }
+
+ @staticmethod
+ def import_backup_record():
+ """Creates a fake backup record import response from a backup.
+
+ :return: The fake backup object that was encoded.
+ """
+ return {
+ 'backup': {
+ 'id': 'backup.id',
+ 'name': 'backup.name',
+ 'links': [
+ {'href': 'link1', 'rel': 'self'},
+ {'href': 'link2', 'rel': 'bookmark'},
+ ],
+ },
+ }
+
class FakeConsistencyGroup(object):
"""Fake one or more consistency group."""
diff --git a/openstackclient/tests/unit/volume/v2/test_backup_record.py b/openstackclient/tests/unit/volume/v2/test_backup_record.py
new file mode 100644
index 00000000..0e24174c
--- /dev/null
+++ b/openstackclient/tests/unit/volume/v2/test_backup_record.py
@@ -0,0 +1,114 @@
+#
+# 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 openstackclient.tests.unit.volume.v2 import fakes as volume_fakes
+from openstackclient.volume.v2 import backup_record
+
+
+class TestBackupRecord(volume_fakes.TestVolume):
+
+ def setUp(self):
+ super(TestBackupRecord, self).setUp()
+
+ self.backups_mock = self.app.client_manager.volume.backups
+ self.backups_mock.reset_mock()
+
+
+class TestBackupRecordExport(TestBackupRecord):
+
+ new_backup = volume_fakes.FakeBackup.create_one_backup(
+ attrs={'volume_id': 'a54708a2-0388-4476-a909-09579f885c25'})
+ new_record = volume_fakes.FakeBackup.create_backup_record()
+
+ def setUp(self):
+ super(TestBackupRecordExport, self).setUp()
+
+ self.backups_mock.export_record.return_value = self.new_record
+ self.backups_mock.get.return_value = self.new_backup
+
+ # Get the command object to mock
+ self.cmd = backup_record.ExportBackupRecord(self.app, None)
+
+ def test_backup_export_table(self):
+ arglist = [
+ self.new_backup.name,
+ ]
+ verifylist = [
+ ("backup", self.new_backup.name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ parsed_args.formatter = 'table'
+ columns, __ = self.cmd.take_action(parsed_args)
+
+ self.backups_mock.export_record.assert_called_with(
+ self.new_backup.id,
+ )
+
+ expected_columns = ('Backup Service', 'Metadata')
+ self.assertEqual(columns, expected_columns)
+
+ def test_backup_export_json(self):
+ arglist = [
+ self.new_backup.name,
+ ]
+ verifylist = [
+ ("backup", self.new_backup.name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ parsed_args.formatter = 'json'
+ columns, __ = self.cmd.take_action(parsed_args)
+
+ self.backups_mock.export_record.assert_called_with(
+ self.new_backup.id,
+ )
+
+ expected_columns = ('backup_service', 'backup_url')
+ self.assertEqual(columns, expected_columns)
+
+
+class TestBackupRecordImport(TestBackupRecord):
+
+ new_backup = volume_fakes.FakeBackup.create_one_backup(
+ attrs={'volume_id': 'a54708a2-0388-4476-a909-09579f885c25'})
+ new_import = volume_fakes.FakeBackup.import_backup_record()
+
+ def setUp(self):
+ super(TestBackupRecordImport, self).setUp()
+
+ self.backups_mock.import_record.return_value = self.new_import
+
+ # Get the command object to mock
+ self.cmd = backup_record.ImportBackupRecord(self.app, None)
+
+ def test_backup_import(self):
+ arglist = [
+ "cinder.backup.drivers.swift.SwiftBackupDriver",
+ "fake_backup_record_data",
+ ]
+ verifylist = [
+ ("backup_service",
+ "cinder.backup.drivers.swift.SwiftBackupDriver"),
+ ("backup_metadata", "fake_backup_record_data"),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, __ = self.cmd.take_action(parsed_args)
+
+ self.backups_mock.import_record.assert_called_with(
+ "cinder.backup.drivers.swift.SwiftBackupDriver",
+ "fake_backup_record_data",
+ )
+ self.assertEqual(columns, ('backup',))
diff --git a/openstackclient/volume/v2/backup_record.py b/openstackclient/volume/v2/backup_record.py
new file mode 100644
index 00000000..f4918032
--- /dev/null
+++ b/openstackclient/volume/v2/backup_record.py
@@ -0,0 +1,82 @@
+#
+# 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.
+#
+
+"""Volume v2 Backup action implementations"""
+
+import logging
+
+from osc_lib.command import command
+from osc_lib import utils
+import six
+
+from openstackclient.i18n import _
+
+
+LOG = logging.getLogger(__name__)
+
+
+class ExportBackupRecord(command.ShowOne):
+ _description = _('Export volume backup details. Backup information can be '
+ 'imported into a new service instance to be able to '
+ 'restore.')
+
+ def get_parser(self, prog_name):
+ parser = super(ExportBackupRecord, self).get_parser(prog_name)
+ parser.add_argument(
+ "backup",
+ metavar="<backup>",
+ help=_("Backup to export (name or ID)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
+ backup = utils.find_resource(volume_client.backups, parsed_args.backup)
+ backup_data = volume_client.backups.export_record(backup.id)
+
+ # We only want to show "friendly" display names, but also want to keep
+ # json structure compatibility with cinderclient
+ if parsed_args.formatter == 'table':
+ backup_data['Backup Service'] = backup_data.pop('backup_service')
+ backup_data['Metadata'] = backup_data.pop('backup_url')
+
+ return zip(*sorted(six.iteritems(backup_data)))
+
+
+class ImportBackupRecord(command.ShowOne):
+ _description = _('Import volume backup details. Exported backup details '
+ 'contain the metadata necessary to restore to a new or '
+ 'rebuilt service instance')
+
+ def get_parser(self, prog_name):
+ parser = super(ImportBackupRecord, self).get_parser(prog_name)
+ parser.add_argument(
+ "backup_service",
+ metavar="<backup_service>",
+ help=_("Backup service containing the backup.")
+ )
+ parser.add_argument(
+ "backup_metadata",
+ metavar="<backup_metadata>",
+ help=_("Encoded backup metadata from export.")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
+ backup_data = volume_client.backups.import_record(
+ parsed_args.backup_service,
+ parsed_args.backup_metadata)
+ backup_data.pop('links', None)
+ return zip(*sorted(six.iteritems(backup_data)))