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 <adeiter@infinidat.com>
This commit is contained in:
parent
7d442e5b5b
commit
907550015e
@ -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',
|
||||
|
@ -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."""
|
||||
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
fixes:
|
||||
- |
|
||||
Infinidat Driver `bug #1983287
|
||||
<https://bugs.launchpad.net/cinder/+bug/1983287>`_:
|
||||
Fixed Infinidat driver to allow backup of an attached volume.
|
Loading…
x
Reference in New Issue
Block a user