Coverage for libs/sm/core/scsiutil.py: 35%

520 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2026-07-10 07:59 +0000

1# 

2# Copyright (C) Citrix Systems Inc. 

3# 

4# This program is free software; you can redistribute it and/or modify 

5# it under the terms of the GNU Lesser General Public License as published 

6# by the Free Software Foundation; version 2.1 only. 

7# 

8# This program is distributed in the hope that it will be useful, 

9# but WITHOUT ANY WARRANTY; without even the implied warranty of 

10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

11# GNU Lesser General Public License for more details. 

12# 

13# You should have received a copy of the GNU Lesser General Public License 

14# along with this program; if not, write to the Free Software Foundation, Inc., 

15# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 

16# 

17# Miscellaneous scsi utility functions 

18# 

19 

20from sm.core import util 

21import os 

22import re 

23from sm.core import xs_errors 

24import base64 

25import time 

26import errno 

27import glob 

28from sm.core import mpath_cli 

29import traceback 

30 

31PREFIX_LEN = 4 

32SUFFIX_LEN = 12 

33SECTOR_SHIFT = 9 

34SCSI_ID_BIN = '/usr/lib/udev/scsi_id' 

35 

36 

37def gen_hash(st, len): 

38 hs = 0 

39 for i in st: 

40 hs = ord(i) + (hs << 6) + (hs << 16) - hs 

41 return str(hs)[0:len] 

42 

43 

44def gen_uuid_from_serial(iqn, serial): 

45 if len(serial) < SUFFIX_LEN: 

46 raise util.CommandException(1) 

47 prefix = gen_hash(iqn, PREFIX_LEN) 

48 suffix = gen_hash(serial, SUFFIX_LEN) 

49 str = prefix.encode("hex") + suffix.encode("hex") 

50 return str[0:8] + '-' + str[8:12] + '-' + str[12:16] + '-' + str[16:20] + '-' + str[20:32] 

51 

52 

53def gen_serial_from_uuid(iqn, uuid): 

54 str = uuid.replace('-', '') 

55 prefix = gen_hash(iqn, PREFIX_LEN) 

56 if str[0:(PREFIX_LEN * 2)].decode("hex") != prefix: 

57 raise util.CommandException(1) 

58 return str[(PREFIX_LEN * 2):].decode("hex") 

59 

60 

61def getsize(path): 

62 dev = getdev(path) 

63 sysfs = os.path.join('/sys/block', dev, 'size') 

64 size = 0 

65 if os.path.exists(sysfs): 

66 try: 

67 f = open(sysfs, 'r') 

68 size = (int(f.readline()) << SECTOR_SHIFT) 

69 f.close() 

70 except: 

71 pass 

72 return size 

73 

74 

75def getuniqueserial(path): 

76 dev = getdev(path) 

77 try: 

78 cmd = ["md5sum"] 

79 txt = util.pread3(cmd, getSCSIid(path)) 

80 return txt.split(' ')[0] 

81 except: 

82 return '' 

83 

84 

85def gen_uuid_from_string(src_string): 

86 if len(src_string) < (PREFIX_LEN + SUFFIX_LEN): 

87 raise util.CommandException(1) 

88 return (src_string[0:8] + '-' + 

89 src_string[8:12] + '-' + 

90 src_string[12:16] + '-' + 

91 src_string[16:20] + '-' + 

92 src_string[20:32]) 

93 

94 

95def SCSIid_sanitise(str): 

96 text = str.strip() 

97 return re.sub(r"\s+", "_", text) 

98 

99 

100def getSCSIid(path): 

101 """Get the SCSI id of a block device 

102 

103 Input: 

104 path -- (str) path to block device; can be symlink 

105 

106 Return: 

107 scsi_id -- (str) the device's SCSI id 

108 

109 Raise: 

110 util.CommandException 

111 """ 

112 

113 if not path.startswith('/dev/'): 113 ↛ 114line 113 didn't jump to line 114, because the condition on line 113 was never true

