@@ -53,12 +53,13 @@ import (
5353// Define constants for test plugins.
5454// Constants must match those used in testdata_test.go.
5555const (
56- testPluginType = "test-plugin"
57- testPickerType = "test-picker"
58- testScorerType = "test-scorer"
59- testProfileHandler = "test-profile-handler"
60- testSourceType = "test-source"
61- testExtractorType = "test-extractor"
56+ testPluginType = "test-plugin"
57+ testPickerType = "test-picker"
58+ testScorerType = "test-scorer"
59+ testProfileHandler = "test-profile-handler"
60+ testSourceType = "test-source"
61+ testExtractorType = "test-extractor"
62+ testSaturationDetector = "test-saturation-detector"
6263)
6364
6465// --- Test: Phase 1 (Raw Loading & Static Defaults) ---
@@ -177,6 +178,88 @@ func TestLoadRawConfiguration(t *testing.T) {
177178 },
178179 wantErr : false ,
179180 },
181+ {
182+ name : "Success - SaturationDetector correctly configured" ,
183+ configText : successSaturationDetectorConfigText ,
184+ want : & configapi.EndpointPickerConfig {
185+ TypeMeta : metav1.TypeMeta {
186+ APIVersion : "inference.networking.x-k8s.io/v1alpha1" ,
187+ Kind : "EndpointPickerConfig" ,
188+ },
189+ Plugins : []configapi.PluginSpec {
190+ {Name : "maxScore" , Type : "max-score-picker" },
191+ {Name : "my-detector" , Type : utilizationdetector .UtilizationDetectorType },
192+ },
193+ SchedulingProfiles : []configapi.SchedulingProfile {
194+ {
195+ Name : "default" ,
196+ Plugins : []configapi.SchedulingPlugin {
197+ {PluginRef : "maxScore" },
198+ },
199+ },
200+ },
201+ FlowControl : & configapi.FlowControlConfig {
202+ SaturationDetectorRef : "my-detector" ,
203+ },
204+ FeatureGates : configapi.FeatureGates {
205+ flowcontrol .FeatureGate ,
206+ },
207+ },
208+ wantErr : false ,
209+ },
210+ {
211+ name : "Success - SaturationDetector default configuration" ,
212+ configText : successSaturationDetectorDefaultConfigText ,
213+ want : & configapi.EndpointPickerConfig {
214+ TypeMeta : metav1.TypeMeta {
215+ APIVersion : "inference.networking.x-k8s.io/v1alpha1" ,
216+ Kind : "EndpointPickerConfig" ,
217+ },
218+ Plugins : []configapi.PluginSpec {
219+ {Name : "maxScore" , Type : "max-score-picker" },
220+ },
221+ SchedulingProfiles : []configapi.SchedulingProfile {
222+ {
223+ Name : "default" ,
224+ Plugins : []configapi.SchedulingPlugin {
225+ {PluginRef : "maxScore" },
226+ },
227+ },
228+ },
229+ FlowControl : nil ,
230+ FeatureGates : configapi.FeatureGates {
231+ flowcontrol .FeatureGate ,
232+ },
233+ },
234+ wantErr : false ,
235+ },
236+ {
237+ name : "Success - SaturationDetector with flowControl feature gate disabled" ,
238+ configText : successSaturationDetectorFlowControlDisabledConfigText ,
239+ want : & configapi.EndpointPickerConfig {
240+ TypeMeta : metav1.TypeMeta {
241+ APIVersion : "inference.networking.x-k8s.io/v1alpha1" ,
242+ Kind : "EndpointPickerConfig" ,
243+ },
244+ Plugins : []configapi.PluginSpec {
245+ {Name : "maxScore" , Type : "max-score-picker" },
246+ {Name : "my-detector" , Type : utilizationdetector .UtilizationDetectorType },
247+ },
248+ SchedulingProfiles : []configapi.SchedulingProfile {
249+ {
250+ Name : "default" ,
251+ Plugins : []configapi.SchedulingPlugin {
252+ {PluginRef : "maxScore" },
253+ },
254+ },
255+ },
256+ FeatureGates : configapi.FeatureGates {},
257+ FlowControl : & configapi.FlowControlConfig {
258+ SaturationDetectorRef : "my-detector" ,
259+ },
260+ },
261+ wantErr : false ,
262+ },
180263 {
181264 name : "Error - Invalid YAML" ,
182265 configText : errorBadYamlText ,
@@ -356,6 +439,39 @@ func TestInstantiateAndConfigure(t *testing.T) {
356439 "Should be GlobalStrict type" )
357440 },
358441 },
442+ {
443+ name : "Success - SaturationDetector default configuration" ,
444+ configText : successSaturationDetectorDefaultConfigText ,
445+ wantErr : false ,
446+ validate : func (t * testing.T , handle fwkplugin.Handle , rawCfg * configapi.EndpointPickerConfig , cfg * config.Config ) {
447+ require .NotNil (t , rawCfg .FlowControl , "FlowControl should be defaulted" )
448+ require .Equal (t , utilizationdetector .UtilizationDetectorType , rawCfg .FlowControl .SaturationDetectorRef )
449+ require .NotNil (t , cfg .SaturationDetector , "SaturationDetector should be instantiated" )
450+ require .Equal (t , utilizationdetector .UtilizationDetectorType , cfg .SaturationDetector .TypedName ().Name )
451+ },
452+ },
453+ {
454+ name : "Success - SaturationDetector explicit configuration" ,
455+ configText : successSaturationDetectorConfigText ,
456+ wantErr : false ,
457+ validate : func (t * testing.T , handle fwkplugin.Handle , rawCfg * configapi.EndpointPickerConfig , cfg * config.Config ) {
458+ require .NotNil (t , rawCfg .FlowControl )
459+ require .Equal (t , "my-detector" , rawCfg .FlowControl .SaturationDetectorRef )
460+ require .NotNil (t , cfg .SaturationDetector )
461+ require .Equal (t , "my-detector" , cfg .SaturationDetector .TypedName ().Name )
462+ },
463+ },
464+ {
465+ name : "Success - SaturationDetector with flowControl feature gate disabled" ,
466+ configText : successSaturationDetectorFlowControlDisabledConfigText ,
467+ wantErr : false ,
468+ validate : func (t * testing.T , handle fwkplugin.Handle , rawCfg * configapi.EndpointPickerConfig , cfg * config.Config ) {
469+ // Even if FG is disabled, we still instantiate SaturationDetector if it's in the config.
470+ // This is because SaturationDetector is used by the main picker logic, independent of FlowControl queueing.
471+ require .NotNil (t , cfg .SaturationDetector )
472+ require .Equal (t , "my-detector" , cfg .SaturationDetector .TypedName ().Name )
473+ },
474+ },
359475 {
360476 name : "Success - Parser Config" ,
361477 configText : successParserConfigText ,
@@ -493,6 +609,16 @@ func TestInstantiateAndConfigure(t *testing.T) {
493609 configText : errorParserWrongPluginNameText ,
494610 wantErr : true ,
495611 },
612+ {
613+ name : "Error - SaturationDetector wrong plugin type" ,
614+ configText : errorSaturationDetectorWrongPluginTypeText ,
615+ wantErr : true ,
616+ },
617+ {
618+ name : "Error (SaturationDetector) - Missing Plugin" ,
619+ configText : errorSaturationDetectorMissingPluginText ,
620+ wantErr : true ,
621+ },
496622 }
497623
498624 for _ , tc := range tests {
@@ -616,6 +742,13 @@ func (m *mockExtractor) Extract(ctx context.Context, data any, ep fwkdl.Endpoint
616742 return nil
617743}
618744
745+ // Mock SaturationDetector
746+ type mockSaturationDetector struct { mockPlugin }
747+
748+ func (m * mockSaturationDetector ) Saturation (ctx context.Context , candidatePods []fwkdl.Endpoint ) float64 {
749+ return 0.0
750+ }
751+
619752func registerTestPlugins (t * testing.T ) {
620753 t .Helper ()
621754
@@ -662,6 +795,10 @@ func registerTestPlugins(t *testing.T) {
662795 return & mockExtractor {mockPlugin {t : fwkplugin.TypedName {Name : name , Type : testExtractorType }}}, nil
663796 })
664797
798+ fwkplugin .Register (testSaturationDetector , func (name string , _ json.RawMessage , _ fwkplugin.Handle ) (fwkplugin.Plugin , error ) {
799+ return & mockSaturationDetector {mockPlugin {t : fwkplugin.TypedName {Name : name , Type : testSaturationDetector }}}, nil
800+ })
801+
665802 fwkplugin .Register (fairness .GlobalStrictFairnessPolicyType , func (name string , _ json.RawMessage , _ fwkplugin.Handle ) (fwkplugin.Plugin , error ) {
666803 return & flowcontrolmocks.MockFairnessPolicy {
667804 TypedNameV : fwkplugin.TypedName {Name : name , Type : fairness .GlobalStrictFairnessPolicyType },
0 commit comments