Beispiel #1
0
 def create_backup(self, context, backup):
     volume = self.db.volume_get(context, backup.volume_id)
     try:
         host = self.driver.get_backup_host(volume)
         backup.host = host
         backup.save()
         self.backup_api.create_backup(context, backup)
     except exception.ServiceNotFound:
         msg = "Service not found for creating backup."
         LOG.error(msg)
         vol_utils.update_backup_error(backup, msg)
Beispiel #2
0
 def create_backup(self, context, backup):
     volume_id = backup.volume_id
     volume = self.db.volume_get(context, volume_id)
     try:
         host = self.driver.get_backup_host(volume)
         backup.host = host
         backup.save()
         self.backup_api.create_backup(context, backup)
     except exception.ServiceNotFound:
         self.db.volume_update(
             context, volume_id, {
                 'status': volume['previous_status'],
                 'previous_status': volume['status']
             })
         msg = "Service not found for creating backup."
         LOG.error(msg)
         vol_utils.update_backup_error(backup, msg)
Beispiel #3
0
    def _cleanup_one_backup(self, ctxt, backup):
        if backup['status'] == fields.BackupStatus.CREATING:
            LOG.info('Resetting backup %s to error (was creating).',
                     backup['id'])

            volume = objects.Volume.get_by_id(ctxt, backup.volume_id)
            self._cleanup_one_volume(ctxt, volume)

            err = 'incomplete backup reset on manager restart'
            volume_utils.update_backup_error(backup, err)
        elif backup['status'] == fields.BackupStatus.RESTORING:
            LOG.info('Resetting backup %s to '
                     'available (was restoring).',
                     backup['id'])
            volume = objects.Volume.get_by_id(ctxt, backup.restore_volume_id)
            self._cleanup_one_volume(ctxt, volume)

            backup.status = fields.BackupStatus.AVAILABLE
            backup.save()
        elif backup['status'] == fields.BackupStatus.DELETING:
            # Don't resume deleting the backup of an encrypted volume. The
            # admin context won't be sufficient to delete the backup's copy
            # of the encryption key ID (a real user context is required).
            if backup.encryption_key_id is None:
                LOG.info('Resuming delete on backup: %s.', backup.id)
                if CONF.backup_service_inithost_offload:
                    # Offload all the pending backup delete operations to the
                    # threadpool to prevent the main backup service thread
                    # from being blocked.
                    self._add_to_threadpool(self.delete_backup, ctxt, backup)
                else:
                    # Delete backups sequentially
                    self.delete_backup(ctxt, backup)
            else:
                LOG.info('Unable to resume deleting backup of an encrypted '
                         'volume, resetting backup %s to error_deleting '
                         '(was deleting).',
                         backup.id)
                backup.status = fields.BackupStatus.ERROR_DELETING
                backup.save()
Beispiel #4
0
 def create_backup(self, context, backup):
     volume_id = backup.volume_id
     volume = self.db.volume_get(context, volume_id)
     try:
         host = self.driver.get_backup_host(volume)
         backup.host = host
         backup.save()
         self.backup_api.create_backup(context, backup)
     except exception.ServiceNotFound:
         self.db.volume_update(
             context, volume_id, {
                 'status': volume['previous_status'],
                 'previous_status': volume['status']
             })
         msg = "Service not found for creating backup."
         LOG.error(msg)
         vol_utils.update_backup_error(backup, msg)
         self.message_api.create(
             context,
             action=message_field.Action.BACKUP_CREATE,
             resource_type=message_field.Resource.VOLUME_BACKUP,
             resource_uuid=backup.id,
             detail=message_field.Detail.BACKUP_SCHEDULE_ERROR)