114 util.SMlog("getSCSIid: fixing invalid input {}".format(path), 

115 priority=util.LOG_WARNING) 

116 path = '/dev/' + path.lstrip('/') 

117 

118 stdout = util.pread2([SCSI_ID_BIN, '-g', '--device', path]) 

119 

120 return SCSIid_sanitise(stdout) 

121 

122 

123def compareSCSIid_2_6_18(SCSIid, path): 

124 serial = getserial(path) 

125 len_serial = len(serial) 

126 if (len_serial == 0) or (len_serial > (len(SCSIid) - 1)): 

127 return False 

128 list_SCSIid = list(SCSIid) 

129 list_serial = list_SCSIid[1:(len_serial + 1)] 

130 serial_2_6_18 = ''.join(list_serial) 

131 if (serial == serial_2_6_18): 

132 return True 

133 else: 

134 return False 

135 

136 

137def getserial(path): 

138 dev = os.path.join('/dev', getdev(path)) 

139 try: 

140 cmd = ["sginfo", "-s", dev] 

141 text = re.sub(r"\s+", "", util.pread2(cmd)) 

142 except: 

143 raise xs_errors.XenError('EIO', \ 

144 opterr='An error occured querying device serial number [%s]' \ 

145 % dev) 

146 try: 

147 return text.split("'")[1] 

148 except: 

149 return '' 

150 

151 

152def get_serial(path): 

153 """ 

154 Compatibility method 

155 """ 

156 return getserial(path) 

157 

158 

159def getmanufacturer(path): 

160 cmd = ["sginfo", "-M", path] 

161 try: 

162 for line in filter(match_vendor, util.pread2(cmd).split('\n')): 

163 return line.replace(' ', '').split(':')[-1] 

164 except: 

165 return '' 

166 

167 

168def get_vendor(path): 

169 """ 

170 Compatibility method 

171 """ 

172 return getmanufacturer(path) 

173 

174 

175def cacheSCSIidentifiers(): 

176 SCSI = {} 

177 SYS_PATH = "/dev/disk/by-scsibus/*" 

178 for node in glob.glob(SYS_PATH): 

179 if not re.match(r'.*-\d+:\d+:\d+:\d+$', node): 179 ↛ 180line 179 didn't jump to line 180, because the condition on line 179 was never true

180 continue 

181 dev = os.path.realpath(node) 

182 HBTL = os.path.basename(node).split("-")[-1].split(":") 

183 line = "NONE %s %s %s %s 0 %s" % \ 

184 (HBTL[0], HBTL[1], HBTL[2], HBTL[3], dev) 

185 ids = line.split() 

186 SCSI[ids[6]] = ids 

187 return SCSI 

188 

189 

190def scsi_dev_ctrl(ids, cmd): 

191 f = None 

192 for i in range(0, 10): 

193 try: 

194 str = "scsi %s-single-device %s %s %s %s" % \ 

195 (cmd, ids[1], ids[2], ids[3], ids[4]) 

196 util.SMlog(str) 

197 f = open('/proc/scsi/scsi', 'w') 

198 print(str, file=f) 

199 f.close() 

200 return 

201 except IOError as e: 

202 util.SMlog("SCSI_DEV_CTRL: Failure, %s [%d]" % (e.strerror, e.errno)) 

203 if f is not None: 

204 f.close() 

205 f = None 

206 if e.errno == errno.ENXIO: 

207 util.SMlog("Device has disappeared already") 

208 return 

209 time.sleep(6) 

210 continue 

211 raise xs_errors.XenError('EIO', \ 

212 opterr='An error occured during the scsi operation') 

213 

214 

215def getdev(path): 

216 realpath = os.path.realpath(path) 

217 if match_dm(realpath): 

218 newpath = realpath.replace("/dev/mapper/", "/dev/disk/by-id/scsi-") 

219 else: 

220 newpath = path 

221 return os.path.realpath(newpath).split('/')[-1] 

222 

223 

224def get_devices_by_SCSIid(SCSIid): 

