Repository: kubernetes-sigs/gateway-api-inference-extension

Issue:
Support scraping metrics from target running with TLS
<!-- Please only use this template for submitting enhancement requests -->

**What would you like to be added**:

Support scraping metrics from model server (vLLM) running with TLS.

**Why is this needed**:

Currently, if a model server is using TLS, the metrics scraping doesn't work.


Hints from issue comments:
@pierDipi: /assign

Solution patch:
diff --git a/cmd/epp/main.go b/cmd/epp/main.go
index b5e0617..bd15e87 100644
--- a/cmd/epp/main.go
+++ b/cmd/epp/main.go
@@ -25,8 +25,6 @@ import (
 )
 
 func main() {
-	// Register all known plugin factories
-	runner.RegisterAllPlugins()
 	// For adding out-of-tree plugins to the plugins registry, use the following:
 	// plugins.Register(my-out-of-tree-plugin-name, my-out-of-tree-plugin-factory-function)
 
diff --git a/cmd/epp/runner/register.go b/cmd/epp/runner/register.go
deleted file mode 100644
index 161a224..0000000
--- a/cmd/epp/runner/register.go
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
-Copyright 2025 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package runner
-
-import (
-	"context"
-
-	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/plugins"
-	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework/plugins/filter"
-	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework/plugins/multi/prefix"
-	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework/plugins/picker"
-	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework/plugins/profile"
-	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework/plugins/scorer"
-	testfilter "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework/plugins/test/filter"
-)
-
-// RegisterAllPlugins registers the factory functions of all known plugins
-func RegisterAllPlugins() {
-	plugins.Register(filter.DecisionTreeFilterType, filter.DecisionTreeFilterFactory)
-	plugins.Register(filter.LeastKVCacheFilterType, filter.LeastKVCacheFilterFactory)
-	plugins.Register(filter.LeastQueueFilterType, filter.LeastQueueFilterFactory)
-	plugins.Register(filter.LoraAffinityFilterType, filter.LoraAffinityFilterFactory)
-	plugins.Register(filter.LowQueueFilterType, filter.LowQueueFilterFactory)
-	plugins.Register(prefix.PrefixCachePluginType, prefix.PrefixCachePluginFactory)
-	plugins.Register(picker.MaxScorePickerType, picker.MaxScorePickerFactory)
-	plugins.Register(picker.RandomPickerType, picker.RandomPickerFactory)
-	plugins.Register(profile.SingleProfileHandlerType, profile.SingleProfileHandlerFactory)
-	plugins.Register(scorer.KvCacheScorerType, scorer.KvCacheScorerFactory)
-	plugins.Register(scorer.QueueScorerType, scorer.QueueScorerFactory)
-	// register filter for test purpose only (used in conformance tests)
-	plugins.Register(testfilter.HeaderBasedTestingFilterType, testfilter.HeaderBasedTestingFilterFactory)
-}
-
-// eppHandle is an implementation of the interface plugins.Handle
-type eppHandle struct {
-	ctx context.Context
-	plugins.HandlePlugins
-}
-
-// Context returns a context the plugins can use, if they need one
-func (h *eppHandle) Context() context.Context {
-	return h.ctx
-}
-
-// eppHandlePlugins implements the set of APIs to work with instantiated plugins
-type eppHandlePlugins struct {
-	plugins map[string]plugins.Plugin
-}
-
-// Plugin returns the named plugin instance
-func (h *eppHandlePlugins) Plugin(name string) plugins.Plugin {
-	return h.plugins[name]
-}
-
-// AddPlugin adds a plugin to the set of known plugin instances
-func (h *eppHandlePlugins) AddPlugin(name string, plugin plugins.Plugin) {
-	h.plugins[name] = plugin
-}
-
-// GetAllPlugins returns all of the known plugins
-func (h *eppHandlePlugins) GetAllPlugins() []plugins.Plugin {
-	result := make([]plugins.Plugin, 0)
-	for _, plugin := range h.plugins {
-		result = append(result, plugin)
-	}
-	return result
-}
-
-// GetAllPluginsWithNames returns al of the known plugins with their names
-func (h *eppHandlePlugins) GetAllPluginsWithNames() map[string]plugins.Plugin {
-	return h.plugins
-}
-
-func newEppHandle(ctx context.Context) *eppHandle {
-	return &eppHandle{
-		ctx: ctx,
-		HandlePlugins: &eppHandlePlugins{
-			plugins: map[string]plugins.Plugin{},
-		},
-	}
-}
diff --git a/cmd/epp/runner/runner.go b/cmd/epp/runner/runner.go
index 1c45ce6..53c2466 100644
--- a/cmd/epp/runner/runner.go
+++ b/cmd/epp/runner/runner.go
@@ -18,9 +18,11 @@ package runner
 
 import (
 	"context"
+	"crypto/tls"
 	"errors"
 	"flag"
 	"fmt"
+	"net/http"
 	"net/http/pprof"
 	"os"
 
@@ -44,9 +46,16 @@ import (
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datastore"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/metrics"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/metrics/collectors"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/plugins"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/requestcontrol"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/saturationdetector"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework/plugins/filter"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework/plugins/multi/prefix"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework/plugins/picker"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework/plugins/profile"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework/plugins/scorer"
+	testfilter "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework/plugins/test/filter"
 	runserver "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/server"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging"
 	"sigs.k8s.io/gateway-api-inference-extension/version"
@@ -138,7 +147,9 @@ var (
 
 	modelServerMetricsPort = flag.Int("model-server-metrics-port", 0, "Port to scrape metrics from pods. "+
 		"Default value will be set to InferencePool.Spec.TargetPortNumber if not set.")
-	modelServerMetricsPath = flag.String("model-server-metrics-path", "/metrics", "Path to scrape metrics from pods")
+	modelServerMetricsPath                    = flag.String("model-server-metrics-path", "/metrics", "Path to scrape metrics from pods")
+	modelServerMetricsScheme                  = flag.String("model-server-metrics-scheme", "http", "Scheme to scrape metrics from pods")
+	modelServerMetricsHttpsInsecureSkipVerify = flag.Bool("model-server-metrics-https-insecure-skip-verify", true, "When using 'https' scheme for 'model-server-metrics-scheme', configure 'InsecureSkipVerify' (default to true)")
 
 	setupLog = ctrl.Log.WithName("setup")
 )
@@ -169,13 +180,15 @@ func (r *Runner) WithSchedulerConfig(schedulerConfig *scheduling.SchedulerConfig
 func bindEnvToFlags() {
 	// map[ENV_VAR]flagName   – add more as needed
 	for env, flg := range map[string]string{
-		"GRPC_PORT":                     "grpc-port",
-		"GRPC_HEALTH_PORT":              "grpc-health-port",
-		"MODEL_SERVER_METRICS_PORT":     "model-server-metrics-port",
-		"MODEL_SERVER_METRICS_PATH":     "model-server-metrics-path",
-		"DESTINATION_ENDPOINT_HINT_KEY": "destination-endpoint-hint-key",
-		"POOL_NAME":                     "pool-name",
-		"POOL_NAMESPACE":                "pool-namespace",
+		"GRPC_PORT":                                       "grpc-port",
+		"GRPC_HEALTH_PORT":                                "grpc-health-port",
+		"MODEL_SERVER_METRICS_PORT":                       "model-server-metrics-port",
+		"MODEL_SERVER_METRICS_PATH":                       "model-server-metrics-path",
+		"MODEL_SERVER_METRICS_SCHEME":                     "model-server-metrics-scheme",
+		"MODEL_SERVER_METRICS_HTTPS_INSECURE_SKIP_VERIFY": "model-server-metrics-https-insecure-skip-verify",
+		"DESTINATION_ENDPOINT_HINT_KEY":                   "destination-endpoint-hint-key",
+		"POOL_NAME":                                       "pool-name",
+		"POOL_NAMESPACE":                                  "pool-namespace",
 		// durations & bools work too; flag.Set expects the *string* form
 		"REFRESH_METRICS_INTERVAL": "refresh-metrics-interval",
 		"SECURE_SERVING":           "secure-serving",
@@ -235,10 +248,26 @@ func (r *Runner) Run(ctx context.Context) error {
 		return err
 	}
 	verifyMetricMapping(*mapping, setupLog)
+
+	var metricsHttpClient *http.Client
+	if *modelServerMetricsScheme == "https" {
+		metricsHttpClient = &http.Client{
+			Transport: &http.Transport{
+				TLSClientConfig: &tls.Config{
+					InsecureSkipVerify: *modelServerMetricsHttpsInsecureSkipVerify,
+				},
+			},
+		}
+	} else {
+		metricsHttpClient = http.DefaultClient
+	}
+
 	pmf := backendmetrics.NewPodMetricsFactory(&backendmetrics.PodMetricsClientImpl{
-		MetricMapping:          mapping,
-		ModelServerMetricsPort: int32(*modelServerMetricsPort),
-		ModelServerMetricsPath: *modelServerMetricsPath,
+		MetricMapping:            mapping,
+		ModelServerMetricsPort:   int32(*modelServerMetricsPort),
+		ModelServerMetricsPath:   *modelServerMetricsPath,
+		ModelServerMetricsScheme: *modelServerMetricsScheme,
+		Client:                   metricsHttpClient,
 	}, *refreshMetricsInterval)
 
 	datastore := datastore.NewDatastore(ctx, pmf)
@@ -335,6 +364,23 @@ func (r *Runner) Run(ctx context.Context) error {
 	return nil
 }
 
+// registerInTreePlugins registers the factory functions of all known plugins
+func (r *Runner) registerInTreePlugins() {
+	plugins.Register(filter.DecisionTreeFilterType, filter.DecisionTreeFilterFactory)
+	plugins.Register(filter.LeastKVCacheFilterType, filter.LeastKVCacheFilterFactory)
+	plugins.Register(filter.LeastQueueFilterType, filter.LeastQueueFilterFactory)
+	plugins.Register(filter.LoraAffinityFilterType, filter.LoraAffinityFilterFactory)
+	plugins.Register(filter.LowQueueFilterType, filter.LowQueueFilterFactory)
+	plugins.Register(prefix.PrefixCachePluginType, prefix.PrefixCachePluginFactory)
+	plugins.Register(picker.MaxScorePickerType, picker.MaxScorePickerFactory)
+	plugins.Register(picker.RandomPickerType, picker.RandomPickerFactory)
+	plugins.Register(profile.SingleProfileHandlerType, profile.SingleProfileHandlerFactory)
+	plugins.Register(scorer.KvCacheScorerType, scorer.KvCacheScorerFactory)
+	plugins.Register(scorer.QueueScorerType, scorer.QueueScorerFactory)
+	// register filter for test purpose only (used in conformance tests)
+	plugins.Register(testfilter.HeaderBasedTestingFilterType, testfilter.HeaderBasedTestingFilterFactory)
+}
+
 func (r *Runner) parsePluginsConfiguration(ctx context.Context) error {
 	if *configText == "" && *configFile == "" {
 		return nil // configuring through code, not through file
@@ -351,7 +397,8 @@ func (r *Runner) parsePluginsConfiguration(ctx context.Context) error {
 		}
 	}
 
-	handle := newEppHandle(ctx)
+	r.registerInTreePlugins()
+	handle := plugins.NewEppHandle(ctx)
 	config, err := loader.LoadConfig(configBytes, handle)
 	if err != nil {
 		return fmt.Errorf("failed to load the configuration - %w", err)
@@ -421,6 +468,9 @@ func validateFlags() error {
 	if *configText != "" && *configFile != "" {
 		return fmt.Errorf("both the %q and %q flags can not be set at the same time", "configText", "configFile")
 	}
+	if *modelServerMetricsScheme != "http" && *modelServerMetricsScheme != "https" {
+		return fmt.Errorf("unexpected %q value for %q flag, it can only be set to 'http' or 'https'", *modelServerMetricsScheme, "model-server-metrics-scheme")
+	}
 
 	return nil
 }
diff --git a/config/charts/inferencepool/README.md b/config/charts/inferencepool/README.md
index bed4f33..b8a8d0a 100644
--- a/config/charts/inferencepool/README.md
+++ b/config/charts/inferencepool/README.md
@@ -24,26 +24,44 @@ Note that the provider name is needed to deploy provider-specific resources. If
 
 ### Install with Custom Environment Variables
 
-To set custom environment variables for the EndpointPicker deployment:
+To set custom environment variables for the EndpointPicker deployment, you can define them as free-form YAML in the `values.yaml` file:
+
+```yaml
+inferenceExtension:
+  env:
+    - name: FEATURE_FLAG_ENABLED
+      value: "true"
+    - name: CUSTOM_ENV_VAR
+      value: "custom_value"
+    - name: POD_IP
+      valueFrom:
+        fieldRef:
+          fieldPath: status.podIP
+```
+
+Then apply it with:
 
 ```txt
-$ helm install vllm-llama3-8b-instruct \
-  --set inferencePool.modelServers.matchLabels.app=vllm-llama3-8b-instruct \
-  --set provider.name=[none|gke] \
-  --set inferenceExtension.env.FEATURE_FLAG_ENABLED=true \
-  oci://us-central1-docker.pkg.dev/k8s-staging-images/gateway-api-inference-extension/charts/inferencepool --version v0
+$ helm install vllm-llama3-8b-instruct ./config/charts/inferencepool -f values.yaml
 ```
 
-Alternatively, you can define environment variables in a values file:
+### Install with Additional Ports
+
+To expose additional ports (e.g., for ZMQ), you can define them in the `values.yaml` file:
 
 ```yaml
-# values.yaml
 inferenceExtension:
-  env:
-    FEATURE_FLAG_ENABLED: "true"
+  extraContainerPorts:
+    - name: zmq
+      containerPort: 5557
+      protocol: TCP
+  extraServicePorts: # if need to expose the port for external communication
+    - name: zmq
+      port: 5557
+      protocol: TCP
 ```
 
-And apply it with:
+Then apply it with:
 
 ```txt
 $ helm install vllm-llama3-8b-instruct ./config/charts/inferencepool -f values.yaml
@@ -84,7 +102,10 @@ The following table list the configurable parameters of the chart.
 | `inferenceExtension.image.tag`              | Image tag of the endpoint picker.                                                                                      |
 | `inferenceExtension.image.pullPolicy`       | Image pull policy for the container. Possible values: `Always`, `IfNotPresent`, or `Never`. Defaults to `Always`.      |
 | `inferenceExtension.extProcPort`            | Port where the endpoint picker service is served for external processing. Defaults to `9002`.                          |
-| `inferenceExtension.env`                    | Map of environment variables to set in the endpoint picker container. Defaults to `{}`.                                |
+| `inferenceExtension.env`                    | List of environment variables to set in the endpoint picker container as free-form YAML. Defaults to `[]`.             |
+| `inferenceExtension.extraContainerPorts`    | List of additional container ports to expose. Defaults to `[]`.                                                       |
+| `inferenceExtension.extraServicePorts`      | List of additional service ports to expose. Defaults to `[]`.                                                         |
+| `inferenceExtension.logVerbosity`           | Logging verbosity level for the endpoint picker. Defaults to `"3"`.                                                   |
 | `provider.name`                             | Name of the Inference Gateway implementation being used. Possible values: `gke`. Defaults to `none`.                   |
 
 ## Notes
diff --git a/config/charts/inferencepool/templates/epp-deployment.yaml b/config/charts/inferencepool/templates/epp-deployment.yaml
index 89afbe8..5ab9e2d 100644
--- a/config/charts/inferencepool/templates/epp-deployment.yaml
+++ b/config/charts/inferencepool/templates/epp-deployment.yaml
@@ -28,7 +28,7 @@ spec:
         - --pool-namespace
         - {{ .Release.Namespace }}
         - --v
-        - "3"
+        - "{{ .Values.inferenceExtension.logVerbosity | default "3" }}"
         - --grpc-port
         - "9002"
         - --grpc-health-port
@@ -39,6 +39,9 @@ spec:
         - "config/{{ .Values.inferenceExtension.pluginsConfigFile }}"
         # https://pkg.go.dev/flag#hdr-Command_line_flag_syntax; space is only for non-bool flags
         - "--enable-pprof={{ .Values.inferenceExtension.enablePprof }}"
+        - "--model-server-metrics-path={{ .Values.inferenceExtension.modelServerMetricsPath }}"
+        - "--model-server-metrics-scheme={{ .Values.inferenceExtension.modelServerMetricsScheme }}"
+        - "--model-server-metrics-https-insecure-skip-verify={{ .Values.inferenceExtension.modelServerMetricsHttpsInsecureSkipVerify }}"
         {{- if eq (.Values.inferencePool.modelServerType | default "vllm") "triton-tensorrt-llm" }}
         - --total-queued-requests-metric
         - "nv_trt_llm_request_metrics{request_type=waiting}"
@@ -54,6 +57,9 @@ spec:
           containerPort: 9003
         - name: metrics
           containerPort: 9090
+        {{- with .Values.inferenceExtension.extraContainerPorts }}
+        {{- toYaml . | nindent 8 }}
+        {{- end }}
         livenessProbe:
           grpc:
             port: 9003
@@ -66,10 +72,9 @@ spec:
             service: inference-extension
           initialDelaySeconds: 5
           periodSeconds: 10
+        {{- with .Values.inferenceExtension.env }}
         env:
-        {{- range $key, $value := .Values.inferenceExtension.env }}
-        - name: {{ $key }}
-          value: {{ $value | quote }}
+        {{- toYaml . | nindent 8 }}
         {{- end }}
         volumeMounts:
         - name: plugins-config-volume
diff --git a/config/charts/inferencepool/templates/epp-service.yaml b/config/charts/inferencepool/templates/epp-service.yaml
index ed23db1..b1a48df 100644
--- a/config/charts/inferencepool/templates/epp-service.yaml
+++ b/config/charts/inferencepool/templates/epp-service.yaml
@@ -15,4 +15,7 @@ spec:
     - name: http-metrics
       protocol: TCP
       port: {{ .Values.inferenceExtension.metricsPort | default 9090 }}
+    {{- with .Values.inferenceExtension.extraServicePorts }}
+    {{- toYaml . | nindent 4 }}
+    {{- end }}
   type: ClusterIP
diff --git a/config/charts/inferencepool/values.yaml b/config/charts/inferencepool/values.yaml
index a070c4b..df8d947 100644
--- a/config/charts/inferencepool/values.yaml
+++ b/config/charts/inferencepool/values.yaml
@@ -6,8 +6,11 @@ inferenceExtension:
     tag: main
     pullPolicy: Always
   extProcPort: 9002
-  env: {}
+  env: []
   enablePprof: true # Enable pprof handlers for profiling and debugging
+  modelServerMetricsPath: "/metrics"
+  modelServerMetricsScheme: "http"
+  modelServerMetricsHttpsInsecureSkipVerify: true
   # This is the plugins configuration file. 
   pluginsConfigFile: "default-plugins.yaml"
   # pluginsCustomConfig:
@@ -32,6 +35,11 @@ inferenceExtension:
   # env:
   #   KV_CACHE_SCORE_WEIGHT: "1"
 
+  # Define additional container ports
+  extraContainerPorts: []
+  # Define additional service ports
+  extraServicePorts: []
+
 inferencePool:
   targetPortNumber: 8000
   modelServerType: vllm # vllm, triton-tensorrt-llm
diff --git a/pkg/epp/backend/metrics/metrics.go b/pkg/epp/backend/metrics/metrics.go
index 590685c..6a50faa 100644
--- a/pkg/epp/backend/metrics/metrics.go
+++ b/pkg/epp/backend/metrics/metrics.go
@@ -37,9 +37,12 @@ const (
 )
 
 type PodMetricsClientImpl struct {
-	MetricMapping          *MetricMapping
-	ModelServerMetricsPort int32
-	ModelServerMetricsPath string
+	MetricMapping            *MetricMapping
+	ModelServerMetricsPort   int32
+	ModelServerMetricsPath   string
+	ModelServerMetricsScheme string
+
+	Client *http.Client
 }
 
 // FetchMetrics fetches metrics from a given pod, clones the existing metrics object and returns an updated one.
@@ -49,7 +52,7 @@ func (p *PodMetricsClientImpl) FetchMetrics(ctx context.Context, pod *backend.Po
 	if err != nil {
 		return nil, fmt.Errorf("failed to create request: %v", err)
 	}
-	resp, err := http.DefaultClient.Do(req)
+	resp, err := p.Client.Do(req)
 	if err != nil {
 		return nil, fmt.Errorf("failed to fetch metrics from %s: %w", pod.NamespacedName, err)
 	}
@@ -73,7 +76,7 @@ func (p *PodMetricsClientImpl) getMetricEndpoint(pod *backend.Pod, targetPortNum
 	if p.ModelServerMetricsPort == 0 {
 		p.ModelServerMetricsPort = targetPortNumber
 	}
-	return fmt.Sprintf("http://%s:%d%s", pod.Address, p.ModelServerMetricsPort, p.ModelServerMetricsPath)
+	return fmt.Sprintf("%s://%s:%d%s", p.ModelServerMetricsScheme, pod.Address, p.ModelServerMetricsPort, p.ModelServerMetricsPath)
 }
 
 // promToPodMetrics updates internal pod metrics with scraped Prometheus metrics.
diff --git a/pkg/epp/flowcontrol/framework/mocks/mocks.go b/pkg/epp/flowcontrol/framework/mocks/mocks.go
index 141b735..804ba71 100644
--- a/pkg/epp/flowcontrol/framework/mocks/mocks.go
+++ b/pkg/epp/flowcontrol/framework/mocks/mocks.go
@@ -21,7 +21,7 @@ import (
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types"
 )
 
-// MockItemComparator provides a mock implementation of the `framework.ItemComparator` interface.
+// MockItemComparator is a simple stub mock for the `framework.ItemComparator` interface.
 type MockItemComparator struct {
 	FuncV      framework.ItemComparatorFunc
 	ScoreTypeV string
@@ -30,12 +30,11 @@ type MockItemComparator struct {
 func (m *MockItemComparator) Func() framework.ItemComparatorFunc { return m.FuncV }
 func (m *MockItemComparator) ScoreType() string                  { return m.ScoreTypeV }
 
-var _ framework.ItemComparator = &MockItemComparator{}
-
-// MockFlowQueueAccessor is a mock implementation of the `framework.FlowQueueAccessor` interface.
+// MockFlowQueueAccessor is a simple stub mock for the `framework.FlowQueueAccessor` interface.
+// It is used for tests that require static, predictable return values from a queue accessor.
+// For complex, stateful queue behavior, use the mock in `contracts/mocks.MockManagedQueue`.
 type MockFlowQueueAccessor struct {
 	NameV         string
-	CapabilitiesV []framework.QueueCapability
 	LenV          int
 	ByteSizeV     uint64
 	PeekHeadV     types.QueueItemAccessor
@@ -44,12 +43,15 @@ type MockFlowQueueAccessor struct {
 	PeekTailErrV  error
 	FlowSpecV     types.FlowSpecification
 	ComparatorV   framework.ItemComparator
+	CapabilitiesV []framework.QueueCapability
 }
 
 func (m *MockFlowQueueAccessor) Name() string                              { return m.NameV }
-func (m *MockFlowQueueAccessor) Capabilities() []framework.QueueCapability { return m.CapabilitiesV }
 func (m *MockFlowQueueAccessor) Len() int                                  { return m.LenV }
 func (m *MockFlowQueueAccessor) ByteSize() uint64                          { return m.ByteSizeV }
+func (m *MockFlowQueueAccessor) Comparator() framework.ItemComparator      { return m.ComparatorV }
+func (m *MockFlowQueueAccessor) FlowSpec() types.FlowSpecification         { return m.FlowSpecV }
+func (m *MockFlowQueueAccessor) Capabilities() []framework.QueueCapability { return m.CapabilitiesV }
 
 func (m *MockFlowQueueAccessor) PeekHead() (types.QueueItemAccessor, error) {
 	return m.PeekHeadV, m.PeekHeadErrV
@@ -59,51 +61,47 @@ func (m *MockFlowQueueAccessor) PeekTail() (types.QueueItemAccessor, error) {
 	return m.PeekTailV, m.PeekTailErrV
 }
 
-func (m *MockFlowQueueAccessor) Comparator() framework.ItemComparator { return m.ComparatorV }
-func (m *MockFlowQueueAccessor) FlowSpec() types.FlowSpecification    { return m.FlowSpecV }
-
 var _ framework.FlowQueueAccessor = &MockFlowQueueAccessor{}
 
-// MockPriorityBandAccessor is a mock implementation of the `framework.PriorityBandAccessor` interface.
+// MockPriorityBandAccessor is a behavioral mock for the `framework.MockPriorityBandAccessor` interface.
+// Simple accessors are configured with public value fields (e.g., `PriorityV`).
+// Complex methods with logic are configured with function fields (e.g., `IterateQueuesFunc`).
 type MockPriorityBandAccessor struct {
-	PriorityV      uint
-	PriorityNameV  string
-	FlowIDsV       []string
-	QueueV         framework.FlowQueueAccessor // Value to return for any Queue(flowID) call
-	QueueFuncV     func(flowID string) framework.FlowQueueAccessor
-	IterateQueuesV func(callback func(queue framework.FlowQueueAccessor) bool)
+	PriorityV         uint
+	PriorityNameV     string
+	FlowIDsFunc       func() []string
+	QueueFunc         func(flowID string) framework.FlowQueueAccessor
+	IterateQueuesFunc func(callback func(queue framework.FlowQueueAccessor) (keepIterating bool))
 }
 
 func (m *MockPriorityBandAccessor) Priority() uint       { return m.PriorityV }
 func (m *MockPriorityBandAccessor) PriorityName() string { return m.PriorityNameV }
-func (m *MockPriorityBandAccessor) FlowIDs() []string    { return m.FlowIDsV }
+
+func (m *MockPriorityBandAccessor) FlowIDs() []string {
+	if m.FlowIDsFunc != nil {
+		return m.FlowIDsFunc()
+	}
+	return nil
+}
 
 func (m *MockPriorityBandAccessor) Queue(flowID string) framework.FlowQueueAccessor {
-	if m.QueueFuncV != nil {
-		return m.QueueFuncV(flowID)
+	if m.QueueFunc != nil {
+		return m.QueueFunc(flowID)
 	}
-	return m.QueueV
+	return nil
 }
 
 func (m *MockPriorityBandAccessor) IterateQueues(callback func(queue framework.FlowQueueAccessor) bool) {
-	if m.IterateQueuesV != nil {
-		m.IterateQueuesV(callback)
-	} else {
-		// Default behavior: iterate based on FlowIDsV and QueueV/QueueFuncV
-		for _, id := range m.FlowIDsV {
-			q := m.Queue(id)
-			if q != nil { // Only call callback if queue exists
-				if !callback(q) {
-					return
-				}
-			}
-		}
+	if m.IterateQueuesFunc != nil {
+		m.IterateQueuesFunc(callback)
 	}
 }
 
 var _ framework.PriorityBandAccessor = &MockPriorityBandAccessor{}
 
-// MockSafeQueue is a mock implementation of the `framework.SafeQueue` interface.
+// MockSafeQueue is a simple stub mock for the `framework.SafeQueue` interface.
+// It is used for tests that need to control the exact return values of a queue's methods without simulating the queue's
+// internal logic or state.
 type MockSafeQueue struct {
 	NameV         string
 	CapabilitiesV []framework.QueueCapability
@@ -162,24 +160,48 @@ func (m *MockSafeQueue) Drain() ([]types.QueueItemAccessor, error) {
 
 var _ framework.SafeQueue = &MockSafeQueue{}
 
-// MockIntraFlowDispatchPolicy is a mock implementation of the `framework.IntraFlowDispatchPolicy` interface.
+// MockIntraFlowDispatchPolicy is a behavioral mock for the `framework.IntraFlowDispatchPolicy` interface.
+// Simple accessors are configured with public value fields (e.g., `NameV`).
+// Complex methods with logic are configured with function fields (e.g., `SelectItemFunc`).
 type MockIntraFlowDispatchPolicy struct {
 	NameV                      string
-	SelectItemV                types.QueueItemAccessor
-	SelectItemErrV             error
 	ComparatorV                framework.ItemComparator
 	RequiredQueueCapabilitiesV []framework.QueueCapability
+	SelectItemFunc             func(queue framework.FlowQueueAccessor) (types.QueueItemAccessor, error)
 }
 
 func (m *MockIntraFlowDispatchPolicy) Name() string                         { return m.NameV }
 func (m *MockIntraFlowDispatchPolicy) Comparator() framework.ItemComparator { return m.ComparatorV }
+func (m *MockIntraFlowDispatchPolicy) RequiredQueueCapabilities() []framework.QueueCapability {
+	return m.RequiredQueueCapabilitiesV
+}
 
 func (m *MockIntraFlowDispatchPolicy) SelectItem(queue framework.FlowQueueAccessor) (types.QueueItemAccessor, error) {
-	return m.SelectItemV, m.SelectItemErrV
+	if m.SelectItemFunc != nil {
+		return m.SelectItemFunc(queue)
+	}
+	return nil, nil
 }
 
-func (m *MockIntraFlowDispatchPolicy) RequiredQueueCapabilities() []framework.QueueCapability {
-	return m.RequiredQueueCapabilitiesV
+var _ framework.IntraFlowDispatchPolicy = &MockIntraFlowDispatchPolicy{}
+
+// MockInterFlowDispatchPolicy is a behavioral mock for the `framework.InterFlowDispatchPolicy` interface.
+// Simple accessors are configured with public value fields (e.g., `NameV`).
+// Complex methods with logic are configured with function fields (e.g., `SelectQueueFunc`).
+type MockInterFlowDispatchPolicy struct {
+	NameV           string
+	SelectQueueFunc func(band framework.PriorityBandAccessor) (framework.FlowQueueAccessor, error)
 }
 
-var _ framework.IntraFlowDispatchPolicy = &MockIntraFlowDispatchPolicy{}
+func (m *MockInterFlowDispatchPolicy) Name() string {
+	return m.NameV
+}
+
+func (m *MockInterFlowDispatchPolicy) SelectQueue(band framework.PriorityBandAccessor) (framework.FlowQueueAccessor, error) {
+	if m.SelectQueueFunc != nil {
+		return m.SelectQueueFunc(band)
+	}
+	return nil, nil
+}
+
+var _ framework.InterFlowDispatchPolicy = &MockInterFlowDispatchPolicy{}
diff --git a/pkg/epp/plugins/handle.go b/pkg/epp/plugins/handle.go
index f00aa50..8c9153c 100644
--- a/pkg/epp/plugins/handle.go
+++ b/pkg/epp/plugins/handle.go
@@ -44,6 +44,55 @@ type HandlePlugins interface {
 	GetAllPluginsWithNames() map[string]Plugin
 }
 
+// eppHandle is an implementation of the interface plugins.Handle
+type eppHandle struct {
+	ctx context.Context
+	HandlePlugins
+}
+
+// Context returns a context the plugins can use, if they need one
+func (h *eppHandle) Context() context.Context {
+	return h.ctx
+}
+
+// eppHandlePlugins implements the set of APIs to work with instantiated plugins
+type eppHandlePlugins struct {
+	plugins map[string]Plugin
+}
+
+// Plugin returns the named plugin instance
+func (h *eppHandlePlugins) Plugin(name string) Plugin {
+	return h.plugins[name]
+}
+
+// AddPlugin adds a plugin to the set of known plugin instances
+func (h *eppHandlePlugins) AddPlugin(name string, plugin Plugin) {
+	h.plugins[name] = plugin
+}
+
+// GetAllPlugins returns all of the known plugins
+func (h *eppHandlePlugins) GetAllPlugins() []Plugin {
+	result := make([]Plugin, 0)
+	for _, plugin := range h.plugins {
+		result = append(result, plugin)
+	}
+	return result
+}
+
+// GetAllPluginsWithNames returns al of the known plugins with their names
+func (h *eppHandlePlugins) GetAllPluginsWithNames() map[string]Plugin {
+	return h.plugins
+}
+
+func NewEppHandle(ctx context.Context) Handle {
+	return &eppHandle{
+		ctx: ctx,
+		HandlePlugins: &eppHandlePlugins{
+			plugins: map[string]Plugin{},
+		},
+	}
+}
+
 // PluginByType retrieves the specified plugin by name and verifies its type
 func PluginByType[P Plugin](handlePlugins HandlePlugins, name string) (P, error) {
 	var zero P
diff --git a/pkg/epp/scheduling/framework/plugins/picker/max_score_picker.go b/pkg/epp/scheduling/framework/plugins/picker/max_score_picker.go
index bf3ca8d..e7ee333 100644
--- a/pkg/epp/scheduling/framework/plugins/picker/max_score_picker.go
+++ b/pkg/epp/scheduling/framework/plugins/picker/max_score_picker.go
@@ -20,7 +20,9 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"math/rand"
 	"slices"
+	"time"
 
 	"sigs.k8s.io/controller-runtime/pkg/log"
 
@@ -58,13 +60,15 @@ func NewMaxScorePicker(maxNumOfEndpoints int) *MaxScorePicker {
 	return &MaxScorePicker{
 		typedName:         plugins.TypedName{Type: MaxScorePickerType, Name: MaxScorePickerType},
 		maxNumOfEndpoints: maxNumOfEndpoints,
+		randomGenerator:   rand.New(rand.NewSource(time.Now().UnixNano())),
 	}
 }
 
 // MaxScorePicker picks pod(s) with the maximum score from the list of candidates.
 type MaxScorePicker struct {
 	typedName         plugins.TypedName
-	maxNumOfEndpoints int // maximum number of endpoints to pick
+	maxNumOfEndpoints int        // maximum number of endpoints to pick
+	randomGenerator   *rand.Rand // randomGenerator for randomly pick endpoint on tie-break
 }
 
 // WithName sets the picker's name
@@ -83,6 +87,11 @@ func (p *MaxScorePicker) Pick(ctx context.Context, cycleState *types.CycleState,
 	log.FromContext(ctx).V(logutil.DEBUG).Info(fmt.Sprintf("Selecting maximum '%d' pods from %d candidates sorted by max score: %+v", p.maxNumOfEndpoints,
 		len(scoredPods), scoredPods))
 
+	// Shuffle in-place - needed for random tie break when scores are equal
+	p.randomGenerator.Shuffle(len(scoredPods), func(i, j int) {
+		scoredPods[i], scoredPods[j] = scoredPods[j], scoredPods[i]
+	})
+
 	slices.SortStableFunc(scoredPods, func(i, j *types.ScoredPod) int { // highest score first
 		if i.Score > j.Score {
 			return -1
diff --git a/pkg/epp/scheduling/framework/plugins/picker/random_picker.go b/pkg/epp/scheduling/framework/plugins/picker/random_picker.go
index bb272f1..eb62c37 100644
--- a/pkg/epp/scheduling/framework/plugins/picker/random_picker.go
+++ b/pkg/epp/scheduling/framework/plugins/picker/random_picker.go
@@ -21,6 +21,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"math/rand"
+	"time"
 
 	"sigs.k8s.io/controller-runtime/pkg/log"
 
@@ -57,6 +58,7 @@ func NewRandomPicker(maxNumOfEndpoints int) *RandomPicker {
 	return &RandomPicker{
 		typedName:         plugins.TypedName{Type: RandomPickerType, Name: RandomPickerType},
 		maxNumOfEndpoints: maxNumOfEndpoints,
+		randomGenerator:   rand.New(rand.NewSource(time.Now().UnixNano())),
 	}
 }
 
@@ -64,6 +66,7 @@ func NewRandomPicker(maxNumOfEndpoints int) *RandomPicker {
 type RandomPicker struct {
 	typedName         plugins.TypedName
 	maxNumOfEndpoints int
+	randomGenerator   *rand.Rand // randomGenerator for randomly pick endpoint on tie-break
 }
 
 // WithName sets the name of the picker.
@@ -83,7 +86,7 @@ func (p *RandomPicker) Pick(ctx context.Context, _ *types.CycleState, scoredPods
 		len(scoredPods), scoredPods))
 
 	// Shuffle in-place
-	rand.Shuffle(len(scoredPods), func(i, j int) {
+	p.randomGenerator.Shuffle(len(scoredPods), func(i, j int) {
 		scoredPods[i], scoredPods[j] = scoredPods[j], scoredPods[i]
 	})
 



Test patch:
diff --git a/pkg/epp/backend/metrics/metrics_test.go b/pkg/epp/backend/metrics/metrics_test.go
index 9f7c2b8..2dd8ca5 100644
--- a/pkg/epp/backend/metrics/metrics_test.go
+++ b/pkg/epp/backend/metrics/metrics_test.go
@@ -19,6 +19,7 @@ package metrics
 import (
 	"context"
 	"errors"
+	"net/http"
 	"reflect"
 	"strconv"
 	"strings"
@@ -495,7 +496,13 @@ func TestFetchMetrics(t *testing.T) {
 		},
 	}
 	existing := &MetricsState{}
-	p := &PodMetricsClientImpl{ModelServerMetricsPort: 9999, ModelServerMetricsPath: "/metrics"} // No MetricMapping needed for this basic test
+	// No MetricMapping needed for this basic test
+	p := &PodMetricsClientImpl{
+		ModelServerMetricsScheme: "http",
+		ModelServerMetricsPort:   9999,
+		ModelServerMetricsPath:   "/metrics",
+		Client:                   http.DefaultClient,
+	}
 
 	_, err := p.FetchMetrics(ctx, pod, existing, 9999) // Use a port that's unlikely to be in use
 	if err == nil {
diff --git a/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch/besthead/besthead_test.go b/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch/besthead/besthead_test.go
index ef7cbb3..eb3077b 100644
--- a/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch/besthead/besthead_test.go
+++ b/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch/besthead/besthead_test.go
@@ -31,8 +31,9 @@ import (
 )
 
 const (
-	flow1 = "flow1"
-	flow2 = "flow2"
+	flow1           = "flow1"
+	flow2           = "flow2"
+	commonScoreType = "enqueue_time_ns_asc"
 )
 
 // enqueueTimeComparatorFunc is a test utility. Lower enqueue time is better.
@@ -40,8 +41,6 @@ func enqueueTimeComparatorFunc(a, b types.QueueItemAccessor) bool {
 	return a.EnqueueTime().Before(b.EnqueueTime())
 }
 
-const commonScoreType = "enqueue_time_ns_asc"
-
 func newTestComparator() *frameworkmocks.MockItemComparator {
 	return &frameworkmocks.MockItemComparator{
 		ScoreTypeV: commonScoreType,
@@ -49,6 +48,26 @@ func newTestComparator() *frameworkmocks.MockItemComparator {
 	}
 }
 
+func newTestBand(queues map[string]framework.FlowQueueAccessor) *frameworkmocks.MockPriorityBandAccessor {
+	flowIDs := make([]string, 0, len(queues))
+	for id := range queues {
+		flowIDs = append(flowIDs, id)
+	}
+	return &frameworkmocks.MockPriorityBandAccessor{
+		FlowIDsFunc: func() []string { return flowIDs },
+		QueueFunc: func(id string) framework.FlowQueueAccessor {
+			return queues[id]
+		},
+		IterateQueuesFunc: func(iterator func(queue framework.FlowQueueAccessor) bool) {
+			for _, id := range flowIDs {
+				if !iterator(queues[id]) {
+					break
+				}
+			}
+		},
+	}
+}
+
 func TestBestHead_Name(t *testing.T) {
 	t.Parallel()
 	policy := newBestHead()
@@ -58,269 +77,155 @@ func TestBestHead_Name(t *testing.T) {
 func TestBestHead_SelectQueue(t *testing.T) {
 	t.Parallel()
 	policy := newBestHead()
+	now := time.Now()
+
+	itemBetter := typesmocks.NewMockQueueItemAccessor(10, "itemBetter", flow1)
+	itemBetter.EnqueueTimeV = now.Add(-10 * time.Second)
+	itemWorse := typesmocks.NewMockQueueItemAccessor(20, "itemWorse", flow2)
+	itemWorse.EnqueueTimeV = now.Add(-5 * time.Second)
+
+	queue1 := &frameworkmocks.MockFlowQueueAccessor{
+		LenV:        1,
+		PeekHeadV:   itemBetter,
+		FlowSpecV:   types.FlowSpecification{ID: flow1},
+		ComparatorV: newTestComparator(),
+	}
+	queue2 := &frameworkmocks.MockFlowQueueAccessor{
+		LenV:        1,
+		PeekHeadV:   itemWorse,
+		FlowSpecV:   types.FlowSpecification{ID: flow2},
+		ComparatorV: newTestComparator(),
+	}
+	queueEmpty := &frameworkmocks.MockFlowQueueAccessor{
+		LenV:         0,
+		PeekHeadErrV: framework.ErrQueueEmpty,
+		FlowSpecV:    types.FlowSpecification{ID: "flowEmpty"},
+		ComparatorV:  newTestComparator(),
+	}
 
-	t.Run("SelectQueue_Logic", func(t *testing.T) {
-		t.Parallel()
-
-		t.Run("BasicSelection_TwoQueues", func(t *testing.T) {
-			t.Parallel()
-			now := time.Now()
-			itemBetter := typesmocks.NewMockQueueItemAccessor(10, "itemBetter", flow1)
-			itemBetter.EnqueueTimeV = now.Add(-10 * time.Second) // Earlier enqueue time = better
-			queue1 := &frameworkmocks.MockFlowQueueAccessor{
-				LenV:        1,
-				PeekHeadV:   itemBetter,
-				FlowSpecV:   types.FlowSpecification{ID: flow1},
-				ComparatorV: newTestComparator(),
-			}
-
-			itemWorse := typesmocks.NewMockQueueItemAccessor(20, "itemWorse", flow2)
-			itemWorse.EnqueueTimeV = now.Add(-5 * time.Second) // Later enqueue time = worse
-			queue2 := &frameworkmocks.MockFlowQueueAccessor{
-				LenV:        1,
-				PeekHeadV:   itemWorse,
-				FlowSpecV:   types.FlowSpecification{ID: flow2},
-				ComparatorV: newTestComparator(),
-			}
-
-			mockBand := &frameworkmocks.MockPriorityBandAccessor{
-				FlowIDsV: []string{flow1, flow2},
-				QueueFuncV: func(id string) framework.FlowQueueAccessor {
-					if id == flow1 {
-						return queue1
-					}
-					if id == flow2 {
-						return queue2
-					}
-					return nil
+	testCases := []struct {
+		name            string
+		band            framework.PriorityBandAccessor
+		expectedQueueID string
+		expectedErr     error
+		shouldPanic     bool
+	}{
+		{
+			name: "BasicSelection_TwoQueues",
+			band: newTestBand(map[string]framework.FlowQueueAccessor{
+				flow1: queue1,
+				flow2: queue2,
+			}),
+			expectedQueueID: flow1,
+		},
+		{
+			name: "IgnoresEmptyQueues",
+			band: newTestBand(map[string]framework.FlowQueueAccessor{
+				flow1:       queue1,
+				"flowEmpty": queueEmpty,
+				flow2:       queue2,
+			}),
+			expectedQueueID: flow1,
+		},
+		{
+			name:            "SingleNonEmptyQueue",
+			band:            newTestBand(map[string]framework.FlowQueueAccessor{flow1: queue1}),
+			expectedQueueID: flow1,
+		},
+		{
+			name: "ComparatorCompatibility",
+			band: newTestBand(map[string]framework.FlowQueueAccessor{
+				flow1: &frameworkmocks.MockFlowQueueAccessor{
+					LenV:        1,
+					PeekHeadV:   itemBetter,
+					FlowSpecV:   types.FlowSpecification{ID: flow1},
+					ComparatorV: &frameworkmocks.MockItemComparator{ScoreTypeV: "typeA", FuncV: enqueueTimeComparatorFunc},
 				},
-			}
-
-			selected, err := policy.SelectQueue(mockBand)
-			require.NoError(t, err, "SelectQueue should not error with valid inputs")
-			require.NotNil(t, selected, "SelectQueue should select a queue")
-			assert.Equal(t, flow1, selected.FlowSpec().ID, "Should select queue1 with the better item")
-		})
-
-		t.Run("IgnoresEmptyQueues", func(t *testing.T) {
-			t.Parallel()
-			now := time.Now()
-			itemBetter := typesmocks.NewMockQueueItemAccessor(10, "itemBetter", flow1)
-			itemBetter.EnqueueTimeV = now.Add(-10 * time.Second)
-			queue1 := &frameworkmocks.MockFlowQueueAccessor{ // Non-empty
-				LenV:        1,
-				PeekHeadV:   itemBetter,
-				FlowSpecV:   types.FlowSpecification{ID: flow1},
-				ComparatorV: newTestComparator(),
-			}
-			queueEmpty := &frameworkmocks.MockFlowQueueAccessor{ // Empty
-				LenV:         0,
-				PeekHeadErrV: framework.ErrQueueEmpty,
-				FlowSpecV:    types.FlowSpecification{ID: "flowEmpty"},
-				ComparatorV:  newTestComparator(),
-			}
-			itemWorse := typesmocks.NewMockQueueItemAccessor(20, "itemWorse", flow2)
-			itemWorse.EnqueueTimeV = now.Add(-5 * time.Second)
-			queue2 := &frameworkmocks.MockFlowQueueAccessor{ // Non-empty
-				LenV:        1,
-				PeekHeadV:   itemWorse,
-				FlowSpecV:   types.FlowSpecification{ID: flow2},
-				ComparatorV: newTestComparator(),
-			}
-
-			mockBand := &frameworkmocks.MockPriorityBandAccessor{
-				FlowIDsV: []string{"flowEmpty", flow1, flow2},
-				QueueFuncV: func(id string) framework.FlowQueueAccessor {
-					switch id {
-					case flow1:
-						return queue1
-					case "flowEmpty":
-						return queueEmpty
-					case flow2:
-						return queue2
-					}
-					return nil
+				flow2: &frameworkmocks.MockFlowQueueAccessor{
+					LenV:        1,
+					PeekHeadV:   itemWorse,
+					FlowSpecV:   types.FlowSpecification{ID: flow2},
+					ComparatorV: &frameworkmocks.MockItemComparator{ScoreTypeV: "typeB", FuncV: enqueueTimeComparatorFunc},
 				},
-			}
-			selected, err := policy.SelectQueue(mockBand)
-			require.NoError(t, err, "SelectQueue should not error when some queues are empty")
-			require.NotNil(t, selected, "SelectQueue should select a queue from the non-empty set")
-			assert.Equal(t, flow1, selected.FlowSpec().ID, "Should select queue1, ignoring empty queue")
-		})
-
-		t.Run("SingleNonEmptyQueue", func(t *testing.T) {
-			t.Parallel()
-			item := typesmocks.NewMockQueueItemAccessor(10, "item", flow1)
-			queue1 := &frameworkmocks.MockFlowQueueAccessor{
-				LenV:        1,
-				PeekHeadV:   item,
-				FlowSpecV:   types.FlowSpecification{ID: flow1},
-				ComparatorV: newTestComparator(),
-			}
-			mockBand := &frameworkmocks.MockPriorityBandAccessor{
-				FlowIDsV: []string{flow1},
-				QueueFuncV: func(id string) framework.FlowQueueAccessor {
-					if id == flow1 {
-						return queue1
-					}
-					return nil
+			}),
+			expectedErr: framework.ErrIncompatiblePriorityType,
+		},
+		{
+			name: "QueuePeekHeadErrors",
+			band: newTestBand(map[string]framework.FlowQueueAccessor{
+				flow1: &frameworkmocks.MockFlowQueueAccessor{
+					LenV:         1,
+					PeekHeadErrV: errors.New("peek error"),
+					FlowSpecV:    types.FlowSpecification{ID: flow1},
+					ComparatorV:  newTestComparator(),
 				},
-			}
-			selected, err := policy.SelectQueue(mockBand)
-			require.NoError(t, err, "SelectQueue should not error for a single valid queue")
-			require.NotNil(t, selected, "SelectQueue should select the only available queue")
-			assert.Equal(t, flow1, selected.FlowSpec().ID, "Should select the only non-empty queue")
-		})
-	})
-
-	t.Run("SelectQueue_ComparatorCompatibility", func(t *testing.T) {
-		t.Parallel()
-		queue1 := &frameworkmocks.MockFlowQueueAccessor{
-			LenV:      1,
-			PeekHeadV: typesmocks.NewMockQueueItemAccessor(10, "item1", flow1),
-			FlowSpecV: types.FlowSpecification{ID: flow1},
-			ComparatorV: &frameworkmocks.MockItemComparator{
-				ScoreTypeV: "typeA",
-				FuncV:      enqueueTimeComparatorFunc,
-			},
-		}
-		queue2 := &frameworkmocks.MockFlowQueueAccessor{
-			LenV:      1,
-			PeekHeadV: typesmocks.NewMockQueueItemAccessor(20, "item2", flow2),
-			FlowSpecV: types.FlowSpecification{ID: flow2},
-			ComparatorV: &frameworkmocks.MockItemComparator{
-				ScoreTypeV: "typeB", // Different ScoreType
-				FuncV:      enqueueTimeComparatorFunc,
-			},
-		}
-		mockBand := &frameworkmocks.MockPriorityBandAccessor{
-			FlowIDsV: []string{flow1, flow2},
-			QueueFuncV: func(id string) framework.FlowQueueAccessor {
-				if id == flow1 {
-					return queue1
-				}
-				if id == flow2 {
-					return queue2
-				}
-				return nil
-			},
-		}
-		selected, err := policy.SelectQueue(mockBand)
-		assert.Nil(t, selected, "SelectQueue should return a nil queue on incompatible comparator error")
-		require.Error(t, err, "SelectQueue should return an error for incompatible comparators")
-		assert.ErrorIs(t, err, framework.ErrIncompatiblePriorityType, "Error should be ErrIncompatiblePriorityType")
-	})
-
-	t.Run("SelectQueue_EdgeCase", func(t *testing.T) {
-		// Scenarios like NilBand, NoQueuesInBand, AllQueuesEmpty are covered by functional tests.
-		// These tests focus on edge cases more specific to BestHead's logic.
-		t.Parallel()
-
-		t.Run("QueuePeekHeadErrors", func(t *testing.T) {
-			t.Parallel()
-			queue1 := &frameworkmocks.MockFlowQueueAccessor{
-				LenV:         1, // Non-empty
-				PeekHeadErrV: errors.New("internal peek error"),
-				FlowSpecV:    types.FlowSpecification{ID: flow1},
-				ComparatorV:  newTestComparator(),
-			}
-			queue2 := &frameworkmocks.MockFlowQueueAccessor{
-				LenV:        1,
-				PeekHeadV:   typesmocks.NewMockQueueItemAccessor(10, "item2", flow2),
-				FlowSpecV:   types.FlowSpecification{ID: flow2},
-				ComparatorV: newTestComparator(),
-			}
-
-			mockBand := &frameworkmocks.MockPriorityBandAccessor{
-				FlowIDsV: []string{flow1, flow2},
-				QueueFuncV: func(id string) framework.FlowQueueAccessor {
-					if id == flow1 {
-						return queue1
-					}
-					if id == flow2 {
-						return queue2
-					}
-					return nil
+				flow2: queue2,
+			}),
+			expectedQueueID: flow2,
+		},
+		{
+			name: "QueueComparatorIsNil",
+			band: newTestBand(map[string]framework.FlowQueueAccessor{
+				flow1: &frameworkmocks.MockFlowQueueAccessor{
+					LenV:        1,
+					PeekHeadV:   itemBetter,
+					FlowSpecV:   types.FlowSpecification{ID: flow1},
+					ComparatorV: nil,
 				},
-			}
-			selected, err := policy.SelectQueue(mockBand)
-			require.NoError(t, err, "Policy should gracefully skip queue with PeekHead error")
-			require.NotNil(t, selected, "Policy should select the valid queue (flow2)")
-			assert.Equal(t, flow2, selected.FlowSpec().ID, "Should select the valid queue when the other has a peek error")
-		})
+				flow2: queue2,
+			}),
+			shouldPanic: true,
+		},
+		{
+			name: "ComparatorFuncIsNil",
+			band: newTestBand(map[string]framework.FlowQueueAccessor{
+				flow1: &frameworkmocks.MockFlowQueueAccessor{
+					LenV:        1,
+					PeekHeadV:   itemBetter,
+					FlowSpecV:   types.FlowSpecification{ID: flow1},
+					ComparatorV: &frameworkmocks.MockItemComparator{ScoreTypeV: commonScoreType, FuncV: nil},
+				},
+				flow2: queue2,
+			}),
+			shouldPanic: true,
+		},
+		{
+			name: "AllQueuesEmpty",
+			band: newTestBand(map[string]framework.FlowQueueAccessor{
+				"empty1": queueEmpty,
+				"empty2": queueEmpty,
+			}),
+		},
+		{
+			name: "NilBand",
+			band: nil,
+		},
+	}
 
-		t.Run("QueueComparatorIsNil", func(t *testing.T) {
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
 			t.Parallel()
-			queue1 := &frameworkmocks.MockFlowQueueAccessor{
-				LenV:        1,
-				PeekHeadV:   typesmocks.NewMockQueueItemAccessor(10, "item1", flow1),
-				FlowSpecV:   types.FlowSpecification{ID: flow1},
-				ComparatorV: nil, // Nil Comparator
-			}
-			queue2 := &frameworkmocks.MockFlowQueueAccessor{ // A valid queue
-				LenV:        1,
-				PeekHeadV:   typesmocks.NewMockQueueItemAccessor(20, "item2", flow2),
-				FlowSpecV:   types.FlowSpecification{ID: flow2},
-				ComparatorV: newTestComparator(),
-			}
 
-			mockBand := &frameworkmocks.MockPriorityBandAccessor{
-				FlowIDsV: []string{flow1, flow2},
-				QueueFuncV: func(id string) framework.FlowQueueAccessor {
-					if id == flow1 {
-						return queue1
-					}
-					if id == flow2 {
-						return queue2
-					}
-					return nil
-				},
+			if tc.shouldPanic {
+				assert.Panics(t, func() { _, _ = policy.SelectQueue(tc.band) }, "SelectQueue should panic for this edge case")
+				return
 			}
 
-			require.NotNil(t, queue1, "Setup: queue1 should not be nil")
-			require.Nil(t, queue1.Comparator(), "Setup: queue1's comparator should be nil for this test")
-			assert.Panics(t, func() {
-				_, _ = policy.SelectQueue(mockBand)
-			}, "Policy should panic if a queue's comparator is nil when it's about to be used.")
-		})
+			selected, err := policy.SelectQueue(tc.band)
 
-		t.Run("ComparatorFuncIsNil", func(t *testing.T) {
-			t.Parallel()
-			queue1 := &frameworkmocks.MockFlowQueueAccessor{
-				LenV:      1,
-				PeekHeadV: typesmocks.NewMockQueueItemAccessor(10, "item1", flow1),
-				FlowSpecV: types.FlowSpecification{ID: flow1},
-				ComparatorV: &frameworkmocks.MockItemComparator{ // Comparator exists
-					ScoreTypeV: commonScoreType,
-					FuncV:      nil, // But its Func is nil
-				},
-			}
-			queue2 := &frameworkmocks.MockFlowQueueAccessor{ // A valid queue
-				LenV:        1,
-				PeekHeadV:   typesmocks.NewMockQueueItemAccessor(20, "item2", flow2),
-				FlowSpecV:   types.FlowSpecification{ID: flow2},
-				ComparatorV: newTestComparator(),
-			}
-			mockBand := &frameworkmocks.MockPriorityBandAccessor{
-				FlowIDsV: []string{flow1, flow2},
-				QueueFuncV: func(id string) framework.FlowQueueAccessor {
-					if id == flow1 {
-						return queue1
-					}
-					if id == flow2 {
-						return queue2
-					}
-					return nil
-				},
+			if tc.expectedErr != nil {
+				require.Error(t, err, "SelectQueue should return an error")
+				assert.ErrorIs(t, err, tc.expectedErr, "The returned error should match the expected error type")
+				assert.Nil(t, selected, "No queue should be selected when an error occurs")
+			} else {
+				require.NoError(t, err, "SelectQueue should not return an error for valid inputs")
+				if tc.expectedQueueID == "" {
+					assert.Nil(t, selected, "No queue should be selected")
+				} else {
+					require.NotNil(t, selected, "A queue should have been selected")
+					assert.Equal(t, tc.expectedQueueID, selected.FlowSpec().ID, "The selected queue should have the expected ID")
+				}
 			}
-
-			// Similar to nil comparator, current bestHead panics if Func is nil.
-			require.NotNil(t, queue1, "Setup: queue1 should not be nil")
-			require.NotNil(t, queue1.Comparator(), "Setup: queue1's comparator should NOT be nil for this test")
-			require.Nil(t, queue1.Comparator().Func(), "Setup: queue1's comparator Func should be nil for this test")
-			assert.Panics(t, func() {
-				_, _ = policy.SelectQueue(mockBand)
-			}, "Policy should panic if a comparator's func is nil when it's about to be used.")
 		})
-	})
+	}
 }
diff --git a/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch/functional_test.go b/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch/functional_test.go
index dd5d31a..77a325a 100644
--- a/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch/functional_test.go
+++ b/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch/functional_test.go
@@ -85,8 +85,8 @@ func runSelectQueueConformanceTests(t *testing.T, policy framework.InterFlowDisp
 		{
 			name: "With an empty priority band accessor",
 			band: &frameworkmocks.MockPriorityBandAccessor{
-				FlowIDsV:       []string{},
-				IterateQueuesV: func(callback func(queue framework.FlowQueueAccessor) bool) { /* no-op */ },
+				FlowIDsFunc:       func() []string { return []string{} },
+				IterateQueuesFunc: func(callback func(queue framework.FlowQueueAccessor) bool) { /* no-op */ },
 			},
 			expectErr: false,
 			expectNil: true,
@@ -94,8 +94,8 @@ func runSelectQueueConformanceTests(t *testing.T, policy framework.InterFlowDisp
 		{
 			name: "With a band that has one empty queue",
 			band: &frameworkmocks.MockPriorityBandAccessor{
-				FlowIDsV: []string{flowIDEmpty},
-				QueueFuncV: func(fID string) framework.FlowQueueAccessor {
+				FlowIDsFunc: func() []string { return []string{flowIDEmpty} },
+				QueueFunc: func(fID string) framework.FlowQueueAccessor {
 					if fID == flowIDEmpty {
 						return mockQueueEmpty
 					}
@@ -108,8 +108,8 @@ func runSelectQueueConformanceTests(t *testing.T, policy framework.InterFlowDisp
 		{
 			name: "With a band that has multiple empty queues",
 			band: &frameworkmocks.MockPriorityBandAccessor{
-				FlowIDsV: []string{flowIDEmpty, "flow-empty-2"},
-				QueueFuncV: func(fID string) framework.FlowQueueAccessor {
+				FlowIDsFunc: func() []string { return []string{flowIDEmpty, "flow-empty-2"} },
+				QueueFunc: func(fID string) framework.FlowQueueAccessor {
 					return mockQueueEmpty
 				},
 			},
diff --git a/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch/roundrobin/roundrobin_test.go b/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch/roundrobin/roundrobin_test.go
index 804a4c8..f2255f0 100644
--- a/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch/roundrobin/roundrobin_test.go
+++ b/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch/roundrobin/roundrobin_test.go
@@ -46,8 +46,8 @@ func TestRoundRobin_SelectQueue_Logic(t *testing.T) {
 	queue3 := &frameworkmocks.MockFlowQueueAccessor{LenV: 3, FlowSpecV: types.FlowSpecification{ID: "flow3"}}
 
 	mockBand := &frameworkmocks.MockPriorityBandAccessor{
-		FlowIDsV: []string{"flow3", "flow1", "flow2"}, // Unsorted to test sorting
-		QueueFuncV: func(id string) framework.FlowQueueAccessor {
+		FlowIDsFunc: func() []string { return []string{"flow3", "flow1", "flow2"} }, // Unsorted to test sorting
+		QueueFunc: func(id string) framework.FlowQueueAccessor {
 			switch id {
 			case "flow1":
 				return queue1
@@ -68,7 +68,8 @@ func TestRoundRobin_SelectQueue_Logic(t *testing.T) {
 		selected, err := policy.SelectQueue(mockBand)
 		require.NoError(t, err, "SelectQueue should not error on a valid band")
 		require.NotNil(t, selected, "SelectQueue should have selected a queue")
-		assert.Equal(t, expectedOrder[i], selected.FlowSpec().ID, "Cycle 1, selection %d should be %s", i+1, expectedOrder[i])
+		assert.Equal(t, expectedOrder[i], selected.FlowSpec().ID,
+			"Cycle 1, selection %d should be %s", i+1, expectedOrder[i])
 	}
 
 	// Second cycle (wraps around)
@@ -76,7 +77,8 @@ func TestRoundRobin_SelectQueue_Logic(t *testing.T) {
 		selected, err := policy.SelectQueue(mockBand)
 		require.NoError(t, err, "SelectQueue should not error on a valid band")
 		require.NotNil(t, selected, "SelectQueue should have selected a queue")
-		assert.Equal(t, expectedOrder[i], selected.FlowSpec().ID, "Cycle 2, selection %d should be %s", i+1, expectedOrder[i])
+		assert.Equal(t, expectedOrder[i], selected.FlowSpec().ID,
+			"Cycle 2, selection %d should be %s", i+1, expectedOrder[i])
 	}
 }
 
@@ -90,8 +92,8 @@ func TestRoundRobin_SelectQueue_SkipsEmptyQueues(t *testing.T) {
 	queue3 := &frameworkmocks.MockFlowQueueAccessor{LenV: 3, FlowSpecV: types.FlowSpecification{ID: "flow3"}}
 
 	mockBand := &frameworkmocks.MockPriorityBandAccessor{
-		FlowIDsV: []string{"flow1", "flowEmpty", "flow3"},
-		QueueFuncV: func(id string) framework.FlowQueueAccessor {
+		FlowIDsFunc: func() []string { return []string{"flow1", "flowEmpty", "flow3"} },
+		QueueFunc: func(id string) framework.FlowQueueAccessor {
 			switch id {
 			case "flow1":
 				return queue1
@@ -129,8 +131,8 @@ func TestRoundRobin_SelectQueue_HandlesDynamicFlows(t *testing.T) {
 	queue1 := &frameworkmocks.MockFlowQueueAccessor{LenV: 1, FlowSpecV: types.FlowSpecification{ID: "flow1"}}
 	queue2 := &frameworkmocks.MockFlowQueueAccessor{LenV: 1, FlowSpecV: types.FlowSpecification{ID: "flow2"}}
 	mockBand := &frameworkmocks.MockPriorityBandAccessor{
-		FlowIDsV: []string{"flow1", "flow2"},
-		QueueFuncV: func(id string) framework.FlowQueueAccessor {
+		FlowIDsFunc: func() []string { return []string{"flow1", "flow2"} },
+		QueueFunc: func(id string) framework.FlowQueueAccessor {
 			if id == "flow1" {
 				return queue1
 			}
@@ -146,8 +148,8 @@ func TestRoundRobin_SelectQueue_HandlesDynamicFlows(t *testing.T) {
 
 	// --- Simulate adding a flow ---
 	queue3 := &frameworkmocks.MockFlowQueueAccessor{LenV: 1, FlowSpecV: types.FlowSpecification{ID: "flow3"}}
-	mockBand.FlowIDsV = []string{"flow1", "flow2", "flow3"}
-	mockBand.QueueFuncV = func(id string) framework.FlowQueueAccessor {
+	mockBand.FlowIDsFunc = func() []string { return []string{"flow1", "flow2", "flow3"} }
+	mockBand.QueueFunc = func(id string) framework.FlowQueueAccessor {
 		switch id {
 		case "flow1":
 			return queue1
@@ -172,7 +174,7 @@ func TestRoundRobin_SelectQueue_HandlesDynamicFlows(t *testing.T) {
 	assert.Equal(t, "flow3", selected.FlowSpec().ID, "Next selection should be the new flow3")
 
 	// --- Simulate removing a flow ---
-	mockBand.FlowIDsV = []string{"flow1", "flow3"} // flow2 is removed
+	mockBand.FlowIDsFunc = func() []string { return []string{"flow1", "flow3"} } // flow2 is removed
 
 	// Next selection should wrap around and pick flow1
 	selected, err = policy.SelectQueue(mockBand)
@@ -198,8 +200,8 @@ func TestRoundRobin_SelectQueue_Concurrency(t *testing.T) {
 			numQueues := int64(len(queues))
 
 			mockBand := &frameworkmocks.MockPriorityBandAccessor{
-				FlowIDsV: []string{"flow1", "flow2", "flow3"},
-				QueueFuncV: func(id string) framework.FlowQueueAccessor {
+				FlowIDsFunc: func() []string { return []string{"flow1", "flow2", "flow3"} },
+				QueueFunc: func(id string) framework.FlowQueueAccessor {
 					for _, q := range queues {
 						if q.FlowSpec().ID == id {
 							return q
diff --git a/pkg/epp/scheduling/framework/plugins/picker/picker_test.go b/pkg/epp/scheduling/framework/plugins/picker/picker_test.go
index 2089ed3..2c3aceb 100644
--- a/pkg/epp/scheduling/framework/plugins/picker/picker_test.go
+++ b/pkg/epp/scheduling/framework/plugins/picker/picker_test.go
@@ -21,6 +21,7 @@ import (
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
 	k8stypes "k8s.io/apimachinery/pkg/types"
 
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend"
@@ -34,10 +35,11 @@ func TestPickMaxScorePicker(t *testing.T) {
 	pod3 := &types.PodMetrics{Pod: &backend.Pod{NamespacedName: k8stypes.NamespacedName{Name: "pod3"}}}
 
 	tests := []struct {
-		name   string
-		picker framework.Picker
-		input  []*types.ScoredPod
-		output []types.Pod
+		name               string
+		picker             framework.Picker
+		input              []*types.ScoredPod
+		output             []types.Pod
+		tieBreakCandidates int // tie break is random, specify how many candidate with max score
 	}{
 		{
 			name:   "Single max score",
@@ -63,6 +65,7 @@ func TestPickMaxScorePicker(t *testing.T) {
 				&types.ScoredPod{Pod: pod1, Score: 50},
 				&types.ScoredPod{Pod: pod2, Score: 50},
 			},
+			tieBreakCandidates: 2,
 		},
 		{
 			name:   "Multiple results sorted by highest score, more pods than needed",
@@ -104,6 +107,7 @@ func TestPickMaxScorePicker(t *testing.T) {
 				&types.ScoredPod{Pod: pod3, Score: 30},
 				&types.ScoredPod{Pod: pod2, Score: 25},
 			},
+			tieBreakCandidates: 2,
 		},
 	}
 
@@ -112,6 +116,19 @@ func TestPickMaxScorePicker(t *testing.T) {
 			result := test.picker.Pick(context.Background(), types.NewCycleState(), test.input)
 			got := result.TargetPods
 
+			if test.tieBreakCandidates > 0 {
+				testMaxScoredPods := test.output[:test.tieBreakCandidates]
+				gotMaxScoredPods := got[:test.tieBreakCandidates]
+				diff := cmp.Diff(testMaxScoredPods, gotMaxScoredPods, cmpopts.SortSlices(func(a, b types.Pod) bool {
+					return a.String() < b.String() // predictable order within the pods with equal scores
+				}))
+				if diff != "" {
+					t.Errorf("Unexpected output (-want +got): %v", diff)
+				}
+				test.output = test.output[test.tieBreakCandidates:]
+				got = got[test.tieBreakCandidates:]
+			}
+
 			if diff := cmp.Diff(test.output, got); diff != "" {
 				t.Errorf("Unexpected output (-want +got): %v", diff)
 			}