Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/epp/runner/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (

// 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)
Expand Down
52 changes: 29 additions & 23 deletions cmd/epp/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,29 +216,10 @@ func (r *Runner) Run(ctx context.Context) error {
return err
}

if len(*configText) != 0 || len(*configFile) != 0 {
theConfig, err := loader.LoadConfig([]byte(*configText), *configFile)
if err != nil {
setupLog.Error(err, "Failed to load the configuration")
return err
}

epp := newEppHandle()

err = loader.LoadPluginReferences(theConfig.Plugins, epp)
if err != nil {
setupLog.Error(err, "Failed to instantiate the plugins")
return err
}

r.schedulerConfig, err = loader.LoadSchedulerConfig(theConfig.SchedulingProfiles, epp)
if err != nil {
setupLog.Error(err, "Failed to create Scheduler configuration")
return err
}

// Add requestControl plugins
r.requestControlConfig.AddPlugins(epp.Plugins().GetAllPlugins()...)
err = r.parseConfiguration()
if err != nil {
setupLog.Error(err, "Failed to parse the configuration")
return err
}

// --- Initialize Core EPP Components ---
Expand Down Expand Up @@ -328,6 +309,31 @@ func (r *Runner) initializeScheduler() (*scheduling.Scheduler, error) {
return scheduler, nil
}

func (r *Runner) parseConfiguration() error {
if len(*configText) != 0 || len(*configFile) != 0 {
theConfig, err := loader.LoadConfig([]byte(*configText), *configFile)
if err != nil {
return fmt.Errorf("failed to load the configuration - %w", err)
}

epp := newEppHandle()

err = loader.LoadPluginReferences(theConfig.Plugins, epp)
if err != nil {
return fmt.Errorf("failed to instantiate the plugins - %w", err)
}

r.schedulerConfig, err = loader.LoadSchedulerConfig(theConfig.SchedulingProfiles, epp)
if err != nil {
return fmt.Errorf("failed to create Scheduler configuration - %w", err)
}

// Add requestControl plugins
r.requestControlConfig.AddPlugins(epp.Plugins().GetAllPlugins()...)
}
return nil
}

func initLogging(opts *zap.Options) {
// Unless -zap-log-level is explicitly set, use -v
useV := true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,21 @@ package filter

import (
"context"
"encoding/json"
"errors"
"fmt"

"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/plugins"
"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework"
"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/types"
logutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging"
)

const (
DecisionTreeFilterType = "decision-tree"
)

// compile-time type assertion
var _ framework.Filter = &DecisionTreeFilter{}

Expand All @@ -47,6 +55,82 @@ type DecisionTreeFilter struct {
NextOnSuccessOrFailure framework.Filter
}

type decisionTreeFilterParameters struct {
Current *decisionTreeFilterEntry `json:"current"`
NextOnSuccess *decisionTreeFilterEntry `json:"nextOnSuccess"`
NextOnFailure *decisionTreeFilterEntry `json:"nextOnFailure"`
NextOnSuccessOrFailure *decisionTreeFilterEntry `json:"nextOnSuccessOrFailure"`
}

type decisionTreeFilterEntry struct {
PluginRef *string `json:"pluginRef"`
DecisionTree *decisionTreeFilterParameters `json:"decisionTree"`
}

func DecisionTreeFilterFactory(name string, rawParameters json.RawMessage, handle plugins.Handle) (plugins.Plugin, error) {
parameters := decisionTreeFilterParameters{}
if err := json.Unmarshal(rawParameters, &parameters); err != nil {
return nil, fmt.Errorf("failed to parse the parameters of the '%s' filter - %w", name, err)
}
return loadDecisionTree(&parameters, handle)
}

func loadDecisionTree(parameters *decisionTreeFilterParameters, handle plugins.Handle) (*DecisionTreeFilter, error) {
result := &DecisionTreeFilter{}
var err error

if parameters.Current == nil {
return nil, errors.New("a current filter must be specified")
}
result.Current, err = loadDecisionTreeEntry(parameters.Current, handle)
if err != nil {
return nil, err
}

if parameters.NextOnSuccess != nil {
result.NextOnSuccess, err = loadDecisionTreeEntry(parameters.NextOnSuccess, handle)
if err != nil {
return nil, err
}
}

if parameters.NextOnFailure != nil {
result.NextOnFailure, err = loadDecisionTreeEntry(parameters.NextOnFailure, handle)
if err != nil {
return nil, err
}
}

if parameters.NextOnSuccessOrFailure != nil {
result.NextOnSuccessOrFailure, err = loadDecisionTreeEntry(parameters.NextOnSuccessOrFailure, handle)
if err != nil {
return nil, err
}
}

return result, nil
}

func loadDecisionTreeEntry(entry *decisionTreeFilterEntry, handle plugins.Handle) (framework.Filter, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same nit, return concrete type

Copy link
Contributor Author

@shmuelk shmuelk Jun 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function doesn't always return a DecisionTreeFilter, it may return a framework.Filter.

if entry.PluginRef != nil && entry.DecisionTree != nil {
return nil, errors.New("both pluginRef and decisionTree may not be specified")
}

if entry.PluginRef != nil {
instance := handle.Plugins().Plugin(*entry.PluginRef)
if instance == nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unrelated but I think the handle should provide a GetPlugin() method and return an error if the plugin ref doesn't exist. That way is more explicit and less error prone.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

++, agree with one small difference.
I suggest GetPlugin to return (plugin, bool) same as map does (bool - if key was found or not)

Copy link
Contributor Author

@shmuelk shmuelk Jun 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

map[key], doesn't have to return a bool. There were reviews in which w were told to remove the bool and check for nll instead.

In general getters in Go do not have the prefix Get unlike for example Java.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's more idiomatic in go to have the Plugin method to either return a bool or error to indicate the failure scenario. Maybe I was missing some context but I am not sure why checking nil is preferred.

Just calling this out but not related to this PR thought

return nil, errors.New(*entry.PluginRef + " is a reference to an undefined Plugin")
}
if theFilter, ok := instance.(framework.Filter); ok {
return theFilter, nil
}
return nil, errors.New(*entry.PluginRef + " is not a filter")
} else if entry.DecisionTree != nil {
return loadDecisionTree(entry.DecisionTree, handle)
}
return nil, errors.New("either pluginRef or decisionTree must be specified")
}

// Type returns the type of the filter.
func (f *DecisionTreeFilter) Type() string {
if f == nil {
Expand Down
Loading