225 id_path = os.path.join('/dev/disk/by-scsid', SCSIid) 

226 if not os.path.exists(id_path): 

227 util.SMlog(f'SCSId Path not found for {id_path}') 

228 return [] 

229 

230 devices = os.listdir(id_path) 

231 if 'mapper' in devices: 

232 devices.remove('mapper') 

233 return devices 

234 

235 

236def get_device_provisioning_mode(SCSIid): 

237 devices = get_devices_by_SCSIid(SCSIid) 

238 if not devices: 

239 # Device can't be found so assume full 

240 return 'full' 

241 

242 device = devices[0] 

243 glob_pattern = os.path.join('/sys', 'block', device, 'device', 'scsi_disk', '*', 'provisioning_mode') 

244 mode = 'full' 

245 for file in glob.glob(glob_pattern): 

246 with open(file, 'r') as device_mode: 

247 mode = device_mode.readline().strip() 

248 

249 return mode 

250 

251 

252def device_is_thin_provisioned(SCSIid): 

253 mode = get_device_provisioning_mode(SCSIid) 

254 # Kernel modes are 

255 # [SD_LBP_FULL] = "full", 

256 # [SD_LBP_UNMAP] = "unmap", 

257 # [SD_LBP_WS16] = "writesame_16", 

258 # [SD_LBP_WS10] = "writesame_10", 

259 # [SD_LBP_ZERO] = "writesame_zero", 

260 # [SD_LBP_DISABLE] = "disabled" 

261 return mode in ['unmap', 'writesame_16', 'writesame_10', 'writesame_zero'] 

262 

263 

264def rawdev(dev): 

265 device = getdev(dev) 

266 if device.startswith('dm-') and device[3:].isdigit(): 

267 return device 

268 

269 return re.sub('[0-9]*$', '', device) 

270 

271 

272def getSessionID(path): 

273 for line in filter(match_session, util.listdir(path)): 

274 return line.split('-')[-1] 

275 

276 

277def match_session(s): 

278 regex = re.compile("^SESSIONID-") 

279 return regex.search(s, 0) 

280 

281 

282def match_vendor(s): 

283 regex = re.compile("^Vendor:") 

284 return regex.search(s, 0) 

285 

286 

287def match_dm(s): 

288 regex = re.compile("mapper/") 

289 return regex.search(s, 0) 

290 

291 

292def match_sd(s): 

293 regex = re.compile("/dev/sd") 

294 return regex.search(s, 0) 

295 

296 

297def _isSCSIdev(dev): 

298 if match_dm(dev): 

299 path = dev.replace("/dev/mapper/", "/dev/disk/by-id/scsi-") 

300 else: 

301 path = dev 

302 return match_sd(os.path.realpath(path)) 

303 

304 

305def add_serial_record(session, sr_ref, devstring): 

306 try: 

307 conf = session.xenapi.SR.get_sm_config(sr_ref) 

308 conf['devserial'] = devstring 

309 session.xenapi.SR.set_sm_config(sr_ref, conf) 

310 except: 

311 pass 

312 

313 

314def get_serial_record(session, sr_ref): 

315 try: 

316 conf = session.xenapi.SR.get_sm_config(sr_ref) 

317 return conf['devserial'] 

318 except: 

319 return "" 

320 

321 

322def devlist_to_serialstring(devlist): 

323 serial = '' 

324 for dev in devlist: 

325 try: 

326 devserial = "scsi-%s" % getSCSIid(dev) 

327 if not len(devserial) > 0: 

328 continue 

329 if len(serial): 

330 serial += ',' 

331 serial += devserial 

332 except: 

333 pass 

334 

335 return serial 

336 

337 

338def gen_synthetic_page_data(uuid): 

339 # For generating synthetic page data for non-raw LUNs 

340 # we set the vendor ID to XENSRC 

341 # Note that the Page 80 serial number must be limited 

342 # to 16 characters 

343 page80 = b'' 

344 page80 += b'\x00\x80' 

345 page80 += b'\x00\x12' 

