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

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 

15 

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 

23 

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' 

29 

30DRIVER_BLACKLIST = ['^(s|p|)ata_.*', '^ahci$', '^pdc_adma$', '^iscsi_tcp$', '^usb-storage$'] 

31 

32INVALID_DEVICE_NAME = '' 

33 

34 

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" 

41 

42 

43def update_devs_dict(devs, dev, entry): 

44 if dev != INVALID_DEVICE_NAME: 

45 devs[dev] = entry 

46 

47 

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 

56 

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) 

63 

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) 

106 

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) 

127 

128 dict['devs'] = devs 

129 dict['adt'] = adt 

130 return dict 

131 

132 

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 

147 

148 

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 

182 

183 

184def _parseHostId(str): 

185 id = str.split() 

186 val = "%s:%s:%s" % (id[1], id[3], id[5]) 

187 return val.replace(',', '') 

188 

189 

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 "" 

197 

198 

199def match_blacklist(driver_name): 

200 return re.search("(" + ")|(".join(DRIVER_BLACKLIST) + ")", driver_name) 

201 

202 

203def match_filterstr(filterstr, driver_name): 

204 return re.search("^%s" % filterstr, driver_name) 

205 

206 

207def match_host(s): 

208 return re.search("^host[0-9]", s) 

209 

210 

211def match_rport(s): 

212 regex = re.compile("^rport-*") 

213 return regex.search(s, 0) 

214 

215 

216def match_targets(s): 

217 regex = re.compile("^target[0-9]") 

218 return regex.search(s, 0) 

219 

220 

221def match_phy(s): 

222 regex = re.compile("^phy-*") 

223 return regex.search(s, 0) 

224 

225 

226def match_LUNs(s, prefix): 

227 regex = re.compile("^%s" % prefix) 

228 return regex.search(s, 0) 

229 

230 

231def match_dev(s): 

232 regex = re.compile("^block:") 

233 return regex.search(s, 0) 

234 

235 

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) 

239 

240 

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 

248 

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) 

257 

258 

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 

278 

279 

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) 

287 

288 if not os.path.exists(DEVPATH): 

289 return dom.toprettyxml() 

290 

291 devs = srobj.devs 

292 vdis = {} 

293 

294 for key in hbadict: 

295 hba = hbadict[key] 

296 path = os.path.join("/dev", key) 

297 realpath = path 

298 

299 obj = srobj.vdi("") 

300 try: 

301 obj._query(realpath, devs[realpath][4]) 

302 except: 

303 continue 

304 

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 

314 

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 

327 

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) 

332 

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) 

343 

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) 

351 

352 entry = dom.createElement('name') 

353 a.appendChild(entry) 

354 textnode = dom.createTextNode(hbas[key]) 

355 entry.appendChild(textnode) 

356 

357 entry = dom.createElement('manufacturer') 

358 a.appendChild(entry) 

359 textnode = dom.createTextNode(getManufacturer(hbas[key])) 

360 entry.appendChild(textnode) 

361 

362 id = key.replace("host", "") 

363 entry = dom.createElement('id') 

364 a.appendChild(entry) 

365 textnode = dom.createTextNode(id) 

366 entry.appendChild(textnode) 

367 

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']) 

376 

377 return dom.toprettyxml() 

378 

379 

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 

391 

392 

393def match_nonpartitions(s): 

394 regex = re.compile("-part[0-9]") 

395 if not regex.search(s, 0): 

396 return True