trove/trove/guestagent/backup/backupagent.py

179 lines
7.5 KiB
Python

# Copyright 2013 Hewlett-Packard Development Company, L.P.
# 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.
#
from oslo_log import log as logging
from oslo_utils import timeutils
from trove.backup.state import BackupState
from trove.common import cfg
from trove.common.i18n import _
from trove.common.strategies.storage import get_storage_strategy
from trove.conductor import api as conductor_api
from trove.guestagent.dbaas import get_filesystem_volume_stats
from trove.guestagent.strategies.backup.base import BackupError
from trove.guestagent.strategies.backup.base import UnknownBackupType
from trove.guestagent.strategies.backup import get_backup_strategy
from trove.guestagent.strategies.restore import get_restore_strategy
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
CONFIG_MANAGER = CONF.get('mysql'
if not CONF.datastore_manager
else CONF.datastore_manager)
STRATEGY = CONFIG_MANAGER.backup_strategy
BACKUP_NAMESPACE = CONFIG_MANAGER.backup_namespace
RESTORE_NAMESPACE = CONFIG_MANAGER.restore_namespace
RUNNER = get_backup_strategy(STRATEGY, BACKUP_NAMESPACE)
EXTRA_OPTS = CONF.backup_runner_options.get(STRATEGY, '')
# Try to get the incremental strategy or return the default 'backup_strategy'
INCREMENTAL = CONFIG_MANAGER.backup_incremental_strategy.get(
STRATEGY, STRATEGY)
INCREMENTAL_RUNNER = get_backup_strategy(INCREMENTAL, BACKUP_NAMESPACE)
class BackupAgent(object):
def _get_restore_runner(self, backup_type):
"""Returns the RestoreRunner associated with this backup type."""
try:
runner = get_restore_strategy(backup_type, RESTORE_NAMESPACE)
except ImportError:
raise UnknownBackupType(_("Unknown Backup type: %(type)s in "
"namespace %(ns)s")
% {"type": backup_type,
"ns": RESTORE_NAMESPACE})
return runner
def stream_backup_to_storage(self, context, backup_info, runner, storage,
parent_metadata={}, extra_opts=EXTRA_OPTS):
backup_id = backup_info['id']
conductor = conductor_api.API(context)
# Store the size of the filesystem before the backup.
mount_point = CONFIG_MANAGER.mount_point
stats = get_filesystem_volume_stats(mount_point)
backup_state = {
'backup_id': backup_id,
'size': stats.get('used', 0.0),
'state': BackupState.BUILDING,
}
conductor.update_backup(CONF.guest_id,
sent=timeutils.utcnow_ts(microsecond=True),
**backup_state)
LOG.debug("Updated state for %s to %s.", backup_id, backup_state)
try:
with runner(filename=backup_id, extra_opts=extra_opts,
**parent_metadata) as bkup:
LOG.debug("Starting backup %s.", backup_id)
meta = {}
meta['datastore'] = backup_info['datastore']
meta['datastore_version'] = backup_info['datastore_version']
success, note, checksum, location = storage.save(
bkup.manifest,
bkup,
metadata=meta)
backup_state.update({
'checksum': checksum,
'location': location,
'note': note,
'success': success,
'backup_type': bkup.backup_type,
})
LOG.debug("Backup %(backup_id)s completed status: "
"%(success)s.", backup_state)
LOG.debug("Backup %(backup_id)s file swift checksum: "
"%(checksum)s.", backup_state)
LOG.debug("Backup %(backup_id)s location: "
"%(location)s.", backup_state)
if not success:
raise BackupError(note)
backup_state.update({'state': BackupState.COMPLETED})
return meta
except Exception:
LOG.exception(
"Error saving backup: %(backup_id)s.", backup_state)
backup_state.update({'state': BackupState.FAILED})
raise
finally:
LOG.info("Completed backup %(backup_id)s.", backup_state)
conductor.update_backup(CONF.guest_id,
sent=timeutils.utcnow_ts(
microsecond=True),
**backup_state)
LOG.info("Updated state for %s to %s.", backup_id, backup_state)
def execute_backup(self, context, backup_info,
runner=RUNNER, extra_opts=EXTRA_OPTS,
incremental_runner=INCREMENTAL_RUNNER):
LOG.info("Running backup %(id)s.", backup_info)
storage = get_storage_strategy(
CONF.storage_strategy,
CONF.storage_namespace)(context)
# Check if this is an incremental backup and grab the parent metadata
parent_metadata = {}
if backup_info.get('parent'):
runner = incremental_runner
LOG.debug("Using incremental backup runner: %s.", runner.__name__)
parent = backup_info['parent']
parent_metadata = storage.load_metadata(parent['location'],
parent['checksum'])
# The parent could be another incremental backup so we need to
# reset the location and checksum to *this* parents info
parent_metadata.update({
'parent_location': parent['location'],
'parent_checksum': parent['checksum']
})
self.stream_backup_to_storage(context, backup_info, runner, storage,
parent_metadata, extra_opts)
def execute_restore(self, context, backup_info, restore_location):
try:
restore_runner = self._get_restore_runner(backup_info['type'])
storage = get_storage_strategy(
CONF.storage_strategy,
CONF.storage_namespace)(context)
runner = restore_runner(storage, location=backup_info['location'],
checksum=backup_info['checksum'],
restore_location=restore_location)
backup_info['restore_location'] = restore_location
LOG.info("Restoring instance from backup %(id)s to "
"%(restore_location)s", backup_info)
content_size = runner.restore()
LOG.info("Restore from backup %(id)s completed successfully "
"to %(restore_location)s", backup_info)
LOG.debug("Restore size: %s", content_size)
except Exception:
LOG.exception("Error restoring backup %(id)s", backup_info)
raise
else:
LOG.debug("Restored backup %(id)s", backup_info)