Coverage for sm/core/scsiutil.py : 55%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""Fake scsiutil module"""
3import glob
4import os
5import re
6import time
7import traceback
9from . import f_exceptions
10from . import util
11from . import mpath_cli
13SECTOR_SHIFT = 9
15def match_dm(s):
16 regex = re.compile("mapper/")
17 return regex.search(s, 0)
20def getdev(path):
21 realpath = os.path.realpath(path)
22 if match_dm(realpath):
23 newpath = realpath.replace("/dev/mapper/","/dev/disk/by-id/scsi-")
24 else:
25 newpath = path
26 return os.path.realpath(newpath).split('/')[-1]
29def getsize(path):
30 dev = getdev(path)
31 sysfs = os.path.join('/sys/block',dev,'size')
32 size = 0
33 if os.path.exists(sysfs):
34 try:
35 f=open(sysfs, 'r')
36 size = (int(f.readline()) << SECTOR_SHIFT)
37 f.close()
38 except:
39 pass
40 return size
43def get_vendor(device_path):
44 vendor_command = ['sginfo', '-M', device_path]
45 (rc, stdout, _) = util.doexec(vendor_command)
46 if rc != 0:
47 raise f_exceptions.XenError(
48 'SCSIRead', 'sginfo failed {}, {}'.format(device_path, rc))
50 match = re.search(r'(^|.*\n)Vendor:\s+(.*)[$|\n]', stdout)
51 if not match:
52 raise f_exceptions.XenError(
53 'SCSIRead', 'sginfo no vendor match {}'.format(device_path))
54 return match.group(2).strip()
57def get_serial(device_path):
58 serial_command = ['sginfo', '-s', device_path]
59 (rc, stdout, _) = util.doexec(serial_command)
60 if rc != 0:
61 raise f_exceptions.XenError(
62 'SCSIRead', 'sginfo failed {}, {}'.format(device_path, rc))
64 match = re.search(r'(^|.*\n)Serial Number\s+\'([^\']*)\'[$|\n]', stdout)
65 if not match:
66 raise f_exceptions.XenError(
67 'SCSIRead', 'sginfo no serial match {}'.format(device_path))
68 return match.group(2).strip()
71def get_devices_by_SCSIid(SCSIid):
72 id_path = os.path.join('/dev/disk/by-scsid', SCSIid)
73 if not os.path.exists(id_path):
74 util.SMlog(f'SCSId Path not found for {id_path}')
75 return []
77 devices = os.listdir(id_path)
78 if 'mapper' in devices:
79 devices.remove('mapper')
80 return devices
83def get_device_provisioning_mode(SCSIid):
84 devices = get_devices_by_SCSIid(SCSIid)
85 if not devices:
86 # Device can't be found so assume full
87 return 'full'
89 device = devices[0]
90 glob_pattern = os.path.join('/sys', 'block', device, 'device', 'scsi_disk', '*', 'provisioning_mode')
91 mode = 'full'
92 for file in glob.glob(glob_pattern):
93 with open(file, 'r') as device_mode:
94 mode = device_mode.readline().strip()
96 return mode
99def device_is_thin_provisioned(SCSIid):
100 mode = get_device_provisioning_mode(SCSIid)
101 # Kernel modes are
102 # [SD_LBP_FULL] = "full",
103 # [SD_LBP_UNMAP] = "unmap",
104 # [SD_LBP_WS16] = "writesame_16",
105 # [SD_LBP_WS10] = "writesame_10",
106 # [SD_LBP_ZERO] = "writesame_zero",
107 # [SD_LBP_DISABLE] = "disabled"
108 return mode in ['unmap', 'writesame_16', 'writesame_10', 'writesame_zero']
111def sg_return_check(rc, dbg, logmsg, stderr):
112 """
113 Return true if the return code indicates success, false if it is not
114 success but is retryable, and raise a util.SMException if it is not
115 retryable using the logmessage and stderr provided.
117 In the event that a delay is desirable before a retry, that delay is
118 baked into this function.
119 """
120 if rc == 0:
121 return True
122 if rc == 2:
123 # This is "device not ready", so sleep and try again
124 util.SMlog(f"{logmsg}: not ready")
125 time.sleep(1)
126 return False
127 if rc == 6:
128 # retry without a wait for "unit attention".
129 util.SMlog(f"{logmsg}: unit attention")
130 return False
131 if rc == 11:
132 # Aborted command. Retryable without delay
133 util.SMlog(f"{logmsg}: command aborted")
134 return False
135 if rc == 14:
136 # Sense miscompare. Retryable without delay until proven otherwise
137 util.SMlog(f"{logmsg}: sense miscompare")
138 return False
139 if rc == 21:
140 # An error was recovered. This is a success but we would not normally
141 # expect to see it. Log if it happens.
142 util.SMlog(f"{logmsg}: recovered error: {stderr}")
143 return True
144 if rc == 33:
145 # Timed out. Retryable without delay
146 util.SMlog(f"{logmsg}: timed out")
147 return False
149 raise f_exceptions.XenError(dbg, f"{logmsg}: RC={rc}, STDERR={stderr}")
152def sg_readcap(device):
153 device = os.path.join('/dev', getdev(device))
154 readcapcommand = ['/usr/bin/sg_readcap', '-b', device]
155 attempts = 3
156 succeeded = False
157 while attempts > 0:
158 attempts -= 1
159 (rc, stdout, stderr) = util.doexec(readcapcommand)
160 if sg_return_check(
161 rc, "SCSIRead", f"scsiutil.sg_readcap({device})", stderr):
162 succeeded = True
163 break
165 if not succeeded:
166 raise f_exceptions.XenError(
167 "SCSIRead", f"scsiutil.sg_readcap({device}): too many failures")
169 match = re.search('(^|.*\n)(0x[0-9a-fA-F]+) (0x[0-9a-fA-F]+)\n$', stdout)
170 if not match: 170 ↛ 171line 170 didn't jump to line 171, because the condition on line 170 was never true
171 raise f_exceptions.XenError("SCSIRead",
172 "scsiutil.sg_readcap(%s) failed to parse: %s"
173 % (device, stdout))
174 (blockcount, blocksize) = match.group(2, 3)
175 return (int(blockcount, 0) * int(blocksize, 0))
178def refreshdev(pathlist):
179 """
180 Refresh block devices given a path list
181 """
182 for path in pathlist:
183 dev = getdev(path)
184 util.SMlog("Refreshing size for device %s (%s)" % (path, dev))
185 sysfs = os.path.join('/sys/block',dev,'device/rescan')
186 if os.path.exists(sysfs):
187 try:
188 f = os.open(sysfs, os.O_WRONLY)
189 os.write(f, b'1')
190 os.close(f)
191 except:
192 pass
195def refresh_lun_size_by_SCSIid(SCSIid):
196 """
197 Refresh all devices for the SCSIid.
198 Returns True if all known devices and the mapper device are up to date.
199 """
200 def get_primary_device(SCSIid):
201 mapperdevice = os.path.join('/dev/mapper', SCSIid)
202 if os.path.exists(mapperdevice):
203 return mapperdevice
204 else:
205 devices = get_devices_by_SCSIid(SCSIid)
206 if devices:
207 return devices[0]
208 else:
209 return None
211 def get_outdated_size_devices(currentcapacity, devices):
212 devicesthatneedrefresh = []
213 for device in devices:
214 if getsize(device) != currentcapacity:
215 devicesthatneedrefresh.append(device)
216 return devicesthatneedrefresh
218 def refresh_devices_if_needed(primarydevice, SCSIid, currentcapacity):
219 devices = get_devices_by_SCSIid(SCSIid)
220 if "/dev/mapper/" in primarydevice:
221 devices = set(devices + mpath_cli.list_paths(SCSIid))
222 devicesthatneedrefresh = get_outdated_size_devices(currentcapacity,
223 devices)
224 if devicesthatneedrefresh:
225 # timing out avoids waiting for min(dev_loss_tmo, fast_io_fail_tmo)
226 # if one or multiple devices don't answer
227 util.timeout_call(10, refreshdev, devicesthatneedrefresh)
228 if get_outdated_size_devices(currentcapacity,
229 devicesthatneedrefresh):
230 # in this state we shouldn't force resizing the mapper dev
231 raise f_exceptions.XenError("DeviceResize",
232 "Failed to get %s to agree on the "
233 "current capacity."
234 % devicesthatneedrefresh)
236 def refresh_mapper_if_needed(primarydevice, SCSIid, currentcapacity):
237 if ("/dev/mapper/" in primarydevice
238 and get_outdated_size_devices(currentcapacity, [primarydevice])):
239 util.SMlog("Resizing multpath map for device %s" % SCSIid)
240 mpath_cli.resize_map(SCSIid)
241 if get_outdated_size_devices(currentcapacity, [primarydevice]):
242 raise f_exceptions.XenError("DeviceResize",
243 "Failed to get the mapper dev to "
244 "agree on the current capacity.")
246 try:
247 primarydevice = get_primary_device(SCSIid)
248 if primarydevice:
249 currentcapacity = sg_readcap(primarydevice)
250 refresh_devices_if_needed(primarydevice, SCSIid, currentcapacity)
251 refresh_mapper_if_needed(primarydevice, SCSIid, currentcapacity)
252 else:
253 util.SMlog("scsiutil.refresh_lun_size_by_SCSIid(%s) could not "
254 "find any devices for the SCSIid." % SCSIid)
255 return True
256 except:
257 util.SMlog("Error in scsiutil.refresh_lun_size_by_SCSIid(%s: %s)" % (SCSIid, traceback.format_exc()))
258 return False