Kaminario K2: Add non discovery iSCSI multipath

Current Kaminario K2 driver only supports iSCSI multipathing using
discovery (sendtargets), which in some cases may be undesirable, since a
broken primary path could prevent an attachment.

This code adds the possibility of not using discovery mode and return
all connection information when `initialize_connection` is called, which
should provide increased reliability.

Change-Id: I7932b1d952844b49cfdb504ed0ba188b489d7f44
This commit is contained in:
Gorka Eguileor 2017-09-28 14:00:56 +02:00 committed by Nikesh Mahalka
parent 2986a4bce5
commit c9ec9b9bd7
4 changed files with 101 additions and 42 deletions

View File

@ -15,6 +15,7 @@
"""Unit tests for kaminario driver."""
import re
import ddt
import mock
from oslo_utils import units
import time
@ -47,12 +48,13 @@ class FakeK2Obj(object):
class FakeSaveObject(FakeK2Obj):
def __init__(self, *args, **kwargs):
item = kwargs.pop('item', 1)
self.ntype = kwargs.get('ntype')
self.ip_address = '10.0.0.1'
self.ip_address = '10.0.0.%s' % item
self.iscsi_qualified_target_name = "xyztlnxyz"
self.snapshot = FakeK2Obj()
self.name = 'test'
self.pwwn = '50024f4053300300'
self.pwwn = '50024f405330030%s' % item
self.volume_group = self
self.is_dedup = True
self.size = units.Mi
@ -83,8 +85,8 @@ class FakeSaveObjectExp(FakeSaveObject):
class FakeSearchObject(object):
hits = [FakeSaveObject()]
total = 1
hits = [FakeSaveObject(item=1), FakeSaveObject(item=2)]
total = 2
def __init__(self, *args):
if args and "mappings" in args[0]:
@ -119,14 +121,14 @@ class Replication(object):
rpo = 500
class TestKaminarioISCSI(test.TestCase):
class TestKaminarioCommon(test.TestCase):
driver = None
conf = None
def setUp(self):
self._setup_config()
self._setup_driver()
super(TestKaminarioISCSI, self).setUp()
super(TestKaminarioCommon, self).setUp()
self.context = context.get_admin_context()
self.vol = fake_volume.fake_volume_obj(self.context)
self.vol.volume_type = fake_volume.fake_volume_type_obj(self.context)
@ -140,6 +142,7 @@ class TestKaminarioISCSI(test.TestCase):
self.conf.kaminario_dedup_type_name = "dedup"
self.conf.volume_dd_blocksize = 2
self.conf.unique_fqdn_network = True
self.conf.disable_discovery = False
def _setup_driver(self):
self.driver = (kaminario_iscsi.
@ -258,12 +261,6 @@ class TestKaminarioISCSI(test.TestCase):
self.assertRaises(exception.KaminarioCinderDriverException,
self.driver.extend_volume, self.vol, new_size)
def test_initialize_connection(self):
"""Test initialize_connection."""
conn_info = self.driver.initialize_connection(self.vol, CONNECTOR)
self.assertIn('data', conn_info)
self.assertIn('target_iqn', conn_info['data'])
def test_initialize_connection_with_exception(self):
"""Test initialize_connection_with_exception."""
self.driver.client = FakeKrestException()
@ -271,11 +268,6 @@ class TestKaminarioISCSI(test.TestCase):
self.driver.initialize_connection, self.vol,
CONNECTOR)
def test_terminate_connection(self):
"""Test terminate_connection."""
result = self.driver.terminate_connection(self.vol, CONNECTOR)
self.assertIsNone(result)
def test_get_lun_number(self):
"""Test _get_lun_number."""
host, host_rs, host_name = self.driver._get_host_object(CONNECTOR)
@ -291,20 +283,15 @@ class TestKaminarioISCSI(test.TestCase):
"""Test _get_host_object."""
host, host_rs, host_name = self.driver._get_host_object(CONNECTOR)
self.assertEqual(548, host.id)
self.assertEqual(1, host_rs.total)
self.assertEqual(2, host_rs.total)
self.assertEqual('test-k2', host_name)
def test_get_target_info(self):
"""Test get_target_info."""
iscsi_portal, target_iqn = self.driver.get_target_info(self.vol)
self.assertEqual('10.0.0.1:3260', iscsi_portal)
self.assertEqual('xyztlnxyz', target_iqn)
def test_k2_initialize_connection(self):
"""Test k2_initialize_connection."""
result = self.driver.k2_initialize_connection(self.vol, CONNECTOR)
self.assertEqual(548, result)
@mock.patch.object(FakeSearchObject, 'total', 1)
def test_manage_existing(self):
"""Test manage_existing."""
self.driver._get_replica_status = mock.Mock(return_value=False)
@ -543,7 +530,64 @@ class TestKaminarioISCSI(test.TestCase):
self.assertEqual(expected, result)
class TestKaminarioFC(TestKaminarioISCSI):
@ddt.ddt
class TestKaminarioISCSI(TestKaminarioCommon):
def test_get_target_info(self):
"""Test get_target_info."""
iscsi_portals, target_iqns = self.driver.get_target_info(self.vol)
self.assertEqual(['10.0.0.1:3260', '10.0.0.2:3260'],
iscsi_portals)
self.assertEqual(['xyztlnxyz', 'xyztlnxyz'],
target_iqns)
@ddt.data(True, False)
def test_initialize_connection(self, multipath):
"""Test initialize_connection."""
connector = CONNECTOR.copy()
connector['multipath'] = multipath
self.driver.configuration.disable_discovery = False
conn_info = self.driver.initialize_connection(self.vol, CONNECTOR)
expected = {
'data': {
'target_discovered': True,
'target_iqn': 'xyztlnxyz',
'target_lun': 548,
'target_portal': '10.0.0.1:3260',
},
'driver_volume_type': 'iscsi',
}
self.assertEqual(expected, conn_info)
def test_initialize_connection_multipath(self):
"""Test initialize_connection with multipath."""
connector = CONNECTOR.copy()
connector['multipath'] = True
self.driver.configuration.disable_discovery = True
conn_info = self.driver.initialize_connection(self.vol, connector)
expected = {
'data': {
'target_discovered': True,
'target_iqn': 'xyztlnxyz',
'target_iqns': ['xyztlnxyz', 'xyztlnxyz'],
'target_lun': 548,
'target_luns': [548, 548],
'target_portal': '10.0.0.1:3260',
'target_portals': ['10.0.0.1:3260', '10.0.0.2:3260'],
},
'driver_volume_type': 'iscsi',
}
self.assertEqual(expected, conn_info)
def test_terminate_connection(self):
"""Test terminate_connection."""
result = self.driver.terminate_connection(self.vol, CONNECTOR)
self.assertIsNone(result)
class TestKaminarioFC(TestKaminarioCommon):
def _setup_driver(self):
self.driver = (kaminario_fc.
@ -562,7 +606,8 @@ class TestKaminarioFC(TestKaminarioISCSI):
def test_get_target_info(self):
"""Test get_target_info."""
target_wwpn = self.driver.get_target_info(self.vol)
self.assertEqual(['50024f4053300300'], target_wwpn)
self.assertEqual(['50024f4053300301', '50024f4053300302'],
target_wwpn)
def test_terminate_connection(self):
"""Test terminate_connection."""

View File

@ -59,6 +59,10 @@ kaminario_opts = [
"FQDN. When true this will create host entries on K2 "
"using the FQDN, when false it will use the reversed "
"IQN/WWNN."),
cfg.BoolOpt('disable_discovery',
default=False,
help="Disabling iSCSI discovery (sendtargets) for multipath "
"connections on K2 driver."),
]
CONF = cfg.CONF

