Merge "Fix backup using temp snapshot code path"
This commit is contained in:
commit
110a7bce64
@ -91,11 +91,85 @@ class BackupManager(manager.SchedulerDependentManager):
|
||||
self.service = importutils.import_module(self.driver_name)
|
||||
self.az = CONF.storage_availability_zone
|
||||
self.volume_managers = {}
|
||||
# TODO(xyang): If backup_use_same_host is True, we'll find
|
||||
# the volume backend on the backup node. This allows us
|
||||
# to use a temp snapshot to backup an in-use volume if the
|
||||
# driver supports it. This code should go away when we add
|
||||
# support for backing up in-use volume using a temp snapshot
|
||||
# on a remote node.
|
||||
if CONF.backup_use_same_host:
|
||||
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)
|
||||
|
||||
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)
|
||||
# NOTE(xyang): If host='myhost@lvmdriver', backend='lvmdriver'
|
||||
# by the logic below. This is different from extract_host.
|
||||
# vol_utils.extract_host(host, 'backend')='myhost@lvmdriver'.
|
||||
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 the top of the list.
|
||||
return self.volume_managers.keys()[0]
|
||||
|
||||
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
|
||||
|
||||
@property
|
||||
def driver_name(self):
|
||||
"""This function maps old backup services to backup drivers."""
|
||||
@ -812,8 +886,12 @@ class BackupManager(manager.SchedulerDependentManager):
|
||||
if not is_snapshot:
|
||||
return self._attach_volume(context, backup_device, properties)
|
||||
else:
|
||||
msg = _("Can't attach snapshot.")
|
||||
raise NotImplementedError(msg)
|
||||
volume = self.db.volume_get(context, backup_device.volume_id)
|
||||
host = volume_utils.extract_host(volume['host'], 'backend')
|
||||
backend = self._get_volume_backend(host=host)
|
||||
rc = self._get_driver(backend)._attach_snapshot(
|
||||
context, backup_device, properties)
|
||||
return rc
|
||||
|
||||
def _attach_volume(self, context, volume, properties):
|
||||
"""Attach a volume."""
|
||||
@ -849,13 +927,21 @@ class BackupManager(manager.SchedulerDependentManager):
|
||||
|
||||
return {'conn': conn, 'device': vol_handle, 'connector': connector}
|
||||
|
||||
def _detach_device(self, context, attach_info, volume,
|
||||
def _detach_device(self, context, attach_info, device,
|
||||
properties, is_snapshot=False, force=False):
|
||||
"""Disconnect the volume from the host. """
|
||||
"""Disconnect the volume or snapshot 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)
|
||||
if not is_snapshot:
|
||||
rpcapi.terminate_connection(context, device, properties,
|
||||
force=force)
|
||||
rpcapi.remove_export(context, device)
|
||||
else:
|
||||
volume = self.db.volume_get(context, device.volume_id)
|
||||
host = volume_utils.extract_host(volume['host'], 'backend')
|
||||
backend = self._get_volume_backend(host=host)
|
||||
self._get_driver(backend)._detach_snapshot(
|
||||
context, attach_info, device, properties, force)
|
||||
|
@ -20,6 +20,7 @@ import tempfile
|
||||
import uuid
|
||||
|
||||
import mock
|
||||
import os_brick
|
||||
from oslo_config import cfg
|
||||
from oslo_db import exception as db_exc
|
||||
from oslo_utils import importutils
|
||||
@ -35,6 +36,7 @@ from cinder.objects import fields
|
||||
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 import driver
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
@ -589,6 +591,56 @@ class BackupTestCase(BaseBackupTest):
|
||||
self.assertEqual(fields.BackupStatus.AVAILABLE, backup['status'])
|
||||
self.assertEqual(vol_size, backup['size'])
|
||||
|
||||
@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_with_temp_snapshot(self, mock_open,
|
||||
mock_temporary_chown,
|
||||
mock_get_backup_device,
|
||||
mock_get_conn):
|
||||
"""Test backup in-use volume using temp snapshot."""
|
||||
self.override_config('backup_use_same_host', True)
|
||||
self.backup_mgr._setup_volume_drivers()
|
||||
vol_size = 1
|
||||
vol_id = self._create_volume_db_entry(size=vol_size,
|
||||
previous_status='in-use')
|
||||
backup = self._create_backup_db_entry(volume_id=vol_id)
|
||||
snap = self._create_snapshot_db_entry(volume_id = vol_id)
|
||||
|
||||
vol = objects.Volume.get_by_id(self.ctxt, vol_id)
|
||||
mock_get_backup_device.return_value = {'backup_device': snap,
|
||||
'secure_enabled': False,
|
||||
'is_snapshot': True, }
|
||||
|
||||
attach_info = {
|
||||
'device': {'path': '/dev/null'},
|
||||
'conn': {'data': {}},
|
||||
'connector': os_brick.initiator.connector.FakeConnector(None)}
|
||||
mock_detach_snapshot = self.mock_object(driver.BaseVD,
|
||||
'_detach_snapshot')
|
||||
mock_attach_snapshot = self.mock_object(driver.BaseVD,
|
||||
'_attach_snapshot')
|
||||
mock_attach_snapshot.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)
|
||||
mock_temporary_chown.assert_called_once_with('/dev/null')
|
||||
mock_attach_snapshot.assert_called_once_with(self.ctxt, snap,
|
||||
properties)
|
||||
mock_get_backup_device.assert_called_once_with(self.ctxt, backup, vol)
|
||||
mock_get_conn.assert_called_once_with()
|
||||
mock_detach_snapshot.assert_called_once_with(self.ctxt, attach_info,
|
||||
snap, properties, False)
|
||||
vol = objects.Volume.get_by_id(self.ctxt, vol_id)
|
||||
self.assertEqual('in-use', 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'])
|
||||
|
||||
@mock.patch('cinder.volume.utils.notify_about_backup_usage')
|
||||
def test_create_backup_with_notify(self, notify):
|
||||
"""Test normal backup creation with notifications."""
|
||||
|
@ -6027,73 +6027,6 @@ class GenericVolumeDriverTestCase(DriverTestCase):
|
||||
self.volume.driver._delete_temp_volume.assert_called_once_with(
|
||||
self.context, temp_vol)
|
||||
|
||||
@mock.patch.object(cinder.volume.driver.VolumeDriver,
|
||||
'backup_use_temp_snapshot',
|
||||
return_value=True)
|
||||
@mock.patch.object(utils, 'temporary_chown')
|
||||
@mock.patch('six.moves.builtins.open')
|
||||
@mock.patch.object(os_brick.initiator.connector.LocalConnector,
|
||||
'connect_volume')
|
||||
@mock.patch.object(os_brick.initiator.connector.LocalConnector,
|
||||
'check_valid_device',
|
||||
return_value=True)
|
||||
@mock.patch.object(os_brick.initiator.connector,
|
||||
'get_connector_properties',
|
||||
return_value={})
|
||||
@mock.patch.object(db.sqlalchemy.api, 'volume_get')
|
||||
def test_backup_volume_inuse_temp_snapshot(self, mock_volume_get,
|
||||
mock_get_connector_properties,
|
||||
mock_check_device,
|
||||
mock_connect_volume,
|
||||
mock_file_open,
|
||||
mock_temporary_chown,
|
||||
mock_temp_snapshot):
|
||||
vol = tests_utils.create_volume(self.context,
|
||||
status='backing-up',
|
||||
previous_status='in-use')
|
||||
self.context.user_id = fake.USER_ID
|
||||
self.context.project_id = fake.PROJECT_ID
|
||||
backup = tests_utils.create_backup(self.context,
|
||||
vol['id'])
|
||||
backup_obj = objects.Backup.get_by_id(self.context, backup.id)
|
||||
attach_info = {'device': {'path': '/dev/null'},
|
||||
'driver_volume_type': 'LOCAL',
|
||||
'data': {}}
|
||||
backup_service = mock.Mock()
|
||||
|
||||
self.volume.driver.terminate_connection_snapshot = mock.MagicMock()
|
||||
self.volume.driver.initialize_connection_snapshot = mock.MagicMock()
|
||||
self.volume.driver.create_snapshot = mock.MagicMock()
|
||||
self.volume.driver.delete_snapshot = mock.MagicMock()
|
||||
self.volume.driver.create_export_snapshot = mock.MagicMock()
|
||||
self.volume.driver.remove_export_snapshot = mock.MagicMock()
|
||||
|
||||
mock_volume_get.return_value = vol
|
||||
mock_connect_volume.return_value = {'type': 'local',
|
||||
'path': '/dev/null'}
|
||||
f = mock_file_open.return_value = open('/dev/null', 'rb')
|
||||
|
||||
self.volume.driver._connect_device
|
||||
backup_service.backup(backup_obj, f, None)
|
||||
self.volume.driver.initialize_connection_snapshot.return_value = (
|
||||
attach_info)
|
||||
self.volume.driver.create_export_snapshot.return_value = (
|
||||
{'provider_location': '/dev/null',
|
||||
'provider_auth': 'xxxxxxxx'})
|
||||
|
||||
self.volume.driver.backup_volume(self.context, backup_obj,
|
||||
backup_service)
|
||||
|
||||
mock_volume_get.assert_called_with(self.context, vol['id'])
|
||||
self.assertTrue(self.volume.driver.create_snapshot.called)
|
||||
self.assertTrue(self.volume.driver.create_export_snapshot.called)
|
||||
self.assertTrue(
|
||||
self.volume.driver.initialize_connection_snapshot.called)
|
||||
self.assertTrue(
|
||||
self.volume.driver.terminate_connection_snapshot.called)
|
||||
self.assertTrue(self.volume.driver.remove_export_snapshot.called)
|
||||
self.assertTrue(self.volume.driver.delete_snapshot.called)
|
||||
|
||||
@mock.patch.object(utils, 'temporary_chown')
|
||||
@mock.patch.object(os_brick.initiator.connector,
|
||||
'get_connector_properties')
|
||||
|
@ -278,6 +278,7 @@ iser_opts = [
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(volume_opts)
|
||||
CONF.register_opts(iser_opts)
|
||||
CONF.import_opt('backup_use_same_host', 'cinder.backup.api')
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
@ -995,7 +996,7 @@ class BaseVD(object):
|
||||
LOG.error(err_msg)
|
||||
raise exception.VolumeBackendAPIException(data=ex_msg)
|
||||
raise exception.VolumeBackendAPIException(data=err_msg)
|
||||
return (self._connect_device(conn), snapshot)
|
||||
return self._connect_device(conn)
|
||||
|
||||
def _connect_device(self, conn):
|
||||
# Use Brick's code to do attach/detach
|
||||
@ -1052,8 +1053,7 @@ class BaseVD(object):
|
||||
"""
|
||||
backup_device = None
|
||||
is_snapshot = False
|
||||
if (self.backup_use_temp_snapshot() and
|
||||
self.snapshot_remote_attachable()):
|
||||
if self.backup_use_temp_snapshot() and CONF.backup_use_same_host:
|
||||
(backup_device, is_snapshot) = (
|
||||
self._get_backup_volume_temp_snapshot(context, backup))
|
||||
else:
|
||||
|
Loading…
x
Reference in New Issue
Block a user