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)
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)
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()
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)
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)
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")
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")
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")