346 page80 += str.encode(uuid[0:16]) 

347 page80 += str.encode(" ") 

348 

349 page83 = b'' 

350 page83 += b'\x00\x83' 

351 page83 += b'\x00\x31' 

352 page83 += b'\x02\x01\x00\x2d' 

353 page83 += str.encode("XENSRC ") 

354 page83 += str.encode(uuid) 

355 page83 += str.encode(" ") 

356 return ["", base64.b64encode(page80).decode(), 

357 base64.b64encode(page83).decode()] 

358 

359 

360def gen_raw_page_data(path): 

361 default = "" 

362 page80 = "" 

363 page83 = "" 

364 try: 

365 cmd = ["sg_inq", "-r", path] 

366 text = util.pread2(cmd) 

367 default = base64.b64encode(text) 

368 

369 cmd = ["sg_inq", "--page=0x80", "-r", path] 

370 text = util.pread2(cmd) 

371 page80 = base64.b64encode(text) 

372 

373 cmd = ["sg_inq", "--page=0x83", "-r", path] 

374 text = util.pread2(cmd) 

375 page83 = base64.b64encode(text) 

376 except: 

377 pass 

378 return [default, page80, page83] 

379 

380 

381def update_XS_SCSIdata(vdi_uuid, data): 

382 # XXX: PR-1255: passing through SCSI data doesn't make sense when 

383 # it will change over storage migration. It also doesn't make sense 

384 # to preserve one array's identity and copy it when a VM moves to 

385 # a new array because the drivers in the VM may attempt to contact 

386 # the original array, fail and bluescreen. 

387 

388 xenstore_data = {} 

389 xenstore_data["vdi-uuid"] = vdi_uuid 

390 if len(data[0]): 390 ↛ 391line 390 didn't jump to line 391, because the condition on line 390 was never true

391 xenstore_data["scsi/0x12/default"] = data[0] 

392 

393 if len(data[1]): 393 ↛ 396line 393 didn't jump to line 396, because the condition on line 393 was never false

394 xenstore_data["scsi/0x12/0x80"] = data[1] 

395 

396 if len(data[2]): 396 ↛ 399line 396 didn't jump to line 399, because the condition on line 396 was never false

397 xenstore_data["scsi/0x12/0x83"] = data[2] 

398 

399 return xenstore_data 

400 

401 

402def rescan(ids, fullrescan=True): 

403 for id in ids: 

404 refresh_HostID(id, fullrescan) 

405 

406 

407def _genHostList(procname): 

408 # loop through and check all adapters 

409 ids = [] 

410 try: 

411 for dir in util.listdir('/sys/class/scsi_host'): 

412 filename = os.path.join('/sys/class/scsi_host', dir, 'proc_name') 

413 if os.path.exists(filename): 

414 f = open(filename, 'r') 

415 if f.readline().find(procname) != -1: 

416 ids.append(dir.replace("host", "")) 

417 f.close() 

418 except: 

419 pass 

420 return ids 

421 

422 

423def _genReverseSCSIidmap(SCSIid, pathname="scsibus"): 

424 util.SMlog("map_by_scsibus: sid=%s" % SCSIid) 

425 

426 devices = [] 

427 for link in glob.glob('/dev/disk/by-%s/%s-*' % (pathname, SCSIid)): 

428 realpath = os.path.realpath(link) 

429 if os.path.exists(realpath): 

430 devices.append(realpath) 

431 return devices 

432 

433 

434def _genReverseSCSidtoLUNidmap(SCSIid): 

435 devices = [] 

436 for link in glob.glob('/dev/disk/by-scsibus/%s-*' % SCSIid): 

437 devices.append(link.split('-')[-1]) 

438 return devices 

439 

440 

441def _dosgscan(): 

442 regex = re.compile(r"([^:]*):\s+scsi([0-9]+)\s+channel=([0-9]+)\s+id=([0-9]+)\s+lun=([0-9]+)") 

443 scan = util.pread2(["/usr/bin/sg_scan"]).split('\n') 

