forked from llm-d/llm-d-router
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconfig.go
More file actions
487 lines (423 loc) · 18.2 KB
/
Copy pathconfig.go
File metadata and controls
487 lines (423 loc) · 18.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
/*
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 registry
import (
"errors"
"fmt"
"time"
"github.com/llm-d/llm-d-router/pkg/epp/framework/interface/flowcontrol"
"github.com/llm-d/llm-d-router/pkg/epp/framework/plugins/flowcontrol/fairness/globalstrict"
"github.com/llm-d/llm-d-router/pkg/epp/framework/plugins/flowcontrol/ordering/fcfs"
"github.com/llm-d/llm-d-router/pkg/epp/framework/plugins/flowcontrol/usagelimits"
)
// --- Defaults ---
const (
// DefaultOrderingPolicyRef is the default policy for selecting items within a single flow's queue.
DefaultOrderingPolicyRef string = fcfs.FCFSOrderingPolicyType
// DefaultFairnessPolicyRef is the default policy for selecting which flow's queue to service next.
DefaultFairnessPolicyRef string = globalstrict.GlobalStrictFairnessPolicyType
// DefaultUsageLimitPolicyRef is the default policy to compute usage limit of a priority band dynamically.
DefaultUsageLimitPolicyRef string = usagelimits.StaticUsageLimitPolicyType
)
const (
// defaultPriorityBandMaxBytes is the default byte-size capacity for a priority band if not explicitly
// configured. It is set to 1 GB.
defaultPriorityBandMaxBytes uint64 = 1_000_000_000
// defaultPriorityBandMaxRequests is the default request-count capacity for a priority band if not
// explicitly configured. Together with the default request TTL (60s), it only binds when a band's
// arrival rate under a dispatch halt exceeds maxRequests/TTL (~83 req/s); below that, TTL expiry
// bounds occupancy first.
defaultPriorityBandMaxRequests uint64 = 5000
// defaultFlowGCTimeout is the default duration of inactivity after which an idle flow is garbage collected.
// This also serves as the interval for the periodic garbage collection scan.
defaultFlowGCTimeout time.Duration = 5 * time.Minute
// defaultPriorityBandGCTimeout is the default duration of inactivity after which a dynamically provisioned
// priority band is garbage collected. Set to 2x flow GC timeout to ensure flows are cleaned up first.
defaultPriorityBandGCTimeout time.Duration = 2 * defaultFlowGCTimeout
)
// --- Configuration ---
// PriorityBandPolicyDefaults carries pre-resolved default policy instances.
// It is populated by the config loader (the single boundary for plugin resolution)
// and passed into registry constructors so they never need to access the plugin Handle.
type PriorityBandPolicyDefaults struct {
OrderingPolicy flowcontrol.OrderingPolicy
FairnessPolicy flowcontrol.FairnessPolicy
}
// Config holds the master configuration for the entire FlowRegistry.
// It serves as the top-level blueprint, defining global settings and the templates for its priority bands.
type Config struct {
// MaxBytes defines an optional, global maximum total byte size limit aggregated across all priority bands.
// The `controller.FlowController` enforces this limit in addition to per-band capacity limits.
// A value of 0 signifies that this global limit is ignored, and only per-band limits apply.
// Optional: Defaults to 0.
MaxBytes uint64
// MaxRequests defines an optional, global maximum total request count aggregated across all priority bands.
// The `controller.FlowController` enforces this limit in addition to per-band capacity limits.
// A value of 0 signifies that this global limit is ignored, and only per-band limits apply.
// Optional: Defaults to 0.
MaxRequests uint64
// PriorityBands defines the set of priority band templates managed by the `FlowRegistry`.
// It is a map keyed by Priority level, providing O(1) access and ensuring priority uniqueness by definition.
PriorityBands map[int]*PriorityBandConfig
// DefaultPriorityBand serves as a template for dynamically provisioning priority bands when a request arrives with a
// priority level that was not explicitly configured.
// If nil, it is automatically populated with system defaults during NewConfig.
DefaultPriorityBand *PriorityBandConfig
// DefaultNegativePriorityBand is an optional template for dynamically provisioning priority bands when a request
// arrives with a priority level strictly below zero. This allows setting lower capacity limits for
// negative-priority traffic to designate it as sheddable (a value of 0 is treated as unset and receives the
// default).
// If nil, negative priorities fall back to DefaultPriorityBand.
DefaultNegativePriorityBand *PriorityBandConfig
// FlowGCTimeout defines the interval at which the registry scans for and garbage collects idle flows.
// A flow is collected if it has been observed to be Idle for at least one full scan interval.
// Optional: Defaults to `defaultFlowGCTimeout` (5 minutes).
FlowGCTimeout time.Duration
// PriorityBandGCTimeout defines the duration of inactivity after which a dynamically provisioned priority band
// is garbage collected. A band is considered idle when it has no flows and no buffered requests.
// Must be >= FlowGCTimeout to ensure flows are collected before bands.
// Optional: Defaults to `defaultPriorityBandGCTimeout` (10 minutes).
PriorityBandGCTimeout time.Duration
}
func (c *Config) String() string {
if c == nil {
return "<nil>"
}
// Define a local type definition to prevent infinite recursion when calling Sprintf("%+v").
// A new type definition inherits the struct fields but does not copy its methods,
// bypassing the Stringer check and allowing a safe reflection-based field dump.
type temp Config
return fmt.Sprintf("%+v", temp(*c))
}
// PriorityBandConfig defines the configuration template for a single priority band.
// A "Band" is defined as the collection (or range) of all flows having the same priority level.
// It establishes the default behaviors (such as queueing and dispatch behaviors) and total capacity limits for all flows
// that operate at this priority level.
type PriorityBandConfig struct {
// Priority is the unique numerical priority level for this band.
// Convention: Highest numeric value corresponds to highest priority (centered on 0).
// Required.
Priority int
// OrderingPolicy is the hydrated singleton instance of the policy.
// This policy governs which request *within this flow's queue* to select next (e.g., "fcfs").
// This field is populated either via WithOrderingPolicy (using a handle lookup) or via applyDefaults.
// Optional: Defaults to defaultOrderingPolicyRef ("fcfs-ordering-policy").
OrderingPolicy flowcontrol.OrderingPolicy
// FairnessPolicy is the hydrated singleton instance of the policy.
// This policy governs which Flow *within this band* to select next (e.g., "round-robin").
// This field is populated either via WithFairnessPolicy (using a handle lookup) or via applyDefaults.
// Optional: Defaults to defaultFairnessPolicyRef ("global-strict-fairness-policy").
FairnessPolicy flowcontrol.FairnessPolicy
// MaxBytes defines the maximum total byte size for this priority band.
// Optional: Defaults to defaultPriorityBandMaxBytes (1 GB). A value of 0 is treated as unset and receives the
// default; per-band limits are always bounded, unlike the optional global limits. To effectively remove the
// bound, set an explicit large value.
MaxBytes uint64
// MaxRequests defines the maximum total request count for this priority band.
// Optional: Defaults to defaultPriorityBandMaxRequests (5000). A value of 0 is treated as unset and receives
// the default; per-band limits are always bounded, unlike the optional global limits. To effectively remove
// the bound, set an explicit large value.
MaxRequests uint64
}
func (p *PriorityBandConfig) String() string {
if p == nil {
return "<nil>"
}
// Define a local type definition to prevent infinite recursion when calling Sprintf("%+v").
// A new type definition inherits the struct fields but does not copy its methods,
// bypassing the Stringer check and allowing a safe reflection-based field dump.
type temp PriorityBandConfig
return fmt.Sprintf("%+v", temp(*p))
}
// --- Config Functional Options ---
// configBuilder holds the intermediate state during NewConfig.
type configBuilder struct {
config *Config
}
// ConfigOption defines a functional option for configuring the registry.
type ConfigOption func(*configBuilder) error
// WithMaxBytes sets the global maximum total byte size limit.
func WithMaxBytes(maxBytes uint64) ConfigOption {
return func(b *configBuilder) error {
b.config.MaxBytes = maxBytes
return nil
}
}
// WithMaxRequests sets the global maximum total request count limit.
func WithMaxRequests(maxRequests uint64) ConfigOption {
return func(b *configBuilder) error {
b.config.MaxRequests = maxRequests
return nil
}
}
// WithFlowGCTimeout sets the idle flow garbage collection interval.
func WithFlowGCTimeout(d time.Duration) ConfigOption {
return func(b *configBuilder) error {
if d <= 0 {
return errors.New("flowGCTimeout must be positive")
}
b.config.FlowGCTimeout = d
return nil
}
}
// WithPriorityBandGCTimeout sets the idle priority band garbage collection timeout.
func WithPriorityBandGCTimeout(d time.Duration) ConfigOption {
return func(b *configBuilder) error {
if d <= 0 {
return errors.New("priorityBandGCTimeout must be positive")
}
if b.config.FlowGCTimeout > 0 && d < b.config.FlowGCTimeout {
return errors.New("priorityBandGCTimeout must be >= flowGCTimeout")
}
b.config.PriorityBandGCTimeout = d
return nil
}
}
// WithPriorityBand adds a priority band configuration.
// If a band with the same Priority already exists, it returns an error.
func WithPriorityBand(band *PriorityBandConfig) ConfigOption {
return func(b *configBuilder) error {
if band == nil {
return errors.New("cannot add nil PriorityBandConfig")
}
if _, exists := b.config.PriorityBands[band.Priority]; exists {
return fmt.Errorf("duplicate priority level %d", band.Priority)
}
b.config.PriorityBands[band.Priority] = band
return nil
}
}
// WithDefaultPriorityBand sets the template configuration used for dynamically provisioning priority bands.
func WithDefaultPriorityBand(band *PriorityBandConfig) ConfigOption {
return func(b *configBuilder) error {
b.config.DefaultPriorityBand = band
return nil
}
}
// WithDefaultNegativePriorityBand sets the template configuration used for dynamically provisioning priority bands
// with priority levels strictly below zero.
func WithDefaultNegativePriorityBand(band *PriorityBandConfig) ConfigOption {
return func(b *configBuilder) error {
b.config.DefaultNegativePriorityBand = band
return nil
}
}
// --- PriorityBandConfig Functional Options ---
// PriorityBandConfigOption defines a functional option for configuring a single PriorityBandConfig.
type PriorityBandConfigOption func(*PriorityBandConfig) error
// WithOrderingPolicy sets the ordering policy instance for this priority band.
func WithOrderingPolicy(policy flowcontrol.OrderingPolicy) PriorityBandConfigOption {
return func(p *PriorityBandConfig) error {
if policy == nil {
return errors.New("ordering policy cannot be nil")
}
p.OrderingPolicy = policy
return nil
}
}
// WithFairnessPolicy sets the fairness policy instance for this priority band.
func WithFairnessPolicy(policy flowcontrol.FairnessPolicy) PriorityBandConfigOption {
return func(p *PriorityBandConfig) error {
if policy == nil {
return errors.New("fairness policy cannot be nil")
}
p.FairnessPolicy = policy
return nil
}
}
// WithBandMaxBytes sets the capacity limit for this specific priority band.
func WithBandMaxBytes(maxBytes uint64) PriorityBandConfigOption {
return func(p *PriorityBandConfig) error {
p.MaxBytes = maxBytes
return nil
}
}
// WithBandMaxRequests sets the request count limit for this specific priority band.
func WithBandMaxRequests(maxRequests uint64) PriorityBandConfigOption {
return func(p *PriorityBandConfig) error {
p.MaxRequests = maxRequests
return nil
}
}
// NewConfig creates a new Config populated with system defaults, applies the provided options, and enforces strict
// validation.
//
// Arguments:
// - defaults: Default policy instances, provided by the config loader.
// - opts: Optional configuration overrides.
func NewConfig(defaults PriorityBandPolicyDefaults, opts ...ConfigOption) (*Config, error) {
builder := &configBuilder{
config: &Config{
MaxBytes: 0, // no limit enforced
MaxRequests: 0, // no limit enforced
FlowGCTimeout: defaultFlowGCTimeout,
PriorityBandGCTimeout: defaultPriorityBandGCTimeout,
PriorityBands: make(map[int]*PriorityBandConfig),
},
}
for _, opt := range opts {
if err := opt(builder); err != nil {
return nil, err
}
}
// Initialize DefaultPriorityBand if missing.
// This ensures we always have a template for dynamic provisioning.
if builder.config.DefaultPriorityBand == nil {
template, err := NewPriorityBandConfig(0, defaults)
if err != nil {
return nil, fmt.Errorf("failed to create default priority band: %w", err)
}
builder.config.DefaultPriorityBand = template
} else {
if err := builder.config.DefaultPriorityBand.applyDefaults(defaults); err != nil {
return nil, fmt.Errorf("failed to apply defaults to DefaultPriorityBand: %w", err)
}
}
// Apply defaults to DefaultNegativePriorityBand if set.
if builder.config.DefaultNegativePriorityBand != nil {
if err := builder.config.DefaultNegativePriorityBand.applyDefaults(defaults); err != nil {
return nil, fmt.Errorf("failed to apply defaults to DefaultNegativePriorityBand: %w", err)
}
}
// Apply defaults to all explicitly configured bands.
for _, band := range builder.config.PriorityBands {
if err := band.applyDefaults(defaults); err != nil {
return nil, fmt.Errorf("failed to apply defaults to priority band %d: %w", band.Priority, err)
}
}
// Ensure priority 0 is always provisioned. It is the fallback band for unmatched requests
// and the demotion target in withConnectionWithDemotion.
if _, exists := builder.config.PriorityBands[0]; !exists {
zero := *builder.config.DefaultPriorityBand
zero.Priority = 0
if err := zero.applyDefaults(defaults); err != nil {
return nil, fmt.Errorf("failed to apply defaults to priority band 0: %w", err)
}
builder.config.PriorityBands[0] = &zero
}
if err := builder.config.validate(); err != nil {
return nil, fmt.Errorf("invalid registry config: %w", err)
}
return builder.config, nil
}
// NewPriorityBandConfig creates a new band configuration with the required fields.
// It applies system defaults first, then applies any provided options to override those defaults.
func NewPriorityBandConfig(
priority int,
defaults PriorityBandPolicyDefaults,
opts ...PriorityBandConfigOption,
) (*PriorityBandConfig, error) {
pb := &PriorityBandConfig{
Priority: priority,
}
for _, opt := range opts {
if err := opt(pb); err != nil {
return nil, err
}
}
if err := pb.applyDefaults(defaults); err != nil {
return nil, err
}
return pb, nil
}
// --- Validation, Defaults & Hydration ---
func (p *PriorityBandConfig) applyDefaults(defaults PriorityBandPolicyDefaults) error {
if p.OrderingPolicy == nil {
if defaults.OrderingPolicy == nil {
return fmt.Errorf("no default ordering policy provided and none set on band %d", p.Priority)
}
p.OrderingPolicy = defaults.OrderingPolicy
}
if p.MaxBytes == 0 {
p.MaxBytes = defaultPriorityBandMaxBytes
}
if p.MaxRequests == 0 {
p.MaxRequests = defaultPriorityBandMaxRequests
}
if p.FairnessPolicy == nil {
if defaults.FairnessPolicy == nil {
return fmt.Errorf("no default fairness policy provided and none set on band %d", p.Priority)
}
p.FairnessPolicy = defaults.FairnessPolicy
}
return nil
}
// validate checks the integrity of a single band's configuration.
func (p *PriorityBandConfig) validate() error {
if p.OrderingPolicy == nil {
return fmt.Errorf("OrderingPolicy instance is missing for priority band %d", p.Priority)
}
if p.FairnessPolicy == nil {
return fmt.Errorf("FairnessPolicy instance is missing for priority band %d", p.Priority)
}
return nil
}
// validate checks global constraints and delegates band validation.
func (c *Config) validate() error {
if c.FlowGCTimeout <= 0 {
return errors.New("flowGCTimeout must be positive")
}
if c.PriorityBandGCTimeout <= 0 {
return errors.New("priorityBandGCTimeout must be positive")
}
if c.PriorityBandGCTimeout < c.FlowGCTimeout {
return errors.New("priorityBandGCTimeout must be >= flowGCTimeout")
}
// Validate the dynamic template.
// We use a dummy priority since the template itself doesn't have a fixed priority.
templateValidationCopy := *c.DefaultPriorityBand
templateValidationCopy.Priority = 0
if err := templateValidationCopy.validate(); err != nil {
return fmt.Errorf("invalid DefaultPriorityBand configuration: %w", err)
}
if c.DefaultNegativePriorityBand != nil {
negTemplateCopy := *c.DefaultNegativePriorityBand
negTemplateCopy.Priority = -1
if err := negTemplateCopy.validate(); err != nil {
return fmt.Errorf("invalid DefaultNegativePriorityBand configuration: %w", err)
}
}
// Validate statically configured bands.
for _, band := range c.PriorityBands {
if err := band.validate(); err != nil {
return err
}
}
return nil
}
// Clone creates a deep copy of the Config.
// It ensures the new Config has its own independent map and PriorityBandConfig instances.
func (c *Config) Clone() *Config {
if c == nil {
return nil
}
clone := *c
if c.DefaultPriorityBand != nil {
val := *c.DefaultPriorityBand
clone.DefaultPriorityBand = &val
}
if c.DefaultNegativePriorityBand != nil {
val := *c.DefaultNegativePriorityBand
clone.DefaultNegativePriorityBand = &val
}
if c.PriorityBands != nil {
clone.PriorityBands = make(map[int]*PriorityBandConfig, len(c.PriorityBands))
for prio, band := range c.PriorityBands {
// Dereference the pointer to copy the struct value, then take the address of the new value.
// This ensures 'clone' points to a new memory address.
b := *band
clone.PriorityBands[prio] = &b
}
}
return &clone
}