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.
|
# under the License.
|
||||||
"""Unit tests for INFINIDAT InfiniBox volume driver."""
|
"""Unit tests for INFINIDAT InfiniBox volume driver."""
|
||||||
|
|
||||||
|
import collections
|
||||||
import copy
|
import copy
|
||||||
import functools
|
import functools
|
||||||
|
import itertools
|
||||||
import platform
|
import platform
|
||||||
import socket
|
import socket
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
import uuid
|
||||||
|
|
||||||
import ddt
|
import ddt
|
||||||
from oslo_utils import units
|
from oslo_utils import units
|
||||||
@ -215,6 +218,14 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase):
|
|||||||
self._mock_host.set_metadata_from_dict.assert_called_once_with(
|
self._mock_host.set_metadata_from_dict.assert_called_once_with(
|
||||||
self._generate_mock_host_metadata())
|
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
|
@skip_driver_setup
|
||||||
def test__setup_and_get_system_object(self):
|
def test__setup_and_get_system_object(self):
|
||||||
# This test should skip the driver setup, as it generates more calls to
|
# This test should skip the driver setup, as it generates more calls to
|
||||||
@ -230,6 +241,12 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase):
|
|||||||
infinidat._INFINIDAT_CINDER_IDENTIFIER)
|
infinidat._INFINIDAT_CINDER_IDENTIFIER)
|
||||||
self._system.login.assert_called_once()
|
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')
|
@mock.patch('cinder.volume.drivers.infinidat.infinisdk.InfiniBox')
|
||||||
@ddt.data(True, False)
|
@ddt.data(True, False)
|
||||||
def test_ssl_options(self, use_ssl, infinibox):
|
def test_ssl_options(self, use_ssl, infinibox):
|
||||||
@ -240,9 +257,31 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase):
|
|||||||
infinibox.assert_called_once_with(self.configuration.san_ip,
|
infinibox.assert_called_once_with(self.configuration.san_ip,
|
||||||
auth=auth, use_ssl=use_ssl)
|
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
|
self._system.hosts.safe_get.return_value = None
|
||||||
result = self.driver.initialize_connection(test_volume, test_connector)
|
result = self.driver.initialize_connection(test_volume, test_connector)
|
||||||
|
get_volume.assert_called_once_with(test_volume)
|
||||||
self.assertEqual(1, result["data"]["target_lun"])
|
self.assertEqual(1, result["data"]["target_lun"])
|
||||||
|
|
||||||
def test_initialize_connection_host_exists(self):
|
def test_initialize_connection_host_exists(self):
|
||||||
@ -257,6 +296,13 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase):
|
|||||||
result = self.driver.initialize_connection(test_volume, test_connector)
|
result = self.driver.initialize_connection(test_volume, test_connector)
|
||||||
self.assertEqual(888, result["data"]["target_lun"])
|
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):
|
def test_initialize_connection_volume_doesnt_exist(self):
|
||||||
self._system.volumes.safe_get.return_value = None
|
self._system.volumes.safe_get.return_value = None
|
||||||
self.assertRaises(exception.VolumeNotFound,
|
self.assertRaises(exception.VolumeNotFound,
|
||||||
@ -300,11 +346,37 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase):
|
|||||||
self.assertFalse(self.driver._is_volume_multiattached(volume,
|
self.assertFalse(self.driver._is_volume_multiattached(volume,
|
||||||
connector))
|
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 = copy.deepcopy(test_volume)
|
||||||
volume.volume_attachment = [test_attachment1]
|
volume.volume_attachment = [test_attachment1]
|
||||||
|
volume_multiattached.return_value = False
|
||||||
|
get_volume.return_value = self._mock_volume
|
||||||
self.assertFalse(self.driver.terminate_connection(volume,
|
self.assertFalse(self.driver.terminate_connection(volume,
|
||||||
test_connector))
|
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):
|
def test_terminate_connection_delete_host(self):
|
||||||
self._mock_host.get_luns.return_value = [object()]
|
self._mock_host.get_luns.return_value = [object()]
|
||||||
@ -470,7 +542,7 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase):
|
|||||||
test_clone, test_snapshot)
|
test_clone, test_snapshot)
|
||||||
|
|
||||||
def test_create_volume_from_snapshot_create_fails(self):
|
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.assertRaises(exception.VolumeBackendAPIException,
|
||||||
self.driver.create_volume_from_snapshot,
|
self.driver.create_volume_from_snapshot,
|
||||||
test_clone, test_snapshot)
|
test_clone, test_snapshot)
|
||||||
@ -484,13 +556,18 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase):
|
|||||||
self.driver.create_volume_from_snapshot,
|
self.driver.create_volume_from_snapshot,
|
||||||
test_clone, test_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")
|
@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.volume_utils.brick_get_connector_properties')
|
||||||
return_value=test_connector)
|
@mock.patch('cinder.volume.drivers.infinidat.InfiniboxVolumeDriver.'
|
||||||
def test_create_volume_from_snapshot_delete_clone_fails(self, *mocks):
|
'_connect_device')
|
||||||
self._mock_volume.delete.side_effect = self._raise_infinisdk
|
def test_create_volume_from_snapshot_connect_fails(self, connect_device,
|
||||||
self.assertRaises(exception.VolumeBackendAPIException,
|
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,
|
self.driver.create_volume_from_snapshot,
|
||||||
test_clone, test_snapshot)
|
test_clone, test_snapshot)
|
||||||
|
|
||||||
@ -507,21 +584,28 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase):
|
|||||||
self.assertRaises(exception.VolumeBackendAPIException,
|
self.assertRaises(exception.VolumeBackendAPIException,
|
||||||
self.driver.delete_snapshot, test_snapshot)
|
self.driver.delete_snapshot, test_snapshot)
|
||||||
|
|
||||||
@mock.patch("cinder.volume.volume_utils.copy_volume")
|
@mock.patch('cinder.volume.drivers.infinidat.InfiniboxVolumeDriver.'
|
||||||
@mock.patch("cinder.volume.volume_utils.brick_get_connector")
|
'delete_snapshot')
|
||||||
@mock.patch("cinder.volume.volume_utils.brick_get_connector_properties",
|
@mock.patch('cinder.volume.drivers.infinidat.InfiniboxVolumeDriver.'
|
||||||
return_value=test_connector)
|
'create_volume_from_snapshot')
|
||||||
@mock.patch("cinder.volume.volume_types.get_volume_type_qos_specs")
|
@mock.patch('cinder.volume.drivers.infinidat.InfiniboxVolumeDriver.'
|
||||||
def test_create_cloned_volume(self, *mocks):
|
'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)
|
self.driver.create_cloned_volume(test_clone, test_volume)
|
||||||
|
create_snapshot.assert_called_once_with(snapshot)
|
||||||
def test_create_cloned_volume_volume_already_mapped(self):
|
create_volume_from_snapshot.assert_called_once_with(test_clone,
|
||||||
mock_mapping = mock.Mock()
|
snapshot)
|
||||||
mock_mapping.get_volume.return_value = self._mock_volume
|
delete_snapshot.assert_called_once_with(snapshot)
|
||||||
self._mock_volume.get_logical_units.return_value = [mock_mapping]
|
|
||||||
self.assertRaises(exception.VolumeBackendAPIException,
|
|
||||||
self.driver.create_cloned_volume,
|
|
||||||
test_clone, test_volume)
|
|
||||||
|
|
||||||
def test_create_cloned_volume_create_fails(self):
|
def test_create_cloned_volume_create_fails(self):
|
||||||
self._system.volumes.create.side_effect = self._raise_infinisdk
|
self._system.volumes.create.side_effect = self._raise_infinisdk
|
||||||
@ -724,21 +808,6 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase):
|
|||||||
self.driver.delete_group_snapshot,
|
self.driver.delete_group_snapshot,
|
||||||
None, test_snapgroup, [test_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):
|
def test_snapshot_revert_use_temp_snapshot(self):
|
||||||
result = self.driver.snapshot_revert_use_temp_snapshot()
|
result = self.driver.snapshot_revert_use_temp_snapshot()
|
||||||
self.assertFalse(result)
|
self.assertFalse(result)
|
||||||
@ -1123,6 +1192,18 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase):
|
|||||||
|
|
||||||
@ddt.ddt
|
@ddt.ddt
|
||||||
class InfiniboxDriverTestCaseFC(InfiniboxDriverTestCaseBase):
|
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):
|
def test_initialize_connection_multiple_wwpns(self):
|
||||||
connector = {'wwpns': [TEST_WWN_1, TEST_WWN_2]}
|
connector = {'wwpns': [TEST_WWN_1, TEST_WWN_2]}
|
||||||
result = self.driver.initialize_connection(test_volume, connector)
|
result = self.driver.initialize_connection(test_volume, connector)
|
||||||
@ -1154,6 +1235,19 @@ class InfiniboxDriverTestCaseFC(InfiniboxDriverTestCaseBase):
|
|||||||
self.assertTrue(self.driver.terminate_connection(volume,
|
self.assertTrue(self.driver.terminate_connection(volume,
|
||||||
test_connector))
|
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
|
@ddt.ddt
|
||||||
class InfiniboxDriverTestCaseISCSI(InfiniboxDriverTestCaseBase):
|
class InfiniboxDriverTestCaseISCSI(InfiniboxDriverTestCaseBase):
|
||||||
@ -1356,6 +1450,19 @@ class InfiniboxDriverTestCaseISCSI(InfiniboxDriverTestCaseBase):
|
|||||||
self.assertFalse(self.driver.terminate_connection(volume,
|
self.assertFalse(self.driver.terminate_connection(volume,
|
||||||
test_connector))
|
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):
|
def test_validate_connector(self):
|
||||||
fc_connector = {'wwpns': [TEST_WWN_1, TEST_WWN_2]}
|
fc_connector = {'wwpns': [TEST_WWN_1, TEST_WWN_2]}
|
||||||
iscsi_connector = {'initiator': TEST_INITIATOR_IQN}
|
iscsi_connector = {'initiator': TEST_INITIATOR_IQN}
|
||||||
@ -1365,6 +1472,13 @@ class InfiniboxDriverTestCaseISCSI(InfiniboxDriverTestCaseBase):
|
|||||||
|
|
||||||
|
|
||||||
class InfiniboxDriverTestCaseQoS(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")
|
@mock.patch("cinder.volume.volume_types.get_volume_type_qos_specs")
|
||||||
def test_qos_max_ipos(self, qos_specs):
|
def test_qos_max_ipos(self, qos_specs):
|
||||||
qos_specs.return_value = {'qos_specs': {'id': 'qos_name',
|
qos_specs.return_value = {'qos_specs': {'id': 'qos_name',
|
||||||
|
@ -14,12 +14,12 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
"""INFINIDAT InfiniBox Volume Driver."""
|
"""INFINIDAT InfiniBox Volume Driver."""
|
||||||
|
|
||||||
|
import collections
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
import functools
|
import functools
|
||||||
import math
|
import math
|
||||||
import platform
|
import platform
|
||||||
import socket
|
import socket
|
||||||
from unittest import mock
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
@ -129,10 +129,11 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver):
|
|||||||
1.12 - fixed volume multi-attach
|
1.12 - fixed volume multi-attach
|
||||||
1.13 - fixed consistency groups feature
|
1.13 - fixed consistency groups feature
|
||||||
1.14 - added storage assisted volume migration
|
1.14 - added storage assisted volume migration
|
||||||
|
1.15 - fixed backup for attached volume
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
VERSION = '1.14'
|
VERSION = '1.15'
|
||||||
|
|
||||||
# ThirdPartySystems wiki page
|
# ThirdPartySystems wiki page
|
||||||
CI_WIKI_NAME = "INFINIDAT_CI"
|
CI_WIKI_NAME = "INFINIDAT_CI"
|
||||||
@ -390,8 +391,7 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver):
|
|||||||
port.get_state() == 'OK'):
|
port.get_state() == 'OK'):
|
||||||
yield str(port.get_wwpn())
|
yield str(port.get_wwpn())
|
||||||
|
|
||||||
def _initialize_connection_fc(self, volume, connector):
|
def _initialize_connection_fc(self, infinidat_volume, connector):
|
||||||
infinidat_volume = self._get_infinidat_volume(volume)
|
|
||||||
ports = [wwn.WWN(wwpn) for wwpn in connector['wwpns']]
|
ports = [wwn.WWN(wwpn) for wwpn in connector['wwpns']]
|
||||||
for port in ports:
|
for port in ports:
|
||||||
infinidat_host = self._get_or_create_host(port)
|
infinidat_host = self._get_or_create_host(port)
|
||||||
@ -431,8 +431,7 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver):
|
|||||||
netspace.get_name())
|
netspace.get_name())
|
||||||
raise exception.VolumeDriverException(message=msg)
|
raise exception.VolumeDriverException(message=msg)
|
||||||
|
|
||||||
def _initialize_connection_iscsi(self, volume, connector):
|
def _initialize_connection_iscsi(self, infinidat_volume, connector):
|
||||||
infinidat_volume = self._get_infinidat_volume(volume)
|
|
||||||
port = iqn.IQN(connector['initiator'])
|
port = iqn.IQN(connector['initiator'])
|
||||||
infinidat_host = self._get_or_create_host(port)
|
infinidat_host = self._get_or_create_host(port)
|
||||||
if self.configuration.use_chap_auth:
|
if self.configuration.use_chap_auth:
|
||||||
@ -519,22 +518,40 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver):
|
|||||||
return True
|
return True
|
||||||
return False
|
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')
|
@coordination.synchronized('infinidat-{self.management_address}-lock')
|
||||||
def initialize_connection(self, volume, connector):
|
def _initialize_connection(self, infinidat_volume, connector):
|
||||||
"""Map an InfiniBox volume to the host"""
|
|
||||||
if self._protocol == constants.FC:
|
if self._protocol == constants.FC:
|
||||||
return self._initialize_connection_fc(volume, connector)
|
initialize_connection = self._initialize_connection_fc
|
||||||
else:
|
else:
|
||||||
return self._initialize_connection_iscsi(volume, connector)
|
initialize_connection = self._initialize_connection_iscsi
|
||||||
|
return initialize_connection(infinidat_volume, connector)
|
||||||
|
|
||||||
@infinisdk_to_cinder_exceptions
|
@infinisdk_to_cinder_exceptions
|
||||||
@coordination.synchronized('infinidat-{self.management_address}-lock')
|
def initialize_connection(self, volume, connector, **kwargs):
|
||||||
def terminate_connection(self, volume, connector, **kwargs):
|
"""Map an InfiniBox volume to the host"""
|
||||||
"""Unmap an InfiniBox volume from the host"""
|
|
||||||
if self._is_volume_multiattached(volume, connector):
|
|
||||||
return True
|
|
||||||
infinidat_volume = self._get_infinidat_volume(volume)
|
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:
|
if self._protocol == constants.FC:
|
||||||
volume_type = 'fibre_channel'
|
volume_type = 'fibre_channel'
|
||||||
else:
|
else:
|
||||||
@ -568,8 +585,22 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver):
|
|||||||
conn_info = dict(driver_volume_type=volume_type,
|
conn_info = dict(driver_volume_type=volume_type,
|
||||||
data=result_data)
|
data=result_data)
|
||||||
fczm_utils.remove_fc_zone(conn_info)
|
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
|
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
|
@infinisdk_to_cinder_exceptions
|
||||||
def get_volume_stats(self, refresh=False):
|
def get_volume_stats(self, refresh=False):
|
||||||
if self._volume_stats is None or refresh:
|
if self._volume_stats is None or refresh:
|
||||||
@ -660,17 +691,17 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver):
|
|||||||
self._set_cinder_object_metadata(infinidat_snapshot, snapshot)
|
self._set_cinder_object_metadata(infinidat_snapshot, snapshot)
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def _connection_context(self, volume):
|
def _connection_context(self, infinidat_volume):
|
||||||
use_multipath = self.configuration.use_multipath_for_image_xfer
|
use_multipath = self.configuration.use_multipath_for_image_xfer
|
||||||
enforce_multipath = self.configuration.enforce_multipath_for_image_xfer
|
enforce_multipath = self.configuration.enforce_multipath_for_image_xfer
|
||||||
connector = volume_utils.brick_get_connector_properties(
|
connector = volume_utils.brick_get_connector_properties(
|
||||||
use_multipath,
|
use_multipath,
|
||||||
enforce_multipath)
|
enforce_multipath)
|
||||||
connection = self.initialize_connection(volume, connector)
|
connection = self._initialize_connection(infinidat_volume, connector)
|
||||||
try:
|
try:
|
||||||
yield connection
|
yield connection
|
||||||
finally:
|
finally:
|
||||||
self.terminate_connection(volume, connector)
|
self._terminate_connection(infinidat_volume, connector)
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def _attach_context(self, connection):
|
def _attach_context(self, connection):
|
||||||
@ -695,8 +726,8 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver):
|
|||||||
attach_info['device'])
|
attach_info['device'])
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def _device_connect_context(self, volume):
|
def _device_connect_context(self, infinidat_volume):
|
||||||
with self._connection_context(volume) as connection:
|
with self._connection_context(infinidat_volume) as connection:
|
||||||
with self._attach_context(connection) as attach_info:
|
with self._attach_context(connection) as attach_info:
|
||||||
yield 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.
|
InfiniBox does not yet support detached clone so use dd to copy data.
|
||||||
This could be a lengthy operation.
|
This could be a lengthy operation.
|
||||||
|
|
||||||
- create a clone from snapshot and map it
|
- create destination volume
|
||||||
- create a volume and map it
|
- map source snapshot and destination volume
|
||||||
- copy data from clone to volume
|
- copy data from snapshot to volume
|
||||||
- unmap volume and clone and delete the clone
|
- unmap volume and snapshot
|
||||||
"""
|
"""
|
||||||
infinidat_snapshot = self._get_infinidat_snapshot(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=[])
|
|
||||||
try:
|
|
||||||
infinidat_volume = self._create_volume(volume)
|
infinidat_volume = self._create_volume(volume)
|
||||||
try:
|
try:
|
||||||
src_ctx = self._device_connect_context(clone)
|
src_ctx = self._device_connect_context(infinidat_snapshot)
|
||||||
dst_ctx = self._device_connect_context(volume)
|
dst_ctx = self._device_connect_context(infinidat_volume)
|
||||||
with src_ctx as src_dev, dst_ctx as dst_dev:
|
with src_ctx as src_dev, dst_ctx as dst_dev:
|
||||||
dd_block_size = self.configuration.volume_dd_blocksize
|
dd_block_size = self.configuration.volume_dd_blocksize
|
||||||
volume_utils.copy_volume(src_dev['device']['path'],
|
volume_utils.copy_volume(src_dev['device']['path'],
|
||||||
dst_dev['device']['path'],
|
dst_dev['device']['path'],
|
||||||
snapshot.volume.size * units.Ki,
|
snapshot.volume.size * units.Ki,
|
||||||
dd_block_size,
|
dd_block_size, sparse=True)
|
||||||
sparse=True)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
infinidat_volume.delete()
|
infinidat_volume.delete()
|
||||||
raise
|
raise
|
||||||
finally:
|
|
||||||
infinidat_clone.delete()
|
|
||||||
|
|
||||||
@infinisdk_to_cinder_exceptions
|
@infinisdk_to_cinder_exceptions
|
||||||
def delete_snapshot(self, snapshot):
|
def delete_snapshot(self, snapshot):
|
||||||
@ -747,20 +767,6 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver):
|
|||||||
return
|
return
|
||||||
snapshot.safe_delete()
|
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
|
@infinisdk_to_cinder_exceptions
|
||||||
def create_cloned_volume(self, volume, src_vref):
|
def create_cloned_volume(self, volume, src_vref):
|
||||||
"""Create a clone from source volume.
|
"""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.
|
InfiniBox does not yet support detached clone so use dd to copy data.
|
||||||
This could be a lengthy operation.
|
This could be a lengthy operation.
|
||||||
|
|
||||||
* map source volume
|
* create temporary snapshot from source volume
|
||||||
|
* map temporary snapshot
|
||||||
* create and map new volume
|
* create and map new volume
|
||||||
* copy data from source to new volume
|
* copy data from temporary snapshot to new volume
|
||||||
* unmap both volumes
|
* unmap volume and temporary snapshot
|
||||||
|
* delete temporary snapshot
|
||||||
"""
|
"""
|
||||||
self._assert_volume_not_mapped(src_vref)
|
attributes = ('id', 'name', 'volume')
|
||||||
infinidat_volume = self._create_volume(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:
|
try:
|
||||||
src_ctx = self._device_connect_context(src_vref)
|
self.create_snapshot(snapshot)
|
||||||
dst_ctx = self._device_connect_context(volume)
|
self.create_volume_from_snapshot(volume, snapshot)
|
||||||
with src_ctx as src_dev, dst_ctx as dst_dev:
|
finally:
|
||||||
dd_block_size = self.configuration.volume_dd_blocksize
|
self.delete_snapshot(snapshot)
|
||||||
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
|
|
||||||
|
|
||||||
def _build_initiator_target_map(self, connector, all_target_wwns):
|
def _build_initiator_target_map(self, connector, all_target_wwns):
|
||||||
"""Build the target_wwns and the initiator target map."""
|
"""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