From 2143b39da53d3c1cd48e85ab73ca0ecfd0410107 Mon Sep 17 00:00:00 2001 From: Xing Yang Date: Fri, 7 Aug 2015 14:49:30 -0400 Subject: [PATCH] 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 --- ...2_add_provider_auth_column_to_snapshots.py | 35 +++ cinder/db/sqlalchemy/models.py | 1 + cinder/objects/snapshot.py | 1 + cinder/tests/unit/fake_driver.py | 12 + cinder/tests/unit/test_migrations.py | 9 + cinder/tests/unit/test_volume.py | 148 +++++++--- cinder/tests/unit/utils.py | 2 + cinder/volume/driver.py | 269 ++++++++++++++++-- cinder/volume/drivers/lvm.py | 2 +- 9 files changed, 418 insertions(+), 61 deletions(-) create mode 100644 cinder/db/sqlalchemy/migrate_repo/versions/052_add_provider_auth_column_to_snapshots.py diff --git a/cinder/db/sqlalchemy/migrate_repo/versions/052_add_provider_auth_column_to_snapshots.py b/cinder/db/sqlalchemy/migrate_repo/versions/052_add_provider_auth_column_to_snapshots.py new file mode 100644 index 000000000..dbfc231e0 --- /dev/null +++ b/cinder/db/sqlalchemy/migrate_repo/versions/052_add_provider_auth_column_to_snapshots.py @@ -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) diff --git a/cinder/db/sqlalchemy/models.py b/cinder/db/sqlalchemy/models.py index 52639711a..3011c9463 100644 --- a/cinder/db/sqlalchemy/models.py +++ b/cinder/db/sqlalchemy/models.py @@ -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, diff --git a/cinder/objects/snapshot.py b/cinder/objects/snapshot.py index 3e69d3eda..c48231389 100644 --- a/cinder/objects/snapshot.py +++ b/cinder/objects/snapshot.py @@ -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), } diff --git a/cinder/tests/unit/fake_driver.py b/cinder/tests/unit/fake_driver.py index 286c46898..e0221702b 100644 --- a/cinder/tests/unit/fake_driver.py +++ b/cinder/tests/unit/fake_driver.py @@ -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) diff --git a/cinder/tests/unit/test_migrations.py b/cinder/tests/unit/test_migrations.py index 589c68052..f0e5d5677 100644 --- a/cinder/tests/unit/test_migrations.py +++ b/cinder/tests/unit/test_migrations.py @@ -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) diff --git a/cinder/tests/unit/test_volume.py b/cinder/tests/unit/test_volume.py index bc6d5a52a..eb815323f 100644 --- a/cinder/tests/unit/test_volume.py +++ b/cinder/tests/unit/test_volume.py @@ -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): diff --git a/cinder/tests/unit/utils.py b/cinder/tests/unit/utils.py index 0f46fdec6..c3e161238 100644 --- a/cinder/tests/unit/utils.py +++ b/cinder/tests/unit/utils.py @@ -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) diff --git a/cinder/volume/driver.py b/cinder/volume/driver.py index f1489bcbc..9f4fda470 100644 --- a/cinder/volume/driver.py +++ b/cinder/volume/driver.py @@ -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. diff --git a/cinder/volume/drivers/lvm.py b/cinder/volume/drivers/lvm.py index a78038dfb..82ba49ecf 100644 --- a/cinder/volume/drivers/lvm.py +++ b/cinder/volume/drivers/lvm.py @@ -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() -- 2.45.2