444 sgs = [] 

445 for line in scan: 

446 m = regex.match(line) 

447 if m: 

448 device = m.group(1) 

449 host = m.group(2) 

450 channel = m.group(3) 

451 sid = m.group(4) 

452 lun = m.group(5) 

453 sgs.append([device, host, channel, sid, lun]) 

454 return sgs 

455 

456 

457def refresh_HostID(HostID, fullrescan): 

458 LUNs = glob.glob('/sys/class/scsi_disk/%s*' % HostID) 

459 li = [] 

460 for l in LUNs: 

461 chan = re.sub(":[0-9]*$", '', os.path.basename(l)) 

462 if chan not in li: 462 ↛ 460line 462 didn't jump to line 460, because the condition on line 462 was never false

463 li.append(chan) 

464 

465 if len(li) and not fullrescan: 465 ↛ 466line 465 didn't jump to line 466, because the condition on line 465 was never true

466 for c in li: 

467 if not refresh_scsi_channel(c): 

468 fullrescan = True 

469 

470 if fullrescan: 470 ↛ exitline 470 didn't return from function 'refresh_HostID', because the condition on line 470 was never false

471 util.SMlog("Full rescan of HostID %s" % HostID) 

472 path = '/sys/class/scsi_host/host%s/scan' % HostID 

473 if os.path.exists(path): 473 ↛ 474line 473 didn't jump to line 474, because the condition on line 473 was never true

474 try: 

475 scanstring = "- - -" 

476 f = open(path, 'w') 

477 f.write('%s\n' % scanstring) 

478 f.close() 

479 if len(li): 

480 # Channels already exist, allow some time for 

481 # undiscovered LUNs/channels to appear 

482 time.sleep(2) 

483 except: 

484 pass 

485 # Host Bus scan issued, now try to detect channels 

486 if util.wait_for_path("/sys/class/scsi_disk/%s*" % HostID, 5): 486 ↛ exitline 486 didn't return from function 'refresh_HostID', because the condition on line 486 was never false

487 # At least one LUN is mapped 

488 LUNs = glob.glob('/sys/class/scsi_disk/%s*' % HostID) 

489 li = [] 

490 for l in LUNs: 

491 chan = re.sub(":[0-9]*$", '', os.path.basename(l)) 

492 if chan not in li: 492 ↛ 490line 492 didn't jump to line 490, because the condition on line 492 was never false

493 li.append(chan) 

494 for c in li: 

495 refresh_scsi_channel(c) 

496 

497 

498def refresh_scsi_channel(channel): 

499 DEV_WAIT = 5 

500 util.SMlog("Refreshing channel %s" % channel) 

501 util.wait_for_path('/dev/disk/by-scsibus/*-%s*' % channel, DEV_WAIT) 

502 LUNs = glob.glob('/dev/disk/by-scsibus/*-%s*' % channel) 

503 try: 

504 rootdevs = util.dom0_disks() 

505 except: 

506 util.SMlog("Failed to query root disk, failing operation") 

507 return False 

508 

509 # a) Find a LUN to issue a Query LUNs command 

510 li = [] 

511 Query = False 

512 for lun in LUNs: 

513 try: 

514 hbtl = lun.split('-')[-1] 

515 h = hbtl.split(':') 

516 l = util.pread2(["/usr/bin/sg_luns", "-q", lun]).split('\n') 

517 li = [] 

518 for i in l: 

519 if len(i): 

520 li.append(int(i[0:4], 16)) 

521 util.SMlog("sg_luns query returned %s" % li) 

522 Query = True 

523 break 

524 except: 

525 pass 

526 if not Query: 

527 util.SMlog("Failed to detect or query LUN on Channel %s" % channel) 

528 return False 

529 

530 # b) Remove stale LUNs 

531 current = glob.glob('/dev/disk/by-scsibus/*-%s:%s:%s*' % (h[0], h[1], h[2])) 

532 for cur in current: 

533 lunID = int(cur.split(':')[-1]) 

