@@ -436,7 +436,7 @@ def create_vdev_id(server):
436436 "SAS9305-16i" : [2 ,3 ,1 ,0 , 6 ,7 ,5 ,4 , 18 ,19 ,17 ,16 , 22 ,23 ,21 ,20 ],
437437 "SAS9305-24i" : [2 ,3 ,1 ,0 , 6 ,7 ,5 ,4 , 18 ,19 ,17 ,16 , 22 ,23 ,21 ,20 , 10 ,11 ,9 ,8 , 14 ,15 ,13 ,12 ],
438438 "AVAGO3108MegaRAID" : [25 ,31 ,37 ,43 , 26 ,32 ,38 ,44 , 27 ,33 ,39 ,45 , 28 ,34 ,41 ,46 , 29 ,35 ,40 ,47 , 30 ,36 ,42 ,48 ],
439- "9600-24i" : [27 ,28 ,29 ,30 ,31 ,32 ,33 ,34 ,35 ,36 ,37 ,38 ,39 ,41 ,41 ,42 ,43 ,44 ,45 ,46 ,47 ,48 ,49 ,50 ],
439+ "9600-24i" : [27 ,28 ,29 ,30 ,31 ,32 ,33 ,34 ,35 ,36 ,37 ,38 ,39 ,40 ,41 ,42 ,43 ,44 ,45 ,46 ,47 ,48 ,49 ,50 ],
440440 #"9600-16i": [27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42]
441441 "9600-16i" : [59 ,60 ,61 ,62 ,63 ,64 ,65 ,66 ,67 ,68 ,69 ,70 ,71 ,72 ,73 ,74 ]
442442 }
@@ -503,7 +503,10 @@ def create_vdev_id(server):
503503 "F8" :{
504504 "F8X1" :[20 ],
505505 "F8X2" :[20 ,20 ],
506- "F8X3" :[20 ,20 ,20 ]
506+ "F8X3" :[20 ,20 ,20 ],
507+ # "NVME-F8X1":[20],
508+ # "NVME-F8X2":[20,20],
509+ "NVME-F8X3" :[20 ,20 ,20 ]
507510 },
508511 "STUDIO" :{
509512 "STUDIO8" :[4 ,4 ],
@@ -660,7 +663,7 @@ def create_vdev_id(server):
660663 vdev_id_str += alias_destroyinator (server ,alias_template ,phy_order )
661664 elif server ["Alias Style" ] == "F8" :
662665 # we are using the DESTROYINATOR ailiasing scheme.
663- vdev_id_str += alias_f8 (server ,alias_template )
666+ vdev_id_str += alias_f8 (server ,alias_template , phy_order )
664667 elif server ["Alias Style" ] == "HOMELAB" and server ["Chassis Size" ] == "HL4" :
665668 vdev_id_str += alias_hl4 ()
666669 elif server ["Alias Style" ] == "HOMELAB" and server ["Chassis Size" ] == "HL8" :
@@ -677,7 +680,27 @@ def create_vdev_id(server):
677680
678681 return vdev_id_str
679682
680- def alias_f8 (server ,alias_template ):
683+ def alias_f8 (server ,alias_template ,phy_order ):
684+ def _discover_scsi_targets (bus_addr : str ):
685+ """Return sorted unique SCSI target IDs visible under /dev/disk/by-path for a given PCI bus address."""
686+ import glob
687+ targets = set ()
688+ pattern = f"/dev/disk/by-path/pci-{ bus_addr } -scsi-0:0:*:0"
689+ for p in glob .glob (pattern ):
690+ # Ignore partition symlinks
691+ if "-part" in p :
692+ continue
693+ base = os .path .basename (p )
694+ # pci-0000:01:00.0-scsi-0:0:109:0
695+ m = re .search (r"-scsi-0:0:(\d+):0$" , base )
696+ if not m :
697+ continue
698+ targets .add (int (m .group (1 )))
699+ return sorted (targets )
700+
701+ def _scsi_path_exists (bus_addr : str , target : int ) -> bool :
702+ return os .path .exists (f"/dev/disk/by-path/pci-{ bus_addr } -scsi-0:0:{ target } :0" )
703+
681704 # Cables are installed into a 24i card with port 5 unused
682705 # Order is as follows:
683706 # P0 -> X-1 to X-3 (SSDs)
@@ -688,32 +711,213 @@ def alias_f8(server,alias_template):
688711 # P5 -> UNUSED
689712 f8_order = {
690713 "SAS9305-24i" : [0 ,1 ,3 ,2 , 4 ,5 ,7 ,6 , 8 ,9 ,11 ,10 , 20 ,21 ,23 ,22 , 16 ,17 ,19 ,18 , 12 ,13 ,15 ,14 ],
691- "9600-24i" : [30 ,29 ,28 ,27 , 34 ,33 ,32 ,31 , 46 ,45 ,44 ,43 , 42 ,41 ,41 ,39 , 38 ,37 ,36 ,35 , 50 ,49 ,48 ,47 ]
714+ "9600-24i" : [30 ,29 ,28 ,27 , 34 ,33 ,32 ,31 , 46 ,45 ,44 ,43 , 42 ,41 ,40 ,39 , 38 ,37 ,36 ,35 , 50 ,49 ,48 ,47 ]
692715 }
693716 vdev_id_str = ""
694- for i in range (0 ,len (server ["HBA" ])):
695- if server ["HBA" ][i ]["Model" ] in ["9361-16i" ,"9361-24i" ]:
696- hmap = hwraid_map (server ["HBA" ][i ],server )
697- count = alias_template [server ["Alias Style" ]][server ["Chassis Size" ]][i ]
717+
718+ # ── NVME-F8X3 special case ──────────────────────────────────────────────
719+ # NVME-F8X3 has 1x 9600-16i + 3x 9600-24i. Physical cabling splits
720+ # multiple HBAs across each row (multi-HBA-per-row):
721+ #
722+ # Row 1: 16i (bays 1-12) + 24i_A (bays 13-20)
723+ # Row 2: 24i_B (bays 1-12) + 24i_A (bays 13-16) + 24i_C (bays 17-20)
724+ # Row 3: 24i_B (bays 1-12) + 24i_C (bays 13-20)
725+ #
726+ # Target ordering (derived from dalias):
727+ # Bays 1-12 (16i / 24i_B): simple descending (reverse sorted targets)
728+ # Bays 13-20 (24i_A / 24i_C): reverse port-group order, ascending within
729+ # groups of 4
730+ #
731+ # HBA role identification:
732+ # 16i – only 9600-16i on the board (12 bays)
733+ # 24i_B (full) – the 9600-24i with the most targets (24 bays)
734+ # 24i_A / 24i_C – the two 12-target 9600-24is;
735+ # higher bus address → 24i_A (row 1-2 overlap)
736+ # lower bus address → 24i_C (row 2-3 overlap)
737+ # ────────────────────────────────────────────────────────────────────────
738+ if server .get ("Chassis Size" ) == "NVME-F8X3" :
739+ hba_16i = None
740+ hba_24i_list = []
741+ for h in list (server .get ("HBA" , [])):
742+ if h .get ("Model" ) == "9600-16i" and hba_16i is None :
743+ hba_16i = h
744+ elif h .get ("Model" ) == "9600-24i" :
745+ hba_24i_list .append (h )
746+
747+ if hba_16i is not None and len (hba_24i_list ) == 3 :
748+ # ── discover targets for every controller ──
749+ t_16i = _discover_scsi_targets (hba_16i ["Bus Address" ])
750+ t_24i = {}
751+ for h in hba_24i_list :
752+ t_24i [h ["Bus Address" ]] = _discover_scsi_targets (h ["Bus Address" ])
753+
754+ # ── identify roles ──
755+ # 24i_B = the one with the most discovered targets (uses all 6 ports → 24 bays)
756+ hba_24i_list_sorted = sorted (
757+ hba_24i_list ,
758+ key = lambda h : (- len (t_24i [h ["Bus Address" ]]), h .get ("Bus Address" , "" ))
759+ )
760+ hba_24i_B = hba_24i_list_sorted [0 ]
761+ # Remaining two: higher bus address → 24i_A, lower → 24i_C
762+ remaining = sorted (
763+ hba_24i_list_sorted [1 :],
764+ key = lambda h : h .get ("Bus Address" , "" )
765+ )
766+ hba_24i_C = remaining [0 ] # lower bus addr
767+ hba_24i_A = remaining [1 ] # higher bus addr
768+
769+ log ("NVME-F8X3 HBA roles:" )
770+ log (" 16i = {b} ({n} targets)" .format (b = hba_16i ["Bus Address" ], n = len (t_16i )))
771+ log (" 24i_A = {b} ({n} targets)" .format (b = hba_24i_A ["Bus Address" ], n = len (t_24i [hba_24i_A ["Bus Address" ]])))
772+ log (" 24i_B = {b} ({n} targets)" .format (b = hba_24i_B ["Bus Address" ], n = len (t_24i [hba_24i_B ["Bus Address" ]])))
773+ log (" 24i_C = {b} ({n} targets)" .format (b = hba_24i_C ["Bus Address" ], n = len (t_24i [hba_24i_C ["Bus Address" ]])))
774+
775+ # ── ordering helpers ──
776+ def _order_desc (targets , expected ):
777+ """Bays 1-12 pattern: simple descending (highest target = bay 1)."""
778+ if len (targets ) >= expected :
779+ return sorted (targets , reverse = True )[:expected ]
780+ elif targets :
781+ base = min (targets )
782+ return sorted (range (base , base + expected ), reverse = True )
783+ return [100000 + j for j in range (expected )]
784+
785+ def _order_reverse_group_asc (targets , expected ):
786+ """Bays 13-20 pattern: reverse port-group order, ascending within groups of 4."""
787+ if len (targets ) >= expected :
788+ st = sorted (targets )[:expected ]
789+ elif targets :
790+ base = min (targets )
791+ st = list (range (base , base + expected ))
792+ else :
793+ return [100000 + j for j in range (expected )]
794+ groups = [st [k :k + 4 ] for k in range (0 , len (st ), 4 )]
795+ groups .reverse ()
796+ return [t for g in groups for t in g ]
797+
798+ # ── compute ordered target lists per HBA ──
799+ ord_16i = _order_desc (t_16i , 12 )
800+ ord_24i_A = _order_reverse_group_asc (t_24i [hba_24i_A ["Bus Address" ]], 12 )
801+ ord_24i_B = _order_desc (t_24i [hba_24i_B ["Bus Address" ]], 24 )
802+ ord_24i_C = _order_reverse_group_asc (t_24i [hba_24i_C ["Bus Address" ]], 12 )
803+
804+ # ── build the 60-bay alias map (3 rows × 20 bays) ──
805+ bus_16i = hba_16i ["Bus Address" ]
806+ bus_24i_A = hba_24i_A ["Bus Address" ]
807+ bus_24i_B = hba_24i_B ["Bus Address" ]
808+ bus_24i_C = hba_24i_C ["Bus Address" ]
809+
810+ def _alias (row , bay , bus , target ):
811+ return "alias {r}-{b} /dev/disk/by-path/pci-{addr}-scsi-0:0:{t}:0\n " .format (
812+ r = row , b = bay , addr = bus , t = target )
813+
814+ # Row 1: 16i (bays 1-12) + 24i_A (bays 13-20)
815+ for j in range (12 ):
816+ vdev_id_str += _alias (1 , j + 1 , bus_16i , ord_16i [j ])
817+ for j in range (8 ):
818+ vdev_id_str += _alias (1 , j + 13 , bus_24i_A , ord_24i_A [j ])
819+
820+ # Row 2: 24i_B (bays 1-12) + 24i_A (bays 13-16) + 24i_C (bays 17-20)
821+ for j in range (12 ):
822+ vdev_id_str += _alias (2 , j + 1 , bus_24i_B , ord_24i_B [j ])
823+ for j in range (4 ):
824+ vdev_id_str += _alias (2 , j + 13 , bus_24i_A , ord_24i_A [j + 8 ])
825+ for j in range (4 ):
826+ vdev_id_str += _alias (2 , j + 17 , bus_24i_C , ord_24i_C [j ])
827+
828+ # Row 3: 24i_B (bays 1-12) + 24i_C (bays 13-20)
829+ for j in range (12 ):
830+ vdev_id_str += _alias (3 , j + 1 , bus_24i_B , ord_24i_B [j + 12 ])
831+ for j in range (8 ):
832+ vdev_id_str += _alias (3 , j + 13 , bus_24i_C , ord_24i_C [j + 4 ])
833+
834+ log ("\n " )
835+ return vdev_id_str
836+ else :
837+ log ("WARNING: NVME-F8X3 expected 1x 9600-16i + 3x 9600-24i but found: {m}" .format (
838+ m = ", " .join (h .get ("Model" ,"?" ) + " @ " + h .get ("Bus Address" ,"?" ) for h in server .get ("HBA" ,[]))
839+ ))
840+ log (" Falling back to generic F8 mapping" )
841+
842+ hbas = list (server .get ("HBA" , []))
843+
844+ for i in range (0 ,len (hbas )):
845+ counts = alias_template [server ["Alias Style" ]][server ["Chassis Size" ]]
846+ if i >= len (counts ):
847+ log (
848+ "WARNING - More HBAs detected than template supports for {cs}. Ignoring extra controller: {model} @ {bus}" .format (
849+ cs = server ["Chassis Size" ],
850+ model = hbas [i ].get ("Model" ,"?" ),
851+ bus = hbas [i ].get ("Bus Address" ,"?" )
852+ )
853+ )
854+ continue
855+ hba = hbas [i ]
856+ if hba ["Model" ] in ["9361-16i" ,"9361-24i" ]:
857+ hmap = hwraid_map (hba ,server )
858+ bus_addr = hba ["Bus Address" ]
859+ discovered_targets = None
860+ if hba ["Model" ] in ["9600-24i" ,"9600-16i" ]:
861+ # On some platforms/drivers the visible SCSI target IDs are not stable (e.g. 109-128).
862+ # Prefer discovering the actual by-path targets per controller bus address.
863+ discovered_targets = _discover_scsi_targets (bus_addr )
864+ count = counts [i ]
698865 for j in range (0 ,count ):
699- if server [ "HBA" ][ i ] ["Model" ]== "SAS9305-24i" :
866+ if hba ["Model" ]== "SAS9305-24i" :
700867 # The default case for ailiasing hba cards.
701868 vdev_id_str += (
702869 "alias {i}-{j} /dev/disk/by-path/pci-{addr}-sas-phy{p}-lun-0\n " .format (
703- i = i + 1 ,j = j + 1 ,addr = server [ "HBA" ][ i ][ " Bus Address" ],p = f8_order [server [ "HBA" ][ i ] ["Model" ]][j ]
870+ i = i + 1 ,j = j + 1 ,addr = hba [ " Bus Address" ],p = f8_order [hba ["Model" ]][j ]
704871 )
705872 )
706- elif server ["HBA" ][i ]["Model" ] in ["9600-24i" ]:
707- # alias 9600 style cards, and attempt hardware raid cards with warning.
873+ elif hba ["Model" ] in ["9600-24i" ]:
874+ # Prefer the traditional F8 cabling map if those targets exist; otherwise fall back
875+ # to discovered SCSI targets (platform-dependent target numbering).
876+ mapped_target = f8_order [hba ["Model" ]][j ]
877+ if not _scsi_path_exists (bus_addr , mapped_target ):
878+ if discovered_targets :
879+ if j < len (discovered_targets ):
880+ mapped_target = discovered_targets [j ]
881+ else :
882+ # Pad beyond currently-visible targets (empty bays). Keeps row width stable for lsdev.
883+ mapped_target = discovered_targets [- 1 ] + (j - (len (discovered_targets ) - 1 ))
884+ vdev_id_str += (
885+ "alias {i}-{j} /dev/disk/by-path/pci-{addr}-scsi-0:0:{p}:0\n " .format (
886+ i = i + 1 ,j = j + 1 ,addr = bus_addr ,p = mapped_target
887+ )
888+ )
889+ elif hba ["Model" ] in ["9600-16i" ]:
890+ # Prefer discovered targets when available to avoid duplicates and platform-specific numbering.
891+ if discovered_targets :
892+ ordered_targets = discovered_targets
893+ if j < len (ordered_targets ):
894+ mapped_target = ordered_targets [j ]
895+ else :
896+ # Beyond discovered targets = empty bay. Use impossible target ID to avoid duplicates.
897+ mapped_target = 100000 + j
898+ else :
899+ po = phy_order .get (hba ["Model" ], [])
900+ if j < len (po ):
901+ mapped_target = po [j ]
902+ else :
903+ log (
904+ "WARNING - No mapping for {model} {bus} slot {slot}; leaving bay empty" .format (
905+ model = hba .get ("Model" ,"?" ),
906+ bus = bus_addr ,
907+ slot = j + 1
908+ )
909+ )
910+ # Use an impossible target id so the by-path won't exist.
911+ mapped_target = 100000 + j
708912 vdev_id_str += (
709913 "alias {i}-{j} /dev/disk/by-path/pci-{addr}-scsi-0:0:{p}:0\n " .format (
710- i = i + 1 ,j = j + 1 ,addr = server [ "HBA" ][ i ][ "Bus Address" ] ,p = f8_order [ server [ "HBA" ][ i ][ "Model" ]][ j ]
914+ i = i + 1 ,j = j + 1 ,addr = bus_addr ,p = mapped_target
711915 )
712916 )
713- elif server [ "HBA" ][ i ] ["Model" ] in ["9361-24i" ]:
917+ elif hba ["Model" ] in ["9361-24i" ]:
714918 vdev_id_str += (
715919 "alias {i}-{j} /dev/disk/by-path/pci-{addr}-scsi-0:0:{p}:0\n " .format (
716- i = i + 1 ,j = j + 1 ,addr = server [ "HBA" ][ i ] ["Bus Address" ],p = hmap [j ]
920+ i = i + 1 ,j = j + 1 ,addr = hba ["Bus Address" ],p = hmap [j ]
717921 )
718922 )
719923 if hmap [j ] == 99 :
0 commit comments