From 7d50d58b634e9821dbd680303f25c0eee2116598 Mon Sep 17 00:00:00 2001
From: Ivan Pchelintsev <Ivan.Pchelintsev@dell.com>
Date: Thu, 22 Oct 2020 12:42:07 +0300
Subject: [PATCH] Add CHAP support to Dell EMC PowerStore driver

Fix bug with using PowerStore with enabled CHAP as a storage backend.

Change-Id: I2164be24a2c6adce78dc7782d2fa2791e1a8f27e
Closes-Bug: #1900979
---
 .../drivers/dell_emc/powerstore/test_base.py  |  16 ++-
 .../test_snapshot_create_delete_revert.py     |   4 +-
 .../powerstore/test_volume_attach_detach.py   |  80 ++++++++-----
 .../test_volume_create_delete_extend.py       |   4 +-
 .../test_volume_create_from_source.py         |   4 +-
 .../drivers/dell_emc/powerstore/adapter.py    | 108 ++++++++++++++----
 .../drivers/dell_emc/powerstore/client.py     |  35 ++++--
 .../drivers/dell_emc/powerstore/driver.py     |   3 +-
 .../drivers/dell_emc/powerstore/utils.py      |  17 +++
 .../drivers/dell-emc-powerstore-driver.rst    |  16 +++
 .../bug-1900979-powerstore-chap-support.yaml  |   5 +
 11 files changed, 228 insertions(+), 64 deletions(-)
 create mode 100644 releasenotes/notes/bug-1900979-powerstore-chap-support.yaml

diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_base.py b/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_base.py
index 833a2bfa29c..c42dce70d83 100644
--- a/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_base.py
+++ b/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_base.py
@@ -20,9 +20,11 @@ from cinder.tests.unit.volume.drivers.dell_emc import powerstore
 
 
 class TestBase(powerstore.TestPowerStoreDriver):
+    @mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
+                "PowerStoreClient.get_chap_config")
     @mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
                 "PowerStoreClient.get_appliance_id_by_name")
-    def test_configuration(self, mock_appliance):
+    def test_configuration(self, mock_appliance, mock_chap):
         mock_appliance.return_value = "A1"
         self.driver.check_for_setup_error()
 
@@ -50,11 +52,16 @@ class TestBase(powerstore.TestPowerStoreDriver):
                                   self.driver.check_for_setup_error)
         self.assertIn("Failed to query PowerStore appliances.", error.msg)
 
+    @mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
+                "PowerStoreClient.get_chap_config")
     @mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
                 "PowerStoreClient.get_appliance_id_by_name")
     @mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
                 "PowerStoreClient.get_appliance_metrics")
-    def test_update_volume_stats(self, mock_metrics, mock_appliance):
+    def test_update_volume_stats(self,
+                                 mock_metrics,
+                                 mock_appliance,
+                                 mock_chap):
         mock_appliance.return_value = "A1"
         mock_metrics.return_value = {
             "physical_total": 2147483648,
@@ -63,12 +70,15 @@ class TestBase(powerstore.TestPowerStoreDriver):
         self.driver.check_for_setup_error()
         self.driver._update_volume_stats()
 
+    @mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
+                "PowerStoreClient.get_chap_config")
     @mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
                 "PowerStoreClient.get_appliance_id_by_name")
     @mock.patch("requests.request")
     def test_update_volume_stats_bad_status(self,
                                             mock_metrics,
-                                            mock_appliance):
+                                            mock_appliance,
+                                            mock_chap):
         mock_appliance.return_value = "A1"
         mock_metrics.return_value = powerstore.MockResponse(rc=400)
         self.driver.check_for_setup_error()
diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_snapshot_create_delete_revert.py b/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_snapshot_create_delete_revert.py
index 728a90b4185..4dc44aed6a5 100644
--- a/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_snapshot_create_delete_revert.py
+++ b/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_snapshot_create_delete_revert.py
@@ -22,9 +22,11 @@ from cinder.tests.unit.volume.drivers.dell_emc import powerstore
 
 
 class TestSnapshotCreateDelete(powerstore.TestPowerStoreDriver):
