@@ -21,14 +21,17 @@ import (
2121 "encoding/json"
2222 "os"
2323 "testing"
24+ "time"
2425
2526 "github.com/google/go-cmp/cmp"
2627 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2728 "k8s.io/utils/ptr"
2829
2930 configapi "sigs.k8s.io/gateway-api-inference-extension/apix/config/v1alpha1"
3031 "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/config"
32+ "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datalayer"
3133 "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/plugins"
34+ "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/saturationdetector"
3235 "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework"
3336 "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework/plugins/multi/prefix"
3437 "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework/plugins/picker"
@@ -97,6 +100,10 @@ func TestLoadRawConfiguration(t *testing.T) {
97100 },
98101 },
99102 },
103+ FeatureGates : configapi.FeatureGates {datalayer .FeatureGate },
104+ SaturationDetector : & configapi.SaturationDetector {
105+ MetricsStalenessThreshold : metav1.Duration {Duration : 150 * time .Millisecond },
106+ },
100107 }
101108
102109 goodConfigNoProfiles := & configapi.EndpointPickerConfig {
@@ -199,6 +206,12 @@ func TestLoadRawConfigurationWithDefaults(t *testing.T) {
199206 },
200207 },
201208 },
209+ FeatureGates : configapi.FeatureGates {datalayer .FeatureGate },
210+ SaturationDetector : & configapi.SaturationDetector {
211+ QueueDepthThreshold : saturationdetector .DefaultQueueDepthThreshold ,
212+ KVCacheUtilThreshold : saturationdetector .DefaultKVCacheUtilThreshold ,
213+ MetricsStalenessThreshold : metav1.Duration {Duration : 150 * time .Millisecond },
214+ },
202215 }
203216
204217 goodConfigNoProfiles := & configapi.EndpointPickerConfig {
@@ -234,6 +247,12 @@ func TestLoadRawConfigurationWithDefaults(t *testing.T) {
234247 },
235248 },
236249 },
250+ FeatureGates : configapi.FeatureGates {},
251+ SaturationDetector : & configapi.SaturationDetector {
252+ QueueDepthThreshold : saturationdetector .DefaultQueueDepthThreshold ,
253+ KVCacheUtilThreshold : saturationdetector .DefaultKVCacheUtilThreshold ,
254+ MetricsStalenessThreshold : metav1.Duration {Duration : saturationdetector .DefaultMetricsStalenessThreshold },
255+ },
237256 }
238257
239258 tests := []testStruct {
@@ -346,6 +365,7 @@ func checkError(t *testing.T, function string, test testStruct, err error) {
346365}
347366
348367func TestInstantiatePlugins (t * testing.T ) {
368+ registerNeededFeatureGates ()
349369 handle := utils .NewTestHandle (context .Background ())
350370 _ , err := LoadConfig ([]byte (successConfigText ), handle , logging .NewTestLogger ())
351371 if err != nil {
@@ -425,8 +445,14 @@ func TestLoadConfig(t *testing.T) {
425445 configText : errorMultiProfilesUseSingleProfileHandlerText ,
426446 wantErr : true ,
427447 },
448+ {
449+ name : "errorUnknownFeatureGate" ,
450+ configText : errorUnknownFeatureGateText ,
451+ wantErr : true ,
452+ },
428453 }
429454
455+ registerNeededFeatureGates ()
430456 registerNeededPlgugins ()
431457
432458 logger := logging .NewTestLogger ()
@@ -444,6 +470,10 @@ func TestLoadConfig(t *testing.T) {
444470 }
445471}
446472
473+ func registerNeededFeatureGates () {
474+ RegisterFeatureGate (datalayer .FeatureGate )
475+ }
476+
447477func registerNeededPlgugins () {
448478 plugins .Register (prefix .PrefixCachePluginType , prefix .PrefixCachePluginFactory )
449479 plugins .Register (picker .MaxScorePickerType , picker .MaxScorePickerFactory )
@@ -452,6 +482,64 @@ func registerNeededPlgugins() {
452482 plugins .Register (profile .SingleProfileHandlerType , profile .SingleProfileHandlerFactory )
453483}
454484
485+ func TestNewDetector (t * testing.T ) {
486+ tests := []struct {
487+ name string
488+ config * configapi.SaturationDetector
489+ expectedConfig saturationdetector.Config
490+ }{
491+ {
492+ name : "Valid config" ,
493+ config : & configapi.SaturationDetector {
494+ QueueDepthThreshold : 10 ,
495+ KVCacheUtilThreshold : 0.8 ,
496+ MetricsStalenessThreshold : metav1.Duration {Duration : 100 * time .Millisecond },
497+ },
498+ expectedConfig : saturationdetector.Config {
499+ QueueDepthThreshold : 10 ,
500+ KVCacheUtilThreshold : 0.8 ,
501+ MetricsStalenessThreshold : 100 * time .Millisecond ,
502+ },
503+ },
504+ {
505+ name : "invalid thresholds, fallback to default" ,
506+ config : & configapi.SaturationDetector {
507+ QueueDepthThreshold : - 1 ,
508+ KVCacheUtilThreshold : - 5.0 ,
509+ MetricsStalenessThreshold : metav1.Duration {Duration : 0 * time .Second },
510+ },
511+ expectedConfig : saturationdetector.Config {
512+ QueueDepthThreshold : saturationdetector .DefaultQueueDepthThreshold ,
513+ KVCacheUtilThreshold : saturationdetector .DefaultKVCacheUtilThreshold ,
514+ MetricsStalenessThreshold : saturationdetector .DefaultMetricsStalenessThreshold ,
515+ },
516+ },
517+ {
518+ name : "kv cache threshold above range, fallback to default" ,
519+ config : & configapi.SaturationDetector {
520+ QueueDepthThreshold : 10 ,
521+ KVCacheUtilThreshold : 1.5 ,
522+ MetricsStalenessThreshold : metav1.Duration {Duration : 100 * time .Millisecond },
523+ },
524+ expectedConfig : saturationdetector.Config {
525+ QueueDepthThreshold : 10 ,
526+ KVCacheUtilThreshold : saturationdetector .DefaultKVCacheUtilThreshold ,
527+ MetricsStalenessThreshold : 100 * time .Millisecond ,
528+ },
529+ },
530+ }
531+
532+ for _ , test := range tests {
533+ t .Run (test .name , func (t * testing.T ) {
534+ // validate configuration values are loaded from configuration struct properly, including the use of default values when provided value is invalid.
535+ sdConfig := loadSaturationDetectorConfig (test .config )
536+ if diff := cmp .Diff (test .expectedConfig , sdConfig ); diff != "" {
537+ t .Errorf ("Unexpected output (-want +got): %v" , diff )
538+ }
539+ })
540+ }
541+ }
542+
455543// The following multi-line string constants, cause false positive lint errors (dupword)
456544
457545// valid configuration
@@ -479,6 +567,10 @@ schedulingProfiles:
479567 - pluginRef: test-two
480568 weight: 50
481569 - pluginRef: testPicker
570+ featureGates:
571+ - dataLayer
572+ saturationDetector:
573+ metricsStalenessThreshold: 150ms
482574`
483575
484576// success with missing scheduling profiles
@@ -644,6 +736,21 @@ schedulingProfiles:
644736 - pluginRef: test2
645737`
646738
739+ // error with an unknown feature gate
740+ //
741+ //nolint:dupword
742+ const errorUnknownFeatureGateText = `
743+ apiVersion: inference.networking.x-k8s.io/v1alpha1
744+ kind: EndpointPickerConfig
745+ plugins:
746+ - name: test1
747+ type: test-one
748+ parameters:
749+ threshold: 10
750+ featureGates:
751+ - qwerty
752+ `
753+
647754// compile-time type validation
648755var _ framework.Filter = & test1 {}
649756
@@ -783,6 +890,8 @@ schedulingProfiles:
783890 - pluginRef: prefixCacheScorer
784891 weight: 50
785892 - pluginRef: maxScorePicker
893+ featureGates:
894+ - dataLayer
786895`
787896
788897// valid configuration, with default weight for scorer
0 commit comments