Merge "Windows iSCSI: Add CHAP authentication support"
This commit is contained in:
@@ -35,7 +35,6 @@ from cinder.volume.drivers.windows import vhdutils
|
||||
from cinder.volume.drivers.windows import windows
|
||||
from cinder.volume.drivers.windows import windows_utils
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
@@ -156,18 +155,35 @@ class TestWindowsDriver(test.TestCase):
|
||||
|
||||
drv.delete_snapshot(snapshot)
|
||||
|
||||
def test_create_export(self):
|
||||
def _test_create_export(self, chap_enabled=False):
|
||||
drv = self._driver
|
||||
|
||||
volume = db_fakes.get_fake_volume_info()
|
||||
|
||||
initiator_name = "%s%s" % (CONF.iscsi_target_prefix, volume['name'])
|
||||
fake_chap_username = 'fake_chap_username'
|
||||
fake_chap_password = 'fake_chap_password'
|
||||
|
||||
self.flags(use_chap_auth=chap_enabled)
|
||||
self.flags(chap_username=fake_chap_username)
|
||||
self.flags(chap_password=fake_chap_password)
|
||||
|
||||
self.mox.StubOutWithMock(windows_utils.WindowsUtils,
|
||||
'create_iscsi_target')
|
||||
windows_utils.WindowsUtils.create_iscsi_target(initiator_name)
|
||||
self.mox.StubOutWithMock(windows_utils.WindowsUtils,
|
||||
'add_disk_to_target')
|
||||
self.mox.StubOutWithMock(windows_utils.WindowsUtils,
|
||||
'create_iscsi_target')
|
||||
self.mox.StubOutWithMock(windows_utils.WindowsUtils,
|
||||
'set_chap_credentials')
|
||||
self.mox.StubOutWithMock(self._driver,
|
||||
'remove_export')
|
||||
|
||||
self._driver.remove_export(mox.IgnoreArg(), mox.IgnoreArg())
|
||||
windows_utils.WindowsUtils.create_iscsi_target(initiator_name)
|
||||
|
||||
if chap_enabled:
|
||||
windows_utils.WindowsUtils.set_chap_credentials(
|
||||
mox.IgnoreArg(),
|
||||
fake_chap_username,
|
||||
fake_chap_password)
|
||||
|
||||
windows_utils.WindowsUtils.add_disk_to_target(volume['name'],
|
||||
initiator_name)
|
||||
|
||||
@@ -175,7 +191,19 @@ class TestWindowsDriver(test.TestCase):
|
||||
|
||||
export_info = drv.create_export(None, volume)
|
||||
|
||||
self.assertEqual(export_info['provider_location'], initiator_name)
|
||||
self.assertEqual(initiator_name, export_info['provider_location'])
|
||||
if chap_enabled:
|
||||
expected_provider_auth = ' '.join(('CHAP',
|
||||
fake_chap_username,
|
||||
fake_chap_password))
|
||||
self.assertEqual(expected_provider_auth,
|
||||
export_info['provider_auth'])
|
||||
|
||||
def test_create_export_chap_disabled(self):
|
||||
self._test_create_export()
|
||||
|
||||
def test_create_export_chap_enabled(self):
|
||||
self._test_create_export(chap_enabled=True)
|
||||
|
||||
def test_initialize_connection(self):
|
||||
drv = self._driver
|
||||
|
||||
@@ -90,3 +90,49 @@ class WindowsUtilsTestCase(test.TestCase):
|
||||
exception.VolumeBackendAPIException,
|
||||
self.wutils.is_resize_needed,
|
||||
mock.sentinel.vhd_path, 1, 2)
|
||||
|
||||
@mock.patch.object(windows_utils.WindowsUtils, '_wmi_obj_set_attr')
|
||||
@mock.patch.object(windows_utils, 'wmi', create=True)
|
||||
def test_set_chap_credentials(self, mock_wmi, mock_set_attr):
|
||||
mock_wt_host = mock.Mock()
|
||||
mock_wt_host_class = self.wutils._conn_wmi.WT_Host
|
||||
mock_wt_host_class.return_value = [mock_wt_host]
|
||||
|
||||
self.wutils.set_chap_credentials(mock.sentinel.target_name,
|
||||
mock.sentinel.chap_username,
|
||||
mock.sentinel.chap_password)
|
||||
|
||||
mock_wt_host_class.assert_called_once_with(
|
||||
HostName=mock.sentinel.target_name)
|
||||
|
||||
mock_set_attr.assert_has_calls([
|
||||
mock.call(mock_wt_host, 'EnableCHAP', True),
|
||||
mock.call(mock_wt_host, 'CHAPUserName',
|
||||
mock.sentinel.chap_username),
|
||||
mock.call(mock_wt_host, 'CHAPSecret',
|
||||
mock.sentinel.chap_password)])
|
||||
|
||||
mock_wt_host.put.assert_called_once_with()
|
||||
|
||||
@mock.patch.object(windows_utils.WindowsUtils, '_wmi_obj_set_attr')
|
||||
@mock.patch.object(windows_utils, 'wmi', create=True)
|
||||
def test_set_chap_credentials_exc(self, mock_wmi, mock_set_attr):
|
||||
mock_wmi.x_wmi = Exception
|
||||
mock_set_attr.side_effect = mock_wmi.x_wmi
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.wutils.set_chap_credentials,
|
||||
mock.sentinel.target_name,
|
||||
mock.sentinel.chap_username,
|
||||
mock.sentinel.chap_password)
|
||||
|
||||
def test_set_wmi_obj_attr(self):
|
||||
wmi_obj = mock.Mock()
|
||||
wmi_property_method = wmi_obj.wmi_property
|
||||
wmi_property = wmi_obj.wmi_property.return_value
|
||||
|
||||
self.wutils._wmi_obj_set_attr(wmi_obj,
|
||||
mock.sentinel.key,
|
||||
mock.sentinel.value)
|
||||
|
||||
wmi_property_method.assert_called_once_with(mock.sentinel.key)
|
||||
wmi_property.set.assert_called_once_with(mock.sentinel.value)
|
||||
|
||||
@@ -30,6 +30,7 @@ from cinder.volume import driver
|
||||
from cinder.volume.drivers.windows import constants
|
||||
from cinder.volume.drivers.windows import vhdutils
|
||||
from cinder.volume.drivers.windows import windows_utils
|
||||
from cinder.volume import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
@@ -134,15 +135,34 @@ class WindowsDriver(driver.ISCSIDriver):
|
||||
|
||||
def create_export(self, context, volume):
|
||||
"""Driver entry point to get the export info for a new volume."""
|
||||
# Since the iSCSI targets are not reused, being deleted when the
|
||||
# volume is detached, we should clean up existing targets before
|
||||
# creating a new one.
|
||||
self.remove_export(context, volume)
|
||||
|
||||
target_name = "%s%s" % (self.configuration.iscsi_target_prefix,
|
||||
volume['name'])
|
||||
updates = {'provider_location': target_name}
|
||||
self.utils.create_iscsi_target(target_name)
|
||||
|
||||
if self.configuration.use_chap_auth:
|
||||
chap_username = (self.configuration.chap_username or
|
||||
utils.generate_username())
|
||||
chap_password = (self.configuration.chap_password or
|
||||
utils.generate_password())
|
||||
|
||||
self.utils.set_chap_credentials(target_name,
|
||||
chap_username,
|
||||
chap_password)
|
||||
|
||||
updates['provider_auth'] = ' '.join(('CHAP',
|
||||
chap_username,
|
||||
chap_password))
|
||||
# Get the disk to add
|
||||
vol_name = volume['name']
|
||||
self.utils.add_disk_to_target(vol_name, target_name)
|
||||
|
||||
return {'provider_location': target_name}
|
||||
return updates
|
||||
|
||||
def remove_export(self, context, volume):
|
||||
"""Driver entry point to remove an export for a volume.
|
||||
|
||||
@@ -316,6 +316,28 @@ class WindowsUtils(object):
|
||||
LOG.error(err_msg)
|
||||
raise exception.VolumeBackendAPIException(data=err_msg)
|
||||
|
||||
def set_chap_credentials(self, target_name, chap_username, chap_password):
|
||||
try:
|
||||
wt_host = self._conn_wmi.WT_Host(HostName=target_name)[0]
|
||||
self._wmi_obj_set_attr(wt_host, 'EnableCHAP', True)
|
||||
self._wmi_obj_set_attr(wt_host, 'CHAPUserName', chap_username)
|
||||
self._wmi_obj_set_attr(wt_host, 'CHAPSecret', chap_password)
|
||||
wt_host.put()
|
||||
except wmi.x_wmi as exc:
|
||||
err_msg = (_('Failed to set CHAP credentials on '
|
||||
'target %(target_name)s. WMI exception: %(wmi_exc)s')
|
||||
% {'target_name': target_name,
|
||||
'wmi_exc': exc})
|
||||
LOG.error(err_msg)
|
||||
raise exception.VolumeBackendAPIException(data=err_msg)
|
||||
|
||||
@staticmethod
|
||||
def _wmi_obj_set_attr(wmi_obj, key, value):
|
||||
# Due to a bug in the python WMI module, some wmi object attributes
|
||||
# cannot be modified. This method is used as a workaround.
|
||||
wmi_property = getattr(wmi_obj, 'wmi_property')
|
||||
wmi_property(key).set(value)
|
||||
|
||||
def add_disk_to_target(self, vol_name, target_name):
|
||||
"""Adds the disk to the target."""
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user