+    @mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
+                "PowerStoreClient.get_chap_config")
     @mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
                 "PowerStoreClient.get_appliance_id_by_name")
-    def setUp(self, mock_appliance):
+    def setUp(self, mock_appliance, mock_chap):
         super(TestSnapshotCreateDelete, self).setUp()
         mock_appliance.return_value = "A1"
         self.driver.check_for_setup_error()
diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_volume_attach_detach.py b/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_volume_attach_detach.py
index c86ec4ebe6f..dd5a80eec62 100644
--- a/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_volume_attach_detach.py
+++ b/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_volume_attach_detach.py
@@ -24,11 +24,14 @@ from cinder.volume.drivers.dell_emc.powerstore import utils
 
 
 class TestVolumeAttachDetach(powerstore.TestPowerStoreDriver):
+    @mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
+                "PowerStoreClient.get_chap_config")
     @mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
                 "PowerStoreClient.get_appliance_id_by_name")
-    def setUp(self, mock_appliance):
+    def setUp(self, mock_appliance, mock_chap):
         super(TestVolumeAttachDetach, self).setUp()
         mock_appliance.return_value = "A1"
+        mock_chap.return_value = {"mode": "Single"}
         self.iscsi_driver.check_for_setup_error()
         self.fc_driver.check_for_setup_error()
         self.volume = fake_volume.fake_volume_obj(
@@ -50,7 +53,7 @@ class TestVolumeAttachDetach(powerstore.TestPowerStoreDriver):
                 attached_host=self.volume.host
             )
         ]
-        self.fake_iscsi_targets_response = [
+        fake_iscsi_targets_response = [
             {
                 "address": "1.2.3.4",
                 "ip_port": {
@@ -66,7 +69,7 @@ class TestVolumeAttachDetach(powerstore.TestPowerStoreDriver):
                 },
             },
         ]
-        self.fake_fc_wwns_response = [
+        fake_fc_wwns_response = [
             {
                 "wwn": "58:cc:f0:98:49:21:07:02"
             },
@@ -79,18 +82,52 @@ class TestVolumeAttachDetach(powerstore.TestPowerStoreDriver):
             "wwpns": ["58:cc:f0:98:49:21:07:02", "58:cc:f0:98:49:23:07:02"],
             "initiator": "fake_initiator",
         }
+        self.iscsi_targets_mock = self.mock_object(
+            self.iscsi_driver.adapter.client,
+            "get_ip_pool_address",
+            return_value=fake_iscsi_targets_response
+        )
+        self.fc_wwns_mock = self.mock_object(
+            self.fc_driver.adapter.client,
+            "get_fc_port",
+            return_value=fake_fc_wwns_response
+        )
 
-    @mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
-                "PowerStoreClient.get_fc_port")
-    def test_get_fc_targets(self, mock_get_ip_pool):
-        mock_get_ip_pool.return_value = self.fake_fc_wwns_response
+    def test_initialize_connection_chap_enabled(self):
+        self.iscsi_driver.adapter.use_chap_auth = True
+        with mock.patch.object(self.iscsi_driver.adapter,
+                               "_create_host_and_attach",
+                               return_value=(
+                                   utils.get_chap_credentials(),
+                                   1
+                               )):
+            connection_properties = self.iscsi_driver.initialize_connection(
+                self.volume,
+                self.fake_connector
+            )
+            self.assertIn("auth_username", connection_properties["data"])
+            self.assertIn("auth_password", connection_properties["data"])
+
+    def test_initialize_connection_chap_disabled(self):
+        self.iscsi_driver.adapter.use_chap_auth = False
+        with mock.patch.object(self.iscsi_driver.adapter,
+                               "_create_host_and_attach",
+                               return_value=(
+                                   utils.get_chap_credentials(),
+                                   1
+                               )):
+            connection_properties = self.iscsi_driver.initialize_connection(
+                self.volume,
+                self.fake_connector
+            )
+            self.assertNotIn("auth_username", connection_properties["data"])
+            self.assertNotIn("auth_password", connection_properties["data"])
+
+    def test_get_fc_targets(self):
         wwns = self.fc_driver.adapter._get_fc_targets("A1")
         self.assertEqual(2, len(wwns))
 
-    @mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
-                "PowerStoreClient.get_fc_port")
-    def test_get_fc_targets_filtered(self, mock_get_ip_pool):
-        mock_get_ip_pool.return_value = self.fake_fc_wwns_response
+    def test_get_fc_targets_filtered(self):
         self.fc_driver.adapter.allowed_ports = ["58:cc:f0:98:49:23:07:02"]
         wwns = self.fc_driver.adapter._get_fc_targets("A1")
         self.assertEqual(1, len(wwns))
