INFINIDAT: add support for iSCSI
Change-Id: I4d3adc14610f8b6281b7d88c50a6ac04fef3041b Implements: blueprint infinidat-iscsi-support
This commit is contained in:
parent
63433278a4
commit
747d4464c7
@ -29,19 +29,21 @@ TEST_WWN_2 = '11:11:22:33:44:55:66:77'
|
|||||||
test_volume = mock.Mock(id=1, size=1)
|
test_volume = mock.Mock(id=1, size=1)
|
||||||
test_snapshot = mock.Mock(id=2, volume=test_volume)
|
test_snapshot = mock.Mock(id=2, volume=test_volume)
|
||||||
test_clone = mock.Mock(id=3, size=1)
|
test_clone = mock.Mock(id=3, size=1)
|
||||||
test_connector = dict(wwpns=[TEST_WWN_1])
|
test_connector = dict(wwpns=[TEST_WWN_1],
|
||||||
|
initiator='iqn.2012-07.org.fake:01')
|
||||||
|
|
||||||
|
|
||||||
class FakeInfinisdkException(Exception):
|
class FakeInfinisdkException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class InfiniboxDriverTestCase(test.TestCase):
|
class InfiniboxDriverTestCaseBase(test.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(InfiniboxDriverTestCase, self).setUp()
|
super(InfiniboxDriverTestCaseBase, self).setUp()
|
||||||
|
|
||||||
# create mock configuration
|
# create mock configuration
|
||||||
self.configuration = mock.Mock(spec=configuration.Configuration)
|
self.configuration = mock.Mock(spec=configuration.Configuration)
|
||||||
|
self.configuration.infinidat_storage_protocol = 'fc'
|
||||||
self.configuration.san_ip = 'mockbox'
|
self.configuration.san_ip = 'mockbox'
|
||||||
self.configuration.infinidat_pool_name = 'mockpool'
|
self.configuration.infinidat_pool_name = 'mockpool'
|
||||||
self.configuration.san_thin_provision = 'thin'
|
self.configuration.san_thin_provision = 'thin'
|
||||||
@ -50,14 +52,20 @@ class InfiniboxDriverTestCase(test.TestCase):
|
|||||||
self.configuration.volume_backend_name = 'mock'
|
self.configuration.volume_backend_name = 'mock'
|
||||||
self.configuration.volume_dd_blocksize = '1M'
|
self.configuration.volume_dd_blocksize = '1M'
|
||||||
self.configuration.use_multipath_for_image_xfer = False
|
self.configuration.use_multipath_for_image_xfer = False
|
||||||
|
self.configuration.enforce_multipath_for_image_xfer = False
|
||||||
self.configuration.num_volume_device_scan_tries = 1
|
self.configuration.num_volume_device_scan_tries = 1
|
||||||
self.configuration.san_is_local = False
|
self.configuration.san_is_local = False
|
||||||
|
self.configuration.chap_username = None
|
||||||
|
self.configuration.chap_password = None
|
||||||
|
|
||||||
self.driver = infinidat.InfiniboxVolumeDriver(
|
self.driver = infinidat.InfiniboxVolumeDriver(
|
||||||
configuration=self.configuration)
|
configuration=self.configuration)
|
||||||
self._system = self._infinibox_mock()
|
self._system = self._infinibox_mock()
|
||||||
|
# mock external library dependencies
|
||||||
infinisdk = self.patch("cinder.volume.drivers.infinidat.infinisdk")
|
infinisdk = self.patch("cinder.volume.drivers.infinidat.infinisdk")
|
||||||
capacity = self.patch("cinder.volume.drivers.infinidat.capacity")
|
capacity = self.patch("cinder.volume.drivers.infinidat.capacity")
|
||||||
|
self.patch("cinder.volume.drivers.infinidat.iqn")
|
||||||
|
self.patch("cinder.volume.drivers.infinidat.wwn")
|
||||||
capacity.byte = 1
|
capacity.byte = 1
|
||||||
capacity.GiB = units.Gi
|
capacity.GiB = units.Gi
|
||||||
infinisdk.core.exceptions.InfiniSDKException = FakeInfinisdkException
|
infinisdk.core.exceptions.InfiniSDKException = FakeInfinisdkException
|
||||||
@ -76,34 +84,22 @@ class InfiniboxDriverTestCase(test.TestCase):
|
|||||||
self._mock_pool = mock.Mock()
|
self._mock_pool = mock.Mock()
|
||||||
self._mock_pool.get_free_physical_capacity.return_value = units.Gi
|
self._mock_pool.get_free_physical_capacity.return_value = units.Gi
|
||||||
self._mock_pool.get_physical_capacity.return_value = units.Gi
|
self._mock_pool.get_physical_capacity.return_value = units.Gi
|
||||||
|
self._mock_ns = mock.Mock()
|
||||||
|
self._mock_ns.get_ips.return_value = [mock.Mock(ip_address='1.1.1.1')]
|
||||||
result.volumes.safe_get.return_value = self._mock_volume
|
result.volumes.safe_get.return_value = self._mock_volume
|
||||||
result.volumes.create.return_value = self._mock_volume
|
result.volumes.create.return_value = self._mock_volume
|
||||||
result.pools.safe_get.return_value = self._mock_pool
|
result.pools.safe_get.return_value = self._mock_pool
|
||||||
result.hosts.safe_get.return_value = self._mock_host
|
result.hosts.safe_get.return_value = self._mock_host
|
||||||
result.hosts.create.return_value = self._mock_host
|
result.hosts.create.return_value = self._mock_host
|
||||||
|
result.network_spaces.safe_get.return_value = self._mock_ns
|
||||||
result.components.nodes.get_all.return_value = []
|
result.components.nodes.get_all.return_value = []
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _raise_infinisdk(self, *args, **kwargs):
|
def _raise_infinisdk(self, *args, **kwargs):
|
||||||
raise FakeInfinisdkException()
|
raise FakeInfinisdkException()
|
||||||
|
|
||||||
def test_get_volume_stats_refreshes(self):
|
|
||||||
result = self.driver.get_volume_stats()
|
|
||||||
self.assertEqual(1, result["free_capacity_gb"])
|
|
||||||
# change the "free space" in the pool
|
|
||||||
self._mock_pool.get_free_physical_capacity.return_value = 0
|
|
||||||
# no refresh - free capacity should stay the same
|
|
||||||
result = self.driver.get_volume_stats(refresh=False)
|
|
||||||
self.assertEqual(1, result["free_capacity_gb"])
|
|
||||||
# refresh - free capacity should change to 0
|
|
||||||
result = self.driver.get_volume_stats(refresh=True)
|
|
||||||
self.assertEqual(0, result["free_capacity_gb"])
|
|
||||||
|
|
||||||
def test_get_volume_stats_pool_not_found(self):
|
|
||||||
self._system.pools.safe_get.return_value = None
|
|
||||||
self.assertRaises(exception.VolumeDriverException,
|
|
||||||
self.driver.get_volume_stats)
|
|
||||||
|
|
||||||
|
class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase):
|
||||||
def test_initialize_connection(self):
|
def test_initialize_connection(self):
|
||||||
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)
|
||||||
@ -121,11 +117,6 @@ class InfiniboxDriverTestCase(test.TestCase):
|
|||||||
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_multiple_hosts(self):
|
|
||||||
connector = {'wwpns': [TEST_WWN_1, TEST_WWN_2]}
|
|
||||||
result = self.driver.initialize_connection(test_volume, connector)
|
|
||||||
self.assertEqual(1, 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.InvalidVolume,
|
self.assertRaises(exception.InvalidVolume,
|
||||||
@ -168,6 +159,23 @@ class InfiniboxDriverTestCase(test.TestCase):
|
|||||||
self.driver.terminate_connection,
|
self.driver.terminate_connection,
|
||||||
test_volume, test_connector)
|
test_volume, test_connector)
|
||||||
|
|
||||||
|
def test_get_volume_stats_refreshes(self):
|
||||||
|
result = self.driver.get_volume_stats()
|
||||||
|
self.assertEqual(1, result["free_capacity_gb"])
|
||||||
|
# change the "free space" in the pool
|
||||||
|
self._mock_pool.get_free_physical_capacity.return_value = 0
|
||||||
|
# no refresh - free capacity should stay the same
|
||||||
|
result = self.driver.get_volume_stats(refresh=False)
|
||||||
|
self.assertEqual(1, result["free_capacity_gb"])
|
||||||
|
# refresh - free capacity should change to 0
|
||||||
|
result = self.driver.get_volume_stats(refresh=True)
|
||||||
|
self.assertEqual(0, result["free_capacity_gb"])
|
||||||
|
|
||||||
|
def test_get_volume_stats_pool_not_found(self):
|
||||||
|
self._system.pools.safe_get.return_value = None
|
||||||
|
self.assertRaises(exception.VolumeDriverException,
|
||||||
|
self.driver.get_volume_stats)
|
||||||
|
|
||||||
def test_create_volume(self):
|
def test_create_volume(self):
|
||||||
self.driver.create_volume(test_volume)
|
self.driver.create_volume(test_volume)
|
||||||
|
|
||||||
@ -293,3 +301,60 @@ class InfiniboxDriverTestCase(test.TestCase):
|
|||||||
self.assertRaises(exception.VolumeBackendAPIException,
|
self.assertRaises(exception.VolumeBackendAPIException,
|
||||||
self.driver.create_cloned_volume,
|
self.driver.create_cloned_volume,
|
||||||
test_clone, test_volume)
|
test_clone, test_volume)
|
||||||
|
|
||||||
|
|
||||||
|
class InfiniboxDriverTestCaseFC(InfiniboxDriverTestCaseBase):
|
||||||
|
def test_initialize_connection_multiple_wwpns(self):
|
||||||
|
connector = {'wwpns': [TEST_WWN_1, TEST_WWN_2]}
|
||||||
|
result = self.driver.initialize_connection(test_volume, connector)
|
||||||
|
self.assertEqual(1, result["data"]["target_lun"])
|
||||||
|
|
||||||
|
|
||||||
|
class InfiniboxDriverTestCaseISCSI(InfiniboxDriverTestCaseBase):
|
||||||
|
def setUp(self):
|
||||||
|
super(InfiniboxDriverTestCaseISCSI, self).setUp()
|
||||||
|
self.configuration.infinidat_storage_protocol = 'iscsi'
|
||||||
|
self.configuration.infinidat_iscsi_netspaces = ['netspace1']
|
||||||
|
self.configuration.use_chap_auth = False
|
||||||
|
self.driver.do_setup(None)
|
||||||
|
|
||||||
|
def test_setup_without_netspaces_configured(self):
|
||||||
|
self.configuration.infinidat_iscsi_netspaces = []
|
||||||
|
self.assertRaises(exception.VolumeDriverException,
|
||||||
|
self.driver.do_setup, None)
|
||||||
|
|
||||||
|
def test_initialize_connection(self):
|
||||||
|
result = self.driver.initialize_connection(test_volume, test_connector)
|
||||||
|
self.assertEqual(1, result['data']['target_lun'])
|
||||||
|
|
||||||
|
def test_initialize_netspace_does_not_exist(self):
|
||||||
|
self._system.network_spaces.safe_get.return_value = None
|
||||||
|
self.assertRaises(exception.VolumeDriverException,
|
||||||
|
self.driver.initialize_connection,
|
||||||
|
test_volume, test_connector)
|
||||||
|
|
||||||
|
def test_initialize_netspace_has_no_ips(self):
|
||||||
|
self._mock_ns.get_ips.return_value = []
|
||||||
|
self.assertRaises(exception.VolumeDriverException,
|
||||||
|
self.driver.initialize_connection,
|
||||||
|
test_volume, test_connector)
|
||||||
|
|
||||||
|
def test_initialize_connection_with_chap(self):
|
||||||
|
self.configuration.use_chap_auth = True
|
||||||
|
result = self.driver.initialize_connection(test_volume, test_connector)
|
||||||
|
self.assertEqual(1, result['data']['target_lun'])
|
||||||
|
self.assertEqual('CHAP', result['data']['auth_method'])
|
||||||
|
self.assertIn('auth_username', result['data'])
|
||||||
|
self.assertIn('auth_password', result['data'])
|
||||||
|
|
||||||
|
def test_initialize_connection_multiple_netspaces(self):
|
||||||
|
self.configuration.infinidat_iscsi_netspaces = ['netspace1',
|
||||||
|
'netspace2']
|
||||||
|
result = self.driver.initialize_connection(test_volume, test_connector)
|
||||||
|
self.assertEqual(1, result['data']['target_lun'])
|
||||||
|
self.assertEqual(2, len(result['data']['target_luns']))
|
||||||
|
self.assertEqual(2, len(result['data']['target_iqns']))
|
||||||
|
self.assertEqual(2, len(result['data']['target_portals']))
|
||||||
|
|
||||||
|
def test_terminate_connection(self):
|
||||||
|
self.driver.terminate_connection(test_volume, test_connector)
|
||||||
|
@ -34,13 +34,18 @@ from cinder.volume import utils as vol_utils
|
|||||||
from cinder.zonemanager import utils as fczm_utils
|
from cinder.zonemanager import utils as fczm_utils
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# capacity is a dependency of infinisdk, so if infinisdk is available
|
# we check that infinisdk is installed. the other imported modules
|
||||||
# then capacity should be available too
|
# are dependencies, so if any of the dependencies are not importable
|
||||||
|
# we assume infinisdk is not installed
|
||||||
import capacity
|
import capacity
|
||||||
|
from infi.dtypes import iqn
|
||||||
|
from infi.dtypes import wwn
|
||||||
import infinisdk
|
import infinisdk
|
||||||
except ImportError:
|
except ImportError:
|
||||||
capacity = None
|
capacity = None
|
||||||
infinisdk = None
|
infinisdk = None
|
||||||
|
iqn = None
|
||||||
|
wwn = None
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -50,6 +55,18 @@ VENDOR_NAME = 'INFINIDAT'
|
|||||||
infinidat_opts = [
|
infinidat_opts = [
|
||||||
cfg.StrOpt('infinidat_pool_name',
|
cfg.StrOpt('infinidat_pool_name',
|
||||||
help='Name of the pool from which volumes are allocated'),
|
help='Name of the pool from which volumes are allocated'),
|
||||||
|
# We can't use the existing "storage_protocol" option because its default
|
||||||
|
# is "iscsi", but for backward-compatibility our default must be "fc"
|
||||||
|
cfg.StrOpt('infinidat_storage_protocol',
|
||||||
|
ignore_case=True,
|
||||||
|
default='fc',
|
||||||
|
choices=['iscsi', 'fc'],
|
||||||
|
help='Protocol for transferring data between host and '
|
||||||
|
'storage back-end.'),
|
||||||
|
cfg.ListOpt('infinidat_iscsi_netspaces',
|
||||||
|
default=[],
|
||||||
|
help='List of names of network spaces to use for iSCSI '
|
||||||
|
'connectivity'),
|
||||||
]
|
]
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
@ -70,8 +87,8 @@ def infinisdk_to_cinder_exceptions(func):
|
|||||||
|
|
||||||
|
|
||||||
@interface.volumedriver
|
@interface.volumedriver
|
||||||
class InfiniboxVolumeDriver(san.SanDriver):
|
class InfiniboxVolumeDriver(san.SanISCSIDriver):
|
||||||
VERSION = '1.1'
|
VERSION = '1.2'
|
||||||
|
|
||||||
# ThirdPartySystems wiki page
|
# ThirdPartySystems wiki page
|
||||||
CI_WIKI_NAME = "INFINIDAT_Cinder_CI"
|
CI_WIKI_NAME = "INFINIDAT_Cinder_CI"
|
||||||
@ -95,20 +112,23 @@ class InfiniboxVolumeDriver(san.SanDriver):
|
|||||||
backend_name = self.configuration.safe_get('volume_backend_name')
|
backend_name = self.configuration.safe_get('volume_backend_name')
|
||||||
self._backend_name = backend_name or self.__class__.__name__
|
self._backend_name = backend_name or self.__class__.__name__
|
||||||
self._volume_stats = None
|
self._volume_stats = None
|
||||||
|
if self.configuration.infinidat_storage_protocol.lower() == 'iscsi':
|
||||||
|
self._protocol = 'iSCSI'
|
||||||
|
if len(self.configuration.infinidat_iscsi_netspaces) == 0:
|
||||||
|
msg = _('No iSCSI network spaces configured')
|
||||||
|
raise exception.VolumeDriverException(message=msg)
|
||||||
|
else:
|
||||||
|
self._protocol = 'FC'
|
||||||
LOG.debug('setup complete')
|
LOG.debug('setup complete')
|
||||||
|
|
||||||
def _cleanup_wwpn(self, wwpn):
|
|
||||||
return wwpn.replace(':', '')
|
|
||||||
|
|
||||||
def _make_volume_name(self, cinder_volume):
|
def _make_volume_name(self, cinder_volume):
|
||||||
return 'openstack-vol-%s' % cinder_volume.id
|
return 'openstack-vol-%s' % cinder_volume.id
|
||||||
|
|
||||||
def _make_snapshot_name(self, cinder_snapshot):
|
def _make_snapshot_name(self, cinder_snapshot):
|
||||||
return 'openstack-snap-%s' % cinder_snapshot.id
|
return 'openstack-snap-%s' % cinder_snapshot.id
|
||||||
|
|
||||||
def _make_host_name(self, wwpn):
|
def _make_host_name(self, port):
|
||||||
wwn_for_name = self._cleanup_wwpn(wwpn)
|
return 'openstack-host-%s' % str(port).replace(":", ".")
|
||||||
return 'openstack-host-%s' % wwn_for_name
|
|
||||||
|
|
||||||
def _get_infinidat_volume_by_name(self, name):
|
def _get_infinidat_volume_by_name(self, name):
|
||||||
volume = self._system.volumes.safe_get(name=name)
|
volume = self._system.volumes.safe_get(name=name)
|
||||||
@ -143,12 +163,12 @@ class InfiniboxVolumeDriver(san.SanDriver):
|
|||||||
raise exception.VolumeDriverException(message=msg)
|
raise exception.VolumeDriverException(message=msg)
|
||||||
return pool
|
return pool
|
||||||
|
|
||||||
def _get_or_create_host(self, wwpn):
|
def _get_or_create_host(self, port):
|
||||||
host_name = self._make_host_name(wwpn)
|
host_name = self._make_host_name(port)
|
||||||
infinidat_host = self._system.hosts.safe_get(name=host_name)
|
infinidat_host = self._system.hosts.safe_get(name=host_name)
|
||||||
if infinidat_host is None:
|
if infinidat_host is None:
|
||||||
infinidat_host = self._system.hosts.create(name=host_name)
|
infinidat_host = self._system.hosts.create(name=host_name)
|
||||||
infinidat_host.add_port(self._cleanup_wwpn(wwpn))
|
infinidat_host.add_port(port)
|
||||||
return infinidat_host
|
return infinidat_host
|
||||||
|
|
||||||
def _get_mapping(self, host, volume):
|
def _get_mapping(self, host, volume):
|
||||||
@ -172,19 +192,15 @@ class InfiniboxVolumeDriver(san.SanDriver):
|
|||||||
port.get_state() == 'OK'):
|
port.get_state() == 'OK'):
|
||||||
yield str(port.get_wwpn())
|
yield str(port.get_wwpn())
|
||||||
|
|
||||||
@fczm_utils.add_fc_zone
|
def _initialize_connection_fc(self, volume, connector):
|
||||||
@infinisdk_to_cinder_exceptions
|
|
||||||
@coordination.synchronized('infinidat-{self.management_address}-lock')
|
|
||||||
def initialize_connection(self, volume, connector):
|
|
||||||
"""Map an InfiniBox volume to the host"""
|
|
||||||
volume_name = self._make_volume_name(volume)
|
volume_name = self._make_volume_name(volume)
|
||||||
infinidat_volume = self._get_infinidat_volume_by_name(volume_name)
|
infinidat_volume = self._get_infinidat_volume_by_name(volume_name)
|
||||||
for wwpn in connector['wwpns']:
|
ports = [wwn.WWN(wwpn) for wwpn in connector['wwpns']]
|
||||||
infinidat_host = self._get_or_create_host(wwpn)
|
for port in ports:
|
||||||
|
infinidat_host = self._get_or_create_host(port)
|
||||||
mapping = self._get_or_create_mapping(infinidat_host,
|
mapping = self._get_or_create_mapping(infinidat_host,
|
||||||
infinidat_volume)
|
infinidat_volume)
|
||||||
lun = mapping.get_lun()
|
lun = mapping.get_lun()
|
||||||
|
|
||||||
# Create initiator-target mapping.
|
# Create initiator-target mapping.
|
||||||
target_wwpns = list(self._get_online_fc_ports())
|
target_wwpns = list(self._get_online_fc_ports())
|
||||||
target_wwpns, init_target_map = self._build_initiator_target_map(
|
target_wwpns, init_target_map = self._build_initiator_target_map(
|
||||||
@ -195,15 +211,93 @@ class InfiniboxVolumeDriver(san.SanDriver):
|
|||||||
target_lun=lun,
|
target_lun=lun,
|
||||||
initiator_target_map=init_target_map))
|
initiator_target_map=init_target_map))
|
||||||
|
|
||||||
|
def _get_iscsi_network_space(self, netspace_name):
|
||||||
|
netspace = self._system.network_spaces.safe_get(
|
||||||
|
service='ISCSI_SERVICE',
|
||||||
|
name=netspace_name)
|
||||||
|
if netspace is None:
|
||||||
|
msg = (_('Could not find iSCSI network space with name "%s"') %
|
||||||
|
netspace_name)
|
||||||
|
raise exception.VolumeDriverException(message=msg)
|
||||||
|
return netspace
|
||||||
|
|
||||||
|
def _get_iscsi_portal(self, netspace):
|
||||||
|
for netpsace_interface in netspace.get_ips():
|
||||||
|
if netpsace_interface.enabled:
|
||||||
|
port = netspace.get_properties().iscsi_tcp_port
|
||||||
|
return "%s:%s" % (netpsace_interface.ip_address, port)
|
||||||
|
# if we get here it means there are no enabled ports
|
||||||
|
msg = (_('No available interfaces in iSCSI network space %s') %
|
||||||
|
netspace.get_name())
|
||||||
|
raise exception.VolumeDriverException(message=msg)
|
||||||
|
|
||||||
|
def _initialize_connection_iscsi(self, volume, connector):
|
||||||
|
volume_name = self._make_volume_name(volume)
|
||||||
|
infinidat_volume = self._get_infinidat_volume_by_name(volume_name)
|
||||||
|
port = iqn.IQN(connector['initiator'])
|
||||||
|
infinidat_host = self._get_or_create_host(port)
|
||||||
|
if self.configuration.use_chap_auth:
|
||||||
|
chap_username = (self.configuration.chap_username or
|
||||||
|
vol_utils.generate_username())
|
||||||
|
chap_password = (self.configuration.chap_password or
|
||||||
|
vol_utils.generate_password())
|
||||||
|
infinidat_host.update_fields(
|
||||||
|
security_method='CHAP',
|
||||||
|
security_chap_inbound_username=chap_username,
|
||||||
|
security_chap_inbound_secret=chap_password)
|
||||||
|
mapping = self._get_or_create_mapping(infinidat_host,
|
||||||
|
infinidat_volume)
|
||||||
|
lun = mapping.get_lun()
|
||||||
|
netspace_names = self.configuration.infinidat_iscsi_netspaces
|
||||||
|
target_portals = []
|
||||||
|
target_iqns = []
|
||||||
|
target_luns = []
|
||||||
|
for netspace_name in netspace_names:
|
||||||
|
netspace = self._get_iscsi_network_space(netspace_name)
|
||||||
|
target_portals.append(self._get_iscsi_portal(netspace))
|
||||||
|
target_iqns.append(netspace.get_properties().iscsi_iqn)
|
||||||
|
target_luns.append(lun)
|
||||||
|
result_data = dict(target_discovered=True,
|
||||||
|
target_portal=target_portals[0],
|
||||||
|
target_iqn=target_iqns[0],
|
||||||
|
target_lun=target_luns[0])
|
||||||
|
if len(target_portals) > 1:
|
||||||
|
# multiple network spaces defined
|
||||||
|
result_data.update(dict(target_portals=target_portals,
|
||||||
|
target_iqns=target_iqns,
|
||||||
|
target_luns=target_luns))
|
||||||
|
if self.configuration.use_chap_auth:
|
||||||
|
result_data.update(dict(auth_method='CHAP',
|
||||||
|
auth_username=chap_username,
|
||||||
|
auth_password=chap_password))
|
||||||
|
return dict(driver_volume_type='iscsi',
|
||||||
|
data=result_data)
|
||||||
|
|
||||||
|
@fczm_utils.add_fc_zone
|
||||||
|
@infinisdk_to_cinder_exceptions
|
||||||
|
@coordination.synchronized('infinidat-{self.management_address}-lock')
|
||||||
|
def initialize_connection(self, volume, connector):
|
||||||
|
"""Map an InfiniBox volume to the host"""
|
||||||
|
if self._protocol == 'FC':
|
||||||
|
return self._initialize_connection_fc(volume, connector)
|
||||||
|
else:
|
||||||
|
return self._initialize_connection_iscsi(volume, connector)
|
||||||
|
|
||||||
@fczm_utils.remove_fc_zone
|
@fczm_utils.remove_fc_zone
|
||||||
@infinisdk_to_cinder_exceptions
|
@infinisdk_to_cinder_exceptions
|
||||||
@coordination.synchronized('infinidat-{self.management_address}-lock')
|
@coordination.synchronized('infinidat-{self.management_address}-lock')
|
||||||
def terminate_connection(self, volume, connector, **kwargs):
|
def terminate_connection(self, volume, connector, **kwargs):
|
||||||
"""Unmap an InfiniBox volume from the host"""
|
"""Unmap an InfiniBox volume from the host"""
|
||||||
infinidat_volume = self._get_infinidat_volume(volume)
|
infinidat_volume = self._get_infinidat_volume(volume)
|
||||||
|
if self._protocol == 'FC':
|
||||||
|
volume_type = 'fibre_channel'
|
||||||
|
ports = [wwn.WWN(wwpn) for wwpn in connector['wwpns']]
|
||||||
|
else:
|
||||||
|
volume_type = 'iscsi'
|
||||||
|
ports = [iqn.IQN(connector['initiator'])]
|
||||||
result_data = dict()
|
result_data = dict()
|
||||||
for wwpn in connector['wwpns']:
|
for port in ports:
|
||||||
host_name = self._make_host_name(wwpn)
|
host_name = self._make_host_name(port)
|
||||||
host = self._system.hosts.safe_get(name=host_name)
|
host = self._system.hosts.safe_get(name=host_name)
|
||||||
if host is None:
|
if host is None:
|
||||||
# not found. ignore.
|
# not found. ignore.
|
||||||
@ -213,17 +307,17 @@ class InfiniboxVolumeDriver(san.SanDriver):
|
|||||||
host.unmap_volume(infinidat_volume)
|
host.unmap_volume(infinidat_volume)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
continue # volume mapping not found
|
continue # volume mapping not found
|
||||||
# check if the host now doesn't have mappings, to delete host_entry
|
# check if the host now doesn't have mappings
|
||||||
# if needed
|
if host is not None and len(host.get_luns()) == 0:
|
||||||
if len(host.get_luns()) == 0:
|
|
||||||
host.safe_delete()
|
host.safe_delete()
|
||||||
# Create initiator-target mapping.
|
if self._protocol == 'FC':
|
||||||
target_wwpns = list(self._get_online_fc_ports())
|
# Create initiator-target mapping to delete host entry
|
||||||
target_wwpns, init_target_map = self._build_initiator_target_map(
|
target_wwpns = list(self._get_online_fc_ports())
|
||||||
connector, target_wwpns)
|
target_wwpns, target_map = self._build_initiator_target_map(
|
||||||
result_data = dict(target_wwn=target_wwpns,
|
connector, target_wwpns)
|
||||||
initiator_target_map=init_target_map)
|
result_data = dict(target_wwn=target_wwpns,
|
||||||
return dict(driver_volume_type='fibre_channel',
|
initiator_target_map=target_map)
|
||||||
|
return dict(driver_volume_type=volume_type,
|
||||||
data=result_data)
|
data=result_data)
|
||||||
|
|
||||||
@infinisdk_to_cinder_exceptions
|
@infinisdk_to_cinder_exceptions
|
||||||
@ -239,7 +333,7 @@ class InfiniboxVolumeDriver(san.SanDriver):
|
|||||||
self._volume_stats = dict(volume_backend_name=self._backend_name,
|
self._volume_stats = dict(volume_backend_name=self._backend_name,
|
||||||
vendor_name=VENDOR_NAME,
|
vendor_name=VENDOR_NAME,
|
||||||
driver_version=self.VERSION,
|
driver_version=self.VERSION,
|
||||||
storage_protocol='FC',
|
storage_protocol=self._protocol,
|
||||||
consistencygroup_support='False',
|
consistencygroup_support='False',
|
||||||
total_capacity_gb=total_capacity_gb,
|
total_capacity_gb=total_capacity_gb,
|
||||||
free_capacity_gb=free_capacity_gb)
|
free_capacity_gb=free_capacity_gb)
|
||||||
@ -288,14 +382,45 @@ class InfiniboxVolumeDriver(san.SanDriver):
|
|||||||
volume.create_snapshot(name=name)
|
volume.create_snapshot(name=name)
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def _device_connect_context(self, volume):
|
def _connection_context(self, volume):
|
||||||
connector = utils.brick_get_connector_properties()
|
use_multipath = self.configuration.use_multipath_for_image_xfer
|
||||||
|
enforce_multipath = self.configuration.enforce_multipath_for_image_xfer
|
||||||
|
connector = utils.brick_get_connector_properties(use_multipath,
|
||||||
|
enforce_multipath)
|
||||||
connection = self.initialize_connection(volume, connector)
|
connection = self.initialize_connection(volume, connector)
|
||||||
try:
|
try:
|
||||||
yield self._connect_device(connection)
|
yield connection
|
||||||
finally:
|
finally:
|
||||||
self.terminate_connection(volume, connector)
|
self.terminate_connection(volume, connector)
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def _attach_context(self, connection):
|
||||||
|
use_multipath = self.configuration.use_multipath_for_image_xfer
|
||||||
|
device_scan_attempts = self.configuration.num_volume_device_scan_tries
|
||||||
|
protocol = connection['driver_volume_type']
|
||||||
|
connector = utils.brick_get_connector(
|
||||||
|
protocol,
|
||||||
|
use_multipath=use_multipath,
|
||||||
|
device_scan_attempts=device_scan_attempts,
|
||||||
|
conn=connection)
|
||||||
|
attach_info = None
|
||||||
|
try:
|
||||||
|
attach_info = self._connect_device(connection)
|
||||||
|
yield attach_info
|
||||||
|
except exception.DeviceUnavailable as exc:
|
||||||
|
attach_info = exc.kwargs.get('attach_info', None)
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
if attach_info:
|
||||||
|
connector.disconnect_volume(attach_info['conn']['data'],
|
||||||
|
attach_info['device'])
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def _device_connect_context(self, volume):
|
||||||
|
with self._connection_context(volume) as connection:
|
||||||
|
with self._attach_context(connection) as attach_info:
|
||||||
|
yield attach_info
|
||||||
|
|
||||||
@infinisdk_to_cinder_exceptions
|
@infinisdk_to_cinder_exceptions
|
||||||
def create_volume_from_snapshot(self, volume, snapshot):
|
def create_volume_from_snapshot(self, volume, snapshot):
|
||||||
"""Create volume from snapshot.
|
"""Create volume from snapshot.
|
||||||
|
@ -39,4 +39,6 @@ vmemclient>=1.1.8 # Apache-2.0
|
|||||||
|
|
||||||
# INFINIDAT
|
# INFINIDAT
|
||||||
infinisdk # BSD-3
|
infinisdk # BSD-3
|
||||||
capacity # BSD
|
capacity # BSD
|
||||||
|
infi.dtypes.wwn # PSF
|
||||||
|
infi.dtypes.iqn # PSF
|
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Support for iSCSI in INFINIDAT InfiniBox driver.
|
Loading…
Reference in New Issue
Block a user