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