534 newhbtl = ['', h[0], h[1], h[2], str(lunID)] 

535 if os.path.realpath(cur) in rootdevs: 

536 # Don't touch the rootdev 

537 if lunID in li: 

538 li.remove(lunID) 

539 continue 

540 

541 # Check if LUN is stale, and remove it 

542 if not lunID in li: 

543 util.SMlog("Stale LUN detected. Removing HBTL: %s" % newhbtl) 

544 scsi_dev_ctrl(newhbtl, "remove") 

545 util.wait_for_nopath(cur, DEV_WAIT) 

546 continue 

547 else: 

548 li.remove(lunID) 

549 

550 # Check if the device is still present 

551 if not os.path.exists(cur): 

552 continue 

553 

554 # Query SCSIid, check it matches, if not, re-probe 

555 cur_SCSIid = os.path.basename(cur).split("-%s:%s:%s" % (h[0], h[1], h[2]))[0] 

556 real_SCSIid = getSCSIid(cur) 

557 if cur_SCSIid != real_SCSIid: 

558 util.SMlog("HBTL %s does not match, re-probing" % newhbtl) 

559 scsi_dev_ctrl(newhbtl, "remove") 

560 util.wait_for_nopath(cur, DEV_WAIT) 

561 scsi_dev_ctrl(newhbtl, "add") 

562 util.wait_for_path('/dev/disk/by-scsibus/%s-%s' % (real_SCSIid, hbtl), DEV_WAIT) 

563 pass 

564 

565 # c) Probe for any LUNs that are not present in the system 

566 for l in li: 

567 newhbtl = ['', h[0], h[1], h[2], str(l)] 

568 newhbtlstr = "%s:%s:%s:%s" % (h[0], h[1], h[2], str(l)) 

569 util.SMlog("Probing new HBTL: %s" % newhbtl) 

570 scsi_dev_ctrl(newhbtl, "add") 

571 util.wait_for_path('/dev/disk/by-scsibus/*-%s' % newhbtlstr, DEV_WAIT) 

572 

573 return True 

574 

575 

576def refreshdev(pathlist): 

577 """ 

578 Refresh block devices given a path list 

579 """ 

580 for path in pathlist: 

581 dev = getdev(path) 

582 sysfs = os.path.join('/sys/block', dev, 'device/rescan') 

583 if os.path.exists(sysfs): 

584 try: 

585 with open(sysfs, 'w') as f: 

586 f.write('1') 

587 except: 

588 pass 

589 

590 

591def refresh_lun_size_by_SCSIid(SCSIid): 

592 """ 

593 Refresh all devices for the SCSIid. 

594 Returns True if all known devices and the mapper device are up to date. 

595 """ 

596 

597 def get_primary_device(SCSIid): 

598 mapperdevice = os.path.join('/dev/mapper', SCSIid) 

599 if os.path.exists(mapperdevice): 

600 return mapperdevice 

601 else: 

602 devices = get_devices_by_SCSIid(SCSIid) 

603 if devices: 

604 return devices[0] 

605 else: 

606 return None 

607 

608 def get_outdated_size_devices(currentcapacity, devices): 

609 devicesthatneedrefresh = [] 

610 for device in devices: 

611 if getsize(device) != currentcapacity: 

612 devicesthatneedrefresh.append(device) 

613 return devicesthatneedrefresh 

614 

615 def refresh_devices_if_needed(primarydevice, SCSIid, currentcapacity): 

616 devices = get_devices_by_SCSIid(SCSIid) 

617 if "/dev/mapper/" in primarydevice: 

618 devices = set(devices + mpath_cli.list_paths(SCSIid)) 

619 devicesthatneedrefresh = get_outdated_size_devices(currentcapacity, 

620 devices) 

621 if devicesthatneedrefresh: 

622 # timing out avoids waiting for min(dev_loss_tmo, fast_io_fail_tmo) 

623 # if one or multiple devices don't answer 

624 util.timeout_call(10, refreshdev, devicesthatneedrefresh) 

