From: Lucian Petrut Date: Tue, 5 May 2015 08:00:30 +0000 (+0300) Subject: Windows iSCSI: Add CHAP authentication support X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=c0a503f0b41f54124e8a4c4d46ede635e5a424e8;p=openstack-build%2Fcinder-build.git Windows iSCSI: Add CHAP authentication support This patch adds CHAP authentication support for the Windows iSCSI driver. One-way CHAP authentication is used, having the CHAP credentials generated when the iSCSI target is created. Implements: blueprint windows-iscsi-chap Change-Id: Iee24b74c66f72efe2a9bb2187e2a4f9d9e135872 --- diff --git a/cinder/tests/unit/windows/test_windows.py b/cinder/tests/unit/windows/test_windows.py index 259b7ad65..da30c88ef 100644 --- a/cinder/tests/unit/windows/test_windows.py +++ b/cinder/tests/unit/windows/test_windows.py @@ -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, + 'add_disk_to_target') 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') + '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 diff --git a/cinder/tests/unit/windows/test_windows_utils.py b/cinder/tests/unit/windows/test_windows_utils.py index ae2008a6e..c1a65626c 100644 --- a/cinder/tests/unit/windows/test_windows_utils.py +++ b/cinder/tests/unit/windows/test_windows_utils.py @@ -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) diff --git a/cinder/volume/drivers/windows/windows.py b/cinder/volume/drivers/windows/windows.py index a5e33c2ff..df765b867 100644 --- a/cinder/volume/drivers/windows/windows.py +++ b/cinder/volume/drivers/windows/windows.py @@ -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. diff --git a/cinder/volume/drivers/windows/windows_utils.py b/cinder/volume/drivers/windows/windows_utils.py index 333905570..4034d4be7 100644 --- a/cinder/volume/drivers/windows/windows_utils.py +++ b/cinder/volume/drivers/windows/windows_utils.py @@ -304,6 +304,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: