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
« 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#
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
31PREFIX_LEN = 4
32SUFFIX_LEN = 12
33SECTOR_SHIFT = 9
34SCSI_ID_BIN = '/usr/lib/udev/scsi_id'
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]
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]
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")
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
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 ''
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])
95def SCSIid_sanitise(str):
96 text = str.strip()
97 return re.sub(r"\s+", "_", text)
100def getSCSIid(path):
101 """Get the SCSI id of a block device
103 Input:
104 path -- (str) path to block device; can be symlink
106 Return:
107 scsi_id -- (str) the device's SCSI id
109 Raise:
110 util.CommandException
111 """
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('/')
118 stdout = util.pread2([SCSI_ID_BIN, '-g', '--device', path])
120 return SCSIid_sanitise(stdout)
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
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 ''
152def get_serial(path):
153 """
154 Compatibility method
155 """
156 return getserial(path)
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 ''
168def get_vendor(path):
169 """
170 Compatibility method
171 """
172 return getmanufacturer(path)
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
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')
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]
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 []
230 devices = os.listdir(id_path)
231 if 'mapper' in devices:
232 devices.remove('mapper')
233 return devices
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'
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()
249 return mode
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']
264def rawdev(dev):
265 device = getdev(dev)
266 if device.startswith('dm-') and device[3:].isdigit():
267 return device
269 return re.sub('[0-9]*$', '', device)
272def getSessionID(path):
273 for line in filter(match_session, util.listdir(path)):
274 return line.split('-')[-1]
277def match_session(s):
278 regex = re.compile("^SESSIONID-")
279 return regex.search(s, 0)
282def match_vendor(s):
283 regex = re.compile("^Vendor:")
284 return regex.search(s, 0)
287def match_dm(s):
288 regex = re.compile("mapper/")
289 return regex.search(s, 0)
292def match_sd(s):
293 regex = re.compile("/dev/sd")
294 return regex.search(s, 0)
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))
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
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 ""
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
335 return serial
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(" ")
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()]
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)
369 cmd = ["sg_inq", "--page=0x80", "-r", path]
370 text = util.pread2(cmd)
371 page80 = base64.b64encode(text)
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]
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.
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]
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]
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]
399 return xenstore_data
402def rescan(ids, fullrescan=True):
403 for id in ids:
404 refresh_HostID(id, fullrescan)
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
423def _genReverseSCSIidmap(SCSIid, pathname="scsibus"):
424 util.SMlog("map_by_scsibus: sid=%s" % SCSIid)
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
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
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
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)
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
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)
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
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
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
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)
550 # Check if the device is still present
551 if not os.path.exists(cur):
552 continue
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
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)
573 return True
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
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 """
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
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
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)
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")
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.")
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
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))
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))
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
695 # get the HBTL
696 basename = os.path.basename(lun)
697 hbtl_list = basename.split(':')
698 hbtl = basename.split('-')[1]
700 # the first one in scsiid-hostid
701 hbtl_list[0] = hbtl_list[0].split('-')[1]
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)
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
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')
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))
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.
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
771 raise util.SMException(f"{logmsg}: RC={rc}, STDERR={stderr}")
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
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")
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))