@@ -62,8 +62,8 @@ type parameters struct {
6262 DefaultProfile string `json:"defaultProfile"`
6363}
6464
65- // Factory defines the factory function for HeaderPhaseProfileHandler.
66- func Factory (name string , rawParameters * json.Decoder , _ fwkplugin.Handle ) (fwkplugin.Plugin , error ) {
65+ // HeaderPhaseProfileHandlerFactory defines the factory function for HeaderPhaseProfileHandler.
66+ func HeaderPhaseProfileHandlerFactory (name string , rawParameters * json.Decoder , _ fwkplugin.Handle ) (fwkplugin.Plugin , error ) {
6767 params := parameters {}
6868 if rawParameters != nil {
6969 if err := rawParameters .Decode (& params ); err != nil {
@@ -74,17 +74,10 @@ func Factory(name string, rawParameters *json.Decoder, _ fwkplugin.Handle) (fwkp
7474 return NewHeaderPhaseProfileHandler (params .HeaderName , params .DefaultProfile ).WithName (name ), nil
7575}
7676
77- // NewHeaderPhaseProfileHandler initializes a new HeaderPhaseProfileHandler and returns
78- // its pointer.
79- //
80- // headerName is lowercased and trimmed, falling back to defaultHeaderName when that
81- // leaves it empty: the EPP's request handler lowercases every incoming header name at
82- // ingestion (pkg/epp/handlers/request.go), so the configured name must be normalized the
83- // same way to match, and an empty header key would never match any request.
84- //
85- // defaultProfile is trimmed, falling back to defaultProfileName when that leaves it
86- // empty. Unlike headerName, it is not case-normalized: it names a schedulingProfiles
87- // entry, which (like the header value itself) is matched case-sensitively.
77+ // NewHeaderPhaseProfileHandler trims and defaults both arguments, additionally
78+ // lowercasing headerName to match how the EPP stores ingested header keys
79+ // (pkg/epp/handlers/request.go); defaultProfile stays case-sensitive since it names a
80+ // schedulingProfiles entry.
8881func NewHeaderPhaseProfileHandler (headerName , defaultProfile string ) * HeaderPhaseProfileHandler {
8982 headerName = strings .ToLower (strings .TrimSpace (headerName ))
9083 if headerName == "" {
@@ -103,18 +96,10 @@ func NewHeaderPhaseProfileHandler(headerName, defaultProfile string) *HeaderPhas
10396 }
10497}
10598
106- // HeaderPhaseProfileHandler runs exactly one scheduling profile per request: the one
107- // named by the value of a request header. This lets a single EPP instance serve several
108- // phases of a disaggregated pipeline (e.g. encode, prefill, decode) whose caller already
109- // knows, out of band, which phase each request is for - unlike the disagg profile
110- // handler, which decides which profiles to run via decider plugins.
111- //
112- // Two fallbacks keep single-stage and header-less traffic working without a different
113- // profile handler: with exactly one configured profile there is nothing to choose,
114- // so that profile always runs regardless of the header (or its absence); with more than
115- // one configured profile, a request whose header is missing or blank runs defaultProfile
116- // instead of failing. A header naming a profile that isn't configured is still an error -
117- // only the header's absence triggers the default, not an unrecognized value.
99+ // HeaderPhaseProfileHandler runs exactly one scheduling profile per request, named by a
100+ // request header, falling back to the sole configured profile or to defaultProfile when
101+ // there's nothing to choose or the header is missing/blank; an unrecognized header value
102+ // is still an error.
118103type HeaderPhaseProfileHandler struct {
119104 typedName fwkplugin.TypedName
120105 headerName string
@@ -151,28 +136,13 @@ func (h *HeaderPhaseProfileHandler) noMatchError(phase string) error {
151136 return fmt .Errorf ("header-phase profile handler: no scheduling profile configured for %q header value %q" , h .headerName , phase )
152137}
153138
154- // defaultProfileNotConfiguredError explains that defaultProfile itself isn't a
155- // configured scheduling profile. Distinct from noMatchError: this is a configuration
156- // bug that fails every request whose phase header is missing or blank, not an ordinary
157- // client-side unrecognized header value.
139+ // defaultProfileNotConfiguredError reports defaultProfile itself missing from
140+ // schedulingProfiles - a config bug, distinct from noMatchError's per-request issue.
158141func (h * HeaderPhaseProfileHandler ) defaultProfileNotConfiguredError () error {
159142 return fmt .Errorf ("header-phase profile handler: defaultProfile %q is not a configured scheduling profile" , h .defaultProfile )
160143}
161144
162- // Pick selects the single SchedulingProfile to run: the only configured profile when
163- // there is just one, otherwise the one named by the request's phase header, falling
164- // back to defaultProfile when the header is missing or blank. It returns an empty map
165- // once that profile has run, or when no profile could be resolved. In the latter case
166- // the scheduler's run loop (pkg/epp/scheduling.Scheduler.Schedule) stops without ever
167- // calling ProcessResults, so the specific reason is logged here rather than returned
168- // from ProcessResults, where it would be unreachable. The client never sees that
169- // reason: it only gets the scheduler's generic "failed to run any scheduler profile"
170- // error, which pkg/epp/requestcontrol/director.go maps to a 429 ResourceExhausted
171- // response - misleading, since a malformed or missing header is a client error, not a
172- // capacity problem. Surfacing the real reason to the client needs a
173- // scheduler/ProfileHandler contract change and is out of scope here; the log is a
174- // diagnostic aid for operators, not an equivalent substitute for what the caller
175- // receives.
145+ // Pick implements fwksched.ProfileHandler.Pick; see README.md for behavior.
176146func (h * HeaderPhaseProfileHandler ) Pick (ctx context.Context , request * fwksched.InferenceRequest , profiles map [string ]fwksched.SchedulerProfile ,
177147 profileResults map [string ]* fwksched.ProfileRunResult ) map [string ]fwksched.SchedulerProfile {
178148 if len (profileResults ) > 0 { // the selected profile has already run
@@ -196,19 +166,14 @@ func (h *HeaderPhaseProfileHandler) Pick(ctx context.Context, request *fwksched.
196166
197167 profile , ok := profiles [resolvedPhase ]
198168 if ! ok {
199- // A missing or unrecognized header value is a per-request client issue, not a
200- // system fault - log via Info, not Error, so it doesn't page anyone or drown out
201- // real errors. It's still a once-per-request operational signal an operator needs
202- // to see by default, so it's unguarded rather than gated behind a verbosity level
203- // that's off in production.
204- // A missing header whose defaultProfile substitute also fails to resolve is a
205- // distinct, config-time condition - it fails every header-less request, not just
206- // an occasional bad caller - so it gets its own message rather than being
207- // reported as an ordinary missing header.
208169 err := h .noMatchError (phase )
209170 if phase == "" {
210171 err = h .defaultProfileNotConfiguredError ()
211172 }
173+ // Logged here, not returned from ProcessResults (skipped when Pick returns empty),
174+ // at Info, not Error, since a bad or missing header is a client issue an operator
175+ // still needs to see by default.
176+ // See README.md for why the client only gets a generic 429 instead of this reason.
212177 log .FromContext (ctx ).Info ("no scheduling profile selected for request" , "error" , err )
213178 return map [string ]fwksched.SchedulerProfile {}
214179 }
0 commit comments