@@ -88,183 +88,207 @@ func loadPtpConfigFromFile(filename string) (*ptpv1.PtpConfig, error) {
8888}
8989
9090// TestClockChainResolution tests that a minimal hardwareconfig with clockType
91- // gets resolved with structure and behavior derived from ptpconfig and templates
91+ // gets resolved with structure and behavior derived from ptpconfig and templates.
92+ // It covers multiple hardware vendors (intel/e825, dell/XR8720t).
9293func TestClockChainResolution (t * testing.T ) {
93- // Load minimal hardwareconfig
94- hwConfig , err := loadHardwareConfigFromFile ("testdata/gnrd-hwconfig-minimal.yaml" )
95- assert .NoError (t , err )
96- assert .NotNil (t , hwConfig )
97- if hwConfig == nil {
98- t .Fatal ("hwConfig is nil" )
94+ testCases := []struct {
95+ name string
96+ hwConfigFile string
97+ hwDefPath string
98+ expectedPtpInput string
99+ expectedGnssInput string
100+ }{
101+ {
102+ name : "intel/e825" ,
103+ hwConfigFile : "testdata/gnrd-hwconfig-minimal.yaml" ,
104+ hwDefPath : "intel/e825" ,
105+ expectedPtpInput : "GNR-D_SDP0" ,
106+ expectedGnssInput : "GNSS_1PPS_IN" ,
107+ },
108+ {
109+ name : "dell/XR8720t" ,
110+ hwConfigFile : "testdata/gnrd-hwconfig-minimal-perla4.yaml" ,
111+ hwDefPath : "dell/XR8720t" ,
112+ expectedPtpInput : "ETH01_SDP_TIMESYNC_0" ,
113+ expectedGnssInput : "GNSS_1PPS_IN" ,
114+ },
99115 }
100116
101- // Verify it's minimal (no DPLL/Ethernet in structure)
102- assert .NotNil (t , hwConfig .Spec .Profile .ClockType )
103- assert .Equal (t , "T-BC" , * hwConfig .Spec .Profile .ClockType )
104- assert .NotNil (t , hwConfig .Spec .Profile .ClockChain )
105- assert .Len (t , hwConfig .Spec .Profile .ClockChain .Structure , 1 )
106-
107- subsystem := hwConfig .Spec .Profile .ClockChain .Structure [0 ]
108- assert .Equal (t , "leader" , subsystem .Name )
109- assert .Equal (t , "intel/e825" , subsystem .HardwareSpecificDefinitions )
110-
111- // Before resolution: DPLL and Ethernet should be empty/omitted
112- assert .Empty (t , subsystem .DPLL .NetworkInterface )
113- assert .Empty (t , subsystem .DPLL .PhaseInputs )
114- assert .Empty (t , subsystem .Ethernet )
115- assert .Nil (t , hwConfig .Spec .Profile .ClockChain .Behavior )
116-
117- // Load ptpconfig
118- ptpConfig , err := loadPtpConfigFromFile ("testdata/tbc-gnrd.yaml" )
119- assert .NoError (t , err )
120- assert .NotNil (t , ptpConfig )
121- if ptpConfig == nil {
122- t .Fatal ("ptpConfig is nil" )
123- }
117+ for _ , tc := range testCases {
118+ t .Run (tc .name , func (t * testing.T ) {
119+ // Load minimal hardwareconfig
120+ hwConfig , err := loadHardwareConfigFromFile (tc .hwConfigFile )
121+ assert .NoError (t , err )
122+ assert .NotNil (t , hwConfig )
123+ if hwConfig == nil {
124+ t .Fatal ("hwConfig is nil" )
125+ }
124126
125- // Find the matching profile
126- var ptpProfile * ptpv1.PtpProfile
127- for i := range ptpConfig .Spec .Profile {
128- if ptpConfig .Spec .Profile [i ].Name != nil &&
129- * ptpConfig .Spec .Profile [i ].Name == hwConfig .Spec .RelatedPtpProfileName {
130- ptpProfile = & ptpConfig .Spec .Profile [i ]
131- break
132- }
133- }
134- assert .NotNil (t , ptpProfile , "PTP profile %s not found" , hwConfig .Spec .RelatedPtpProfileName )
135- if ptpProfile == nil {
136- t .Fatalf ("PTP profile %s not found" , hwConfig .Spec .RelatedPtpProfileName )
137- }
127+ // Verify it's minimal (no DPLL/Ethernet in structure)
128+ assert .NotNil (t , hwConfig .Spec .Profile .ClockType )
129+ assert .Equal (t , "T-BC" , * hwConfig .Spec .Profile .ClockType )
130+ assert .NotNil (t , hwConfig .Spec .Profile .ClockChain )
131+ assert .Len (t , hwConfig .Spec .Profile .ClockChain .Structure , 1 )
132+
133+ subsystem := hwConfig .Spec .Profile .ClockChain .Structure [0 ]
134+ assert .Equal (t , "leader" , subsystem .Name )
135+ assert .Equal (t , tc .hwDefPath , subsystem .HardwareSpecificDefinitions )
136+
137+ // Before resolution: DPLL and Ethernet should be empty/omitted
138+ assert .Empty (t , subsystem .DPLL .NetworkInterface )
139+ assert .Empty (t , subsystem .DPLL .PhaseInputs )
140+ assert .Empty (t , subsystem .Ethernet )
141+ assert .Nil (t , hwConfig .Spec .Profile .ClockChain .Behavior )
142+
143+ // Load ptpconfig
144+ ptpConfig , err := loadPtpConfigFromFile ("testdata/tbc-gnrd.yaml" )
145+ assert .NoError (t , err )
146+ assert .NotNil (t , ptpConfig )
147+ if ptpConfig == nil {
148+ t .Fatal ("ptpConfig is nil" )
149+ }
138150
139- // Extract upstream ports from ptpconfig
140- upstreamPorts := extractUpstreamPortsFromPtpProfile (ptpProfile )
141- assert .NotEmpty (t , upstreamPorts , "Should find at least one upstream port" )
142- if len (upstreamPorts ) == 0 {
143- t .Fatal ("Should find at least one upstream port" )
144- }
145- assert .Contains (t , upstreamPorts , "eno2" , "Should find eno2 as upstream port" )
146-
147- // Set up fake ConfigMap loader for tests (reused later for hcm)
148- fakeClient := fake .NewSimpleClientset ()
149- loader := NewBoardLabelMapLoader (fakeClient , "default" )
150-
151- // Load behavior profile template
152- behaviorTemplate , err := LoadBehaviorProfile ("intel/e825" , * hwConfig .Spec .Profile .ClockType , loader )
153- assert .NoError (t , err )
154- assert .NotNil (t , behaviorTemplate , "Behavior template should be loaded" )
155- if behaviorTemplate == nil {
156- t .Fatal ("Behavior template should be loaded" )
157- }
151+ // Find the matching profile
152+ var ptpProfile * ptpv1.PtpProfile
153+ for i := range ptpConfig .Spec .Profile {
154+ if ptpConfig .Spec .Profile [i ].Name != nil &&
155+ * ptpConfig .Spec .Profile [i ].Name == hwConfig .Spec .RelatedPtpProfileName {
156+ ptpProfile = & ptpConfig .Spec .Profile [i ]
157+ break
158+ }
159+ }
160+ assert .NotNil (t , ptpProfile , "PTP profile %s not found" , hwConfig .Spec .RelatedPtpProfileName )
161+ if ptpProfile == nil {
162+ t .Fatalf ("PTP profile %s not found" , hwConfig .Spec .RelatedPtpProfileName )
163+ }
158164
159- // Verify template has pinRoles
160- assert .NotEmpty (t , behaviorTemplate .PinRoles )
161- assert .Equal (t , "GNR-D_SDP0" , behaviorTemplate .PinRoles ["ptpInputPin" ])
162- assert .Equal (t , "GNSS_1PPS_IN" , behaviorTemplate .PinRoles ["gnssInputPin" ])
163- assert .Equal (t , "GNSS_1PPS_IN" , behaviorTemplate .PinRoles ["gnssInputPin" ])
164-
165- // Set up mock leading interface resolver
166- mockResolver := newMockLeadingInterfaceResolver ()
167- // Mock: eno2 -> PHC 0 -> PCI 0000:13:00.0 -> eno5
168- mockResolver .phcIDs ["eno2" ] = "/dev/ptp0"
169- mockResolver .symlinks ["/sys/class/ptp/ptp0/device" ] = "../../../0000:13:00.0"
170- mockResolver .dirEntries ["/sys/bus/pci/devices/0000:13:00.0/net" ] = []os.DirEntry {
171- & mockDirEntry {name : "eno5" , isDir : false },
172- }
165+ // Extract upstream ports from ptpconfig
166+ upstreamPorts := extractUpstreamPortsFromPtpProfile (ptpProfile )
167+ assert .NotEmpty (t , upstreamPorts , "Should find at least one upstream port" )
168+ if len (upstreamPorts ) == 0 {
169+ t .Fatal ("Should find at least one upstream port" )
170+ }
171+ assert .Contains (t , upstreamPorts , "eno2" , "Should find eno2 as upstream port" )
172+
173+ // Set up fake ConfigMap loader for tests (reused later for hcm)
174+ fakeClient := fake .NewSimpleClientset ()
175+ loader := NewBoardLabelMapLoader (fakeClient , "default" )
176+
177+ // Load behavior profile template
178+ behaviorTemplate , err := LoadBehaviorProfile (tc .hwDefPath , * hwConfig .Spec .Profile .ClockType , loader )
179+ assert .NoError (t , err )
180+ assert .NotNil (t , behaviorTemplate , "Behavior template should be loaded" )
181+ if behaviorTemplate == nil {
182+ t .Fatal ("Behavior template should be loaded" )
183+ }
173184
174- // Inject mock resolver
175- SetLeadingInterfaceResolver (mockResolver )
176- defer ResetLeadingInterfaceResolver ()
177-
178- // Resolve clock chain (this is what we're testing)
179- hcm := NewHardwareConfigManager (fakeClient , "default" )
180- resolvedConfig , err := hcm .ResolveClockChain (hwConfig , ptpConfig )
181- assert .NoError (t , err )
182- assert .NotNil (t , resolvedConfig )
183- if resolvedConfig == nil {
184- t .Fatal ("resolvedConfig is nil" )
185- }
185+ // Verify template has pinRoles
186+ assert .NotEmpty (t , behaviorTemplate .PinRoles )
187+ assert .Equal (t , tc .expectedPtpInput , behaviorTemplate .PinRoles ["ptpInputPin" ])
188+ assert .Equal (t , tc .expectedGnssInput , behaviorTemplate .PinRoles ["gnssInputPin" ])
189+
190+ // Set up mock leading interface resolver
191+ mockResolver := newMockLeadingInterfaceResolver ()
192+ // Mock: eno2 -> PHC 0 -> PCI 0000:13:00.0 -> eno5
193+ mockResolver .phcIDs ["eno2" ] = "/dev/ptp0"
194+ mockResolver .symlinks ["/sys/class/ptp/ptp0/device" ] = "../../../0000:13:00.0"
195+ mockResolver .dirEntries ["/sys/bus/pci/devices/0000:13:00.0/net" ] = []os.DirEntry {
196+ & mockDirEntry {name : "eno5" , isDir : false },
197+ }
186198
187- // Verify structure was derived
188- resolvedSubsystem := resolvedConfig .Spec .Profile .ClockChain .Structure [0 ]
189-
190- // NetworkInterface should be derived (leading interface found via PHC -> PCI -> net)
191- assert .NotEmpty (t , resolvedSubsystem .DPLL .NetworkInterface )
192- assert .Equal (t , "eno5" , resolvedSubsystem .DPLL .NetworkInterface ,
193- "NetworkInterface should be derived from upstream port eno2 via PHC -> PCI -> net path" )
194-
195- // PhaseInputs should be derived from pinRoles (ptpInputPin -> GNR-D_SDP0)
196- assert .NotEmpty (t , resolvedSubsystem .DPLL .PhaseInputs )
197- ptpInputPin , exists := resolvedSubsystem .DPLL .PhaseInputs ["GNR-D_SDP0" ]
198- assert .True (t , exists , "PhaseInputs should contain ptpInputPin from template" )
199- assert .NotNil (t , ptpInputPin .Frequency )
200- assert .Equal (t , int64 (1 ), * ptpInputPin .Frequency , "PTP input should be 1 PPS" )
201-
202- // Ethernet ports should be derived (all upstream ports)
203- assert .NotEmpty (t , resolvedSubsystem .Ethernet )
204- assert .Len (t , resolvedSubsystem .Ethernet , 1 )
205- assert .Equal (t , upstreamPorts , resolvedSubsystem .Ethernet [0 ].Ports ,
206- "Ethernet ports should match upstream ports" )
207-
208- // Verify behavior derivation
209- assert .NotNil (t , resolvedConfig .Spec .Profile .ClockChain .Behavior )
210- behavior := resolvedConfig .Spec .Profile .ClockChain .Behavior
211-
212- // Sources should be instantiated with resolved variables
213- assert .NotEmpty (t , behavior .Sources )
214- ptpSource := findSourceByName (behavior .Sources , "PTP" )
215- assert .NotNil (t , ptpSource , "PTP source should be present" )
216- if ptpSource == nil {
217- t .Fatal ("PTP source should be present" )
218- }
219- assert .Equal (t , "ptpTimeReceiver" , ptpSource .SourceType )
220- assert .Equal (t , "leader" , ptpSource .Subsystem , "Subsystem should be resolved" )
221- assert .Equal (t , "GNR-D_SDP0" , ptpSource .BoardLabel , "BoardLabel should be resolved from pinRoles" )
222- assert .Equal (t , upstreamPorts , ptpSource .PTPTimeReceivers ,
223- "PTPTimeReceivers should match upstream ports" )
224-
225- // Conditions should be instantiated with resolved variables
226- assert .NotEmpty (t , behavior .Conditions )
227- initCondition := findConditionByName (behavior .Conditions , "Initialize T-BC" )
228- assert .NotNil (t , initCondition , "Initialize T-BC condition should be present" )
229- if initCondition == nil {
230- t .Fatal ("Initialize T-BC condition should be present" )
231- }
232- // Init condition should use GNSS input pin for DPLL
233- if len (initCondition .DesiredStates ) > 0 && initCondition .DesiredStates [0 ].DPLL != nil {
234- assert .Equal (t , "GNSS_1PPS_IN" , initCondition .DesiredStates [0 ].DPLL .BoardLabel ,
235- "Init condition DPLL should use gnssInputPin from template" )
236- }
199+ // Inject mock resolver
200+ SetLeadingInterfaceResolver (mockResolver )
201+ defer ResetLeadingInterfaceResolver ()
202+
203+ // Resolve clock chain (this is what we're testing)
204+ hcm := NewHardwareConfigManager (fakeClient , "default" )
205+ resolvedConfig , err := hcm .ResolveClockChain (hwConfig , ptpConfig )
206+ assert .NoError (t , err )
207+ assert .NotNil (t , resolvedConfig )
208+ if resolvedConfig == nil {
209+ t .Fatal ("resolvedConfig is nil" )
210+ }
237211
238- lockedCondition := findConditionByName (behavior .Conditions , "PTP Source Locked" )
239- assert .NotNil (t , lockedCondition , "PTP Source Locked condition should be present" )
240- if lockedCondition == nil {
241- t .Fatal ("PTP Source Locked condition should be present" )
242- }
243- // Locked condition should use PTP input pin for DPLL
244- if len (lockedCondition .DesiredStates ) > 0 && lockedCondition .DesiredStates [0 ].DPLL != nil {
245- assert .Equal (t , "GNR-D_SDP0" , lockedCondition .DesiredStates [0 ].DPLL .BoardLabel ,
246- "Locked condition DPLL should use ptpInputPin from template" )
247- }
212+ // Verify structure was derived
213+ resolvedSubsystem := resolvedConfig .Spec .Profile .ClockChain .Structure [0 ]
214+
215+ // NetworkInterface should be derived (leading interface found via PHC -> PCI -> net)
216+ assert .NotEmpty (t , resolvedSubsystem .DPLL .NetworkInterface )
217+ assert .Equal (t , "eno5" , resolvedSubsystem .DPLL .NetworkInterface ,
218+ "NetworkInterface should be derived from upstream port eno2 via PHC -> PCI -> net path" )
219+
220+ // PhaseInputs should be derived from pinRoles (ptpInputPin)
221+ assert .NotEmpty (t , resolvedSubsystem .DPLL .PhaseInputs )
222+ ptpInputPin , exists := resolvedSubsystem .DPLL .PhaseInputs [tc .expectedPtpInput ]
223+ assert .True (t , exists , "PhaseInputs should contain ptpInputPin %s from template" , tc .expectedPtpInput )
224+ assert .NotNil (t , ptpInputPin .Frequency )
225+ assert .Equal (t , int64 (1 ), * ptpInputPin .Frequency , "PTP input should be 1 PPS" )
226+
227+ // Ethernet ports should be derived (all upstream ports)
228+ assert .NotEmpty (t , resolvedSubsystem .Ethernet )
229+ assert .Len (t , resolvedSubsystem .Ethernet , 1 )
230+ assert .Equal (t , upstreamPorts , resolvedSubsystem .Ethernet [0 ].Ports ,
231+ "Ethernet ports should match upstream ports" )
232+
233+ // Verify behavior derivation
234+ assert .NotNil (t , resolvedConfig .Spec .Profile .ClockChain .Behavior )
235+ behavior := resolvedConfig .Spec .Profile .ClockChain .Behavior
236+
237+ // Sources should be instantiated with resolved variables
238+ assert .NotEmpty (t , behavior .Sources )
239+ ptpSource := findSourceByName (behavior .Sources , "PTP" )
240+ assert .NotNil (t , ptpSource , "PTP source should be present" )
241+ if ptpSource == nil {
242+ t .Fatal ("PTP source should be present" )
243+ }
244+ assert .Equal (t , "ptpTimeReceiver" , ptpSource .SourceType )
245+ assert .Equal (t , "leader" , ptpSource .Subsystem , "Subsystem should be resolved" )
246+ assert .Equal (t , tc .expectedPtpInput , ptpSource .BoardLabel , "BoardLabel should be resolved from pinRoles" )
247+ assert .Equal (t , upstreamPorts , ptpSource .PTPTimeReceivers ,
248+ "PTPTimeReceivers should match upstream ports" )
249+
250+ // Conditions should be instantiated with resolved variables
251+ assert .NotEmpty (t , behavior .Conditions )
252+ initCondition := findConditionByName (behavior .Conditions , "Initialize T-BC" )
253+ assert .NotNil (t , initCondition , "Initialize T-BC condition should be present" )
254+ if initCondition == nil {
255+ t .Fatal ("Initialize T-BC condition should be present" )
256+ }
257+ // Init condition should use GNSS input pin for DPLL
258+ if len (initCondition .DesiredStates ) > 0 && initCondition .DesiredStates [0 ].DPLL != nil {
259+ assert .Equal (t , tc .expectedGnssInput , initCondition .DesiredStates [0 ].DPLL .BoardLabel ,
260+ "Init condition DPLL should use gnssInputPin from template" )
261+ }
248262
249- lostCondition := findConditionByName (behavior .Conditions , "PTP Source Lost - Leader Holdover" )
250- assert .NotNil (t , lostCondition , "PTP Source Lost condition should be present" )
251- if lostCondition == nil {
252- t .Fatal ("PTP Source Lost condition should be present" )
253- }
263+ lockedCondition := findConditionByName (behavior .Conditions , "PTP Source Locked" )
264+ assert .NotNil (t , lockedCondition , "PTP Source Locked condition should be present" )
265+ if lockedCondition == nil {
266+ t .Fatal ("PTP Source Locked condition should be present" )
267+ }
268+ // Locked condition should use PTP input pin for DPLL
269+ if len (lockedCondition .DesiredStates ) > 0 && lockedCondition .DesiredStates [0 ].DPLL != nil {
270+ assert .Equal (t , tc .expectedPtpInput , lockedCondition .DesiredStates [0 ].DPLL .BoardLabel ,
271+ "Locked condition DPLL should use ptpInputPin from template" )
272+ }
254273
255- // Verify template variables were resolved in conditions
256- // Check that {subsystem} was replaced with "leader"
257- // Check that {ptpInputPin} was replaced with "GNR-D_SDP0"
258- // Check that {interface} was replaced with "eno5" (leading interface, not upstream port)
259- for _ , condition := range behavior .Conditions {
260- for _ , desiredState := range condition .DesiredStates {
261- if desiredState .DPLL != nil {
262- assert .NotEqual (t , "{subsystem}" , desiredState .DPLL .Subsystem ,
263- "Subsystem variable should be resolved" )
264- assert .NotEqual (t , "{ptpInputPin}" , desiredState .DPLL .BoardLabel ,
265- "ptpInputPin variable should be resolved" )
274+ lostCondition := findConditionByName (behavior .Conditions , "PTP Source Lost - Leader Holdover" )
275+ assert .NotNil (t , lostCondition , "PTP Source Lost condition should be present" )
276+ if lostCondition == nil {
277+ t .Fatal ("PTP Source Lost condition should be present" )
266278 }
267- }
279+
280+ // Verify template variables were resolved in conditions
281+ for _ , condition := range behavior .Conditions {
282+ for _ , desiredState := range condition .DesiredStates {
283+ if desiredState .DPLL != nil {
284+ assert .NotEqual (t , "{subsystem}" , desiredState .DPLL .Subsystem ,
285+ "Subsystem variable should be resolved" )
286+ assert .NotEqual (t , "{ptpInputPin}" , desiredState .DPLL .BoardLabel ,
287+ "ptpInputPin variable should be resolved" )
288+ }
289+ }
290+ }
291+ })
268292 }
269293}
270294
0 commit comments