diff --git a/os_win/constants.py b/os_win/constants.py index 07caf6e3..5ee30abf 100644 --- a/os_win/constants.py +++ b/os_win/constants.py @@ -212,3 +212,19 @@ VM_SNAPSHOT_TYPE_PROD_ENFORCED = 4 VM_SNAPSHOT_TYPE_STANDARD = 5 DEFAULT_WMI_EVENT_TIMEOUT_MS = 2000 + +SCSI_UID_SCSI_NAME_STRING = 8 +SCSI_UID_FCPH_NAME = 3 +SCSI_UID_EUI64 = 2 +SCSI_UID_VENDOR_ID = 1 +SCSI_UID_VENDOR_SPECIFIC = 0 + +# The following SCSI Unique ID formats are accepted by Windows, in this +# specific order of precedence. +SUPPORTED_SCSI_UID_FORMATS = [ + SCSI_UID_SCSI_NAME_STRING, + SCSI_UID_FCPH_NAME, + SCSI_UID_EUI64, + SCSI_UID_VENDOR_ID, + SCSI_UID_VENDOR_SPECIFIC +] diff --git a/os_win/exceptions.py b/os_win/exceptions.py index d47c3a74..a73517a5 100644 --- a/os_win/exceptions.py +++ b/os_win/exceptions.py @@ -251,3 +251,13 @@ class ClusterPropertyListEntryNotFound(ClusterPropertyRetrieveFailed): class ClusterPropertyListParsingError(ClusterPropertyRetrieveFailed): msg_fmt = _("Parsing a cluster property list failed.") + + +class SCSIPageParsingError(Invalid): + msg_fmt = _("Parsing SCSI Page %(page)s failed. " + "Reason: %(reason)s.") + + +class SCSIIdDescriptorParsingError(Invalid): + msg_fmt = _("Parsing SCSI identification descriptor failed. " + "Reason: %(reason)s.") diff --git a/os_win/tests/unit/utils/storage/initiator/test_fc_utils.py b/os_win/tests/unit/utils/storage/initiator/test_fc_utils.py index 1afdaf58..a539925e 100644 --- a/os_win/tests/unit/utils/storage/initiator/test_fc_utils.py +++ b/os_win/tests/unit/utils/storage/initiator/test_fc_utils.py @@ -36,6 +36,10 @@ class FCUtilsTestCase(base.BaseTestCase): self._setup_lib_mocks() self._fc_utils = fc_utils.FCUtils() + self._fc_utils._diskutils = mock.Mock() + + self._diskutils = self._fc_utils._diskutils + self._run_mocker = mock.patch.object(self._fc_utils, '_run_and_check_output') self._run_mocker.start() @@ -330,3 +334,121 @@ class FCUtilsTestCase(base.BaseTestCase): expected_func = fc_utils.hbaapi.HBA_RefreshAdapterConfiguration expected_func.assert_called_once_with() + + def test_send_scsi_inquiry_v2(self): + self._ctypes_mocker.stop() + + fake_port_wwn = fc_struct.HBA_WWN() + fake_remote_port_wwn = fc_struct.HBA_WWN() + fake_fcp_lun = 11 + + fake_cdb_byte_1 = 1 + fake_cdb_byte_2 = 0x80 + + fake_resp = bytearray(range(200)) + fake_sense_data = bytearray(range(200)[::-1]) + fake_scsi_status = 5 + + def mock_run(func, hba_handle, port_wwn_struct, + remote_port_wwn_struct, fcp_lun, cdb_byte1, + cdb_byte2, p_resp_buff, p_resp_buff_sz, + p_scsi_status, p_sense_buff, p_sense_buff_sz): + self.assertEqual(fc_utils.hbaapi.HBA_ScsiInquiryV2, func) + self.assertEqual(mock.sentinel.hba_handle, hba_handle) + self.assertEqual(fake_port_wwn, port_wwn_struct) + self.assertEqual(fake_remote_port_wwn, remote_port_wwn_struct) + + self.assertEqual(fake_fcp_lun, fcp_lun.value) + self.assertEqual(fake_cdb_byte_1, cdb_byte1.value) + self.assertEqual(fake_cdb_byte_2, cdb_byte2.value) + + resp_buff_sz = ctypes.cast( + p_resp_buff_sz, + ctypes.POINTER(ctypes.c_uint32)).contents + sense_buff_sz = ctypes.cast( + p_sense_buff_sz, + ctypes.POINTER(ctypes.c_uint32)).contents + scsi_status = ctypes.cast( + p_scsi_status, + ctypes.POINTER(ctypes.c_ubyte)).contents + + self.assertEqual(fc_utils.SCSI_INQ_BUFF_SZ, resp_buff_sz.value) + self.assertEqual(fc_utils.SENSE_BUFF_SZ, sense_buff_sz.value) + + resp_buff_type = (ctypes.c_ubyte * resp_buff_sz.value) + sense_buff_type = (ctypes.c_ubyte * sense_buff_sz.value) + + resp_buff = ctypes.cast(p_resp_buff, + ctypes.POINTER(resp_buff_type)).contents + sense_buff = ctypes.cast(p_sense_buff, + ctypes.POINTER(sense_buff_type)).contents + + resp_buff[:len(fake_resp)] = fake_resp + sense_buff[:len(fake_sense_data)] = fake_sense_data + + resp_buff_sz.value = len(fake_resp) + sense_buff_sz.value = len(fake_sense_data) + scsi_status.value = fake_scsi_status + + self._mock_run.side_effect = mock_run + + resp_buff = self._fc_utils._send_scsi_inquiry_v2( + mock.sentinel.hba_handle, + fake_port_wwn, + fake_remote_port_wwn, + fake_fcp_lun, + fake_cdb_byte_1, + fake_cdb_byte_2) + + self.assertEqual(fake_resp, bytearray(resp_buff[:len(fake_resp)])) + + @mock.patch.object(fc_utils.FCUtils, '_send_scsi_inquiry_v2') + def test_get_scsi_device_id_vpd(self, mock_send_scsi_inq): + self._fc_utils._get_scsi_device_id_vpd( + mock.sentinel.hba_handle, mock.sentinel.port_wwn, + mock.sentinel.remote_port_wwn, mock.sentinel.fcp_lun) + + mock_send_scsi_inq.assert_called_once_with( + mock.sentinel.hba_handle, mock.sentinel.port_wwn, + mock.sentinel.remote_port_wwn, mock.sentinel.fcp_lun, + 1, 0x83) + + @mock.patch.object(fc_utils.FCUtils, '_wwn_struct_from_hex_str') + @mock.patch.object(fc_utils.FCUtils, '_open_adapter_by_wwn') + @mock.patch.object(fc_utils.FCUtils, '_close_adapter') + @mock.patch.object(fc_utils.FCUtils, '_get_scsi_device_id_vpd') + def test_get_scsi_device_identifiers(self, mock_get_scsi_dev_id_vpd, + mock_close_adapter, mock_open_adapter, + mock_wwn_struct_from_hex_str): + + mock_wwn_struct_from_hex_str.side_effect = ( + mock.sentinel.local_wwnn_struct, mock.sentinel.local_wwpn_struct, + mock.sentinel.remote_wwpn_struct) + self._diskutils._parse_scsi_page_83.return_value = ( + mock.sentinel.identifiers) + + identifiers = self._fc_utils.get_scsi_device_identifiers( + mock.sentinel.local_wwnn, mock.sentinel.local_wwpn, + mock.sentinel.remote_wwpn, mock.sentinel.fcp_lun, + mock.sentinel.select_supp_ids) + + self.assertEqual(mock.sentinel.identifiers, identifiers) + + mock_wwn_struct_from_hex_str.assert_has_calls( + [mock.call(wwn) + for wwn in (mock.sentinel.local_wwnn, mock.sentinel.local_wwpn, + mock.sentinel.remote_wwpn)]) + + mock_get_scsi_dev_id_vpd.assert_called_once_with( + mock_open_adapter.return_value, + mock.sentinel.local_wwpn_struct, + mock.sentinel.remote_wwpn_struct, + mock.sentinel.fcp_lun) + self._diskutils._parse_scsi_page_83.assert_called_once_with( + mock_get_scsi_dev_id_vpd.return_value, + select_supported_identifiers=mock.sentinel.select_supp_ids) + + mock_open_adapter.assert_called_once_with( + mock.sentinel.local_wwnn_struct) + mock_close_adapter.assert_called_once_with( + mock_open_adapter.return_value) diff --git a/os_win/tests/unit/utils/storage/test_diskutils.py b/os_win/tests/unit/utils/storage/test_diskutils.py index 5390fcf9..33ae6f36 100644 --- a/os_win/tests/unit/utils/storage/test_diskutils.py +++ b/os_win/tests/unit/utils/storage/test_diskutils.py @@ -16,6 +16,8 @@ import ddt import mock +from os_win import _utils +from os_win import constants from os_win import exceptions from os_win.tests.unit import test_base from os_win.utils.storage import diskutils @@ -185,3 +187,117 @@ class DiskUtilsTestCase(test_base.OsWinBaseTestCase): def test_get_disk_capacity_raised_exc(self): self._test_get_disk_capacity( raised_exc=exceptions.Win32Exception) + + def test_parse_scsi_id_desc(self): + vpd_str = ('008300240103001060002AC00000000000000EA0' + '0000869902140004746573740115000400000001') + buff = _utils.hex_str_to_byte_array(vpd_str) + + identifiers = self._diskutils._parse_scsi_page_83(buff) + + exp_scsi_id_0 = '60002AC00000000000000EA000008699' + exp_scsi_id_1 = '74657374' + exp_scsi_id_2 = '00000001' + + exp_identifiers = [ + {'protocol': None, + 'raw_id_desc_size': 20, + 'raw_id': _utils.hex_str_to_byte_array(exp_scsi_id_0), + 'code_set': 1, + 'type': 3, + 'id': exp_scsi_id_0, + 'association': 0}, + {'protocol': None, + 'raw_id_desc_size': 8, + 'raw_id': _utils.hex_str_to_byte_array(exp_scsi_id_1), + 'code_set': 2, + 'type': 4, + 'id': 'test', + 'association': 1}, + {'protocol': None, + 'raw_id_desc_size': 8, + 'raw_id': _utils.hex_str_to_byte_array(exp_scsi_id_2), + 'code_set': 1, + 'type': 5, + 'id': exp_scsi_id_2, + 'association': 1}] + + self.assertEqual(exp_identifiers, identifiers) + + def test_parse_supported_scsi_id_desc(self): + vpd_str = ('008300240103001060002AC00000000000000EA0' + '0000869901140004000003F40115000400000001') + buff = _utils.hex_str_to_byte_array(vpd_str) + + identifiers = self._diskutils._parse_scsi_page_83( + buff, select_supported_identifiers=True) + + exp_scsi_id = '60002AC00000000000000EA000008699' + exp_identifiers = [ + {'protocol': None, + 'raw_id_desc_size': 20, + 'raw_id': _utils.hex_str_to_byte_array(exp_scsi_id), + 'code_set': 1, + 'type': 3, + 'id': exp_scsi_id, + 'association': 0}] + self.assertEqual(exp_identifiers, identifiers) + + def test_parse_scsi_page_83_no_desc(self): + # We've set the page length field to 0, so we're expecting an + # empty list to be returned. + vpd_str = ('008300000103001060002AC00000000000000EA0' + '0000869901140004000003F40115000400000001') + buff = _utils.hex_str_to_byte_array(vpd_str) + + identifiers = self._diskutils._parse_scsi_page_83(buff) + self.assertEqual([], identifiers) + + def test_parse_scsi_id_desc_exc(self): + vpd_str = '0083' + # Invalid VPD page data (buffer too small) + self.assertRaises(exceptions.SCSIPageParsingError, + self._diskutils._parse_scsi_page_83, + _utils.hex_str_to_byte_array(vpd_str)) + + vpd_str = ('00FF00240103001060002AC00000000000000EA0' + '0000869901140004000003F40115000400000001') + # Unexpected page code + self.assertRaises(exceptions.SCSIPageParsingError, + self._diskutils._parse_scsi_page_83, + _utils.hex_str_to_byte_array(vpd_str)) + + vpd_str = ('008300F40103001060002AC00000000000000EA0' + '0000869901140004000003F40115000400000001') + # VPD page overflow + self.assertRaises(exceptions.SCSIPageParsingError, + self._diskutils._parse_scsi_page_83, + _utils.hex_str_to_byte_array(vpd_str)) + + vpd_str = ('00830024010300FF60002AC00000000000000EA0' + '0000869901140004000003F40115000400000001') + # Identifier overflow + self.assertRaises(exceptions.SCSIIdDescriptorParsingError, + self._diskutils._parse_scsi_page_83, + _utils.hex_str_to_byte_array(vpd_str)) + + vpd_str = ('0083001F0103001060002AC00000000000000EA0' + '0000869901140004000003F4011500') + # Invalid identifier structure (too small) + self.assertRaises(exceptions.SCSIIdDescriptorParsingError, + self._diskutils._parse_scsi_page_83, + _utils.hex_str_to_byte_array(vpd_str)) + + def test_select_supported_scsi_identifiers(self): + identifiers = [ + {'type': id_type} + for id_type in constants.SUPPORTED_SCSI_UID_FORMATS[::-1]] + identifiers.append({'type': mock.sentinel.scsi_id_format}) + + expected_identifiers = [ + {'type': id_type} + for id_type in constants.SUPPORTED_SCSI_UID_FORMATS] + + result = self._diskutils._select_supported_scsi_identifiers( + identifiers) + self.assertEqual(expected_identifiers, result) diff --git a/os_win/utils/storage/diskutils.py b/os_win/utils/storage/diskutils.py index e05ed7ac..c4f78cd7 100644 --- a/os_win/utils/storage/diskutils.py +++ b/os_win/utils/storage/diskutils.py @@ -22,6 +22,7 @@ from oslo_log import log as logging from os_win._i18n import _ from os_win import _utils +from os_win import constants from os_win import exceptions from os_win.utils import baseutils from os_win.utils import win32utils @@ -32,6 +33,36 @@ kernel32 = w_lib.get_shared_lib_handle(w_lib.KERNEL32) LOG = logging.getLogger(__name__) +class DEVICE_ID_VPD_PAGE(ctypes.BigEndianStructure): + _fields_ = [ + ('DeviceType', ctypes.c_ubyte, 5), + ('Qualifier', ctypes.c_ubyte, 3), + ('PageCode', ctypes.c_ubyte), + ('PageLength', ctypes.c_uint16) + ] + + +class IDENTIFICATION_DESCRIPTOR(ctypes.Structure): + _fields_ = [ + ('CodeSet', ctypes.c_ubyte, 4), + ('ProtocolIdentifier', ctypes.c_ubyte, 4), + ('IdentifierType', ctypes.c_ubyte, 4), + ('Association', ctypes.c_ubyte, 2), + ('_reserved', ctypes.c_ubyte, 1), + ('Piv', ctypes.c_ubyte, 1), + ('_reserved', ctypes.c_ubyte), + ('IdentifierLength', ctypes.c_ubyte) + ] + + +PDEVICE_ID_VPD_PAGE = ctypes.POINTER(DEVICE_ID_VPD_PAGE) +PIDENTIFICATION_DESCRIPTOR = ctypes.POINTER(IDENTIFICATION_DESCRIPTOR) + +SCSI_ID_ASSOC_TYPE_DEVICE = 0 +SCSI_ID_CODE_SET_BINARY = 1 +SCSI_ID_CODE_SET_ASCII = 2 + + class DiskUtils(baseutils.BaseUtils): _wmi_namespace = 'root/microsoft/windows/storage' @@ -106,3 +137,110 @@ class DiskUtils(baseutils.BaseUtils): return 0, 0 else: raise exc + + def _parse_scsi_page_83(self, buff, + select_supported_identifiers=False): + """Parse SCSI Device Identification VPD (page 0x83 data). + + :param buff: a byte array containing the SCSI page 0x83 data. + :param select_supported_identifiers: select identifiers supported + by Windows, in the order of precedence. + :returns: a list of identifiers represented as dicts, containing + SCSI Unique IDs. + """ + identifiers = [] + + buff_sz = len(buff) + buff = (ctypes.c_ubyte * buff_sz)(*bytearray(buff)) + + vpd_pg_struct_sz = ctypes.sizeof(DEVICE_ID_VPD_PAGE) + + if buff_sz < vpd_pg_struct_sz: + reason = _('Invalid VPD page data.') + raise exceptions.SCSIPageParsingError(page='0x83', + reason=reason) + + vpd_page = ctypes.cast(buff, PDEVICE_ID_VPD_PAGE).contents + vpd_page_addr = ctypes.addressof(vpd_page) + total_page_sz = vpd_page.PageLength + vpd_pg_struct_sz + + if vpd_page.PageCode != 0x83: + reason = _('Unexpected page code: %s') % vpd_page.PageCode + raise exceptions.SCSIPageParsingError(page='0x83', + reason=reason) + if total_page_sz > buff_sz: + reason = _('VPD page overflow.') + raise exceptions.SCSIPageParsingError(page='0x83', + reason=reason) + if not vpd_page.PageLength: + LOG.info('Page 0x83 data does not contain any ' + 'identification descriptors.') + return identifiers + + id_desc_offset = vpd_pg_struct_sz + while id_desc_offset < total_page_sz: + id_desc_addr = vpd_page_addr + id_desc_offset + # Remaining buffer size + id_desc_buff_sz = buff_sz - id_desc_offset + + identifier = self._parse_scsi_id_desc(id_desc_addr, + id_desc_buff_sz) + identifiers.append(identifier) + + id_desc_offset += identifier['raw_id_desc_size'] + + if select_supported_identifiers: + identifiers = self._select_supported_scsi_identifiers(identifiers) + + return identifiers + + def _parse_scsi_id_desc(self, id_desc_addr, buff_sz): + """Parse SCSI VPD identification descriptor.""" + id_desc_struct_sz = ctypes.sizeof(IDENTIFICATION_DESCRIPTOR) + + if buff_sz < id_desc_struct_sz: + reason = _('Identifier descriptor overflow.') + raise exceptions.SCSIIdDescriptorParsingError(reason=reason) + + id_desc = IDENTIFICATION_DESCRIPTOR.from_address(id_desc_addr) + id_desc_sz = id_desc_struct_sz + id_desc.IdentifierLength + identifier_addr = id_desc_addr + id_desc_struct_sz + + if id_desc_sz > buff_sz: + reason = _('Identifier overflow.') + raise exceptions.SCSIIdDescriptorParsingError(reason=reason) + + identifier = (ctypes.c_ubyte * + id_desc.IdentifierLength).from_address( + identifier_addr) + raw_id = bytearray(identifier) + + if id_desc.CodeSet == SCSI_ID_CODE_SET_ASCII: + parsed_id = bytes( + bytearray(identifier)).decode('ascii').strip('\x00') + else: + parsed_id = _utils.byte_array_to_hex_str(raw_id) + + id_dict = { + 'code_set': id_desc.CodeSet, + 'protocol': (id_desc.ProtocolIdentifier + if id_desc.Piv else None), + 'type': id_desc.IdentifierType, + 'association': id_desc.Association, + 'raw_id': raw_id, + 'id': parsed_id, + 'raw_id_desc_size': id_desc_sz, + } + return id_dict + + def _select_supported_scsi_identifiers(self, identifiers): + # This method will filter out unsupported SCSI identifiers, + # also sorting them based on the order of precedence. + selected_identifiers = [] + + for id_type in constants.SUPPORTED_SCSI_UID_FORMATS: + for identifier in identifiers: + if identifier['type'] == id_type: + selected_identifiers.append(identifier) + + return selected_identifiers diff --git a/os_win/utils/storage/initiator/fc_utils.py b/os_win/utils/storage/initiator/fc_utils.py index f6710d44..0afd443d 100644 --- a/os_win/utils/storage/initiator/fc_utils.py +++ b/os_win/utils/storage/initiator/fc_utils.py @@ -23,6 +23,7 @@ from os_win._i18n import _ from os_win import _utils import os_win.conf from os_win import exceptions +from os_win.utils.storage import diskutils from os_win.utils import win32utils from os_win.utils.winapi import constants as w_const from os_win.utils.winapi import libs as w_lib @@ -37,10 +38,14 @@ LOG = logging.getLogger(__name__) HBA_STATUS_OK = 0 HBA_STATUS_ERROR_MORE_DATA = 7 +SCSI_INQ_BUFF_SZ = 256 +SENSE_BUFF_SZ = 256 + class FCUtils(object): def __init__(self): self._win32_utils = win32utils.Win32Utils() + self._diskutils = diskutils.DiskUtils() def _run_and_check_output(self, *args, **kwargs): kwargs['failure_exc'] = exceptions.FCWin32Exception @@ -206,3 +211,80 @@ class FCUtils(object): @_utils.avoid_blocking_call_decorator def refresh_hba_configuration(self): hbaapi.HBA_RefreshAdapterConfiguration() + + def _send_scsi_inquiry_v2(self, hba_handle, port_wwn_struct, + remote_port_wwn_struct, + fcp_lun, cdb_byte1, cdb_byte2): + port_wwn = _utils.byte_array_to_hex_str(port_wwn_struct.wwn) + remote_port_wwn = _utils.byte_array_to_hex_str( + remote_port_wwn_struct.wwn) + + LOG.debug("Sending SCSI INQUIRY to WWPN %(remote_port_wwn)s, " + "FCP LUN %(fcp_lun)s from WWPN %(port_wwn)s. " + "CDB byte 1 %(cdb_byte1)s, CDB byte 2: %(cdb_byte2)s.", + dict(port_wwn=port_wwn, + remote_port_wwn=remote_port_wwn, + fcp_lun=fcp_lun, + cdb_byte1=hex(cdb_byte1), + cdb_byte2=hex(cdb_byte2))) + + resp_buffer_sz = ctypes.c_uint32(SCSI_INQ_BUFF_SZ) + resp_buffer = (ctypes.c_ubyte * resp_buffer_sz.value)() + + sense_buffer_sz = ctypes.c_uint32(SENSE_BUFF_SZ) + sense_buffer = (ctypes.c_ubyte * sense_buffer_sz.value)() + + scsi_status = ctypes.c_ubyte() + + try: + self._run_and_check_output( + hbaapi.HBA_ScsiInquiryV2, + hba_handle, + port_wwn_struct, + remote_port_wwn_struct, + ctypes.c_uint64(fcp_lun), + ctypes.c_uint8(cdb_byte1), + ctypes.c_uint8(cdb_byte2), + ctypes.byref(resp_buffer), + ctypes.byref(resp_buffer_sz), + ctypes.byref(scsi_status), + ctypes.byref(sense_buffer), + ctypes.byref(sense_buffer_sz)) + finally: + sense_data = _utils.byte_array_to_hex_str( + sense_buffer[:sense_buffer_sz.value]) + LOG.debug("SCSI inquiry returned sense data: %(sense_data)s. " + "SCSI status: %(scsi_status)s.", + dict(sense_data=sense_data, + scsi_status=scsi_status.value)) + + return resp_buffer + + def _get_scsi_device_id_vpd(self, hba_handle, port_wwn_struct, + remote_port_wwn_struct, fcp_lun): + # The following bytes will be included in the CDB passed to the + # lun, requesting the 0x83 VPD page. + cdb_byte1 = 1 + cdb_byte2 = 0x83 + return self._send_scsi_inquiry_v2(hba_handle, port_wwn_struct, + remote_port_wwn_struct, fcp_lun, + cdb_byte1, cdb_byte2) + + def get_scsi_device_identifiers(self, node_wwn, port_wwn, + remote_port_wwn, fcp_lun, + select_supported_identifiers=True): + node_wwn_struct = self._wwn_struct_from_hex_str(node_wwn) + port_wwn_struct = self._wwn_struct_from_hex_str(port_wwn) + remote_port_wwn_struct = self._wwn_struct_from_hex_str( + remote_port_wwn) + + with self._get_hba_handle( + adapter_wwn_struct=node_wwn_struct) as hba_handle: + vpd_data = self._get_scsi_device_id_vpd(hba_handle, + port_wwn_struct, + remote_port_wwn_struct, + fcp_lun) + identifiers = self._diskutils._parse_scsi_page_83( + vpd_data, + select_supported_identifiers=select_supported_identifiers) + return identifiers diff --git a/os_win/utils/winapi/libs/hbaapi.py b/os_win/utils/winapi/libs/hbaapi.py index d2a20e4b..14e27ae8 100644 --- a/os_win/utils/winapi/libs/hbaapi.py +++ b/os_win/utils/winapi/libs/hbaapi.py @@ -142,5 +142,20 @@ def register(): HBA_WWN] lib_handle.HBA_OpenAdapterByWWN.restype = HBA_STATUS + lib_handle.HBA_ScsiInquiryV2.argtypes = [ + HBA_HANDLE, + HBA_WWN, + HBA_WWN, + ctypes.c_uint64, + ctypes.c_uint8, + ctypes.c_uint8, + wintypes.PVOID, + ctypes.POINTER(ctypes.c_uint32), + ctypes.POINTER(ctypes.c_uint8), + wintypes.PVOID, + ctypes.POINTER(ctypes.c_uint32) + ] + lib_handle.HBA_ScsiInquiryV2.restype = HBA_STATUS + lib_handle.HBA_RefreshAdapterConfiguration.argtypes = [] lib_handle.HBA_RefreshAdapterConfiguration.restype = None diff --git a/releasenotes/notes/fc_scsi_id-c29d2e17c4d83453.yaml b/releasenotes/notes/fc_scsi_id-c29d2e17c4d83453.yaml new file mode 100644 index 00000000..06d0b846 --- /dev/null +++ b/releasenotes/notes/fc_scsi_id-c29d2e17c4d83453.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + os-win now supports retrieving SCSI unique ids for FibreChannel disks. This + allows discovering FibreChannel disks in a more efficient way.