81
81
}
82
82
}
83
83
84
+ MULTIPATH_ENABLED = None
85
+
84
86
85
87
def _get_device_info (dev , devclass , field ):
86
88
"""Get the device info according to device class and field."""
@@ -138,6 +140,36 @@ def _load_ipmi_modules():
138
140
il_utils .try_execute ('modprobe' , 'ipmi_si' )
139
141
140
142
143
+ def _load_multipath_modules ():
144
+ """Load multipath modules
145
+
146
+ This is required to be able to collect multipath information.
147
+
148
+ Two separate paths exist, one with a helper utility for Centos/RHEL
149
+ and another which is just load the modules, and trust multipathd
150
+ will do the needful.
151
+ """
152
+ if (os .path .isfile ('/usr/sbin/mpathconf' )
153
+ and not os .path .isfile ('/etc/multipath.conf' )):
154
+ # For Centos/Rhel/Etc which uses mpathconf, this does
155
+ # a couple different things, including configuration generation...
156
+ # which is not *really* required.. at least *shouldn't* be.
157
+ # WARNING(TheJulia): This command explicitly replaces local
158
+ # configuration.
159
+ il_utils .try_execute ('/usr/sbin/mpathconf' , '--enable' ,
160
+ '--find_multipaths' , 'yes' ,
161
+ '--with_module' , 'y' ,
162
+ '--with_multipathd' , 'y' )
163
+ else :
164
+ # Ensure modules are loaded. Configuration is not required
165
+ # and implied based upon compiled in defaults.
166
+ # NOTE(TheJulia): Debian/Ubuntu specifically just document
167
+ # using `multipath -t` output to start a new configuration
168
+ # file, if needed.
169
+ il_utils .try_execute ('modprobe' , 'dm_multipath' )
170
+ il_utils .try_execute ('modprobe' , 'multipath' )
171
+
172
+
141
173
def _check_for_iscsi ():
142
174
"""Connect iSCSI shared connected via iBFT or OF.
143
175
@@ -181,6 +213,84 @@ def _get_md_uuid(raid_device):
181
213
return match .group (1 )
182
214
183
215
216
+ def _enable_multipath ():
217
+ """Initialize multipath IO if possible.
218
+
219
+ :returns: True if the multipathd daemon and multipath command to enumerate
220
+ devices was scucessfully able to be called.
221
+ """
222
+ try :
223
+ _load_multipath_modules ()
224
+ # This might not work, ideally it *should* already be running...
225
+ # NOTE(TheJulia): Testing locally, a prior running multipathd, the
226
+ # explicit multipathd start just appears to silently exit with a
227
+ # result code of 0.
228
+ il_utils .execute ('multipathd' )
229
+ # This is mainly to get the system to actually do the needful and
230
+ # identify/enumerate paths by combining what it can detect and what
231
+ # it already knows. This may be useful, and in theory this should be
232
+ # logged in the IPA log should it be needed.
233
+ il_utils .execute ('multipath' , '-ll' )
234
+ return True
235
+ except FileNotFoundError as e :
236
+ LOG .warning ('Attempted to determine if multipath tools were present. '
237
+ 'Not detected. Error recorded: %s' , e )
238
+ return False
239
+ except processutils .ProcessExecutionError as e :
240
+ LOG .warning ('Attempted to invoke multipath utilities, but we '
241
+ 'encountered an error: %s' , e )
242
+ return False
243
+
244
+
245
+ def _get_multipath_parent_device (device ):
246
+ """Check and return a multipath device."""
247
+ if not device :
248
+ # if lsblk provides invalid output, this can be None.
249
+ return
250
+ check_device = os .path .join ('/dev' , str (device ))
251
+ try :
252
+ # Explicitly run the check as regardless of if the device is mpath or
253
+ # not, multipath tools when using list always exits with a return
254
+ # code of 0.
255
+ utils .execute ('multipath' , '-c' , check_device )
256
+ # path check with return an exit code of 1 if you send it a multipath
257
+ # device mapper device, like dm-0.
258
+ # NOTE(TheJulia): -ll is supposed to load from all available
259
+ # information, but may not force a rescan. It may be -f if we need
260
+ # that. That being said, it has been about a decade since I was
261
+ # running multipath tools on SAN connected gear, so my memory is
262
+ # definitely fuzzy.
263
+ out , _ = utils .execute ('multipath' , '-ll' , check_device )
264
+ except processutils .ProcessExecutionError as e :
265
+ # FileNotFoundError if the utility does not exist.
266
+ # -1 return code if the device is not valid.
267
+ LOG .debug ('Checked device %(dev)s and determined it was '
268
+ 'not a multipath device. %(error)s' ,
269
+ {'dev' : check_device ,
270
+ 'error' : e })
271
+ return
272
+ except FileNotFoundError :
273
+ # This should never happen, as MULTIPATH_ENABLED would be False
274
+ # before this occurs.
275
+ LOG .warning ('Attempted to check multipathing status, however '
276
+ 'the \' multipath\' binary is missing or not in the '
277
+ 'execution PATH.' )
278
+ return
279
+ # Data format:
280
+ # MPATHDEVICENAME dm-0 TYPE,HUMANNAME
281
+ # size=56G features='1 retain_attached_hw_handler' hwhandler='0' wp=rw
282
+ # `-+- policy='service-time 0' prio=1 status=active
283
+ # `- 0:0:0:0 sda 8:0 active ready running
284
+ try :
285
+ lines = out .splitlines ()
286
+ mpath_device = lines [0 ].split (' ' )[1 ]
287
+ # give back something like dm-0 so we can log it.
288
+ return mpath_device
289
+ except IndexError :
290
+ # We didn't get any command output, so Nope.
291
+ pass
292
+
293
+
184
294
def get_component_devices (raid_device ):
185
295
"""Get the component devices of a Software RAID device.
186
296
@@ -371,7 +481,8 @@ def _md_scan_and_assemble():
371
481
def list_all_block_devices (block_type = 'disk' ,
372
482
ignore_raid = False ,
373
483
ignore_floppy = True ,
374
- ignore_empty = True ):
484
+ ignore_empty = True ,
485
+ ignore_multipath = False ):
375
486
"""List all physical block devices
376
487
377
488
The switches we use for lsblk: P for KEY="value" output, b for size output
@@ -388,6 +499,9 @@ def list_all_block_devices(block_type='disk',
388
499
:param ignore_floppy: Ignore floppy disk devices in the block device
389
500
list. By default, these devices are filtered out.
390
501
:param ignore_empty: Whether to ignore disks with size equal 0.
502
+ :param ignore_multipath: Whether to ignore devices backing multipath
503
+ devices. Default is to consider multipath
504
+ devices, if possible.
391
505
:return: A list of BlockDevices
392
506
"""
393
507
@@ -398,6 +512,8 @@ def _is_known_device(existing, new_device_name):
398
512
return True
399
513
return False
400
514
515
+ check_multipath = not ignore_multipath and get_multipath_status ()
516
+
401
517
_udev_settle ()
402
518
403
519
# map device names to /dev/disk/by-path symbolic links that points to it
@@ -428,7 +544,6 @@ def _is_known_device(existing, new_device_name):
428
544
'-o{}' .format (',' .join (columns )))[0 ]
429
545
lines = report .splitlines ()
430
546
context = pyudev .Context ()
431
-
432
547
devices = []
433
548
for line in lines :
434
549
device = {}
@@ -450,10 +565,25 @@ def _is_known_device(existing, new_device_name):
450
565
LOG .debug ('Ignoring floppy disk device: %s' , line )
451
566
continue
452
567
568
+ dev_kname = device .get ('KNAME' )
569
+ if check_multipath :
570
+ # Net effect is we ignore base devices, and their base devices
571
+ # to what would be the mapped device name which would not pass the
572
+ # validation, but would otherwise be match-able.
573
+ mpath_parent_dev = _get_multipath_parent_device (dev_kname )
574
+ if mpath_parent_dev :
575
+ LOG .warning (
576
+ "We have identified a multipath device %(device)s, this "
577
+ "is being ignored in favor of %(mpath_device)s and its "
578
+ "related child devices." ,
579
+ {'device' : dev_kname ,
580
+ 'mpath_device' : mpath_parent_dev })
581
+ continue
453
582
# Search for raid in the reply type, as RAID is a
454
583
# disk device, and we should honor it if is present.
455
584
# Other possible type values, which we skip recording:
456
585
# lvm, part, rom, loop
586
+
457
587
if devtype != block_type :
458
588
if devtype is None or ignore_raid :
459
589
LOG .debug (
@@ -462,7 +592,7 @@ def _is_known_device(existing, new_device_name):
462
592
{'block_type' : block_type , 'line' : line })
463
593
continue
464
594
elif ('raid' in devtype
465
- and block_type in ['raid' , 'disk' ]):
595
+ and block_type in ['raid' , 'disk' , 'mpath' ]):
466
596
LOG .debug (
467
597
"TYPE detected to contain 'raid', signifying a "
468
598
"RAID volume. Found: %s" , line )
@@ -476,6 +606,11 @@ def _is_known_device(existing, new_device_name):
476
606
LOG .debug (
477
607
"TYPE detected to contain 'md', signifying a "
478
608
"RAID partition. Found: %s" , line )
609
+ elif devtype == 'mpath' and block_type == 'disk' :
610
+ LOG .debug (
611
+ "TYPE detected to contain 'mpath', "
612
+ "signifing a device mapper multipath device. "
613
+ "Found: %s" , line )
479
614
else :
480
615
LOG .debug (
481
616
"TYPE did not match. Wanted: %(block_type)s but found: "
@@ -1001,6 +1136,10 @@ def evaluate_hardware_support(self):
1001
1136
_check_for_iscsi ()
1002
1137
_md_scan_and_assemble ()
1003
1138
_load_ipmi_modules ()
1139
+ global MULTIPATH_ENABLED
1140
+ if MULTIPATH_ENABLED is None :
1141
+ MULTIPATH_ENABLED = _enable_multipath ()
1142
+
1004
1143
self .wait_for_disks ()
1005
1144
return HardwareSupport .GENERIC
1006
1145
@@ -2732,3 +2871,11 @@ def deduplicate_steps(candidate_steps):
2732
2871
deduped_steps [manager ].append (winning_step )
2733
2872
2734
2873
return deduped_steps
2874
+
2875
+
2876
+ def get_multipath_status ():
2877
+ """Return the status of multipath initialization."""
2878
+ # NOTE(TheJulia): Provides a nice place to mock out and simplify testing
2879
+ # as if we directly try and work with the global var, we will be racing
2880
+ # tests endlessly.
2881
+ return MULTIPATH_ENABLED
0 commit comments