diff --git a/os_brick/exception.py b/os_brick/exception.py index 294eb3751..30ae498e1 100644 --- a/os_brick/exception.py +++ b/os_brick/exception.py @@ -153,6 +153,11 @@ class InvalidIOHandleObject(BrickException): 'type %(actual_type)s.') +class VolumeDeviceValidationFailed(BrickException): + message = _("Volume device validation failed for device %(device)s " + "(expected %(expected)s, found %(found)s.") + + class VolumeEncryptionNotSupported(Invalid): message = _("Volume encryption is not supported for %(volume_type)s " "volume %(volume_id)s.") diff --git a/os_brick/initiator/connectors/iscsi.py b/os_brick/initiator/connectors/iscsi.py index d31f7054a..840c01aa6 100644 --- a/os_brick/initiator/connectors/iscsi.py +++ b/os_brick/initiator/connectors/iscsi.py @@ -538,6 +538,9 @@ class ISCSIConnector(base.BaseLinuxConnector, base_iscsi.BaseISCSIConnector): return symlink def _get_connect_result(self, con_props, wwn, devices_names, mpath=None): + if 'wwn' in con_props: + self._linuxscsi.verify_sysfs_wwns(devices_names, con_props['wwn']) + device = '/dev/' + (mpath or devices_names[0]) # NOTE(geguileo): This is only necessary because of the current diff --git a/os_brick/initiator/linuxscsi.py b/os_brick/initiator/linuxscsi.py index 1e1f76c5d..822352529 100644 --- a/os_brick/initiator/linuxscsi.py +++ b/os_brick/initiator/linuxscsi.py @@ -154,6 +154,16 @@ class LinuxSCSI(executor.Executor): return udev_wwid return '' + def verify_sysfs_wwns(self, device_names, expected_wwn): + """Verify that all specified devices have the expected WWN.""" + + for device_name in device_names: + found_wwn = self.get_sysfs_wwn([device_name]) + if found_wwn != expected_wwn: + raise exception.VolumeDeviceValidationFailed( + device=device_name, expected=expected_wwn, + found=found_wwn) + def get_scsi_wwn(self, path): """Read the WWN from page 0x83 value for a SCSI device.""" diff --git a/os_brick/tests/initiator/test_linuxscsi.py b/os_brick/tests/initiator/test_linuxscsi.py index 7493ecf35..f7061a148 100644 --- a/os_brick/tests/initiator/test_linuxscsi.py +++ b/os_brick/tests/initiator/test_linuxscsi.py @@ -920,6 +920,18 @@ loop0 0""" open_mock.assert_has_calls([mock.call('/sys/block/sda/device/wwid'), mock.call('/sys/block/sdb/device/wwid')]) + @mock.patch.object(linuxscsi.LinuxSCSI, 'get_sysfs_wwn') + def test_verify_sysfs_wwns_valid(self, get_wwn): + get_wwn.side_effect = ['123', '123', '123', '123'] + self.linuxscsi.verify_sysfs_wwns(['sda', 'sdb', 'sdc', 'sdd'], '123') + + @mock.patch.object(linuxscsi.LinuxSCSI, 'get_sysfs_wwn') + def test_verify_sysfs_wwns_invalid(self, get_wwn): + get_wwn.side_effect = ['123', '456', '123', '123'] + self.assertRaises(exception.VolumeDeviceValidationFailed, + self.linuxscsi.verify_sysfs_wwns, + ['sda', 'sdb', 'sdc', 'sdd'], '123') + @mock.patch.object(linuxscsi.priv_rootwrap, 'unlink_root') @mock.patch('glob.glob') @mock.patch('os.path.realpath', side_effect=['/dev/sda', '/dev/sdb', diff --git a/releasenotes/notes/verify-iscsi-wwns-f6cb536a7fbd4b42.yaml b/releasenotes/notes/verify-iscsi-wwns-f6cb536a7fbd4b42.yaml new file mode 100644 index 000000000..e89c94227 --- /dev/null +++ b/releasenotes/notes/verify-iscsi-wwns-f6cb536a7fbd4b42.yaml @@ -0,0 +1,9 @@ +--- +fixes: + - | + Adds verification that found iSCSI devices have the expected WWN + (must be provided in connection_properties). The attachment fails if + the WWNs do not match. This prevents cases where a device already + exists on the host from an old attachment that wasn't fully cleaned + up, and the new attachment got the same LUN. Using this old device + could lead to data corruption.