1 |
charliebrady |
1.1 |
|
2 |
|
|
import re |
3 |
|
|
|
4 |
|
|
from ..udev import * |
5 |
|
|
from flags import flags |
6 |
|
|
import iutil |
7 |
|
|
import logging |
8 |
|
|
|
9 |
|
|
log = logging.getLogger("storage") |
10 |
|
|
|
11 |
|
|
def _filter_out_mpath_devices(devices): |
12 |
|
|
retval = [] |
13 |
|
|
for d in devices: |
14 |
|
|
if udev_device_is_dm_mpath(d): |
15 |
|
|
log.debug("filtering out coalesced mpath device: %s" % d['name']) |
16 |
|
|
else: |
17 |
|
|
retval.append(d) |
18 |
|
|
return retval |
19 |
|
|
|
20 |
|
|
def _filter_out_mpath_partitions(devices, multipaths): |
21 |
|
|
""" |
22 |
|
|
Use serial numbers of the multipath members to filter devices from the |
23 |
|
|
devices list. The returned list will only partitions that are NOT partitions |
24 |
|
|
of the multipath members. |
25 |
|
|
""" |
26 |
|
|
serials = set(udev_device_get_serial(d) |
27 |
|
|
for mpath_members in multipaths for d in mpath_members) |
28 |
|
|
retval = [] |
29 |
|
|
for d in devices: |
30 |
|
|
if udev_device_get_serial(d) in serials: |
31 |
|
|
log.debug("filtering out mpath partition: %s" % d['name']) |
32 |
|
|
else: |
33 |
|
|
retval.append(d) |
34 |
|
|
return retval |
35 |
|
|
|
36 |
|
|
def parseMultipathOutput(output): |
37 |
|
|
""" |
38 |
|
|
Parse output from "multipath -d" or "multipath -ll" and form a topology. |
39 |
|
|
|
40 |
|
|
Returns a dictionary: |
41 |
|
|
{'mpatha':['sdb','sdc'], 'mpathb': ['sdd', 'sde'], ... } |
42 |
|
|
|
43 |
|
|
The 'multipath -d' output looks like: |
44 |
|
|
create: mpathc (1ATA ST3120026AS 5M) undef ATA,ST3120026AS |
45 |
|
|
size=112G features='0' hwhandler='0' wp=undef |
46 |
|
|
`-+- policy='round-robin 0' prio=1 status=undef |
47 |
|
|
`- 2:0:0:0 sda 8:0 undef ready running |
48 |
|
|
create: mpathb (36006016092d21800703762872c60db11) undef DGC,RAID 5 |
49 |
|
|
size=10G features='1 queue_if_no_path' hwhandler='1 emc' wp=undef |
50 |
|
|
`-+- policy='round-robin 0' prio=2 status=undef |
51 |
|
|
|- 6:0:0:0 sdb 8:16 undef ready running |
52 |
|
|
`- 7:0:0:0 sdc 8:32 undef ready running |
53 |
|
|
create: mpatha (36001438005deb4710000500000270000) dm-0 HP,HSV400 |
54 |
|
|
size=20G features='0' hwhandler='0' wp=rw |
55 |
|
|
|-+- policy='round-robin 0' prio=-1 status=active |
56 |
|
|
| |- 7:0:0:1 sda 8:0 active undef running |
57 |
|
|
| `- 7:0:1:1 sdb 8:16 active undef running |
58 |
|
|
`-+- policy='round-robin 0' prio=-1 status=enabled |
59 |
|
|
|- 7:0:2:1 sdc 8:32 active undef running |
60 |
|
|
`- 7:0:3:1 sdd 8:48 active undef running |
61 |
|
|
|
62 |
|
|
(In anaconda, the first one there won't be included because we blacklist |
63 |
|
|
"ATA" as a vendor.) |
64 |
|
|
|
65 |
|
|
The 'multipath -ll' output looks like (notice the missing 'create' before |
66 |
|
|
'mpatha'): |
67 |
|
|
|
68 |
|
|
mpatha (3600a0b800067fcc9000001694b557dd1) dm-0 IBM,1726-4xx FAStT |
69 |
|
|
size=360G features='0' hwhandler='1 rdac' wp=rw |
70 |
|
|
`-+- policy='round-robin 0' prio=3 status=active |
71 |
|
|
|- 2:0:0:0 sda 8:0 active ready running |
72 |
|
|
`- 3:0:0:0 sdb 8:16 active ready running |
73 |
|
|
|
74 |
|
|
""" |
75 |
|
|
mpaths = {} |
76 |
|
|
if output is None: |
77 |
|
|
return mpaths |
78 |
|
|
|
79 |
|
|
action = None |
80 |
|
|
name = None |
81 |
|
|
devices = [] |
82 |
|
|
|
83 |
|
|
policy = re.compile('^[|+` -]+policy') |
84 |
|
|
device = re.compile('^[|+` -]+[0-9]+:[0-9]+:[0-9]+:[0-9]+ +([a-zA-Z0-9!/]+)') |
85 |
|
|
create = re.compile('^(?:([a-z]+): )?(mpath\w+|[a-f0-9]+)') |
86 |
|
|
|
87 |
|
|
lines = output.split('\n') |
88 |
|
|
for line in lines: |
89 |
|
|
pmatch = policy.match(line) |
90 |
|
|
dmatch = device.match(line) |
91 |
|
|
cmatch = create.match(line) |
92 |
|
|
lexemes = line.split() |
93 |
|
|
if not lexemes: |
94 |
|
|
break |
95 |
|
|
if cmatch and cmatch.group(2): |
96 |
|
|
if name and devices and action in (None, 'create'): |
97 |
|
|
mpaths[name] = devices |
98 |
|
|
action = cmatch.group(1) |
99 |
|
|
name = cmatch.group(2) |
100 |
|
|
devices = [] |
101 |
|
|
elif lexemes[0].startswith('size='): |
102 |
|
|
pass |
103 |
|
|
elif pmatch: |
104 |
|
|
pass |
105 |
|
|
elif dmatch: |
106 |
|
|
devices.append(dmatch.groups()[0].replace('!','/')) |
107 |
|
|
|
108 |
|
|
if name and devices and action in (None, 'create'): |
109 |
|
|
mpaths[name] = devices |
110 |
|
|
|
111 |
|
|
return mpaths |
112 |
|
|
|
113 |
|
|
def identifyMultipaths(devices): |
114 |
|
|
""" |
115 |
|
|
This function does a couple of things: |
116 |
|
|
1) identifies multipath disks, |
117 |
|
|
2) sets their ID_FS_TYPE to multipath_member, |
118 |
|
|
3) removes the individual members of an mpath's partitions as well as any |
119 |
|
|
coalesced multipath devices: |
120 |
|
|
|
121 |
|
|
The return value is a tuple of 3 lists, the first list containing all |
122 |
|
|
devices except multipath members and partitions, the second list containing |
123 |
|
|
only the multipath members and the last list only partitions. Specifically, |
124 |
|
|
the second list is empty if there are no multipath devices found. |
125 |
|
|
|
126 |
|
|
sample input: |
127 |
|
|
[sr0, sda, sda1, sdb, sdb1, sdb2, sdc, sdc1, sdd, sdd1, sdd2, dm-0] |
128 |
|
|
where: |
129 |
|
|
[sdb, sdc] is a multipath pair |
130 |
|
|
dm-0 is a mutliapth device already coalesced from [sdb, sdc] |
131 |
|
|
|
132 |
|
|
sample output: |
133 |
|
|
[sda, sdd], [[sdb, sdc]], [sr0, sda1, sdd1, sdd2]] |
134 |
|
|
""" |
135 |
|
|
log.info("devices to scan for multipath: %s" % [d['name'] for d in devices]) |
136 |
|
|
|
137 |
|
|
with open("/etc/multipath.conf") as conf: |
138 |
|
|
log.debug("/etc/multipath.conf contents:") |
139 |
|
|
map(lambda line: log.debug(line.rstrip()), conf) |
140 |
|
|
log.debug("(end of /etc/multipath.conf)") |
141 |
|
|
|
142 |
|
|
topology = parseMultipathOutput( |
143 |
|
|
iutil.execWithCapture("multipath", ["-d",])) |
144 |
|
|
topology.update(parseMultipathOutput( |
145 |
|
|
iutil.execWithCapture("multipath", ["-ll",]))) |
146 |
|
|
# find the devices that aren't in topology, and add them into it... |
147 |
|
|
topodevs = reduce(lambda x,y: x.union(y), topology.values(), set()) |
148 |
|
|
for name in set([d['name'] for d in devices]).difference(topodevs): |
149 |
|
|
topology[name] = [name] |
150 |
|
|
|
151 |
|
|
devmap = {} |
152 |
|
|
non_disk_devices = {} |
153 |
|
|
for d in devices: |
154 |
|
|
if not udev_device_is_disk(d): |
155 |
|
|
non_disk_devices[d['name']] = d |
156 |
|
|
log.info("adding %s to non_disk_device list" % (d['name'],)) |
157 |
|
|
continue |
158 |
|
|
devmap[d['name']] = d |
159 |
|
|
|
160 |
|
|
singlepath_disks = [] |
161 |
|
|
multipaths = [] |
162 |
|
|
|
163 |
|
|
for name, disks in topology.items(): |
164 |
|
|
if len(disks) == 1: |
165 |
|
|
if not non_disk_devices.has_key(disks[0]): |
166 |
|
|
log.info("adding %s to singlepath_disks" % (disks[0],)) |
167 |
|
|
singlepath_disks.append(devmap[disks[0]]) |
168 |
|
|
else: |
169 |
|
|
# some usb cardreaders use multiple lun's (for different slots) |
170 |
|
|
# and report a fake disk serial which is the same for all the |
171 |
|
|
# lun's (#517603) |
172 |
|
|
all_usb = True |
173 |
|
|
# see if we've got any non-disk devices on our mpath list. |
174 |
|
|
# If so, they're probably false-positives. |
175 |
|
|
non_disks = False |
176 |
|
|
for disk in disks: |
177 |
|
|
d = devmap[disk] |
178 |
|
|
if d.get("ID_USB_DRIVER") != "usb-storage": |
179 |
|
|
all_usb = False |
180 |
|
|
if (not devmap.has_key(disk)) and non_disk_devices.has_key(disk): |
181 |
|
|
log.warning("non-disk device %s is part of an mpath" % |
182 |
|
|
(disk,)) |
183 |
|
|
non_disks = True |
184 |
|
|
|
185 |
|
|
if all_usb: |
186 |
|
|
log.info("adding multi lun usb mass storage device to singlepath_disks: %s" % |
187 |
|
|
(disks,)) |
188 |
|
|
singlepath_disks.extend([devmap[d] for d in disks]) |
189 |
|
|
continue |
190 |
|
|
|
191 |
|
|
if non_disks: |
192 |
|
|
for disk in disks: |
193 |
|
|
if devmap.has_key(disk): |
194 |
|
|
del devmap[disk] |
195 |
|
|
if topology.has_key(disk): |
196 |
|
|
del topology[disk] |
197 |
|
|
continue |
198 |
|
|
|
199 |
|
|
log.info("found multipath set: %s" % (disks,)) |
200 |
|
|
for disk in disks: |
201 |
|
|
d = devmap[disk] |
202 |
|
|
log.info("adding %s to multipath_disks" % (disk,)) |
203 |
|
|
d["ID_FS_TYPE"] = "multipath_member" |
204 |
|
|
d["ID_MPATH_NAME"] = name |
205 |
|
|
|
206 |
|
|
multipaths.append([devmap[d] for d in disks]) |
207 |
|
|
|
208 |
|
|
# singlepaths and partitions should not contain multipath devices: |
209 |
|
|
singlepath_disks = _filter_out_mpath_devices(singlepath_disks) |
210 |
|
|
partition_devices = _filter_out_mpath_partitions( |
211 |
|
|
non_disk_devices.values(), multipaths) |
212 |
|
|
|
213 |
|
|
# this is the list of devices we want to keep from the original |
214 |
|
|
# device list, but we want to maintain its original order. |
215 |
|
|
singlepath_disks = filter(lambda d: d in devices, singlepath_disks) |
216 |
|
|
#multipaths = filter(lambda d: d in devices, multipaths) |
217 |
|
|
partition_devices = filter(lambda d: d in devices, partition_devices) |
218 |
|
|
|
219 |
|
|
mpathStr = "[" |
220 |
|
|
for mpath in multipaths: |
221 |
|
|
mpathStr += str([d['name'] for d in mpath]) |
222 |
|
|
mpathStr += "]" |
223 |
|
|
|
224 |
|
|
s = "(%s, %s, %s)" % ([d['name'] for d in singlepath_disks], \ |
225 |
|
|
mpathStr, \ |
226 |
|
|
[d['name'] for d in partition_devices]) |
227 |
|
|
log.info("devices post multipath scan: %s" % s) |
228 |
|
|
return (singlepath_disks, multipaths, partition_devices) |
229 |
|
|
|
230 |
|
|
class MultipathConfigWriter: |
231 |
|
|
def __init__(self): |
232 |
|
|
self.blacklist_devices = [] |
233 |
|
|
self.mpaths = [] |
234 |
|
|
|
235 |
|
|
def addBlacklistDevice(self, device): |
236 |
|
|
self.blacklist_devices.append(device) |
237 |
|
|
|
238 |
|
|
def addMultipathDevice(self, mpath): |
239 |
|
|
self.mpaths.append(mpath) |
240 |
|
|
|
241 |
|
|
def write(self, friendly_names): |
242 |
|
|
# if you add anything here, be sure and also add it to anaconda's |
243 |
|
|
# multipath.conf |
244 |
|
|
ret = '' |
245 |
|
|
ret += """\ |
246 |
|
|
# multipath.conf written by anaconda |
247 |
|
|
|
248 |
|
|
defaults { |
249 |
|
|
user_friendly_names %(friendly_names)s |
250 |
|
|
} |
251 |
|
|
blacklist { |
252 |
|
|
devnode "^(ram|raw|loop|fd|md|dm-|sr|scd|st)[0-9]*" |
253 |
|
|
devnode "^hd[a-z]" |
254 |
|
|
devnode "^dcssblk[0-9]*" |
255 |
|
|
device { |
256 |
|
|
vendor "DGC" |
257 |
|
|
product "LUNZ" |
258 |
|
|
} |
259 |
|
|
device { |
260 |
|
|
vendor "IBM" |
261 |
|
|
product "S/390.*" |
262 |
|
|
} |
263 |
|
|
# don't count normal SATA devices as multipaths |
264 |
|
|
device { |
265 |
|
|
vendor "ATA" |
266 |
|
|
} |
267 |
|
|
# don't count 3ware devices as multipaths |
268 |
|
|
device { |
269 |
|
|
vendor "3ware" |
270 |
|
|
} |
271 |
|
|
device { |
272 |
|
|
vendor "AMCC" |
273 |
|
|
} |
274 |
|
|
# nor highpoint devices |
275 |
|
|
device { |
276 |
|
|
vendor "HPT" |
277 |
|
|
} |
278 |
|
|
""" % {'friendly_names' : "yes" if friendly_names else "no"} |
279 |
|
|
for device in self.blacklist_devices: |
280 |
|
|
if device.serial: |
281 |
|
|
ret += '\twwid "%s"\n' % device.serial |
282 |
|
|
elif device.vendor and device.model: |
283 |
|
|
ret += '\tdevice {\n' |
284 |
|
|
ret += '\t\tvendor %s\n' % device.vendor |
285 |
|
|
ret += '\t\tproduct %s\n' % device.model |
286 |
|
|
ret += '\t}\n' |
287 |
|
|
if self.mpaths: |
288 |
|
|
ret += '\twwid "*"\n' |
289 |
|
|
ret += '}\n' |
290 |
|
|
ret += 'blacklist_exceptions {\n' |
291 |
|
|
for mpath in self.mpaths: |
292 |
|
|
for k,v in mpath.config.items(): |
293 |
|
|
if k == 'wwid': |
294 |
|
|
ret += '\twwid "%s"\n' % v |
295 |
|
|
ret += '}\n' |
296 |
|
|
ret += 'multipaths {\n' |
297 |
|
|
for mpath in self.mpaths: |
298 |
|
|
ret += '\tmultipath {\n' |
299 |
|
|
for k,v in mpath.config.items(): |
300 |
|
|
if k == 'wwid': |
301 |
|
|
ret += '\t\twwid "%s"\n' % v |
302 |
|
|
else: |
303 |
|
|
ret += '\t\t%s %s\n' % (k, v) |
304 |
|
|
ret += '\t}\n' |
305 |
|
|
ret += '}\n' |
306 |
|
|
|
307 |
|
|
return ret |
308 |
|
|
|
309 |
|
|
def writeMultipathConf(writer=None, friendly_names=True): |
310 |
|
|
if not flags.mpath: |
311 |
|
|
# not writing out a multipath.conf will effectively blacklist all mpaths |
312 |
|
|
# which will prevent any of them from being activated during install |
313 |
|
|
return |
314 |
|
|
|
315 |
|
|
if writer is None: |
316 |
|
|
writer = MultipathConfigWriter() |
317 |
|
|
|
318 |
|
|
cfg = writer.write(friendly_names=friendly_names) |
319 |
|
|
with open("/etc/multipath.conf", "w+") as mpath_cfg: |
320 |
|
|
mpath_cfg.write(cfg) |
321 |
|
|
|
322 |
|
|
def flush_mpaths(): |
323 |
|
|
iutil.execWithRedirect("multipath", ["-F"]) |
324 |
|
|
check_output = iutil.execWithCapture("multipath", ["-ll"]).strip() |
325 |
|
|
if check_output: |
326 |
|
|
log.error("multipath: some devices could not be flushed") |