625 if get_outdated_size_devices(currentcapacity, 

626 devicesthatneedrefresh): 

627 # in this state we shouldn't force resizing the mapper dev 

628 raise util.SMException("Failed to get %s to agree on the " 

629 "current capacity." 

630 % devicesthatneedrefresh) 

631 

632 if any([getsize(x) != sg_readcap(x) for x in devices]): 

633 raise util.SMException(f"Not all devices in {devices} agree on size and capacity") 

634 

635 def refresh_mapper_if_needed(primarydevice, SCSIid, currentcapacity): 

636 if "/dev/mapper/" in primarydevice \ 

637 and get_outdated_size_devices(currentcapacity, [primarydevice]): 

638 mpath_cli.resize_map(SCSIid) 

639 if get_outdated_size_devices(currentcapacity, [primarydevice]): 

640 raise util.SMException("Failed to get the mapper dev to agree " 

641 "on the current capacity.") 

642 

643 try: 

644 primarydevice = get_primary_device(SCSIid) 

645 if primarydevice: 

646 currentcapacity = sg_readcap(primarydevice) 

647 refresh_devices_if_needed(primarydevice, SCSIid, currentcapacity) 

648 refresh_mapper_if_needed(primarydevice, SCSIid, currentcapacity) 

649 else: 

650 util.SMlog("scsiutil.refresh_lun_size_by_SCSIid(%s) could not " 

651 "find any devices for the SCSIid." % SCSIid) 

652 return True 

653 except: 

654 util.logException(f"Error in scsiutil.refresh_lun_size_by_SCSIid({SCSIid}: {traceback.format_exc()})") 

655 return False 

656 

657 

658def refresh_lun_size_by_SCSIid_on_slaves(session, SCSIid): 

659 for slave in util.get_all_slaves(session): 

660 util.SMlog("Calling on-slave.refresh_lun_size_by_SCSIid(%s) on %s." 

661 % (SCSIid, slave)) 

662 resulttext = session.xenapi.host.call_plugin( 

663 slave, 

664 "on-slave", 

665 "refresh_lun_size_by_SCSIid", 

666 {'SCSIid': SCSIid}) 

667 if "True" == resulttext: 

668 util.SMlog("Calling on-slave.refresh_lun_size_by_SCSIid(%s) on" 

669 " %s succeeded." % (SCSIid, slave)) 

670 else: 

671 message = ("Failed in on-slave.refresh_lun_size_by_SCSIid(%s) " 

672 "on %s." % (SCSIid, slave)) 

673 raise util.SMException("Slave %s failed in on-slave.refresh_lun_" 

674 "size_by_SCSIid(%s) " % (slave, SCSIid)) 

675 

676 

677def remove_stale_luns(hostids, lunid, expectedPath, mpath): 

678 try: 

679 for hostid in hostids: 

680 # get all LUNs of the format hostid:x:y:lunid 

681 luns = glob.glob('/dev/disk/by-scsibus/*-%s:*:*:%s' % (hostid, lunid)) 

682 

683 # try to get the scsiid for each of these luns 

684 for lun in luns: 

685 try: 

686 getSCSIid(lun) 

687 # if it works, we have a problem as this device should not 

688 # be present and be valid on this system 

689 util.SMlog("Warning! The lun %s should not be present and" \ 

690 " be valid on this system." % lun) 

691 except: 

692 # Now do the rest. 

693 pass 

694 

695 # get the HBTL 

696 basename = os.path.basename(lun) 

697 hbtl_list = basename.split(':') 

698 hbtl = basename.split('-')[1] 

699 

700 # the first one in scsiid-hostid 

701 hbtl_list[0] = hbtl_list[0].split('-')[1] 

702 

703 expectedPath = expectedPath + '*' + hbtl 

704 if not os.path.exists(expectedPath): 

705 # wait for sometime and check if the expected path exists 

706 # check if a rescan was done outside of this process 

707 time.sleep(2) 

708 

709 if os.path.exists(expectedPath): 

