Merge "Kaminario K2: Add non discovery iSCSI multipath"

This commit is contained in:
Zuul 2017-10-29 23:17:05 +00:00 committed by Gerrit Code Review
commit cbf2c57e02
4 changed files with 101 additions and 42 deletions

View File

@ -15,6 +15,7 @@
"""Unit tests for kaminario driver.""" """Unit tests for kaminario driver."""
import re import re
import ddt
import mock import mock
from oslo_utils import units from oslo_utils import units
import time import time
@ -47,12 +48,13 @@ class FakeK2Obj(object):
class FakeSaveObject(FakeK2Obj): class FakeSaveObject(FakeK2Obj):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
item = kwargs.pop('item', 1)
self.ntype = kwargs.get('ntype') 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.iscsi_qualified_target_name = "xyztlnxyz"
self.snapshot = FakeK2Obj() self.snapshot = FakeK2Obj()
self.name = 'test' self.name = 'test'
self.pwwn = '50024f4053300300' self.pwwn = '50024f405330030%s' % item
self.volume_group = self self.volume_group = self
self.is_dedup = True self.is_dedup = True
self.size = units.Mi self.size = units.Mi
@ -83,8 +85,8 @@ class FakeSaveObjectExp(FakeSaveObject):
class FakeSearchObject(object): class FakeSearchObject(object):
hits = [FakeSaveObject()] hits = [FakeSaveObject(item=1), FakeSaveObject(item=2)]
total = 1 total = 2
def __init__(self, *args): def __init__(self, *args):
if args and "mappings" in args[0]: if args and "mappings" in args[0]:
@ -119,14 +121,14 @@ class Replication(object):
rpo = 500 rpo = 500
class TestKaminarioISCSI(test.TestCase): class TestKaminarioCommon(test.TestCase):
driver = None driver = None
conf = None conf = None
def setUp(self): def setUp(self):
self._setup_config() self._setup_config()
self._setup_driver() self._setup_driver()
super(TestKaminarioISCSI, self).setUp() super(TestKaminarioCommon, self).setUp()
self.context = context.get_admin_context() self.context = context.get_admin_context()
self.vol = fake_volume.fake_volume_obj(self.context) self.vol = fake_volume.fake_volume_obj(self.context)
self.vol.volume_type = fake_volume.fake_volume_type_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.kaminario_dedup_type_name = "dedup"
self.conf.volume_dd_blocksize = 2 self.conf.volume_dd_blocksize = 2
self.conf.unique_fqdn_network = True self.conf.unique_fqdn_network = True
self.conf.disable_discovery = False
def _setup_driver(self): def _setup_driver(self):
self.driver = (kaminario_iscsi. self.driver = (kaminario_iscsi.
@ -258,12 +261,6 @@ class TestKaminarioISCSI(test.TestCase):
self.assertRaises(exception.KaminarioCinderDriverException, self.assertRaises(exception.KaminarioCinderDriverException,
self.driver.extend_volume, self.vol, new_size) 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): def test_initialize_connection_with_exception(self):
"""Test initialize_connection_with_exception.""" """Test initialize_connection_with_exception."""
self.driver.client = FakeKrestException() self.driver.client = FakeKrestException()
@ -271,11 +268,6 @@ class TestKaminarioISCSI(test.TestCase):
self.driver.initialize_connection, self.vol, self.driver.initialize_connection, self.vol,
CONNECTOR) 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): def test_get_lun_number(self):
"""Test _get_lun_number.""" """Test _get_lun_number."""
host, host_rs, host_name = self.driver._get_host_object(CONNECTOR) host, host_rs, host_name = self.driver._get_host_object(CONNECTOR)
@ -291,20 +283,15 @@ class TestKaminarioISCSI(test.TestCase):
"""Test _get_host_object.""" """Test _get_host_object."""
host, host_rs, host_name = self.driver._get_host_object(CONNECTOR) host, host_rs, host_name = self.driver._get_host_object(CONNECTOR)
self.assertEqual(548, host.id) self.assertEqual(548, host.id)
self.assertEqual(1, host_rs.total) self.assertEqual(2, host_rs.total)
self.assertEqual('test-k2', host_name) 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): def test_k2_initialize_connection(self):
"""Test k2_initialize_connection.""" """Test k2_initialize_connection."""
result = self.driver.k2_initialize_connection(self.vol, CONNECTOR) result = self.driver.k2_initialize_connection(self.vol, CONNECTOR)
self.assertEqual(548, result) self.assertEqual(548, result)
@mock.patch.object(FakeSearchObject, 'total', 1)
def test_manage_existing(self): def test_manage_existing(self):
"""Test manage_existing.""" """Test manage_existing."""
self.driver._get_replica_status = mock.Mock(return_value=False) self.driver._get_replica_status = mock.Mock(return_value=False)
@ -543,7 +530,64 @@ class TestKaminarioISCSI(test.TestCase):
self.assertEqual(expected, result) 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): def _setup_driver(self):
self.driver = (kaminario_fc. self.driver = (kaminario_fc.
@ -562,7 +606,8 @@ class TestKaminarioFC(TestKaminarioISCSI):
def test_get_target_info(self): def test_get_target_info(self):
"""Test get_target_info.""" """Test get_target_info."""
target_wwpn = self.driver.get_target_info(self.vol) 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): def test_terminate_connection(self):
"""Test terminate_connection.""" """Test terminate_connection."""

View File

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

View File

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