# Copyright 2023 Cloudification GmbH. # All Rights Reserved. # # 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. import logging from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils as osc_utils from manilaclient import api_versions from manilaclient.common._i18n import _ from manilaclient.common import constants from manilaclient.osc import utils LOG = logging.getLogger(__name__) class CreateShareBackup(command.ShowOne): """Create a share backup.""" _description = _("Create a backup of the given share") def get_parser(self, prog_name): parser = super(CreateShareBackup, self).get_parser(prog_name) parser.add_argument( "share", metavar="", help=_("Name or ID of the share to backup.") ) parser.add_argument( '--name', metavar='', default=None, help=_('Optional share backup name. (Default=None).') ) parser.add_argument( '--description', metavar='', default=None, help=_('Optional share backup description. (Default=None).') ) parser.add_argument( "--backup-options", metavar="", default={}, action=parseractions.KeyValueAction, help=_("Backup driver option key=value pairs (Optional, " "Default=None)."), ) return parser def take_action(self, parsed_args): share_client = self.app.client_manager.share share = osc_utils.find_resource( share_client.shares, parsed_args.share) body = {} if parsed_args.backup_options: body['backup_options'] = utils.extract_key_value_options( parsed_args.backup_options) if parsed_args.description: body['description'] = parsed_args.description if parsed_args.name: body['name'] = parsed_args.name share_backup = share_client.share_backups.create(share, **body) share_backup._info.pop('links', None) return self.dict2columns(share_backup._info) class DeleteShareBackup(command.Command): """Delete one or more share backups.""" _description = _("Delete one or more share backups") def get_parser(self, prog_name): parser = super(DeleteShareBackup, self).get_parser(prog_name) parser.add_argument( "backup", metavar="", nargs="+", help=_("Name or ID of the backup(s) to delete") ) parser.add_argument( "--wait", action='store_true', default=False, help=_("Wait for share backup deletion") ) return parser def take_action(self, parsed_args): share_client = self.app.client_manager.share result = 0 for backup in parsed_args.backup: try: share_backup_obj = osc_utils.find_resource( share_client.share_backups, backup) share_client.share_backups.delete(share_backup_obj) if parsed_args.wait: if not osc_utils.wait_for_delete( manager=share_client.share_backups, res_id=share_backup_obj.id): result += 1 except Exception as e: result += 1 LOG.error(_( "Failed to delete a share backup with " "name or ID '%(backup)s': %(e)s"), {'backup': backup, 'e': e}) if result > 0: total = len(parsed_args.backup) msg = (_("%(result)s of %(total)s backups failed " "to delete.") % {'result': result, 'total': total}) raise exceptions.CommandError(msg) class ListShareBackup(command.Lister): """List share backups.""" _description = _("List share backups") def get_parser(self, prog_name): parser = super(ListShareBackup, self).get_parser(prog_name) parser.add_argument( "--share", metavar="", default=None, help=_("Name or ID of the share to list backups for.") ) parser.add_argument( "--name", metavar="", default=None, help=_("Filter results by name. Default=None.") ) parser.add_argument( '--description', metavar="", default=None, help=_("Filter results by description. Default=None.") ) parser.add_argument( "--name~", metavar="", default=None, help=_("Filter results matching a share backup name pattern. ") ) parser.add_argument( '--description~', metavar="", default=None, help=_("Filter results matching a share backup description ") ) parser.add_argument( '--status', metavar="", default=None, help=_('Filter results by status. Default=None.') ) parser.add_argument( "--limit", metavar="", type=int, default=None, action=parseractions.NonNegativeAction, help=_("Limit the number of backups returned. Default=None.") ) parser.add_argument( '--offset', metavar="", default=None, help='Start position of backup records listing.') parser.add_argument( '--sort-key', '--sort_key', metavar='', type=str, default=None, help='Key to be sorted, available keys are %(keys)s. ' 'Default=None.' % {'keys': constants.BACKUP_SORT_KEY_VALUES}) parser.add_argument( '--sort-dir', '--sort_dir', metavar='', type=str, default=None, help='Sort direction, available values are %(values)s. ' 'OPTIONAL: Default=None.' % { 'values': constants.SORT_DIR_VALUES}) parser.add_argument( '--detail', dest='detail', metavar='<0|1>', nargs='?', type=int, const=1, default=0, help="Show detailed information about share backups.") return parser def take_action(self, parsed_args): share_client = self.app.client_manager.share share_id = None if parsed_args.share: share_id = osc_utils.find_resource(share_client.shares, parsed_args.share).id columns = [ 'ID', 'Name', 'Share ID', 'Status' ] if parsed_args.detail: columns.extend(['Description', 'Size', 'Created At', 'Updated At', 'Availability Zone', 'Progress', 'Restore Progress', 'Host', 'Topic']) search_opts = { 'limit': parsed_args.limit, 'offset': parsed_args.offset, 'name': parsed_args.name, 'description': parsed_args.description, 'status': parsed_args.status, 'share_id': share_id, } search_opts['name~'] = getattr(parsed_args, 'name~') search_opts['description~'] = getattr(parsed_args, 'description~') backups = share_client.share_backups.list( detailed=parsed_args.detail, search_opts=search_opts, sort_key=parsed_args.sort_key, sort_dir=parsed_args.sort_dir) return (columns, (osc_utils.get_item_properties(b, columns) for b in backups)) class ShowShareBackup(command.ShowOne): """Show share backup.""" _description = _("Show details of a backup") def get_parser(self, prog_name): parser = super(ShowShareBackup, self).get_parser(prog_name) parser.add_argument( "backup", metavar="", help=_("ID of the share backup. ") ) return parser def take_action(self, parsed_args): share_client = self.app.client_manager.share backup = osc_utils.find_resource(share_client.share_backups, parsed_args.backup) backup._info.pop('links', None) return self.dict2columns(backup._info) class RestoreShareBackup(command.Command): """Restore share backup to share""" _description = _("Attempt to restore share backup") def get_parser(self, prog_name): parser = super(RestoreShareBackup, self).get_parser(prog_name) parser.add_argument( "backup", metavar="", help=_('ID of backup to restore.') ) parser.add_argument( "--target-share", metavar="", default=None, help=_('share to restore backup to. Source share if none supplied') ) parser.add_argument( '--wait', action='store_true', default=False, help=_('Wait for restore conclusion') ) return parser def take_action(self, parsed_args): share_client = self.app.client_manager.share kwargs = {} share_backup = osc_utils.find_resource( share_client.share_backups, parsed_args.backup ) target_share_id = None if parsed_args.target_share is not None: if share_client.api_version < api_versions.APIVersion('2.91'): raise exceptions.CommandError( 'performing targeted restores is only available ' 'for API microversion >= 2.91') else: target_share_id = osc_utils.find_resource( share_client.shares, parsed_args.target_share ).id kwargs['target_share_id'] = target_share_id share_client.share_backups.restore(share_backup.id, **kwargs) if parsed_args.wait: if not osc_utils.wait_for_status( status_f=share_client.shares.get, res_id=(target_share_id or share_backup.share_id), success_status=['available'], error_status=['error', 'backup_restoring_error'] ): LOG.error(_("ERROR: share is in error state.")) class SetShareBackup(command.Command): """Set share backup properties.""" _description = _("Set share backup properties") def get_parser(self, prog_name): parser = super(SetShareBackup, self).get_parser(prog_name) parser.add_argument( "backup", metavar="", help=_('Name or ID of the backup to set a property for') ) parser.add_argument( "--name", metavar="", default=None, help=_("Set a name to the backup.") ) parser.add_argument( "--description", metavar="", default=None, help=_("Set a description to the backup.") ) parser.add_argument( "--status", metavar="", choices=['available', 'error', 'creating', 'deleting', 'restoring'], help=_("Assign a status to the backup(Admin only). " "Options include : available, error, creating, " "deleting, restoring.") ) return parser def take_action(self, parsed_args): share_client = self.app.client_manager.share result = 0 share_backup = osc_utils.find_resource( share_client.share_backups, parsed_args.backup) kwargs = {} if parsed_args.name is not None: kwargs['name'] = parsed_args.name if parsed_args.description is not None: kwargs['description'] = parsed_args.description try: share_client.share_backups.update(share_backup, **kwargs) except Exception as e: result += 1 LOG.error(_( "Failed to set share backup properties " "'%(properties)s': %(exception)s"), {'properties': kwargs, 'exception': e}) if parsed_args.status: try: share_client.share_backups.reset_status( share_backup, parsed_args.status ) except Exception as e: result += 1 LOG.error(_( "Failed to update backup status to " "'%(status)s': %(e)s"), {'status': parsed_args.status, 'e': e}) if result > 0: raise exceptions.CommandError(_("One or more of the " "set operations failed")) class UnsetShareBackup(command.Command): """Unset share backup properties.""" _description = _("Unset share backup properties") def get_parser(self, prog_name): parser = super(UnsetShareBackup, self).get_parser(prog_name) parser.add_argument( "backup", metavar="", help=_('Name or ID of the backup to unset a property for') ) parser.add_argument( "--name", action='store_true', help=_("Unset a name to the backup.") ) parser.add_argument( "--description", action='store_true', help=_("Unset a description to the backup.") ) return parser def take_action(self, parsed_args): share_client = self.app.client_manager.share share_backup = osc_utils.find_resource( share_client.share_backups, parsed_args.backup) kwargs = {} if parsed_args.name: kwargs['name'] = None if parsed_args.description: kwargs['description'] = None if not kwargs: msg = "Either name or description must be provided." raise exceptions.CommandError(msg) try: share_client.share_backups.update(share_backup, **kwargs) except Exception as e: LOG.error(_( "Failed to unset share backup properties " "'%(properties)s': %(exception)s"), {'properties': kwargs, 'exception': e})