Attach snapshot - driver only

This patch is a continuation of adding support for
non-disruptive backup. It provides a more efficient way
to backup an attached volume by creating a temp snapshot.
Since this is used internally for backup, the attach
snapshot interface is added in the driver only. For
drivers not implementing the attach snapshot interface,
backup will still be done using a volume.

Partial-implements blueprint non-disruptive-backup
Change-Id: I3649ef1d7c8a18f9d6ed0543d463354273d5f62a
This commit is contained in:
Xing Yang 2015-08-07 14:49:30 -04:00
parent 693ad7ce16
commit 2143b39da5
9 changed files with 418 additions and 61 deletions

View File

@ -0,0 +1,35 @@
# 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 sqlalchemy import Column
from sqlalchemy import MetaData, String, Table
def upgrade(migrate_engine):
"""Add provider_auth column to snapshots."""
meta = MetaData()
meta.bind = migrate_engine
snapshots = Table('snapshots', meta, autoload=True)
provider_auth = Column('provider_auth', String(255))
snapshots.create_column(provider_auth)
snapshots.update().values(provider_auth=None).execute()
def downgrade(migrate_engine):
"""Remove provider_auth column from snapshots."""
meta = MetaData()
meta.bind = migrate_engine
snapshots = Table('snapshots', meta, autoload=True)
provider_auth = snapshots.columns.provider_auth
snapshots.drop_column(provider_auth)

View File

