From 907550015e0480fac3e69c5fc73f11f825723bf4 Mon Sep 17 00:00:00 2001 From: Alexander Deiter Date: Mon, 23 May 2022 21:36:35 +0400 Subject: [PATCH] Fix Infinidat driver to backup attached volume For some historical reason, the Infinidat Cinder driver does not allow to backup an attached volume. This makes it impossible to create a backup for most use cases. As a possible solution we can create and use snapshots as a backup source. This patch implements attach/detach for snapshots and the ability to use snapshots to perform non-disruptive backups. Added functions: * initialize_connection_snapshot() * terminate_connection_snapshot() * create_export_snapshot() * remove_export_snapshot() * backup_use_temp_snapshot() And changed functions: * create_volume_from_snapshot() * create_cloned_volume() remove the duplicated code and use the temporary snapshot as the source for creating the cloned volume. Closes-bug: #1983287 Change-Id: I0e1534c6015eaa8baef48b7878324625a4cc9f1a Signed-off-by: Alexander Deiter --- .../unit/volume/drivers/test_infinidat.py | 192 ++++++++++++++---- cinder/volume/drivers/infinidat.py | 162 ++++++++------- ...ckup-attached-volume-b28e5dd5c25a24ec.yaml | 6 + 3 files changed, 242 insertions(+), 118 deletions(-) create mode 100644 releasenotes/notes/bug-1983287-infinidat-fix-backup-attached-volume-b28e5dd5c25a24ec.yaml diff --git a/cinder/tests/unit/volume/drivers/test_infinidat.py b/cinder/tests/unit/volume/drivers/test_infinidat.py index 3142ed8eb58..c917e2e3ad6 100644 --- a/cinder/tests/unit/volume/drivers/test_infinidat.py +++ b/cinder/tests/unit/volume/drivers/test_infinidat.py @@ -14,11 +14,14 @@ # under the License. """Unit tests for INFINIDAT InfiniBox volume driver.""" +import collections import copy import functools +import itertools import platform import socket from unittest import mock +import uuid import ddt from oslo_utils import units @@ -215,6 +218,14 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase): self._mock_host.set_metadata_from_dict.assert_called_once_with( self._generate_mock_host_metadata()) + @mock.patch('cinder.volume.drivers.infinidat.InfiniboxVolumeDriver.' + '_get_oslo_driver_opts') + def test_get_driver_options(self, _get_oslo_driver_opts): + _get_oslo_driver_opts.return_value = [] + result = self.driver.get_driver_options() + actual = (infinidat.infinidat_opts) + self.assertEqual(actual, result) + @skip_driver_setup def test__setup_and_get_system_object(self): # This test should skip the driver setup, as it generates more calls to @@ -230,6 +241,12 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase): infinidat._INFINIDAT_CINDER_IDENTIFIER) self._system.login.assert_called_once() + @skip_driver_setup + @mock.patch('cinder.volume.drivers.infinidat.infinisdk', None) + def test_do_setup_no_infinisdk(self): + self.assertRaises(exception.VolumeDriverException, + self.driver.do_setup, None) + @mock.patch('cinder.volume.drivers.infinidat.infinisdk.InfiniBox') @ddt.data(True, False) def test_ssl_options(self, use_ssl, infinibox): @@ -240,9 +257,31 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase): infinibox.assert_called_once_with(self.configuration.san_ip, auth=auth, use_ssl=use_ssl) - def test_initialize_connection(self): + def test_create_export_snapshot(self): + self.assertIsNone(self.driver.create_export_snapshot( + None, test_snapshot, test_connector)) + + def test_remove_export_snapshot(self): + self.assertIsNone(self.driver.remove_export_snapshot( + None, test_snapshot)) + + def test_backup_use_temp_snapshot(self): + self.assertTrue(self.driver.backup_use_temp_snapshot()) + + @mock.patch('cinder.volume.drivers.infinidat.InfiniboxVolumeDriver.' + '_get_infinidat_snapshot') + def test_initialize_connection_snapshot(self, get_snapshot): + result = self.driver.initialize_connection_snapshot( + test_snapshot, test_connector) + get_snapshot.assert_called_once_with(test_snapshot) + self.assertEqual(1, result["data"]["target_lun"]) + + @mock.patch('cinder.volume.drivers.infinidat.InfiniboxVolumeDriver.' + '_get_infinidat_volume') + def test_initialize_connection(self, get_volume): self._system.hosts.safe_get.return_value = None result = self.driver.initialize_connection(test_volume, test_connector) + get_volume.assert_called_once_with(test_volume) self.assertEqual(1, result["data"]["target_lun"]) def test_initialize_connection_host_exists(self): @@ -257,6 +296,13 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase): result = self.driver.initialize_connection(test_volume, test_connector) self.assertEqual(888, result["data"]["target_lun"]) + def test_initialize_connection_mapping_not_found(self): + mock_mapping = mock.Mock() + mock_mapping.get_volume.return_value = None + self._mock_host.get_luns.return_value = [mock_mapping] + result = self.driver.initialize_connection(test_volume, test_connector) + self.assertEqual(TEST_LUN, result["data"]["target_lun"]) + def test_initialize_connection_volume_doesnt_exist(self): self._system.volumes.safe_get.return_value = None self.assertRaises(exception.VolumeNotFound, @@ -300,11 +346,37 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase): self.assertFalse(self.driver._is_volume_multiattached(volume, connector)) - def test_terminate_connection(self): + @mock.patch('cinder.volume.drivers.infinidat.InfiniboxVolumeDriver.' + '_terminate_connection') + @mock.patch('cinder.volume.drivers.infinidat.InfiniboxVolumeDriver.' + '_get_infinidat_volume') + @mock.patch('cinder.volume.drivers.infinidat.InfiniboxVolumeDriver.' + '_is_volume_multiattached') + def test_terminate_connection(self, volume_multiattached, get_volume, + terminate_connection): volume = copy.deepcopy(test_volume) volume.volume_attachment = [test_attachment1] + volume_multiattached.return_value = False + get_volume.return_value = self._mock_volume self.assertFalse(self.driver.terminate_connection(volume, test_connector)) + volume_multiattached.assert_called_once_with(volume, test_connector) + get_volume.assert_called_once_with(volume) + terminate_connection.assert_called_once_with(self._mock_volume, + test_connector) + + @mock.patch('cinder.volume.drivers.infinidat.InfiniboxVolumeDriver.' + '_terminate_connection') + @mock.patch('cinder.volume.drivers.infinidat.InfiniboxVolumeDriver.' + '_get_infinidat_snapshot') + def test_terminate_connection_snapshot(self, get_snapshot, + terminate_connection): + get_snapshot.return_value = self._mock_snapshot + self.assertIsNone(self.driver.terminate_connection_snapshot( + test_snapshot, test_connector)) + get_snapshot.assert_called_once_with(test_snapshot) + terminate_connection.assert_called_once_with(self._mock_snapshot, + test_connector) def test_terminate_connection_delete_host(self): self._mock_host.get_luns.return_value = [object()] @@ -470,7 +542,7 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase): test_clone, test_snapshot) def test_create_volume_from_snapshot_create_fails(self): - self._mock_volume.create_snapshot.side_effect = self._raise_infinisdk + self._system.volumes.create.side_effect = self._raise_infinisdk self.assertRaises(exception.VolumeBackendAPIException, self.driver.create_volume_from_snapshot, test_clone, test_snapshot) @@ -484,13 +556,18 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase): self.driver.create_volume_from_snapshot, test_clone, test_snapshot) - @mock.patch("cinder.volume.volume_utils.copy_volume") - @mock.patch("cinder.volume.volume_utils.brick_get_connector") - @mock.patch("cinder.volume.volume_utils.brick_get_connector_properties", - return_value=test_connector) - def test_create_volume_from_snapshot_delete_clone_fails(self, *mocks): - self._mock_volume.delete.side_effect = self._raise_infinisdk - self.assertRaises(exception.VolumeBackendAPIException, + @mock.patch('cinder.volume.volume_utils.brick_get_connector') + @mock.patch('cinder.volume.volume_types.get_volume_type_qos_specs') + @mock.patch('cinder.volume.volume_utils.brick_get_connector_properties') + @mock.patch('cinder.volume.drivers.infinidat.InfiniboxVolumeDriver.' + '_connect_device') + def test_create_volume_from_snapshot_connect_fails(self, connect_device, + connector_properties, + *mocks): + connector_properties.return_value = test_connector + connect_device.side_effect = exception.DeviceUnavailable( + path='/dev/sdb', reason='Block device required') + self.assertRaises(exception.DeviceUnavailable, self.driver.create_volume_from_snapshot, test_clone, test_snapshot) @@ -507,21 +584,28 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase): self.assertRaises(exception.VolumeBackendAPIException, self.driver.delete_snapshot, test_snapshot) - @mock.patch("cinder.volume.volume_utils.copy_volume") - @mock.patch("cinder.volume.volume_utils.brick_get_connector") - @mock.patch("cinder.volume.volume_utils.brick_get_connector_properties", - return_value=test_connector) - @mock.patch("cinder.volume.volume_types.get_volume_type_qos_specs") - def test_create_cloned_volume(self, *mocks): + @mock.patch('cinder.volume.drivers.infinidat.InfiniboxVolumeDriver.' + 'delete_snapshot') + @mock.patch('cinder.volume.drivers.infinidat.InfiniboxVolumeDriver.' + 'create_volume_from_snapshot') + @mock.patch('cinder.volume.drivers.infinidat.InfiniboxVolumeDriver.' + 'create_snapshot') + @mock.patch('uuid.uuid4') + def test_create_cloned_volume(self, mock_uuid, create_snapshot, + create_volume_from_snapshot, + delete_snapshot): + mock_uuid.return_value = uuid.UUID(test_snapshot.id) + snapshot_attributes = ('id', 'name', 'volume') + Snapshot = collections.namedtuple('Snapshot', snapshot_attributes) + snapshot_id = test_snapshot.id + snapshot_name = self.configuration.snapshot_name_template % snapshot_id + snapshot = Snapshot(id=snapshot_id, name=snapshot_name, + volume=test_volume) self.driver.create_cloned_volume(test_clone, test_volume) - - def test_create_cloned_volume_volume_already_mapped(self): - mock_mapping = mock.Mock() - mock_mapping.get_volume.return_value = self._mock_volume - self._mock_volume.get_logical_units.return_value = [mock_mapping] - self.assertRaises(exception.VolumeBackendAPIException, - self.driver.create_cloned_volume, - test_clone, test_volume) + create_snapshot.assert_called_once_with(snapshot) + create_volume_from_snapshot.assert_called_once_with(test_clone, + snapshot) + delete_snapshot.assert_called_once_with(snapshot) def test_create_cloned_volume_create_fails(self): self._system.volumes.create.side_effect = self._raise_infinisdk @@ -724,21 +808,6 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase): self.driver.delete_group_snapshot, None, test_snapgroup, [test_snapshot]) - def test_terminate_connection_force_detach(self): - mock_infinidat_host = mock.Mock() - mock_infinidat_host.get_ports.return_value = [ - self._wwn.WWN(TEST_WWN_1)] - mock_mapping = mock.Mock() - mock_mapping.get_host.return_value = mock_infinidat_host - self._mock_volume.get_logical_units.return_value = [mock_mapping] - volume = copy.deepcopy(test_volume) - volume.volume_attachment = [test_attachment1, test_attachment2] - # connector is None - force detach - detach all mappings - self.assertTrue(self.driver.terminate_connection(volume, None)) - # make sure we actually detached the host mapping - self._mock_host.unmap_volume.assert_called_once() - self._mock_host.safe_delete.assert_called_once() - def test_snapshot_revert_use_temp_snapshot(self): result = self.driver.snapshot_revert_use_temp_snapshot() self.assertFalse(result) @@ -1123,6 +1192,18 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase): @ddt.ddt class InfiniboxDriverTestCaseFC(InfiniboxDriverTestCaseBase): + @ddt.data(*itertools.product(('UP', 'DOWN'), ('OK', 'ERROR'))) + @ddt.unpack + def test_initialize_connection_nodes_ports(self, link_state, port_state): + node = mock.Mock() + port = mock.Mock() + port.get_link_state.return_value = link_state + port.get_state.return_value = port_state + node.get_fc_ports.return_value = [port] + self._system.components.nodes.get_all.return_value = [node] + result = self.driver.initialize_connection(test_volume, test_connector) + self.assertEqual(1, result["data"]["target_lun"]) + def test_initialize_connection_multiple_wwpns(self): connector = {'wwpns': [TEST_WWN_1, TEST_WWN_2]} result = self.driver.initialize_connection(test_volume, connector) @@ -1154,6 +1235,19 @@ class InfiniboxDriverTestCaseFC(InfiniboxDriverTestCaseBase): self.assertTrue(self.driver.terminate_connection(volume, test_connector)) + def test_terminate_connection_force_detach(self): + mock_infinidat_host = mock.Mock() + mock_infinidat_host.get_ports.return_value = [ + self._wwn.WWN(TEST_WWN_1)] + mock_mapping = mock.Mock() + mock_mapping.get_host.return_value = mock_infinidat_host + self._mock_volume.get_logical_units.return_value = [mock_mapping] + volume = copy.deepcopy(test_volume) + volume.volume_attachment = [test_attachment1, test_attachment2] + self.assertTrue(self.driver.terminate_connection(volume, None)) + self._mock_host.unmap_volume.assert_called_once() + self._mock_host.safe_delete.assert_called_once() + @ddt.ddt class InfiniboxDriverTestCaseISCSI(InfiniboxDriverTestCaseBase): @@ -1356,6 +1450,19 @@ class InfiniboxDriverTestCaseISCSI(InfiniboxDriverTestCaseBase): self.assertFalse(self.driver.terminate_connection(volume, test_connector)) + def test_terminate_connection_force_detach(self): + mock_infinidat_host = mock.Mock() + mock_infinidat_host.get_ports.return_value = [ + self._iqn.IQN(TEST_TARGET_IQN)] + mock_mapping = mock.Mock() + mock_mapping.get_host.return_value = mock_infinidat_host + self._mock_volume.get_logical_units.return_value = [mock_mapping] + volume = copy.deepcopy(test_volume) + volume.volume_attachment = [test_attachment1, test_attachment2] + self.assertTrue(self.driver.terminate_connection(volume, None)) + self._mock_host.unmap_volume.assert_called_once() + self._mock_host.safe_delete.assert_called_once() + def test_validate_connector(self): fc_connector = {'wwpns': [TEST_WWN_1, TEST_WWN_2]} iscsi_connector = {'initiator': TEST_INITIATOR_IQN} @@ -1365,6 +1472,13 @@ class InfiniboxDriverTestCaseISCSI(InfiniboxDriverTestCaseBase): class InfiniboxDriverTestCaseQoS(InfiniboxDriverTestCaseBase): + @mock.patch("cinder.volume.volume_types.get_volume_type_qos_specs") + def test_no_qos(self, qos_specs): + qos_specs.return_value = None + self.driver.create_volume(test_volume) + self._system.qos_policies.create.assert_not_called() + self._mock_qos_policy.assign_entity.assert_not_called() + @mock.patch("cinder.volume.volume_types.get_volume_type_qos_specs") def test_qos_max_ipos(self, qos_specs): qos_specs.return_value = {'qos_specs': {'id': 'qos_name', diff --git a/cinder/volume/drivers/infinidat.py b/cinder/volume/drivers/infinidat.py index 741c48019f0..edba38c0fef 100644 --- a/cinder/volume/drivers/infinidat.py +++ b/cinder/volume/drivers/infinidat.py @@ -14,12 +14,12 @@ # under the License. """INFINIDAT InfiniBox Volume Driver.""" +import collections from contextlib import contextmanager import functools import math import platform import socket -from unittest import mock import uuid from oslo_config import cfg @@ -129,10 +129,11 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver): 1.12 - fixed volume multi-attach 1.13 - fixed consistency groups feature 1.14 - added storage assisted volume migration + 1.15 - fixed backup for attached volume """ - VERSION = '1.14' + VERSION = '1.15' # ThirdPartySystems wiki page CI_WIKI_NAME = "INFINIDAT_CI" @@ -390,8 +391,7 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver): port.get_state() == 'OK'): yield str(port.get_wwpn()) - def _initialize_connection_fc(self, volume, connector): - infinidat_volume = self._get_infinidat_volume(volume) + def _initialize_connection_fc(self, infinidat_volume, connector): ports = [wwn.WWN(wwpn) for wwpn in connector['wwpns']] for port in ports: infinidat_host = self._get_or_create_host(port) @@ -431,8 +431,7 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver): netspace.get_name()) raise exception.VolumeDriverException(message=msg) - def _initialize_connection_iscsi(self, volume, connector): - infinidat_volume = self._get_infinidat_volume(volume) + def _initialize_connection_iscsi(self, infinidat_volume, connector): port = iqn.IQN(connector['initiator']) infinidat_host = self._get_or_create_host(port) if self.configuration.use_chap_auth: @@ -519,22 +518,40 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver): return True return False - @infinisdk_to_cinder_exceptions + def create_export_snapshot(self, context, snapshot, connector): + """Exports the snapshot.""" + pass + + def remove_export_snapshot(self, context, snapshot): + """Removes an export for a snapshot.""" + pass + + def backup_use_temp_snapshot(self): + """Use a temporary snapshot for performing non-disruptive backups.""" + return True + @coordination.synchronized('infinidat-{self.management_address}-lock') - def initialize_connection(self, volume, connector): - """Map an InfiniBox volume to the host""" + def _initialize_connection(self, infinidat_volume, connector): if self._protocol == constants.FC: - return self._initialize_connection_fc(volume, connector) + initialize_connection = self._initialize_connection_fc else: - return self._initialize_connection_iscsi(volume, connector) + initialize_connection = self._initialize_connection_iscsi + return initialize_connection(infinidat_volume, connector) @infinisdk_to_cinder_exceptions - @coordination.synchronized('infinidat-{self.management_address}-lock') - def terminate_connection(self, volume, connector, **kwargs): - """Unmap an InfiniBox volume from the host""" - if self._is_volume_multiattached(volume, connector): - return True + def initialize_connection(self, volume, connector, **kwargs): + """Map an InfiniBox volume to the host""" infinidat_volume = self._get_infinidat_volume(volume) + return self._initialize_connection(infinidat_volume, connector) + + @infinisdk_to_cinder_exceptions + def initialize_connection_snapshot(self, snapshot, connector, **kwargs): + """Map an InfiniBox snapshot to the host""" + infinidat_snapshot = self._get_infinidat_snapshot(snapshot) + return self._initialize_connection(infinidat_snapshot, connector) + + @coordination.synchronized('infinidat-{self.management_address}-lock') + def _terminate_connection(self, infinidat_volume, connector): if self._protocol == constants.FC: volume_type = 'fibre_channel' else: @@ -568,8 +585,22 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver): conn_info = dict(driver_volume_type=volume_type, data=result_data) fczm_utils.remove_fc_zone(conn_info) + + @infinisdk_to_cinder_exceptions + def terminate_connection(self, volume, connector, **kwargs): + """Unmap an InfiniBox volume from the host""" + if self._is_volume_multiattached(volume, connector): + return True + infinidat_volume = self._get_infinidat_volume(volume) + self._terminate_connection(infinidat_volume, connector) return volume.volume_attachment and len(volume.volume_attachment) > 1 + @infinisdk_to_cinder_exceptions + def terminate_connection_snapshot(self, snapshot, connector, **kwargs): + """Unmap an InfiniBox snapshot from the host""" + infinidat_snapshot = self._get_infinidat_snapshot(snapshot) + self._terminate_connection(infinidat_snapshot, connector) + @infinisdk_to_cinder_exceptions def get_volume_stats(self, refresh=False): if self._volume_stats is None or refresh: @@ -660,17 +691,17 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver): self._set_cinder_object_metadata(infinidat_snapshot, snapshot) @contextmanager - def _connection_context(self, volume): + def _connection_context(self, infinidat_volume): use_multipath = self.configuration.use_multipath_for_image_xfer enforce_multipath = self.configuration.enforce_multipath_for_image_xfer connector = volume_utils.brick_get_connector_properties( use_multipath, enforce_multipath) - connection = self.initialize_connection(volume, connector) + connection = self._initialize_connection(infinidat_volume, connector) try: yield connection finally: - self.terminate_connection(volume, connector) + self._terminate_connection(infinidat_volume, connector) @contextmanager def _attach_context(self, connection): @@ -695,8 +726,8 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver): attach_info['device']) @contextmanager - def _device_connect_context(self, volume): - with self._connection_context(volume) as connection: + def _device_connect_context(self, infinidat_volume): + with self._connection_context(infinidat_volume) as connection: with self._attach_context(connection) as attach_info: yield attach_info @@ -707,36 +738,25 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver): InfiniBox does not yet support detached clone so use dd to copy data. This could be a lengthy operation. - - create a clone from snapshot and map it - - create a volume and map it - - copy data from clone to volume - - unmap volume and clone and delete the clone + - create destination volume + - map source snapshot and destination volume + - copy data from snapshot to volume + - unmap volume and snapshot """ infinidat_snapshot = self._get_infinidat_snapshot(snapshot) - clone_name = self._make_volume_name(volume) + '-internal' - infinidat_clone = infinidat_snapshot.create_snapshot(name=clone_name) - # we need a cinder-volume-like object to map the clone by name - # (which is derived from the cinder id) but the clone is internal - # so there is no such object. mock one - clone = mock.Mock(name_id=str(volume.name_id) + '-internal', - multiattach=False, volume_attachment=[]) + infinidat_volume = self._create_volume(volume) try: - infinidat_volume = self._create_volume(volume) - try: - src_ctx = self._device_connect_context(clone) - dst_ctx = self._device_connect_context(volume) - with src_ctx as src_dev, dst_ctx as dst_dev: - dd_block_size = self.configuration.volume_dd_blocksize - volume_utils.copy_volume(src_dev['device']['path'], - dst_dev['device']['path'], - snapshot.volume.size * units.Ki, - dd_block_size, - sparse=True) - except Exception: - infinidat_volume.delete() - raise - finally: - infinidat_clone.delete() + src_ctx = self._device_connect_context(infinidat_snapshot) + dst_ctx = self._device_connect_context(infinidat_volume) + with src_ctx as src_dev, dst_ctx as dst_dev: + dd_block_size = self.configuration.volume_dd_blocksize + volume_utils.copy_volume(src_dev['device']['path'], + dst_dev['device']['path'], + snapshot.volume.size * units.Ki, + dd_block_size, sparse=True) + except Exception: + infinidat_volume.delete() + raise @infinisdk_to_cinder_exceptions def delete_snapshot(self, snapshot): @@ -747,20 +767,6 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver): return snapshot.safe_delete() - def _assert_volume_not_mapped(self, volume): - # copy is not atomic so we can't clone while the volume is mapped - infinidat_volume = self._get_infinidat_volume(volume) - if len(infinidat_volume.get_logical_units()) == 0: - return - - # volume has mappings - msg = _("INFINIDAT Cinder driver does not support clone of an " - "attached volume. " - "To get this done, create a snapshot from the attached " - "volume and then create a volume from the snapshot.") - LOG.error(msg) - raise exception.VolumeBackendAPIException(data=msg) - @infinisdk_to_cinder_exceptions def create_cloned_volume(self, volume, src_vref): """Create a clone from source volume. @@ -768,26 +774,24 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver): InfiniBox does not yet support detached clone so use dd to copy data. This could be a lengthy operation. - * map source volume + * create temporary snapshot from source volume + * map temporary snapshot * create and map new volume - * copy data from source to new volume - * unmap both volumes + * copy data from temporary snapshot to new volume + * unmap volume and temporary snapshot + * delete temporary snapshot """ - self._assert_volume_not_mapped(src_vref) - infinidat_volume = self._create_volume(volume) + attributes = ('id', 'name', 'volume') + Snapshot = collections.namedtuple('Snapshot', attributes) + snapshot_id = str(uuid.uuid4()) + snapshot_name = CONF.snapshot_name_template % snapshot_id + snapshot = Snapshot(id=snapshot_id, name=snapshot_name, + volume=src_vref) try: - src_ctx = self._device_connect_context(src_vref) - dst_ctx = self._device_connect_context(volume) - with src_ctx as src_dev, dst_ctx as dst_dev: - dd_block_size = self.configuration.volume_dd_blocksize - volume_utils.copy_volume(src_dev['device']['path'], - dst_dev['device']['path'], - src_vref.size * units.Ki, - dd_block_size, - sparse=True) - except Exception: - infinidat_volume.delete() - raise + self.create_snapshot(snapshot) + self.create_volume_from_snapshot(volume, snapshot) + finally: + self.delete_snapshot(snapshot) def _build_initiator_target_map(self, connector, all_target_wwns): """Build the target_wwns and the initiator target map.""" diff --git a/releasenotes/notes/bug-1983287-infinidat-fix-backup-attached-volume-b28e5dd5c25a24ec.yaml b/releasenotes/notes/bug-1983287-infinidat-fix-backup-attached-volume-b28e5dd5c25a24ec.yaml new file mode 100644 index 00000000000..2cb710183a5 --- /dev/null +++ b/releasenotes/notes/bug-1983287-infinidat-fix-backup-attached-volume-b28e5dd5c25a24ec.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Infinidat Driver `bug #1983287 + `_: + Fixed Infinidat driver to allow backup of an attached volume.