From fc6ef93a2aed6fddea7ebd2af110a46a51a73148 Mon Sep 17 00:00:00 2001
From: Mark Syms <mark.syms@citrix.com>
Date: Fri, 15 May 2026 11:12:36 +0100
Subject: [PATCH] CP-312772: extend SR probe to find additional devices.

diff --git a/drivers/HBASR.py b/drivers/HBASR.py
index 66ca3ee..ed01495 100755
--- a/drivers/HBASR.py
+++ b/drivers/HBASR.py
@@ -244,9 +244,17 @@ class HBASR(SR.SR):
             self.virtual_allocation = self.physical_utilisation
         return super(HBASR, self).scan(sr_uuid)
 
+    def load_powerflex(self):
+        devices = devscan.powerflex_devices()
+
+        if devices:
+            self.powerflex_devices = devices
+
     def print_devs(self):
         self.attach("")
         self._init_hbadict()
+        # Load any PowerFlex volumes
+        self.load_powerflex()
         return devscan.scan(self)
 
     # This function returns a dictionary of HBA attached LUNs
diff --git a/drivers/devscan.py b/drivers/devscan.py
index 08b0f2f..b219ba6 100644
--- a/drivers/devscan.py
+++ b/drivers/devscan.py
@@ -27,6 +27,8 @@ DMDEVPATH = '/dev/mapper'
 SYSFS_PATH1 = '/sys/class/scsi_host'
 SYSFS_PATH2 = '/sys/class/scsi_disk'
 SYSFS_PATH3 = '/sys/class/fc_transport'
+PFX_PATH = '/dev/disk/by-scsid/emc-vol-*'
+SYSFS_BLOCK = '/sys/class/block'
 
 DRIVER_BLACKLIST = ['^(s|p|)ata_.*', '^ahci$', '^pdc_adma$', '^iscsi_tcp$', '^usb-storage$']
 
@@ -376,6 +378,16 @@ def scan(srobj):
             textnode = dom.createTextNode(str(aval))
             entry.appendChild(textnode)
 
+    pfx_vols = getattr(srobj, 'powerflex_devices', {})
+    for pfx_vol in pfx_vols.values():
+        dev_elem = dom.createElement("BlockDevice")
+        e.appendChild(dev_elem)
+        for k, v in pfx_vol.items():
+            entry = dom.createElement(str(k))
+            dev_elem.appendChild(entry)
+            text_node = dom.createTextNode(str(v))
+            entry.appendChild(text_node)
+
     for key in hbas:
         a = dom.createElement("Adapter")
         e.appendChild(a)
@@ -429,3 +441,32 @@ def match_nonpartitions(s):
     regex = re.compile("-part[0-9]")
     if not regex.search(s, 0):
         return True
+
+
+def powerflex_devices():
+    devices = {}
+
+    for vol in glob.glob(PFX_PATH):
+        try:
+            SCSIid = os.path.basename(vol)
+            path = os.path.realpath(os.path.join(vol, 'scini'))
+            dev_name = os.path.basename(path)
+
+            with open(os.path.join(SYSFS_BLOCK, dev_name, 'size')) as f:
+                size_in_blocks = int(f.readline().strip())
+
+            with open(os.path.join(SYSFS_BLOCK, dev_name,
+                                   'queue', 'logical_block_size')) as f:
+                blocksize = int(f.readline().strip())
+
+            devices[SCSIid] = {
+                'SCSIid': SCSIid,
+                'path': path,
+                'vendor': 'emc',
+                'size': size_in_blocks * blocksize,
+            }
+        except (OSError, ValueError) as exc:
+            util.SMlog("Skipping PowerFlex volume %s during probe: %s",
+                       vol, exc)
+
+    return devices
diff --git a/tests/test_HBASR.py b/tests/test_HBASR.py
index 4a06080..7714895 100644
--- a/tests/test_HBASR.py
+++ b/tests/test_HBASR.py
@@ -7,6 +7,10 @@ from DummySR import DRIVER_INFO
 import xml.dom.minidom
 import util
 import xs_errors
+
+import errno
+import io
+import os
 import uuid
 
 
@@ -68,6 +72,27 @@ class TestHBASR(unittest.TestCase):
             "HBASR.devscan.adapters", autospec=True)
         self.mock_devscan_adapters = adapters_patcher.start()
 