710 # do not remove device, this might be dangerous 

711 util.SMlog("Path %s appeared before checking for " \ 

712 "stale LUNs, ignore this LUN %s." % (expectedPath, lun)) 

713 continue 

714 

715 # remove the scsi device 

716 l = [os.path.realpath(lun), hbtl_list[0], hbtl_list[1], \ 

717 hbtl_list[2], hbtl_list[3]] 

718 scsi_dev_ctrl(l, 'remove') 

719 

720 # if multipath is enabled, do a best effort cleanup 

721 if mpath: 

722 try: 

723 path = os.path.basename(os.path.realpath(lun)) 

724 mpath_cli.remove_path(path) 

725 except Exception as e: 

726 util.SMlog("Failed to remove path %s, ignoring " \ 

727 "exception as path may not be present." % path) 

728 except Exception as e: 

729 util.SMlog("Exception removing stale LUNs, new devices may not come" \ 

730 " up properly! Error: %s" % str(e)) 

731 

732 

733def sg_return_check(rc, logmsg, stderr): 

734 """ 

735 Return true if the return code indicates success, false if it is not success 

736 but is retryable, and raise a util.SMException if it is not retryable using the 

737 logmessage and stderr provided. 

738 

739 In the event that a delay is desirable before a retry, that delay is 

740 baked into this function. 

741 """ 

742 if rc == 0: 

743 return True 

744 if rc == 2: 744 ↛ 746line 744 didn't jump to line 746, because the condition on line 744 was never true

745 # This is "device not ready", so sleep and try again 

746 util.SMlog(f"{logmsg}: not ready") 

747 time.sleep(1) 

748 return False 

749 if rc == 6: 749 ↛ 753line 749 didn't jump to line 753, because the condition on line 749 was never false

750 # retry without a wait for "unit attention". 

751 util.SMlog(f"{logmsg}: unit attention") 

752 return False 

753 if rc == 11: 

754 # Aborted command. Retryable without delay 

755 util.SMlog(f"{logmsg}: command aborted") 

756 return False 

757 if rc == 14: 

758 # Sense miscompare. Retryable without delay until proven otherwise 

759 util.SMlog(f"{logmsg}: sense miscompare") 

760 return False 

761 if rc == 21: 

762 # An error was recovered. This is a success but we would not normally 

763 # expect to see it. Log if it happens. 

764 util.SMlog(f"{logmsg}: recovered error: {stderr}") 

765 return True 

766 if rc == 33: 

767 # Timed out. Retryable without delay 

768 util.SMlog(f"{logmsg}: timed out") 

769 return False 

770 

771 raise util.SMException(f"{logmsg}: RC={rc}, STDERR={stderr}") 

772 

773 

774def sg_readcap(device): 

775 device = os.path.join('/dev', getdev(device)) 

776 readcapcommand = ['/usr/bin/sg_readcap', '-b', device] 

777 attempts = 3 

778 succeeded = False 

779 while attempts > 0: 779 ↛ 786line 779 didn't jump to line 786, because the condition on line 779 was never false

780 attempts -= 1 

781 (rc, stdout, stderr) = util.doexec(readcapcommand) 

782 if sg_return_check(rc, f"scsiutil.sg_readcap({device})", stderr): 

783 succeeded = True 

784 break 

785 

786 if not succeeded: 786 ↛ 787line 786 didn't jump to line 787, because the condition on line 786 was never true

787 raise util.SMException(f"scsiutil.sg_readcap({device}): too many failures") 

788 

789 match = re.search('(^|.*\n)(0x[0-9a-fA-F]+) (0x[0-9a-fA-F]+)\n$', stdout) 

790 if not match: 790 ↛ 791line 790 didn't jump to line 791, because the condition on line 790 was never true

791 raise util.SMException("scsiutil.sg_readcap(%s) failed to parse: %s" 

792 % (device, stdout)) 

793 (blockcount, blocksize) = match.group(2, 3) 

794 return (int(blockcount, 0) * int(blocksize, 0))