Update the StorPool Cinder patches

The context for some of them changed in the Cinder master branch, so
`git am` was no longer able to apply them directly. Also, import some
updates to the patches - documentation, release notes, etc - so that
they match exactly what is submitted to the OpenDev Gerrit instance for
merging upstream.

Change-Id: Iacd95c275340faa435a2bf3a83a61e28ab8f8bf3
diff --git a/patches/openstack/cinder/sep-clone-across-pools.patch b/patches/openstack/cinder/sep-clone-across-pools.patch
index 755f52f..33064ec 100644
--- a/patches/openstack/cinder/sep-clone-across-pools.patch
+++ b/patches/openstack/cinder/sep-clone-across-pools.patch
@@ -1,4 +1,4 @@
-From af80e707f4a921d9c1e83fd9487b941406d20ecd Mon Sep 17 00:00:00 2001
+From c75191ca7990e28528b008066f4c2b89f6dab72f Mon Sep 17 00:00:00 2001
 From: Peter Penchev <openstack-dev@storpool.com>
 Date: Wed, 22 Jun 2022 10:04:31 +0300
 Subject: [PATCH 01/10] Add the clone_across_pools driver capability
@@ -96,7 +96,7 @@
              expect = {'cluster_name': self.volume.cluster_name}
          found_entry = cache.get_entry(self.context,
 diff --git a/cinder/tests/unit/volume/flows/test_create_volume_flow.py b/cinder/tests/unit/volume/flows/test_create_volume_flow.py
-index 5b4ddb35f..83880a9f9 100644
+index 1bb3f2b98..a804d7fe6 100644
 --- a/cinder/tests/unit/volume/flows/test_create_volume_flow.py
 +++ b/cinder/tests/unit/volume/flows/test_create_volume_flow.py
 @@ -1060,6 +1060,7 @@ class CreateVolumeFlowManagerTestCase(test.TestCase):
@@ -146,7 +146,7 @@
 +        fake_driver.capabilities = {}
          fake_volume_manager = mock.MagicMock()
          backup_host = 'host@backend#pool'
-         fake_manager = create_volume_manager.CreateVolumeFromSpecTask(
+         test_manager = create_volume_manager.CreateVolumeFromSpecTask(
 @@ -1291,6 +1297,7 @@ class CreateVolumeFlowManagerTestCase(test.TestCase):
      def test_create_drive_error(self, mock_message_create):
          fake_db = mock.MagicMock()
@@ -250,7 +250,7 @@
              mock.MagicMock(), fake_db, fake_driver)
          fake_image_service = fake_image.FakeImageService()
 diff --git a/cinder/volume/flows/manager/create_volume.py b/cinder/volume/flows/manager/create_volume.py
-index 905258bf4..963cc2d78 100644
+index ac09ed898..7347ac1f8 100644
 --- a/cinder/volume/flows/manager/create_volume.py
 +++ b/cinder/volume/flows/manager/create_volume.py
 @@ -741,8 +741,12 @@ class CreateVolumeFromSpecTask(flow_utils.CinderTask):
@@ -283,10 +283,10 @@
              LOG.info('Image-volume cache enabled for host %(host)s.',
                       {'host': self.host})
 diff --git a/doc/source/reference/support-matrix.ini b/doc/source/reference/support-matrix.ini
-index 7b4b3c38d..86a5660b8 100644
+index 514e2c016..163a7a37c 100644
 --- a/doc/source/reference/support-matrix.ini
 +++ b/doc/source/reference/support-matrix.ini
-@@ -1022,3 +1022,81 @@ driver.win_iscsi=missing
+@@ -1035,3 +1035,81 @@ driver.win_iscsi=missing
  driver.win_smb=missing
  driver.yadro=complete
  driver.zadara=missing
@@ -369,5 +369,5 @@
 +driver.yadro=missing
 +driver.zadara=missing
 -- 
-2.39.2
+2.40.1
 
diff --git a/patches/openstack/cinder/sep-sp-clone-across-pools.patch b/patches/openstack/cinder/sep-sp-clone-across-pools.patch
index 05669d0..0cd1b26 100644
--- a/patches/openstack/cinder/sep-sp-clone-across-pools.patch
+++ b/patches/openstack/cinder/sep-sp-clone-across-pools.patch
@@ -1,13 +1,15 @@
-From 056fd8671716cf50b6916b343a6e45e5c64806b2 Mon Sep 17 00:00:00 2001
+From a0ff072ee506ff7eedda4b727cd613b6032fcfd9 Mon Sep 17 00:00:00 2001
 From: Peter Penchev <openstack-dev@storpool.com>
 Date: Wed, 22 Jun 2022 10:48:25 +0300
 Subject: [PATCH 02/10] StorPool: declare the clone_across_pools capability
 
 Change-Id: I5338c6c4f53a448e495f695cd64b36b722cd947d
 ---
- cinder/volume/drivers/storpool.py       | 1 +
- doc/source/reference/support-matrix.ini | 2 +-
- 2 files changed, 2 insertions(+), 1 deletion(-)
+ cinder/volume/drivers/storpool.py                           | 1 +
+ doc/source/reference/support-matrix.ini                     | 2 +-
+ .../notes/storpool-clone-across-pools-b3f7923dee35503a.yaml | 6 ++++++
+ 3 files changed, 8 insertions(+), 1 deletion(-)
+ create mode 100644 releasenotes/notes/storpool-clone-across-pools-b3f7923dee35503a.yaml
 
 diff --git a/cinder/volume/drivers/storpool.py b/cinder/volume/drivers/storpool.py
 index 47685cb3f..328f76c00 100644
@@ -22,10 +24,10 @@
  
              'pools': pools
 diff --git a/doc/source/reference/support-matrix.ini b/doc/source/reference/support-matrix.ini
-index 86a5660b8..d10137154 100644
+index 163a7a37c..7ee51b722 100644
 --- a/doc/source/reference/support-matrix.ini
 +++ b/doc/source/reference/support-matrix.ini
-@@ -1089,7 +1089,7 @@ driver.rbd=missing
+@@ -1102,7 +1102,7 @@ driver.rbd=missing
  driver.rbd_iscsi=missing
  driver.sandstone=missing
  driver.seagate=missing
@@ -34,6 +36,18 @@
  driver.synology=missing
  driver.toyou_netstor=missing
  driver.vrtsaccess=missing
+diff --git a/releasenotes/notes/storpool-clone-across-pools-b3f7923dee35503a.yaml b/releasenotes/notes/storpool-clone-across-pools-b3f7923dee35503a.yaml
+new file mode 100644
+index 000000000..511ac699b
+--- /dev/null
++++ b/releasenotes/notes/storpool-clone-across-pools-b3f7923dee35503a.yaml
+@@ -0,0 +1,6 @@
++---
++features:
++  - |
++    The StorPool driver now declares the "clone across pools" capability,
++    which allows it to create a volume into an arbitrary StorPool-backed
++    volume type from a StorPool-backed Glance image.
 -- 
-2.39.2
+2.40.1
 
diff --git a/patches/openstack/cinder/sep-sp-clone-volume.patch b/patches/openstack/cinder/sep-sp-clone-volume.patch
index b0132ee..4e18294 100644
--- a/patches/openstack/cinder/sep-sp-clone-volume.patch
+++ b/patches/openstack/cinder/sep-sp-clone-volume.patch
@@ -1,4 +1,4 @@
-From 81f472830ba09a42d887c22fc5f0a938ff9498ca Mon Sep 17 00:00:00 2001
+From dacb33483344276f909e1be008d0eb45758f1923 Mon Sep 17 00:00:00 2001
 From: Peter Penchev <openstack-dev@storpool.com>
 Date: Wed, 20 Apr 2022 15:47:39 +0300
 Subject: [PATCH 09/10] StorPool: create_cloned_volume() improvements
@@ -282,7 +282,7 @@
  
          self.driver.create_volume({'id': 'cfgtempl2', 'name': 'v1', 'size': 1,
 diff --git a/cinder/volume/drivers/storpool.py b/cinder/volume/drivers/storpool.py
-index dcad96bb8..58b64bced 100644
+index dcad96bb8..b15a201c3 100644
 --- a/cinder/volume/drivers/storpool.py
 +++ b/cinder/volume/drivers/storpool.py
 @@ -19,11 +19,13 @@ import platform
@@ -313,9 +313,9 @@
 +        src_template = self._template_from_volume(src_volume)
 +
 +        template = self._template_from_volume(volume)
-+        LOG.debug('clone volume id %(vol_id)s template %(template)s', {
-+            'vol_id': repr(volume['id']),
-+            'template': repr(template),
++        LOG.debug('clone volume id %(vol_id)r template %(template)r', {
++            'vol_id': volume['id'],
++            'template': template,
 +        })
 +        if template == src_template:
 +            LOG.info('Using baseOn to clone a volume into the same template')
@@ -411,16 +411,16 @@
  # Datera
 diff --git a/releasenotes/notes/storpool-clone-better-dca90f40c9273de9.yaml b/releasenotes/notes/storpool-clone-better-dca90f40c9273de9.yaml
 new file mode 100644
-index 000000000..180427d9e
+index 000000000..9d512c831
 --- /dev/null
 +++ b/releasenotes/notes/storpool-clone-better-dca90f40c9273de9.yaml
 @@ -0,0 +1,6 @@
 +---
 +features:
 +  - |
-+    StorPool driver: improved the way volumes are clonsed into different
++    StorPool driver: improved the way volumes are cloned into different
 +    StorPool templates (exposed as Cinder storage pools) if requested,
 +    eliminating some data duplication in the underlying StorPool cluster.
 -- 
-2.39.2
+2.40.1
 
diff --git a/patches/openstack/cinder/sep-sp-cosmetic-comments.patch b/patches/openstack/cinder/sep-sp-cosmetic-comments.patch
index 27d4f1b..d2ea5d3 100644
--- a/patches/openstack/cinder/sep-sp-cosmetic-comments.patch
+++ b/patches/openstack/cinder/sep-sp-cosmetic-comments.patch
@@ -1,4 +1,4 @@
-From 13e958bd85997cd84c37b76c0282bbfde5b9224a Mon Sep 17 00:00:00 2001
+From 1bf20850bdbd86997a59bc639ae51be71dd99da8 Mon Sep 17 00:00:00 2001
 From: Peter Penchev <openstack-dev@storpool.com>
 Date: Mon, 13 Feb 2023 11:15:27 +0200
 Subject: [PATCH 03/10] StorPool: cosmetic: comment headings instead of empty
@@ -13,9 +13,11 @@
  cinder/volume/drivers/storpool.py | 5 +++--
  1 file changed, 3 insertions(+), 2 deletions(-)
 
+diff --git a/cinder/volume/drivers/storpool.py b/cinder/volume/drivers/storpool.py
+index 328f76c00..8c2989fa8 100644
 --- a/cinder/volume/drivers/storpool.py
 +++ b/cinder/volume/drivers/storpool.py
-@@ -302,15 +302,16 @@
+@@ -302,15 +302,16 @@ class StorPoolDriver(driver.VolumeDriver):
                         ) for t in templates]
  
          self._stats = {
@@ -34,3 +36,6 @@
              'pools': pools
          }
  
+-- 
+2.40.1
+
diff --git a/patches/openstack/cinder/sep-sp-fix-test-rename.patch b/patches/openstack/cinder/sep-sp-fix-test-rename.patch
index e9d5060..40a0f5f 100644
--- a/patches/openstack/cinder/sep-sp-fix-test-rename.patch
+++ b/patches/openstack/cinder/sep-sp-fix-test-rename.patch
@@ -1,4 +1,4 @@
-From e655d91587dc5674d274a7ffa3482888207eb1ec Mon Sep 17 00:00:00 2001
+From 769fa5b7ec1e333544f5b8e454225757db73fb87 Mon Sep 17 00:00:00 2001
 From: Peter Penchev <openstack-dev@storpool.com>
 Date: Tue, 17 Jan 2023 01:20:59 +0200
 Subject: [PATCH 07/10] StorPool: fix the "rename volume" unit test emulation
@@ -37,5 +37,5 @@
      @mock_volume_types
      def test_create_delete_volume(self):
 -- 
-2.39.2
+2.40.1
 
diff --git a/patches/openstack/cinder/sep-sp-iscsi.patch b/patches/openstack/cinder/sep-sp-iscsi.patch
index 840007d..5571ce5 100644
--- a/patches/openstack/cinder/sep-sp-iscsi.patch
+++ b/patches/openstack/cinder/sep-sp-iscsi.patch
@@ -1,4 +1,4 @@
-From 9d022ada82cb1aa161e360890c4d86fce958aea4 Mon Sep 17 00:00:00 2001
+From 6e24ec90deb5e5977a4654c0e9f7f02e99ddb131 Mon Sep 17 00:00:00 2001
 From: Peter Penchev <openstack-dev@storpool.com>
 Date: Mon, 12 Mar 2018 12:00:10 +0200
 Subject: [PATCH 10/10] Add iSCSI export support to the StorPool driver
@@ -25,39 +25,367 @@
 
 Change-Id: I9de64306e0e6976268df782053b0651dd1cca96f
 ---
- .../unit/volume/drivers/test_storpool.py      |  64 ++-
- cinder/volume/drivers/storpool.py             | 374 +++++++++++++++++-
- .../drivers/storpool-volume-driver.rst        |  49 ++-
- 3 files changed, 478 insertions(+), 9 deletions(-)
+ .../unit/volume/drivers/test_storpool.py      | 435 +++++++++++++++++-
+ cinder/volume/drivers/storpool.py             | 374 ++++++++++++++-
+ .../drivers/storpool-volume-driver.rst        |  60 ++-
+ .../storpool-iscsi-cefcfe590a07c5c7.yaml      |  10 +
+ 4 files changed, 870 insertions(+), 9 deletions(-)
+ create mode 100644 releasenotes/notes/storpool-iscsi-cefcfe590a07c5c7.yaml
 
 diff --git a/cinder/tests/unit/volume/drivers/test_storpool.py b/cinder/tests/unit/volume/drivers/test_storpool.py
-index 95a1ffffd..7e8a17800 100644
+index 95a1ffffd..842790ab4 100644
 --- a/cinder/tests/unit/volume/drivers/test_storpool.py
 +++ b/cinder/tests/unit/volume/drivers/test_storpool.py
-@@ -32,6 +32,7 @@ fakeStorPool.sptypes = mock.Mock()
+@@ -14,15 +14,25 @@
+ #    under the License.
+ 
+ 
++from __future__ import annotations
++
++import dataclasses
+ import itertools
+ import re
+ import sys
++from typing import Any, NamedTuple, TYPE_CHECKING  # noqa: H301
+ from unittest import mock
+ 
+ import ddt
+ from oslo_utils import units
+ import six
+ 
++if TYPE_CHECKING:
++    if sys.version_info >= (3, 11):
++        from typing import Self
++    else:
++        from typing_extensions import Self
++
+ 
+ fakeStorPool = mock.Mock()
+ fakeStorPool.spopenstack = mock.Mock()
+@@ -32,12 +42,21 @@ fakeStorPool.sptypes = mock.Mock()
  sys.modules['storpool'] = fakeStorPool
  
  
 +from cinder.common import constants
  from cinder import exception
++from cinder.tests.unit import fake_constants as fconst
  from cinder.tests.unit import test
  from cinder.volume import configuration as conf
-@@ -222,7 +223,14 @@ class StorPoolTestCase(test.TestCase):
+ from cinder.volume.drivers import storpool as driver
+ 
+ 
++_ISCSI_IQN_OURS = 'beleriand'
++_ISCSI_IQN_OTHER = 'rohan'
++_ISCSI_IQN_THIRD = 'gondor'
++_ISCSI_PAT_OTHER = 'roh*'
++_ISCSI_PAT_BOTH = '*riand roh*'
++_ISCSI_PORTAL_GROUP = 'openstack_pg'
++
+ volume_types = {
+     1: {},
+     2: {'storpool_template': 'ssd'},
+@@ -71,6 +90,10 @@ def snapshotName(vtype, vid):
+     return 'os--snap--{t}--{id}'.format(t=vtype, id=vid)
+ 
+ 
++def targetName(vid):
++    return 'iqn.2012-11.storpool:{id}'.format(id=vid)
++
++
+ class MockDisk(object):
+     def __init__(self, diskId):
+         self.id = diskId
+@@ -181,6 +204,273 @@ def MockVolumeUpdateDesc(size):
+     return {'size': size}
+ 
+ 
++@dataclasses.dataclass(frozen=True)
++class MockIscsiNetwork:
++    """Mock a StorPool IP CIDR network definition (partially)."""
++
++    address: str
++
++
++@dataclasses.dataclass(frozen=True)
++class MockIscsiPortalGroup:
++    """Mock a StorPool iSCSI portal group definition (partially)."""
++
++    name: str
++    networks: list[MockIscsiNetwork]
++
++
++@dataclasses.dataclass(frozen=True)
++class MockIscsiExport:
++    """Mock a StorPool iSCSI exported volume/target definition."""
++
++    portalGroup: str
++    target: str
++
++
++@dataclasses.dataclass(frozen=True)
++class MockIscsiInitiator:
++    """Mock a StorPool iSCSI initiator definition."""
++
++    name: str
++    exports: list[MockIscsiExport]
++
++
++@dataclasses.dataclass(frozen=True)
++class MockIscsiTarget:
++    """Mock a StorPool iSCSI volume-to-target mapping definition."""
++
++    name: str
++    volume: str
++
++
++class IscsiTestCase(NamedTuple):
++    """A single test case for the iSCSI config and export methods."""
++
++    initiator: str | None
++    volume: str | None
++    exported: bool
++    commands_count: int
++
++
++@dataclasses.dataclass(frozen=True)
++class MockIscsiConfig:
++    """Mock the structure returned by the "get current config" query."""
++
++    portalGroups: dict[str, MockIscsiPortalGroup]
++    initiators: dict[str, MockIscsiInitiator]
++    targets: dict[str, MockIscsiTarget]
++
++    @classmethod
++    def build(cls, tcase: IscsiTestCase) -> Self:
++        """Build a test config structure."""
++        initiators = {
++            '0': MockIscsiInitiator(name=_ISCSI_IQN_OTHER, exports=[]),
++        }
++        if tcase.initiator is not None:
++            initiators['1'] = MockIscsiInitiator(
++                name=tcase.initiator,
++                exports=(
++                    [
++                        MockIscsiExport(
++                            portalGroup=_ISCSI_PORTAL_GROUP,
++                            target=targetName(tcase.volume),
++                        ),
++                    ]
++                    if tcase.exported
++                    else []
++                ),
++            )
++
++        targets = {
++            '0': MockIscsiTarget(
++                name=targetName(fconst.VOLUME2_ID),
++                volume=volumeName(fconst.VOLUME2_ID),
++            ),
++        }
++        if tcase.volume is not None:
++            targets['1'] = MockIscsiTarget(
++                name=targetName(tcase.volume),
++                volume=volumeName(tcase.volume),
++            )
++
++        return cls(
++            portalGroups={
++                '0': MockIscsiPortalGroup(
++                    name=_ISCSI_PORTAL_GROUP + '-not',
++                    networks=[],
++                ),
++                '1': MockIscsiPortalGroup(
++                    name=_ISCSI_PORTAL_GROUP,
++                    networks=[
++                        MockIscsiNetwork(address="192.0.2.0"),
++                        MockIscsiNetwork(address="195.51.100.0"),
++                    ],
++                ),
++            },
++            initiators=initiators,
++            targets=targets,
++        )
++
++
++@dataclasses.dataclass(frozen=True)
++class MockIscsiConfigTop:
++    """Mock the top level of the "get the iSCSI configuration" response."""
++
++    iscsi: MockIscsiConfig
++
++
++class MockIscsiAPI:
++    """Mock only the iSCSI-related calls of the StorPool API bindings."""
++
++    _asrt: test.TestCase
++    _configs: list[MockIscsiConfig]
++
++    def __init__(
++        self,
++        configs: list[MockIscsiConfig],
++        asrt: test.TestCase,
++    ) -> None:
++        """Store the reference to the list of iSCSI config objects."""
++        self._asrt = asrt
++        self._configs = configs
++
++    def iSCSIConfig(self) -> MockIscsiConfigTop:
++        """Return the last version of the iSCSI configuration."""
++        return MockIscsiConfigTop(iscsi=self._configs[-1])
++
++    def _handle_export(
++        self,
++        cfg: MockIscsiConfig, cmd: dict[str, Any],
++    ) -> MockIscsiConfig:
++        """Add an export for an initiator."""
++        self._asrt.assertDictEqual(
++            cmd,
++            {
++                'initiator': _ISCSI_IQN_OURS,
++                'portalGroup': _ISCSI_PORTAL_GROUP,
++                'volumeName': volumeName(fconst.VOLUME_ID),
++            },
++        )
++        self._asrt.assertEqual(cfg.initiators['1'].name, cmd['initiator'])
++        self._asrt.assertListEqual(cfg.initiators['1'].exports, [])
++
++        return dataclasses.replace(
++            cfg,
++            initiators={
++                **cfg.initiators,
++                '1': dataclasses.replace(
++                    cfg.initiators['1'],
++                    exports=[
++                        MockIscsiExport(
++                            portalGroup=cmd['portalGroup'],
++                            target=targetName(fconst.VOLUME_ID),
++                        ),
++                    ],
++                ),
++            },
++        )
++
++    def _handle_create_initiator(
++        self,
++        cfg: MockIscsiConfig,
++        cmd: dict[str, Any],
++    ) -> MockIscsiConfig:
++        """Add a whole new initiator."""
++        self._asrt.assertDictEqual(
++            cmd,
++            {
++                'name': _ISCSI_IQN_OURS,
++                'username': '',
++                'secret': '',
++            },
++        )
++        self._asrt.assertNotIn(
++            cmd['name'],
++            [init.name for init in cfg.initiators.values()],
++        )
++        self._asrt.assertListEqual(sorted(cfg.initiators), ['0'])
++
++        return dataclasses.replace(
++            cfg,
++            initiators={
++                **cfg.initiators,
++                '1': MockIscsiInitiator(name=cmd['name'], exports=[]),
++            },
++        )
++
++    def _handle_create_target(
++        self,
++        cfg: MockIscsiConfig,
++        cmd: dict[str, Any],
++    ) -> MockIscsiConfig:
++        """Add a target for a volume so that it may be exported."""
++        self._asrt.assertDictEqual(
++            cmd,
++            {'volumeName': volumeName(fconst.VOLUME_ID)},
++        )
++        self._asrt.assertListEqual(sorted(cfg.targets), ['0'])
++        return dataclasses.replace(
++            cfg,
++            targets={
++                **cfg.targets,
++                '1': MockIscsiTarget(
++                    name=targetName(fconst.VOLUME_ID),
++                    volume=volumeName(fconst.VOLUME_ID),
++                ),
++            },
++        )
++
++    def _handle_initiator_add_network(
++        self,
++        cfg: MockIscsiConfig,
++        cmd: dict[str, Any],
++    ) -> MockIscsiConfig:
++        """Add a network that an initiator is allowed to log in from."""
++        self._asrt.assertDictEqual(
++            cmd,
++            {
++                'initiator': _ISCSI_IQN_OURS,
++                'net': '0.0.0.0/0',
++            },
++        )
++        return dataclasses.replace(cfg)
++
++    _CMD_HANDLERS = {
++        'createInitiator': _handle_create_initiator,
++        'createTarget': _handle_create_target,
++        'export': _handle_export,
++        'initiatorAddNetwork': _handle_initiator_add_network,
++    }
++
++    def iSCSIConfigChange(
++        self,
++        commands: dict[str, list[dict[str, dict[str, Any]]]],
++    ) -> None:
++        """Apply the requested changes to the iSCSI configuration.
++
++        This method adds a new config object to the configs list,
++        making a shallow copy of the last one and applying the changes
++        specified in the list of commands.
++        """
++        self._asrt.assertListEqual(sorted(commands), ['commands'])
++        self._asrt.assertGreater(len(commands['commands']), 0)
++        for cmd in commands['commands']:
++            keys = sorted(cmd.keys())
++            cmd_name = keys[0]
++            self._asrt.assertListEqual(keys, [cmd_name])
++            handler = self._CMD_HANDLERS[cmd_name]
++            new_cfg = handler(self, self._configs[-1], cmd[cmd_name])
++            self._configs.append(new_cfg)
++
++
++_ISCSI_TEST_CASES = [
++    IscsiTestCase(None, None, False, 4),
++    IscsiTestCase(_ISCSI_IQN_OURS, None, False, 2),
++    IscsiTestCase(_ISCSI_IQN_OURS, fconst.VOLUME_ID, False, 1),
++    IscsiTestCase(_ISCSI_IQN_OURS, fconst.VOLUME_ID, True, 0),
++]
++
++
+ def MockSPConfig(section = 's01'):
+     res = {}
+     m = re.match('^s0*([A-Za-z0-9]+)$', section)
+@@ -222,7 +512,15 @@ class StorPoolTestCase(test.TestCase):
          self.cfg.volume_backend_name = 'storpool_test'
          self.cfg.storpool_template = None
          self.cfg.storpool_replication = 3
 +        self.cfg.iscsi_cinder_volume = False
 +        self.cfg.iscsi_export_to = ''
-+        self.cfg.iscsi_portal_group = 'test-group'
- 
-+        self._setup_test_driver()
++        self.cfg.iscsi_learn_initiator_iqns = True
++        self.cfg.iscsi_portal_group = _ISCSI_PORTAL_GROUP
 +
++        self._setup_test_driver()
+ 
 +    def _setup_test_driver(self):
 +        """Initialize a StorPool driver as per the current configuration."""
          mock_exec = mock.Mock()
          mock_exec.return_value = ('', '')
  
-@@ -231,7 +239,7 @@ class StorPoolTestCase(test.TestCase):
+@@ -231,7 +529,7 @@ class StorPoolTestCase(test.TestCase):
          self.driver.check_for_setup_error()
  
      @ddt.data(
@@ -66,7 +394,7 @@
          ({'no-host': None}, KeyError),
          ({'host': 'sbad'}, driver.StorPoolConfigurationInvalid),
          ({'host': 's01'}, None),
-@@ -247,7 +255,7 @@ class StorPoolTestCase(test.TestCase):
+@@ -247,7 +545,7 @@ class StorPoolTestCase(test.TestCase):
                                conn)
  
      @ddt.data(
@@ -75,27 +403,27 @@
          ({'no-host': None}, KeyError),
          ({'host': 'sbad'}, driver.StorPoolConfigurationInvalid),
      )
-@@ -644,3 +652,55 @@ class StorPoolTestCase(test.TestCase):
+@@ -644,3 +942,136 @@ class StorPoolTestCase(test.TestCase):
                           self.driver.get_pool({
                               'volume_type': volume_type
                           }))
 +
 +    @ddt.data(
 +        # The default values
-+        ('', False, constants.STORPOOL, 'beleriand', False),
++        ('', False, constants.STORPOOL, _ISCSI_IQN_OURS, False),
 +
 +        # Export to all
-+        ('*', True, constants.ISCSI, 'beleriand', True),
-+        ('*', True, constants.ISCSI, 'beleriand', True),
++        ('*', True, constants.ISCSI, _ISCSI_IQN_OURS, True),
++        ('*', True, constants.ISCSI, _ISCSI_IQN_OURS, True),
 +
 +        # Only export to the controller
-+        ('', False, constants.STORPOOL, 'beleriand', False),
++        ('', False, constants.STORPOOL, _ISCSI_IQN_OURS, False),
 +
 +        # Some of the not-fully-supported pattern lists
-+        ('roh*', False, constants.STORPOOL, 'beleriand', False),
-+        ('roh*', False, constants.STORPOOL, 'rohan', True),
-+        ('*riand roh*', False, constants.STORPOOL, 'beleriand', True),
-+        ('*riand roh*', False, constants.STORPOOL, 'rohan', True),
++        (_ISCSI_PAT_OTHER, False, constants.STORPOOL, _ISCSI_IQN_OURS, False),
++        (_ISCSI_PAT_OTHER, False, constants.STORPOOL, _ISCSI_IQN_OTHER, True),
++        (_ISCSI_PAT_BOTH, False, constants.STORPOOL, _ISCSI_IQN_OURS, True),
++        (_ISCSI_PAT_BOTH, False, constants.STORPOOL, _ISCSI_IQN_OTHER, True),
 +    )
 +    @ddt.unpack
 +    def test_wants_iscsi(self, iscsi_export_to, use_iscsi, storage_protocol,
@@ -114,7 +442,7 @@
 +        def check(conn, forced, expected):
 +            """Pass partially or completely valid connector info."""
 +            for initiator in (None, hostname):
-+                for host in (None, 'gondor'):
++                for host in (None, _ISCSI_IQN_THIRD):
 +                    self.assertEqual(
 +                        self.driver._connector_wants_iscsi({
 +                            "host": host,
@@ -131,8 +459,89 @@
 +        # look at the specified expected value.
 +        check({"storpool_wants_iscsi": False}, use_iscsi, expected)
 +        check({}, use_iscsi, expected)
++
++    def _validate_iscsi_config(
++        self,
++        cfg: MockIscsiConfig,
++        res: dict[str, Any],
++        tcase: IscsiTestCase,
++    ) -> None:
++        """Make sure the returned structure makes sense."""
++        initiator = res['initiator']
++        cfg_initiator = cfg.initiators.get('1')
++
++        self.assertIs(res['cfg'].iscsi, cfg)
++        self.assertEqual(res['pg'].name, _ISCSI_PORTAL_GROUP)
++
++        if tcase.initiator is None:
++            self.assertIsNone(initiator)
++        else:
++            self.assertIsNotNone(initiator)
++        self.assertEqual(initiator, cfg_initiator)
++
++        if tcase.volume is None:
++            self.assertIsNone(res['target'])
++        else:
++            self.assertIsNotNone(res['target'])
++        self.assertEqual(res['target'], cfg.targets.get('1'))
++
++        if tcase.initiator is None:
++            self.assertIsNone(cfg_initiator)
++            self.assertIsNone(res['export'])
++        else:
++            self.assertIsNotNone(cfg_initiator)
++            if tcase.exported:
++                self.assertIsNotNone(res['export'])
++                self.assertEqual(res['export'], cfg_initiator.exports[0])
++            else:
++                self.assertIsNone(res['export'])
++
++    @ddt.data(*_ISCSI_TEST_CASES)
++    def test_iscsi_get_config(self, tcase: IscsiTestCase) -> None:
++        """Make sure the StorPool iSCSI configuration is parsed correctly."""
++        cfg_orig = MockIscsiConfig.build(tcase)
++        configs = [cfg_orig]
++        iapi = MockIscsiAPI(configs, self)
++        with mock.patch.object(self.driver._attach, 'api', new=lambda: iapi):
++            res = self.driver._get_iscsi_config(
++                _ISCSI_IQN_OURS,
++                fconst.VOLUME_ID,
++            )
++
++        self._validate_iscsi_config(cfg_orig, res, tcase)
++
++    @ddt.data(*_ISCSI_TEST_CASES)
++    def test_iscsi_create_export(self, tcase: IscsiTestCase) -> None:
++        """Make sure _create_iscsi_export() makes the right API calls."""
++        cfg_orig = MockIscsiConfig.build(tcase)
++        configs = [cfg_orig]
++        iapi = MockIscsiAPI(configs, self)
++        with mock.patch.object(self.driver._attach, 'api', new=lambda: iapi):
++            self.driver._create_iscsi_export(
++                {
++                    'id': fconst.VOLUME_ID,
++                    'display_name': fconst.VOLUME_NAME,
++                },
++                {
++                    # Yeah, okay, so we cheat a little bit here...
++                    'host': _ISCSI_IQN_OURS + '.hostname',
++                    'initiator': _ISCSI_IQN_OURS,
++                },
++            )
++
++        self.assertEqual(len(configs), tcase.commands_count + 1)
++        cfg_final = configs[-1]
++        self.assertEqual(cfg_final.initiators['1'].name, _ISCSI_IQN_OURS)
++        self.assertEqual(
++            cfg_final.initiators['1'].exports[0].target,
++            targetName(fconst.VOLUME_ID),
++        )
++        self.assertEqual(
++            cfg_final.targets['1'].volume,
++            volumeName(fconst.VOLUME_ID),
++        )
 diff --git a/cinder/volume/drivers/storpool.py b/cinder/volume/drivers/storpool.py
-index 58b64bced..f238dc217 100644
+index b15a201c3..ba5aa10c3 100644
 --- a/cinder/volume/drivers/storpool.py
 +++ b/cinder/volume/drivers/storpool.py
 @@ -15,6 +15,7 @@
@@ -581,7 +990,7 @@
              'clone_across_pools': True,
              'sparse_copy_volume': True,
 diff --git a/doc/source/configuration/block-storage/drivers/storpool-volume-driver.rst b/doc/source/configuration/block-storage/drivers/storpool-volume-driver.rst
-index d2c5895a9..c891675bc 100644
+index d2c5895a9..1f3d46cce 100644
 --- a/doc/source/configuration/block-storage/drivers/storpool-volume-driver.rst
 +++ b/doc/source/configuration/block-storage/drivers/storpool-volume-driver.rst
 @@ -19,12 +19,15 @@ Prerequisites
@@ -634,10 +1043,14 @@
  Configuring the StorPool volume driver
  --------------------------------------
  
-@@ -55,6 +81,21 @@ volume backend definition) and per volume type:
+@@ -55,6 +81,32 @@ volume backend definition) and per volume type:
    with the default placement constraints for the StorPool cluster.
    The default value for the chain replication is 3.
  
++In addition, if the iSCSI protocol is used to access the StorPool cluster as
++described in the previous section, the following options may be defined in
++the ``cinder.conf`` volume backend definition:
++
 +- ``iscsi_export_to``: if set to the value ``*``, the StorPool Cinder driver
 +  will export volumes and snapshots using the iSCSI protocol instead of
 +  the StorPool network protocol. The ``iscsi_portal_group`` option must also
@@ -653,9 +1066,32 @@
 +  to attach the volumes and snapshots for transferring data to and from
 +  Glance images.
 +
++- ``iscsi_learn_initiator_iqns``: if enabled, the StorPool Cinder driver will
++  automatically use the StorPool API to create definitions for new initiators
++  in the StorPool cluster's configuration. This is the default behavior of
++  the driver; it may be disabled in the rare case if, e.g. because of site
++  policy, OpenStack iSCSI initiators (e.g. Nova hypervisors) need to be
++  explicitly allowed to use the StorPool iSCSI targets.
++
  Using the StorPool volume driver
  --------------------------------
  
+diff --git a/releasenotes/notes/storpool-iscsi-cefcfe590a07c5c7.yaml b/releasenotes/notes/storpool-iscsi-cefcfe590a07c5c7.yaml
+new file mode 100644
+index 000000000..c48686abb
+--- /dev/null
++++ b/releasenotes/notes/storpool-iscsi-cefcfe590a07c5c7.yaml
+@@ -0,0 +1,10 @@
++---
++features:
++  - |
++    StorPool driver: Added support for exporting the StorPool-backed volumes
++    using the iSCSI protocol, so that the Cinder volume service and/or
++    the Nova or Glance consumers do not need to have the StorPool block
++    device third-party service installed. See the StorPool driver section in
++    the Cinder documentation for more information on the ``iscsi_export_to``,
++    ``iscsi_portal_group``, ``iscsi_cinder_volume``, and
++    ``iscsi_learn_initiator_iqns`` options.
 -- 
-2.39.2
+2.40.1
 
diff --git a/patches/openstack/cinder/sep-sp-leave-it-to-brick.patch b/patches/openstack/cinder/sep-sp-leave-it-to-brick.patch
index 1367a63..1f3233d 100644
--- a/patches/openstack/cinder/sep-sp-leave-it-to-brick.patch
+++ b/patches/openstack/cinder/sep-sp-leave-it-to-brick.patch
@@ -1,4 +1,4 @@
-From 29dbfb6261738d63443bee5c48431a918c6ef376 Mon Sep 17 00:00:00 2001
+From ad5d9ec1cfb4dad9e3d4e2137d915ebe32e1d53f Mon Sep 17 00:00:00 2001
 From: Peter Penchev <openstack-dev@storpool.com>
 Date: Tue, 20 Apr 2021 17:46:41 +0300
 Subject: [PATCH 04/10] StorPool: drop _attach_volume() and _detach_volume()
@@ -109,5 +109,5 @@
 +    encrypted StorPool volumes by dropping the needlessly and incompletely
 +    overridden `_attach_volume()` and `_detach_volume()` methods.
 -- 
-2.39.2
+2.40.1
 
diff --git a/patches/openstack/cinder/sep-sp-retype-vol.patch b/patches/openstack/cinder/sep-sp-retype-vol.patch
index 54ab730..1f42829 100644
--- a/patches/openstack/cinder/sep-sp-retype-vol.patch
+++ b/patches/openstack/cinder/sep-sp-retype-vol.patch
@@ -1,4 +1,4 @@
-From ea9495e33f10979fea5268ff51db5847518cebbc Mon Sep 17 00:00:00 2001
+From 61908eaecc84808b77a4751ca890f92a6067c65e Mon Sep 17 00:00:00 2001
 From: Peter Penchev <openstack-dev@storpool.com>
 Date: Wed, 14 Dec 2022 17:55:56 +0200
 Subject: [PATCH 08/10] StorPool: fix the retype volume flow
@@ -119,5 +119,5 @@
 +                      {'tname': temp_name, 'oname': orig_name, 'err': e})
 +            return {'_name_id': new_volume['_name_id'] or new_volume['id']}
 -- 
-2.39.2
+2.40.1
 
diff --git a/patches/openstack/cinder/sep-sp-rm-backup.patch b/patches/openstack/cinder/sep-sp-rm-backup.patch
index 286f786..1f49d4c 100644
--- a/patches/openstack/cinder/sep-sp-rm-backup.patch
+++ b/patches/openstack/cinder/sep-sp-rm-backup.patch
@@ -1,4 +1,4 @@
-From cf775ebf2b001ae3e0fcbfea51ff0fba03becb72 Mon Sep 17 00:00:00 2001
+From 597e42b37bc722de3e075f3b1a34b79759cb5d86 Mon Sep 17 00:00:00 2001
 From: Peter Penchev <openstack-dev@storpool.com>
 Date: Mon, 11 May 2020 11:02:53 +0300
 Subject: [PATCH 05/10] StorPool driver: remove the obsolete backup_volume()
@@ -51,5 +51,5 @@
          req_id = context.request_id
          volname = self._attach.volumeName(volume['id'])
 -- 
-2.39.2
+2.40.1
 
diff --git a/patches/openstack/cinder/sep-sp-rm-copy-volimg.patch b/patches/openstack/cinder/sep-sp-rm-copy-volimg.patch
index c15c4a5..8dea089 100644
--- a/patches/openstack/cinder/sep-sp-rm-copy-volimg.patch
+++ b/patches/openstack/cinder/sep-sp-rm-copy-volimg.patch
@@ -1,4 +1,4 @@
-From 18a0deedf635f1aa88afa8a3b3dc108e92df286e Mon Sep 17 00:00:00 2001
+From 8991fe8faa2b05a1997259738c4feb3134565126 Mon Sep 17 00:00:00 2001
 From: Peter Penchev <openstack-dev@storpool.com>
 Date: Mon, 26 Sep 2022 16:04:36 +0300
 Subject: [PATCH 06/10] StorPool: drop copy_image_to_volume() and
@@ -68,5 +68,5 @@
          size = int(new_size) * units.Gi
          name = self._attach.volumeName(volume['id'])
 -- 
-2.39.2
+2.40.1