View File

@ -61,18 +61,24 @@ class KaminarioISCSIDriver(common.KaminarioCinderDriver):
temp_client = self.client
self.client = self.target
# Get target_portal and target iqn.
iscsi_portal, target_iqn = self.get_target_info(volume)
iscsi_portals, target_iqns = self.get_target_info(volume)
# Map volume.
lun = self.k2_initialize_connection(volume, connector)
# To support replication failback
if temp_client:
self.client = temp_client
# Return target volume information.
return {"driver_volume_type": "iscsi",
"data": {"target_iqn": target_iqn,
"target_portal": iscsi_portal,
"target_lun": lun,
"target_discovered": True}}
result = {"driver_volume_type": "iscsi",
"data": {"target_iqn": target_iqns[0],
"target_portal": iscsi_portals[0],
"target_lun": lun,
"target_discovered": True}}
if self.configuration.disable_discovery and connector.get('multipath'):
result['data'].update(target_iqns=target_iqns,
target_portals=iscsi_portals,
target_luns=[lun] * len(target_iqns))
return result
@kaminario_logger
@coordination.synchronized('{self.k2_lock_name}')
@ -92,28 +98,26 @@ class KaminarioISCSIDriver(common.KaminarioCinderDriver):
def get_target_info(self, volume):
LOG.debug("Searching first iscsi port ip without wan in K2.")
iscsi_ip_rs = self.client.search("system/net_ips")
iscsi_ip = target_iqn = None
iscsi_portals = target_iqns = None
if hasattr(iscsi_ip_rs, 'hits') and iscsi_ip_rs.total != 0:
for ip in iscsi_ip_rs.hits:
if not ip.wan_port:
iscsi_ip = ip.ip_address
break
if not iscsi_ip:
iscsi_portals = ['%s:%s' % (ip.ip_address, ISCSI_TCP_PORT)
for ip in iscsi_ip_rs.hits if not ip.wan_port]
if not iscsi_portals:
msg = _("Unable to get ISCSI IP address from K2.")
LOG.error(msg)
raise exception.KaminarioCinderDriverException(reason=msg)
iscsi_portal = "{0}:{1}".format(iscsi_ip, ISCSI_TCP_PORT)
LOG.debug("Searching system state for target iqn in K2.")
sys_state_rs = self.client.search("system/state")
if hasattr(sys_state_rs, 'hits') and sys_state_rs.total != 0:
target_iqn = sys_state_rs.hits[0].iscsi_qualified_target_name
iqn = sys_state_rs.hits[0].iscsi_qualified_target_name
target_iqns = [iqn] * len(iscsi_portals)
if not target_iqn:
if not target_iqns:
msg = _("Unable to get target iqn from K2.")
LOG.error(msg)
raise exception.KaminarioCinderDriverException(reason=msg)
return iscsi_portal, target_iqn
return iscsi_portals, target_iqns
@kaminario_logger
def _get_host_object(self, connector):

View File

@ -0,0 +1,6 @@
---
features:
- |
Kaminario K2 iSCSI driver now supports non discovery multipathing (Nova and
Cinder won't use iSCSI sendtargets) which can be enabled by setting
`disable_discovery` to `true` in the configuration.