80
80
}
81
81
}
82
82
83
+ MULTIPATH_ENABLED = None
84
+
83
85
84
86
def _get_device_info (dev , devclass , field ):
85
87
"""Get the device info according to device class and field."""
@@ -126,6 +128,35 @@ def _udev_settle():
126
128
return
127
129
128
130
131
+ def _load_multipath_modules ():
132
+ """Load multipath modules
133
+
134
+ This is required to be able to collect multipath information.
135
+ Two separate paths exist, one with a helper utility for Centos/RHEL
136
+ and another which is just load the modules, and trust multipathd
137
+ will do the needful.
138
+ """
139
+ if (os .path .isfile ('/usr/sbin/mpathconf' )
140
+ and not os .path .isfile ('/etc/multipath.conf' )):
141
+ # For Centos/Rhel/Etc which uses mpathconf, this does
142
+ # a couple different things, including configuration generation...
143
+ # which is not *really* required.. at least *shouldn't* be.
144
+ # WARNING(TheJulia): This command explicitly replaces local
145
+ # configuration.
146
+ il_utils .try_execute ('/usr/sbin/mpathconf' , '--enable' ,
147
+ '--find_multipaths' , 'yes' ,
148
+ '--with_module' , 'y' ,
149
+ '--with_multipathd' , 'y' )
150
+ else :
151
+ # Ensure modules are loaded. Configuration is not required
152
+ # and implied based upon compiled in defaults.
153
+ # NOTE(TheJulia): Debian/Ubuntu specifically just document
154
+ # using `multipath -t` output to start a new configuration
155
+ # file, if needed.
156
+ il_utils .try_execute ('modprobe' , 'dm_multipath' )
157
+ il_utils .try_execute ('modprobe' , 'multipath' )
158
+
159
+
129
160
def _check_for_iscsi ():
130
161
"""Connect iSCSI shared connected via iBFT or OF.
131
162
@@ -169,6 +200,84 @@ def _get_md_uuid(raid_device):
169
200
return match .group (1 )
170
201
171
202
203
+ def _enable_multipath ():
204
+ """Initialize multipath IO if possible.
205
+
206
+ :returns: True if the multipathd daemon and multipath command to enumerate
207
+ devices was scucessfully able to be called.
208
+ """
209
+ try :
210
+ _load_multipath_modules ()
211
+ # This might not work, ideally it *should* already be running...
212
+ # NOTE(TheJulia): Testing locally, a prior running multipathd, the
213
+ # explicit multipathd start just appears to silently exit with a
214
+ # result code of 0.
215
+ utils .execute ('multipathd' )
216
+ # This is mainly to get the system to actually do the needful and
217
+ # identify/enumerate paths by combining what it can detect and what
218
+ # it already knows. This may be useful, and in theory this should be
219
+ # logged in the IPA log should it be needed.
220
+ utils .execute ('multipath' , '-ll' )
221
+ return True
222
+ except FileNotFoundError as e :
223
+ LOG .warning ('Attempted to determine if multipath tools were present. '
224
+ 'Not detected. Error recorded: %s' , e )
225
+ return False
226
+ except processutils .ProcessExecutionError as e :
227
+ LOG .warning ('Attempted to invoke multipath utilities, but we '
228
+ 'encountered an error: %s' , e )
229
+ return False
230
+
231
+
232
+ def _get_multipath_parent_device (device ):
233
+ """Check and return a multipath device."""
234
+ if not device :
235
+ # if lsblk provides invalid output, this can be None.
236
+ return
237
+ check_device = os .path .join ('/dev' , str (device ))
238
+ try :
239
+ # Explicitly run the check as regardless of if the device is mpath or
240
+ # not, multipath tools when using list always exits with a return
241
+ # code of 0.
242
+ utils .execute ('multipath' , '-c' , check_device )
243
+ # path check with return an exit code of 1 if you send it a multipath
244
+ # device mapper device, like dm-0.
245
+ # NOTE(TheJulia): -ll is supposed to load from all available
246
+ # information, but may not force a rescan. It may be -f if we need
247
+ # that. That being said, it has been about a decade since I was
248
+ # running multipath tools on SAN connected gear, so my memory is
249
+ # definitely fuzzy.
250
+ out , _ = utils .execute ('multipath' , '-ll' , check_device )
251
+ except processutils .ProcessExecutionError as e :
252
+ # FileNotFoundError if the utility does not exist.
253
+ # -1 return code if the device is not valid.
254
+ LOG .debug ('Checked device %(dev)s and determined it was '
255
+ 'not a multipath device. %(error)s' ,
256
+ {'dev' : check_device ,
257
+ 'error' : e })
258
+ return
259
+ except FileNotFoundError :
260
+ # This should never happen, as MULTIPATH_ENABLED would be False
261
+ # before this occurs.
262
+ LOG .warning ('Attempted to check multipathing status, however '
263
+ 'the \' multipath\' binary is missing or not in the '
264
+ 'execution PATH.' )
265
+ return
266
+ # Data format:
267
+ # MPATHDEVICENAME dm-0 TYPE,HUMANNAME
268
+ # size=56G features='1 retain_attached_hw_handler' hwhandler='0' wp=rw
269
+ # `-+- policy='service-time 0' prio=1 status=active
270
+ # `- 0:0:0:0 sda 8:0 active ready running
271
+ try :
272
+ lines = out .splitlines ()
273
+ mpath_device = lines [0 ].split (' ' )[1 ]
274
+ # give back something like dm-0 so we can log it.
275
+ return mpath_device
276
+ except IndexError :
277
+ # We didn't get any command output, so Nope.
278
+ pass
279
+
280
+
172
281
def _get_component_devices (raid_device ):
173
282
"""Get the component devices of a Software RAID device.
174
283
@@ -359,7 +468,8 @@ def _md_scan_and_assemble():
359
468
def list_all_block_devices (block_type = 'disk' ,
360
469
ignore_raid = False ,
361
470
ignore_floppy = True ,
362
- ignore_empty = True ):
471
+ ignore_empty = True ,
472
+ ignore_multipath = False ):
363
473
"""List all physical block devices
364
474
365
475
The switches we use for lsblk: P for KEY="value" output, b for size output
@@ -376,6 +486,9 @@ def list_all_block_devices(block_type='disk',
376
486
:param ignore_floppy: Ignore floppy disk devices in the block device
377
487
list. By default, these devices are filtered out.
378
488
:param ignore_empty: Whether to ignore disks with size equal 0.
489
+ :param ignore_multipath: Whether to ignore devices backing multipath
490
+ devices. Default is to consider multipath
491
+ devices, if possible.
379
492
:return: A list of BlockDevices
380
493
"""
381
494
@@ -386,6 +499,8 @@ def _is_known_device(existing, new_device_name):
386
499
return True
387
500
return False
388
501
502
+ check_multipath = not ignore_multipath and get_multipath_status ()
503
+
389
504
_udev_settle ()
390
505
391
506
# map device names to /dev/disk/by-path symbolic links that points to it
@@ -416,7 +531,6 @@ def _is_known_device(existing, new_device_name):
416
531
'-o{}' .format (',' .join (columns )))[0 ]
417
532
lines = report .splitlines ()
418
533
context = pyudev .Context ()
419
-
420
534
devices = []
421
535
for line in lines :
422
536
device = {}
@@ -438,16 +552,31 @@ def _is_known_device(existing, new_device_name):
438
552
LOG .debug ('Ignoring floppy disk device %s' , device )
439
553
continue
440
554
555
+ dev_kname = device .get ('KNAME' )
556
+ if check_multipath :
557
+ # Net effect is we ignore base devices, and their base devices
558
+ # to what would be the mapped device name which would not pass the
559
+ # validation, but would otherwise be match-able.
560
+ mpath_parent_dev = _get_multipath_parent_device (dev_kname )
561
+ if mpath_parent_dev :
562
+ LOG .warning (
563
+ "We have identified a multipath device %(device)s, this "
564
+ "is being ignored in favor of %(mpath_device)s and its "
565
+ "related child devices." ,
566
+ {'device' : dev_kname ,
567
+ 'mpath_device' : mpath_parent_dev })
568
+ continue
441
569
# Search for raid in the reply type, as RAID is a
442
570
# disk device, and we should honor it if is present.
443
571
# Other possible type values, which we skip recording:
444
572
# lvm, part, rom, loop
573
+
445
574
if devtype != block_type :
446
575
if devtype is None or ignore_raid :
447
576
LOG .debug ("Skipping: {!r}" .format (line ))
448
577
continue
449
578
elif ('raid' in devtype
450
- and block_type in ['raid' , 'disk' ]):
579
+ and block_type in ['raid' , 'disk' , 'mpath' ]):
451
580
LOG .debug (
452
581
"TYPE detected to contain 'raid', signifying a "
453
582
"RAID volume. Found: {!r}" .format (line ))
@@ -461,6 +590,11 @@ def _is_known_device(existing, new_device_name):
461
590
LOG .debug (
462
591
"TYPE detected to contain 'md', signifying a "
463
592
"RAID partition. Found: {!r}" .format (line ))
593
+ elif devtype == 'mpath' and block_type == 'disk' :
594
+ LOG .debug (
595
+ "TYPE detected to contain 'mpath', "
596
+ "signifing a device mapper multipath device. "
597
+ "Found: %s" , line )
464
598
else :
465
599
LOG .debug (
466
600
"TYPE did not match. Wanted: {!r} but found: {!r}" .format (
@@ -974,6 +1108,10 @@ def evaluate_hardware_support(self):
974
1108
# Do some initialization before we declare ourself ready
975
1109
_check_for_iscsi ()
976
1110
_md_scan_and_assemble ()
1111
+ global MULTIPATH_ENABLED
1112
+ if MULTIPATH_ENABLED is None :
1113
+ MULTIPATH_ENABLED = _enable_multipath ()
1114
+
977
1115
self .wait_for_disks ()
978
1116
return HardwareSupport .GENERIC
979
1117
@@ -2609,3 +2747,11 @@ def deduplicate_steps(candidate_steps):
2609
2747
deduped_steps [manager ].append (winning_step )
2610
2748
2611
2749
return deduped_steps
2750
+
2751
+
2752
+ def get_multipath_status ():
2753
+ """Return the status of multipath initialization."""
2754
+ # NOTE(TheJulia): Provides a nice place to mock out and simplify testing
2755
+ # as if we directly try and work with the global var, we will be racing
2756
+ # tests endlessly.
2757
+ return MULTIPATH_ENABLED
0 commit comments