@@ -98,10 +135,7 @@ class TestVolumeAttachDetach(powerstore.TestPowerStoreDriver):
             utils.fc_wwn_to_string("58:cc:f0:98:49:21:07:02") in wwns
         )
 
-    @mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
-                "PowerStoreClient.get_fc_port")
-    def test_get_fc_targets_filtered_no_matched_ports(self, mock_get_ip_pool):
-        mock_get_ip_pool.return_value = self.fake_fc_wwns_response
+    def test_get_fc_targets_filtered_no_matched_ports(self):
         self.fc_driver.adapter.allowed_ports = ["fc_wwn_1", "fc_wwn_2"]
         error = self.assertRaises(exception.VolumeBackendAPIException,
                                   self.fc_driver.adapter._get_fc_targets,
@@ -109,18 +143,12 @@ class TestVolumeAttachDetach(powerstore.TestPowerStoreDriver):
         self.assertIn("There are no accessible Fibre Channel targets on the "
                       "system.", error.msg)
 
-    @mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
-                "PowerStoreClient.get_ip_pool_address")
-    def test_get_iscsi_targets(self, mock_get_ip_pool):
-        mock_get_ip_pool.return_value = self.fake_iscsi_targets_response
+    def test_get_iscsi_targets(self):
         iqns, portals = self.iscsi_driver.adapter._get_iscsi_targets("A1")
         self.assertTrue(len(iqns) == len(portals))
         self.assertEqual(2, len(portals))
 
-    @mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
-                "PowerStoreClient.get_ip_pool_address")
-    def test_get_iscsi_targets_filtered(self, mock_get_ip_pool):
-        mock_get_ip_pool.return_value = self.fake_iscsi_targets_response
+    def test_get_iscsi_targets_filtered(self):
         self.iscsi_driver.adapter.allowed_ports = ["1.2.3.4"]
         iqns, portals = self.iscsi_driver.adapter._get_iscsi_targets("A1")
         self.assertTrue(len(iqns) == len(portals))
@@ -129,11 +157,7 @@ class TestVolumeAttachDetach(powerstore.TestPowerStoreDriver):
             "iqn.2020-07.com.dell:dellemc-powerstore-test-iqn-2" in iqns
         )
 
-    @mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
-                "PowerStoreClient.get_ip_pool_address")
-    def test_get_iscsi_targets_filtered_no_matched_ports(self,
-                                                         mock_get_ip_pool):
-        mock_get_ip_pool.return_value = self.fake_iscsi_targets_response
+    def test_get_iscsi_targets_filtered_no_matched_ports(self):
         self.iscsi_driver.adapter.allowed_ports = ["1.1.1.1", "2.2.2.2"]
         error = self.assertRaises(exception.VolumeBackendAPIException,
                                   self.iscsi_driver.adapter._get_iscsi_targets,
diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_volume_create_delete_extend.py b/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_volume_create_delete_extend.py
index 5143f2d4e2c..16ac1a3fc07 100644
--- a/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_volume_create_delete_extend.py
+++ b/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_volume_create_delete_extend.py
@@ -22,9 +22,11 @@ from cinder.volume.drivers.dell_emc.powerstore import client
 
 
 class TestVolumeCreateDeleteExtend(powerstore.TestPowerStoreDriver):
+    @mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
+                "PowerStoreClient.get_chap_config")
     @mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
                 "PowerStoreClient.get_appliance_id_by_name")
-    def setUp(self, mock_appliance):
+    def setUp(self, mock_appliance, mock_chap):
         super(TestVolumeCreateDeleteExtend, self).setUp()
         mock_appliance.return_value = "A1"
         self.driver.check_for_setup_error()
diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_volume_create_from_source.py b/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_volume_create_from_source.py
index ec5983a6a23..1c36133b096 100644
--- a/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_volume_create_from_source.py
+++ b/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_volume_create_from_source.py
@@ -22,9 +22,11 @@ from cinder.tests.unit.volume.drivers.dell_emc import powerstore
 
 
 class TestVolumeCreateFromSource(powerstore.TestPowerStoreDriver):
+    @mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
+                "PowerStoreClient.get_chap_config")
     @mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
                 "PowerStoreClient.get_appliance_id_by_name")
-    def setUp(self, mock_appliance):
+    def setUp(self, mock_appliance, mock_chap):
         super(TestVolumeCreateFromSource, self).setUp()
         mock_appliance.return_value = "A1"
         self.driver.check_for_setup_error()
diff --git a/cinder/volume/drivers/dell_emc/powerstore/adapter.py b/cinder/volume/drivers/dell_emc/powerstore/adapter.py
index 3bab1147d14..0099ff54e29 100644
--- a/cinder/volume/drivers/dell_emc/powerstore/adapter.py
+++ b/cinder/volume/drivers/dell_emc/powerstore/adapter.py
@@ -16,6 +16,7 @@
 """Adapter for Dell EMC PowerStore Cinder driver."""
 
 from oslo_log import log as logging
+from oslo_utils import strutils
 
 from cinder import coordination
 from cinder import exception
@@ -30,6 +31,7 @@ from cinder.volume import volume_utils
 LOG = logging.getLogger(__name__)
 PROTOCOL_FC = "FC"
 PROTOCOL_ISCSI = "iSCSI"
+CHAP_MODE_SINGLE = "Single"
 
 
 class CommonAdapter(object):
@@ -41,6 +43,7 @@ class CommonAdapter(object):
         self.configuration = configuration
         self.storage_protocol = None
         self.allowed_ports = None
+        self.use_chap_auth = None
 
     @staticmethod
     def initiators(connector):
@@ -83,13 +86,20 @@ class CommonAdapter(object):
             self.appliances_to_ids_map[appliance_name] = (
                 self.client.get_appliance_id_by_name(appliance_name)
             )
+        self.use_chap_auth = False
+        if self.storage_protocol == PROTOCOL_ISCSI:
+            chap_config = self.client.get_chap_config()
+            if chap_config.get("mode") == CHAP_MODE_SINGLE:
+                self.use_chap_auth = True
         LOG.debug("Successfully initialized PowerStore %(protocol)s adapter. "
                   "PowerStore appliances: %(appliances)s. "
-                  "Allowed ports: %(allowed_ports)s.",
+                  "Allowed ports: %(allowed_ports)s. "
+                  "Use CHAP authentication: %(use_chap_auth)s.",
                   {
                       "protocol": self.storage_protocol,
                       "appliances": self.appliances,
                       "allowed_ports": self.allowed_ports,
+                      "use_chap_auth": self.use_chap_auth,
                   })
 
     def create_volume(self, volume):
@@ -314,7 +324,9 @@ class CommonAdapter(object):
                   {
                       "volume_name": volume.name,
                       "volume_id": volume.id,
-                      "connection_properties": connection_properties,
+                      "connection_properties": strutils.mask_password(
+                          connection_properties
+                      ),
                   })
         return connection_properties
 
@@ -429,13 +441,17 @@ class CommonAdapter(object):
         """Create PowerStore host if it does not exist.
 
         :param connector: connection properties
-        :return: PowerStore host object
+        :return: PowerStore host object, iSCSI CHAP credentials
         """
 
         initiators = self.initiators(connector)
         host = self._filter_hosts_by_initiators(initiators)
+        if self.use_chap_auth:
+            chap_credentials = utils.get_chap_credentials()
+        else:
+            chap_credentials = {}
         if host:
