Add CHAP credentials support

Currently, the Hyper-V driver ignores the according CHAP
credentials when logging in iSCSI targets. For this reason,
attaching volumes fails when CHAP authentication is enforced.

This patch adds one way CHAP authentication support.

Change-Id: Id4d8800ae08dc24a01fc146f65c68b0b41d49e3d
This commit is contained in:
Lucian Petrut 2014-11-19 13:23:57 +02:00
parent 8accd1cb44
commit 0309ba9480
7 changed files with 85 additions and 35 deletions

View File

@ -61,27 +61,23 @@ def get_fake_volume_info_data(target_portal, volume_id):
return {
'driver_volume_type': 'iscsi',
'data': {
'volume_id': 1,
'volume_id': volume_id,
'target_iqn': 'iqn.2010-10.org.openstack:volume-' + volume_id,
'target_portal': target_portal,
'target_lun': 1,
'auth_method': 'CHAP',
}
'auth_username': 'fake_username',
'auth_password': 'fake_password',
'target_discovered': False,
},
'mount_device': 'vda',
'delete_on_termination': False
}
def get_fake_block_device_info(target_portal, volume_id):
return {'block_device_mapping': [{'connection_info': {
'driver_volume_type': 'iscsi',
'data': {'target_lun': 1,
'volume_id': volume_id,
'target_iqn':
'iqn.2010-10.org.openstack:volume-' +
volume_id,
'target_portal': target_portal,
'target_discovered': False}},
'mount_device': 'vda',
'delete_on_termination': False}],
connection_info = get_fake_volume_info_data(target_portal, volume_id)
return {'block_device_mapping': [{'connection_info': connection_info}],
'root_device_name': 'fake_root_device_name',
'ephemerals': [],
'swap': None

View File

@ -1193,7 +1193,9 @@ class HyperVAPITestCase(HyperVAPIBaseTestCase):
volumeutils.VolumeUtils.login_storage_target(target_lun,
target_iqn,
target_portal)
target_portal,
'fake_username',
'fake_password')
self._mock_get_mounted_disk_from_lun(target_iqn, target_lun,
fake_mounted_disk,

View File

@ -73,12 +73,19 @@ class VolumeUtilsTestCase(test_basevolumeutils.BaseVolumeUtilsTestCase):
def test_login_new_portal(self):
self._test_login_target_portal(False)
def _test_login_target(self, target_connected, raise_exception=False):
def _test_login_target(self, target_connected=False, raise_exception=False,
use_chap=False):
fake_portal = '%s:%s' % (self._FAKE_PORTAL_ADDR,
self._FAKE_PORTAL_PORT)
self._volutils.execute = mock.MagicMock()
self._volutils._login_target_portal = mock.MagicMock()
if use_chap:
username, password = (mock.sentinel.username,
mock.sentinel.password)
else:
username, password = None, None
if target_connected:
self._volutils.execute.return_value = self._FAKE_TARGET
elif raise_exception:
@ -90,28 +97,34 @@ class VolumeUtilsTestCase(test_basevolumeutils.BaseVolumeUtilsTestCase):
if raise_exception:
self.assertRaises(vmutils.HyperVException,
self._volutils.login_storage_target,
self._FAKE_LUN, self._FAKE_TARGET, fake_portal)
self._FAKE_LUN, self._FAKE_TARGET,
fake_portal, username, password)
else:
self._volutils.login_storage_target(self._FAKE_LUN,
self._FAKE_TARGET,
fake_portal)
call_list = self._volutils.execute.call_args_list
all_call_args = [arg for call in call_list for arg in call[0]]
fake_portal,
username, password)
if target_connected:
call_list = self._volutils.execute.call_args_list
all_call_args = [arg for call in call_list for arg in call[0]]
self.assertNotIn('qlogintarget', all_call_args)
else:
self.assertIn('qlogintarget', all_call_args)
self._volutils.execute.assert_any_call(
'iscsicli.exe', 'qlogintarget',
self._FAKE_TARGET, username, password)
def test_login_connected_target(self):
self._test_login_target(True)
self._test_login_target(target_connected=True)
def test_login_disconncted_target(self):
self._test_login_target(False)
self._test_login_target()
def test_login_target_exception(self):
self._test_login_target(False, True)
self._test_login_target(raise_exception=True)
def test_login_target_using_chap(self):
self._test_login_target(use_chap=True)
def _test_execute_wrapper(self, raise_exception):
fake_cmd = ('iscsicli.exe', 'ListTargetPortals')

View File