Beispiel #5
0
    def import_record(self, context, backup, backup_service, backup_url,
                      backup_hosts):
        """Import all volume backup metadata details to the backup db.

        :param context: running context
        :param backup: The new backup object for the import
        :param backup_service: The needed backup driver for import
        :param backup_url: An identifier string to locate the backup
        :param backup_hosts: Potential hosts to execute the import
        :raises InvalidBackup:
        :raises ServiceNotFound:
        """
        LOG.info('Import record started, backup_url: %s.', backup_url)

        # Can we import this backup?
        if not self._is_our_backup(backup_service):
            # No, are there additional potential backup hosts in the list?
            if len(backup_hosts) > 0:
                # try the next host on the list, maybe he can import
                first_host = backup_hosts.pop()
                self.backup_rpcapi.import_record(context, first_host, backup,
                                                 backup_service, backup_url,
                                                 backup_hosts)
            else:
                # empty list - we are the last host on the list, fail
                err = _('Import record failed, cannot find backup '
                        'service to perform the import. Request service '
                        '%(service)s.') % {
                            'service': backup_service
                        }
                volume_utils.update_backup_error(backup, err)
                raise exception.ServiceNotFound(service_id=backup_service)
        else:
            # Yes...
            try:
                # Deserialize backup record information
                backup_options = backup.decode_record(backup_url)

                # Extract driver specific info and pass it to the driver
                driver_options = backup_options.pop('driver_info', {})
                backup_service = self.service(context)
                backup_service.import_record(backup, driver_options)
            except Exception as err:
                msg = str(err)
                volume_utils.update_backup_error(backup, msg)
                raise exception.InvalidBackup(reason=msg)

            required_import_options = {
                'display_name', 'display_description', 'container', 'size',
                'service_metadata', 'object_count', 'id'
            }

            # Check for missing fields in imported data
            missing_opts = required_import_options - set(backup_options)
            if missing_opts:
                msg = (_('Driver successfully decoded imported backup data, '
                         'but there are missing fields (%s).') %
                       ', '.join(missing_opts))
                volume_utils.update_backup_error(backup, msg)
                raise exception.InvalidBackup(reason=msg)

            # Confirm the ID from the record in the DB is the right one
            backup_id = backup_options['id']
            if backup_id != backup.id:
                msg = (_('Trying to import backup metadata from id %(meta_id)s'
                         ' into backup %(id)s.') % {
                             'meta_id': backup_id,
                             'id': backup.id
                         })
                volume_utils.update_backup_error(backup, msg)
                raise exception.InvalidBackup(reason=msg)

            # Overwrite some fields
            backup_options['service'] = self.driver_name
            backup_options['availability_zone'] = self.az
            backup_options['host'] = self.host

            # Remove some values which are not actual fields and some that
            # were set by the API node
            for key in ('name', 'user_id', 'project_id', 'deleted_at',
                        'deleted', 'fail_reason', 'status'):
                backup_options.pop(key, None)

            # Update the database
            backup.update(backup_options)
            backup.save()

            # Update the backup's status
            backup.update({"status": fields.BackupStatus.AVAILABLE})
            backup.save()

            LOG.info('Import record id %s metadata from driver '
                     'finished.', backup.id)
Beispiel #6
0
    def delete_backup(self, context, backup):
        """Delete volume backup from configured backup service."""
        LOG.info('Delete backup started, backup: %s.', backup.id)

        self._notify_about_backup_usage(context, backup, "delete.start")

        expected_status = fields.BackupStatus.DELETING
        actual_status = backup.status
        if actual_status != expected_status:
            err = _('Delete_backup aborted, expected backup status '
                    '%(expected_status)s but got %(actual_status)s.') \
                % {'expected_status': expected_status,
                   'actual_status': actual_status}
            volume_utils.update_backup_error(backup, err)
            raise exception.InvalidBackup(reason=err)

        if backup.service and not self.is_working():
            err = _('Delete backup is aborted due to backup service is down.')
            status = fields.BackupStatus.ERROR_DELETING
            volume_utils.update_backup_error(backup, err, status)
            raise exception.InvalidBackup(reason=err)

        if not self._is_our_backup(backup):
            err = _('Delete backup aborted, the backup service currently'
                    ' configured [%(configured_service)s] is not the'
                    ' backup service that was used to create this'
                    ' backup [%(backup_service)s].')\
                % {'configured_service': self.driver_name,
                   'backup_service': backup.service}
            volume_utils.update_backup_error(backup, err)
            raise exception.InvalidBackup(reason=err)

        if backup.service:
            try:
                backup_service = self.service(context)
                backup_service.delete_backup(backup)
            except Exception as err:
                with excutils.save_and_reraise_exception():
                    volume_utils.update_backup_error(backup, str(err))

        # Get reservations
        try:
            reserve_opts = {
                'backups': -1,
                'backup_gigabytes': -backup.size,
            }
            reservations = QUOTAS.reserve(context,
                                          project_id=backup.project_id,
                                          **reserve_opts)
        except Exception:
            reservations = None
            LOG.exception("Failed to update usages deleting backup")

        if backup.encryption_key_id is not None:
            volume_utils.delete_encryption_key(context, key_manager.API(CONF),
                                               backup.encryption_key_id)
            backup.encryption_key_id = None
            backup.save()

        backup.destroy()
        # If this backup is incremental backup, handle the
        # num_dependent_backups of parent backup
        if backup.parent_id:
            parent_backup = objects.Backup.get_by_id(context, backup.parent_id)
            if parent_backup.has_dependent_backups:
                parent_backup.num_dependent_backups -= 1
                parent_backup.save()
        # Commit the reservations
        if reservations:
            QUOTAS.commit(context, reservations, project_id=backup.project_id)

        LOG.info('Delete backup finished, backup %s deleted.', backup.id)
        self._notify_about_backup_usage(context, backup, "delete.end")