+        mpath_handle_patcher = mock.patch(
+            "HBASR.SR.SR._mpathHandle", autospec=True)
+        self.mock_mpath_handle = mpath_handle_patcher.start()
+
+        rootdev_patcher = mock.patch(
+            'HBASR.devscan.util.getrootdevID', autospec=True)
+        self.mock_rootdevid = rootdev_patcher.start()
+
+        glob_patcher = mock.patch('HBASR.devscan.glob.glob', autospec=True)
+        self.mock_glob = glob_patcher.start()
+
+        real_path_basename = os.path.basename
+        real_path_join = os.path.join
+        os_path_patcher = mock.patch('HBASR.devscan.os.path', autospec=True)
+        self.mock_os_path = os_path_patcher.start()
+        self.mock_os_path.basename = real_path_basename
+        self.mock_os_path.join = real_path_join
+
+        smlog_patcher = mock.patch('HBASR.util.SMlog', autospec=True)
+        self.mock_sm_log = smlog_patcher.start()
+
     def make_sr_cmd(self, command='sr_probe'):
         sr_cmd = mock.Mock(spec=SRCommand(DRIVER_INFO))
         sr_cmd.dconf = {}
@@ -230,10 +255,90 @@ class TestHBASR(unittest.TestCase):
         res = sr._probe_hba()
         self.assertEqual(res, imp_fake_probe())
 
-    @mock.patch('HBASR.HBASR._mpathHandle', autospec=True)
-    def test_attach(self, mock_mpath):
+    def test_attach(self):
         sr_uuid = str(uuid.uuid4())
         sr_cmd = self.make_sr_cmd()
         sr = HBASR.HBASR(sr_cmd, sr_uuid)
         sr.attach(1234)
-        self.assertEqual(mock_mpath.call_count, 1)
+        self.assertEqual(self.mock_mpath_handle.call_count, 1)
+
+    def test_print_devs_no_devs(self):
+        # Arrange
+        sr_uuid = str(uuid.uuid4())
+        sr_cmd = self.make_sr_cmd()
+        sr = HBASR.HBASR(sr_cmd, sr_uuid)
+
+        # Act
+        dev_str = sr.print_devs()
+
+        # Assert
+        self.assertEqual(dev_str,
+                         '<?xml version="1.0" ?>\n<Devlist/>\n')
+
+    def test_print_devs_powerflex_error(self):
+        sr_uuid = str(uuid.uuid4())
+        sr_cmd = self.make_sr_cmd()
+        sr = HBASR.HBASR(sr_cmd, sr_uuid)
+        self.mock_glob.return_value = [
+            '/dev/disk/by-scsid/emc-vol-19ab00bee9314e0f-894b409a00000000'
+        ]
+        self.mock_os_path.realpath.side_effect = OSError(errno.ENOENT)
+
+        # Act
+        dev_str = sr.print_devs()
+
+        # Assert
+        self.assertEqual(dev_str,
+                         '<?xml version="1.0" ?>\n<Devlist/>\n')
+
+    def test_print_devs_powerflex(self):
+        # Arrange
+        sr_uuid = str(uuid.uuid4())
+        sr_cmd = self.make_sr_cmd()
+        sr = HBASR.HBASR(sr_cmd, sr_uuid)
+        self.mock_glob.return_value = [
+            '/dev/disk/by-scsid/emc-vol-19ab00bee9314e0f-894b409a00000000'
+        ]
+        self.mock_os_path.realpath.return_value = '/dev/scinia'
+
+        file_data = {
+            'size': 2 * 1024 * 1024,
+            'logical_block_size': 512
+        }
+
+        def open(filename):
+            basename = os.path.basename(filename.strip())
+            file_contents = io.StringIO()
+            file_contents.write(f'{file_data[basename]}\n')
+
+            file_contents.seek(0)
+            return file_contents
+
+        # Act
+        with mock.patch("builtins.open") as mock_open:
+            mock_open.side_effect = open
+
+            dev_str = sr.print_devs()
+
+        # Assert
+        dom = xml.dom.minidom.Document()
+        dl = dom.createElement("Devlist")
+        dom.appendChild(dl)
+        bd = dom.createElement("BlockDevice")
+        dl.appendChild(bd)
+        device_data = {
+            'SCSIid': 'emc-vol-19ab00bee9314e0f-894b409a00000000',
+            'path': '/dev/scinia',
+            'vendor': 'emc',
+            'size': 1073741824}
+        for k, v in device_data.items():
+            entry = dom.createElement(str(k))
+            bd.appendChild(entry)
+            text_node = dom.createTextNode(str(v))
+            entry.appendChild(text_node)
+
+        self.assertEqual(dev_str, dom.toprettyxml())
+        self.mock_os_path.realpath.assert_called_once_with(
+            '/dev/disk/by-scsid/emc-vol-19ab00bee9314e0f-894b409a00000000/'
+            'scini'
+        )