-            self._modify_host_initiators(host, initiators)
+            self._modify_host_initiators(host, chap_credentials, initiators)
         else:
             host_name = utils.powerstore_host_name(
                 connector,
@@ -451,6 +467,7 @@ class CommonAdapter(object):
                 {
                     "port_name": initiator,
                     "port_type": self.storage_protocol,
+                    **chap_credentials,
                 } for initiator in initiators
             ]
             host = self.client.create_host(host_name, ports)
@@ -463,12 +480,13 @@ class CommonAdapter(object):
                           "initiators": initiators,
                           "host_provider_id": host["id"],
                       })
-        return host
+        return host, chap_credentials
 
-    def _modify_host_initiators(self, host, initiators):
+    def _modify_host_initiators(self, host, chap_credentials, initiators):
         """Update PowerStore host initiators if needed.
 
         :param host: PowerStore host object
+        :param chap_credentials: iSCSI CHAP credentials
         :param initiators: list of initiators
         :return: None
         """
@@ -476,17 +494,22 @@ class CommonAdapter(object):
         initiators_added = [
             initiator["port_name"] for initiator in host["host_initiators"]
         ]
+        initiators_to_add = []
+        initiators_to_modify = []
         initiators_to_remove = [
             initiator for initiator in initiators_added
             if initiator not in initiators
         ]
-        initiators_to_add = [
-            {
+        for initiator in initiators:
+            initiator_add_modify = {
                 "port_name": initiator,
-                "port_type": self.storage_protocol,
-            } for initiator in initiators
-            if initiator not in initiators_added
-        ]
+                **chap_credentials,
+            }
+            if initiator not in initiators_added:
+                initiator_add_modify["port_type"] = self.storage_protocol
+                initiators_to_add.append(initiator_add_modify)
+            elif self.use_chap_auth:
+                initiators_to_modify.append(initiator_add_modify)
         if initiators_to_remove:
             LOG.debug("Remove initiators from PowerStore host %(host_name)s. "
                       "Initiators: %(initiators_to_remove)s. "
@@ -514,7 +537,9 @@ class CommonAdapter(object):
                       "%(host_provider_id)s.",
                       {
                           "host_name": host["name"],
-                          "initiators_to_add": initiators_to_add,
+                          "initiators_to_add": strutils.mask_password(
+                              initiators_to_add
+                          ),
                           "host_provider_id": host["id"],
                       })
             self.client.modify_host_initiators(
@@ -526,7 +551,34 @@ class CommonAdapter(object):
                       "PowerStore host id: %(host_provider_id)s.",
                       {
                           "host_name": host["name"],
-                          "initiators_to_add": initiators_to_add,
+                          "initiators_to_add": strutils.mask_password(
+                              initiators_to_add
+                          ),
+                          "host_provider_id": host["id"],
+                      })
+        if initiators_to_modify:
+            LOG.debug("Modify initiators of PowerStore host %(host_name)s. "
+                      "Initiators: %(initiators_to_modify)s. "
+                      "PowerStore host id: %(host_provider_id)s.",
+                      {
+                          "host_name": host["name"],
+                          "initiators_to_modify": strutils.mask_password(
+                              initiators_to_modify
+                          ),
+                          "host_provider_id": host["id"],
+                      })
+            self.client.modify_host_initiators(
+                host["id"],
+                modify_initiators=initiators_to_modify
+            )
+            LOG.debug("Successfully modified initiators of PowerStore host "
+                      "%(host_name)s. Initiators: %(initiators_to_modify)s. "
+                      "PowerStore host id: %(host_provider_id)s.",
+                      {
+                          "host_name": host["name"],
+                          "initiators_to_modify": strutils.mask_password(
+                              initiators_to_modify
+                          ),
                           "host_provider_id": host["id"],
                       })
 
@@ -572,11 +624,11 @@ class CommonAdapter(object):
 
         :param connector: connection properties
         :param volume: OpenStack volume object
-        :return: attached volume logical number
+        :return: iSCSI CHAP credentials, volume logical number
         """
 
-        host = self._create_host_if_not_exist(connector)
-        return self._attach_volume_to_host(host, volume)
+        host, chap_credentials = self._create_host_if_not_exist(connector)
+        return chap_credentials, self._attach_volume_to_host(host, volume)
 
     def _connect_volume(self, volume, connector):
         """Attach PowerStore volume and return it's connection properties.