Beispiel #7
0
    def restore_backup(self, context, backup, volume_id):
        """Restore volume backups from configured backup service."""
        LOG.info(
            'Restore backup started, backup: %(backup_id)s '
            'volume: %(volume_id)s.', {
                'backup_id': backup.id,
                'volume_id': volume_id
            })

        volume = objects.Volume.get_by_id(context, volume_id)
        self._notify_about_backup_usage(context, backup, "restore.start")

        expected_status = [
            fields.VolumeStatus.RESTORING_BACKUP, fields.VolumeStatus.CREATING
        ]
        volume_previous_status = volume['status']
        if volume_previous_status not in expected_status:
            err = (_('Restore backup aborted, expected volume status '
                     '%(expected_status)s but got %(actual_status)s.') % {
                         'expected_status': ','.join(expected_status),
                         'actual_status': volume_previous_status
                     })
            backup.status = fields.BackupStatus.AVAILABLE
            backup.save()
            self.db.volume_update(
                context, volume_id, {
                    'status':
                    (fields.VolumeStatus.ERROR
                     if volume_previous_status == fields.VolumeStatus.CREATING
                     else fields.VolumeStatus.ERROR_RESTORING)
                })
            raise exception.InvalidVolume(reason=err)

        expected_status = fields.BackupStatus.RESTORING
        actual_status = backup['status']
        if actual_status != expected_status:
            err = (_('Restore backup aborted: expected backup status '
                     '%(expected_status)s but got %(actual_status)s.') % {
                         'expected_status': expected_status,
                         'actual_status': actual_status
                     })
            volume_utils.update_backup_error(backup, err)
            self.db.volume_update(context, volume_id,
                                  {'status': fields.VolumeStatus.ERROR})
            raise exception.InvalidBackup(reason=err)

        if volume['size'] > backup['size']:
            LOG.info(
                'Volume: %(vol_id)s, size: %(vol_size)d is '
                'larger than backup: %(backup_id)s, '
                'size: %(backup_size)d, continuing with restore.', {
                    'vol_id': volume['id'],
                    'vol_size': volume['size'],
                    'backup_id': backup['id'],
                    'backup_size': backup['size']
                })

        if not self._is_our_backup(backup):
            err = _('Restore backup aborted, the backup service currently'
                    ' configured [%(configured_service)s] is not the'
                    ' backup service that was used to create this'
                    ' backup [%(backup_service)s].') % {
                        'configured_service': self.driver_name,
                        'backup_service': backup.service,
                    }
            backup.status = fields.BackupStatus.AVAILABLE
            backup.save()
            self.db.volume_update(context, volume_id,
                                  {'status': fields.VolumeStatus.ERROR})
            raise exception.InvalidBackup(reason=err)

        canceled = False
        try:
            self._run_restore(context, backup, volume)
        except exception.BackupRestoreCancel:
            canceled = True
        except Exception:
            with excutils.save_and_reraise_exception():
                self.db.volume_update(
                    context, volume_id, {
                        'status':
                        (fields.VolumeStatus.ERROR
                         if actual_status == fields.VolumeStatus.CREATING else
                         fields.VolumeStatus.ERROR_RESTORING)
                    })
                backup.status = fields.BackupStatus.AVAILABLE
                backup.save()

        if canceled:
            volume.status = fields.VolumeStatus.ERROR
        else:
            volume.status = fields.VolumeStatus.AVAILABLE
            # NOTE(tommylikehu): If previous status is 'creating', this is
            # just a new created volume and we need update the 'launched_at'
            # attribute as well.
            if volume_previous_status == fields.VolumeStatus.CREATING:
                volume['launched_at'] = timeutils.utcnow()
        old_src_backup_id = self.db.volume_metadata_get(
            context, volume_id).get("src_backup_id", None)
        if backup.volume_id != volume.id or (old_src_backup_id and
                                             old_src_backup_id != backup.id):
            self.db.volume_metadata_update(context, volume.id,
                                           {'src_backup_id': backup.id}, False)

        volume.save()
        backup.status = fields.BackupStatus.AVAILABLE
        backup.save()
        LOG.info(
            '%(result)s restoring backup %(backup_id)s to volume '
            '%(volume_id)s.', {
                'result': 'Canceled' if canceled else 'Finished',
                'backup_id': backup.id,
                'volume_id': volume_id
            })
        self._notify_about_backup_usage(context, backup, "restore.end")
Beispiel #8
0
    def create_backup(self, context, backup):
        """Create volume backups using configured backup service."""
        volume_id = backup.volume_id
        snapshot_id = backup.snapshot_id
        volume = objects.Volume.get_by_id(context, volume_id)
        snapshot = objects.Snapshot.get_by_id(
            context, snapshot_id) if snapshot_id else None
        previous_status = volume.get('previous_status', None)
        updates = {}
        if snapshot_id:
            log_message = ('Create backup started, backup: %(backup_id)s '
                           'volume: %(volume_id)s snapshot: %(snapshot_id)s.' %
                           {
                               'backup_id': backup.id,
                               'volume_id': volume_id,
                               'snapshot_id': snapshot_id
                           })
        else:
            log_message = ('Create backup started, backup: %(backup_id)s '
                           'volume: %(volume_id)s.' % {
                               'backup_id': backup.id,
                               'volume_id': volume_id
                           })
        LOG.info(log_message)

        self._notify_about_backup_usage(context, backup, "create.start")

        expected_status = "backing-up"
        if snapshot_id:
            actual_status = snapshot['status']
            if actual_status != expected_status:
                err = _('Create backup aborted, expected snapshot status '
                        '%(expected_status)s but got %(actual_status)s.') % {
                            'expected_status': expected_status,
                            'actual_status': actual_status,
                        }
                volume_utils.update_backup_error(backup, err)
                raise exception.InvalidSnapshot(reason=err)
        else:
            actual_status = volume['status']
            if actual_status != expected_status:
                err = _('Create backup aborted, expected volume status '
                        '%(expected_status)s but got %(actual_status)s.') % {
                            'expected_status': expected_status,
                            'actual_status': actual_status,
                        }
                volume_utils.update_backup_error(backup, err)
                raise exception.InvalidVolume(reason=err)

        expected_status = fields.BackupStatus.CREATING
        actual_status = backup.status
        if actual_status != expected_status:
            err = _('Create backup aborted, expected backup status '
                    '%(expected_status)s but got %(actual_status)s.') % {
                        'expected_status': expected_status,
                        'actual_status': actual_status,
                    }
            volume_utils.update_backup_error(backup, err)
            raise exception.InvalidBackup(reason=err)

        try:
            if not self.is_working():
                err = _('Create backup aborted due to backup service is down.')
                volume_utils.update_backup_error(backup, err)
                raise exception.InvalidBackup(reason=err)

            backup.service = self.driver_name
            backup.save()
            updates = self._run_backup(context, backup, volume)
        except Exception as err:
            with excutils.save_and_reraise_exception():
                if snapshot_id:
                    snapshot.status = fields.SnapshotStatus.AVAILABLE
                    snapshot.save()
                else:
                    self.db.volume_update(
                        context, volume_id, {
                            'status': previous_status,
                            'previous_status': 'error_backing-up'
                        })
                volume_utils.update_backup_error(backup, str(err))

        # Restore the original status.
        if snapshot_id:
            self.db.snapshot_update(
                context, snapshot_id,
                {'status': fields.SnapshotStatus.AVAILABLE})
        else:
            self.db.volume_update(context, volume_id, {
                'status': previous_status,
                'previous_status': 'backing-up'
            })

        # _run_backup method above updated the status for the backup, so it
        # will reflect latest status, even if it is deleted
        completion_msg = 'finished'
        if backup.status in (fields.BackupStatus.DELETING,
                             fields.BackupStatus.DELETED):
            completion_msg = 'aborted'
        else:
            backup.status = fields.BackupStatus.AVAILABLE
            backup.size = volume['size']

            if updates:
                backup.update(updates)
            backup.save()

            # Handle the num_dependent_backups of parent backup when child
            # backup has created successfully.
            if backup.parent_id:
                parent_backup = objects.Backup.get_by_id(
                    context, backup.parent_id)
                parent_backup.num_dependent_backups += 1
                parent_backup.save()
        LOG.info('Create backup %s. backup: %s.', completion_msg, backup.id)
        self._notify_about_backup_usage(context, backup, "create.end")