diff --git a/doc/source/cli/command-objects/volume-backup.rst b/doc/source/cli/command-objects/volume-backup.rst index 585f47d46..632e215e2 100644 --- a/doc/source/cli/command-objects/volume-backup.rst +++ b/doc/source/cli/command-objects/volume-backup.rst @@ -2,200 +2,8 @@ volume backup ============= -Block Storage v1, v2 +Volume v1, v2 -volume backup create --------------------- +.. autoprogram-cliff:: openstack.volume.v2 + :command: volume backup * -Create new volume backup - -.. program:: volume backup create -.. code:: bash - - openstack volume backup create - [--container ] - [--name ] - [--description ] - [--snapshot ] - [--force] - [--incremental] - - -.. option:: --container - - Optional backup container name - -.. option:: --name - - Name of the backup - -.. option:: --description - - Description of the backup - -.. option:: --snapshot - - Snapshot to backup (name or ID) - - *Volume version 2 only* - -.. option:: --force - - Allow to back up an in-use volume - - *Volume version 2 only* - -.. option:: --incremental - - Perform an incremental backup - - *Volume version 2 only* - -.. _volume_backup_create-backup: -.. describe:: - - Volume to backup (name or ID) - -volume backup delete --------------------- - -Delete volume backup(s) - -.. program:: volume backup delete -.. code:: bash - - openstack volume backup delete - [--force] - [ ...] - -.. option:: --force - - Allow delete in state other than error or available - - *Volume version 2 only* - -.. _volume_backup_delete-backup: -.. describe:: - - Backup(s) to delete (name or ID) - -volume backup list ------------------- - -List volume backups - -.. program:: volume backup list -.. code:: bash - - openstack volume backup list - [--long] - [--name ] - [--status ] - [--volume ] - [--marker ] - [--limit ] - [--all-projects] - -.. _volume_backup_list-backup: -.. option:: --long - - List additional fields in output - -.. option:: --name - - Filters results by the backup name - -.. option:: --status - - Filters results by the backup status - ('creating', 'available', 'deleting', 'error', 'restoring' or 'error_restoring') - -.. option:: --volume - - Filters results by the volume which they backup (name or ID)" - -.. option:: --marker - - The last backup of the previous page (name or ID) - - *Volume version 2 only* - -.. option:: --limit - - Maximum number of backups to display - - *Volume version 2 only* - -.. option:: --all-projects - - Include all projects (admin only) - -volume backup restore ---------------------- - -Restore volume backup - -.. program:: volume backup restore -.. code:: bash - - openstack volume backup restore - - - -.. _volume_backup_restore-backup: -.. describe:: - - Backup to restore (name or ID) - -.. describe:: - - Volume to restore to (name or ID) - -volume backup set ------------------ - -Set volume backup properties - -.. program:: volume backup set -.. code:: bash - - openstack volume backup set - [--name ] - [--description ] - [--state ] - - -.. option:: --name - - New backup name - -.. option:: --description - - New backup description - -.. option:: --state - - New backup state ("available" or "error") (admin only) - (This option simply changes the state of the backup in the database with - no regard to actual status, exercise caution when using) - -.. _backup_set-volume-backup: -.. describe:: - - Backup to modify (name or ID) - -volume backup show ------------------- - -Display volume backup details - -.. program:: volume backup show -.. code:: bash - - openstack volume backup show - - -.. _volume_backup_show-backup: -.. describe:: - - Backup to display (name or ID) diff --git a/doc/source/cli/commands.rst b/doc/source/cli/commands.rst index d7c912404..cdd5e63c9 100644 --- a/doc/source/cli/commands.rst +++ b/doc/source/cli/commands.rst @@ -158,6 +158,8 @@ referring to both Compute and Volume quotas. * ``volume backup``: (**Volume**) backup for volumes * ``volume backend capability``: (**volume**) volume backend storage capabilities * ``volume backend pool``: (**volume**) volume backend storage pools +* ``volume backup record``: (**Volume**) volume record that can be imported or exported +* ``volume backend``: (**volume**) volume backend storage * ``volume host``: (**Volume**) the physical computer for volumes * ``volume qos``: (**Volume**) quality-of-service (QoS) specification for volumes * ``volume snapshot``: (**Volume**) a point-in-time copy of a volume diff --git a/doc/source/cli/data/cinder.csv b/doc/source/cli/data/cinder.csv index cc8ef2d91..22fb84cff 100644 --- a/doc/source/cli/data/cinder.csv +++ b/doc/source/cli/data/cinder.csv @@ -2,8 +2,8 @@ absolute-limits,limits show --absolute,Lists absolute limits for a user. availability-zone-list,availability zone list --volume,Lists all availability zones. backup-create,volume backup create,Creates a volume backup. backup-delete,volume backup delete,Removes a backup. -backup-export,volume backup export,Export backup metadata record. -backup-import,volume backup import,Import backup metadata record. +backup-export,volume backup record export,Export backup metadata record. +backup-import,volume backup record import,Import backup metadata record. backup-list,volume backup list,Lists all backups. backup-reset-state,volume backup set --state,Explicitly updates the backup state. backup-restore,volume backup restore,Restores a backup. diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index 59b08d0b9..c245cbf66 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 000000000..0e24174c5 --- /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 000000000..f49180327 --- /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="", + 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="", + help=_("Backup service containing the backup.") + ) + parser.add_argument( + "backup_metadata", + metavar="", + 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))) diff --git a/releasenotes/notes/volume-backup-record-9f5987c45e294dc6.yaml b/releasenotes/notes/volume-backup-record-9f5987c45e294dc6.yaml new file mode 100644 index 000000000..955e99714 --- /dev/null +++ b/releasenotes/notes/volume-backup-record-9f5987c45e294dc6.yaml @@ -0,0 +1,15 @@ +--- +features: + - | + Add ``openstack volume backup record export `` command that + provides encrypted backup record metadata that can then be used to import + and restore on a different or rebuilt block storage service. Information + about volume backup export and import can be found in the `Cinder block + storage service documentation `_. + - | + Add ``openstack volume backup record import + `` command that can be used to import and restore on a + different or rebuilt block storage service. Information about volume backup + export and import can be found in the `Cinder block storage service + documentation `_. + diff --git a/setup.cfg b/setup.cfg index 73c5fde93..818b6ef25 100644 --- a/setup.cfg +++ b/setup.cfg @@ -629,6 +629,9 @@ openstack.volume.v2 = volume_backup_set = openstackclient.volume.v2.backup:SetVolumeBackup volume_backup_show = openstackclient.volume.v2.backup:ShowVolumeBackup + volume_backup_record_export = openstackclient.volume.v2.backup_record:ExportBackupRecord + volume_backup_record_import = openstackclient.volume.v2.backup_record:ImportBackupRecord + volume_backend_capability_show = openstackclient.volume.v2.volume_backend:ShowCapability volume_backend_pool_list = openstackclient.volume.v2.volume_backend:ListPool