@@ -588,12 +640,21 @@ class CommonAdapter(object):
 
         appliance_name = volume_utils.extract_host(volume.host, "pool")
         appliance_id = self.appliances_to_ids_map[appliance_name]
-        volume_lun = self._create_host_and_attach(
+        chap_credentials, volume_lun = self._create_host_and_attach(
             connector,
             volume
         )
-        return self._get_connection_properties(appliance_id,
-                                               volume_lun)
+        connection_properties = self._get_connection_properties(appliance_id,
+                                                                volume_lun)
+        if self.use_chap_auth:
+            connection_properties["data"]["auth_method"] = "CHAP"
+            connection_properties["data"]["auth_username"] = (
+                chap_credentials.get("chap_single_username")
+            )
+            connection_properties["data"]["auth_password"] = (
+                chap_credentials.get("chap_single_password")
+            )
+        return connection_properties
 
     def _detach_volume_from_hosts(self, volume, hosts_to_detach=None):
         """Detach volume from PowerStore hosts.
@@ -731,7 +792,7 @@ class FibreChannelAdapter(CommonAdapter):
         return {
             "driver_volume_type": self.driver_volume_type,
             "data": {
-                "target_discovered": True,
+                "target_discovered": False,
                 "target_lun": volume_lun,
                 "target_wwn": target_wwns,
             }
@@ -782,7 +843,10 @@ class iSCSIAdapter(CommonAdapter):
         return {
             "driver_volume_type": self.driver_volume_type,
             "data": {
-                "target_discovered": True,
+                "target_discovered": False,
+                "target_portal": portals[0],
+                "target_iqn": iqns[0],
+                "target_lun": volume_lun,
                 "target_portals": portals,
                 "target_iqns": iqns,
                 "target_luns": [volume_lun] * len(portals),
diff --git a/cinder/volume/drivers/dell_emc/powerstore/client.py b/cinder/volume/drivers/dell_emc/powerstore/client.py
index 3927a00c39d..4299b8f65cd 100644
--- a/cinder/volume/drivers/dell_emc/powerstore/client.py
+++ b/cinder/volume/drivers/dell_emc/powerstore/client.py
@@ -19,6 +19,7 @@ import functools
 import json
 
 from oslo_log import log as logging
+from oslo_utils import strutils
 import requests
 
 from cinder import exception
@@ -83,7 +84,12 @@ class PowerStoreClient(object):
                       "verify_cert": self._verify_cert,
                   })
 
-    def _send_request(self, method, url, payload=None, params=None):
+    def _send_request(self,
+                      method,
+                      url,
+                      payload=None,
+                      params=None,
+                      log_response_data=True):
         if not payload:
             payload = {}
         if not params:
@@ -106,11 +112,12 @@ class PowerStoreClient(object):
                 "REST Request: %s %s with body %s",
                 r.request.method,
                 r.request.url,
-                r.request.body)
-        LOG.log(log_level,
-                "REST Response: %s with data %s",
-                r.status_code,
-                r.text)
+                strutils.mask_password(r.request.body))
+        if log_response_data or log_level == logging.ERROR:
+            msg = "REST Response: %s with data %s" % (r.status_code, r.text)
+        else:
+            msg = "REST Response: %s" % r.status_code
+        LOG.log(log_level, msg)
 
         try:
             response = r.json()
@@ -123,6 +130,19 @@ class PowerStoreClient(object):
     _send_patch_request = functools.partialmethod(_send_request, "PATCH")
     _send_delete_request = functools.partialmethod(_send_request, "DELETE")
 
+    def get_chap_config(self):
+        r, response = self._send_get_request(
+            "/chap_config/0",
+            params={
+                "select": "mode"
+            }
+        )
+        if r.status_code not in self.ok_codes:
+            msg = _("Failed to query PowerStore CHAP configuration.")
+            LOG.error(msg)
+            raise exception.VolumeBackendAPIException(data=msg)
+        return response
+
     def get_appliance_id_by_name(self, appliance_name):
         r, response = self._send_get_request(
             "/appliance",
@@ -148,7 +168,8 @@ class PowerStoreClient(object):
             payload={
                 "entity": "space_metrics_by_appliance",
                 "entity_id": appliance_id,
-            }
+            },
+            log_response_data=False
         )
         if r.status_code not in self.ok_codes:
             msg = (_("Failed to query metrics for "
diff --git a/cinder/volume/drivers/dell_emc/powerstore/driver.py b/cinder/volume/drivers/dell_emc/powerstore/driver.py
index 67e8b5f8c75..87d0eaf60ce 100644
--- a/cinder/volume/drivers/dell_emc/powerstore/driver.py
+++ b/cinder/volume/drivers/dell_emc/powerstore/driver.py
@@ -37,9 +37,10 @@ class PowerStoreDriver(driver.VolumeDriver):
 
       Version history:
         1.0.0 - Initial version
+        1.0.1 - Add CHAP support
     """
 
-    VERSION = "1.0.0"
+    VERSION = "1.0.1"
     VENDOR = "Dell EMC"
 
     # ThirdPartySystems wiki page
diff --git a/cinder/volume/drivers/dell_emc/powerstore/utils.py b/cinder/volume/drivers/dell_emc/powerstore/utils.py
index 28e6566bff1..d2bc1d333a5 100644
--- a/cinder/volume/drivers/dell_emc/powerstore/utils.py
+++ b/cinder/volume/drivers/dell_emc/powerstore/utils.py
@@ -23,9 +23,12 @@ from oslo_utils import units
 from cinder import exception
 from cinder.i18n import _
 from cinder.objects import fields
+from cinder.volume import volume_utils
 
 
 LOG = logging.getLogger(__name__)
+CHAP_DEFAULT_USERNAME = "PowerStore_iSCSI_CHAP_Username"
+CHAP_DEFAULT_SECRET_LENGTH = 60
 
 
 def bytes_to_gib(size_in_bytes):
@@ -134,3 +137,17 @@ def is_multiattached_to_host(volume_attachment, host_name):
             attachment.attached_host == host_name)
     ]
     return len(attachments) > 1
