"""
from datetime import datetime
-
from eventlet import greenthread
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import excutils
from oslo_utils import strutils
from pytz import timezone
+import random
from cinder.backup import rpcapi as backup_rpcapi
from cinder import context
from cinder import quota
from cinder import utils
import cinder.volume
-from cinder.volume import utils as volume_utils
+
+backup_api_opts = [
+ cfg.BoolOpt('backup_use_same_backend',
+ default=False,
+ help='Backup services use same backend.')
+]
CONF = cfg.CONF
+CONF.register_opts(backup_api_opts)
LOG = logging.getLogger(__name__)
QUOTAS = quota.QUOTAS
backup.host):
msg = _('force delete')
raise exception.NotSupportedOperation(operation=msg)
- if not self._is_backup_service_enabled(backup['availability_zone'],
- backup.host):
- raise exception.ServiceNotFound(service_id='cinder-backup')
# Don't allow backup to be deleted if there are incremental
# backups dependent on it.
raise exception.InvalidBackup(reason=msg)
backup.status = fields.BackupStatus.DELETING
+ backup.host = self._get_available_backup_service_host(
+ backup.host, backup.availability_zone)
backup.save()
self.backup_rpcapi.delete_backup(context, backup)
return backups
+ def _az_matched(self, service, availability_zone):
+ return ((not availability_zone) or
+ service.availability_zone == availability_zone)
+
def _is_backup_service_enabled(self, availability_zone, host):
"""Check if there is a backup service available."""
topic = CONF.backup_topic
services = objects.ServiceList.get_all_by_topic(
ctxt, topic, disabled=False)
for srv in services:
- if (srv.availability_zone == availability_zone and
+ if (self._az_matched(srv, availability_zone) and
srv.host == host and
utils.service_is_up(srv)):
return True
return False
+ def _get_any_available_backup_service(self, availability_zone):
+ """Get an available backup service host.
+
+ Get an available backup service host in the specified
+ availability zone.
+ """
+ services = [srv for srv in self._list_backup_services()]
+ random.shuffle(services)
+ # Get the next running service with matching availability zone.
+ idx = 0
+ while idx < len(services):
+ srv = services[idx]
+ if(self._az_matched(srv, availability_zone) and
+ utils.service_is_up(srv)):
+ return srv.host
+ idx = idx + 1
+ return None
+
+ def _get_available_backup_service_host(self, host, availability_zone):
+ """Return an appropriate backup service host."""
+ backup_host = None
+ if host and self._is_backup_service_enabled(availability_zone, host):
+ backup_host = host
+ if not backup_host and (not host or CONF.backup_use_same_backend):
+ backup_host = self._get_any_available_backup_service(
+ availability_zone)
+ if not backup_host:
+ raise exception.ServiceNotFound(service_id='cinder-backup')
+ return backup_host
+
def _list_backup_services(self):
"""List all enabled backup services.
"""
topic = CONF.backup_topic
ctxt = context.get_admin_context()
- services = objects.ServiceList.get_all_by_topic(ctxt, topic)
- return [srv.host for srv in services if not srv.disabled]
+ services = objects.ServiceList.get_all_by_topic(
+ ctxt, topic, disabled=False)
+ return services
+
+ def _list_backup_hosts(self):
+ services = self._list_backup_services()
+ return [srv.host for srv in services
+ if not srv.disabled and utils.service_is_up(srv)]
def create(self, context, name, description, volume_id,
container, incremental=False, availability_zone=None,
raise exception.InvalidSnapshot(reason=msg)
previous_status = volume['status']
- volume_host = volume_utils.extract_host(volume['host'], 'host')
- if not self._is_backup_service_enabled(volume['availability_zone'],
- volume_host):
- raise exception.ServiceNotFound(service_id='cinder-backup')
+ host = self._get_available_backup_service_host(
+ None, volume.availability_zone)
# Reserve a quota before setting volume status and backup status
try:
'container': container,
'parent_id': parent_id,
'size': volume['size'],
- 'host': volume_host,
+ 'host': host,
'snapshot_id': snapshot_id,
'data_timestamp': data_timestamp,
}
# Setting the status here rather than setting at start and unrolling
# for each error condition, it should be a very small window
+ backup.host = self._get_available_backup_service_host(
+ backup.host, backup.availability_zone)
backup.status = fields.BackupStatus.RESTORING
backup.restore_volume_id = volume.id
backup.save()
- volume_host = volume_utils.extract_host(volume.host, 'host')
self.db.volume_update(context, volume_id, {'status':
'restoring-backup'})
- self.backup_rpcapi.restore_backup(context, volume_host, backup,
+ self.backup_rpcapi.restore_backup(context, backup.host, backup,
volume_id)
d = {'backup_id': backup_id,
"""
# get backup info
backup = self.get(context, backup_id)
+ backup.host = self._get_available_backup_service_host(
+ backup.host, backup.availability_zone)
+ backup.save()
# send to manager to do reset operation
self.backup_rpcapi.reset_status(ctxt=context, backup=backup,
status=status)
{'ctx': context,
'host': backup['host'],
'id': backup['id']})
+
+ backup.host = self._get_available_backup_service_host(
+ backup.host, backup.availability_zone)
+ backup.save()
export_data = self.backup_rpcapi.export_record(context, backup)
return export_data
# We send it to the first backup service host, and the backup manager
# on that host will forward it to other hosts on the hosts list if it
# cannot support correct service itself.
- hosts = self._list_backup_services()
+ hosts = self._list_backup_hosts()
if len(hosts) == 0:
raise exception.ServiceNotFound(service_id=backup_service)
from cinder import quota
from cinder import rpc
from cinder import utils
+from cinder.volume import rpcapi as volume_rpcapi
from cinder.volume import utils as volume_utils
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
CONF.register_opts(backup_manager_opts)
+CONF.import_opt('use_multipath_for_image_xfer', 'cinder.volume.driver')
+CONF.import_opt('num_volume_device_scan_tries', 'cinder.volume.driver')
QUOTAS = quota.QUOTAS
self.service = importutils.import_module(self.driver_name)
self.az = CONF.storage_availability_zone
self.volume_managers = {}
- self._setup_volume_drivers()
self.backup_rpcapi = backup_rpcapi.BackupAPI()
+ self.volume_rpcapi = volume_rpcapi.VolumeAPI()
super(BackupManager, self).__init__(service_name='backup',
*args, **kwargs)
return mapper[service]
return service
- @property
- def driver(self):
- return self._get_driver()
-
- def _get_volume_backend(self, host=None, allow_null_host=False):
- if host is None:
- if not allow_null_host:
- msg = _("NULL host not allowed for volume backend lookup.")
- raise exception.BackupFailedToGetVolumeBackend(msg)
- else:
- LOG.debug("Checking hostname '%s' for backend info.", host)
- part = host.partition('@')
- if (part[1] == '@') and (part[2] != ''):
- backend = part[2]
- LOG.debug("Got backend '%s'.", backend)
- return backend
-
- LOG.info(_LI("Backend not found in hostname (%s) so using default."),
- host)
-
- if 'default' not in self.volume_managers:
- # For multi-backend we just pick "first" from volume managers dict
- return next(iter(self.volume_managers))
-
- return 'default'
-
- def _get_manager(self, backend):
- LOG.debug("Manager requested for volume_backend '%s'.",
- backend)
- if backend is None:
- LOG.debug("Fetching default backend.")
- backend = self._get_volume_backend(allow_null_host=True)
- if backend not in self.volume_managers:
- msg = (_("Volume manager for backend '%s' does not exist.") %
- (backend))
- raise exception.BackupFailedToGetVolumeBackend(msg)
- return self.volume_managers[backend]
-
- def _get_driver(self, backend=None):
- LOG.debug("Driver requested for volume_backend '%s'.",
- backend)
- if backend is None:
- LOG.debug("Fetching default backend.")
- backend = self._get_volume_backend(allow_null_host=True)
- mgr = self._get_manager(backend)
- mgr.driver.db = self.db
- return mgr.driver
-
- def _setup_volume_drivers(self):
- if CONF.enabled_backends:
- for backend in CONF.enabled_backends:
- host = "%s@%s" % (CONF.host, backend)
- mgr = importutils.import_object(CONF.volume_manager,
- host=host,
- service_name=backend)
- config = mgr.configuration
- backend_name = config.safe_get('volume_backend_name')
- LOG.debug("Registering backend %(backend)s (host=%(host)s "
- "backend_name=%(backend_name)s).",
- {'backend': backend, 'host': host,
- 'backend_name': backend_name})
- self.volume_managers[backend] = mgr
- else:
- default = importutils.import_object(CONF.volume_manager)
- LOG.debug("Registering default backend %s.", default)
- self.volume_managers['default'] = default
-
- def _init_volume_driver(self, ctxt, driver):
- LOG.info(_LI("Starting volume driver %(driver_name)s (%(version)s)."),
- {'driver_name': driver.__class__.__name__,
- 'version': driver.get_version()})
- try:
- driver.do_setup(ctxt)
- driver.check_for_setup_error()
- except Exception:
- LOG.exception(_LE("Error encountered during initialization of "
- "driver: %(name)s."),
- {'name': driver.__class__.__name__})
- # we don't want to continue since we failed
- # to initialize the driver correctly.
- return
-
- driver.set_initialized()
-
def _update_backup_error(self, backup, context, err):
backup.status = fields.BackupStatus.ERROR
backup.fail_reason = err
"""Run initialization needed for a standalone service."""
ctxt = context.get_admin_context()
- for mgr in self.volume_managers.values():
- self._init_volume_driver(ctxt, mgr.driver)
-
try:
self._cleanup_incomplete_backup_operations(ctxt)
except Exception:
def _cleanup_incomplete_backup_operations(self, ctxt):
LOG.info(_LI("Cleaning up incomplete backup operations."))
- volumes = self.db.volume_get_all_by_host(ctxt, self.host)
-
- for volume in volumes:
- try:
- self._cleanup_one_volume(ctxt, volume)
- except Exception:
- LOG.exception(_LE("Problem cleaning up volume %(vol)s."),
- {'vol': volume['id']})
# TODO(smulcahy) implement full resume of backup and restore
# operations on restart (rather than simply resetting)
{'bkup': backup['id']})
def _cleanup_one_volume(self, ctxt, volume):
- volume_host = volume_utils.extract_host(volume['host'], 'backend')
- backend = self._get_volume_backend(host=volume_host)
- mgr = self._get_manager(backend)
if volume['status'] == 'backing-up':
- self._detach_all_attachments(ctxt, mgr, volume)
+ self._detach_all_attachments(ctxt, volume)
LOG.info(_LI('Resetting volume %(vol_id)s to previous '
'status %(status)s (was backing-up).'),
{'vol_id': volume['id'],
self.db.volume_update(ctxt, volume['id'],
{'status': volume['previous_status']})
elif volume['status'] == 'restoring-backup':
- self._detach_all_attachments(ctxt, mgr, volume)
+ self._detach_all_attachments(ctxt, volume)
LOG.info(_LI('setting volume %s to error_restoring '
'(was restoring-backup).'), volume['id'])
self.db.volume_update(ctxt, volume['id'],
if backup['status'] == fields.BackupStatus.CREATING:
LOG.info(_LI('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'
self._update_backup_error(backup, ctxt, err)
- if backup['status'] == fields.BackupStatus.RESTORING:
+ elif backup['status'] == fields.BackupStatus.RESTORING:
LOG.info(_LI('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()
- if backup['status'] == fields.BackupStatus.DELETING:
+ elif backup['status'] == fields.BackupStatus.DELETING:
LOG.info(_LI('Resuming delete on backup: %s.'), backup['id'])
if CONF.backup_service_inithost_offload:
# Offload all the pending backup delete operations to the
# By default, delete backups sequentially
self.delete_backup(ctxt, backup)
- def is_working(self):
- """Return if Manager is ready to accept requests.
-
- This is to inform Service class that in case of volume manager(s)
- initialization failure the manager is actually down and
- may not accept some or all requests.
- """
- return all(mgr.is_working() for mgr in self.volume_managers.values())
-
- def _detach_all_attachments(self, ctxt, mgr, volume):
+ def _detach_all_attachments(self, ctxt, volume):
attachments = volume['volume_attachment'] or []
for attachment in attachments:
if (attachment['attached_host'] == self.host and
attachment['instance_uuid'] is None):
try:
- mgr.detach_volume(ctxt, volume['id'],
- attachment['id'])
+ rpcapi = self.volume_rpcapi
+ rpcapi.detach_volume(ctxt, volume, attachment['id'])
except Exception:
LOG.exception(_LE("Detach attachment %(attach_id)s"
" failed."),
{'attach_id': attachment['id']},
resource=volume)
+ def _delete_temp_volume(self, ctxt, backup):
+ try:
+ temp_volume = objects.Volume.get_by_id(
+ ctxt, backup.temp_volume_id)
+ self.volume_rpcapi.delete_volume(ctxt, temp_volume)
+ except exception.VolumeNotFound:
+ LOG.debug("Could not find temp volume %(vol)s to clean up "
+ "for backup %(backup)s.",
+ {'vol': backup.temp_volume_id,
+ 'backup': backup.id})
+ backup.temp_volume_id = None
+ backup.save()
+
+ def _delete_temp_snapshot(self, ctxt, backup):
+ try:
+ temp_snapshot = objects.Snapshot.get_by_id(
+ ctxt, backup.temp_snapshot_id)
+ volume = objects.Volume.get_by_id(
+ ctxt, backup.volume_id)
+ # The temp snapshot should be deleted directly thru the
+ # volume driver, not thru the volume manager.
+ self.volume_rpcapi.delete_snapshot(ctxt, temp_snapshot,
+ volume.host)
+ except exception.SnapshotNotFound:
+ LOG.debug("Could not find temp snapshot %(snap)s to clean "
+ "up for backup %(backup)s.",
+ {'snap': backup.temp_snapshot_id,
+ 'backup': backup.id})
+ backup.temp_snapshot_id = None
+ backup.save()
+
def _cleanup_temp_volumes_snapshots_for_one_backup(self, ctxt, backup):
# NOTE(xyang): If the service crashes or gets restarted during the
# backup operation, there could be temporary volumes or snapshots
# that are not deleted. Make sure any temporary volumes or snapshots
# create by the backup job are deleted when service is started.
- try:
- volume = self.db.volume_get(ctxt, backup.volume_id)
- volume_host = volume_utils.extract_host(volume['host'],
- 'backend')
- backend = self._get_volume_backend(host=volume_host)
- mgr = self._get_manager(backend)
- except (KeyError, exception.VolumeNotFound):
- LOG.debug("Could not find a volume to clean up for "
- "backup %s.", backup.id)
- return
-
if (backup.temp_volume_id
and backup.status == fields.BackupStatus.ERROR):
- try:
- temp_volume = self.db.volume_get(ctxt,
- backup.temp_volume_id)
- # The temp volume should be deleted directly thru the
- # the volume driver, not thru the volume manager.
- mgr.driver.delete_volume(temp_volume)
- self.db.volume_destroy(ctxt, temp_volume['id'])
- except exception.VolumeNotFound:
- LOG.debug("Could not find temp volume %(vol)s to clean up "
- "for backup %(backup)s.",
- {'vol': backup.temp_volume_id,
- 'backup': backup.id})
- backup.temp_volume_id = None
- backup.save()
+ self._delete_temp_volume(ctxt, backup)
if (backup.temp_snapshot_id
and backup.status == fields.BackupStatus.ERROR):
- try:
- temp_snapshot = objects.Snapshot.get_by_id(
- ctxt, backup.temp_snapshot_id)
- # The temp snapshot should be deleted directly thru the
- # volume driver, not thru the volume manager.
- mgr.driver.delete_snapshot(temp_snapshot)
- with temp_snapshot.obj_as_admin():
- self.db.volume_glance_metadata_delete_by_snapshot(
- ctxt, temp_snapshot.id)
- temp_snapshot.destroy()
- except exception.SnapshotNotFound:
- LOG.debug("Could not find temp snapshot %(snap)s to clean "
- "up for backup %(backup)s.",
- {'snap': backup.temp_snapshot_id,
- 'backup': backup.id})
- backup.temp_snapshot_id = None
- backup.save()
+ self._delete_temp_snapshot(ctxt, backup)
+
+ def _cleanup_temp_volumes_snapshots_when_backup_created(
+ self, ctxt, backup):
+ # Delete temp volumes or snapshots when backup creation is completed.
+ if backup.temp_volume_id:
+ self._delete_temp_volume(ctxt, backup)
+
+ if backup.temp_snapshot_id:
+ self._delete_temp_snapshot(ctxt, backup)
def create_backup(self, context, backup):
"""Create volume backups using configured backup service."""
volume_id = backup.volume_id
- volume = self.db.volume_get(context, volume_id)
+ volume = objects.Volume.get_by_id(context, volume_id)
previous_status = volume.get('previous_status', None)
LOG.info(_LI('Create backup started, backup: %(backup_id)s '
'volume: %(volume_id)s.'),
{'backup_id': backup.id, 'volume_id': volume_id})
self._notify_about_backup_usage(context, backup, "create.start")
- volume_host = volume_utils.extract_host(volume['host'], 'backend')
- backend = self._get_volume_backend(host=volume_host)
backup.host = self.host
backup.service = self.driver_name
+ backup.availability_zone = self.az
backup.save()
expected_status = 'backing-up'
raise exception.InvalidBackup(reason=err)
try:
- # NOTE(flaper87): Verify the driver is enabled
- # before going forward. The exception will be caught,
- # the volume status will be set back to available and
- # the backup status to 'error'
- utils.require_driver_initialized(self._get_driver(backend))
-
- backup_service = self.service.get_backup_driver(context)
- self._get_driver(backend).backup_volume(context, backup,
- backup_service)
+ self._run_backup(context, backup, volume)
except Exception as err:
with excutils.save_and_reraise_exception():
self.db.volume_update(context, volume_id,
'previous_status': 'backing-up'})
backup.status = fields.BackupStatus.AVAILABLE
backup.size = volume['size']
- backup.availability_zone = self.az
backup.save()
+
# Handle the num_dependent_backups of parent backup when child backup
# has created successfully.
if backup.parent_id:
LOG.info(_LI('Create backup finished. backup: %s.'), backup.id)
self._notify_about_backup_usage(context, backup, "create.end")
+ def _run_backup(self, context, backup, volume):
+ backup_service = self.service.get_backup_driver(context)
+
+ properties = utils.brick_get_connector_properties()
+ backup_dic = self.volume_rpcapi.get_backup_device(context,
+ backup, volume)
+ try:
+ backup_device = backup_dic.get('backup_device')
+ is_snapshot = backup_dic.get('is_snapshot')
+ attach_info = self._attach_device(context, backup_device,
+ properties, is_snapshot)
+ try:
+ device_path = attach_info['device']['path']
+ if isinstance(device_path, six.string_types):
+ if backup_dic.get('secure_enabled', False):
+ with open(device_path) as device_file:
+ backup_service.backup(backup, device_file)
+ else:
+ with utils.temporary_chown(device_path):
+ with open(device_path) as device_file:
+ backup_service.backup(backup, device_file)
+ else:
+ backup_service.backup(backup, device_path)
+
+ finally:
+ self._detach_device(context, attach_info,
+ backup_device, properties,
+ is_snapshot)
+ finally:
+ backup = objects.Backup.get_by_id(context, backup.id)
+ self._cleanup_temp_volumes_snapshots_when_backup_created(
+ context, backup)
+
def restore_backup(self, context, backup, volume_id):
"""Restore volume backups from configured backup service."""
LOG.info(_LI('Restore backup started, backup: %(backup_id)s '
'volume: %(volume_id)s.'),
{'backup_id': backup.id, 'volume_id': volume_id})
- volume = self.db.volume_get(context, volume_id)
- volume_host = volume_utils.extract_host(volume['host'], 'backend')
- backend = self._get_volume_backend(host=volume_host)
+ volume = objects.Volume.get_by_id(context, volume_id)
self._notify_about_backup_usage(context, backup, "restore.start")
backup.host = self.host
raise exception.InvalidBackup(reason=err)
try:
- # NOTE(flaper87): Verify the driver is enabled
- # before going forward. The exception will be caught,
- # the volume status will be set back to available and
- # the backup status to 'error'
- utils.require_driver_initialized(self._get_driver(backend))
-
- backup_service = self.service.get_backup_driver(context)
- self._get_driver(backend).restore_backup(context, backup,
- volume,
- backup_service)
+ self._run_restore(context, backup, volume)
except Exception:
with excutils.save_and_reraise_exception():
self.db.volume_update(context, volume_id,
{'backup_id': backup.id, 'volume_id': volume_id})
self._notify_about_backup_usage(context, backup, "restore.end")
+ def _run_restore(self, context, backup, volume):
+ backup_service = self.service.get_backup_driver(context)
+
+ properties = utils.brick_get_connector_properties()
+ secure_enabled = (
+ self.volume_rpcapi.secure_file_operations_enabled(context,
+ volume))
+ attach_info = self._attach_device(context, volume, properties)
+ try:
+ device_path = attach_info['device']['path']
+ if isinstance(device_path, six.string_types):
+ if secure_enabled:
+ with open(device_path, 'wb') as device_file:
+ backup_service.restore(backup, volume.id, device_file)
+ else:
+ with utils.temporary_chown(device_path):
+ with open(device_path, 'wb') as device_file:
+ backup_service.restore(backup, volume.id,
+ device_file)
+ else:
+ backup_service.restore(backup, volume.id, device_path)
+ finally:
+ self._detach_device(context, attach_info, volume, properties)
+
def delete_backup(self, context, backup):
"""Delete volume backup from configured backup service."""
LOG.info(_LI('Delete backup started, backup: %s.'), backup.id)
- try:
- # NOTE(flaper87): Verify the driver is enabled
- # before going forward. The exception will be caught
- # and the backup status updated. Fail early since there
- # are no other status to change but backup's
- utils.require_driver_initialized(self.driver)
- except exception.DriverNotInitialized as err:
- with excutils.save_and_reraise_exception():
- self._update_backup_error(backup, context, six.text_type(err))
-
self._notify_about_backup_usage(context, backup, "delete.start")
backup.host = self.host
backup.save()
# Call driver to create backup description string
try:
- utils.require_driver_initialized(self.driver)
backup_service = self.service.get_backup_driver(context)
driver_info = backup_service.export_record(backup)
backup_url = backup.encode_record(driver_info=driver_info)
# Extract driver specific info and pass it to the driver
driver_options = backup_options.pop('driver_info', {})
- utils.require_driver_initialized(self.driver)
backup_service = self.service.get_backup_driver(context)
backup_service.import_record(backup, driver_options)
except Exception as err:
'%(backup_id)s, status: %(status)s.'),
{'backup_id': backup.id,
'status': status})
- try:
- # NOTE(flaper87): Verify the driver is enabled
- # before going forward. The exception will be caught
- # and the backup status updated. Fail early since there
- # are no other status to change but backup's
- utils.require_driver_initialized(self.driver)
- except exception.DriverNotInitialized:
- with excutils.save_and_reraise_exception():
- LOG.exception(_LE("Backup driver has not been initialized"))
backup_service = self._map_service_to_driver(backup.service)
LOG.info(_LI('Backup service: %s.'), backup_service)
"""
backup_service = self.service.get_backup_driver(context)
return backup_service.support_force_delete
+
+ def _attach_device(self, context, backup_device,
+ properties, is_snapshot=False):
+ """Attach backup device."""
+ if not is_snapshot:
+ return self._attach_volume(context, backup_device, properties)
+ else:
+ msg = _("Can't attach snapshot.")
+ raise NotImplementedError(msg)
+
+ def _attach_volume(self, context, volume, properties):
+ """Attach a volume."""
+
+ try:
+ conn = self.volume_rpcapi.initialize_connection(context,
+ volume,
+ properties)
+ return self._connect_device(conn)
+ except Exception:
+ with excutils.save_and_reraise_exception():
+ try:
+ self.volume_rpcapi.terminate_connection(context, volume,
+ properties,
+ force=True)
+ except Exception:
+ LOG.warning(_LW("Failed to terminate the connection "
+ "of volume %(volume_id)s, but it is "
+ "acceptable."),
+ {'volume_id', volume.id})
+
+ def _connect_device(self, conn):
+ """Establish connection to device."""
+ use_multipath = CONF.use_multipath_for_image_xfer
+ device_scan_attempts = CONF.num_volume_device_scan_tries
+ protocol = conn['driver_volume_type']
+ connector = utils.brick_get_connector(
+ protocol,
+ use_multipath=use_multipath,
+ device_scan_attempts=device_scan_attempts,
+ conn=conn)
+ vol_handle = connector.connect_volume(conn['data'])
+
+ return {'conn': conn, 'device': vol_handle, 'connector': connector}
+
+ def _detach_device(self, context, attach_info, volume,
+ properties, is_snapshot=False, force=False):
+ """Disconnect the volume from the host. """
+ connector = attach_info['connector']
+ connector.disconnect_volume(attach_info['conn']['data'],
+ attach_info['device'])
+
+ rpcapi = self.volume_rpcapi
+ rpcapi.terminate_connection(context, volume, properties, force=force)
+ rpcapi.remove_export(context, volume)
from cinder.api.middleware import sizelimit as cinder_api_middleware_sizelimit
from cinder.api.v2 import volumes as cinder_api_v2_volumes
from cinder.api.views import versions as cinder_api_views_versions
+from cinder.backup import api as cinder_backup_api
from cinder.backup import chunkeddriver as cinder_backup_chunkeddriver
from cinder.backup import driver as cinder_backup_driver
from cinder.backup.drivers import ceph as cinder_backup_drivers_ceph
cinder_volume_drivers_hitachi_hnasiscsi.iSCSI_OPTS,
cinder_volume_drivers_rbd.rbd_opts,
cinder_volume_drivers_tintri.tintri_opts,
+ cinder_backup_api.backup_api_opts,
cinder_volume_drivers_hitachi_hbsdhorcm.volume_opts,
cinder_backup_manager.backup_manager_opts,
cinder_volume_drivers_ibm_storwize_svc_storwizesvccommon.
req.headers['content-type'] = 'application/json'
req.body = jsonutils.dump_as_bytes({'os-reset_status': updated_status})
req.environ['cinder.context'] = ctx
- resp = req.get_response(app())
- return resp
+ with mock.patch('cinder.backup.api.API._is_backup_service_enabled') \
+ as mock_is_service_available:
+ mock_is_service_available.return_value = True
+ resp = req.get_response(app())
+ return resp
def test_valid_updates(self):
vac = admin_actions.VolumeAdminController()
'size': 1,
'volume_id': volume['id'],
'user_id': 'user',
- 'project_id': 'project'})
+ 'project_id': 'project',
+ 'host': 'test'})
resp = self._issue_backup_reset(self.ctx,
backup,
ctx = context.RequestContext('fake', 'fake')
backup = db.backup_create(ctx, {'status': 'available',
'size': 1,
- 'volume_id': "fakeid"})
+ 'volume_id': "fakeid",
+ 'host': 'test'})
resp = self._issue_backup_reset(ctx,
backup,
{'status': fields.BackupStatus.ERROR})
{'status': fields.BackupStatus.AVAILABLE,
'volume_id': volume['id'],
'user_id': 'user',
- 'project_id': 'project'})
+ 'project_id': 'project',
+ 'host': 'test'})
resp = self._issue_backup_reset(self.ctx,
backup,
def test_create_backup_json(self, mock_validate,
_mock_service_get_all_by_topic):
_mock_service_get_all_by_topic.return_value = [
- {'availability_zone': "fake_az", 'host': 'test_host',
+ {'availability_zone': 'fake_az', 'host': 'testhost',
'disabled': 0, 'updated_at': timeutils.utcnow()}]
volume_id = utils.create_volume(self.context, size=5)['id']
def test_create_backup_inuse_no_force(self,
_mock_service_get_all_by_topic):
_mock_service_get_all_by_topic.return_value = [
- {'availability_zone': "fake_az", 'host': 'test_host',
+ {'availability_zone': 'fake_az', 'host': 'testhost',
'disabled': 0, 'updated_at': timeutils.utcnow()}]
volume_id = utils.create_volume(self.context, size=5,
@mock.patch('cinder.db.service_get_all_by_topic')
def test_create_backup_inuse_force(self, _mock_service_get_all_by_topic):
_mock_service_get_all_by_topic.return_value = [
- {'availability_zone': "fake_az", 'host': 'test_host',
+ {'availability_zone': 'fake_az', 'host': 'testhost',
'disabled': 0, 'updated_at': timeutils.utcnow()}]
volume_id = utils.create_volume(self.context, size=5,
def test_create_backup_snapshot_json(self, mock_validate,
_mock_service_get_all_by_topic):
_mock_service_get_all_by_topic.return_value = [
- {'availability_zone': "fake_az", 'host': 'test_host',
+ {'availability_zone': 'fake_az', 'host': 'testhost',
'disabled': 0, 'updated_at': timeutils.utcnow()}]
volume_id = utils.create_volume(self.context, size=5,
def test_create_backup_xml(self, mock_validate,
_mock_service_get_all_by_topic):
_mock_service_get_all_by_topic.return_value = [
- {'availability_zone': "fake_az", 'host': 'test_host',
+ {'availability_zone': 'fake_az', 'host': 'testhost',
'disabled': 0, 'updated_at': timeutils.utcnow()}]
volume_id = utils.create_volume(self.context, size=2)['id']
mock_validate,
_mock_service_get_all_by_topic):
_mock_service_get_all_by_topic.return_value = [
- {'availability_zone': "fake_az", 'host': 'test_host',
+ {'availability_zone': 'fake_az', 'host': 'testhost',
'disabled': 0, 'updated_at': timeutils.utcnow()}]
volume_id = utils.create_volume(self.context, size=5)['id']
def test_create_incremental_backup_invalid_status(
self, _mock_service_get_all_by_topic):
_mock_service_get_all_by_topic.return_value = [
- {'availability_zone': "fake_az", 'host': 'test_host',
+ {'availability_zone': 'fake_az', 'host': 'testhost',
'disabled': 0, 'updated_at': timeutils.utcnow()}]
volume_id = utils.create_volume(self.context, size=5)['id']
def test_create_incremental_backup_invalid_no_full(
self, _mock_service_get_all_by_topic):
_mock_service_get_all_by_topic.return_value = [
- {'availability_zone': "fake_az", 'host': 'test_host',
+ {'availability_zone': 'fake_az', 'host': 'testhost',
'disabled': 0, 'updated_at': timeutils.utcnow()}]
volume_id = utils.create_volume(self.context, size=5,
@mock.patch('cinder.db.service_get_all_by_topic')
def test_is_backup_service_enabled(self, _mock_service_get_all_by_topic):
- test_host = 'test_host'
+ testhost = 'test_host'
alt_host = 'strange_host'
empty_service = []
# service host not match with volume's host
- host_not_match = [{'availability_zone': "fake_az", 'host': alt_host,
+ host_not_match = [{'availability_zone': 'fake_az', 'host': alt_host,
'disabled': 0, 'updated_at': timeutils.utcnow()}]
# service az not match with volume's az
- az_not_match = [{'availability_zone': "strange_az", 'host': test_host,
+ az_not_match = [{'availability_zone': 'strange_az', 'host': testhost,
'disabled': 0, 'updated_at': timeutils.utcnow()}]
# service disabled
disabled_service = []
# dead service that last reported at 20th century
- dead_service = [{'availability_zone': "fake_az", 'host': alt_host,
+ dead_service = [{'availability_zone': 'fake_az', 'host': alt_host,
'disabled': 0, 'updated_at': '1989-04-16 02:55:44'}]
# first service's host not match but second one works.
- multi_services = [{'availability_zone': "fake_az", 'host': alt_host,
+ multi_services = [{'availability_zone': 'fake_az', 'host': alt_host,
'disabled': 0, 'updated_at': timeutils.utcnow()},
- {'availability_zone': "fake_az", 'host': test_host,
+ {'availability_zone': 'fake_az', 'host': testhost,
'disabled': 0, 'updated_at': timeutils.utcnow()}]
# Setup mock to run through the following service cases
multi_services]
volume_id = utils.create_volume(self.context, size=2,
- host=test_host)['id']
+ host=testhost)['id']
volume = self.volume_api.get(context.get_admin_context(), volume_id)
# test empty service
self.assertEqual(False,
self.backup_api._is_backup_service_enabled(
volume['availability_zone'],
- test_host))
+ testhost))
# test host not match service
self.assertEqual(False,
self.backup_api._is_backup_service_enabled(
volume['availability_zone'],
- test_host))
+ testhost))
# test az not match service
self.assertEqual(False,
self.backup_api._is_backup_service_enabled(
volume['availability_zone'],
- test_host))
+ testhost))
# test disabled service
self.assertEqual(False,
self.backup_api._is_backup_service_enabled(
volume['availability_zone'],
- test_host))
+ testhost))
# test dead service
self.assertEqual(False,
self.backup_api._is_backup_service_enabled(
volume['availability_zone'],
- test_host))
+ testhost))
# test multi services and the last service matches
self.assertTrue(self.backup_api._is_backup_service_enabled(
volume['availability_zone'],
- test_host))
+ testhost))
+
+ @mock.patch('cinder.db.service_get_all_by_topic')
+ def test_get_available_backup_service(self,
+ _mock_service_get_all_by_topic):
+ _mock_service_get_all_by_topic.return_value = [
+ {'availability_zone': 'az1', 'host': 'testhost',
+ 'disabled': 0, 'updated_at': timeutils.utcnow()},
+ {'availability_zone': 'az2', 'host': 'fakehost',
+ 'disabled': 0, 'updated_at': timeutils.utcnow()}]
+ actual_host = self.backup_api._get_available_backup_service_host(
+ 'testhost', 'az1')
+ self.assertEqual('testhost', actual_host)
+ self.assertRaises(exception.ServiceNotFound,
+ self.backup_api._get_available_backup_service_host,
+ 'testhost', 'az2')
+ self.assertRaises(exception.ServiceNotFound,
+ self.backup_api._get_available_backup_service_host,
+ 'testhost2', 'az1')
+ self.override_config('backup_use_same_backend', True)
+ actual_host = self.backup_api._get_available_backup_service_host(
+ None, 'az1')
+ self.assertEqual('testhost', actual_host)
+ actual_host = self.backup_api._get_available_backup_service_host(
+ 'testhost2', 'az1')
+ self.assertEqual('testhost', actual_host)
@mock.patch('cinder.db.service_get_all_by_topic')
def test_delete_backup_available(self,
_mock_service_get_all_by_topic):
_mock_service_get_all_by_topic.return_value = [
- {'availability_zone': "az1", 'host': 'testhost',
+ {'availability_zone': 'az1', 'host': 'testhost',
'disabled': 0, 'updated_at': timeutils.utcnow()}]
backup_id = self._create_backup(status=fields.BackupStatus.AVAILABLE)
req = webob.Request.blank('/v2/fake/backups/%s' %
def test_delete_delta_backup(self,
_mock_service_get_all_by_topic):
_mock_service_get_all_by_topic.return_value = [
- {'availability_zone': "az1", 'host': 'testhost',
+ {'availability_zone': 'az1', 'host': 'testhost',
'disabled': 0, 'updated_at': timeutils.utcnow()}]
backup_id = self._create_backup(status=fields.BackupStatus.AVAILABLE)
delta_id = self._create_backup(status=fields.BackupStatus.AVAILABLE,
def test_delete_backup_error(self,
_mock_service_get_all_by_topic):
_mock_service_get_all_by_topic.return_value = [
- {'availability_zone': "az1", 'host': 'testhost',
+ {'availability_zone': 'az1', 'host': 'testhost',
'disabled': 0, 'updated_at': timeutils.utcnow()}]
backup_id = self._create_backup(status=fields.BackupStatus.ERROR)
req = webob.Request.blank('/v2/fake/backups/%s' %
def test_delete_backup_with_InvalidBackup2(self,
_mock_service_get_all_by_topic):
_mock_service_get_all_by_topic.return_value = [
- {'availability_zone': "az1", 'host': 'testhost',
+ {'availability_zone': 'az1', 'host': 'testhost',
'disabled': 0, 'updated_at': timeutils.utcnow()}]
volume_id = utils.create_volume(self.context, size=5)['id']
backup_id = self._create_backup(volume_id,
def test_delete_backup_service_down(self,
_mock_service_get_all_by_topic):
_mock_service_get_all_by_topic.return_value = [
- {'availability_zone': "az1", 'host': 'testhost',
+ {'availability_zone': 'az1', 'host': 'testhost',
'disabled': 0, 'updated_at': '1775-04-19 05:00:00'}]
backup_id = self._create_backup(status='available')
req = webob.Request.blank('/v2/fake/backups/%s' %
db.backup_destroy(context.get_admin_context(), backup_id)
- def test_restore_backup_volume_id_specified_json(self):
+ @mock.patch('cinder.backup.api.API._is_backup_service_enabled')
+ def test_restore_backup_volume_id_specified_json(
+ self, _mock_is_service_enabled):
+ _mock_is_service_enabled.return_value = True
backup_id = self._create_backup(status=fields.BackupStatus.AVAILABLE)
# need to create the volume referenced below first
volume_name = 'test1'
self.assertEqual(volume_id, res_dict['restore']['volume_id'])
self.assertEqual(volume_name, res_dict['restore']['volume_name'])
- def test_restore_backup_volume_id_specified_xml(self):
+ @mock.patch('cinder.backup.api.API._is_backup_service_enabled')
+ def test_restore_backup_volume_id_specified_xml(
+ self, _mock_is_service_enabled):
+ _mock_is_service_enabled.return_value = True
volume_name = 'test1'
backup_id = self._create_backup(status=fields.BackupStatus.AVAILABLE)
volume_id = utils.create_volume(self.context,
self.assertEqual("Missing required element 'restore' in request body.",
res_dict['badRequest']['message'])
+ @mock.patch('cinder.backup.api.API._is_backup_service_enabled')
@mock.patch('cinder.volume.api.API.create')
def test_restore_backup_volume_id_unspecified(self,
- _mock_volume_api_create):
+ _mock_volume_api_create,
+ _mock_is_service_enabled):
# intercept volume creation to ensure created volume
# has status of available
volume_id = utils.create_volume(self.context, size=size)['id']
return db.volume_get(context, volume_id)
+ _mock_is_service_enabled.return_value = True
_mock_volume_api_create.side_effect = fake_volume_api_create
backup_id = self._create_backup(size=5,
self.assertEqual(202, res.status_int)
self.assertEqual(backup_id, res_dict['restore']['backup_id'])
+ @mock.patch('cinder.backup.api.API._is_backup_service_enabled')
@mock.patch('cinder.volume.api.API.create')
def test_restore_backup_name_specified(self,
- _mock_volume_api_create):
-
+ _mock_volume_api_create,
+ _mock_is_service_enabled):
# Intercept volume creation to ensure created volume
# has status of available
def fake_volume_api_create(context, size, name, description):
return db.volume_get(context, volume_id)
_mock_volume_api_create.side_effect = fake_volume_api_create
+ _mock_is_service_enabled.return_value = True
backup_id = self._create_backup(size=5,
status=fields.BackupStatus.AVAILABLE)
self.assertEqual(202, res.status_int)
self.assertEqual(backup_id, res_dict['restore']['backup_id'])
- def test_restore_backup_name_volume_id_specified(self):
-
+ @mock.patch('cinder.backup.api.API._is_backup_service_enabled')
+ def test_restore_backup_name_volume_id_specified(
+ self, _mock_is_service_enabled):
+ _mock_is_service_enabled.return_value = True
backup_id = self._create_backup(size=5,
status=fields.BackupStatus.AVAILABLE)
orig_vol_name = "vol-00"
db.volume_destroy(context.get_admin_context(), volume_id)
db.backup_destroy(context.get_admin_context(), backup_id)
- def test_restore_backup_to_oversized_volume(self):
+ @mock.patch('cinder.backup.api.API._is_backup_service_enabled')
+ def test_restore_backup_to_oversized_volume(
+ self, _mock_is_service_enabled):
+ _mock_is_service_enabled.return_value = True
backup_id = self._create_backup(status=fields.BackupStatus.AVAILABLE,
size=10)
# need to create the volume referenced below first
db.backup_destroy(context.get_admin_context(), backup_id)
@mock.patch('cinder.backup.rpcapi.BackupAPI.restore_backup')
- def test_restore_backup_with_different_host(self, mock_restore_backup):
+ @mock.patch('cinder.backup.api.API._is_backup_service_enabled')
+ def test_restore_backup_with_different_host(self, mock_is_backup_available,
+ mock_restore_backup):
volume_name = 'test1'
backup_id = self._create_backup(status=fields.BackupStatus.AVAILABLE,
- size=10, host='HostA@BackendB#PoolA')
+ size=10, host='HostA')
volume_id = utils.create_volume(self.context, size=10,
host='HostB@BackendB#PoolB',
display_name=volume_name)['id']
+ mock_is_backup_available.return_value = True
body = {"restore": {"volume_id": volume_id, }}
req = webob.Request.blank('/v2/fake/backups/%s/restore' %
backup_id)
self.assertEqual(backup_id, res_dict['restore']['backup_id'])
self.assertEqual(volume_id, res_dict['restore']['volume_id'])
self.assertEqual(volume_name, res_dict['restore']['volume_name'])
- mock_restore_backup.assert_called_once_with(mock.ANY, u'HostB',
+ mock_restore_backup.assert_called_once_with(mock.ANY, u'HostA',
mock.ANY, volume_id)
# Manually check if restore_backup was called with appropriate backup.
self.assertEqual(backup_id, mock_restore_backup.call_args[0][2].id)
# request is not authorized
self.assertEqual(403, res.status_int)
+ @mock.patch('cinder.backup.api.API._is_backup_service_enabled')
@mock.patch('cinder.backup.rpcapi.BackupAPI.export_record')
def test_export_backup_record_id_specified_json(self,
- _mock_export_record_rpc):
+ _mock_export_record_rpc,
+ _mock_service_enabled):
backup_id = self._create_backup(status=fields.BackupStatus.AVAILABLE,
size=10)
ctx = context.RequestContext('admin', 'fake', is_admin=True)
_mock_export_record_rpc.return_value = \
{'backup_service': backup_service,
'backup_url': backup_url}
+ _mock_service_enabled.return_value = True
req = webob.Request.blank('/v2/fake/backups/%s/export_record' %
backup_id)
req.method = 'GET'
res_dict['backup-record']['backup_url'])
db.backup_destroy(context.get_admin_context(), backup_id)
+ @mock.patch('cinder.backup.api.API._is_backup_service_enabled')
@mock.patch('cinder.backup.rpcapi.BackupAPI.export_record')
def test_export_record_backup_id_specified_xml(self,
- _mock_export_record_rpc):
+ _mock_export_record_rpc,
+ _mock_service_enabled):
backup_id = self._create_backup(status=fields.BackupStatus.AVAILABLE,
size=10)
ctx = context.RequestContext('admin', 'fake', is_admin=True)
_mock_export_record_rpc.return_value = \
{'backup_service': backup_service,
'backup_url': backup_url}
+ _mock_service_enabled.return_value = True
req = webob.Request.blank('/v2/fake/backups/%s/export_record' %
backup_id)
req.method = 'GET'
res_dict['badRequest']['message'])
db.backup_destroy(context.get_admin_context(), backup_id)
+ @mock.patch('cinder.backup.api.API._is_backup_service_enabled')
@mock.patch('cinder.backup.rpcapi.BackupAPI.export_record')
def test_export_record_with_unavailable_service(self,
- _mock_export_record_rpc):
+ _mock_export_record_rpc,
+ _mock_service_enabled):
msg = 'fake unavailable service'
_mock_export_record_rpc.side_effect = \
exception.InvalidBackup(reason=msg)
+ _mock_service_enabled.return_value = True
backup_id = self._create_backup(status=fields.BackupStatus.AVAILABLE)
ctx = context.RequestContext('admin', 'fake', is_admin=True)
req = webob.Request.blank('/v2/fake/backups/%s/export_record' %
# request is not authorized
self.assertEqual(403, res.status_int)
- @mock.patch('cinder.backup.api.API._list_backup_services')
+ @mock.patch('cinder.backup.api.API._list_backup_hosts')
@mock.patch('cinder.backup.rpcapi.BackupAPI.import_record')
def test_import_record_volume_id_specified_json(self,
_mock_import_record_rpc,
self.assertEqual('0000-0000-0000-0000', db_backup.volume_id)
self.assertEqual(fields.BackupStatus.CREATING, db_backup.status)
- @mock.patch('cinder.backup.api.API._list_backup_services')
+ @mock.patch('cinder.backup.api.API._list_backup_hosts')
@mock.patch('cinder.backup.rpcapi.BackupAPI.import_record')
def test_import_record_volume_id_exists_deleted(self,
_mock_import_record_rpc,
db.backup_destroy(context.get_admin_context(), backup_id)
- @mock.patch('cinder.backup.api.API._list_backup_services')
+ @mock.patch('cinder.backup.api.API._list_backup_hosts')
@mock.patch('cinder.backup.rpcapi.BackupAPI.import_record')
def test_import_record_volume_id_specified_xml(self,
_mock_import_record_rpc,
back = dom.getElementsByTagName('backup')
self.assertEqual(backup.id, back.item(0).attributes['id'].value)
- @mock.patch('cinder.backup.api.API._list_backup_services')
+ @mock.patch('cinder.backup.api.API._list_backup_hosts')
def test_import_record_with_no_backup_services(self,
_mock_list_services):
ctx = context.RequestContext('admin', 'fake', is_admin=True)
% backup_service,
res_dict['computeFault']['message'])
- @mock.patch('cinder.backup.api.API._list_backup_services')
+ @mock.patch('cinder.backup.api.API._list_backup_hosts')
def test_import_backup_with_wrong_backup_url(self, _mock_list_services):
ctx = context.RequestContext('admin', 'fake', is_admin=True)
backup_service = 'fake'
self.assertEqual("Invalid input received: Can't parse backup record.",
res_dict['badRequest']['message'])
- @mock.patch('cinder.backup.api.API._list_backup_services')
+ @mock.patch('cinder.backup.api.API._list_backup_hosts')
def test_import_backup_with_existing_backup_record(self,
_mock_list_services):
ctx = context.RequestContext('admin', 'fake', is_admin=True)
db.backup_destroy(context.get_admin_context(), backup_id)
- @mock.patch('cinder.backup.api.API._list_backup_services')
+ @mock.patch('cinder.backup.api.API._list_backup_hosts')
@mock.patch('cinder.backup.rpcapi.BackupAPI.import_record')
def test_import_backup_with_missing_backup_services(self,
_mock_import_record,
from cinder import test
from cinder.tests.unit.backup import fake_service_with_verify as fake_service
from cinder.tests.unit import utils
-from cinder.volume.drivers import lvm
CONF = cfg.CONF
self.backup_mgr = importutils.import_object(CONF.backup_manager)
self.backup_mgr.host = 'testhost'
self.ctxt = context.get_admin_context()
- self.backup_mgr.driver.set_initialized()
-
- def _create_backup_db_entry(self, volume_id=1, display_name='test_backup',
+ paths = ['cinder.volume.rpcapi.VolumeAPI.delete_snapshot',
+ 'cinder.volume.rpcapi.VolumeAPI.delete_volume',
+ 'cinder.volume.rpcapi.VolumeAPI.detach_volume',
+ 'cinder.volume.rpcapi.VolumeAPI.'
+ 'secure_file_operations_enabled']
+ self.volume_patches = {}
+ self.volume_mocks = {}
+ for path in paths:
+ name = path.split('.')[-1]
+ self.volume_patches[name] = mock.patch(path)
+ self.volume_mocks[name] = self.volume_patches[name].start()
+ self.addCleanup(self.volume_patches[name].stop)
+
+ def _create_backup_db_entry(self, volume_id=1, restore_volume_id=None,
+ display_name='test_backup',
display_description='this is a test backup',
container='volumebackups',
status=fields.BackupStatus.CREATING,
"""
kwargs = {}
kwargs['volume_id'] = volume_id
+ kwargs['restore_volume_id'] = restore_volume_id
kwargs['user_id'] = 'fake'
kwargs['project_id'] = project_id
kwargs['host'] = 'testhost'
vol['attach_status'] = 'detached'
vol['availability_zone'] = '1'
vol['previous_status'] = previous_status
- return db.volume_create(self.ctxt, vol)['id']
+ volume = objects.Volume(context=self.ctxt, **vol)
+ volume.create()
+ return volume.id
def _create_snapshot_db_entry(self, display_name='test_snapshot',
display_description='test snapshot',
kwargs['volume_id'] = volume_id
kwargs['cgsnapshot_id'] = None
kwargs['volume_size'] = size
+ kwargs['metadata'] = {}
kwargs['provider_location'] = provider_location
snapshot_obj = objects.Snapshot(context=self.ctxt, **kwargs)
snapshot_obj.create()
class BackupTestCase(BaseBackupTest):
"""Test Case for backups."""
- @mock.patch.object(lvm.LVMVolumeDriver, 'delete_snapshot')
- @mock.patch.object(lvm.LVMVolumeDriver, 'delete_volume')
- def test_init_host(self, mock_delete_volume, mock_delete_snapshot):
+ @mock.patch('cinder.context.get_admin_context')
+ def test_init_host(self, mock_get_admin_context):
"""Test stuck volumes and backups.
Make sure stuck volumes and backups are reset to correct
states when backup_manager.init_host() is called
"""
+ def get_admin_context():
+ return self.ctxt
+
vol1_id = self._create_volume_db_entry()
self._create_volume_attach(vol1_id)
db.volume_update(self.ctxt, vol1_id, {'status': 'backing-up'})
temp_snap = self._create_snapshot_db_entry()
temp_snap.status = 'available'
temp_snap.save()
- vol6_id = self._create_volume_db_entry()
- db.volume_update(self.ctxt, vol6_id, {'status': 'restoring-backup'})
backup1 = self._create_backup_db_entry(
status=fields.BackupStatus.CREATING, volume_id=vol1_id)
backup2 = self._create_backup_db_entry(
- status=fields.BackupStatus.RESTORING, volume_id=vol2_id)
+ status=fields.BackupStatus.RESTORING,
+ restore_volume_id=vol2_id)
backup3 = self._create_backup_db_entry(
status=fields.BackupStatus.DELETING, volume_id=vol3_id)
self._create_backup_db_entry(status=fields.BackupStatus.CREATING,
volume_id=vol5_id,
temp_snapshot_id=temp_snap.id)
+ mock_get_admin_context.side_effect = get_admin_context
self.backup_mgr.init_host()
vol1 = db.volume_get(self.ctxt, vol1_id)
self.assertEqual('available', vol4['status'])
vol5 = db.volume_get(self.ctxt, vol5_id)
self.assertEqual('available', vol5['status'])
- vol6 = db.volume_get(self.ctxt, vol6_id)
- self.assertEqual('error_restoring', vol6['status'])
backup1 = db.backup_get(self.ctxt, backup1.id)
self.assertEqual(fields.BackupStatus.ERROR, backup1['status'])
self.ctxt,
backup3.id)
- self.assertTrue(mock_delete_volume.called)
- self.assertTrue(mock_delete_snapshot.called)
+ temp_vol = objects.Volume.get_by_id(self.ctxt, temp_vol_id)
+ self.volume_mocks['delete_volume'].assert_called_once_with(
+ self.ctxt, temp_vol)
+ self.assertTrue(self.volume_mocks['detach_volume'].called)
@mock.patch('cinder.objects.backup.BackupList.get_all_by_host')
@mock.patch('cinder.manager.SchedulerDependentManager._add_to_threadpool')
def test_is_working(self):
self.assertTrue(self.backup_mgr.is_working())
- vmanager_mock = mock.Mock()
- vmanager_mock.is_working.side_effect = [True, False, True]
- vms = {'a': vmanager_mock, 'b': vmanager_mock, 'c': vmanager_mock}
- with mock.patch.dict(self.backup_mgr.volume_managers, vms, True):
- self.assertFalse(self.backup_mgr.is_working())
-
- def test_init_host_handles_exception(self):
- """Test that exception in cleanup is handled."""
-
- self.mock_object(self.backup_mgr, '_init_volume_driver')
- mock_cleanup = self.mock_object(
- self.backup_mgr,
- '_cleanup_incomplete_backup_operations')
- mock_cleanup.side_effect = [Exception]
-
- self.assertIsNone(self.backup_mgr.init_host())
-
def test_cleanup_incomplete_backup_operations_with_exceptions(self):
"""Test cleanup resilience in the face of exceptions."""
- fake_volume_list = [{'id': 'vol1'}, {'id': 'vol2'}]
- mock_volume_get_by_host = self.mock_object(
- db, 'volume_get_all_by_host')
- mock_volume_get_by_host.return_value = fake_volume_list
-
- mock_volume_cleanup = self.mock_object(
- self.backup_mgr, '_cleanup_one_volume')
- mock_volume_cleanup.side_effect = [Exception]
-
fake_backup_list = [{'id': 'bkup1'}, {'id': 'bkup2'}, {'id': 'bkup3'}]
mock_backup_get_by_host = self.mock_object(
objects.BackupList, 'get_all_by_host')
self.backup_mgr._cleanup_incomplete_backup_operations(
self.ctxt))
- self.assertEqual(len(fake_volume_list), mock_volume_cleanup.call_count)
self.assertEqual(len(fake_backup_list), mock_backup_cleanup.call_count)
self.assertEqual(len(fake_backup_list), mock_temp_cleanup.call_count)
def test_cleanup_one_backing_up_volume(self):
"""Test cleanup_one_volume for volume status 'backing-up'."""
- mock_get_manager = self.mock_object(
- self.backup_mgr, '_get_manager')
- mock_get_manager.return_value = 'fake_manager'
-
volume_id = self._create_volume_db_entry(status='backing-up',
previous_status='available')
volume = db.volume_get(self.ctxt, volume_id)
def test_cleanup_one_restoring_backup_volume(self):
"""Test cleanup_one_volume for volume status 'restoring-backup'."""
- mock_get_manager = self.mock_object(
- self.backup_mgr, '_get_manager')
- mock_get_manager.return_value = 'fake_manager'
-
volume_id = self._create_volume_db_entry(status='restoring-backup')
volume = db.volume_get(self.ctxt, volume_id)
def test_cleanup_one_creating_backup(self):
"""Test cleanup_one_backup for volume status 'creating'."""
+ vol1_id = self._create_volume_db_entry()
+ self._create_volume_attach(vol1_id)
+ db.volume_update(self.ctxt, vol1_id, {'status': 'backing-up', })
+
backup = self._create_backup_db_entry(
- status=fields.BackupStatus.CREATING)
+ status=fields.BackupStatus.CREATING,
+ volume_id=vol1_id)
self.backup_mgr._cleanup_one_backup(self.ctxt, backup)
self.assertEqual(fields.BackupStatus.ERROR, backup.status)
+ volume = objects.Volume.get_by_id(self.ctxt, vol1_id)
+ self.assertEqual('available', volume.status)
def test_cleanup_one_restoring_backup(self):
"""Test cleanup_one_backup for volume status 'restoring'."""
+ vol1_id = self._create_volume_db_entry()
+ db.volume_update(self.ctxt, vol1_id, {'status': 'restoring-backup', })
+
backup = self._create_backup_db_entry(
- status=fields.BackupStatus.RESTORING)
+ status=fields.BackupStatus.RESTORING,
+ restore_volume_id=vol1_id)
self.backup_mgr._cleanup_one_backup(self.ctxt, backup)
self.assertEqual(fields.BackupStatus.AVAILABLE, backup.status)
+ volume = objects.Volume.get_by_id(self.ctxt, vol1_id)
+ self.assertEqual('error_restoring', volume.status)
def test_cleanup_one_deleting_backup(self):
"""Test cleanup_one_backup for volume status 'deleting'."""
"""Test detach_all_attachments with exceptions."""
mock_log = self.mock_object(manager, 'LOG')
- mock_volume_mgr = mock.Mock()
- mock_detach_volume = mock_volume_mgr.detach_volume
- mock_detach_volume.side_effect = [Exception]
+ self.volume_mocks['detach_volume'].side_effect = [Exception]
fake_attachments = [
{
}
self.backup_mgr._detach_all_attachments(self.ctxt,
- mock_volume_mgr,
fake_volume)
self.assertEqual(len(fake_attachments), mock_log.exception.call_count)
def test_cleanup_temp_snapshot_for_one_backup_not_found(self):
"""Ensure we handle missing temp snapshot for a backup."""
- mock_delete_snapshot = self.mock_object(
- lvm.LVMVolumeDriver, 'delete_snapshot')
vol1_id = self._create_volume_db_entry()
self._create_volume_attach(vol1_id)
self.ctxt,
backup))
- self.assertFalse(mock_delete_snapshot.called)
+ self.assertFalse(self.volume_mocks['delete_snapshot'].called)
self.assertIsNone(backup.temp_snapshot_id)
backup.destroy()
def test_cleanup_temp_volume_for_one_backup_not_found(self):
"""Ensure we handle missing temp volume for a backup."""
- mock_delete_volume = self.mock_object(
- lvm.LVMVolumeDriver, 'delete_volume')
vol1_id = self._create_volume_db_entry()
self._create_volume_attach(vol1_id)
self.ctxt,
backup))
- self.assertFalse(mock_delete_volume.called)
+ self.assertFalse(self.volume_mocks['delete_volume'].called)
self.assertIsNone(backup.temp_volume_id)
backup.destroy()
self.ctxt,
backup)
- @mock.patch('%s.%s' % (CONF.volume_driver, 'backup_volume'))
- def test_create_backup_with_error(self, _mock_volume_backup):
+ def test_create_backup_with_error(self):
"""Test error handling when error occurs during backup creation."""
vol_id = self._create_volume_db_entry(size=1)
backup = self._create_backup_db_entry(volume_id=vol_id)
- _mock_volume_backup.side_effect = FakeBackupException('fake')
+ mock_run_backup = self.mock_object(self.backup_mgr, '_run_backup')
+ mock_run_backup.side_effect = FakeBackupException('fake')
self.assertRaises(FakeBackupException,
self.backup_mgr.create_backup,
self.ctxt,
self.assertEqual('error_backing-up', vol['previous_status'])
backup = db.backup_get(self.ctxt, backup.id)
self.assertEqual(fields.BackupStatus.ERROR, backup['status'])
- self.assertTrue(_mock_volume_backup.called)
-
- @mock.patch('%s.%s' % (CONF.volume_driver, 'backup_volume'))
- def test_create_backup(self, _mock_volume_backup):
+ self.assertTrue(mock_run_backup.called)
+
+ @mock.patch('cinder.utils.brick_get_connector_properties')
+ @mock.patch('cinder.volume.rpcapi.VolumeAPI.get_backup_device')
+ @mock.patch('cinder.utils.temporary_chown')
+ @mock.patch('six.moves.builtins.open')
+ def test_create_backup(self, mock_open, mock_temporary_chown,
+ mock_get_backup_device, mock_get_conn):
"""Test normal backup creation."""
vol_size = 1
vol_id = self._create_volume_db_entry(size=vol_size)
backup = self._create_backup_db_entry(volume_id=vol_id)
+ vol = objects.Volume.get_by_id(self.ctxt, vol_id)
+ mock_get_backup_device.return_value = {'backup_device': vol,
+ 'secure_enabled': False,
+ 'is_snapshot': False, }
+ attach_info = {'device': {'path': '/dev/null'}}
+ mock_detach_device = self.mock_object(self.backup_mgr,
+ '_detach_device')
+ mock_attach_device = self.mock_object(self.backup_mgr,
+ '_attach_device')
+ mock_attach_device.return_value = attach_info
+ properties = {}
+ mock_get_conn.return_value = properties
+ mock_open.return_value = open('/dev/null', 'rb')
+
self.backup_mgr.create_backup(self.ctxt, backup)
- vol = db.volume_get(self.ctxt, vol_id)
+
+ mock_temporary_chown.assert_called_once_with('/dev/null')
+ mock_attach_device.assert_called_once_with(self.ctxt, vol,
+ properties, False)
+ mock_get_backup_device.assert_called_once_with(self.ctxt, backup, vol)
+ mock_get_conn.assert_called_once_with()
+ mock_detach_device.assert_called_once_with(self.ctxt, attach_info,
+ vol, properties, False)
+
+ vol = objects.Volume.get_by_id(self.ctxt, vol_id)
self.assertEqual('available', vol['status'])
self.assertEqual('backing-up', vol['previous_status'])
backup = db.backup_get(self.ctxt, backup.id)
self.assertEqual(fields.BackupStatus.AVAILABLE, backup['status'])
self.assertEqual(vol_size, backup['size'])
- self.assertTrue(_mock_volume_backup.called)
@mock.patch('cinder.volume.utils.notify_about_backup_usage')
- @mock.patch('%s.%s' % (CONF.volume_driver, 'backup_volume'))
- def test_create_backup_with_notify(self, _mock_volume_backup, notify):
+ def test_create_backup_with_notify(self, notify):
"""Test normal backup creation with notifications."""
vol_size = 1
vol_id = self._create_volume_db_entry(size=vol_size)
backup = self._create_backup_db_entry(volume_id=vol_id)
+ self.mock_object(self.backup_mgr, '_run_backup')
self.backup_mgr.create_backup(self.ctxt, backup)
self.assertEqual(2, notify.call_count)
- def test_require_driver_initialized_in_create_backup(self):
- """Test backup creation.
-
- Test require_driver_initialized with _get_driver
- in a normal backup creation.
- """
- vol_size = 1
- vol_id = self._create_volume_db_entry(size=vol_size)
- backup = self._create_backup_db_entry(volume_id=vol_id)
-
- self.backup_mgr._get_driver = mock.MagicMock()
- self.backup_mgr._get_volume_backend = mock.MagicMock()
- self.backup_mgr._get_volume_backend.return_value = 'mybackend'
-
- self.backup_mgr.create_backup(self.ctxt, backup)
- self.assertEqual(2, self.backup_mgr._get_driver.call_count)
- self.assertEqual(self.backup_mgr._get_driver.call_args_list[0],
- mock.call('mybackend'))
- self.assertEqual(self.backup_mgr._get_driver.call_args_list[1],
- mock.call('mybackend'))
-
def test_restore_backup_with_bad_volume_status(self):
"""Test error handling.
backup = db.backup_get(self.ctxt, backup.id)
self.assertEqual(fields.BackupStatus.ERROR, backup['status'])
- @mock.patch('%s.%s' % (CONF.volume_driver, 'restore_backup'))
- def test_restore_backup_with_driver_error(self, _mock_volume_restore):
+ def test_restore_backup_with_driver_error(self):
"""Test error handling when an error occurs during backup restore."""
vol_id = self._create_volume_db_entry(status='restoring-backup',
size=1)
backup = self._create_backup_db_entry(
status=fields.BackupStatus.RESTORING, volume_id=vol_id)
- _mock_volume_restore.side_effect = FakeBackupException('fake')
+ mock_run_restore = self.mock_object(
+ self.backup_mgr,
+ '_run_restore')
+ mock_run_restore.side_effect = FakeBackupException('fake')
self.assertRaises(FakeBackupException,
self.backup_mgr.restore_backup,
self.ctxt,
self.assertEqual('error_restoring', vol['status'])
backup = db.backup_get(self.ctxt, backup.id)
self.assertEqual(fields.BackupStatus.AVAILABLE, backup['status'])
- self.assertTrue(_mock_volume_restore.called)
+ self.assertTrue(mock_run_restore.called)
def test_restore_backup_with_bad_service(self):
"""Test error handling.
backup = db.backup_get(self.ctxt, backup.id)
self.assertEqual(fields.BackupStatus.AVAILABLE, backup['status'])
- @mock.patch('%s.%s' % (CONF.volume_driver, 'restore_backup'))
- def test_restore_backup(self, _mock_volume_restore):
+ @mock.patch('cinder.utils.brick_get_connector_properties')
+ @mock.patch('cinder.utils.temporary_chown')
+ @mock.patch('six.moves.builtins.open')
+ def test_restore_backup(self, mock_open, mock_temporary_chown,
+ mock_get_conn):
"""Test normal backup restoration."""
vol_size = 1
vol_id = self._create_volume_db_entry(status='restoring-backup',
backup = self._create_backup_db_entry(
status=fields.BackupStatus.RESTORING, volume_id=vol_id)
+ properties = {}
+ mock_get_conn.return_value = properties
+ mock_open.return_value = open('/dev/null', 'wb')
+ mock_secure_enabled = (
+ self.volume_mocks['secure_file_operations_enabled'])
+ mock_secure_enabled.return_value = False
+ vol = objects.Volume.get_by_id(self.ctxt, vol_id)
+ attach_info = {'device': {'path': '/dev/null'}}
+ mock_detach_device = self.mock_object(self.backup_mgr,
+ '_detach_device')
+ mock_attach_device = self.mock_object(self.backup_mgr,
+ '_attach_device')
+ mock_attach_device.return_value = attach_info
+
self.backup_mgr.restore_backup(self.ctxt, backup, vol_id)
- vol = db.volume_get(self.ctxt, vol_id)
+
+ mock_temporary_chown.assert_called_once_with('/dev/null')
+ mock_get_conn.assert_called_once_with()
+ mock_secure_enabled.assert_called_once_with(self.ctxt, vol)
+ mock_attach_device.assert_called_once_with(self.ctxt, vol,
+ properties)
+ mock_detach_device.assert_called_once_with(self.ctxt, attach_info,
+ vol, properties)
+
+ vol = objects.Volume.get_by_id(self.ctxt, vol_id)
self.assertEqual('available', vol['status'])
backup = db.backup_get(self.ctxt, backup.id)
self.assertEqual(fields.BackupStatus.AVAILABLE, backup['status'])
- self.assertTrue(_mock_volume_restore.called)
@mock.patch('cinder.volume.utils.notify_about_backup_usage')
- @mock.patch('%s.%s' % (CONF.volume_driver, 'restore_backup'))
- def test_restore_backup_with_notify(self, _mock_volume_restore, notify):
+ def test_restore_backup_with_notify(self, notify):
"""Test normal backup restoration with notifications."""
vol_size = 1
vol_id = self._create_volume_db_entry(status='restoring-backup',
size=vol_size)
backup = self._create_backup_db_entry(
status=fields.BackupStatus.RESTORING, volume_id=vol_id)
+ self.backup_mgr._run_restore = mock.Mock()
self.backup_mgr.restore_backup(self.ctxt, backup, vol_id)
self.assertEqual(2, notify.call_count)
- def test_require_driver_initialized_in_restore_backup(self):
- """Test backup restoration.
-
- Test require_driver_initialized with _get_driver
- in a normal backup restoration.
- """
- vol_size = 1
- vol_id = self._create_volume_db_entry(status='restoring-backup',
- size=vol_size)
- backup = self._create_backup_db_entry(
- status=fields.BackupStatus.RESTORING, volume_id=vol_id)
-
- self.backup_mgr._get_driver = mock.MagicMock()
- self.backup_mgr._get_volume_backend = mock.MagicMock()
- self.backup_mgr._get_volume_backend.return_value = 'mybackend'
-
- self.backup_mgr.restore_backup(self.ctxt, backup, vol_id)
- self.assertEqual(2, self.backup_mgr._get_driver.call_count)
- self.assertEqual(self.backup_mgr._get_driver.call_args_list[0],
- mock.call('mybackend'))
- self.assertEqual(self.backup_mgr._get_driver.call_args_list[1],
- mock.call('mybackend'))
-
def test_delete_backup_with_bad_backup_status(self):
"""Test error handling.
ctxt, ctxt.project_id, {'key': 'value'}, None, None, None, None,
None)
- @mock.patch.object(api.API, '_is_backup_service_enabled',
- return_value=True)
+ @mock.patch.object(api.API, '_get_available_backup_service_host',
+ return_value='fake_host')
@mock.patch.object(db, 'backup_create',
side_effect=db_exc.DBError())
def test_create_when_failed_to_create_backup_object(
self, mock_create,
- mock_service_enabled):
+ mock_get_service):
volume_id = utils.create_volume(self.ctxt)['id']
self.ctxt.user_id = 'user_id'
self.ctxt.project_id = 'project_id'
volume_id=volume_id,
container='volumebackups')
- @mock.patch.object(api.API, '_is_backup_service_enabled',
- return_value=True)
+ @mock.patch.object(api.API, '_get_available_backup_service_host',
+ return_value='fake_host')
@mock.patch.object(objects.Backup, '__init__',
side_effect=exception.InvalidInput(
reason='Failed to new'))
def test_create_when_failed_to_new_backup_object(self, mock_new,
- mock_service_enabled):
+ mock_get_service):
volume_id = utils.create_volume(self.ctxt)['id']
self.ctxt.user_id = 'user_id'
self.ctxt.project_id = 'project_id'
volume_id=volume_id,
container='volumebackups')
+ @mock.patch('cinder.backup.api.API._is_backup_service_enabled')
@mock.patch('cinder.backup.rpcapi.BackupAPI.restore_backup')
def test_restore_volume(self,
- mock_rpcapi_restore):
+ mock_rpcapi_restore,
+ mock_is_service_enabled):
ctxt = context.RequestContext('fake', 'fake')
volume_id = self._create_volume_db_entry(status='available',
size=1)
backup = self._create_backup_db_entry(size=1,
status='available')
+ mock_is_service_enabled.return_value = True
self.api.restore(ctxt, backup.id, volume_id)
backup = objects.Backup.get_by_id(ctxt, backup.id)
self.assertEqual(volume_id, backup.restore_volume_id)
self.flags(**flag_args)
vol_ref = self._create_volume()
backup_ref = self._create_backup(vol_ref)
- with mock.patch.object(backup.API, '_is_backup_service_enabled') as \
- mock__is_backup_service_enabled:
- mock__is_backup_service_enabled.return_value = True
+ with mock.patch.object(backup.API,
+ '_get_available_backup_service_host') as \
+ mock__get_available_backup_service:
+ mock__get_available_backup_service.return_value = 'host'
self.assertRaises(exception.BackupLimitExceeded,
backup.API().create,
self.context,
def test_too_many_combined_backup_gigabytes(self):
vol_ref = self._create_volume(size=10000)
backup_ref = self._create_backup(vol_ref)
- with mock.patch.object(backup.API, '_is_backup_service_enabled') as \
- mock__is_backup_service_enabled:
- mock__is_backup_service_enabled.return_value = True
+ with mock.patch.object(backup.API,
+ '_get_available_backup_service_host') as \
+ mock__get_available_backup_service:
+ mock__get_available_backup_service.return_value = 'host'
self.assertRaises(
exception.VolumeBackupSizeExceedsAvailableQuota,
backup.API().create,
)
vol_ref = self._create_volume(size=10)
backup_ref = self._create_backup(vol_ref)
- with mock.patch.object(backup.API, '_is_backup_service_enabled') as \
- mock__is_backup_service_enabled:
- mock__is_backup_service_enabled.return_value = True
+ with mock.patch.object(backup.API,
+ '_get_available_backup_service_host') as \
+ mock_mock__get_available_backup_service:
+ mock_mock__get_available_backup_service.return_value = 'host'
backup_ref2 = backup.API().create(self.context,
'name',
'description',
vol_api.delete,
self.context, volume)
+ @mock.patch.object(driver.BaseVD, 'get_backup_device')
+ @mock.patch.object(driver.BaseVD, 'secure_file_operations_enabled')
+ def test_get_backup_device(self, mock_secure, mock_get_backup):
+ vol = tests_utils.create_volume(self.context)
+ backup = tests_utils.create_backup(self.context, vol['id'])
+ mock_secure.return_value = False
+ mock_get_backup.return_value = (vol, False)
+ result = self.volume.get_backup_device(self.context,
+ backup)
+
+ mock_get_backup.assert_called_once_with(self.context, backup)
+ mock_secure.assert_called_once_with()
+ expected_result = {'backup_device': vol,
+ 'secure_enabled': False,
+ 'is_snapshot': False}
+ self.assertEqual(expected_result, result)
+
+ @mock.patch.object(driver.BaseVD, 'secure_file_operations_enabled')
+ def test_secure_file_operations_enabled(self, mock_secure):
+ mock_secure.return_value = True
+ vol = tests_utils.create_volume(self.context)
+ result = self.volume.secure_file_operations_enabled(self.context,
+ vol)
+ mock_secure.assert_called_once_with()
+ self.assertTrue(result)
+
class CopyVolumeToImageTestCase(BaseVolumeTestCase):
def fake_local_path(self, volume):
volume_file)
self.assertEqual(i, backup_service.restore.call_count)
+ def test_get_backup_device_available(self):
+ vol = tests_utils.create_volume(self.context)
+ self.context.user_id = 'fake'
+ self.context.project_id = 'fake'
+ backup = tests_utils.create_backup(self.context,
+ vol['id'])
+ backup_obj = objects.Backup.get_by_id(self.context, backup.id)
+ (backup_device, is_snapshot) = self.volume.driver.get_backup_device(
+ self.context, backup_obj)
+ volume = objects.Volume.get_by_id(self.context, vol.id)
+ self.assertEqual(volume, backup_device)
+ self.assertFalse(is_snapshot)
+ backup_obj = objects.Backup.get_by_id(self.context, backup.id)
+ self.assertIsNone(backup.temp_volume_id)
+
+ def test_get_backup_device_in_use(self):
+ vol = tests_utils.create_volume(self.context,
+ status='backing-up',
+ previous_status='in-use')
+ temp_vol = tests_utils.create_volume(self.context)
+ self.context.user_id = 'fake'
+ self.context.project_id = 'fake'
+ backup = tests_utils.create_backup(self.context,
+ vol['id'])
+ backup_obj = objects.Backup.get_by_id(self.context, backup.id)
+ with mock.patch.object(
+ self.volume.driver,
+ '_create_temp_cloned_volume') as mock_create_temp:
+ mock_create_temp.return_value = temp_vol
+ (backup_device, is_snapshot) = (
+ self.volume.driver.get_backup_device(self.context,
+ backup_obj))
+ self.assertEqual(temp_vol, backup_device)
+ self.assertFalse(is_snapshot)
+ backup_obj = objects.Backup.get_by_id(self.context, backup.id)
+ self.assertEqual(temp_vol.id, backup_obj.temp_volume_id)
+
def test_enable_replication_invalid_state(self):
volume_api = cinder.volume.api.API()
ctxt = context.get_admin_context()
from cinder import db
from cinder import objects
from cinder import test
+from cinder.tests.unit import fake_backup
from cinder.tests.unit import fake_snapshot
from cinder.tests.unit import fake_volume
from cinder.tests.unit import utils as tests_utils
self.fake_cg2 = group2
self.fake_src_cg = jsonutils.to_primitive(source_group)
self.fake_cgsnap = cgsnapshot
+ self.fake_backup_obj = fake_backup.fake_backup_obj(self.context)
def test_serialized_volume_has_id(self):
self.assertIn('id', self.fake_volume)
if cgsnapshot:
cgsnapshot.consistencygroup
kwargs['cgsnapshot'].consistencygroup
+ if 'backup' in expected_msg:
+ backup = expected_msg['backup']
+ del expected_msg['backup']
+ expected_msg['backup_id'] = backup.id
+ expected_msg['backup'] = backup
+
if 'host' in expected_msg:
del expected_msg['host']
if 'dest_host' in expected_msg:
expected_volume = expected_msg[kwarg].obj_to_primitive()
volume = value.obj_to_primitive()
self.assertEqual(expected_volume, volume)
+ elif isinstance(value, objects.Backup):
+ expected_backup = expected_msg[kwarg].obj_to_primitive()
+ backup = value.obj_to_primitive()
+ self.assertEqual(expected_backup, backup)
else:
self.assertEqual(expected_msg[kwarg], value)
rpc_method='cast',
volume=self.fake_volume,
version='1.30')
+
+ def test_get_backup_device(self):
+ self._test_volume_api('get_backup_device',
+ rpc_method='call',
+ backup=self.fake_backup_obj,
+ volume=self.fake_volume_obj,
+ version='1.38')
+
+ def test_secure_file_operations_enabled(self):
+ self._test_volume_api('secure_file_operations_enabled',
+ rpc_method='call',
+ volume=self.fake_volume_obj,
+ version='1.38')
def backup_use_temp_snapshot(self):
return False
+ def snapshot_remote_attachable(self):
+ # TODO(lixiaoy1): the method will be deleted later when remote
+ # attach snapshot is implemented.
+ return False
+
+ def get_backup_device(self, context, backup):
+ """Get a backup device from an existing volume.
+
+ The function returns a volume or snapshot to backup service,
+ and then backup service attaches the device and does backup.
+ """
+ backup_device = None
+ is_snapshot = False
+ if (self.backup_use_temp_snapshot() and
+ self.snapshot_remote_attachable()):
+ (backup_device, is_snapshot) = (
+ self._get_backup_volume_temp_snapshot(context, backup))
+ else:
+ backup_device = self._get_backup_volume_temp_volume(
+ context, backup)
+ is_snapshot = False
+ return (backup_device, is_snapshot)
+
+ def _get_backup_volume_temp_volume(self, context, backup):
+ """Return a volume to do backup.
+
+ To backup a snapshot, create a temp volume from the snapshot and
+ back it up.
+
+ Otherwise to backup an in-use volume, create a temp volume and
+ back it up.
+ """
+ volume = objects.Volume.get_by_id(context, backup.volume_id)
+ snapshot = None
+ if backup.snapshot_id:
+ snapshot = objects.Snapshot.get_by_id(context, backup.snapshot_id)
+
+ LOG.debug('Creating a new backup for volume %s.', volume['name'])
+
+ temp_vol_ref = None
+ device_to_backup = volume
+
+ # NOTE(xyang): If it is to backup from snapshot, create a temp
+ # volume from the source snapshot, backup the temp volume, and
+ # then clean up the temp volume.
+ if snapshot:
+ temp_vol_ref = self._create_temp_volume_from_snapshot(
+ context, volume, snapshot)
+ backup.temp_volume_id = temp_vol_ref['id']
+ backup.save()
+ device_to_backup = temp_vol_ref
+
+ else:
+ # NOTE(xyang): Check volume status if it is not to backup from
+ # snapshot; if 'in-use', create a temp volume from the source
+ # volume, backup the temp volume, and then clean up the temp
+ # volume; if 'available', just backup the volume.
+ previous_status = volume.get('previous_status')
+ if previous_status == "in-use":
+ temp_vol_ref = self._create_temp_cloned_volume(
+ context, volume)
+ backup.temp_volume_id = temp_vol_ref['id']
+ backup.save()
+ device_to_backup = temp_vol_ref
+
+ return device_to_backup
+
+ def _get_backup_volume_temp_snapshot(self, context, backup):
+ """Return a device to backup.
+
+ If it is to backup from snapshot, back it up directly.
+
+ Otherwise for in-use volume, create a temp snapshot and back it up.
+ """
+ volume = self.db.volume_get(context, backup.volume_id)
+ snapshot = None
+ if backup.snapshot_id:
+ snapshot = objects.Snapshot.get_by_id(context, backup.snapshot_id)
+
+ LOG.debug('Creating a new backup for volume %s.', volume['name'])
+
+ device_to_backup = volume
+ is_snapshot = False
+ temp_snapshot = None
+
+ # NOTE(xyang): If it is to backup from snapshot, back it up
+ # directly. No need to clean it up.
+ if snapshot:
+ device_to_backup = snapshot
+ is_snapshot = True
+
+ else:
+ # NOTE(xyang): If it is not to backup from snapshot, check volume
+ # status. If the volume status is 'in-use', create a temp snapshot
+ # from the source volume, backup the temp snapshot, and then clean
+ # up the temp snapshot; if the volume status is 'available', just
+ # backup the volume.
+ previous_status = volume.get('previous_status')
+ if previous_status == "in-use":
+ temp_snapshot = self._create_temp_snapshot(context, volume)
+ backup.temp_snapshot_id = temp_snapshot.id
+ backup.save()
+ device_to_backup = temp_snapshot
+ is_snapshot = True
+
+ return (device_to_backup, is_snapshot)
+
def backup_volume(self, context, backup, backup_service):
"""Create a new backup from an existing volume."""
# NOTE(xyang): _backup_volume_temp_snapshot and
'user_id': context.user_id,
'project_id': context.project_id,
'status': 'creating',
+ 'attach_status': 'detached',
+ 'availability_zone': volume.availability_zone,
}
temp_vol_ref = self.db.volume_create(context, temp_volume)
try:
class VolumeManager(manager.SchedulerDependentManager):
"""Manages attachable block storage devices."""
- RPC_API_VERSION = '1.37'
+ RPC_API_VERSION = '1.38'
target = messaging.Target(version=RPC_API_VERSION)
capabilities = self.driver.capabilities
LOG.debug("Obtained capabilities list: %s.", capabilities)
return capabilities
+
+ def get_backup_device(self, ctxt, backup):
+ (backup_device, is_snapshot) = (
+ self.driver.get_backup_device(ctxt, backup))
+ secure_enabled = self.driver.secure_file_operations_enabled()
+ backup_device_dict = {'backup_device': backup_device,
+ 'secure_enabled': secure_enabled,
+ 'is_snapshot': is_snapshot, }
+ return backup_device_dict
+
+ def secure_file_operations_enabled(self, ctxt, volume):
+ secure_enabled = self.driver.secure_file_operations_enabled()
+ return secure_enabled
migrate_volume_completion(), and update_migrated_volume().
1.37 - Adds old_reservations parameter to retype to support quota
checks in the API.
+ 1.38 - Scaling backup service, add get_backup_device() and
+ secure_file_operations_enabled()
"""
- RPC_API_VERSION = '1.37'
+ RPC_API_VERSION = '1.38'
TOPIC = CONF.volume_topic
BINARY = 'cinder-volume'
def get_capabilities(self, ctxt, host, discover):
cctxt = self._get_cctxt(host, '1.29')
return cctxt.call(ctxt, 'get_capabilities', discover=discover)
+
+ def get_backup_device(self, ctxt, backup, volume):
+ new_host = utils.extract_host(volume.host)
+ cctxt = self.client.prepare(server=new_host, version='1.38')
+ return cctxt.call(ctxt, 'get_backup_device',
+ backup=backup)
+
+ def secure_file_operations_enabled(self, ctxt, volume):
+ new_host = utils.extract_host(volume.host)
+ cctxt = self.client.prepare(server=new_host, version='1.38')
+ return cctxt.call(ctxt, 'secure_file_operations_enabled',
+ volume=volume)