@ -68,7 +68,8 @@ class VolumeUtilsV2TestCase(test.NoDBTestCase):
def test_login_new_portal(self):
self._test_login_target_portal(False)
def _test_login_target(self, target_connected, raise_exception=False):
def _test_login_target(self, target_connected=False, raise_exception=False,
use_chap=False):
fake_portal = '%s:%s' % (self._FAKE_PORTAL_ADDR,
self._FAKE_PORTAL_PORT)
@ -88,6 +89,18 @@ class VolumeUtilsV2TestCase(test.NoDBTestCase):
self._volutilsv2._conn_storage.MSFT_iSCSITarget = (
fake_target_object)
if use_chap:
username, password = (mock.sentinel.username,
mock.sentinel.password)
auth = {
'AuthenticationType': self._volutilsv2._CHAP_AUTH_TYPE,
'ChapUsername': username,
'ChapSecret': password,
}
else:
username, password = None, None
auth = {}
if raise_exception:
self.assertRaises(vmutils.HyperVException,
self._volutilsv2.login_storage_target,
@ -95,22 +108,26 @@ class VolumeUtilsV2TestCase(test.NoDBTestCase):
else:
self._volutilsv2.login_storage_target(self._FAKE_LUN,
self._FAKE_TARGET,
fake_portal)
fake_portal,
username, password)
if target_connected:
fake_target_object.Update.assert_called_with()
else:
fake_target_object.Connect.assert_called_once_with(
IsPersistent=True, NodeAddress=self._FAKE_TARGET)
IsPersistent=True, NodeAddress=self._FAKE_TARGET, **auth)
def test_login_connected_target(self):
self._test_login_target(True)
self._test_login_target(target_connected=True)
def test_login_disconncted_target(self):
self._test_login_target(False)
self._test_login_target()
def test_login_target_exception(self):
self._test_login_target(False, True)
self._test_login_target(raise_exception=True)
def test_login_target_using_chap(self):
self._test_login_target(use_chap=True)
def test_logout_storage_target(self):
mock_msft_target = self._volutilsv2._conn_storage.MSFT_iSCSITarget

View File

@ -95,6 +95,17 @@ class VolumeOps(object):
target_lun = data['target_lun']
target_iqn = data['target_iqn']
target_portal = data['target_portal']
auth_method = data.get('auth_method')
auth_username = data.get('auth_username')
auth_password = data.get('auth_password')
if auth_method and auth_method.upper() != 'CHAP':
raise vmutils.HyperVException(
_("Cannot log in target %(target_iqn)s. Unsupported iSCSI "
"authentication method: %(auth_method)s.") %
{'target_iqn': target_iqn,
'auth_method': auth_method})
# Check if we already logged in
if self._volutils.get_device_number_for_target(target_iqn, target_lun):
LOG.debug("Already logged in on storage target. No need to "
@ -109,7 +120,8 @@ class VolumeOps(object):
{'target_portal': target_portal,
'target_iqn': target_iqn, 'target_lun': target_lun})
self._volutils.login_storage_target(target_lun, target_iqn,
target_portal)
target_portal, auth_username,
auth_password)
# Wait for the target to be mounted
self._get_mounted_disk_from_lun(target_iqn, target_lun, True)

View File

@ -70,7 +70,8 @@ class VolumeUtils(basevolumeutils.BaseVolumeUtils):
'*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*',
'*', '*')
def login_storage_target(self, target_lun, target_iqn, target_portal):
def login_storage_target(self, target_lun, target_iqn, target_portal,
auth_username=None, auth_password=None):
"""Ensure that the target is logged in."""
self._login_target_portal(target_portal)
@ -90,7 +91,8 @@ class VolumeUtils(basevolumeutils.BaseVolumeUtils):
session_info = self.execute('iscsicli.exe', 'SessionList')
if session_info.find(target_iqn) == -1:
# Sending login
self.execute('iscsicli.exe', 'qlogintarget', target_iqn)
self.execute('iscsicli.exe', 'qlogintarget', target_iqn,
auth_username, auth_password)
else:
return
except vmutils.HyperVException as exc:

View File

@ -37,6 +37,8 @@ CONF = cfg.CONF
class VolumeUtilsV2(basevolumeutils.BaseVolumeUtils):
_CHAP_AUTH_TYPE = 'ONEWAYCHAP'
def __init__(self, host='.'):
super(VolumeUtilsV2, self).__init__(host)
@ -62,7 +64,8 @@ class VolumeUtilsV2(basevolumeutils.BaseVolumeUtils):
portal.New(TargetPortalAddress=target_address,
TargetPortalPortNumber=target_port)
def login_storage_target(self, target_lun, target_iqn, target_portal):
def login_storage_target(self, target_lun, target_iqn, target_portal,
auth_username=None, auth_password=None):
"""Ensure that the target is logged in."""
self._login_target_portal(target_portal)
@ -88,8 +91,13 @@ class VolumeUtilsV2(basevolumeutils.BaseVolumeUtils):
return
try:
target = self._conn_storage.MSFT_iSCSITarget
auth = {}
if auth_username and auth_password:
auth['AuthenticationType'] = self._CHAP_AUTH_TYPE
auth['ChapUsername'] = auth_username
auth['ChapSecret'] = auth_password
target.Connect(NodeAddress=target_iqn,
IsPersistent=True)
IsPersistent=True, **auth)
time.sleep(CONF.hyperv.volume_attach_retry_interval)
except wmi.x_wmi as exc:
LOG.debug("Attempt %(attempt)d to connect to target "