@ -460,6 +460,7 @@ class Snapshot(BASE, CinderBase):
provider_location = Column(String(255))
provider_id = Column(String(255))
provider_auth = Column(String(255))
volume = relationship(Volume, backref="snapshots",
foreign_keys=volume_id,

View File

@ -57,6 +57,7 @@ class Snapshot(base.CinderPersistentObject, base.CinderObject,
'provider_location': fields.StringField(nullable=True),
'provider_id': fields.UUIDField(nullable=True),
'metadata': fields.DictOfStringsField(),
'provider_auth': fields.StringField(nullable=True),
'volume': fields.ObjectField('Volume', nullable=True),
}

View File

@ -143,6 +143,18 @@ class LoggingVolumeDriver(driver.VolumeDriver):
def terminate_connection(self, volume, connector):
self.log_action('terminate_connection', volume)
def create_export_snapshot(self, context, snapshot):
self.log_action('create_export_snapshot', snapshot)
def remove_export_snapshot(self, context, snapshot):
self.log_action('remove_export_snapshot', snapshot)
def initialize_connection_snapshot(self, snapshot, connector):
self.log_action('initialize_connection_snapshot', snapshot)
def terminate_connection_snapshot(self, snapshot, connector):
self.log_action('terminate_connection_snapshot', snapshot)
def create_cloned_volume(self, volume, src_vol):
self.log_action('create_cloned_volume', volume)

View File

@ -856,6 +856,15 @@ class MigrationsMixin(test_migrations.WalkVersionsMixin):
consistencygroups = db_utils.get_table(engine, 'consistencygroups')
self.assertNotIn('source_cgid', consistencygroups.c)
def _check_052(self, engine, data):
snapshots = db_utils.get_table(engine, 'snapshots')
self.assertIsInstance(snapshots.c.provider_auth.type,
sqlalchemy.types.VARCHAR)
def _post_downgrade_052(self, engine):
snapshots = db_utils.get_table(engine, 'snapshots')
self.assertNotIn('provider_auth', snapshots.c)
def test_walk_versions(self):
self.walk_versions(True, False)

View File

@ -5635,7 +5635,15 @@ class GenericVolumeDriverTestCase(DriverTestCase):
"""Test case for VolumeDriver."""
driver_name = "cinder.tests.unit.fake_driver.LoggingVolumeDriver"
def test_backup_volume(self):
@mock.patch.object(utils, 'temporary_chown')
@mock.patch.object(fileutils, 'file_open')
@mock.patch.object(os_brick.initiator.connector,
'get_connector_properties')
@mock.patch.object(db, 'volume_get')
def test_backup_volume_available(self, mock_volume_get,
mock_get_connector_properties,
mock_file_open,
mock_temporary_chown):
vol = tests_utils.create_volume(self.context)
self.context.user_id = 'fake'
self.context.project_id = 'fake'
@ -5644,48 +5652,38 @@ class GenericVolumeDriverTestCase(DriverTestCase):
backup_obj = objects.Backup.get_by_id(self.context, backup.id)
properties = {}
attach_info = {'device': {'path': '/dev/null'}}
backup_service = self.mox.CreateMock(backup_driver.BackupDriver)
root_helper = 'sudo cinder-rootwrap /etc/cinder/rootwrap.conf'
self.mox.StubOutWithMock(self.volume.driver.db, 'volume_get')
self.mox.StubOutWithMock(os_brick.initiator.connector,
'get_connector_properties')
self.mox.StubOutWithMock(self.volume.driver, '_attach_volume')
self.mox.StubOutWithMock(os, 'getuid')
self.mox.StubOutWithMock(utils, 'execute')
self.mox.StubOutWithMock(fileutils, 'file_open')
self.mox.StubOutWithMock(self.volume.driver, '_detach_volume')
self.mox.StubOutWithMock(self.volume.driver, 'terminate_connection')
backup_service = mock.Mock()
self.volume.driver._attach_volume = mock.MagicMock()
self.volume.driver._detach_volume = mock.MagicMock()
self.volume.driver.terminate_connection = mock.MagicMock()
self.volume.driver.create_snapshot = mock.MagicMock()
self.volume.driver.delete_snapshot = mock.MagicMock()
mock_volume_get.return_value = vol
mock_get_connector_properties.return_value = properties
f = mock_file_open.return_value = file('/dev/null')
backup_service.backup(backup_obj, f, None)
self.volume.driver._attach_volume.return_value = attach_info, vol
self.volume.driver.db.volume_get(self.context, vol['id']).\
AndReturn(vol)
os_brick.initiator.connector.\
get_connector_properties(root_helper, CONF.my_ip, False, False).\
AndReturn(properties)
self.volume.driver._attach_volume(self.context, vol, properties).\
AndReturn((attach_info, vol))
os.getuid()
utils.execute('chown', None, '/dev/null', run_as_root=True)
f = fileutils.file_open('/dev/null').AndReturn(file('/dev/null'))
backup_service.backup(backup_obj, f)
utils.execute('chown', 0, '/dev/null', run_as_root=True)
self.volume.driver._detach_volume(self.context, attach_info, vol,
properties)
self.mox.ReplayAll()
self.volume.driver.backup_volume(self.context, backup_obj,
backup_service)
self.mox.UnsetStubs()
mock_volume_get.assert_called_with(self.context, vol['id'])
@mock.patch.object(utils, 'temporary_chown')
@mock.patch.object(fileutils, 'file_open')
@mock.patch.object(os_brick.initiator.connector,
'get_connector_properties')
@mock.patch.object(db, 'volume_get')
def test_backup_volume_inuse(self, mock_volume_get,
mock_get_connector_properties,
mock_file_open,
mock_temporary_chown):
vol = tests_utils.create_volume(self.context)
vol['previous_status'] = 'in-use'
def test_backup_volume_inuse_temp_volume(self, mock_volume_get,
mock_get_connector_properties,
mock_file_open,
mock_temporary_chown):
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'
@ -5700,7 +5698,7 @@ class GenericVolumeDriverTestCase(DriverTestCase):
self.volume.driver._detach_volume = mock.MagicMock()
self.volume.driver.terminate_connection = mock.MagicMock()
self.volume.driver._create_temp_cloned_volume = mock.MagicMock()
self.volume.driver._delete_volume = mock.MagicMock()
self.volume.driver._delete_temp_volume = mock.MagicMock()
mock_volume_get.return_value = vol
self.volume.driver._create_temp_cloned_volume.return_value = temp_vol
@ -5716,8 +5714,75 @@ class GenericVolumeDriverTestCase(DriverTestCase):
mock_volume_get.assert_called_with(self.context, vol['id'])
self.volume.driver._create_temp_cloned_volume.assert_called_once_with(
self.context, vol)
self.volume.driver._delete_volume.assert_called_once_with(self.context,
temp_vol)
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.object(fileutils, 'file_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, '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'
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)
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 = file('/dev/null')
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)
def test_restore_backup(self):
vol = tests_utils.create_volume(self.context)
@ -6252,8 +6317,9 @@ class LVMVolumeDriverTestCase(DriverTestCase):
mock_file_open,
mock_temporary_chown):
vol = tests_utils.create_volume(self.context)
vol['previous_status'] = 'in-use'
vol = tests_utils.create_volume(self.context,
status='backing-up',
previous_status='in-use')
self.context.user_id = 'fake'
self.context.project_id = 'fake'
@ -6270,7 +6336,7 @@ class LVMVolumeDriverTestCase(DriverTestCase):
self.volume.driver._attach_volume = mock.MagicMock()
self.volume.driver.terminate_connection = mock.MagicMock()
self.volume.driver._create_temp_snapshot = mock.MagicMock()
self.volume.driver._delete_snapshot = mock.MagicMock()
self.volume.driver._delete_temp_snapshot = mock.MagicMock()
mock_get_connector_properties.return_value = properties
f = mock_file_open.return_value = file('/dev/null')
@ -6285,7 +6351,7 @@ class LVMVolumeDriverTestCase(DriverTestCase):
mock_volume_get.assert_called_with(self.context, vol['id'])
self.volume.driver._create_temp_snapshot.assert_called_once_with(
self.context, vol)
self.volume.driver._delete_snapshot.assert_called_once_with(
self.volume.driver._delete_temp_snapshot.assert_called_once_with(
self.context, temp_snapshot)
def test_create_volume_from_snapshot_none_sparse(self):

View File

@ -39,6 +39,7 @@ def create_volume(ctxt,
replication_extended_status=None,
replication_driver_data=None,
consistencygroup_id=None,
previous_status=None,
**kwargs):
"""Create a volume object in the DB."""
vol = {}
@ -61,6 +62,7 @@ def create_volume(ctxt,
vol['replication_status'] = replication_status
vol['replication_extended_status'] = replication_extended_status
vol['replication_driver_data'] = replication_driver_data
vol['previous_status'] = previous_status
return db.volume_create(ctxt, vol)

View File

@ -379,6 +379,49 @@ class BaseVD(object):
raise exception.RemoveExportException(volume=volume['id'],
reason=ex)
def _detach_snapshot(self, context, attach_info, snapshot, properties,
force=False, remote=False):
"""Disconnect the snapshot from the host."""
# Use Brick's code to do attach/detach
connector = attach_info['connector']
connector.disconnect_volume(attach_info['conn']['data'],
attach_info['device'])
# NOTE(xyang): This method is introduced for non-disruptive backup.
# Currently backup service has to be on the same node as the volume
# driver. Therefore it is not possible to call a volume driver on a
# remote node. In the future, if backup can be done from a remote
# node, this function can be modified to allow RPC calls. The remote
# flag in the interface is for anticipation that it will be enabled
# in the future.
if remote:
LOG.exception(_LE("Detaching snapshot from a remote node "
"is not supported."))
raise exception.NotSupportedOperation(
operation=_("detach snapshot from remote node"))
else:
# Call local driver's terminate_connection and remove export.
# NOTE(avishay) This is copied from the manager's code - need to
# clean this up in the future.
try:
self.terminate_connection_snapshot(snapshot, properties,
force=force)
except Exception as err:
err_msg = (_('Unable to terminate volume connection: %(err)s')
% {'err': six.text_type(err)})
LOG.error(err_msg)
raise exception.VolumeBackendAPIException(data=err_msg)
try:
LOG.debug("Snapshot %s: removing export.", snapshot.id)
self.remove_export_snapshot(context, snapshot)
except Exception as ex:
LOG.exception(_LE("Error detaching snapshot %(snapshot)s, "
"due to remove export failure."),
{"snapshot": snapshot.id})
raise exception.RemoveExportException(volume=snapshot.id,
reason=ex)
def set_execute(self, execute):
self._execute = execute
@ -714,6 +757,64 @@ class BaseVD(object):
raise exception.VolumeBackendAPIException(data=err_msg)
return (self._connect_device(conn), volume)
def _attach_snapshot(self, context, snapshot, properties, remote=False):
"""Attach the snapshot."""
# NOTE(xyang): This method is introduced for non-disruptive backup.
# Currently backup service has to be on the same node as the volume
# driver. Therefore it is not possible to call a volume driver on a
# remote node. In the future, if backup can be done from a remote
# node, this function can be modified to allow RPC calls. The remote
# flag in the interface is for anticipation that it will be enabled
# in the future.
if remote:
LOG.exception(_LE("Attaching snapshot from a remote node "
"is not supported."))
raise exception.NotSupportedOperation(
operation=_("attach snapshot from remote node"))
else:
# Call local driver's create_export and initialize_connection.
# NOTE(avishay) This is copied from the manager's code - need to
# clean this up in the future.
model_update = None
try:
LOG.debug("Snapshot %s: creating export.", snapshot.id)
model_update = self.create_export_snapshot(context, snapshot,
properties)
if model_update:
snapshot.provider_location = model_update.get(
'provider_location', None)
snapshot.provider_auth = model_update.get(
'provider_auth', None)
snapshot.save()
except exception.CinderException as ex:
if model_update:
LOG.exception(_LE("Failed updating model of snapshot "
"%(snapshot_id)s with driver provided "
"model %(model)s."),
{'snapshot_id': snapshot.id,
'model': model_update})
raise exception.ExportFailure(reason=ex)
try:
conn = self.initialize_connection_snapshot(
snapshot, properties)
except Exception as err:
try:
err_msg = (_('Unable to fetch connection information from '
'backend: %(err)s') %
{'err': six.text_type(err)})
LOG.error(err_msg)
LOG.debug("Cleaning up failed connect initialization.")
self.remove_export_snapshot(context, snapshot)
except Exception as ex:
ex_msg = (_('Error encountered during cleanup '
'of a failed attach: %(ex)s') %
{'ex': six.text_type(ex)})
LOG.error(err_msg)
raise exception.VolumeBackendAPIException(data=ex_msg)
raise exception.VolumeBackendAPIException(data=err_msg)
return (self._connect_device(conn), snapshot)
def _connect_device(self, conn):
# Use Brick's code to do attach/detach
use_multipath = self.configuration.use_multipath_for_image_xfer
@ -744,49 +845,112 @@ class BaseVD(object):
image_service):
return None, False
def backup_use_temp_snapshot(self):
return False
def backup_volume(self, context, backup, backup_service):
"""Create a new backup from an existing volume."""
if self.backup_use_temp_snapshot():
self._backup_volume_temp_snapshot(context, backup,
backup_service)
else:
self._backup_volume_temp_volume(context, backup,
backup_service)
def _backup_volume_temp_volume(self, context, backup, backup_service):
"""Create a new backup from an existing volume.
For in-use volume, create a temp volume and back it up.
"""
volume = self.db.volume_get(context, backup.volume_id)
LOG.debug('Creating a new backup for volume %s.', volume['name'])
# NOTE(xyang): Check volume status; if not 'available', create a
# a temp volume from the volume, and backup the temp volume, and
# NOTE(xyang): Check volume status; 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', None)
device_to_backup = volume
temp_vol_ref = None
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()
volume = temp_vol_ref
device_to_backup = temp_vol_ref
self._backup_device(context, backup, backup_service, device_to_backup)
if temp_vol_ref:
self._delete_temp_volume(context, temp_vol_ref)
backup.temp_volume_id = None
backup.save()
def _backup_volume_temp_snapshot(self, context, backup, backup_service):
"""Create a new backup from an existing volume.
For in-use volume, create a temp snapshot and back it up.
"""
volume = self.db.volume_get(context, backup.volume_id)
LOG.debug('Creating a new backup for volume %s.', volume['name'])
# NOTE(xyang): Check volume status; if 'in-use', create a temp
# snapshot from the source volume, backup the temp snapshot, and
# then clean up the temp snapshot; if 'available', just backup the
# volume.
previous_status = volume.get('previous_status', None)
device_to_backup = volume
is_snapshot = False
temp_snapshot = None
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
self._backup_device(context, backup, backup_service, device_to_backup,
is_snapshot)
if temp_snapshot:
self._delete_temp_snapshot(context, temp_snapshot)
backup.temp_snapshot_id = None
backup.save()
def _backup_device(self, context, backup, backup_service, device,
is_snapshot=False):
"""Create a new backup from a volume or snapshot."""
LOG.debug('Creating a new backup for %s.', device['name'])
use_multipath = self.configuration.use_multipath_for_image_xfer
enforce_multipath = self.configuration.enforce_multipath_for_image_xfer
properties = utils.brick_get_connector_properties(use_multipath,
enforce_multipath)
attach_info, volume = self._attach_volume(context, volume, properties)
if is_snapshot:
attach_info, device = self._attach_snapshot(context, device,
properties)
else:
attach_info, device = self._attach_volume(context, device,
properties)
try:
volume_path = attach_info['device']['path']
device_path = attach_info['device']['path']
# Secure network file systems will not chown files.
if self.secure_file_operations_enabled():
with fileutils.file_open(volume_path) as volume_file:
backup_service.backup(backup, volume_file)
with fileutils.file_open(device_path) as device_file:
backup_service.backup(backup, device_file)
else:
with utils.temporary_chown(volume_path):
with fileutils.file_open(volume_path) as volume_file:
backup_service.backup(backup, volume_file)
with utils.temporary_chown(device_path):
with fileutils.file_open(device_path) as device_file:
backup_service.backup(backup, device_file)
finally:
self._detach_volume(context, attach_info, volume, properties)
if temp_vol_ref:
self._delete_volume(context, temp_vol_ref)
backup.temp_volume_id = None
backup.save()
if is_snapshot:
self._detach_snapshot(context, attach_info, device, properties)
else:
self._detach_volume(context, attach_info, device, properties)
def restore_backup(self, context, backup, volume, backup_service):
"""Restore an existing backup to a new or existing volume."""
@ -867,15 +1031,16 @@ class BaseVD(object):
{'status': 'available'})
return temp_vol_ref
def _delete_snapshot(self, context, snapshot):
def _delete_temp_snapshot(self, context, snapshot):
self.delete_snapshot(snapshot)
with snapshot.obj_as_admin():
self.db.volume_glance_metadata_delete_by_snapshot(
context, snapshot.id)
snapshot.destroy()
def _delete_volume(self, context, volume):
def _delete_temp_volume(self, context, volume):
self.delete_volume(volume)
context = context.elevated()
self.db.volume_destroy(context, volume['id'])
def clear_download(self, context, volume):
@ -937,11 +1102,23 @@ class BaseVD(object):
"""
return
def create_export_snapshot(self, context, snapshot, connector):
"""Exports the snapshot.
Can optionally return a Dictionary of changes
to the snapshot object to be persisted.
"""
return
@abc.abstractmethod
def remove_export(self, context, volume):
"""Removes an export for a volume."""
return
def remove_export_snapshot(self, context, snapshot):
"""Removes an export for a snapshot."""
return
@abc.abstractmethod
def initialize_connection(self, volume, connector, initiator_data=None):
"""Allow connection to connector and return connection info.
@ -963,9 +1140,30 @@ class BaseVD(object):
"""
return
def initialize_connection_snapshot(self, snapshot, connector, **kwargs):
"""Allow connection to connector and return connection info.
:param snapshot: The snapshot to be attached
:param connector: Dictionary containing information about what is being
connected to.
:returns conn_info: A dictionary of connection information. This can
optionally include a "initiator_updates" field.
The "initiator_updates" field must be a dictionary containing a
"set_values" and/or "remove_values" field. The "set_values" field must
be a dictionary of key-value pairs to be set/updated in the db. The
"remove_values" field must be a list of keys, previously set with
"set_values", that will be deleted from the db.
"""
return
@abc.abstractmethod
def terminate_connection(self, volume, connector, **kwargs):
"""Disallow connection from connector"""
"""Disallow connection from connector."""
return
def terminate_connection_snapshot(self, snapshot, connector, **kwargs):
"""Disallow connection from connector."""
return
def get_pool(self, volume):
@ -1366,15 +1564,27 @@ class VolumeDriver(ConsistencyGroupVD, TransferVD, ManageableVD, ExtendVD,
def create_export(self, context, volume, connector):
raise NotImplementedError()
def create_export_snapshot(self, context, snapshot, connector):
raise NotImplementedError()
def remove_export(self, context, volume):
raise NotImplementedError()
def remove_export_snapshot(self, context, snapshot):
raise NotImplementedError()
def initialize_connection(self, volume, connector):
raise NotImplementedError()
def initialize_connection_snapshot(self, snapshot, connector, **kwargs):
"""Allow connection from connector for a snapshot."""
def terminate_connection(self, volume, connector, **kwargs):
"""Disallow connection from connector"""
def terminate_connection_snapshot(self, snapshot, connector, **kwargs):
"""Disallow connection from connector for a snapshot."""
def create_consistencygroup(self, context, group):
"""Creates a consistencygroup."""
raise NotImplementedError()
@ -1780,9 +1990,18 @@ class FakeISCSIDriver(ISCSIDriver):
'discard': False,
}
def initialize_connection_snapshot(self, snapshot, connector):
return {
'driver_volume_type': 'iscsi',
'data': {'access_mode': 'rw'}
}
def terminate_connection(self, volume, connector, **kwargs):
pass
def terminate_connection_snapshot(self, snapshot, connector, **kwargs):
pass
@staticmethod
def fake_execute(cmd, *_args, **_kwargs):
"""Execute that simply logs the command."""
@ -1824,10 +2043,22 @@ class FakeISCSIDriver(ISCSIDriver):
"""
pass
def create_export_snapshot(self, context, snapshot, connector):
"""Exports the snapshot.
Can optionally return a Dictionary of changes to the snapshot object to
be persisted.
"""
pass
def remove_export(self, context, volume):
"""Removes an export for a volume."""
pass
def remove_export_snapshot(self, context, snapshot):
"""Removes an export for a snapshot."""
pass
class ISERDriver(ISCSIDriver):
"""Executes commands relating to ISER volumes.

View File

@ -488,7 +488,7 @@ class LVMVolumeDriver(driver.VolumeDriver):
backup_service.backup(backup, volume_file)
finally:
if temp_snapshot:
self._delete_snapshot(context, temp_snapshot)
self._delete_temp_snapshot(context, temp_snapshot)
backup.temp_snapshot_id = None
backup.save()