+
+
+def get_chap_credentials():
+    """Generate CHAP credentials.
+
+    :return: CHAP username and secret
+    """
+
+    return {
+        "chap_single_username": CHAP_DEFAULT_USERNAME,
+        "chap_single_password": volume_utils.generate_password(
+            CHAP_DEFAULT_SECRET_LENGTH
+        )
+    }
diff --git a/doc/source/configuration/block-storage/drivers/dell-emc-powerstore-driver.rst b/doc/source/configuration/block-storage/drivers/dell-emc-powerstore-driver.rst
index f7a92eafac5..d6a2cb41b27 100644
--- a/doc/source/configuration/block-storage/drivers/dell-emc-powerstore-driver.rst
+++ b/doc/source/configuration/block-storage/drivers/dell-emc-powerstore-driver.rst
@@ -77,3 +77,19 @@ Thin provisioning and compression
 
 The driver creates thin provisioned compressed volumes by default.
 Thick provisioning is not supported.
+
+CHAP authentication support
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The driver supports one-way (Single mode) CHAP authentication.
+To use CHAP authentication CHAP Single mode has to be enabled on the storage
+side.
+
+.. note:: When enabling CHAP, any previously added hosts will need to be updated
+          with CHAP configuration since there will be I/O disruption for those hosts.
+          It is recommended that before adding hosts to the cluster,
+          decide what type of CHAP configuration is required, if any.
+
+CHAP configuration is retrieved from the storage during driver initialization,
+no additional configuration is needed.
+Secrets are generated automatically.
diff --git a/releasenotes/notes/bug-1900979-powerstore-chap-support.yaml b/releasenotes/notes/bug-1900979-powerstore-chap-support.yaml
new file mode 100644
index 00000000000..e160b19a023
--- /dev/null
+++ b/releasenotes/notes/bug-1900979-powerstore-chap-support.yaml
@@ -0,0 +1,5 @@
+---
+fixes:
+  - |
+    `Bug #1900979 <https://bugs.launchpad.net/cinder/+bug/1900979>`_:
+    Fix bug with using PowerStore with enabled CHAP as a storage backend.