Coverage for libs/sm/devscan.py: 59%
291 statements
« prev ^ index » next coverage.py v7.3.2, created at 2026-07-10 07:59 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2026-07-10 07:59 +0000
1# Copyright (C) Citrix Systems Inc.
2#
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU Lesser General Public License as published
5# by the Free Software Foundation; version 2.1 only.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU Lesser General Public License for more details.
11#
12# You should have received a copy of the GNU Lesser General Public License
13# along with this program; if not, write to the Free Software Foundation, Inc.,
14# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16import os
17import re
18from sm.core import scsiutil
19from sm.core import util
20import xml.dom.minidom
21from sm.core import xs_errors
22import glob
24DEVPATH = '/dev/disk/by-id'
25DMDEVPATH = '/dev/mapper'
26SYSFS_PATH1 = '/sys/class/scsi_host'
27SYSFS_PATH2 = '/sys/class/scsi_disk'
28SYSFS_PATH3 = '/sys/class/fc_transport'
30DRIVER_BLACKLIST = ['^(s|p|)ata_.*', '^ahci$', '^pdc_adma$', '^iscsi_tcp$', '^usb-storage$']
32INVALID_DEVICE_NAME = ''
35def getManufacturer(s):
36 (rc, stdout, stderr) = util.doexec(['/sbin/modinfo', '-d', s])
37 if stdout: 37 ↛ 40line 37 didn't jump to line 40, because the condition on line 37 was never false
38 return stdout.strip()
39 else:
40 return "Unknown"
43def update_devs_dict(devs, dev, entry):
44 if dev != INVALID_DEVICE_NAME:
45 devs[dev] = entry
48def adapters(filterstr="any"):
49 dict = {}
50 devs = {}
51 adt = {}
52 for a in os.listdir(SYSFS_PATH1):
53 proc = match_hbadevs(a, filterstr)
54 if not proc: 54 ↛ 55line 54 didn't jump to line 55, because the condition on line 54 was never true
55 continue
57 port_name_path = os.path.join(SYSFS_PATH1, a, 'device', \
58 'fc_host', a, 'port_name')
59 port_name_path_exists = os.path.exists(port_name_path)
60 util.SMlog("Port name path exists %s" % port_name_path_exists)
61 if port_name_path_exists: 61 ↛ 62line 61 didn't jump to line 62, because the condition on line 61 was never true
62 port_name = _get_port_name(port_name_path)
64 adt[a] = proc
65 id = a.replace("host", "")
66 scsiutil.rescan([id])
67 emulex = False
68 paths = []
69 if proc == "lpfc":
70 emulex = True
71 paths.append(SYSFS_PATH3)
72 else:
73 for p in [os.path.join(SYSFS_PATH1, a, "device", "session*"), os.path.join(SYSFS_PATH1, a, "device"), \
74 os.path.join(SYSFS_PATH2, "%s:*" % id)]:
75 paths += glob.glob(p)
76 if not len(paths): 76 ↛ 77line 76 didn't jump to line 77, because the condition on line 76 was never true
77 continue
78 for path in paths:
79 for i in filter(match_targets, os.listdir(path)):
80 tgt = i.replace('target', '')
81 if emulex: 81 ↛ 84line 81 didn't jump to line 84, because the condition on line 81 was never false
82 sysfs = os.path.join(SYSFS_PATH3, i, "device")
83 else:
84 sysfs = SYSFS_PATH2
85 for lun in os.listdir(sysfs):
86 if not match_LUNs(lun, tgt): 86 ↛ 87line 86 didn't jump to line 87, because the condition on line 86 was never true
87 continue
88 if emulex: 88 ↛ 91line 88 didn't jump to line 91, because the condition on line 88 was never false
89 dir = os.path.join(sysfs, lun)
90 else:
91 dir = os.path.join(sysfs, lun, "device")
92 (dev, entry) = _extract_dev(dir, proc, id, lun)
93 update_devs_dict(devs, dev, entry)
94 # for new qlogic sysfs layout (rport under device, then target)
95 for i in filter(match_rport, os.listdir(path)): 95 ↛ 96line 95 didn't jump to line 96, because the loop on line 95 never started
96 newpath = os.path.join(path, i)
97 for j in filter(match_targets, os.listdir(newpath)):
98 tgt = j.replace('target', '')
99 sysfs = SYSFS_PATH2
100 for lun in os.listdir(sysfs):
101 if not match_LUNs(lun, tgt):
102 continue
103 dir = os.path.join(sysfs,lun,"device")
104 (dev, entry) = _extract_dev(dir, proc, id, lun)
105 update_devs_dict(devs, dev, entry)
107 # for new mptsas sysfs entries, check for phy* node
108 for i in filter(match_phy, os.listdir(path)): 108 ↛ 109line 108 didn't jump to line 109, because the loop on line 108 never started
109 (target, lunid) = i.replace('phy-', '').split(':')
110 tgt = "%s:0:0:%s" % (target, lunid)
111 sysfs = SYSFS_PATH2
112 for lun in os.listdir(sysfs):
113 if not match_LUNs(lun, tgt):
114 continue
115 dir = os.path.join(sysfs, lun, "device")
116 (dev, entry) = _extract_dev(dir, proc, id, lun)
117 update_devs_dict(devs, dev, entry)
118 if path.startswith(SYSFS_PATH2):
119 os.path.join(path, "device", "block:*")
120 dev = _extract_dev_name(os.path.join(path, 'device'))
121 if dev in devs: 121 ↛ 122line 121 didn't jump to line 122, because the condition on line 121 was never true
122 continue
123 hbtl = os.path.basename(path)
124 (h, b, t, l) = hbtl.split(':')
125 entry = {'procname': proc, 'host': id, 'target': l}
126 update_devs_dict(devs, dev, entry)
128 dict['devs'] = devs
129 dict['adt'] = adt
130 return dict
133def _get_port_name(port_name_path):
134 port_name = 0
135 try:
136 f = open(port_name_path, 'r')
137 try:
138 line = f.readline()[:-1]
139 if not line in ['<NULL>', '(NULL)', '']:
140 port_name = int(line, 16)
141 util.SMlog("Port Name in sysfs is %d" % port_name)
142 finally:
143 f.close()
144 except IOError:
145 pass
146 return port_name
149def _get_driver_name(scsihost):
150 driver_name = 'Unknown'
151 if os.path.exists(os.path.join(SYSFS_PATH1, scsihost, 'fnic_state')): 151 ↛ 152line 151 didn't jump to line 152, because the condition on line 151 was never true
152 driver_name = 'fnic'
153 if os.path.exists(os.path.join(SYSFS_PATH1, scsihost, 'lpfc_fcp_class')): 153 ↛ 154line 153 didn't jump to line 154, because the condition on line 153 was never true
154 driver_name = 'lpfc'
155 if os.path.exists(os.path.join(SYSFS_PATH1, scsihost, '84xx_fw_version')): 155 ↛ 156line 155 didn't jump to line 156, because the condition on line 155 was never true
156 driver_name = 'qla2xxx'
157 if 'Unknown' == driver_name: 157 ↛ 170line 157 didn't jump to line 170, because the condition on line 157 was never false
158 namepath = os.path.join(SYSFS_PATH1, scsihost, 'driver_name')
159 if not os.path.exists(namepath): 159 ↛ 161line 159 didn't jump to line 161, because the condition on line 159 was never false
160 namepath = os.path.join(SYSFS_PATH1, scsihost, 'proc_name')
161 if os.path.exists(namepath): 161 ↛ 162line 161 didn't jump to line 162, because the condition on line 161 was never true
162 try:
163 f = open(namepath, 'r')
164 line = f.readline()[:-1]
165 f.close()
166 if not line in ['<NULL>', '(NULL)', '']:
167 driver_name = line
168 except IOError:
169 pass
170 if 'Unknown' == driver_name: 170 ↛ 181line 170 didn't jump to line 181, because the condition on line 170 was never false
171 ueventpath = os.path.join(SYSFS_PATH1, scsihost, 'uevent')
172 if os.path.exists(ueventpath): 172 ↛ 173line 172 didn't jump to line 173, because the condition on line 172 was never true
173 try:
174 f = open(ueventpath, 'r')
175 for line in f:
176 if line.startswith('PHYSDEVDRIVER='):
177 driver_name = line.replace('PHYSDEVDRIVER=', '').strip()
178 f.close()
179 except IOError:
180 pass
181 return driver_name
184def _parseHostId(str):
185 id = str.split()
186 val = "%s:%s:%s" % (id[1], id[3], id[5])
187 return val.replace(',', '')
190def match_hbadevs(s, filterstr):
191 driver_name = _get_driver_name(s)
192 if match_host(s) and not match_blacklist(driver_name) \ 192 ↛ 196line 192 didn't jump to line 196, because the condition on line 192 was never false
193 and ( filterstr == "any" or match_filterstr(filterstr, driver_name) ):
194 return driver_name
195 else:
196 return ""
199def match_blacklist(driver_name):
200 return re.search("(" + ")|(".join(DRIVER_BLACKLIST) + ")", driver_name)
203def match_filterstr(filterstr, driver_name):
204 return re.search("^%s" % filterstr, driver_name)
207def match_host(s):
208 return re.search("^host[0-9]", s)
211def match_rport(s):
212 regex = re.compile("^rport-*")
213 return regex.search(s, 0)
216def match_targets(s):
217 regex = re.compile("^target[0-9]")
218 return regex.search(s, 0)
221def match_phy(s):
222 regex = re.compile("^phy-*")
223 return regex.search(s, 0)
226def match_LUNs(s, prefix):
227 regex = re.compile("^%s" % prefix)
228 return regex.search(s, 0)
231def match_dev(s):
232 regex = re.compile("^block:")
233 return regex.search(s, 0)
236def _extract_dev_name(device_dir):
237 """Returns the name of the block device from sysfs e.g. 'sda'"""
238 return _get_block_device_name_with_kernel_3x(device_dir)
241def _get_block_device_name_with_kernel_3x(device_dir):
242 devs = glob.glob(os.path.join(device_dir, 'block/*'))
243 if len(devs):
244 # prune path to extract the device name
245 return os.path.basename(devs[0])
246 else:
247 return INVALID_DEVICE_NAME
249def _extract_dev(device_dir, procname, host, target):
250 """Returns device name and creates dictionary entry for it"""
251 dev = _extract_dev_name(device_dir)
252 entry = {}
253 entry['procname'] = procname
254 entry['host'] = host
255 entry['target'] = target
256 return (dev, entry)
259def _add_host_parameters_to_adapter(dom, adapter, host_class, host_id,
260 parameters):
261 """Adds additional information about the adapter to the the adapter node"""
262 host_path = os.path.join('/sys/class/', host_class, 'host%s' % (host_id))
263 if os.path.exists(host_path):
264 host_entry = dom.createElement(host_class)
265 adapter.appendChild(host_entry)
266 for parameter in parameters:
267 try:
268 filehandle = open(os.path.join(host_path, parameter))
269 parameter_value = filehandle.read(512).strip()
270 filehandle.close()
271 if parameter_value: 271 ↛ 266line 271 didn't jump to line 266, because the condition on line 271 was never false
272 entry = dom.createElement(parameter)
273 host_entry.appendChild(entry)
274 text_node = dom.createTextNode(parameter_value)
275 entry.appendChild(text_node)
276 except IOError:
277 pass
280def scan(srobj):
281 systemrootID = util.getrootdevID()
282 hbadict = srobj.hbadict
283 hbas = srobj.hbas
284 dom = xml.dom.minidom.Document()
285 e = dom.createElement("Devlist")
286 dom.appendChild(e)
288 if not os.path.exists(DEVPATH):
289 return dom.toprettyxml()
291 devs = srobj.devs
292 vdis = {}
294 for key in hbadict:
295 hba = hbadict[key]
296 path = os.path.join("/dev", key)
297 realpath = path
299 obj = srobj.vdi("")
300 try:
301 obj._query(realpath, devs[realpath][4])
302 except:
303 continue
305 # Test for root dev or existing PBD
306 if len(obj.SCSIid) and len(systemrootID) and util.match_scsiID(obj.SCSIid, systemrootID):
307 util.SMlog("Ignoring root device %s" % realpath)
308 continue
309 elif util.test_SCSIid(srobj.session, None, obj.SCSIid):
310 util.SMlog("SCSIid in use, ignoring (%s)" % obj.SCSIid)
311 continue
312 elif realpath not in devs:
313 continue
315 ids = devs[realpath]
316 obj.adapter = ids[1]
317 obj.channel = ids[2]
318 obj.id = ids[3]
319 obj.lun = ids[4]
320 obj.hba = hba['procname']
321 obj.numpaths = 1
322 if obj.SCSIid in vdis:
323 vdis[obj.SCSIid].numpaths += 1
324 vdis[obj.SCSIid].path += " [%s]" % key
325 else:
326 vdis[obj.SCSIid] = obj
328 for key in vdis: 328 ↛ 329line 328 didn't jump to line 329, because the loop on line 328 never started
329 obj = vdis[key]
330 d = dom.createElement("BlockDevice")
331 e.appendChild(d)
333 for attr in ['path', 'numpaths', 'SCSIid', 'vendor', 'serial', 'size', 'adapter', 'channel', 'id', 'lun', 'hba']:
334 try:
335 aval = getattr(obj, attr)
336 except AttributeError:
337 raise xs_errors.XenError('InvalidArg',
338 opterr='Missing required field [%s]' % attr)
339 entry = dom.createElement(attr)
340 d.appendChild(entry)
341 textnode = dom.createTextNode(str(aval))
342 entry.appendChild(textnode)
344 for key in hbas:
345 a = dom.createElement("Adapter")
346 e.appendChild(a)
347 entry = dom.createElement('host')
348 a.appendChild(entry)
349 textnode = dom.createTextNode(key)
350 entry.appendChild(textnode)
352 entry = dom.createElement('name')
353 a.appendChild(entry)
354 textnode = dom.createTextNode(hbas[key])
355 entry.appendChild(textnode)
357 entry = dom.createElement('manufacturer')
358 a.appendChild(entry)
359 textnode = dom.createTextNode(getManufacturer(hbas[key]))
360 entry.appendChild(textnode)
362 id = key.replace("host", "")
363 entry = dom.createElement('id')
364 a.appendChild(entry)
365 textnode = dom.createTextNode(id)
366 entry.appendChild(textnode)
368 _add_host_parameters_to_adapter(dom, a, 'fc_host', id,
369 ['node_name', 'port_name',
370 'port_state', 'speed',
371 'supported_speeds'])
372 _add_host_parameters_to_adapter(dom, a, 'iscsi_host', id,
373 ['hwaddress', 'initiatorname',
374 'ipaddress', 'port_speed',
375 'port_state'])
377 return dom.toprettyxml()
380def check_iscsi(adapter):
381 ret = False
382 str = "host%s" % adapter
383 try:
384 filename = os.path.join('/sys/class/scsi_host', str, 'proc_name')
385 f = open(filename, 'r')
386 if f.readline().find("iscsi_tcp") != -1:
387 ret = True
388 except:
389 pass
390 return ret
393def match_nonpartitions(s):
394 regex = re.compile("-part[0-9]")
395 if not regex.search(s, 0):
396 return True