]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Windows iSCSI: Add CHAP authentication support
authorLucian Petrut <lpetrut@cloudbasesolutions.com>
Tue, 5 May 2015 08:00:30 +0000 (11:00 +0300)
committerLucian Petrut <lpetrut@cloudbasesolutions.com>
Mon, 11 May 2015 12:04:09 +0000 (15:04 +0300)
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

cinder/tests/unit/windows/test_windows.py
cinder/tests/unit/windows/test_windows_utils.py
cinder/volume/drivers/windows/windows.py
cinder/volume/drivers/windows/windows_utils.py

index 259b7ad65f97d8d8dd4171754a41a08257992f95..da30c88efb89ce0dd690ee885dc6706205a8c0aa 100644 (file)
@@ -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
index ae2008a6ec25d82bd2adc5fa06b15eca020c35c5..c1a65626c783286e173565a07efd650cd7253c55 100644 (file)
@@ -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)
index a5e33c2ffa4606b2003badbef0d485355296be4c..df765b8670cdc5908fdfe70a2cf05473b43cf08f 100644 (file)
@@ -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.
index 3339055701e69c757eb01fc19671b21fcc9916dd..4034d4be7731774b9983233dec98dbda5936a164 100644 (file)
@@ -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: