From 36b7d9468c5428622f1919a7bf76e4e7788c7483 Mon Sep 17 00:00:00 2001 From: mblos Date: Tue, 5 May 2026 10:25:05 +0200 Subject: [PATCH 1/8] feat: adding pipeline options --- .../filter_weigher_pipeline_controller.go | 2 +- internal/scheduling/lib/filter_monitor.go | 4 +-- .../scheduling/lib/filter_monitor_test.go | 2 +- internal/scheduling/lib/filter_test.go | 2 +- internal/scheduling/lib/filter_validation.go | 4 +-- .../scheduling/lib/filter_validation_test.go | 2 +- .../scheduling/lib/filter_weigher_pipeline.go | 16 ++++++---- .../lib/filter_weigher_pipeline_step.go | 4 ++- .../filter_weigher_pipeline_step_monitor.go | 3 +- ...lter_weigher_pipeline_step_monitor_test.go | 2 +- .../lib/filter_weigher_pipeline_test.go | 4 +-- internal/scheduling/lib/options.go | 32 +++++++++++++++++++ internal/scheduling/lib/weigher_monitor.go | 4 +-- .../scheduling/lib/weigher_monitor_test.go | 2 +- internal/scheduling/lib/weigher_test.go | 2 +- internal/scheduling/lib/weigher_validation.go | 4 +-- .../scheduling/lib/weigher_validation_test.go | 4 +-- .../filter_weigher_pipeline_controller.go | 2 +- ...filter_weigher_pipeline_controller_test.go | 2 +- .../machines/plugins/filters/filter_noop.go | 2 +- .../plugins/filters/filter_noop_test.go | 3 +- .../filter_weigher_pipeline_controller.go | 2 +- .../weighers/netapp_cpu_usage_balancing.go | 2 +- .../netapp_cpu_usage_balancing_test.go | 3 +- .../filter_weigher_pipeline_controller.go | 20 ++++++------ ...filter_weigher_pipeline_controller_test.go | 8 ++--- .../filters/filter_aggregate_metadata.go | 2 +- .../filters/filter_aggregate_metadata_test.go | 5 +-- .../filters/filter_allowed_projects.go | 2 +- .../filters/filter_allowed_projects_test.go | 3 +- .../plugins/filters/filter_capabilities.go | 2 +- .../filters/filter_capabilities_test.go | 5 +-- .../nova/plugins/filters/filter_correct_az.go | 2 +- .../plugins/filters/filter_correct_az_test.go | 3 +- .../plugins/filters/filter_exclude_hosts.go | 1 + .../filters/filter_exclude_hosts_test.go | 2 +- .../filters/filter_external_customer.go | 2 +- .../filters/filter_external_customer_test.go | 3 +- .../filters/filter_has_accelerators.go | 2 +- .../filters/filter_has_accelerators_test.go | 3 +- .../filters/filter_has_enough_capacity.go | 2 +- .../filter_has_enough_capacity_test.go | 13 ++++---- .../filters/filter_has_requested_traits.go | 2 +- .../filter_has_requested_traits_test.go | 3 +- .../filters/filter_host_instructions.go | 2 +- .../filters/filter_host_instructions_test.go | 3 +- .../filters/filter_instance_group_affinity.go | 1 + .../filter_instance_group_affinity_test.go | 3 +- .../filter_instance_group_anti_affinity.go | 1 + ...ilter_instance_group_anti_affinity_test.go | 3 +- .../plugins/filters/filter_live_migratable.go | 1 + .../filters/filter_live_migratable_test.go | 6 ++-- .../filters/filter_requested_destination.go | 2 +- .../filter_requested_destination_test.go | 4 +-- .../filters/filter_status_conditions.go | 2 +- .../filters/filter_status_conditions_test.go | 3 +- .../nova/plugins/weighers/kvm_binpack.go | 2 +- .../nova/plugins/weighers/kvm_binpack_test.go | 3 +- .../weighers/kvm_failover_evacuation.go | 2 +- .../weighers/kvm_failover_evacuation_test.go | 3 +- .../kvm_failover_reservation_consolidation.go | 2 +- ...failover_reservation_consolidation_test.go | 3 +- .../kvm_instance_group_soft_affinity.go | 2 +- .../kvm_instance_group_soft_affinity_test.go | 3 +- .../weighers/kvm_prefer_smaller_hosts.go | 2 +- .../weighers/kvm_prefer_smaller_hosts_test.go | 3 +- .../vmware_anti_affinity_noisy_projects.go | 2 +- ...mware_anti_affinity_noisy_projects_test.go | 3 +- .../vmware_avoid_long_term_contended_hosts.go | 2 +- ...re_avoid_long_term_contended_hosts_test.go | 3 +- ...vmware_avoid_short_term_contended_hosts.go | 2 +- ...e_avoid_short_term_contended_hosts_test.go | 3 +- .../nova/plugins/weighers/vmware_binpack.go | 2 +- .../plugins/weighers/vmware_binpack_test.go | 3 +- .../filter_weigher_pipeline_controller.go | 2 +- ...filter_weigher_pipeline_controller_test.go | 2 +- .../plugins/filters/filter_node_affinity.go | 2 +- .../filters/filter_node_affinity_test.go | 3 +- .../plugins/filters/filter_node_available.go | 2 +- .../filters/filter_node_available_test.go | 3 +- .../plugins/filters/filter_node_capacity.go | 2 +- .../filters/filter_node_capacity_test.go | 3 +- .../pods/plugins/filters/filter_noop.go | 2 +- .../pods/plugins/filters/filter_noop_test.go | 3 +- .../pods/plugins/filters/filter_taint.go | 2 +- .../pods/plugins/filters/filter_taint_test.go | 3 +- .../pods/plugins/weighers/binpack.go | 2 +- .../pods/plugins/weighers/binpack_test.go | 3 +- 88 files changed, 188 insertions(+), 116 deletions(-) create mode 100644 internal/scheduling/lib/options.go diff --git a/internal/scheduling/cinder/filter_weigher_pipeline_controller.go b/internal/scheduling/cinder/filter_weigher_pipeline_controller.go index 52ec37306..5a4081784 100644 --- a/internal/scheduling/cinder/filter_weigher_pipeline_controller.go +++ b/internal/scheduling/cinder/filter_weigher_pipeline_controller.go @@ -121,7 +121,7 @@ func (c *FilterWeigherPipelineController) process(ctx context.Context, decision return err } - result, err := pipeline.Run(request) + result, err := pipeline.Run(request, lib.Options{}) if err != nil { log.Error(err, "failed to run pipeline") return err diff --git a/internal/scheduling/lib/filter_monitor.go b/internal/scheduling/lib/filter_monitor.go index d0afd9282..97e9d661d 100644 --- a/internal/scheduling/lib/filter_monitor.go +++ b/internal/scheduling/lib/filter_monitor.go @@ -43,6 +43,6 @@ func (fm *FilterMonitor[RequestType]) Validate(ctx context.Context, params v1alp } // Run the filter and observe its execution. -func (fm *FilterMonitor[RequestType]) Run(traceLog *slog.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error) { - return fm.monitor.RunWrapped(traceLog, request, fm.filter) +func (fm *FilterMonitor[RequestType]) Run(traceLog *slog.Logger, request RequestType, opts Options) (*FilterWeigherPipelineStepResult, error) { + return fm.monitor.RunWrapped(traceLog, request, opts, fm.filter) } diff --git a/internal/scheduling/lib/filter_monitor_test.go b/internal/scheduling/lib/filter_monitor_test.go index f709d88aa..b27f811b1 100644 --- a/internal/scheduling/lib/filter_monitor_test.go +++ b/internal/scheduling/lib/filter_monitor_test.go @@ -100,7 +100,7 @@ func TestFilterMonitor_Run(t *testing.T) { Weights: map[string]float64{"host1": 0.1, "host2": 0.2, "host3": 0.3}, } - result, err := fm.Run(slog.Default(), request) + result, err := fm.Run(slog.Default(), request, Options{}) if err != nil { t.Errorf("expected no error, got %v", err) } diff --git a/internal/scheduling/lib/filter_test.go b/internal/scheduling/lib/filter_test.go index 652211163..14fe73997 100644 --- a/internal/scheduling/lib/filter_test.go +++ b/internal/scheduling/lib/filter_test.go @@ -31,7 +31,7 @@ func (m *mockFilter[RequestType]) Validate(ctx context.Context, params v1alpha1. } return m.ValidateFunc(ctx, params) } -func (m *mockFilter[RequestType]) Run(traceLog *slog.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error) { +func (m *mockFilter[RequestType]) Run(traceLog *slog.Logger, request RequestType, opts Options) (*FilterWeigherPipelineStepResult, error) { if m.RunFunc == nil { return &FilterWeigherPipelineStepResult{}, nil } diff --git a/internal/scheduling/lib/filter_validation.go b/internal/scheduling/lib/filter_validation.go index 9ad43311d..2054a9ad9 100644 --- a/internal/scheduling/lib/filter_validation.go +++ b/internal/scheduling/lib/filter_validation.go @@ -35,8 +35,8 @@ func validateFilter[RequestType FilterWeigherPipelineRequest](filter Filter[Requ } // Run the filter and validate what happens. -func (s *FilterValidator[RequestType]) Run(traceLog *slog.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error) { - result, err := s.Filter.Run(traceLog, request) +func (s *FilterValidator[RequestType]) Run(traceLog *slog.Logger, request RequestType, opts Options) (*FilterWeigherPipelineStepResult, error) { + result, err := s.Filter.Run(traceLog, request, opts) if err != nil { return nil, err } diff --git a/internal/scheduling/lib/filter_validation_test.go b/internal/scheduling/lib/filter_validation_test.go index dc35c2f6a..deb064dfc 100644 --- a/internal/scheduling/lib/filter_validation_test.go +++ b/internal/scheduling/lib/filter_validation_test.go @@ -156,7 +156,7 @@ func TestFilterValidator_Run(t *testing.T) { } traceLog := slog.Default() - result, err := validator.Run(traceLog, request) + result, err := validator.Run(traceLog, request, Options{}) if tt.expectError && err == nil { t.Error("expected error but got nil") diff --git a/internal/scheduling/lib/filter_weigher_pipeline.go b/internal/scheduling/lib/filter_weigher_pipeline.go index ee769433d..871aa771f 100644 --- a/internal/scheduling/lib/filter_weigher_pipeline.go +++ b/internal/scheduling/lib/filter_weigher_pipeline.go @@ -18,8 +18,8 @@ import ( ) type FilterWeigherPipeline[RequestType FilterWeigherPipelineRequest] interface { - // Run the scheduling pipeline with the given request. - Run(request RequestType) (v1alpha1.DecisionResult, error) + // Run the scheduling pipeline with the given request and call-time options. + Run(request RequestType, opts Options) (v1alpha1.DecisionResult, error) } // Pipeline of scheduler steps. @@ -138,6 +138,7 @@ func InitNewFilterWeigherPipeline[RequestType FilterWeigherPipelineRequest]( func (p *filterWeigherPipeline[RequestType]) runFilters( log *slog.Logger, request RequestType, + opts Options, ) (filteredRequest RequestType, stepResults []v1alpha1.StepResult) { filteredRequest = request @@ -145,7 +146,7 @@ func (p *filterWeigherPipeline[RequestType]) runFilters( filter := p.filters[filterName] stepLog := log.With("filter", filterName) stepLog.Info("scheduler: running filter") - result, err := filter.Run(stepLog, filteredRequest) + result, err := filter.Run(stepLog, filteredRequest, opts) if errors.Is(err, ErrStepSkipped) { stepLog.Info("scheduler: filter skipped") continue @@ -170,6 +171,7 @@ func (p *filterWeigherPipeline[RequestType]) runFilters( func (p *filterWeigherPipeline[RequestType]) runWeighers( log *slog.Logger, filteredRequest RequestType, + opts Options, ) map[string]map[string]float64 { activationsByStep := map[string]map[string]float64{} @@ -181,7 +183,7 @@ func (p *filterWeigherPipeline[RequestType]) runWeighers( wg.Go(func() { stepLog := log.With("weigher", weigherName) stepLog.Info("scheduler: running weigher") - result, err := weigher.Run(stepLog, filteredRequest) + result, err := weigher.Run(stepLog, filteredRequest, opts) if errors.Is(err, ErrStepSkipped) { stepLog.Info("scheduler: weigher skipped") return @@ -262,7 +264,7 @@ func (s *filterWeigherPipeline[RequestType]) sortHostsByWeights(weights map[stri } // Evaluate the pipeline and return a list of hosts in order of preference. -func (p *filterWeigherPipeline[RequestType]) Run(request RequestType) (v1alpha1.DecisionResult, error) { +func (p *filterWeigherPipeline[RequestType]) Run(request RequestType, opts Options) (v1alpha1.DecisionResult, error) { slogArgs := request.GetTraceLogArgs() slogArgsAny := make([]any, 0, len(slogArgs)) for _, arg := range slogArgs { @@ -279,7 +281,7 @@ func (p *filterWeigherPipeline[RequestType]) Run(request RequestType) (v1alpha1. // Run filters first to reduce the number of hosts. // Any weights assigned to filtered out hosts are ignored. - filteredRequest, filterStepResults := p.runFilters(traceLog, request) + filteredRequest, filterStepResults := p.runFilters(traceLog, request, opts) traceLog.Info( "scheduler: finished filters", "remainingHosts", filteredRequest.GetHosts(), @@ -290,7 +292,7 @@ func (p *filterWeigherPipeline[RequestType]) Run(request RequestType) (v1alpha1. for _, host := range filteredRequest.GetHosts() { remainingWeights[host] = inWeights[host] } - stepWeights := p.runWeighers(traceLog, filteredRequest) + stepWeights := p.runWeighers(traceLog, filteredRequest, opts) outWeights := p.applyWeights(traceLog, stepWeights, remainingWeights) traceLog.Info("scheduler: output weights", "weights", outWeights) diff --git a/internal/scheduling/lib/filter_weigher_pipeline_step.go b/internal/scheduling/lib/filter_weigher_pipeline_step.go index 26dc5de40..cf6b3f207 100644 --- a/internal/scheduling/lib/filter_weigher_pipeline_step.go +++ b/internal/scheduling/lib/filter_weigher_pipeline_step.go @@ -30,7 +30,9 @@ type FilterWeigherPipelineStep[RequestType FilterWeigherPipelineRequest] interfa // // A traceLog is provided that contains the global request id and should // be used to log the step's execution. - Run(traceLog *slog.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error) + // + // opts carries per-call behavioral options set by the pipeline caller. + Run(traceLog *slog.Logger, request RequestType, opts Options) (*FilterWeigherPipelineStepResult, error) } // Common base for all steps that provides some functionality diff --git a/internal/scheduling/lib/filter_weigher_pipeline_step_monitor.go b/internal/scheduling/lib/filter_weigher_pipeline_step_monitor.go index 3e64fa6ee..e54651ec5 100644 --- a/internal/scheduling/lib/filter_weigher_pipeline_step_monitor.go +++ b/internal/scheduling/lib/filter_weigher_pipeline_step_monitor.go @@ -65,6 +65,7 @@ func monitorStep[RequestType FilterWeigherPipelineRequest](stepName string, m Fi func (s *FilterWeigherPipelineStepMonitor[RequestType]) RunWrapped( traceLog *slog.Logger, request RequestType, + opts Options, step FilterWeigherPipelineStep[RequestType], ) (*FilterWeigherPipelineStepResult, error) { @@ -74,7 +75,7 @@ func (s *FilterWeigherPipelineStepMonitor[RequestType]) RunWrapped( } inWeights := request.GetWeights() - stepResult, err := step.Run(traceLog, request) + stepResult, err := step.Run(traceLog, request, opts) if err != nil { return nil, err } diff --git a/internal/scheduling/lib/filter_weigher_pipeline_step_monitor_test.go b/internal/scheduling/lib/filter_weigher_pipeline_step_monitor_test.go index 7d7817abd..af4bf74ec 100644 --- a/internal/scheduling/lib/filter_weigher_pipeline_step_monitor_test.go +++ b/internal/scheduling/lib/filter_weigher_pipeline_step_monitor_test.go @@ -38,7 +38,7 @@ func TestStepMonitorRun(t *testing.T) { Hosts: []string{"host1", "host2", "host3"}, Weights: map[string]float64{"host1": 0.2, "host2": 0.1, "host3": 0.0}, } - if _, err := monitor.RunWrapped(slog.Default(), request, step); err != nil { + if _, err := monitor.RunWrapped(slog.Default(), request, Options{}, step); err != nil { t.Fatalf("Run() error = %v, want nil", err) } if len(removedHostsObserver.Observations) != 1 { diff --git a/internal/scheduling/lib/filter_weigher_pipeline_test.go b/internal/scheduling/lib/filter_weigher_pipeline_test.go index 0e2775944..9636084f3 100644 --- a/internal/scheduling/lib/filter_weigher_pipeline_test.go +++ b/internal/scheduling/lib/filter_weigher_pipeline_test.go @@ -72,7 +72,7 @@ func TestPipeline_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result, err := pipeline.Run(tt.request) + result, err := pipeline.Run(tt.request, Options{}) if err != nil { t.Fatalf("expected no error, got %v", err) } @@ -221,7 +221,7 @@ func TestPipeline_RunFilters(t *testing.T) { Weights: map[string]float64{"host1": 0.0, "host2": 0.0, "host3": 0.0}, } - req, _ := p.runFilters(slog.Default(), request) + req, _ := p.runFilters(slog.Default(), request, Options{}) if len(req.Hosts) != 2 { t.Fatalf("expected 2 step results, got %d", len(req.Hosts)) } diff --git a/internal/scheduling/lib/options.go b/internal/scheduling/lib/options.go new file mode 100644 index 000000000..28819415d --- /dev/null +++ b/internal/scheduling/lib/options.go @@ -0,0 +1,32 @@ +// Copyright SAP SE +// SPDX-License-Identifier: Apache-2.0 + +package lib + +import "github.com/cobaltcore-dev/cortex/api/v1alpha1" + +// Options configure the behavior of a single pipeline run at call time. +// These are distinct from per-step YAML options (FilterWeigherPipelineStepOpts), +// which are static and set when the pipeline is initialized. +// +// Consumed by steps: ReadOnly, LockReservations, AssumeEmptyHosts, IgnoredReservationTypes. +// Consumed by the controller after pipeline.Run(): RecordHistory, CreateInflight. +type Options struct { + // ReadOnly means the pipeline could run without using the mutex, i.e. concurrent runs are ok. + ReadOnly bool + // LockReservations prevents reservation unlocking, e.g. in the capacity filter. + // Set when finding hosts for new reservations (failover, CR) to see true available capacity. + LockReservations bool + // AssumeEmptyHosts treats all hosts as having no running VMs. + AssumeEmptyHosts bool + // IgnoredReservationTypes lists reservation types the capacity filter skips entirely. + IgnoredReservationTypes []v1alpha1.ReservationType + // MaxCandidates limits the number of hosts returned after weighing. 0 means no limit. + MaxCandidates int + + // RecordHistory records the placement decision in placement history. + // Replaces pipeline.Spec.CreateHistory once pipelines consolidate. + RecordHistory bool + // CreateInflight creates pessimistic blocking reservations for all returned candidates. + CreateInflight bool +} diff --git a/internal/scheduling/lib/weigher_monitor.go b/internal/scheduling/lib/weigher_monitor.go index df855d067..56a9ebb6e 100644 --- a/internal/scheduling/lib/weigher_monitor.go +++ b/internal/scheduling/lib/weigher_monitor.go @@ -43,6 +43,6 @@ func (wm *WeigherMonitor[RequestType]) Validate(ctx context.Context, params v1al } // Run the weigher and observe its execution. -func (wm *WeigherMonitor[RequestType]) Run(traceLog *slog.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error) { - return wm.monitor.RunWrapped(traceLog, request, wm.weigher) +func (wm *WeigherMonitor[RequestType]) Run(traceLog *slog.Logger, request RequestType, opts Options) (*FilterWeigherPipelineStepResult, error) { + return wm.monitor.RunWrapped(traceLog, request, opts, wm.weigher) } diff --git a/internal/scheduling/lib/weigher_monitor_test.go b/internal/scheduling/lib/weigher_monitor_test.go index 6f8f906e3..c84435234 100644 --- a/internal/scheduling/lib/weigher_monitor_test.go +++ b/internal/scheduling/lib/weigher_monitor_test.go @@ -100,7 +100,7 @@ func TestWeigherMonitor_Run(t *testing.T) { Weights: map[string]float64{"host1": 0.1, "host2": 0.2, "host3": 0.3}, } - result, err := wm.Run(slog.Default(), request) + result, err := wm.Run(slog.Default(), request, Options{}) if err != nil { t.Errorf("expected no error, got %v", err) } diff --git a/internal/scheduling/lib/weigher_test.go b/internal/scheduling/lib/weigher_test.go index 4660207c4..488704ef4 100644 --- a/internal/scheduling/lib/weigher_test.go +++ b/internal/scheduling/lib/weigher_test.go @@ -34,7 +34,7 @@ func (m *mockWeigher[RequestType]) Validate(ctx context.Context, params v1alpha1 } return m.ValidateFunc(ctx, params) } -func (m *mockWeigher[RequestType]) Run(traceLog *slog.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error) { +func (m *mockWeigher[RequestType]) Run(traceLog *slog.Logger, request RequestType, opts Options) (*FilterWeigherPipelineStepResult, error) { if m.RunFunc == nil { return &FilterWeigherPipelineStepResult{}, nil } diff --git a/internal/scheduling/lib/weigher_validation.go b/internal/scheduling/lib/weigher_validation.go index c454d171e..bb4c6b823 100644 --- a/internal/scheduling/lib/weigher_validation.go +++ b/internal/scheduling/lib/weigher_validation.go @@ -35,8 +35,8 @@ func validateWeigher[RequestType FilterWeigherPipelineRequest](weigher Weigher[R } // Run the weigher and validate what happens. -func (s *WeigherValidator[RequestType]) Run(traceLog *slog.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error) { - result, err := s.Weigher.Run(traceLog, request) +func (s *WeigherValidator[RequestType]) Run(traceLog *slog.Logger, request RequestType, opts Options) (*FilterWeigherPipelineStepResult, error) { + result, err := s.Weigher.Run(traceLog, request, opts) if err != nil { return nil, err } diff --git a/internal/scheduling/lib/weigher_validation_test.go b/internal/scheduling/lib/weigher_validation_test.go index 852448a88..af7efb163 100644 --- a/internal/scheduling/lib/weigher_validation_test.go +++ b/internal/scheduling/lib/weigher_validation_test.go @@ -96,7 +96,7 @@ func TestWeigherValidator_Run_ValidHosts(t *testing.T) { Weigher: mockStep, } - result, err := validator.Run(slog.Default(), request) + result, err := validator.Run(slog.Default(), request, Options{}) if err != nil { t.Errorf("Run() error = %v, want nil", err) } @@ -130,7 +130,7 @@ func TestWeigherValidator_Run_HostNumberMismatch(t *testing.T) { Weigher: mockStep, } - result, err := validator.Run(slog.Default(), request) + result, err := validator.Run(slog.Default(), request, Options{}) if err == nil { t.Errorf("Run() error = nil, want error") } diff --git a/internal/scheduling/machines/filter_weigher_pipeline_controller.go b/internal/scheduling/machines/filter_weigher_pipeline_controller.go index 35d51708a..93e0f5e41 100644 --- a/internal/scheduling/machines/filter_weigher_pipeline_controller.go +++ b/internal/scheduling/machines/filter_weigher_pipeline_controller.go @@ -144,7 +144,7 @@ func (c *FilterWeigherPipelineController) process(ctx context.Context, decision // Execute the scheduling pipeline. request := ironcore.MachinePipelineRequest{Pools: pools.Items} - result, err := pipeline.Run(request) + result, err := pipeline.Run(request, lib.Options{}) if err != nil { log.V(1).Error(err, "failed to run scheduler pipeline") return errors.New("failed to run scheduler pipeline") diff --git a/internal/scheduling/machines/filter_weigher_pipeline_controller_test.go b/internal/scheduling/machines/filter_weigher_pipeline_controller_test.go index bc2e0722a..28fe49ed7 100644 --- a/internal/scheduling/machines/filter_weigher_pipeline_controller_test.go +++ b/internal/scheduling/machines/filter_weigher_pipeline_controller_test.go @@ -516,7 +516,7 @@ func createMockPipeline() lib.FilterWeigherPipeline[ironcore.MachinePipelineRequ type mockMachinePipeline struct{} -func (m *mockMachinePipeline) Run(request ironcore.MachinePipelineRequest) (v1alpha1.DecisionResult, error) { +func (m *mockMachinePipeline) Run(request ironcore.MachinePipelineRequest, opts lib.Options) (v1alpha1.DecisionResult, error) { if len(request.Pools) == 0 { return v1alpha1.DecisionResult{}, nil } diff --git a/internal/scheduling/machines/plugins/filters/filter_noop.go b/internal/scheduling/machines/plugins/filters/filter_noop.go index da901e5c0..56fb55dfe 100644 --- a/internal/scheduling/machines/plugins/filters/filter_noop.go +++ b/internal/scheduling/machines/plugins/filters/filter_noop.go @@ -31,7 +31,7 @@ func (f *NoopFilter) Validate(ctx context.Context, params v1alpha1.Parameters) e // not in the map are considered as filtered out. // Provide a traceLog that contains the global request id and should // be used to log the step's execution. -func (NoopFilter) Run(traceLog *slog.Logger, request ironcore.MachinePipelineRequest) (*lib.FilterWeigherPipelineStepResult, error) { +func (NoopFilter) Run(traceLog *slog.Logger, request ironcore.MachinePipelineRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { activations := make(map[string]float64, len(request.Pools)) stats := make(map[string]lib.FilterWeigherPipelineStepStatistics) // Usually you would do some filtering here, or adjust the weights. diff --git a/internal/scheduling/machines/plugins/filters/filter_noop_test.go b/internal/scheduling/machines/plugins/filters/filter_noop_test.go index 2fa369a4f..06d80c771 100644 --- a/internal/scheduling/machines/plugins/filters/filter_noop_test.go +++ b/internal/scheduling/machines/plugins/filters/filter_noop_test.go @@ -4,6 +4,7 @@ package filters import ( + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -64,7 +65,7 @@ func TestNoopFilter_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { filter := &NoopFilter{} - result, err := filter.Run(slog.Default(), tt.request) + result, err := filter.Run(slog.Default(), tt.request, lib.Options{}) if err != nil { t.Errorf("expected Run() to succeed, got error: %v", err) diff --git a/internal/scheduling/manila/filter_weigher_pipeline_controller.go b/internal/scheduling/manila/filter_weigher_pipeline_controller.go index 128b7d719..18ed212f5 100644 --- a/internal/scheduling/manila/filter_weigher_pipeline_controller.go +++ b/internal/scheduling/manila/filter_weigher_pipeline_controller.go @@ -121,7 +121,7 @@ func (c *FilterWeigherPipelineController) process(ctx context.Context, decision return err } - result, err := pipeline.Run(request) + result, err := pipeline.Run(request, lib.Options{}) if err != nil { log.Error(err, "failed to run pipeline") return err diff --git a/internal/scheduling/manila/plugins/weighers/netapp_cpu_usage_balancing.go b/internal/scheduling/manila/plugins/weighers/netapp_cpu_usage_balancing.go index ce3e30ebe..01d31a55a 100644 --- a/internal/scheduling/manila/plugins/weighers/netapp_cpu_usage_balancing.go +++ b/internal/scheduling/manila/plugins/weighers/netapp_cpu_usage_balancing.go @@ -61,7 +61,7 @@ func (s *NetappCPUUsageBalancingStep) Init(ctx context.Context, client client.Cl } // Downvote hosts that are highly contended. -func (s *NetappCPUUsageBalancingStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *NetappCPUUsageBalancingStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) result.Statistics["avg cpu contention"] = s.PrepareStats(request, "%") result.Statistics["max cpu contention"] = s.PrepareStats(request, "%") diff --git a/internal/scheduling/manila/plugins/weighers/netapp_cpu_usage_balancing_test.go b/internal/scheduling/manila/plugins/weighers/netapp_cpu_usage_balancing_test.go index f3e9c66ea..eeb0cdea2 100644 --- a/internal/scheduling/manila/plugins/weighers/netapp_cpu_usage_balancing_test.go +++ b/internal/scheduling/manila/plugins/weighers/netapp_cpu_usage_balancing_test.go @@ -4,6 +4,7 @@ package weighers import ( + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -164,7 +165,7 @@ func TestNetappCPUUsageBalancingStep_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result, err := step.Run(slog.Default(), tt.request) + result, err := step.Run(slog.Default(), tt.request, lib.Options{}) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/filter_weigher_pipeline_controller.go b/internal/scheduling/nova/filter_weigher_pipeline_controller.go index 279ac1c3e..0c8b97e02 100644 --- a/internal/scheduling/nova/filter_weigher_pipeline_controller.go +++ b/internal/scheduling/nova/filter_weigher_pipeline_controller.go @@ -7,7 +7,6 @@ import ( "context" "encoding/json" "errors" - "fmt" "sync" "time" @@ -77,10 +76,6 @@ func (c *FilterWeigherPipelineController) ProcessNewDecisionFromAPI(ctx context. c.processMu.Lock() defer c.processMu.Unlock() - pipelineConf, ok := c.PipelineConfigs[decision.Spec.PipelineRef.Name] - if !ok { - return fmt.Errorf("pipeline %s not configured", decision.Spec.PipelineRef.Name) - } err := c.process(ctx, decision) if err != nil { meta.SetStatusCondition(&decision.Status.Conditions, metav1.Condition{ @@ -97,9 +92,6 @@ func (c *FilterWeigherPipelineController) ProcessNewDecisionFromAPI(ctx context. Message: "pipeline run succeeded", }) } - if pipelineConf.Spec.CreateHistory { - c.upsertHistory(ctx, decision, err) - } return err } @@ -166,7 +158,11 @@ func (c *FilterWeigherPipelineController) process(ctx context.Context, decision log.Info("gathered all placement candidates", "numHosts", len(request.Hosts)) } - result, err := pipeline.Run(request) + opts := c.buildOptions(pipelineConf) + result, err := pipeline.Run(request, opts) + if opts.RecordHistory { + c.upsertHistory(ctx, decision, err) + } if err != nil { log.Error(err, "failed to run pipeline") return err @@ -183,6 +179,12 @@ func (c *FilterWeigherPipelineController) process(ctx context.Context, decision } // The base controller will delegate the pipeline creation down to this method. +func (c *FilterWeigherPipelineController) buildOptions(pipelineConf v1alpha1.Pipeline) lib.Options { + return lib.Options{ + RecordHistory: pipelineConf.Spec.CreateHistory, + } +} + func (c *FilterWeigherPipelineController) InitPipeline( ctx context.Context, p v1alpha1.Pipeline, diff --git a/internal/scheduling/nova/filter_weigher_pipeline_controller_test.go b/internal/scheduling/nova/filter_weigher_pipeline_controller_test.go index 752725df8..ed15145d4 100644 --- a/internal/scheduling/nova/filter_weigher_pipeline_controller_test.go +++ b/internal/scheduling/nova/filter_weigher_pipeline_controller_test.go @@ -528,7 +528,7 @@ func TestFilterWeigherPipelineController_ProcessNewDecisionFromAPI(t *testing.T) expectResult: false, expectHistoryCreated: false, expectUpdatedStatus: false, - errorContains: "pipeline nonexistent-pipeline not configured", + errorContains: "pipeline not found or not ready", }, { name: "decision without novaRaw spec", @@ -573,7 +573,7 @@ func TestFilterWeigherPipelineController_ProcessNewDecisionFromAPI(t *testing.T) createHistory: true, expectError: true, expectResult: false, - expectHistoryCreated: true, + expectHistoryCreated: false, expectUpdatedStatus: false, errorContains: "no novaRaw spec defined", }, @@ -611,7 +611,7 @@ func TestFilterWeigherPipelineController_ProcessNewDecisionFromAPI(t *testing.T) createHistory: true, expectError: true, expectResult: false, - expectHistoryCreated: true, + expectHistoryCreated: false, expectUpdatedStatus: false, errorContains: "pipeline not found or not ready", }, @@ -649,7 +649,7 @@ func TestFilterWeigherPipelineController_ProcessNewDecisionFromAPI(t *testing.T) createHistory: true, expectError: true, expectResult: false, - expectHistoryCreated: true, + expectHistoryCreated: false, expectUpdatedStatus: false, errorContains: "pipeline not found or not ready", }, diff --git a/internal/scheduling/nova/plugins/filters/filter_aggregate_metadata.go b/internal/scheduling/nova/plugins/filters/filter_aggregate_metadata.go index 157a80521..82a50ce1c 100644 --- a/internal/scheduling/nova/plugins/filters/filter_aggregate_metadata.go +++ b/internal/scheduling/nova/plugins/filters/filter_aggregate_metadata.go @@ -19,7 +19,7 @@ type FilterAggregateMetadata struct { // Restrict hosts to specific projects if they are in an aggregate that has // the "filter_tenant_id" metadata key set. -func (s *FilterAggregateMetadata) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *FilterAggregateMetadata) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) hvs := &hv1.HypervisorList{} diff --git a/internal/scheduling/nova/plugins/filters/filter_aggregate_metadata_test.go b/internal/scheduling/nova/plugins/filters/filter_aggregate_metadata_test.go index d1ff9cd2d..f7093a003 100644 --- a/internal/scheduling/nova/plugins/filters/filter_aggregate_metadata_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_aggregate_metadata_test.go @@ -4,6 +4,7 @@ package filters import ( + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "context" "log/slog" "testing" @@ -336,7 +337,7 @@ func TestFilterAggregateMetadata_Run(t *testing.T) { step := &FilterAggregateMetadata{} step.Client = fakeClient - result, err := step.Run(slog.Default(), tt.request) + result, err := step.Run(slog.Default(), tt.request, lib.Options{}) if err != nil { t.Fatalf("expected no error, got %v", err) } @@ -388,7 +389,7 @@ func TestFilterAggregateMetadata_Run_ClientError(t *testing.T) { step := &FilterAggregateMetadata{} step.Client = fakeClient - _, err := step.Run(slog.Default(), request) + _, err := step.Run(slog.Default(), request, lib.Options{}) if err == nil { t.Errorf("expected error when client fails, got none") } diff --git a/internal/scheduling/nova/plugins/filters/filter_allowed_projects.go b/internal/scheduling/nova/plugins/filters/filter_allowed_projects.go index a0a486f3d..21d6c6dd8 100644 --- a/internal/scheduling/nova/plugins/filters/filter_allowed_projects.go +++ b/internal/scheduling/nova/plugins/filters/filter_allowed_projects.go @@ -19,7 +19,7 @@ type FilterAllowedProjectsStep struct { // Lock certain hosts for certain projects, based on the hypervisor spec. // Note that hosts without specified projects are still accessible. -func (s *FilterAllowedProjectsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *FilterAllowedProjectsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) if request.Spec.Data.ProjectID == "" { traceLog.Info("no project ID in request, skipping filter") diff --git a/internal/scheduling/nova/plugins/filters/filter_allowed_projects_test.go b/internal/scheduling/nova/plugins/filters/filter_allowed_projects_test.go index 070160e2e..9dc95da59 100644 --- a/internal/scheduling/nova/plugins/filters/filter_allowed_projects_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_allowed_projects_test.go @@ -4,6 +4,7 @@ package filters import ( + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -295,7 +296,7 @@ func TestFilterAllowedProjectsStep_Run(t *testing.T) { WithScheme(scheme). WithObjects(hvs...). Build() - result, err := step.Run(slog.Default(), tt.request) + result, err := step.Run(slog.Default(), tt.request, lib.Options{}) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/filters/filter_capabilities.go b/internal/scheduling/nova/plugins/filters/filter_capabilities.go index cda9a9a20..0fd1781ea 100644 --- a/internal/scheduling/nova/plugins/filters/filter_capabilities.go +++ b/internal/scheduling/nova/plugins/filters/filter_capabilities.go @@ -45,7 +45,7 @@ func hvToNovaCapabilities(hv hv1.Hypervisor) (map[string]string, error) { // Check the capabilities of each host and if they match the extra spec provided // in the request spec flavor. -func (s *FilterCapabilitiesStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *FilterCapabilitiesStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) extraSpecs := request.Spec.Data.Flavor.Data.ExtraSpecs if len(extraSpecs) == 0 { diff --git a/internal/scheduling/nova/plugins/filters/filter_capabilities_test.go b/internal/scheduling/nova/plugins/filters/filter_capabilities_test.go index 9b5f111dc..2aa6d2ba7 100644 --- a/internal/scheduling/nova/plugins/filters/filter_capabilities_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_capabilities_test.go @@ -4,6 +4,7 @@ package filters import ( + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -553,7 +554,7 @@ func TestFilterCapabilitiesStep_Run(t *testing.T) { WithScheme(scheme). WithObjects(hvs...). Build() - result, err := step.Run(slog.Default(), tt.request) + result, err := step.Run(slog.Default(), tt.request, lib.Options{}) if err != nil { t.Fatalf("expected no error, got %v", err) } @@ -628,7 +629,7 @@ func TestFilterCapabilitiesStep_DoesNotMutateExtraSpecs(t *testing.T) { WithObjects(hvs...). Build() - _, err = step.Run(slog.Default(), request) + _, err = step.Run(slog.Default(), request, lib.Options{}) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/filters/filter_correct_az.go b/internal/scheduling/nova/plugins/filters/filter_correct_az.go index ed7f68188..94311bd82 100644 --- a/internal/scheduling/nova/plugins/filters/filter_correct_az.go +++ b/internal/scheduling/nova/plugins/filters/filter_correct_az.go @@ -18,7 +18,7 @@ type FilterCorrectAZStep struct { } // Only get hosts in the requested az. -func (s *FilterCorrectAZStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *FilterCorrectAZStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) if request.Spec.Data.AvailabilityZone == "" { traceLog.Info("no availability zone requested, skipping filter_correct_az step") diff --git a/internal/scheduling/nova/plugins/filters/filter_correct_az_test.go b/internal/scheduling/nova/plugins/filters/filter_correct_az_test.go index d8389de9e..4ac16cfa3 100644 --- a/internal/scheduling/nova/plugins/filters/filter_correct_az_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_correct_az_test.go @@ -4,6 +4,7 @@ package filters import ( + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -169,7 +170,7 @@ func TestFilterCorrectAZStep_Run(t *testing.T) { WithScheme(scheme). WithObjects(hvs...). Build() - result, err := step.Run(slog.Default(), tt.request) + result, err := step.Run(slog.Default(), tt.request, lib.Options{}) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/filters/filter_exclude_hosts.go b/internal/scheduling/nova/plugins/filters/filter_exclude_hosts.go index 231efa9aa..1a68602a2 100644 --- a/internal/scheduling/nova/plugins/filters/filter_exclude_hosts.go +++ b/internal/scheduling/nova/plugins/filters/filter_exclude_hosts.go @@ -30,6 +30,7 @@ func (opts FilterExcludeHostsStepOpts) Validate() error { return nil } func (s *FilterExcludeHostsStep) Run( traceLog *slog.Logger, request api.ExternalSchedulerRequest, + opts lib.Options, ) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) diff --git a/internal/scheduling/nova/plugins/filters/filter_exclude_hosts_test.go b/internal/scheduling/nova/plugins/filters/filter_exclude_hosts_test.go index 0c9e35c59..42c0ab200 100644 --- a/internal/scheduling/nova/plugins/filters/filter_exclude_hosts_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_exclude_hosts_test.go @@ -218,7 +218,7 @@ func TestFilterExcludeHostsStep_Run(t *testing.T) { ExcludedHosts: tt.excludedHosts, } - result, err := step.Run(slog.Default(), tt.request) + result, err := step.Run(slog.Default(), tt.request, lib.Options{}) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/filters/filter_external_customer.go b/internal/scheduling/nova/plugins/filters/filter_external_customer.go index 827712a84..b96f76b6b 100644 --- a/internal/scheduling/nova/plugins/filters/filter_external_customer.go +++ b/internal/scheduling/nova/plugins/filters/filter_external_customer.go @@ -33,7 +33,7 @@ type FilterExternalCustomerStep struct { // Prefix-match the domain name for external customer domains and filter out hosts // that are not intended for external customers. -func (s *FilterExternalCustomerStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *FilterExternalCustomerStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) domainName, err := request.Spec.Data.GetSchedulerHintStr("domain_name") if err != nil { diff --git a/internal/scheduling/nova/plugins/filters/filter_external_customer_test.go b/internal/scheduling/nova/plugins/filters/filter_external_customer_test.go index 05bdbc6f6..9a46f31ee 100644 --- a/internal/scheduling/nova/plugins/filters/filter_external_customer_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_external_customer_test.go @@ -4,6 +4,7 @@ package filters import ( + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -358,7 +359,7 @@ func TestFilterExternalCustomerStep_Run(t *testing.T) { Build() step.Options = tt.opts - result, err := step.Run(slog.Default(), tt.request) + result, err := step.Run(slog.Default(), tt.request, lib.Options{}) if tt.expectError { if err == nil { t.Errorf("expected error but got none") diff --git a/internal/scheduling/nova/plugins/filters/filter_has_accelerators.go b/internal/scheduling/nova/plugins/filters/filter_has_accelerators.go index dcccdc010..2c61ad588 100644 --- a/internal/scheduling/nova/plugins/filters/filter_has_accelerators.go +++ b/internal/scheduling/nova/plugins/filters/filter_has_accelerators.go @@ -18,7 +18,7 @@ type FilterHasAcceleratorsStep struct { } // If requested, only get hosts with accelerators. -func (s *FilterHasAcceleratorsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *FilterHasAcceleratorsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) extraSpecs := request.Spec.Data.Flavor.Data.ExtraSpecs if _, ok := extraSpecs["accel:device_profile"]; !ok { diff --git a/internal/scheduling/nova/plugins/filters/filter_has_accelerators_test.go b/internal/scheduling/nova/plugins/filters/filter_has_accelerators_test.go index 1d1a06764..1008ae17f 100644 --- a/internal/scheduling/nova/plugins/filters/filter_has_accelerators_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_has_accelerators_test.go @@ -4,6 +4,7 @@ package filters import ( + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -347,7 +348,7 @@ func TestFilterHasAcceleratorsStep_Run(t *testing.T) { WithObjects(hvs...). Build() - result, err := step.Run(slog.Default(), tt.request) + result, err := step.Run(slog.Default(), tt.request, lib.Options{}) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity.go b/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity.go index 88e2f07d5..811371d4b 100644 --- a/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity.go +++ b/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity.go @@ -57,7 +57,7 @@ type FilterHasEnoughCapacity struct { // known at this point. // // Please also note that disk space is currently not considered by this filter. -func (s *FilterHasEnoughCapacity) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *FilterHasEnoughCapacity) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) // This map holds the free resources per host. diff --git a/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity_test.go b/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity_test.go index 5b026408f..85141864b 100644 --- a/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity_test.go @@ -4,6 +4,7 @@ package filters import ( + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -583,7 +584,7 @@ func TestFilterHasEnoughCapacity_ReservationTypes(t *testing.T) { step.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build() step.Options = tt.opts - result, err := step.Run(slog.Default(), tt.request) + result, err := step.Run(slog.Default(), tt.request, lib.Options{}) if err != nil { t.Fatalf("expected no error, got %v", err) } @@ -798,7 +799,7 @@ func TestFilterHasEnoughCapacity_IgnoredReservationTypes(t *testing.T) { IgnoredReservationTypes: tt.ignoredReservationTypes, } - result, err := step.Run(slog.Default(), tt.request) + result, err := step.Run(slog.Default(), tt.request, lib.Options{}) if err != nil { t.Fatalf("expected no error, got %v", err) } @@ -961,7 +962,7 @@ func TestFilterHasEnoughCapacity_ReserveForCommittedResourceIntent(t *testing.T) step.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build() step.Options = tt.opts - result, err := step.Run(slog.Default(), tt.request) + result, err := step.Run(slog.Default(), tt.request, lib.Options{}) if err != nil { t.Fatalf("expected no error, got %v", err) } @@ -1011,7 +1012,7 @@ func TestFilterHasEnoughCapacity_PlannedCRDoesNotBlock(t *testing.T) { step.Options = FilterHasEnoughCapacityOpts{LockReserved: false} request := newNovaRequest("instance-123", "project-A", "m1.large", "gp-1", 4, "8Gi", false, []string{"host1"}) - result, err := step.Run(slog.Default(), request) + result, err := step.Run(slog.Default(), request, lib.Options{}) if err != nil { t.Fatalf("expected no error, got %v", err) } @@ -1084,7 +1085,7 @@ func TestFilterHasEnoughCapacity_NilEffectiveCapacityFallback(t *testing.T) { step.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build() step.Options = FilterHasEnoughCapacityOpts{LockReserved: false} - result, err := step.Run(slog.Default(), tt.request) + result, err := step.Run(slog.Default(), tt.request, lib.Options{}) if err != nil { t.Fatalf("expected no error, got %v", err) } @@ -1283,7 +1284,7 @@ func TestFilterHasEnoughCapacity_VMInterReservationMigration(t *testing.T) { step.Options = FilterHasEnoughCapacityOpts{LockReserved: false} request := newNovaRequest("instance-new", thirdParty, "m1.small", flavorGroup, 3, "6Gi", false, []string{"hv-a", "hv-b"}) - result, err := step.Run(slog.Default(), request) + result, err := step.Run(slog.Default(), request, lib.Options{}) if err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/internal/scheduling/nova/plugins/filters/filter_has_requested_traits.go b/internal/scheduling/nova/plugins/filters/filter_has_requested_traits.go index aa35d2fc9..53f050db5 100644 --- a/internal/scheduling/nova/plugins/filters/filter_has_requested_traits.go +++ b/internal/scheduling/nova/plugins/filters/filter_has_requested_traits.go @@ -21,7 +21,7 @@ type FilterHasRequestedTraits struct { // Filter hosts that do not have the requested traits given by the extra spec: // - "trait:": "forbidden" means the host must not have the specified trait. // - "trait:": "required" means the host must have the specified trait. -func (s *FilterHasRequestedTraits) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *FilterHasRequestedTraits) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) var requiredTraits, forbiddenTraits []string for key, value := range request.Spec.Data.Flavor.Data.ExtraSpecs { diff --git a/internal/scheduling/nova/plugins/filters/filter_has_requested_traits_test.go b/internal/scheduling/nova/plugins/filters/filter_has_requested_traits_test.go index 10a7c94aa..edd7bb9a8 100644 --- a/internal/scheduling/nova/plugins/filters/filter_has_requested_traits_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_has_requested_traits_test.go @@ -4,6 +4,7 @@ package filters import ( + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -461,7 +462,7 @@ func TestFilterHasRequestedTraits_Run(t *testing.T) { WithObjects(hvs...). Build() - result, err := step.Run(slog.Default(), tt.request) + result, err := step.Run(slog.Default(), tt.request, lib.Options{}) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/filters/filter_host_instructions.go b/internal/scheduling/nova/plugins/filters/filter_host_instructions.go index dafb6675f..6e6a13da8 100644 --- a/internal/scheduling/nova/plugins/filters/filter_host_instructions.go +++ b/internal/scheduling/nova/plugins/filters/filter_host_instructions.go @@ -18,7 +18,7 @@ type FilterHostInstructionsStep struct { // Filter hosts based on instructions given in the request spec. Supported are: // - spec.ignore_hosts: Filter out all hosts in this list. // - spec.force_hosts: Include only hosts in this list. -func (s *FilterHostInstructionsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *FilterHostInstructionsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) if request.Spec.Data.IgnoreHosts != nil { for _, host := range *request.Spec.Data.IgnoreHosts { diff --git a/internal/scheduling/nova/plugins/filters/filter_host_instructions_test.go b/internal/scheduling/nova/plugins/filters/filter_host_instructions_test.go index 10bcb60c9..a09e12a2e 100644 --- a/internal/scheduling/nova/plugins/filters/filter_host_instructions_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_host_instructions_test.go @@ -4,6 +4,7 @@ package filters import ( + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -331,7 +332,7 @@ func TestFilterHostInstructionsStep_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { step := &FilterHostInstructionsStep{} - result, err := step.Run(slog.Default(), tt.request) + result, err := step.Run(slog.Default(), tt.request, lib.Options{}) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/filters/filter_instance_group_affinity.go b/internal/scheduling/nova/plugins/filters/filter_instance_group_affinity.go index 326864b9d..41eb5181e 100644 --- a/internal/scheduling/nova/plugins/filters/filter_instance_group_affinity.go +++ b/internal/scheduling/nova/plugins/filters/filter_instance_group_affinity.go @@ -19,6 +19,7 @@ type FilterInstanceGroupAffinityStep struct { func (s *FilterInstanceGroupAffinityStep) Run( traceLog *slog.Logger, request api.ExternalSchedulerRequest, + opts lib.Options, ) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) diff --git a/internal/scheduling/nova/plugins/filters/filter_instance_group_affinity_test.go b/internal/scheduling/nova/plugins/filters/filter_instance_group_affinity_test.go index 7321747e3..25e0c224d 100644 --- a/internal/scheduling/nova/plugins/filters/filter_instance_group_affinity_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_instance_group_affinity_test.go @@ -4,6 +4,7 @@ package filters import ( + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -326,7 +327,7 @@ func TestFilterInstanceGroupAffinityStep_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { step := &FilterInstanceGroupAffinityStep{} - result, err := step.Run(slog.Default(), tt.request) + result, err := step.Run(slog.Default(), tt.request, lib.Options{}) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/filters/filter_instance_group_anti_affinity.go b/internal/scheduling/nova/plugins/filters/filter_instance_group_anti_affinity.go index 0dee29d9e..17a9a8735 100644 --- a/internal/scheduling/nova/plugins/filters/filter_instance_group_anti_affinity.go +++ b/internal/scheduling/nova/plugins/filters/filter_instance_group_anti_affinity.go @@ -22,6 +22,7 @@ type FilterInstanceGroupAntiAffinityStep struct { func (s *FilterInstanceGroupAntiAffinityStep) Run( traceLog *slog.Logger, request api.ExternalSchedulerRequest, + opts lib.Options, ) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) diff --git a/internal/scheduling/nova/plugins/filters/filter_instance_group_anti_affinity_test.go b/internal/scheduling/nova/plugins/filters/filter_instance_group_anti_affinity_test.go index 6eea6bc7f..e70ada792 100644 --- a/internal/scheduling/nova/plugins/filters/filter_instance_group_anti_affinity_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_instance_group_anti_affinity_test.go @@ -4,6 +4,7 @@ package filters import ( + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -519,7 +520,7 @@ func TestFilterInstanceGroupAntiAffinityStep_Run(t *testing.T) { WithScheme(scheme). WithObjects(hvs...). Build() - result, err := step.Run(slog.Default(), tt.request) + result, err := step.Run(slog.Default(), tt.request, lib.Options{}) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/filters/filter_live_migratable.go b/internal/scheduling/nova/plugins/filters/filter_live_migratable.go index a19238721..32c467fe8 100644 --- a/internal/scheduling/nova/plugins/filters/filter_live_migratable.go +++ b/internal/scheduling/nova/plugins/filters/filter_live_migratable.go @@ -51,6 +51,7 @@ func (s *FilterLiveMigratableStep) checkHasSufficientFeatures( func (s *FilterLiveMigratableStep) Run( traceLog *slog.Logger, request api.ExternalSchedulerRequest, + opts lib.Options, ) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) diff --git a/internal/scheduling/nova/plugins/filters/filter_live_migratable_test.go b/internal/scheduling/nova/plugins/filters/filter_live_migratable_test.go index c5651b025..c4cb9df9c 100644 --- a/internal/scheduling/nova/plugins/filters/filter_live_migratable_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_live_migratable_test.go @@ -641,7 +641,7 @@ func TestFilterLiveMigratableStep_Run(t *testing.T) { }, } - result, err := step.Run(slog.Default(), tt.request) + result, err := step.Run(slog.Default(), tt.request, lib.Options{}) if tt.expectErr { if err == nil { @@ -728,7 +728,7 @@ func TestFilterLiveMigratableStep_Run_SourceHostNotFound(t *testing.T) { }, } - _, err := step.Run(slog.Default(), request) + _, err := step.Run(slog.Default(), request, lib.Options{}) if err == nil { t.Errorf("expected error when source host not found, got none") } @@ -774,7 +774,7 @@ func TestFilterLiveMigratableStep_Run_ClientError(t *testing.T) { }, } - _, err := step.Run(slog.Default(), request) + _, err := step.Run(slog.Default(), request, lib.Options{}) if err == nil { t.Errorf("expected error when client fails, got none") } diff --git a/internal/scheduling/nova/plugins/filters/filter_requested_destination.go b/internal/scheduling/nova/plugins/filters/filter_requested_destination.go index 8922ab8c4..83c0d5521 100644 --- a/internal/scheduling/nova/plugins/filters/filter_requested_destination.go +++ b/internal/scheduling/nova/plugins/filters/filter_requested_destination.go @@ -100,7 +100,7 @@ func (s *FilterRequestedDestinationStep) processRequestedHost( // The requested destination can include a specific host, aggregates, or both. // When both are specified, aggregate filtering is applied first, followed by // host filtering. -func (s *FilterRequestedDestinationStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *FilterRequestedDestinationStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) rd := request.Spec.Data.RequestedDestination if rd == nil { diff --git a/internal/scheduling/nova/plugins/filters/filter_requested_destination_test.go b/internal/scheduling/nova/plugins/filters/filter_requested_destination_test.go index 5a752160e..e85d38a42 100644 --- a/internal/scheduling/nova/plugins/filters/filter_requested_destination_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_requested_destination_test.go @@ -578,7 +578,7 @@ func TestFilterRequestedDestinationStep_Run(t *testing.T) { }, } - result, err := step.Run(slog.Default(), tt.request) + result, err := step.Run(slog.Default(), tt.request, lib.Options{}) if tt.expectErr { if err == nil { @@ -777,7 +777,7 @@ func TestFilterRequestedDestinationStep_Run_ClientError(t *testing.T) { }, } - _, err := step.Run(slog.Default(), request) + _, err := step.Run(slog.Default(), request, lib.Options{}) if err == nil { t.Errorf("expected error when client fails, got none") } diff --git a/internal/scheduling/nova/plugins/filters/filter_status_conditions.go b/internal/scheduling/nova/plugins/filters/filter_status_conditions.go index 3d7f2aae6..05bbbdcc4 100644 --- a/internal/scheduling/nova/plugins/filters/filter_status_conditions.go +++ b/internal/scheduling/nova/plugins/filters/filter_status_conditions.go @@ -20,7 +20,7 @@ type FilterStatusConditionsStep struct { // Check that all status conditions meet the expected values, for example, // that the hypervisor is ready and not disabled. -func (s *FilterStatusConditionsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *FilterStatusConditionsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) hvs := &hv1.HypervisorList{} diff --git a/internal/scheduling/nova/plugins/filters/filter_status_conditions_test.go b/internal/scheduling/nova/plugins/filters/filter_status_conditions_test.go index adbfc8c65..91cc67489 100644 --- a/internal/scheduling/nova/plugins/filters/filter_status_conditions_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_status_conditions_test.go @@ -4,6 +4,7 @@ package filters import ( + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -341,7 +342,7 @@ func TestFilterStatusConditionsStep_Run(t *testing.T) { WithScheme(scheme). WithObjects(hvs...). Build() - result, err := step.Run(slog.Default(), tt.request) + result, err := step.Run(slog.Default(), tt.request, lib.Options{}) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/weighers/kvm_binpack.go b/internal/scheduling/nova/plugins/weighers/kvm_binpack.go index e1509a4cc..717f3e667 100644 --- a/internal/scheduling/nova/plugins/weighers/kvm_binpack.go +++ b/internal/scheduling/nova/plugins/weighers/kvm_binpack.go @@ -69,7 +69,7 @@ type KVMBinpackStep struct { } // Run this weigher in the pipeline after filters have been executed. -func (s *KVMBinpackStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *KVMBinpackStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) result.Statistics["binpack score"] = s.PrepareStats(request, "float") diff --git a/internal/scheduling/nova/plugins/weighers/kvm_binpack_test.go b/internal/scheduling/nova/plugins/weighers/kvm_binpack_test.go index 69e1aa9f6..ae7ff0a6d 100644 --- a/internal/scheduling/nova/plugins/weighers/kvm_binpack_test.go +++ b/internal/scheduling/nova/plugins/weighers/kvm_binpack_test.go @@ -4,6 +4,7 @@ package weighers import ( + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "strings" "testing" @@ -448,7 +449,7 @@ func TestKVMBinpackStep_Run(t *testing.T) { step.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build() step.Options = tt.opts - result, err := step.Run(slog.Default(), tt.request) + result, err := step.Run(slog.Default(), tt.request, lib.Options{}) if tt.wantErr { if err == nil { t.Fatalf("expected error, got nil") diff --git a/internal/scheduling/nova/plugins/weighers/kvm_failover_evacuation.go b/internal/scheduling/nova/plugins/weighers/kvm_failover_evacuation.go index dcbcbf8bd..8e463ddc9 100644 --- a/internal/scheduling/nova/plugins/weighers/kvm_failover_evacuation.go +++ b/internal/scheduling/nova/plugins/weighers/kvm_failover_evacuation.go @@ -50,7 +50,7 @@ type KVMFailoverEvacuationStep struct { // Run the weigher step. // For evacuation requests, hosts matching a failover reservation where the VM is in Allocations get a higher weight. // For non-evacuation requests (e.g., live migration, rebuild), this weigher has no effect. -func (s *KVMFailoverEvacuationStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *KVMFailoverEvacuationStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) intent, err := request.GetIntent() diff --git a/internal/scheduling/nova/plugins/weighers/kvm_failover_evacuation_test.go b/internal/scheduling/nova/plugins/weighers/kvm_failover_evacuation_test.go index 0664e55d4..eb7925c71 100644 --- a/internal/scheduling/nova/plugins/weighers/kvm_failover_evacuation_test.go +++ b/internal/scheduling/nova/plugins/weighers/kvm_failover_evacuation_test.go @@ -4,6 +4,7 @@ package weighers import ( + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -264,7 +265,7 @@ func TestKVMFailoverEvacuationStep_Run(t *testing.T) { step.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build() step.Options = tt.opts - result, err := step.Run(slog.Default(), tt.request) + result, err := step.Run(slog.Default(), tt.request, lib.Options{}) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/weighers/kvm_failover_reservation_consolidation.go b/internal/scheduling/nova/plugins/weighers/kvm_failover_reservation_consolidation.go index 727afce33..754db1be4 100644 --- a/internal/scheduling/nova/plugins/weighers/kvm_failover_reservation_consolidation.go +++ b/internal/scheduling/nova/plugins/weighers/kvm_failover_reservation_consolidation.go @@ -78,7 +78,7 @@ type KVMFailoverReservationConsolidationStep struct { // Run the weigher step. // For reserve_for_failover requests, hosts are scored based on existing failover reservation density // and same-spec diversity. For all other request types, this weigher has no effect. -func (s *KVMFailoverReservationConsolidationStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *KVMFailoverReservationConsolidationStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) intent, err := request.GetIntent() diff --git a/internal/scheduling/nova/plugins/weighers/kvm_failover_reservation_consolidation_test.go b/internal/scheduling/nova/plugins/weighers/kvm_failover_reservation_consolidation_test.go index 62d69d319..a065d1ef8 100644 --- a/internal/scheduling/nova/plugins/weighers/kvm_failover_reservation_consolidation_test.go +++ b/internal/scheduling/nova/plugins/weighers/kvm_failover_reservation_consolidation_test.go @@ -4,6 +4,7 @@ package weighers import ( + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "math" "testing" @@ -256,7 +257,7 @@ func TestKVMFailoverReservationConsolidationStep_Run(t *testing.T) { step.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build() step.Options = tt.opts - result, err := step.Run(slog.Default(), tt.request) + result, err := step.Run(slog.Default(), tt.request, lib.Options{}) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/weighers/kvm_instance_group_soft_affinity.go b/internal/scheduling/nova/plugins/weighers/kvm_instance_group_soft_affinity.go index 5f13897f0..95c35fe92 100644 --- a/internal/scheduling/nova/plugins/weighers/kvm_instance_group_soft_affinity.go +++ b/internal/scheduling/nova/plugins/weighers/kvm_instance_group_soft_affinity.go @@ -26,7 +26,7 @@ type KVMInstanceGroupSoftAffinityStep struct { lib.BaseWeigher[api.ExternalSchedulerRequest, lib.EmptyFilterWeigherPipelineStepOpts] } -func (s *KVMInstanceGroupSoftAffinityStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *KVMInstanceGroupSoftAffinityStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) result.Statistics["affinity"] = s.PrepareStats(request, "float") diff --git a/internal/scheduling/nova/plugins/weighers/kvm_instance_group_soft_affinity_test.go b/internal/scheduling/nova/plugins/weighers/kvm_instance_group_soft_affinity_test.go index fcf13f86b..b0817b737 100644 --- a/internal/scheduling/nova/plugins/weighers/kvm_instance_group_soft_affinity_test.go +++ b/internal/scheduling/nova/plugins/weighers/kvm_instance_group_soft_affinity_test.go @@ -4,6 +4,7 @@ package weighers import ( + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -346,7 +347,7 @@ func TestKVMInstanceGroupSoftAffinityStep_Run(t *testing.T) { step := &KVMInstanceGroupSoftAffinityStep{} step.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build() - result, err := step.Run(slog.Default(), tt.request) + result, err := step.Run(slog.Default(), tt.request, lib.Options{}) if tt.wantErr { if err == nil { t.Fatalf("expected error, got nil") diff --git a/internal/scheduling/nova/plugins/weighers/kvm_prefer_smaller_hosts.go b/internal/scheduling/nova/plugins/weighers/kvm_prefer_smaller_hosts.go index b65a5f75f..88fbd2ca9 100644 --- a/internal/scheduling/nova/plugins/weighers/kvm_prefer_smaller_hosts.go +++ b/internal/scheduling/nova/plugins/weighers/kvm_prefer_smaller_hosts.go @@ -57,7 +57,7 @@ type KVMPreferSmallerHostsStep struct { } // Run this weigher in the pipeline after filters have been executed. -func (s *KVMPreferSmallerHostsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *KVMPreferSmallerHostsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) result.Statistics["small host score"] = s.PrepareStats(request, "float") diff --git a/internal/scheduling/nova/plugins/weighers/kvm_prefer_smaller_hosts_test.go b/internal/scheduling/nova/plugins/weighers/kvm_prefer_smaller_hosts_test.go index 2ab2deb89..6f306b414 100644 --- a/internal/scheduling/nova/plugins/weighers/kvm_prefer_smaller_hosts_test.go +++ b/internal/scheduling/nova/plugins/weighers/kvm_prefer_smaller_hosts_test.go @@ -4,6 +4,7 @@ package weighers import ( + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "strings" "testing" @@ -599,7 +600,7 @@ func TestKVMPreferSmallerHostsStep_Run(t *testing.T) { step.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build() step.Options = tt.opts - result, err := step.Run(slog.Default(), tt.request) + result, err := step.Run(slog.Default(), tt.request, lib.Options{}) if tt.wantErr { if err == nil { t.Fatalf("expected error, got nil") diff --git a/internal/scheduling/nova/plugins/weighers/vmware_anti_affinity_noisy_projects.go b/internal/scheduling/nova/plugins/weighers/vmware_anti_affinity_noisy_projects.go index b2b886d49..59a485d07 100644 --- a/internal/scheduling/nova/plugins/weighers/vmware_anti_affinity_noisy_projects.go +++ b/internal/scheduling/nova/plugins/weighers/vmware_anti_affinity_noisy_projects.go @@ -52,7 +52,7 @@ func (s *VMwareAntiAffinityNoisyProjectsStep) Init(ctx context.Context, client c } // Downvote the hosts a project is currently running on if it's noisy. -func (s *VMwareAntiAffinityNoisyProjectsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *VMwareAntiAffinityNoisyProjectsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) result.Statistics["avg cpu usage of this project"] = s.PrepareStats(request, "%") diff --git a/internal/scheduling/nova/plugins/weighers/vmware_anti_affinity_noisy_projects_test.go b/internal/scheduling/nova/plugins/weighers/vmware_anti_affinity_noisy_projects_test.go index 304ab0612..6c33ab721 100644 --- a/internal/scheduling/nova/plugins/weighers/vmware_anti_affinity_noisy_projects_test.go +++ b/internal/scheduling/nova/plugins/weighers/vmware_anti_affinity_noisy_projects_test.go @@ -4,6 +4,7 @@ package weighers import ( + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "context" "log/slog" "strings" @@ -273,7 +274,7 @@ func TestVMwareAntiAffinityNoisyProjectsStep_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result, err := step.Run(slog.Default(), tt.request) + result, err := step.Run(slog.Default(), tt.request, lib.Options{}) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/weighers/vmware_avoid_long_term_contended_hosts.go b/internal/scheduling/nova/plugins/weighers/vmware_avoid_long_term_contended_hosts.go index 14905cf00..8f46dc5d8 100644 --- a/internal/scheduling/nova/plugins/weighers/vmware_avoid_long_term_contended_hosts.go +++ b/internal/scheduling/nova/plugins/weighers/vmware_avoid_long_term_contended_hosts.go @@ -61,7 +61,7 @@ func (s *VMwareAvoidLongTermContendedHostsStep) Init(ctx context.Context, client } // Downvote hosts that are highly contended. -func (s *VMwareAvoidLongTermContendedHostsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *VMwareAvoidLongTermContendedHostsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) result.Statistics["avg cpu contention"] = s.PrepareStats(request, "%") diff --git a/internal/scheduling/nova/plugins/weighers/vmware_avoid_long_term_contended_hosts_test.go b/internal/scheduling/nova/plugins/weighers/vmware_avoid_long_term_contended_hosts_test.go index 5a69cdaf7..fd0bd99d8 100644 --- a/internal/scheduling/nova/plugins/weighers/vmware_avoid_long_term_contended_hosts_test.go +++ b/internal/scheduling/nova/plugins/weighers/vmware_avoid_long_term_contended_hosts_test.go @@ -4,6 +4,7 @@ package weighers import ( + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "context" "log/slog" "strings" @@ -256,7 +257,7 @@ func TestVMwareAvoidLongTermContendedHostsStep_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result, err := step.Run(slog.Default(), tt.request) + result, err := step.Run(slog.Default(), tt.request, lib.Options{}) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/weighers/vmware_avoid_short_term_contended_hosts.go b/internal/scheduling/nova/plugins/weighers/vmware_avoid_short_term_contended_hosts.go index fd9e81335..f2d7896f0 100644 --- a/internal/scheduling/nova/plugins/weighers/vmware_avoid_short_term_contended_hosts.go +++ b/internal/scheduling/nova/plugins/weighers/vmware_avoid_short_term_contended_hosts.go @@ -61,7 +61,7 @@ func (s *VMwareAvoidShortTermContendedHostsStep) Init(ctx context.Context, clien } // Downvote hosts that are highly contended. -func (s *VMwareAvoidShortTermContendedHostsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *VMwareAvoidShortTermContendedHostsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) result.Statistics["avg cpu contention"] = s.PrepareStats(request, "%") diff --git a/internal/scheduling/nova/plugins/weighers/vmware_avoid_short_term_contended_hosts_test.go b/internal/scheduling/nova/plugins/weighers/vmware_avoid_short_term_contended_hosts_test.go index 0dfe280d0..8c0f8a941 100644 --- a/internal/scheduling/nova/plugins/weighers/vmware_avoid_short_term_contended_hosts_test.go +++ b/internal/scheduling/nova/plugins/weighers/vmware_avoid_short_term_contended_hosts_test.go @@ -4,6 +4,7 @@ package weighers import ( + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "context" "log/slog" "strings" @@ -256,7 +257,7 @@ func TestVMwareAvoidShortTermContendedHostsStep_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result, err := step.Run(slog.Default(), tt.request) + result, err := step.Run(slog.Default(), tt.request, lib.Options{}) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/weighers/vmware_binpack.go b/internal/scheduling/nova/plugins/weighers/vmware_binpack.go index 217dc7d7f..adb6f86bc 100644 --- a/internal/scheduling/nova/plugins/weighers/vmware_binpack.go +++ b/internal/scheduling/nova/plugins/weighers/vmware_binpack.go @@ -85,7 +85,7 @@ func (s *VMwareBinpackStep) Init(ctx context.Context, client client.Client, weig } // Run this weigher in the pipeline after filters have been executed. -func (s *VMwareBinpackStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *VMwareBinpackStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) result.Statistics["binpack score"] = s.PrepareStats(request, "float") diff --git a/internal/scheduling/nova/plugins/weighers/vmware_binpack_test.go b/internal/scheduling/nova/plugins/weighers/vmware_binpack_test.go index cdca6c569..d274ef40d 100644 --- a/internal/scheduling/nova/plugins/weighers/vmware_binpack_test.go +++ b/internal/scheduling/nova/plugins/weighers/vmware_binpack_test.go @@ -4,6 +4,7 @@ package weighers import ( + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -211,7 +212,7 @@ func TestVMwareBinpackStep_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result, err := step.Run(slog.Default(), tt.request) + result, err := step.Run(slog.Default(), tt.request, lib.Options{}) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/pods/filter_weigher_pipeline_controller.go b/internal/scheduling/pods/filter_weigher_pipeline_controller.go index 0ceee6485..7f76adede 100644 --- a/internal/scheduling/pods/filter_weigher_pipeline_controller.go +++ b/internal/scheduling/pods/filter_weigher_pipeline_controller.go @@ -158,7 +158,7 @@ func (c *FilterWeigherPipelineController) process(ctx context.Context, decision // Execute the scheduling pipeline. request := pods.PodPipelineRequest{Nodes: nodes.Items, Pod: *pod} - result, err := pipeline.Run(request) + result, err := pipeline.Run(request, lib.Options{}) if err != nil { log.V(1).Error(err, "failed to run scheduler pipeline") return errors.New("failed to run scheduler pipeline") diff --git a/internal/scheduling/pods/filter_weigher_pipeline_controller_test.go b/internal/scheduling/pods/filter_weigher_pipeline_controller_test.go index 143ed9f83..f1e429da3 100644 --- a/internal/scheduling/pods/filter_weigher_pipeline_controller_test.go +++ b/internal/scheduling/pods/filter_weigher_pipeline_controller_test.go @@ -492,7 +492,7 @@ func createMockPodPipeline() lib.FilterWeigherPipeline[pods.PodPipelineRequest] type mockPodPipeline struct{} -func (m *mockPodPipeline) Run(request pods.PodPipelineRequest) (v1alpha1.DecisionResult, error) { +func (m *mockPodPipeline) Run(request pods.PodPipelineRequest, opts lib.Options) (v1alpha1.DecisionResult, error) { if len(request.Nodes) == 0 { return v1alpha1.DecisionResult{}, nil } diff --git a/internal/scheduling/pods/plugins/filters/filter_node_affinity.go b/internal/scheduling/pods/plugins/filters/filter_node_affinity.go index 996897bdb..c8953ec1b 100644 --- a/internal/scheduling/pods/plugins/filters/filter_node_affinity.go +++ b/internal/scheduling/pods/plugins/filters/filter_node_affinity.go @@ -27,7 +27,7 @@ func (f *NodeAffinityFilter) Validate(ctx context.Context, params v1alpha1.Param return nil } -func (NodeAffinityFilter) Run(traceLog *slog.Logger, request pods.PodPipelineRequest) (*lib.FilterWeigherPipelineStepResult, error) { +func (NodeAffinityFilter) Run(traceLog *slog.Logger, request pods.PodPipelineRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { activations := make(map[string]float64) stats := make(map[string]lib.FilterWeigherPipelineStepStatistics) diff --git a/internal/scheduling/pods/plugins/filters/filter_node_affinity_test.go b/internal/scheduling/pods/plugins/filters/filter_node_affinity_test.go index 0172d60d7..1442994c2 100644 --- a/internal/scheduling/pods/plugins/filters/filter_node_affinity_test.go +++ b/internal/scheduling/pods/plugins/filters/filter_node_affinity_test.go @@ -4,6 +4,7 @@ package filters import ( + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -399,7 +400,7 @@ func TestNodeAffinityFilter_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { filter := &NodeAffinityFilter{} - result, err := filter.Run(slog.Default(), tt.request) + result, err := filter.Run(slog.Default(), tt.request, lib.Options{}) if err != nil { t.Errorf("expected Run() to succeed, got error: %v", err) diff --git a/internal/scheduling/pods/plugins/filters/filter_node_available.go b/internal/scheduling/pods/plugins/filters/filter_node_available.go index 2d6e11d22..cfbccfa28 100644 --- a/internal/scheduling/pods/plugins/filters/filter_node_available.go +++ b/internal/scheduling/pods/plugins/filters/filter_node_available.go @@ -26,7 +26,7 @@ func (f *NodeAvailableFilter) Validate(ctx context.Context, params v1alpha1.Para return nil } -func (NodeAvailableFilter) Run(traceLog *slog.Logger, request pods.PodPipelineRequest) (*lib.FilterWeigherPipelineStepResult, error) { +func (NodeAvailableFilter) Run(traceLog *slog.Logger, request pods.PodPipelineRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { activations := make(map[string]float64) stats := make(map[string]lib.FilterWeigherPipelineStepStatistics) diff --git a/internal/scheduling/pods/plugins/filters/filter_node_available_test.go b/internal/scheduling/pods/plugins/filters/filter_node_available_test.go index 3eac7873c..e9966d594 100644 --- a/internal/scheduling/pods/plugins/filters/filter_node_available_test.go +++ b/internal/scheduling/pods/plugins/filters/filter_node_available_test.go @@ -4,6 +4,7 @@ package filters import ( + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -309,7 +310,7 @@ func TestNodeAvailableFilter_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { filter := &NodeAvailableFilter{} - result, err := filter.Run(slog.Default(), tt.request) + result, err := filter.Run(slog.Default(), tt.request, lib.Options{}) if err != nil { t.Errorf("expected Run() to succeed, got error: %v", err) diff --git a/internal/scheduling/pods/plugins/filters/filter_node_capacity.go b/internal/scheduling/pods/plugins/filters/filter_node_capacity.go index cfceb8835..6d2f0eae7 100644 --- a/internal/scheduling/pods/plugins/filters/filter_node_capacity.go +++ b/internal/scheduling/pods/plugins/filters/filter_node_capacity.go @@ -27,7 +27,7 @@ func (f *NodeCapacityFilter) Validate(ctx context.Context, params v1alpha1.Param return nil } -func (NodeCapacityFilter) Run(traceLog *slog.Logger, request pods.PodPipelineRequest) (*lib.FilterWeigherPipelineStepResult, error) { +func (NodeCapacityFilter) Run(traceLog *slog.Logger, request pods.PodPipelineRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { activations := make(map[string]float64) stats := make(map[string]lib.FilterWeigherPipelineStepStatistics) diff --git a/internal/scheduling/pods/plugins/filters/filter_node_capacity_test.go b/internal/scheduling/pods/plugins/filters/filter_node_capacity_test.go index 543b4561d..950adace7 100644 --- a/internal/scheduling/pods/plugins/filters/filter_node_capacity_test.go +++ b/internal/scheduling/pods/plugins/filters/filter_node_capacity_test.go @@ -4,6 +4,7 @@ package filters import ( + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -351,7 +352,7 @@ func TestNodeCapacityFilter_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { filter := &NodeCapacityFilter{} - result, err := filter.Run(slog.Default(), tt.request) + result, err := filter.Run(slog.Default(), tt.request, lib.Options{}) if err != nil { t.Errorf("expected Run() to succeed, got error: %v", err) diff --git a/internal/scheduling/pods/plugins/filters/filter_noop.go b/internal/scheduling/pods/plugins/filters/filter_noop.go index e0666537b..fda0510f6 100644 --- a/internal/scheduling/pods/plugins/filters/filter_noop.go +++ b/internal/scheduling/pods/plugins/filters/filter_noop.go @@ -31,7 +31,7 @@ func (f *NoopFilter) Validate(ctx context.Context, params v1alpha1.Parameters) e // not in the map are considered as filtered out. // Provide a traceLog that contains the global request id and should // be used to log the step's execution. -func (NoopFilter) Run(traceLog *slog.Logger, request pods.PodPipelineRequest) (*lib.FilterWeigherPipelineStepResult, error) { +func (NoopFilter) Run(traceLog *slog.Logger, request pods.PodPipelineRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { activations := make(map[string]float64, len(request.Nodes)) stats := make(map[string]lib.FilterWeigherPipelineStepStatistics) // Usually you would do some filtering here, or adjust the weights. diff --git a/internal/scheduling/pods/plugins/filters/filter_noop_test.go b/internal/scheduling/pods/plugins/filters/filter_noop_test.go index dcdd90a69..71e22f72f 100644 --- a/internal/scheduling/pods/plugins/filters/filter_noop_test.go +++ b/internal/scheduling/pods/plugins/filters/filter_noop_test.go @@ -4,6 +4,7 @@ package filters import ( + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -85,7 +86,7 @@ func TestNoopFilter_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { filter := &NoopFilter{} - result, err := filter.Run(slog.Default(), tt.request) + result, err := filter.Run(slog.Default(), tt.request, lib.Options{}) if err != nil { t.Errorf("expected Run() to succeed, got error: %v", err) diff --git a/internal/scheduling/pods/plugins/filters/filter_taint.go b/internal/scheduling/pods/plugins/filters/filter_taint.go index 5a54d1cb6..f349c5213 100644 --- a/internal/scheduling/pods/plugins/filters/filter_taint.go +++ b/internal/scheduling/pods/plugins/filters/filter_taint.go @@ -26,7 +26,7 @@ func (f *TaintFilter) Validate(ctx context.Context, params v1alpha1.Parameters) return nil } -func (TaintFilter) Run(traceLog *slog.Logger, request pods.PodPipelineRequest) (*lib.FilterWeigherPipelineStepResult, error) { +func (TaintFilter) Run(traceLog *slog.Logger, request pods.PodPipelineRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { activations := make(map[string]float64) stats := make(map[string]lib.FilterWeigherPipelineStepStatistics) diff --git a/internal/scheduling/pods/plugins/filters/filter_taint_test.go b/internal/scheduling/pods/plugins/filters/filter_taint_test.go index 1d248b685..258f78ef0 100644 --- a/internal/scheduling/pods/plugins/filters/filter_taint_test.go +++ b/internal/scheduling/pods/plugins/filters/filter_taint_test.go @@ -4,6 +4,7 @@ package filters import ( + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -252,7 +253,7 @@ func TestTaintFilter_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { filter := &TaintFilter{} - result, err := filter.Run(slog.Default(), tt.request) + result, err := filter.Run(slog.Default(), tt.request, lib.Options{}) if err != nil { t.Errorf("expected Run() to succeed, got error: %v", err) diff --git a/internal/scheduling/pods/plugins/weighers/binpack.go b/internal/scheduling/pods/plugins/weighers/binpack.go index 07d310fb8..d0c8e2cf9 100644 --- a/internal/scheduling/pods/plugins/weighers/binpack.go +++ b/internal/scheduling/pods/plugins/weighers/binpack.go @@ -31,7 +31,7 @@ type BinpackingStep struct { lib.BaseWeigher[api.PodPipelineRequest, BinpackingStepOpts] } -func (s *BinpackingStep) Run(traceLog *slog.Logger, request api.PodPipelineRequest) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *BinpackingStep) Run(traceLog *slog.Logger, request api.PodPipelineRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) podResources := helpers.GetPodResourceRequests(request.Pod) diff --git a/internal/scheduling/pods/plugins/weighers/binpack_test.go b/internal/scheduling/pods/plugins/weighers/binpack_test.go index 198e110c1..82838909f 100644 --- a/internal/scheduling/pods/plugins/weighers/binpack_test.go +++ b/internal/scheduling/pods/plugins/weighers/binpack_test.go @@ -4,6 +4,7 @@ package weighers import ( + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "math" "testing" @@ -257,7 +258,7 @@ func TestBinpackingStep_Run(t *testing.T) { }, } - result, err := tt.step.Run(slog.Default(), tt.request) + result, err := tt.step.Run(slog.Default(), tt.request, lib.Options{}) if err != nil { t.Fatalf("expected no error, got %v", err) } From ad6611254d711318b4516f37e1e03ff3895a65ae Mon Sep 17 00:00:00 2001 From: mblos Date: Tue, 5 May 2026 10:30:46 +0200 Subject: [PATCH 2/8] example usage --- .../nova/filter_weigher_pipeline_controller.go | 14 +++++++++++--- .../filters/filter_aggregate_metadata_test.go | 2 +- .../plugins/filters/filter_has_enough_capacity.go | 14 +++++--------- .../filters/filter_has_enough_capacity_test.go | 13 +++++++++---- .../vmware_anti_affinity_noisy_projects_test.go | 2 +- .../vmware_avoid_long_term_contended_hosts_test.go | 2 +- ...vmware_avoid_short_term_contended_hosts_test.go | 2 +- .../commitments/reservation_controller.go | 10 ++++++++++ .../scheduling/reservations/scheduler_client.go | 5 +++++ 9 files changed, 44 insertions(+), 20 deletions(-) diff --git a/internal/scheduling/nova/filter_weigher_pipeline_controller.go b/internal/scheduling/nova/filter_weigher_pipeline_controller.go index 0c8b97e02..3ebf3216a 100644 --- a/internal/scheduling/nova/filter_weigher_pipeline_controller.go +++ b/internal/scheduling/nova/filter_weigher_pipeline_controller.go @@ -158,7 +158,7 @@ func (c *FilterWeigherPipelineController) process(ctx context.Context, decision log.Info("gathered all placement candidates", "numHosts", len(request.Hosts)) } - opts := c.buildOptions(pipelineConf) + opts := c.buildOptions(request, pipelineConf) result, err := pipeline.Run(request, opts) if opts.RecordHistory { c.upsertHistory(ctx, decision, err) @@ -179,10 +179,18 @@ func (c *FilterWeigherPipelineController) process(ctx context.Context, decision } // The base controller will delegate the pipeline creation down to this method. -func (c *FilterWeigherPipelineController) buildOptions(pipelineConf v1alpha1.Pipeline) lib.Options { - return lib.Options{ +func (c *FilterWeigherPipelineController) buildOptions(request api.ExternalSchedulerRequest, pipelineConf v1alpha1.Pipeline) lib.Options { + opts := lib.Options{ RecordHistory: pipelineConf.Spec.CreateHistory, } + intent, err := request.GetIntent() + if err == nil { + switch intent { + case api.ReserveForCommittedResourceIntent, api.ReserveForFailoverIntent: + opts.LockReservations = true + } + } + return opts } func (c *FilterWeigherPipelineController) InitPipeline( diff --git a/internal/scheduling/nova/plugins/filters/filter_aggregate_metadata_test.go b/internal/scheduling/nova/plugins/filters/filter_aggregate_metadata_test.go index f7093a003..45814d50f 100644 --- a/internal/scheduling/nova/plugins/filters/filter_aggregate_metadata_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_aggregate_metadata_test.go @@ -4,8 +4,8 @@ package filters import ( - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "context" + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" diff --git a/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity.go b/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity.go index 811371d4b..2b627e19c 100644 --- a/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity.go +++ b/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity.go @@ -122,18 +122,14 @@ func (s *FilterHasEnoughCapacity) Run(traceLog *slog.Logger, request api.Externa // Check if this is a CR reservation scheduling request. // If so, we should NOT unlock any CR reservations to prevent overbooking. // CR capacity should only be unlocked for actual VM scheduling. - intent, err := request.GetIntent() switch { - case err == nil && intent == api.ReserveForCommittedResourceIntent: - traceLog.Debug("keeping CR reservation locked for CR reservation scheduling", + case opts.LockReservations || s.Options.LockReserved: + traceLog.Debug("keeping CR reservation locked", "reservation", reservation.Name, - "intent", intent) + "lockReservations", opts.LockReservations, + "lockReserved", s.Options.LockReserved) // Don't continue - fall through to block the resources - case !s.Options.LockReserved && - // For committed resource reservations: unlock resources only if: - // 1. Project ID matches - // 2. ResourceGroup matches the flavor's hw_version - reservation.Spec.CommittedResourceReservation.ProjectID == request.Spec.Data.ProjectID && + case reservation.Spec.CommittedResourceReservation.ProjectID == request.Spec.Data.ProjectID && reservation.Spec.CommittedResourceReservation.ResourceGroup == request.Spec.Data.Flavor.Data.ExtraSpecs["hw_version"]: traceLog.Info("unlocking resources reserved by matching committed resource reservation with allocation", "reservation", reservation.Name, diff --git a/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity_test.go b/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity_test.go index 85141864b..56941bbd3 100644 --- a/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity_test.go @@ -820,6 +820,7 @@ func TestFilterHasEnoughCapacity_ReserveForCommittedResourceIntent(t *testing.T) reservations []*v1alpha1.Reservation request api.ExternalSchedulerRequest opts FilterHasEnoughCapacityOpts + pipelineOpts lib.Options expectedHosts []string filteredHosts []string }{ @@ -835,8 +836,9 @@ func TestFilterHasEnoughCapacity_ReserveForCommittedResourceIntent(t *testing.T) }, // Request with reserve_for_committed_resource intent (scheduling a new CR reservation) request: newNovaRequestWithIntent("new-reservation-uuid", "project-A", "m1.large", "gp-1", 4, "8Gi", "reserve_for_committed_resource", false, []string{"host1", "host2"}), - opts: FilterHasEnoughCapacityOpts{LockReserved: false}, // Note: LockReserved is false, but intent overrides - expectedHosts: []string{"host2"}, // host1 blocked because existing-cr stays locked + opts: FilterHasEnoughCapacityOpts{LockReserved: false}, + pipelineOpts: lib.Options{LockReservations: true}, + expectedHosts: []string{"host2"}, // host1 blocked because existing-cr stays locked filteredHosts: []string{"host1"}, }, { @@ -868,6 +870,7 @@ func TestFilterHasEnoughCapacity_ReserveForCommittedResourceIntent(t *testing.T) // Request with reserve_for_committed_resource intent request: newNovaRequestWithIntent("new-reservation-uuid", "project-A", "m1.large", "gp-1", 4, "8Gi", "reserve_for_committed_resource", false, []string{"host1", "host2"}), opts: FilterHasEnoughCapacityOpts{LockReserved: false}, + pipelineOpts: lib.Options{LockReservations: true}, expectedHosts: []string{"host2"}, filteredHosts: []string{"host1"}, // host1 blocked by other project's reservation (would be blocked anyway) }, @@ -886,6 +889,7 @@ func TestFilterHasEnoughCapacity_ReserveForCommittedResourceIntent(t *testing.T) // After blocking all 3 reservations (24 CPU), only 8 CPU free -> should fail request: newNovaRequestWithIntent("new-reservation-uuid", "project-A", "m1.large", "gp-1", 10, "20Gi", "reserve_for_committed_resource", false, []string{"host1"}), opts: FilterHasEnoughCapacityOpts{LockReserved: false}, + pipelineOpts: lib.Options{LockReservations: true}, expectedHosts: []string{}, filteredHosts: []string{"host1"}, // All reservations stay locked, not enough capacity }, @@ -917,13 +921,14 @@ func TestFilterHasEnoughCapacity_ReserveForCommittedResourceIntent(t *testing.T) newCommittedReservation("existing-cr", "host1", "project-A", "m1.large", "gp-1", "8", "16Gi", nil, nil), }, // Request with reserve_for_committed_resource intent - // IgnoredReservationTypes is a safety flag that overrides everything, including intent + // IgnoredReservationTypes is a safety flag that overrides everything, including LockReservations request: newNovaRequestWithIntent("new-reservation-uuid", "project-A", "m1.large", "gp-1", 4, "8Gi", "reserve_for_committed_resource", false, []string{"host1"}), opts: FilterHasEnoughCapacityOpts{ LockReserved: false, // IgnoredReservationTypes is a safety override - ignores CR even for CR scheduling IgnoredReservationTypes: []v1alpha1.ReservationType{v1alpha1.ReservationTypeCommittedResource}, }, + pipelineOpts: lib.Options{LockReservations: true}, expectedHosts: []string{"host1"}, // CR reservation is ignored via IgnoredReservationTypes (safety override) filteredHosts: []string{}, }, @@ -962,7 +967,7 @@ func TestFilterHasEnoughCapacity_ReserveForCommittedResourceIntent(t *testing.T) step.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build() step.Options = tt.opts - result, err := step.Run(slog.Default(), tt.request, lib.Options{}) + result, err := step.Run(slog.Default(), tt.request, tt.pipelineOpts) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/weighers/vmware_anti_affinity_noisy_projects_test.go b/internal/scheduling/nova/plugins/weighers/vmware_anti_affinity_noisy_projects_test.go index 6c33ab721..887982049 100644 --- a/internal/scheduling/nova/plugins/weighers/vmware_anti_affinity_noisy_projects_test.go +++ b/internal/scheduling/nova/plugins/weighers/vmware_anti_affinity_noisy_projects_test.go @@ -4,8 +4,8 @@ package weighers import ( - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "context" + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "strings" "testing" diff --git a/internal/scheduling/nova/plugins/weighers/vmware_avoid_long_term_contended_hosts_test.go b/internal/scheduling/nova/plugins/weighers/vmware_avoid_long_term_contended_hosts_test.go index fd0bd99d8..e3a62423c 100644 --- a/internal/scheduling/nova/plugins/weighers/vmware_avoid_long_term_contended_hosts_test.go +++ b/internal/scheduling/nova/plugins/weighers/vmware_avoid_long_term_contended_hosts_test.go @@ -4,8 +4,8 @@ package weighers import ( - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "context" + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "strings" "testing" diff --git a/internal/scheduling/nova/plugins/weighers/vmware_avoid_short_term_contended_hosts_test.go b/internal/scheduling/nova/plugins/weighers/vmware_avoid_short_term_contended_hosts_test.go index 8c0f8a941..9de020c95 100644 --- a/internal/scheduling/nova/plugins/weighers/vmware_avoid_short_term_contended_hosts_test.go +++ b/internal/scheduling/nova/plugins/weighers/vmware_avoid_short_term_contended_hosts_test.go @@ -4,8 +4,8 @@ package weighers import ( - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "context" + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "strings" "testing" diff --git a/internal/scheduling/reservations/commitments/reservation_controller.go b/internal/scheduling/reservations/commitments/reservation_controller.go index b65842c60..09cca220d 100644 --- a/internal/scheduling/reservations/commitments/reservation_controller.go +++ b/internal/scheduling/reservations/commitments/reservation_controller.go @@ -24,6 +24,7 @@ import ( schedulerdelegationapi "github.com/cobaltcore-dev/cortex/api/external/nova" "github.com/cobaltcore-dev/cortex/api/v1alpha1" + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations" "github.com/cobaltcore-dev/cortex/pkg/multicluster" hv1 "github.com/cobaltcore-dev/openstack-hypervisor-operator/api/v1" @@ -285,6 +286,15 @@ func (r *CommitmentReservationController) Reconcile(ctx context.Context, req ctr SchedulerHints: map[string]any{ "_nova_check_type": string(schedulerdelegationapi.ReserveForCommittedResourceIntent), }, + Options: lib.Options{ + ReadOnly: false, // mutates state (reservation placement) + LockReservations: true, // don't unlock CR reservations; finding a slot, not placing a VM + AssumeEmptyHosts: false, + IgnoredReservationTypes: nil, + MaxCandidates: 1, + RecordHistory: false, + CreateInflight: false, + }, } scheduleResp, err := r.SchedulerClient.ScheduleReservation(ctx, scheduleReq) diff --git a/internal/scheduling/reservations/scheduler_client.go b/internal/scheduling/reservations/scheduler_client.go index a42172ce2..e250d170d 100644 --- a/internal/scheduling/reservations/scheduler_client.go +++ b/internal/scheduling/reservations/scheduler_client.go @@ -12,6 +12,7 @@ import ( "time" api "github.com/cobaltcore-dev/cortex/api/external/nova" + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "github.com/go-logr/logr" logf "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -78,6 +79,10 @@ type ScheduleReservationRequest struct { // SchedulerHints are hints passed to the scheduler pipeline. // Used to set _nova_check_type for evacuation intent detection. SchedulerHints map[string]any + // Options configures the pipeline behavior for this scheduling call. + // These are derived from intent in buildOptions for the current HTTP path; + // will be passed directly once the scheduler client is a direct Go call. + Options lib.Options } // ScheduleReservationResponse contains the result of scheduling a reservation. From 4a7bc9e16918efd6dbd9e031acb5d774928753dd Mon Sep 17 00:00:00 2001 From: mblos Date: Tue, 5 May 2026 15:36:45 +0200 Subject: [PATCH 3/8] adding: MaxCandidates, IgnoredReservationTypes, ReadOnly --- .../scheduling/lib/filter_weigher_pipeline.go | 10 +++ .../lib/filter_weigher_pipeline_test.go | 56 ++++++++++++++ .../filter_weigher_pipeline_controller.go | 47 ++++++++++-- ...filter_weigher_pipeline_controller_test.go | 74 +++++++++++++++++++ .../filters/filter_has_enough_capacity.go | 3 +- .../filter_has_enough_capacity_test.go | 37 ++++++++++ 6 files changed, 219 insertions(+), 8 deletions(-) diff --git a/internal/scheduling/lib/filter_weigher_pipeline.go b/internal/scheduling/lib/filter_weigher_pipeline.go index 871aa771f..232c64313 100644 --- a/internal/scheduling/lib/filter_weigher_pipeline.go +++ b/internal/scheduling/lib/filter_weigher_pipeline.go @@ -299,6 +299,16 @@ func (p *filterWeigherPipeline[RequestType]) Run(request RequestType, opts Optio hosts := p.sortHostsByWeights(outWeights) traceLog.Info("scheduler: sorted hosts", "hosts", hosts) + if opts.MaxCandidates > 0 && len(hosts) > opts.MaxCandidates { + hosts = hosts[:opts.MaxCandidates] + // Drop trimmed hosts from outWeights so AggregatedOutWeights stays consistent. + for host := range outWeights { + if !slices.Contains(hosts, host) { + delete(outWeights, host) + } + } + } + // Collect some metrics about the pipeline execution. go p.monitor.observePipelineResult(request, hosts) diff --git a/internal/scheduling/lib/filter_weigher_pipeline_test.go b/internal/scheduling/lib/filter_weigher_pipeline_test.go index 9636084f3..9b89a2592 100644 --- a/internal/scheduling/lib/filter_weigher_pipeline_test.go +++ b/internal/scheduling/lib/filter_weigher_pipeline_test.go @@ -372,3 +372,59 @@ func TestFilterWeigherPipelineMonitor_SubPipeline(t *testing.T) { t.Error("original monitor should not be modified") } } + +func TestPipeline_MaxCandidates(t *testing.T) { + // Pipeline that passes all 4 hosts with descending weights. + pipeline := &filterWeigherPipeline[mockFilterWeigherPipelineRequest]{ + filters: map[string]Filter[mockFilterWeigherPipelineRequest]{}, + filtersOrder: []string{}, + weighersOrder: []string{}, + weighers: map[string]Weigher[mockFilterWeigherPipelineRequest]{}, + } + request := mockFilterWeigherPipelineRequest{ + Hosts: []string{"host1", "host2", "host3", "host4"}, + Weights: map[string]float64{"host1": 4.0, "host2": 3.0, "host3": 2.0, "host4": 1.0}, + } + + tests := []struct { + name string + maxCandidates int + wantLen int + wantFirst string + }{ + {"no limit", 0, 4, "host1"}, + {"limit to 2", 2, 2, "host1"}, + {"limit to 1", 1, 1, "host1"}, + {"limit larger than hosts", 10, 4, "host1"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := pipeline.Run(request, Options{MaxCandidates: tt.maxCandidates}) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if len(result.OrderedHosts) != tt.wantLen { + t.Errorf("expected %d hosts, got %d: %v", tt.wantLen, len(result.OrderedHosts), result.OrderedHosts) + } + if len(result.OrderedHosts) > 0 && result.OrderedHosts[0] != tt.wantFirst { + t.Errorf("expected first host %s, got %s", tt.wantFirst, result.OrderedHosts[0]) + } + if tt.maxCandidates > 0 && len(result.OrderedHosts) <= tt.maxCandidates { + // AggregatedOutWeights must only contain returned hosts. + for host := range result.AggregatedOutWeights { + found := false + for _, h := range result.OrderedHosts { + if h == host { + found = true + break + } + } + if !found { + t.Errorf("AggregatedOutWeights contains trimmed host %s", host) + } + } + } + }) + } +} diff --git a/internal/scheduling/nova/filter_weigher_pipeline_controller.go b/internal/scheduling/nova/filter_weigher_pipeline_controller.go index 3ebf3216a..53d4555f1 100644 --- a/internal/scheduling/nova/filter_weigher_pipeline_controller.go +++ b/internal/scheduling/nova/filter_weigher_pipeline_controller.go @@ -37,8 +37,9 @@ type FilterWeigherPipelineController struct { // Toolbox shared between all pipeline controllers. lib.BasePipelineController[lib.FilterWeigherPipeline[api.ExternalSchedulerRequest]] - // Mutex to only allow one process at a time - processMu sync.Mutex + // Mutex to only allow one process at a time. + // Read-only runs (opts.ReadOnly == true) acquire a read lock; write runs acquire the full lock. + processMu sync.RWMutex // Monitor to pass down to all pipelines. Monitor lib.FilterWeigherPipelineMonitor @@ -53,13 +54,23 @@ func (c *FilterWeigherPipelineController) PipelineType() v1alpha1.PipelineType { // Callback executed when kubernetes asks to reconcile a decision resource. func (c *FilterWeigherPipelineController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - c.processMu.Lock() - defer c.processMu.Unlock() - + // Peek at the decision before acquiring the lock so we can choose the right lock type. + // Read-only runs can proceed concurrently; write runs need the exclusive lock. decision := &v1alpha1.Decision{} if err := c.Get(ctx, req.NamespacedName, decision); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) } + if c.peekReadOnly(decision) { + c.processMu.RLock() + defer c.processMu.RUnlock() + } else { + c.processMu.Lock() + defer c.processMu.Unlock() + // Re-fetch after acquiring the exclusive lock to see consistent state. + if err := c.Get(ctx, req.NamespacedName, decision); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + } old := decision.DeepCopy() if err := c.process(ctx, decision); err != nil { return ctrl.Result{}, err @@ -73,8 +84,13 @@ func (c *FilterWeigherPipelineController) Reconcile(ctx context.Context, req ctr // Process the decision from the API. Should create and return the updated decision. func (c *FilterWeigherPipelineController) ProcessNewDecisionFromAPI(ctx context.Context, decision *v1alpha1.Decision) error { - c.processMu.Lock() - defer c.processMu.Unlock() + if c.peekReadOnly(decision) { + c.processMu.RLock() + defer c.processMu.RUnlock() + } else { + c.processMu.Lock() + defer c.processMu.Unlock() + } err := c.process(ctx, decision) if err != nil { @@ -178,6 +194,23 @@ func (c *FilterWeigherPipelineController) process(ctx context.Context, decision return nil } +// peekReadOnly determines whether a decision should use a read lock instead of +// the exclusive write lock. Defaults to false (exclusive) on any parse error. +func (c *FilterWeigherPipelineController) peekReadOnly(decision *v1alpha1.Decision) bool { + if decision.Spec.NovaRaw == nil { + return false + } + var request api.ExternalSchedulerRequest + if err := json.Unmarshal(decision.Spec.NovaRaw.Raw, &request); err != nil { + return false + } + pipelineConf, ok := c.PipelineConfigs[decision.Spec.PipelineRef.Name] + if !ok { + return false + } + return c.buildOptions(request, pipelineConf).ReadOnly +} + // The base controller will delegate the pipeline creation down to this method. func (c *FilterWeigherPipelineController) buildOptions(request api.ExternalSchedulerRequest, pipelineConf v1alpha1.Pipeline) lib.Options { opts := lib.Options{ diff --git a/internal/scheduling/nova/filter_weigher_pipeline_controller_test.go b/internal/scheduling/nova/filter_weigher_pipeline_controller_test.go index ed15145d4..781e259bd 100644 --- a/internal/scheduling/nova/filter_weigher_pipeline_controller_test.go +++ b/internal/scheduling/nova/filter_weigher_pipeline_controller_test.go @@ -928,3 +928,77 @@ func TestFilterWeigherPipelineController_IgnorePreselection(t *testing.T) { // Error variable for testing var errGathererFailed = errors.New("gatherer failed") + +func TestFilterWeigherPipelineController_PeekReadOnly(t *testing.T) { + validRequest := api.ExternalSchedulerRequest{ + Spec: api.NovaObject[api.NovaSpec]{ + Data: api.NovaSpec{NumInstances: 1}, + }, + } + validRaw, err := json.Marshal(validRequest) + if err != nil { + t.Fatalf("failed to marshal test request: %v", err) + } + + c := &FilterWeigherPipelineController{} + c.PipelineConfigs = map[string]v1alpha1.Pipeline{ + "test-pipeline": { + Spec: v1alpha1.PipelineSpec{CreateHistory: false}, + }, + } + + tests := []struct { + name string + decision *v1alpha1.Decision + want bool + }{ + { + name: "nil NovaRaw defaults to exclusive lock", + decision: &v1alpha1.Decision{ + Spec: v1alpha1.DecisionSpec{ + PipelineRef: corev1.ObjectReference{Name: "test-pipeline"}, + }, + }, + want: false, + }, + { + name: "invalid JSON defaults to exclusive lock", + decision: &v1alpha1.Decision{ + Spec: v1alpha1.DecisionSpec{ + PipelineRef: corev1.ObjectReference{Name: "test-pipeline"}, + NovaRaw: &runtime.RawExtension{Raw: []byte("not-json")}, + }, + }, + want: false, + }, + { + name: "unknown pipeline defaults to exclusive lock", + decision: &v1alpha1.Decision{ + Spec: v1alpha1.DecisionSpec{ + PipelineRef: corev1.ObjectReference{Name: "unknown-pipeline"}, + NovaRaw: &runtime.RawExtension{Raw: validRaw}, + }, + }, + want: false, + }, + { + name: "valid request with non-ReadOnly intent uses exclusive lock", + decision: &v1alpha1.Decision{ + Spec: v1alpha1.DecisionSpec{ + PipelineRef: corev1.ObjectReference{Name: "test-pipeline"}, + NovaRaw: &runtime.RawExtension{Raw: validRaw}, + }, + }, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := c.peekReadOnly(tt.decision) + if got != tt.want { + t.Errorf("expected peekReadOnly = %v, got %v", tt.want, got) + } + }) + } +} diff --git a/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity.go b/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity.go index 2b627e19c..343554d82 100644 --- a/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity.go +++ b/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity.go @@ -106,7 +106,8 @@ func (s *FilterHasEnoughCapacity) Run(traceLog *slog.Logger, request api.Externa } // Check if this reservation type should be ignored - if slices.Contains(s.Options.IgnoredReservationTypes, reservation.Spec.Type) { + if slices.Contains(s.Options.IgnoredReservationTypes, reservation.Spec.Type) || + slices.Contains(opts.IgnoredReservationTypes, reservation.Spec.Type) { traceLog.Debug("ignoring reservation type", "type", reservation.Spec.Type, "reservation", reservation.Name) continue } diff --git a/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity_test.go b/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity_test.go index 56941bbd3..fbbd21587 100644 --- a/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity_test.go @@ -808,6 +808,43 @@ func TestFilterHasEnoughCapacity_IgnoredReservationTypes(t *testing.T) { } } +func TestFilterHasEnoughCapacity_IgnoredReservationTypes_CallTime(t *testing.T) { + scheme := buildTestScheme(t) + + // Same two-host setup as the YAML-path test: CR on host1, Failover on host2. + // Each blocks 4 CPU, leaving 4 free; request needs 8 CPU so both hosts fail without ignoring. + hypervisors := []*hv1.Hypervisor{ + newHypervisor("host1", "16", "8", "32Gi", "16Gi"), + newHypervisor("host2", "16", "8", "32Gi", "16Gi"), + } + reservations := []*v1alpha1.Reservation{ + newCommittedReservation("cr-res", "host1", "project-X", "m1.large", "gp-1", "4", "8Gi", nil, nil), + newFailoverReservation("failover-res", "host2", "4", "8Gi", map[string]string{"other-vm": "host3"}), + } + request := newNovaRequest("instance-123", "project-A", "m1.large", "gp-1", 8, "16Gi", false, []string{"host1", "host2"}) + + objects := make([]client.Object, 0, len(hypervisors)+len(reservations)) + for _, h := range hypervisors { + objects = append(objects, h.DeepCopy()) + } + for _, r := range reservations { + objects = append(objects, r.DeepCopy()) + } + + step := &FilterHasEnoughCapacity{} + step.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build() + step.Options = FilterHasEnoughCapacityOpts{LockReserved: true} // no YAML-level ignores + + // Call-time: ignore CR reservations → host1 passes, host2 still blocked by failover. + result, err := step.Run(slog.Default(), request, lib.Options{ + IgnoredReservationTypes: []v1alpha1.ReservationType{v1alpha1.ReservationTypeCommittedResource}, + }) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + assertActivations(t, result.Activations, []string{"host1"}, []string{"host2"}) +} + func TestFilterHasEnoughCapacity_ReserveForCommittedResourceIntent(t *testing.T) { scheme := buildTestScheme(t) From d298e75c81337d2e4791846d0fea03d324ed314c Mon Sep 17 00:00:00 2001 From: mblos Date: Tue, 5 May 2026 15:41:06 +0200 Subject: [PATCH 4/8] validate options --- .../scheduling/lib/filter_weigher_pipeline.go | 3 +++ internal/scheduling/lib/options.go | 17 ++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/internal/scheduling/lib/filter_weigher_pipeline.go b/internal/scheduling/lib/filter_weigher_pipeline.go index 232c64313..a67fa9b96 100644 --- a/internal/scheduling/lib/filter_weigher_pipeline.go +++ b/internal/scheduling/lib/filter_weigher_pipeline.go @@ -265,6 +265,9 @@ func (s *filterWeigherPipeline[RequestType]) sortHostsByWeights(weights map[stri // Evaluate the pipeline and return a list of hosts in order of preference. func (p *filterWeigherPipeline[RequestType]) Run(request RequestType, opts Options) (v1alpha1.DecisionResult, error) { + if err := opts.Validate(); err != nil { + return v1alpha1.DecisionResult{}, err + } slogArgs := request.GetTraceLogArgs() slogArgsAny := make([]any, 0, len(slogArgs)) for _, arg := range slogArgs { diff --git a/internal/scheduling/lib/options.go b/internal/scheduling/lib/options.go index 28819415d..c516957f0 100644 --- a/internal/scheduling/lib/options.go +++ b/internal/scheduling/lib/options.go @@ -3,7 +3,11 @@ package lib -import "github.com/cobaltcore-dev/cortex/api/v1alpha1" +import ( + "errors" + + "github.com/cobaltcore-dev/cortex/api/v1alpha1" +) // Options configure the behavior of a single pipeline run at call time. // These are distinct from per-step YAML options (FilterWeigherPipelineStepOpts), @@ -30,3 +34,14 @@ type Options struct { // CreateInflight creates pessimistic blocking reservations for all returned candidates. CreateInflight bool } + +// Validate checks for mutually exclusive or inconsistent option combinations. +func (o Options) Validate() error { + if o.ReadOnly && o.RecordHistory { + return errors.New("ReadOnly and RecordHistory are mutually exclusive: read-only runs must not mutate state") + } + if o.ReadOnly && o.CreateInflight { + return errors.New("ReadOnly and CreateInflight are mutually exclusive: read-only runs must not mutate state") + } + return nil +} From 54639d144a11bae08e7b7cab097e8bb2a54b3880 Mon Sep 17 00:00:00 2001 From: mblos Date: Tue, 5 May 2026 16:52:53 +0200 Subject: [PATCH 5/8] refactor --- api/external/cinder/messages.go | 3 ++ api/external/ironcore/messages.go | 3 ++ api/external/manila/messages.go | 3 ++ api/external/nova/messages.go | 6 ++++ api/external/pods/messages.go | 3 ++ .../filter_weigher_pipeline_controller.go | 2 +- .../scheduling/lib/filter_weigher_pipeline.go | 8 +++-- .../lib/filter_weigher_pipeline_request.go | 2 ++ .../filter_weigher_pipeline_request_test.go | 2 ++ .../lib/filter_weigher_pipeline_test.go | 6 ++-- internal/scheduling/lib/options_test.go | 34 ++++++++++++++++++ .../filter_weigher_pipeline_controller.go | 2 +- ...filter_weigher_pipeline_controller_test.go | 2 +- .../filter_weigher_pipeline_controller.go | 2 +- .../filter_weigher_pipeline_controller.go | 30 ++++------------ ...filter_weigher_pipeline_controller_test.go | 35 +++++++++---------- .../filter_weigher_pipeline_controller.go | 2 +- ...filter_weigher_pipeline_controller_test.go | 2 +- .../commitments/reservation_controller.go | 20 +++++------ .../failover/reservation_scheduling.go | 5 +-- .../reservations/scheduler_client.go | 7 ++-- 21 files changed, 109 insertions(+), 70 deletions(-) create mode 100644 internal/scheduling/lib/options_test.go diff --git a/api/external/cinder/messages.go b/api/external/cinder/messages.go index 260e93815..08fe6edee 100644 --- a/api/external/cinder/messages.go +++ b/api/external/cinder/messages.go @@ -30,8 +30,11 @@ type ExternalSchedulerRequest struct { Weights map[string]float64 `json:"weights"` // The name of the pipeline to execute. Pipeline string `json:"pipeline"` + // Options configure the pipeline behavior for this scheduling call. + Options lib.Options `json:"options,omitempty"` } +func (r ExternalSchedulerRequest) GetOptions() lib.Options { return r.Options } func (r ExternalSchedulerRequest) GetHosts() []string { hosts := make([]string, len(r.Hosts)) for i, host := range r.Hosts { diff --git a/api/external/ironcore/messages.go b/api/external/ironcore/messages.go index ac517f61a..29da83b9b 100644 --- a/api/external/ironcore/messages.go +++ b/api/external/ironcore/messages.go @@ -13,8 +13,11 @@ import ( type MachinePipelineRequest struct { // The available machine pools. Pools []ironcorev1alpha1.MachinePool `json:"pools"` + // Options configure the pipeline behavior for this scheduling call. + Options lib.Options } +func (r MachinePipelineRequest) GetOptions() lib.Options { return r.Options } func (r MachinePipelineRequest) GetHosts() []string { hosts := make([]string, len(r.Pools)) for i, host := range r.Pools { diff --git a/api/external/manila/messages.go b/api/external/manila/messages.go index 5255a0d4f..013fa70fb 100644 --- a/api/external/manila/messages.go +++ b/api/external/manila/messages.go @@ -30,8 +30,11 @@ type ExternalSchedulerRequest struct { Weights map[string]float64 `json:"weights"` // The name of the pipeline to execute. Pipeline string `json:"pipeline"` + // Options configure the pipeline behavior for this scheduling call. + Options lib.Options `json:"options,omitempty"` } +func (r ExternalSchedulerRequest) GetOptions() lib.Options { return r.Options } func (r ExternalSchedulerRequest) GetHosts() []string { hosts := make([]string, len(r.Hosts)) for i, host := range r.Hosts { diff --git a/api/external/nova/messages.go b/api/external/nova/messages.go index e82568941..202f85cf1 100644 --- a/api/external/nova/messages.go +++ b/api/external/nova/messages.go @@ -37,8 +37,14 @@ type ExternalSchedulerRequest struct { // The name of the pipeline to execute. Pipeline string `json:"pipeline"` + + // Options configure the pipeline behavior for this scheduling call. + // Set by the caller (CR controller, failover controller, Nova). + // Nova does not set these; Cortex fills in config-derived defaults server-side. + Options lib.Options `json:"options,omitempty"` } +func (r ExternalSchedulerRequest) GetOptions() lib.Options { return r.Options } func (r ExternalSchedulerRequest) GetHosts() []string { hosts := make([]string, len(r.Hosts)) for i, host := range r.Hosts { diff --git a/api/external/pods/messages.go b/api/external/pods/messages.go index 3ec329b39..0a801ac22 100644 --- a/api/external/pods/messages.go +++ b/api/external/pods/messages.go @@ -15,8 +15,11 @@ type PodPipelineRequest struct { Nodes []corev1.Node `json:"nodes"` // The pod to be scheduled. Pod corev1.Pod `json:"pod"` + // Options configure the pipeline behavior for this scheduling call. + Options lib.Options } +func (r PodPipelineRequest) GetOptions() lib.Options { return r.Options } func (r PodPipelineRequest) GetHosts() []string { hosts := make([]string, len(r.Nodes)) for i, host := range r.Nodes { diff --git a/internal/scheduling/cinder/filter_weigher_pipeline_controller.go b/internal/scheduling/cinder/filter_weigher_pipeline_controller.go index 5a4081784..52ec37306 100644 --- a/internal/scheduling/cinder/filter_weigher_pipeline_controller.go +++ b/internal/scheduling/cinder/filter_weigher_pipeline_controller.go @@ -121,7 +121,7 @@ func (c *FilterWeigherPipelineController) process(ctx context.Context, decision return err } - result, err := pipeline.Run(request, lib.Options{}) + result, err := pipeline.Run(request) if err != nil { log.Error(err, "failed to run pipeline") return err diff --git a/internal/scheduling/lib/filter_weigher_pipeline.go b/internal/scheduling/lib/filter_weigher_pipeline.go index a67fa9b96..7930d1434 100644 --- a/internal/scheduling/lib/filter_weigher_pipeline.go +++ b/internal/scheduling/lib/filter_weigher_pipeline.go @@ -18,8 +18,9 @@ import ( ) type FilterWeigherPipeline[RequestType FilterWeigherPipelineRequest] interface { - // Run the scheduling pipeline with the given request and call-time options. - Run(request RequestType, opts Options) (v1alpha1.DecisionResult, error) + // Run the scheduling pipeline with the given request. + // Call-time options are read from request.GetOptions(). + Run(request RequestType) (v1alpha1.DecisionResult, error) } // Pipeline of scheduler steps. @@ -264,7 +265,8 @@ func (s *filterWeigherPipeline[RequestType]) sortHostsByWeights(weights map[stri } // Evaluate the pipeline and return a list of hosts in order of preference. -func (p *filterWeigherPipeline[RequestType]) Run(request RequestType, opts Options) (v1alpha1.DecisionResult, error) { +func (p *filterWeigherPipeline[RequestType]) Run(request RequestType) (v1alpha1.DecisionResult, error) { + opts := request.GetOptions() if err := opts.Validate(); err != nil { return v1alpha1.DecisionResult{}, err } diff --git a/internal/scheduling/lib/filter_weigher_pipeline_request.go b/internal/scheduling/lib/filter_weigher_pipeline_request.go index 26688c358..69a9522e5 100644 --- a/internal/scheduling/lib/filter_weigher_pipeline_request.go +++ b/internal/scheduling/lib/filter_weigher_pipeline_request.go @@ -21,4 +21,6 @@ type FilterWeigherPipelineRequest interface { // Get logging args to be used in the step's trace log. // Usually, this will be the request context including the request ID. GetTraceLogArgs() []slog.Attr + // Get the call-time options for this pipeline run. + GetOptions() Options } diff --git a/internal/scheduling/lib/filter_weigher_pipeline_request_test.go b/internal/scheduling/lib/filter_weigher_pipeline_request_test.go index 87ab0d786..765752a45 100644 --- a/internal/scheduling/lib/filter_weigher_pipeline_request_test.go +++ b/internal/scheduling/lib/filter_weigher_pipeline_request_test.go @@ -11,6 +11,7 @@ type mockFilterWeigherPipelineRequest struct { Hosts []string Weights map[string]float64 Pipeline string + Options Options } func (m mockFilterWeigherPipelineRequest) GetWeightKeys() []string { return m.WeightKeys } @@ -18,6 +19,7 @@ func (m mockFilterWeigherPipelineRequest) GetTraceLogArgs() []slog.Attr { retu func (m mockFilterWeigherPipelineRequest) GetHosts() []string { return m.Hosts } func (m mockFilterWeigherPipelineRequest) GetWeights() map[string]float64 { return m.Weights } func (m mockFilterWeigherPipelineRequest) GetPipeline() string { return m.Pipeline } +func (m mockFilterWeigherPipelineRequest) GetOptions() Options { return m.Options } func (m mockFilterWeigherPipelineRequest) Filter(hosts map[string]float64) FilterWeigherPipelineRequest { filteredHosts := make([]string, 0, len(hosts)) diff --git a/internal/scheduling/lib/filter_weigher_pipeline_test.go b/internal/scheduling/lib/filter_weigher_pipeline_test.go index 9b89a2592..d22349a81 100644 --- a/internal/scheduling/lib/filter_weigher_pipeline_test.go +++ b/internal/scheduling/lib/filter_weigher_pipeline_test.go @@ -72,7 +72,7 @@ func TestPipeline_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result, err := pipeline.Run(tt.request, Options{}) + result, err := pipeline.Run(tt.request) if err != nil { t.Fatalf("expected no error, got %v", err) } @@ -400,7 +400,9 @@ func TestPipeline_MaxCandidates(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result, err := pipeline.Run(request, Options{MaxCandidates: tt.maxCandidates}) + req := request + req.Options = Options{MaxCandidates: tt.maxCandidates} + result, err := pipeline.Run(req) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/lib/options_test.go b/internal/scheduling/lib/options_test.go new file mode 100644 index 000000000..6eb366b0c --- /dev/null +++ b/internal/scheduling/lib/options_test.go @@ -0,0 +1,34 @@ +// Copyright SAP SE +// SPDX-License-Identifier: Apache-2.0 + +package lib + +import "testing" + +func TestOptions_Validate(t *testing.T) { + tests := []struct { + name string + opts Options + wantErr bool + }{ + {"zero value is valid", Options{}, false}, + {"write run with history", Options{RecordHistory: true}, false}, + {"write run with inflight", Options{CreateInflight: true}, false}, + {"read-only run, no side effects", Options{ReadOnly: true}, false}, + {"ReadOnly + RecordHistory is invalid", Options{ReadOnly: true, RecordHistory: true}, true}, + {"ReadOnly + CreateInflight is invalid", Options{ReadOnly: true, CreateInflight: true}, true}, + {"ReadOnly + both invalid", Options{ReadOnly: true, RecordHistory: true, CreateInflight: true}, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.opts.Validate() + if tt.wantErr && err == nil { + t.Error("expected error, got nil") + } + if !tt.wantErr && err != nil { + t.Errorf("expected no error, got %v", err) + } + }) + } +} diff --git a/internal/scheduling/machines/filter_weigher_pipeline_controller.go b/internal/scheduling/machines/filter_weigher_pipeline_controller.go index 93e0f5e41..35d51708a 100644 --- a/internal/scheduling/machines/filter_weigher_pipeline_controller.go +++ b/internal/scheduling/machines/filter_weigher_pipeline_controller.go @@ -144,7 +144,7 @@ func (c *FilterWeigherPipelineController) process(ctx context.Context, decision // Execute the scheduling pipeline. request := ironcore.MachinePipelineRequest{Pools: pools.Items} - result, err := pipeline.Run(request, lib.Options{}) + result, err := pipeline.Run(request) if err != nil { log.V(1).Error(err, "failed to run scheduler pipeline") return errors.New("failed to run scheduler pipeline") diff --git a/internal/scheduling/machines/filter_weigher_pipeline_controller_test.go b/internal/scheduling/machines/filter_weigher_pipeline_controller_test.go index 28fe49ed7..bc2e0722a 100644 --- a/internal/scheduling/machines/filter_weigher_pipeline_controller_test.go +++ b/internal/scheduling/machines/filter_weigher_pipeline_controller_test.go @@ -516,7 +516,7 @@ func createMockPipeline() lib.FilterWeigherPipeline[ironcore.MachinePipelineRequ type mockMachinePipeline struct{} -func (m *mockMachinePipeline) Run(request ironcore.MachinePipelineRequest, opts lib.Options) (v1alpha1.DecisionResult, error) { +func (m *mockMachinePipeline) Run(request ironcore.MachinePipelineRequest) (v1alpha1.DecisionResult, error) { if len(request.Pools) == 0 { return v1alpha1.DecisionResult{}, nil } diff --git a/internal/scheduling/manila/filter_weigher_pipeline_controller.go b/internal/scheduling/manila/filter_weigher_pipeline_controller.go index 18ed212f5..128b7d719 100644 --- a/internal/scheduling/manila/filter_weigher_pipeline_controller.go +++ b/internal/scheduling/manila/filter_weigher_pipeline_controller.go @@ -121,7 +121,7 @@ func (c *FilterWeigherPipelineController) process(ctx context.Context, decision return err } - result, err := pipeline.Run(request, lib.Options{}) + result, err := pipeline.Run(request) if err != nil { log.Error(err, "failed to run pipeline") return err diff --git a/internal/scheduling/nova/filter_weigher_pipeline_controller.go b/internal/scheduling/nova/filter_weigher_pipeline_controller.go index 53d4555f1..703f6d23f 100644 --- a/internal/scheduling/nova/filter_weigher_pipeline_controller.go +++ b/internal/scheduling/nova/filter_weigher_pipeline_controller.go @@ -174,9 +174,12 @@ func (c *FilterWeigherPipelineController) process(ctx context.Context, decision log.Info("gathered all placement candidates", "numHosts", len(request.Hosts)) } - opts := c.buildOptions(request, pipelineConf) - result, err := pipeline.Run(request, opts) - if opts.RecordHistory { + // Fill RecordHistory from config if the caller didn't set it. + if !request.Options.RecordHistory { + request.Options.RecordHistory = pipelineConf.Spec.CreateHistory + } + result, err := pipeline.Run(request) + if request.Options.RecordHistory { c.upsertHistory(ctx, decision, err) } if err != nil { @@ -204,26 +207,7 @@ func (c *FilterWeigherPipelineController) peekReadOnly(decision *v1alpha1.Decisi if err := json.Unmarshal(decision.Spec.NovaRaw.Raw, &request); err != nil { return false } - pipelineConf, ok := c.PipelineConfigs[decision.Spec.PipelineRef.Name] - if !ok { - return false - } - return c.buildOptions(request, pipelineConf).ReadOnly -} - -// The base controller will delegate the pipeline creation down to this method. -func (c *FilterWeigherPipelineController) buildOptions(request api.ExternalSchedulerRequest, pipelineConf v1alpha1.Pipeline) lib.Options { - opts := lib.Options{ - RecordHistory: pipelineConf.Spec.CreateHistory, - } - intent, err := request.GetIntent() - if err == nil { - switch intent { - case api.ReserveForCommittedResourceIntent, api.ReserveForFailoverIntent: - opts.LockReservations = true - } - } - return opts + return request.Options.ReadOnly } func (c *FilterWeigherPipelineController) InitPipeline( diff --git a/internal/scheduling/nova/filter_weigher_pipeline_controller_test.go b/internal/scheduling/nova/filter_weigher_pipeline_controller_test.go index 781e259bd..031527b84 100644 --- a/internal/scheduling/nova/filter_weigher_pipeline_controller_test.go +++ b/internal/scheduling/nova/filter_weigher_pipeline_controller_test.go @@ -930,22 +930,19 @@ func TestFilterWeigherPipelineController_IgnorePreselection(t *testing.T) { var errGathererFailed = errors.New("gatherer failed") func TestFilterWeigherPipelineController_PeekReadOnly(t *testing.T) { - validRequest := api.ExternalSchedulerRequest{ - Spec: api.NovaObject[api.NovaSpec]{ - Data: api.NovaSpec{NumInstances: 1}, - }, - } - validRaw, err := json.Marshal(validRequest) - if err != nil { - t.Fatalf("failed to marshal test request: %v", err) + makeRaw := func(readOnly bool) []byte { + r := api.ExternalSchedulerRequest{ + Spec: api.NovaObject[api.NovaSpec]{Data: api.NovaSpec{NumInstances: 1}}, + Options: lib.Options{ReadOnly: readOnly}, + } + raw, err := json.Marshal(r) + if err != nil { + panic(err) + } + return raw } c := &FilterWeigherPipelineController{} - c.PipelineConfigs = map[string]v1alpha1.Pipeline{ - "test-pipeline": { - Spec: v1alpha1.PipelineSpec{CreateHistory: false}, - }, - } tests := []struct { name string @@ -972,24 +969,24 @@ func TestFilterWeigherPipelineController_PeekReadOnly(t *testing.T) { want: false, }, { - name: "unknown pipeline defaults to exclusive lock", + name: "ReadOnly=false uses exclusive lock", decision: &v1alpha1.Decision{ Spec: v1alpha1.DecisionSpec{ - PipelineRef: corev1.ObjectReference{Name: "unknown-pipeline"}, - NovaRaw: &runtime.RawExtension{Raw: validRaw}, + PipelineRef: corev1.ObjectReference{Name: "test-pipeline"}, + NovaRaw: &runtime.RawExtension{Raw: makeRaw(false)}, }, }, want: false, }, { - name: "valid request with non-ReadOnly intent uses exclusive lock", + name: "ReadOnly=true uses read lock", decision: &v1alpha1.Decision{ Spec: v1alpha1.DecisionSpec{ PipelineRef: corev1.ObjectReference{Name: "test-pipeline"}, - NovaRaw: &runtime.RawExtension{Raw: validRaw}, + NovaRaw: &runtime.RawExtension{Raw: makeRaw(true)}, }, }, - want: false, + want: true, }, } diff --git a/internal/scheduling/pods/filter_weigher_pipeline_controller.go b/internal/scheduling/pods/filter_weigher_pipeline_controller.go index 7f76adede..0ceee6485 100644 --- a/internal/scheduling/pods/filter_weigher_pipeline_controller.go +++ b/internal/scheduling/pods/filter_weigher_pipeline_controller.go @@ -158,7 +158,7 @@ func (c *FilterWeigherPipelineController) process(ctx context.Context, decision // Execute the scheduling pipeline. request := pods.PodPipelineRequest{Nodes: nodes.Items, Pod: *pod} - result, err := pipeline.Run(request, lib.Options{}) + result, err := pipeline.Run(request) if err != nil { log.V(1).Error(err, "failed to run scheduler pipeline") return errors.New("failed to run scheduler pipeline") diff --git a/internal/scheduling/pods/filter_weigher_pipeline_controller_test.go b/internal/scheduling/pods/filter_weigher_pipeline_controller_test.go index f1e429da3..143ed9f83 100644 --- a/internal/scheduling/pods/filter_weigher_pipeline_controller_test.go +++ b/internal/scheduling/pods/filter_weigher_pipeline_controller_test.go @@ -492,7 +492,7 @@ func createMockPodPipeline() lib.FilterWeigherPipeline[pods.PodPipelineRequest] type mockPodPipeline struct{} -func (m *mockPodPipeline) Run(request pods.PodPipelineRequest, opts lib.Options) (v1alpha1.DecisionResult, error) { +func (m *mockPodPipeline) Run(request pods.PodPipelineRequest) (v1alpha1.DecisionResult, error) { if len(request.Nodes) == 0 { return v1alpha1.DecisionResult{}, nil } diff --git a/internal/scheduling/reservations/commitments/reservation_controller.go b/internal/scheduling/reservations/commitments/reservation_controller.go index 09cca220d..5d9ab7483 100644 --- a/internal/scheduling/reservations/commitments/reservation_controller.go +++ b/internal/scheduling/reservations/commitments/reservation_controller.go @@ -286,18 +286,18 @@ func (r *CommitmentReservationController) Reconcile(ctx context.Context, req ctr SchedulerHints: map[string]any{ "_nova_check_type": string(schedulerdelegationapi.ReserveForCommittedResourceIntent), }, - Options: lib.Options{ - ReadOnly: false, // mutates state (reservation placement) - LockReservations: true, // don't unlock CR reservations; finding a slot, not placing a VM - AssumeEmptyHosts: false, - IgnoredReservationTypes: nil, - MaxCandidates: 1, - RecordHistory: false, - CreateInflight: false, - }, + } + scheduleOpts := lib.Options{ + ReadOnly: false, // mutates state (reservation placement) + LockReservations: true, // don't unlock CR reservations; finding a slot, not placing a VM + AssumeEmptyHosts: false, + IgnoredReservationTypes: nil, + MaxCandidates: 1, + RecordHistory: false, + CreateInflight: false, // not a VM placement; no pessimistic blocking needed } - scheduleResp, err := r.SchedulerClient.ScheduleReservation(ctx, scheduleReq) + scheduleResp, err := r.SchedulerClient.ScheduleReservation(ctx, scheduleReq, scheduleOpts) if err != nil { logger.Error(err, "failed to schedule reservation") return ctrl.Result{}, err diff --git a/internal/scheduling/reservations/failover/reservation_scheduling.go b/internal/scheduling/reservations/failover/reservation_scheduling.go index f482f3393..5f8c93767 100644 --- a/internal/scheduling/reservations/failover/reservation_scheduling.go +++ b/internal/scheduling/reservations/failover/reservation_scheduling.go @@ -11,6 +11,7 @@ import ( api "github.com/cobaltcore-dev/cortex/api/external/nova" "github.com/cobaltcore-dev/cortex/api/v1alpha1" + "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations" ) @@ -91,7 +92,7 @@ func (c *FailoverReservationController) queryHypervisorsFromScheduler(ctx contex "eligibleHypervisors", len(eligibleHypervisors), "ignoreHypervisors", ignoreHypervisors) - scheduleResp, err := c.SchedulerClient.ScheduleReservation(ctx, scheduleReq) + scheduleResp, err := c.SchedulerClient.ScheduleReservation(ctx, scheduleReq, lib.Options{LockReservations: true}) if err != nil { logger.Error(err, "failed to schedule failover reservation", "vmUUID", vm.UUID, "pipeline", pipeline) return nil, fmt.Errorf("failed to schedule failover reservation: %w", err) @@ -222,7 +223,7 @@ func (c *FailoverReservationController) validateVMViaSchedulerEvacuation( "vmCurrentHost", vm.CurrentHypervisor, "pipeline", PipelineAcknowledgeFailoverReservation) - resp, err := c.SchedulerClient.ScheduleReservation(ctx, scheduleReq) + resp, err := c.SchedulerClient.ScheduleReservation(ctx, scheduleReq, lib.Options{ReadOnly: true}) if err != nil { logger.Error(err, "failed to validate VM for reservation host", "vmUUID", vm.UUID, "reservationHost", reservationHost) return false, fmt.Errorf("failed to validate VM for reservation host: %w", err) diff --git a/internal/scheduling/reservations/scheduler_client.go b/internal/scheduling/reservations/scheduler_client.go index e250d170d..f10ad21d0 100644 --- a/internal/scheduling/reservations/scheduler_client.go +++ b/internal/scheduling/reservations/scheduler_client.go @@ -79,10 +79,6 @@ type ScheduleReservationRequest struct { // SchedulerHints are hints passed to the scheduler pipeline. // Used to set _nova_check_type for evacuation intent detection. SchedulerHints map[string]any - // Options configures the pipeline behavior for this scheduling call. - // These are derived from intent in buildOptions for the current HTTP path; - // will be passed directly once the scheduler client is a direct Go call. - Options lib.Options } // ScheduleReservationResponse contains the result of scheduling a reservation. @@ -94,7 +90,7 @@ type ScheduleReservationResponse struct { // ScheduleReservation calls the external scheduler API to find a host for a reservation. // The context should contain GlobalRequestID and RequestID for logging (use WithGlobalRequestID/WithRequestID). -func (c *SchedulerClient) ScheduleReservation(ctx context.Context, req ScheduleReservationRequest) (*ScheduleReservationResponse, error) { +func (c *SchedulerClient) ScheduleReservation(ctx context.Context, req ScheduleReservationRequest, opts lib.Options) (*ScheduleReservationResponse, error) { logger := loggerFromContext(ctx) // Build weights map (all zero for reservations) @@ -120,6 +116,7 @@ func (c *SchedulerClient) ScheduleReservation(ctx context.Context, req ScheduleR Pipeline: req.Pipeline, Hosts: req.EligibleHosts, Weights: weights, + Options: opts, Context: api.NovaRequestContext{ RequestID: RequestIDFromContext(ctx), GlobalRequestID: globalReqID, From c2d0fc255402989f60b6d78e112de2792355eeb6 Mon Sep 17 00:00:00 2001 From: mblos Date: Tue, 5 May 2026 17:01:53 +0200 Subject: [PATCH 6/8] . --- api/external/ironcore/messages.go | 2 +- api/external/pods/messages.go | 2 +- internal/scheduling/lib/options.go | 8 +++++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/api/external/ironcore/messages.go b/api/external/ironcore/messages.go index 29da83b9b..05797e15a 100644 --- a/api/external/ironcore/messages.go +++ b/api/external/ironcore/messages.go @@ -14,7 +14,7 @@ type MachinePipelineRequest struct { // The available machine pools. Pools []ironcorev1alpha1.MachinePool `json:"pools"` // Options configure the pipeline behavior for this scheduling call. - Options lib.Options + Options lib.Options `json:"options,omitempty"` } func (r MachinePipelineRequest) GetOptions() lib.Options { return r.Options } diff --git a/api/external/pods/messages.go b/api/external/pods/messages.go index 0a801ac22..0b5466415 100644 --- a/api/external/pods/messages.go +++ b/api/external/pods/messages.go @@ -16,7 +16,7 @@ type PodPipelineRequest struct { // The pod to be scheduled. Pod corev1.Pod `json:"pod"` // Options configure the pipeline behavior for this scheduling call. - Options lib.Options + Options lib.Options `json:"options,omitempty"` } func (r PodPipelineRequest) GetOptions() lib.Options { return r.Options } diff --git a/internal/scheduling/lib/options.go b/internal/scheduling/lib/options.go index c516957f0..44445cf6d 100644 --- a/internal/scheduling/lib/options.go +++ b/internal/scheduling/lib/options.go @@ -16,7 +16,9 @@ import ( // Consumed by steps: ReadOnly, LockReservations, AssumeEmptyHosts, IgnoredReservationTypes. // Consumed by the controller after pipeline.Run(): RecordHistory, CreateInflight. type Options struct { - // ReadOnly means the pipeline could run without using the mutex, i.e. concurrent runs are ok. + // ReadOnly means the pipeline run does not modify shared scheduling state (reservations, + // history, inflight records). Concurrent read-only runs are safe under a shared read lock. + // Note: the controller may still write the Decision status after Run() regardless of this flag. ReadOnly bool // LockReservations prevents reservation unlocking, e.g. in the capacity filter. // Set when finding hosts for new reservations (failover, CR) to see true available capacity. @@ -38,10 +40,10 @@ type Options struct { // Validate checks for mutually exclusive or inconsistent option combinations. func (o Options) Validate() error { if o.ReadOnly && o.RecordHistory { - return errors.New("ReadOnly and RecordHistory are mutually exclusive: read-only runs must not mutate state") + return errors.New("ReadOnly and RecordHistory are mutually exclusive: read-only runs must not write scheduling history") } if o.ReadOnly && o.CreateInflight { - return errors.New("ReadOnly and CreateInflight are mutually exclusive: read-only runs must not mutate state") + return errors.New("ReadOnly and CreateInflight are mutually exclusive: read-only runs must not create inflight reservations") } return nil } From 4de31f9c4ce717aef0739cbc6567723a9d604285 Mon Sep 17 00:00:00 2001 From: mblos Date: Wed, 6 May 2026 11:31:28 +0200 Subject: [PATCH 7/8] refactor --- internal/scheduling/lib/filter_monitor.go | 4 ++-- internal/scheduling/lib/filter_monitor_test.go | 2 +- internal/scheduling/lib/filter_test.go | 2 +- internal/scheduling/lib/filter_validation.go | 4 ++-- .../scheduling/lib/filter_validation_test.go | 2 +- .../scheduling/lib/filter_weigher_pipeline.go | 11 +++++------ .../lib/filter_weigher_pipeline_step.go | 4 ++-- .../filter_weigher_pipeline_step_monitor.go | 3 +-- ...ilter_weigher_pipeline_step_monitor_test.go | 2 +- .../lib/filter_weigher_pipeline_test.go | 2 +- internal/scheduling/lib/weigher_monitor.go | 4 ++-- .../scheduling/lib/weigher_monitor_test.go | 2 +- internal/scheduling/lib/weigher_test.go | 2 +- internal/scheduling/lib/weigher_validation.go | 4 ++-- .../scheduling/lib/weigher_validation_test.go | 4 ++-- .../machines/plugins/filters/filter_noop.go | 2 +- .../plugins/filters/filter_noop_test.go | 3 +-- .../weighers/netapp_cpu_usage_balancing.go | 2 +- .../netapp_cpu_usage_balancing_test.go | 3 +-- .../filters/filter_aggregate_metadata.go | 2 +- .../filters/filter_aggregate_metadata_test.go | 5 ++--- .../plugins/filters/filter_allowed_projects.go | 2 +- .../filters/filter_allowed_projects_test.go | 3 +-- .../plugins/filters/filter_capabilities.go | 2 +- .../filters/filter_capabilities_test.go | 5 ++--- .../nova/plugins/filters/filter_correct_az.go | 2 +- .../plugins/filters/filter_correct_az_test.go | 3 +-- .../plugins/filters/filter_exclude_hosts.go | 1 - .../filters/filter_exclude_hosts_test.go | 2 +- .../filters/filter_external_customer.go | 2 +- .../filters/filter_external_customer_test.go | 3 +-- .../plugins/filters/filter_has_accelerators.go | 2 +- .../filters/filter_has_accelerators_test.go | 3 +-- .../filters/filter_has_enough_capacity.go | 3 ++- .../filters/filter_has_enough_capacity_test.go | 18 ++++++++++-------- .../filters/filter_has_requested_traits.go | 2 +- .../filter_has_requested_traits_test.go | 3 +-- .../filters/filter_host_instructions.go | 2 +- .../filters/filter_host_instructions_test.go | 3 +-- .../filters/filter_instance_group_affinity.go | 1 - .../filter_instance_group_affinity_test.go | 3 +-- .../filter_instance_group_anti_affinity.go | 1 - ...filter_instance_group_anti_affinity_test.go | 3 +-- .../plugins/filters/filter_live_migratable.go | 1 - .../filters/filter_live_migratable_test.go | 6 +++--- .../filters/filter_requested_destination.go | 2 +- .../filter_requested_destination_test.go | 4 ++-- .../filters/filter_status_conditions.go | 2 +- .../filters/filter_status_conditions_test.go | 3 +-- .../nova/plugins/weighers/kvm_binpack.go | 2 +- .../nova/plugins/weighers/kvm_binpack_test.go | 3 +-- .../weighers/kvm_failover_evacuation.go | 2 +- .../weighers/kvm_failover_evacuation_test.go | 3 +-- .../kvm_failover_reservation_consolidation.go | 2 +- ..._failover_reservation_consolidation_test.go | 3 +-- .../kvm_instance_group_soft_affinity.go | 2 +- .../kvm_instance_group_soft_affinity_test.go | 3 +-- .../weighers/kvm_prefer_smaller_hosts.go | 2 +- .../weighers/kvm_prefer_smaller_hosts_test.go | 3 +-- .../vmware_anti_affinity_noisy_projects.go | 2 +- ...vmware_anti_affinity_noisy_projects_test.go | 3 +-- .../vmware_avoid_long_term_contended_hosts.go | 2 +- ...are_avoid_long_term_contended_hosts_test.go | 3 +-- .../vmware_avoid_short_term_contended_hosts.go | 2 +- ...re_avoid_short_term_contended_hosts_test.go | 3 +-- .../nova/plugins/weighers/vmware_binpack.go | 2 +- .../plugins/weighers/vmware_binpack_test.go | 3 +-- .../plugins/filters/filter_node_affinity.go | 2 +- .../filters/filter_node_affinity_test.go | 3 +-- .../plugins/filters/filter_node_available.go | 2 +- .../filters/filter_node_available_test.go | 3 +-- .../plugins/filters/filter_node_capacity.go | 2 +- .../filters/filter_node_capacity_test.go | 3 +-- .../pods/plugins/filters/filter_noop.go | 2 +- .../pods/plugins/filters/filter_noop_test.go | 3 +-- .../pods/plugins/filters/filter_taint.go | 2 +- .../pods/plugins/filters/filter_taint_test.go | 3 +-- .../pods/plugins/weighers/binpack.go | 2 +- .../pods/plugins/weighers/binpack_test.go | 3 +-- 79 files changed, 100 insertions(+), 131 deletions(-) diff --git a/internal/scheduling/lib/filter_monitor.go b/internal/scheduling/lib/filter_monitor.go index 97e9d661d..d0afd9282 100644 --- a/internal/scheduling/lib/filter_monitor.go +++ b/internal/scheduling/lib/filter_monitor.go @@ -43,6 +43,6 @@ func (fm *FilterMonitor[RequestType]) Validate(ctx context.Context, params v1alp } // Run the filter and observe its execution. -func (fm *FilterMonitor[RequestType]) Run(traceLog *slog.Logger, request RequestType, opts Options) (*FilterWeigherPipelineStepResult, error) { - return fm.monitor.RunWrapped(traceLog, request, opts, fm.filter) +func (fm *FilterMonitor[RequestType]) Run(traceLog *slog.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error) { + return fm.monitor.RunWrapped(traceLog, request, fm.filter) } diff --git a/internal/scheduling/lib/filter_monitor_test.go b/internal/scheduling/lib/filter_monitor_test.go index b27f811b1..f709d88aa 100644 --- a/internal/scheduling/lib/filter_monitor_test.go +++ b/internal/scheduling/lib/filter_monitor_test.go @@ -100,7 +100,7 @@ func TestFilterMonitor_Run(t *testing.T) { Weights: map[string]float64{"host1": 0.1, "host2": 0.2, "host3": 0.3}, } - result, err := fm.Run(slog.Default(), request, Options{}) + result, err := fm.Run(slog.Default(), request) if err != nil { t.Errorf("expected no error, got %v", err) } diff --git a/internal/scheduling/lib/filter_test.go b/internal/scheduling/lib/filter_test.go index 14fe73997..652211163 100644 --- a/internal/scheduling/lib/filter_test.go +++ b/internal/scheduling/lib/filter_test.go @@ -31,7 +31,7 @@ func (m *mockFilter[RequestType]) Validate(ctx context.Context, params v1alpha1. } return m.ValidateFunc(ctx, params) } -func (m *mockFilter[RequestType]) Run(traceLog *slog.Logger, request RequestType, opts Options) (*FilterWeigherPipelineStepResult, error) { +func (m *mockFilter[RequestType]) Run(traceLog *slog.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error) { if m.RunFunc == nil { return &FilterWeigherPipelineStepResult{}, nil } diff --git a/internal/scheduling/lib/filter_validation.go b/internal/scheduling/lib/filter_validation.go index 2054a9ad9..9ad43311d 100644 --- a/internal/scheduling/lib/filter_validation.go +++ b/internal/scheduling/lib/filter_validation.go @@ -35,8 +35,8 @@ func validateFilter[RequestType FilterWeigherPipelineRequest](filter Filter[Requ } // Run the filter and validate what happens. -func (s *FilterValidator[RequestType]) Run(traceLog *slog.Logger, request RequestType, opts Options) (*FilterWeigherPipelineStepResult, error) { - result, err := s.Filter.Run(traceLog, request, opts) +func (s *FilterValidator[RequestType]) Run(traceLog *slog.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error) { + result, err := s.Filter.Run(traceLog, request) if err != nil { return nil, err } diff --git a/internal/scheduling/lib/filter_validation_test.go b/internal/scheduling/lib/filter_validation_test.go index deb064dfc..dc35c2f6a 100644 --- a/internal/scheduling/lib/filter_validation_test.go +++ b/internal/scheduling/lib/filter_validation_test.go @@ -156,7 +156,7 @@ func TestFilterValidator_Run(t *testing.T) { } traceLog := slog.Default() - result, err := validator.Run(traceLog, request, Options{}) + result, err := validator.Run(traceLog, request) if tt.expectError && err == nil { t.Error("expected error but got nil") diff --git a/internal/scheduling/lib/filter_weigher_pipeline.go b/internal/scheduling/lib/filter_weigher_pipeline.go index 7930d1434..e2b9ce468 100644 --- a/internal/scheduling/lib/filter_weigher_pipeline.go +++ b/internal/scheduling/lib/filter_weigher_pipeline.go @@ -139,7 +139,6 @@ func InitNewFilterWeigherPipeline[RequestType FilterWeigherPipelineRequest]( func (p *filterWeigherPipeline[RequestType]) runFilters( log *slog.Logger, request RequestType, - opts Options, ) (filteredRequest RequestType, stepResults []v1alpha1.StepResult) { filteredRequest = request @@ -147,7 +146,7 @@ func (p *filterWeigherPipeline[RequestType]) runFilters( filter := p.filters[filterName] stepLog := log.With("filter", filterName) stepLog.Info("scheduler: running filter") - result, err := filter.Run(stepLog, filteredRequest, opts) + result, err := filter.Run(stepLog, filteredRequest) if errors.Is(err, ErrStepSkipped) { stepLog.Info("scheduler: filter skipped") continue @@ -172,7 +171,6 @@ func (p *filterWeigherPipeline[RequestType]) runFilters( func (p *filterWeigherPipeline[RequestType]) runWeighers( log *slog.Logger, filteredRequest RequestType, - opts Options, ) map[string]map[string]float64 { activationsByStep := map[string]map[string]float64{} @@ -184,7 +182,7 @@ func (p *filterWeigherPipeline[RequestType]) runWeighers( wg.Go(func() { stepLog := log.With("weigher", weigherName) stepLog.Info("scheduler: running weigher") - result, err := weigher.Run(stepLog, filteredRequest, opts) + result, err := weigher.Run(stepLog, filteredRequest) if errors.Is(err, ErrStepSkipped) { stepLog.Info("scheduler: weigher skipped") return @@ -286,7 +284,7 @@ func (p *filterWeigherPipeline[RequestType]) Run(request RequestType) (v1alpha1. // Run filters first to reduce the number of hosts. // Any weights assigned to filtered out hosts are ignored. - filteredRequest, filterStepResults := p.runFilters(traceLog, request, opts) + filteredRequest, filterStepResults := p.runFilters(traceLog, request) traceLog.Info( "scheduler: finished filters", "remainingHosts", filteredRequest.GetHosts(), @@ -297,7 +295,7 @@ func (p *filterWeigherPipeline[RequestType]) Run(request RequestType) (v1alpha1. for _, host := range filteredRequest.GetHosts() { remainingWeights[host] = inWeights[host] } - stepWeights := p.runWeighers(traceLog, filteredRequest, opts) + stepWeights := p.runWeighers(traceLog, filteredRequest) outWeights := p.applyWeights(traceLog, stepWeights, remainingWeights) traceLog.Info("scheduler: output weights", "weights", outWeights) @@ -305,6 +303,7 @@ func (p *filterWeigherPipeline[RequestType]) Run(request RequestType) (v1alpha1. traceLog.Info("scheduler: sorted hosts", "hosts", hosts) if opts.MaxCandidates > 0 && len(hosts) > opts.MaxCandidates { + traceLog.Info("scheduler: trimming candidate list", "maxCandidates", opts.MaxCandidates, "before", len(hosts)) hosts = hosts[:opts.MaxCandidates] // Drop trimmed hosts from outWeights so AggregatedOutWeights stays consistent. for host := range outWeights { diff --git a/internal/scheduling/lib/filter_weigher_pipeline_step.go b/internal/scheduling/lib/filter_weigher_pipeline_step.go index cf6b3f207..54816519c 100644 --- a/internal/scheduling/lib/filter_weigher_pipeline_step.go +++ b/internal/scheduling/lib/filter_weigher_pipeline_step.go @@ -31,8 +31,8 @@ type FilterWeigherPipelineStep[RequestType FilterWeigherPipelineRequest] interfa // A traceLog is provided that contains the global request id and should // be used to log the step's execution. // - // opts carries per-call behavioral options set by the pipeline caller. - Run(traceLog *slog.Logger, request RequestType, opts Options) (*FilterWeigherPipelineStepResult, error) + // Per-call options are available via request.GetOptions(). + Run(traceLog *slog.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error) } // Common base for all steps that provides some functionality diff --git a/internal/scheduling/lib/filter_weigher_pipeline_step_monitor.go b/internal/scheduling/lib/filter_weigher_pipeline_step_monitor.go index e54651ec5..3e64fa6ee 100644 --- a/internal/scheduling/lib/filter_weigher_pipeline_step_monitor.go +++ b/internal/scheduling/lib/filter_weigher_pipeline_step_monitor.go @@ -65,7 +65,6 @@ func monitorStep[RequestType FilterWeigherPipelineRequest](stepName string, m Fi func (s *FilterWeigherPipelineStepMonitor[RequestType]) RunWrapped( traceLog *slog.Logger, request RequestType, - opts Options, step FilterWeigherPipelineStep[RequestType], ) (*FilterWeigherPipelineStepResult, error) { @@ -75,7 +74,7 @@ func (s *FilterWeigherPipelineStepMonitor[RequestType]) RunWrapped( } inWeights := request.GetWeights() - stepResult, err := step.Run(traceLog, request, opts) + stepResult, err := step.Run(traceLog, request) if err != nil { return nil, err } diff --git a/internal/scheduling/lib/filter_weigher_pipeline_step_monitor_test.go b/internal/scheduling/lib/filter_weigher_pipeline_step_monitor_test.go index af4bf74ec..7d7817abd 100644 --- a/internal/scheduling/lib/filter_weigher_pipeline_step_monitor_test.go +++ b/internal/scheduling/lib/filter_weigher_pipeline_step_monitor_test.go @@ -38,7 +38,7 @@ func TestStepMonitorRun(t *testing.T) { Hosts: []string{"host1", "host2", "host3"}, Weights: map[string]float64{"host1": 0.2, "host2": 0.1, "host3": 0.0}, } - if _, err := monitor.RunWrapped(slog.Default(), request, Options{}, step); err != nil { + if _, err := monitor.RunWrapped(slog.Default(), request, step); err != nil { t.Fatalf("Run() error = %v, want nil", err) } if len(removedHostsObserver.Observations) != 1 { diff --git a/internal/scheduling/lib/filter_weigher_pipeline_test.go b/internal/scheduling/lib/filter_weigher_pipeline_test.go index d22349a81..54251731c 100644 --- a/internal/scheduling/lib/filter_weigher_pipeline_test.go +++ b/internal/scheduling/lib/filter_weigher_pipeline_test.go @@ -221,7 +221,7 @@ func TestPipeline_RunFilters(t *testing.T) { Weights: map[string]float64{"host1": 0.0, "host2": 0.0, "host3": 0.0}, } - req, _ := p.runFilters(slog.Default(), request, Options{}) + req, _ := p.runFilters(slog.Default(), request) if len(req.Hosts) != 2 { t.Fatalf("expected 2 step results, got %d", len(req.Hosts)) } diff --git a/internal/scheduling/lib/weigher_monitor.go b/internal/scheduling/lib/weigher_monitor.go index 56a9ebb6e..df855d067 100644 --- a/internal/scheduling/lib/weigher_monitor.go +++ b/internal/scheduling/lib/weigher_monitor.go @@ -43,6 +43,6 @@ func (wm *WeigherMonitor[RequestType]) Validate(ctx context.Context, params v1al } // Run the weigher and observe its execution. -func (wm *WeigherMonitor[RequestType]) Run(traceLog *slog.Logger, request RequestType, opts Options) (*FilterWeigherPipelineStepResult, error) { - return wm.monitor.RunWrapped(traceLog, request, opts, wm.weigher) +func (wm *WeigherMonitor[RequestType]) Run(traceLog *slog.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error) { + return wm.monitor.RunWrapped(traceLog, request, wm.weigher) } diff --git a/internal/scheduling/lib/weigher_monitor_test.go b/internal/scheduling/lib/weigher_monitor_test.go index c84435234..6f8f906e3 100644 --- a/internal/scheduling/lib/weigher_monitor_test.go +++ b/internal/scheduling/lib/weigher_monitor_test.go @@ -100,7 +100,7 @@ func TestWeigherMonitor_Run(t *testing.T) { Weights: map[string]float64{"host1": 0.1, "host2": 0.2, "host3": 0.3}, } - result, err := wm.Run(slog.Default(), request, Options{}) + result, err := wm.Run(slog.Default(), request) if err != nil { t.Errorf("expected no error, got %v", err) } diff --git a/internal/scheduling/lib/weigher_test.go b/internal/scheduling/lib/weigher_test.go index 488704ef4..4660207c4 100644 --- a/internal/scheduling/lib/weigher_test.go +++ b/internal/scheduling/lib/weigher_test.go @@ -34,7 +34,7 @@ func (m *mockWeigher[RequestType]) Validate(ctx context.Context, params v1alpha1 } return m.ValidateFunc(ctx, params) } -func (m *mockWeigher[RequestType]) Run(traceLog *slog.Logger, request RequestType, opts Options) (*FilterWeigherPipelineStepResult, error) { +func (m *mockWeigher[RequestType]) Run(traceLog *slog.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error) { if m.RunFunc == nil { return &FilterWeigherPipelineStepResult{}, nil } diff --git a/internal/scheduling/lib/weigher_validation.go b/internal/scheduling/lib/weigher_validation.go index bb4c6b823..c454d171e 100644 --- a/internal/scheduling/lib/weigher_validation.go +++ b/internal/scheduling/lib/weigher_validation.go @@ -35,8 +35,8 @@ func validateWeigher[RequestType FilterWeigherPipelineRequest](weigher Weigher[R } // Run the weigher and validate what happens. -func (s *WeigherValidator[RequestType]) Run(traceLog *slog.Logger, request RequestType, opts Options) (*FilterWeigherPipelineStepResult, error) { - result, err := s.Weigher.Run(traceLog, request, opts) +func (s *WeigherValidator[RequestType]) Run(traceLog *slog.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error) { + result, err := s.Weigher.Run(traceLog, request) if err != nil { return nil, err } diff --git a/internal/scheduling/lib/weigher_validation_test.go b/internal/scheduling/lib/weigher_validation_test.go index af7efb163..852448a88 100644 --- a/internal/scheduling/lib/weigher_validation_test.go +++ b/internal/scheduling/lib/weigher_validation_test.go @@ -96,7 +96,7 @@ func TestWeigherValidator_Run_ValidHosts(t *testing.T) { Weigher: mockStep, } - result, err := validator.Run(slog.Default(), request, Options{}) + result, err := validator.Run(slog.Default(), request) if err != nil { t.Errorf("Run() error = %v, want nil", err) } @@ -130,7 +130,7 @@ func TestWeigherValidator_Run_HostNumberMismatch(t *testing.T) { Weigher: mockStep, } - result, err := validator.Run(slog.Default(), request, Options{}) + result, err := validator.Run(slog.Default(), request) if err == nil { t.Errorf("Run() error = nil, want error") } diff --git a/internal/scheduling/machines/plugins/filters/filter_noop.go b/internal/scheduling/machines/plugins/filters/filter_noop.go index 56fb55dfe..da901e5c0 100644 --- a/internal/scheduling/machines/plugins/filters/filter_noop.go +++ b/internal/scheduling/machines/plugins/filters/filter_noop.go @@ -31,7 +31,7 @@ func (f *NoopFilter) Validate(ctx context.Context, params v1alpha1.Parameters) e // not in the map are considered as filtered out. // Provide a traceLog that contains the global request id and should // be used to log the step's execution. -func (NoopFilter) Run(traceLog *slog.Logger, request ironcore.MachinePipelineRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { +func (NoopFilter) Run(traceLog *slog.Logger, request ironcore.MachinePipelineRequest) (*lib.FilterWeigherPipelineStepResult, error) { activations := make(map[string]float64, len(request.Pools)) stats := make(map[string]lib.FilterWeigherPipelineStepStatistics) // Usually you would do some filtering here, or adjust the weights. diff --git a/internal/scheduling/machines/plugins/filters/filter_noop_test.go b/internal/scheduling/machines/plugins/filters/filter_noop_test.go index 06d80c771..2fa369a4f 100644 --- a/internal/scheduling/machines/plugins/filters/filter_noop_test.go +++ b/internal/scheduling/machines/plugins/filters/filter_noop_test.go @@ -4,7 +4,6 @@ package filters import ( - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -65,7 +64,7 @@ func TestNoopFilter_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { filter := &NoopFilter{} - result, err := filter.Run(slog.Default(), tt.request, lib.Options{}) + result, err := filter.Run(slog.Default(), tt.request) if err != nil { t.Errorf("expected Run() to succeed, got error: %v", err) diff --git a/internal/scheduling/manila/plugins/weighers/netapp_cpu_usage_balancing.go b/internal/scheduling/manila/plugins/weighers/netapp_cpu_usage_balancing.go index 01d31a55a..ce3e30ebe 100644 --- a/internal/scheduling/manila/plugins/weighers/netapp_cpu_usage_balancing.go +++ b/internal/scheduling/manila/plugins/weighers/netapp_cpu_usage_balancing.go @@ -61,7 +61,7 @@ func (s *NetappCPUUsageBalancingStep) Init(ctx context.Context, client client.Cl } // Downvote hosts that are highly contended. -func (s *NetappCPUUsageBalancingStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *NetappCPUUsageBalancingStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) result.Statistics["avg cpu contention"] = s.PrepareStats(request, "%") result.Statistics["max cpu contention"] = s.PrepareStats(request, "%") diff --git a/internal/scheduling/manila/plugins/weighers/netapp_cpu_usage_balancing_test.go b/internal/scheduling/manila/plugins/weighers/netapp_cpu_usage_balancing_test.go index eeb0cdea2..f3e9c66ea 100644 --- a/internal/scheduling/manila/plugins/weighers/netapp_cpu_usage_balancing_test.go +++ b/internal/scheduling/manila/plugins/weighers/netapp_cpu_usage_balancing_test.go @@ -4,7 +4,6 @@ package weighers import ( - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -165,7 +164,7 @@ func TestNetappCPUUsageBalancingStep_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result, err := step.Run(slog.Default(), tt.request, lib.Options{}) + result, err := step.Run(slog.Default(), tt.request) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/filters/filter_aggregate_metadata.go b/internal/scheduling/nova/plugins/filters/filter_aggregate_metadata.go index 82a50ce1c..157a80521 100644 --- a/internal/scheduling/nova/plugins/filters/filter_aggregate_metadata.go +++ b/internal/scheduling/nova/plugins/filters/filter_aggregate_metadata.go @@ -19,7 +19,7 @@ type FilterAggregateMetadata struct { // Restrict hosts to specific projects if they are in an aggregate that has // the "filter_tenant_id" metadata key set. -func (s *FilterAggregateMetadata) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *FilterAggregateMetadata) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) hvs := &hv1.HypervisorList{} diff --git a/internal/scheduling/nova/plugins/filters/filter_aggregate_metadata_test.go b/internal/scheduling/nova/plugins/filters/filter_aggregate_metadata_test.go index 45814d50f..d1ff9cd2d 100644 --- a/internal/scheduling/nova/plugins/filters/filter_aggregate_metadata_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_aggregate_metadata_test.go @@ -5,7 +5,6 @@ package filters import ( "context" - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -337,7 +336,7 @@ func TestFilterAggregateMetadata_Run(t *testing.T) { step := &FilterAggregateMetadata{} step.Client = fakeClient - result, err := step.Run(slog.Default(), tt.request, lib.Options{}) + result, err := step.Run(slog.Default(), tt.request) if err != nil { t.Fatalf("expected no error, got %v", err) } @@ -389,7 +388,7 @@ func TestFilterAggregateMetadata_Run_ClientError(t *testing.T) { step := &FilterAggregateMetadata{} step.Client = fakeClient - _, err := step.Run(slog.Default(), request, lib.Options{}) + _, err := step.Run(slog.Default(), request) if err == nil { t.Errorf("expected error when client fails, got none") } diff --git a/internal/scheduling/nova/plugins/filters/filter_allowed_projects.go b/internal/scheduling/nova/plugins/filters/filter_allowed_projects.go index 21d6c6dd8..a0a486f3d 100644 --- a/internal/scheduling/nova/plugins/filters/filter_allowed_projects.go +++ b/internal/scheduling/nova/plugins/filters/filter_allowed_projects.go @@ -19,7 +19,7 @@ type FilterAllowedProjectsStep struct { // Lock certain hosts for certain projects, based on the hypervisor spec. // Note that hosts without specified projects are still accessible. -func (s *FilterAllowedProjectsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *FilterAllowedProjectsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) if request.Spec.Data.ProjectID == "" { traceLog.Info("no project ID in request, skipping filter") diff --git a/internal/scheduling/nova/plugins/filters/filter_allowed_projects_test.go b/internal/scheduling/nova/plugins/filters/filter_allowed_projects_test.go index 9dc95da59..070160e2e 100644 --- a/internal/scheduling/nova/plugins/filters/filter_allowed_projects_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_allowed_projects_test.go @@ -4,7 +4,6 @@ package filters import ( - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -296,7 +295,7 @@ func TestFilterAllowedProjectsStep_Run(t *testing.T) { WithScheme(scheme). WithObjects(hvs...). Build() - result, err := step.Run(slog.Default(), tt.request, lib.Options{}) + result, err := step.Run(slog.Default(), tt.request) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/filters/filter_capabilities.go b/internal/scheduling/nova/plugins/filters/filter_capabilities.go index 0fd1781ea..cda9a9a20 100644 --- a/internal/scheduling/nova/plugins/filters/filter_capabilities.go +++ b/internal/scheduling/nova/plugins/filters/filter_capabilities.go @@ -45,7 +45,7 @@ func hvToNovaCapabilities(hv hv1.Hypervisor) (map[string]string, error) { // Check the capabilities of each host and if they match the extra spec provided // in the request spec flavor. -func (s *FilterCapabilitiesStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *FilterCapabilitiesStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) extraSpecs := request.Spec.Data.Flavor.Data.ExtraSpecs if len(extraSpecs) == 0 { diff --git a/internal/scheduling/nova/plugins/filters/filter_capabilities_test.go b/internal/scheduling/nova/plugins/filters/filter_capabilities_test.go index 2aa6d2ba7..9b5f111dc 100644 --- a/internal/scheduling/nova/plugins/filters/filter_capabilities_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_capabilities_test.go @@ -4,7 +4,6 @@ package filters import ( - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -554,7 +553,7 @@ func TestFilterCapabilitiesStep_Run(t *testing.T) { WithScheme(scheme). WithObjects(hvs...). Build() - result, err := step.Run(slog.Default(), tt.request, lib.Options{}) + result, err := step.Run(slog.Default(), tt.request) if err != nil { t.Fatalf("expected no error, got %v", err) } @@ -629,7 +628,7 @@ func TestFilterCapabilitiesStep_DoesNotMutateExtraSpecs(t *testing.T) { WithObjects(hvs...). Build() - _, err = step.Run(slog.Default(), request, lib.Options{}) + _, err = step.Run(slog.Default(), request) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/filters/filter_correct_az.go b/internal/scheduling/nova/plugins/filters/filter_correct_az.go index 94311bd82..ed7f68188 100644 --- a/internal/scheduling/nova/plugins/filters/filter_correct_az.go +++ b/internal/scheduling/nova/plugins/filters/filter_correct_az.go @@ -18,7 +18,7 @@ type FilterCorrectAZStep struct { } // Only get hosts in the requested az. -func (s *FilterCorrectAZStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *FilterCorrectAZStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) if request.Spec.Data.AvailabilityZone == "" { traceLog.Info("no availability zone requested, skipping filter_correct_az step") diff --git a/internal/scheduling/nova/plugins/filters/filter_correct_az_test.go b/internal/scheduling/nova/plugins/filters/filter_correct_az_test.go index 4ac16cfa3..d8389de9e 100644 --- a/internal/scheduling/nova/plugins/filters/filter_correct_az_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_correct_az_test.go @@ -4,7 +4,6 @@ package filters import ( - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -170,7 +169,7 @@ func TestFilterCorrectAZStep_Run(t *testing.T) { WithScheme(scheme). WithObjects(hvs...). Build() - result, err := step.Run(slog.Default(), tt.request, lib.Options{}) + result, err := step.Run(slog.Default(), tt.request) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/filters/filter_exclude_hosts.go b/internal/scheduling/nova/plugins/filters/filter_exclude_hosts.go index 1a68602a2..231efa9aa 100644 --- a/internal/scheduling/nova/plugins/filters/filter_exclude_hosts.go +++ b/internal/scheduling/nova/plugins/filters/filter_exclude_hosts.go @@ -30,7 +30,6 @@ func (opts FilterExcludeHostsStepOpts) Validate() error { return nil } func (s *FilterExcludeHostsStep) Run( traceLog *slog.Logger, request api.ExternalSchedulerRequest, - opts lib.Options, ) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) diff --git a/internal/scheduling/nova/plugins/filters/filter_exclude_hosts_test.go b/internal/scheduling/nova/plugins/filters/filter_exclude_hosts_test.go index 42c0ab200..0c9e35c59 100644 --- a/internal/scheduling/nova/plugins/filters/filter_exclude_hosts_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_exclude_hosts_test.go @@ -218,7 +218,7 @@ func TestFilterExcludeHostsStep_Run(t *testing.T) { ExcludedHosts: tt.excludedHosts, } - result, err := step.Run(slog.Default(), tt.request, lib.Options{}) + result, err := step.Run(slog.Default(), tt.request) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/filters/filter_external_customer.go b/internal/scheduling/nova/plugins/filters/filter_external_customer.go index b96f76b6b..827712a84 100644 --- a/internal/scheduling/nova/plugins/filters/filter_external_customer.go +++ b/internal/scheduling/nova/plugins/filters/filter_external_customer.go @@ -33,7 +33,7 @@ type FilterExternalCustomerStep struct { // Prefix-match the domain name for external customer domains and filter out hosts // that are not intended for external customers. -func (s *FilterExternalCustomerStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *FilterExternalCustomerStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) domainName, err := request.Spec.Data.GetSchedulerHintStr("domain_name") if err != nil { diff --git a/internal/scheduling/nova/plugins/filters/filter_external_customer_test.go b/internal/scheduling/nova/plugins/filters/filter_external_customer_test.go index 9a46f31ee..05bdbc6f6 100644 --- a/internal/scheduling/nova/plugins/filters/filter_external_customer_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_external_customer_test.go @@ -4,7 +4,6 @@ package filters import ( - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -359,7 +358,7 @@ func TestFilterExternalCustomerStep_Run(t *testing.T) { Build() step.Options = tt.opts - result, err := step.Run(slog.Default(), tt.request, lib.Options{}) + result, err := step.Run(slog.Default(), tt.request) if tt.expectError { if err == nil { t.Errorf("expected error but got none") diff --git a/internal/scheduling/nova/plugins/filters/filter_has_accelerators.go b/internal/scheduling/nova/plugins/filters/filter_has_accelerators.go index 2c61ad588..dcccdc010 100644 --- a/internal/scheduling/nova/plugins/filters/filter_has_accelerators.go +++ b/internal/scheduling/nova/plugins/filters/filter_has_accelerators.go @@ -18,7 +18,7 @@ type FilterHasAcceleratorsStep struct { } // If requested, only get hosts with accelerators. -func (s *FilterHasAcceleratorsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *FilterHasAcceleratorsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) extraSpecs := request.Spec.Data.Flavor.Data.ExtraSpecs if _, ok := extraSpecs["accel:device_profile"]; !ok { diff --git a/internal/scheduling/nova/plugins/filters/filter_has_accelerators_test.go b/internal/scheduling/nova/plugins/filters/filter_has_accelerators_test.go index 1008ae17f..1d1a06764 100644 --- a/internal/scheduling/nova/plugins/filters/filter_has_accelerators_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_has_accelerators_test.go @@ -4,7 +4,6 @@ package filters import ( - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -348,7 +347,7 @@ func TestFilterHasAcceleratorsStep_Run(t *testing.T) { WithObjects(hvs...). Build() - result, err := step.Run(slog.Default(), tt.request, lib.Options{}) + result, err := step.Run(slog.Default(), tt.request) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity.go b/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity.go index 343554d82..b01976a93 100644 --- a/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity.go +++ b/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity.go @@ -57,7 +57,8 @@ type FilterHasEnoughCapacity struct { // known at this point. // // Please also note that disk space is currently not considered by this filter. -func (s *FilterHasEnoughCapacity) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *FilterHasEnoughCapacity) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { + opts := request.GetOptions() result := s.IncludeAllHostsFromRequest(request) // This map holds the free resources per host. diff --git a/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity_test.go b/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity_test.go index fbbd21587..f6f3689b9 100644 --- a/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity_test.go @@ -584,7 +584,7 @@ func TestFilterHasEnoughCapacity_ReservationTypes(t *testing.T) { step.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build() step.Options = tt.opts - result, err := step.Run(slog.Default(), tt.request, lib.Options{}) + result, err := step.Run(slog.Default(), tt.request) if err != nil { t.Fatalf("expected no error, got %v", err) } @@ -799,7 +799,7 @@ func TestFilterHasEnoughCapacity_IgnoredReservationTypes(t *testing.T) { IgnoredReservationTypes: tt.ignoredReservationTypes, } - result, err := step.Run(slog.Default(), tt.request, lib.Options{}) + result, err := step.Run(slog.Default(), tt.request) if err != nil { t.Fatalf("expected no error, got %v", err) } @@ -836,9 +836,10 @@ func TestFilterHasEnoughCapacity_IgnoredReservationTypes_CallTime(t *testing.T) step.Options = FilterHasEnoughCapacityOpts{LockReserved: true} // no YAML-level ignores // Call-time: ignore CR reservations → host1 passes, host2 still blocked by failover. - result, err := step.Run(slog.Default(), request, lib.Options{ + request.Options = lib.Options{ IgnoredReservationTypes: []v1alpha1.ReservationType{v1alpha1.ReservationTypeCommittedResource}, - }) + } + result, err := step.Run(slog.Default(), request) if err != nil { t.Fatalf("expected no error, got %v", err) } @@ -1003,8 +1004,9 @@ func TestFilterHasEnoughCapacity_ReserveForCommittedResourceIntent(t *testing.T) step := &FilterHasEnoughCapacity{} step.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build() step.Options = tt.opts + tt.request.Options = tt.pipelineOpts - result, err := step.Run(slog.Default(), tt.request, tt.pipelineOpts) + result, err := step.Run(slog.Default(), tt.request) if err != nil { t.Fatalf("expected no error, got %v", err) } @@ -1054,7 +1056,7 @@ func TestFilterHasEnoughCapacity_PlannedCRDoesNotBlock(t *testing.T) { step.Options = FilterHasEnoughCapacityOpts{LockReserved: false} request := newNovaRequest("instance-123", "project-A", "m1.large", "gp-1", 4, "8Gi", false, []string{"host1"}) - result, err := step.Run(slog.Default(), request, lib.Options{}) + result, err := step.Run(slog.Default(), request) if err != nil { t.Fatalf("expected no error, got %v", err) } @@ -1127,7 +1129,7 @@ func TestFilterHasEnoughCapacity_NilEffectiveCapacityFallback(t *testing.T) { step.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build() step.Options = FilterHasEnoughCapacityOpts{LockReserved: false} - result, err := step.Run(slog.Default(), tt.request, lib.Options{}) + result, err := step.Run(slog.Default(), tt.request) if err != nil { t.Fatalf("expected no error, got %v", err) } @@ -1326,7 +1328,7 @@ func TestFilterHasEnoughCapacity_VMInterReservationMigration(t *testing.T) { step.Options = FilterHasEnoughCapacityOpts{LockReserved: false} request := newNovaRequest("instance-new", thirdParty, "m1.small", flavorGroup, 3, "6Gi", false, []string{"hv-a", "hv-b"}) - result, err := step.Run(slog.Default(), request, lib.Options{}) + result, err := step.Run(slog.Default(), request) if err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/internal/scheduling/nova/plugins/filters/filter_has_requested_traits.go b/internal/scheduling/nova/plugins/filters/filter_has_requested_traits.go index 53f050db5..aa35d2fc9 100644 --- a/internal/scheduling/nova/plugins/filters/filter_has_requested_traits.go +++ b/internal/scheduling/nova/plugins/filters/filter_has_requested_traits.go @@ -21,7 +21,7 @@ type FilterHasRequestedTraits struct { // Filter hosts that do not have the requested traits given by the extra spec: // - "trait:": "forbidden" means the host must not have the specified trait. // - "trait:": "required" means the host must have the specified trait. -func (s *FilterHasRequestedTraits) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *FilterHasRequestedTraits) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) var requiredTraits, forbiddenTraits []string for key, value := range request.Spec.Data.Flavor.Data.ExtraSpecs { diff --git a/internal/scheduling/nova/plugins/filters/filter_has_requested_traits_test.go b/internal/scheduling/nova/plugins/filters/filter_has_requested_traits_test.go index edd7bb9a8..10a7c94aa 100644 --- a/internal/scheduling/nova/plugins/filters/filter_has_requested_traits_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_has_requested_traits_test.go @@ -4,7 +4,6 @@ package filters import ( - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -462,7 +461,7 @@ func TestFilterHasRequestedTraits_Run(t *testing.T) { WithObjects(hvs...). Build() - result, err := step.Run(slog.Default(), tt.request, lib.Options{}) + result, err := step.Run(slog.Default(), tt.request) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/filters/filter_host_instructions.go b/internal/scheduling/nova/plugins/filters/filter_host_instructions.go index 6e6a13da8..dafb6675f 100644 --- a/internal/scheduling/nova/plugins/filters/filter_host_instructions.go +++ b/internal/scheduling/nova/plugins/filters/filter_host_instructions.go @@ -18,7 +18,7 @@ type FilterHostInstructionsStep struct { // Filter hosts based on instructions given in the request spec. Supported are: // - spec.ignore_hosts: Filter out all hosts in this list. // - spec.force_hosts: Include only hosts in this list. -func (s *FilterHostInstructionsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *FilterHostInstructionsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) if request.Spec.Data.IgnoreHosts != nil { for _, host := range *request.Spec.Data.IgnoreHosts { diff --git a/internal/scheduling/nova/plugins/filters/filter_host_instructions_test.go b/internal/scheduling/nova/plugins/filters/filter_host_instructions_test.go index a09e12a2e..10bcb60c9 100644 --- a/internal/scheduling/nova/plugins/filters/filter_host_instructions_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_host_instructions_test.go @@ -4,7 +4,6 @@ package filters import ( - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -332,7 +331,7 @@ func TestFilterHostInstructionsStep_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { step := &FilterHostInstructionsStep{} - result, err := step.Run(slog.Default(), tt.request, lib.Options{}) + result, err := step.Run(slog.Default(), tt.request) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/filters/filter_instance_group_affinity.go b/internal/scheduling/nova/plugins/filters/filter_instance_group_affinity.go index 41eb5181e..326864b9d 100644 --- a/internal/scheduling/nova/plugins/filters/filter_instance_group_affinity.go +++ b/internal/scheduling/nova/plugins/filters/filter_instance_group_affinity.go @@ -19,7 +19,6 @@ type FilterInstanceGroupAffinityStep struct { func (s *FilterInstanceGroupAffinityStep) Run( traceLog *slog.Logger, request api.ExternalSchedulerRequest, - opts lib.Options, ) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) diff --git a/internal/scheduling/nova/plugins/filters/filter_instance_group_affinity_test.go b/internal/scheduling/nova/plugins/filters/filter_instance_group_affinity_test.go index 25e0c224d..7321747e3 100644 --- a/internal/scheduling/nova/plugins/filters/filter_instance_group_affinity_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_instance_group_affinity_test.go @@ -4,7 +4,6 @@ package filters import ( - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -327,7 +326,7 @@ func TestFilterInstanceGroupAffinityStep_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { step := &FilterInstanceGroupAffinityStep{} - result, err := step.Run(slog.Default(), tt.request, lib.Options{}) + result, err := step.Run(slog.Default(), tt.request) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/filters/filter_instance_group_anti_affinity.go b/internal/scheduling/nova/plugins/filters/filter_instance_group_anti_affinity.go index 17a9a8735..0dee29d9e 100644 --- a/internal/scheduling/nova/plugins/filters/filter_instance_group_anti_affinity.go +++ b/internal/scheduling/nova/plugins/filters/filter_instance_group_anti_affinity.go @@ -22,7 +22,6 @@ type FilterInstanceGroupAntiAffinityStep struct { func (s *FilterInstanceGroupAntiAffinityStep) Run( traceLog *slog.Logger, request api.ExternalSchedulerRequest, - opts lib.Options, ) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) diff --git a/internal/scheduling/nova/plugins/filters/filter_instance_group_anti_affinity_test.go b/internal/scheduling/nova/plugins/filters/filter_instance_group_anti_affinity_test.go index e70ada792..6eea6bc7f 100644 --- a/internal/scheduling/nova/plugins/filters/filter_instance_group_anti_affinity_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_instance_group_anti_affinity_test.go @@ -4,7 +4,6 @@ package filters import ( - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -520,7 +519,7 @@ func TestFilterInstanceGroupAntiAffinityStep_Run(t *testing.T) { WithScheme(scheme). WithObjects(hvs...). Build() - result, err := step.Run(slog.Default(), tt.request, lib.Options{}) + result, err := step.Run(slog.Default(), tt.request) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/filters/filter_live_migratable.go b/internal/scheduling/nova/plugins/filters/filter_live_migratable.go index 32c467fe8..a19238721 100644 --- a/internal/scheduling/nova/plugins/filters/filter_live_migratable.go +++ b/internal/scheduling/nova/plugins/filters/filter_live_migratable.go @@ -51,7 +51,6 @@ func (s *FilterLiveMigratableStep) checkHasSufficientFeatures( func (s *FilterLiveMigratableStep) Run( traceLog *slog.Logger, request api.ExternalSchedulerRequest, - opts lib.Options, ) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) diff --git a/internal/scheduling/nova/plugins/filters/filter_live_migratable_test.go b/internal/scheduling/nova/plugins/filters/filter_live_migratable_test.go index c4cb9df9c..c5651b025 100644 --- a/internal/scheduling/nova/plugins/filters/filter_live_migratable_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_live_migratable_test.go @@ -641,7 +641,7 @@ func TestFilterLiveMigratableStep_Run(t *testing.T) { }, } - result, err := step.Run(slog.Default(), tt.request, lib.Options{}) + result, err := step.Run(slog.Default(), tt.request) if tt.expectErr { if err == nil { @@ -728,7 +728,7 @@ func TestFilterLiveMigratableStep_Run_SourceHostNotFound(t *testing.T) { }, } - _, err := step.Run(slog.Default(), request, lib.Options{}) + _, err := step.Run(slog.Default(), request) if err == nil { t.Errorf("expected error when source host not found, got none") } @@ -774,7 +774,7 @@ func TestFilterLiveMigratableStep_Run_ClientError(t *testing.T) { }, } - _, err := step.Run(slog.Default(), request, lib.Options{}) + _, err := step.Run(slog.Default(), request) if err == nil { t.Errorf("expected error when client fails, got none") } diff --git a/internal/scheduling/nova/plugins/filters/filter_requested_destination.go b/internal/scheduling/nova/plugins/filters/filter_requested_destination.go index 83c0d5521..8922ab8c4 100644 --- a/internal/scheduling/nova/plugins/filters/filter_requested_destination.go +++ b/internal/scheduling/nova/plugins/filters/filter_requested_destination.go @@ -100,7 +100,7 @@ func (s *FilterRequestedDestinationStep) processRequestedHost( // The requested destination can include a specific host, aggregates, or both. // When both are specified, aggregate filtering is applied first, followed by // host filtering. -func (s *FilterRequestedDestinationStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *FilterRequestedDestinationStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) rd := request.Spec.Data.RequestedDestination if rd == nil { diff --git a/internal/scheduling/nova/plugins/filters/filter_requested_destination_test.go b/internal/scheduling/nova/plugins/filters/filter_requested_destination_test.go index e85d38a42..5a752160e 100644 --- a/internal/scheduling/nova/plugins/filters/filter_requested_destination_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_requested_destination_test.go @@ -578,7 +578,7 @@ func TestFilterRequestedDestinationStep_Run(t *testing.T) { }, } - result, err := step.Run(slog.Default(), tt.request, lib.Options{}) + result, err := step.Run(slog.Default(), tt.request) if tt.expectErr { if err == nil { @@ -777,7 +777,7 @@ func TestFilterRequestedDestinationStep_Run_ClientError(t *testing.T) { }, } - _, err := step.Run(slog.Default(), request, lib.Options{}) + _, err := step.Run(slog.Default(), request) if err == nil { t.Errorf("expected error when client fails, got none") } diff --git a/internal/scheduling/nova/plugins/filters/filter_status_conditions.go b/internal/scheduling/nova/plugins/filters/filter_status_conditions.go index 05bbbdcc4..3d7f2aae6 100644 --- a/internal/scheduling/nova/plugins/filters/filter_status_conditions.go +++ b/internal/scheduling/nova/plugins/filters/filter_status_conditions.go @@ -20,7 +20,7 @@ type FilterStatusConditionsStep struct { // Check that all status conditions meet the expected values, for example, // that the hypervisor is ready and not disabled. -func (s *FilterStatusConditionsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *FilterStatusConditionsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) hvs := &hv1.HypervisorList{} diff --git a/internal/scheduling/nova/plugins/filters/filter_status_conditions_test.go b/internal/scheduling/nova/plugins/filters/filter_status_conditions_test.go index 91cc67489..adbfc8c65 100644 --- a/internal/scheduling/nova/plugins/filters/filter_status_conditions_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_status_conditions_test.go @@ -4,7 +4,6 @@ package filters import ( - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -342,7 +341,7 @@ func TestFilterStatusConditionsStep_Run(t *testing.T) { WithScheme(scheme). WithObjects(hvs...). Build() - result, err := step.Run(slog.Default(), tt.request, lib.Options{}) + result, err := step.Run(slog.Default(), tt.request) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/weighers/kvm_binpack.go b/internal/scheduling/nova/plugins/weighers/kvm_binpack.go index 717f3e667..e1509a4cc 100644 --- a/internal/scheduling/nova/plugins/weighers/kvm_binpack.go +++ b/internal/scheduling/nova/plugins/weighers/kvm_binpack.go @@ -69,7 +69,7 @@ type KVMBinpackStep struct { } // Run this weigher in the pipeline after filters have been executed. -func (s *KVMBinpackStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *KVMBinpackStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) result.Statistics["binpack score"] = s.PrepareStats(request, "float") diff --git a/internal/scheduling/nova/plugins/weighers/kvm_binpack_test.go b/internal/scheduling/nova/plugins/weighers/kvm_binpack_test.go index ae7ff0a6d..69e1aa9f6 100644 --- a/internal/scheduling/nova/plugins/weighers/kvm_binpack_test.go +++ b/internal/scheduling/nova/plugins/weighers/kvm_binpack_test.go @@ -4,7 +4,6 @@ package weighers import ( - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "strings" "testing" @@ -449,7 +448,7 @@ func TestKVMBinpackStep_Run(t *testing.T) { step.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build() step.Options = tt.opts - result, err := step.Run(slog.Default(), tt.request, lib.Options{}) + result, err := step.Run(slog.Default(), tt.request) if tt.wantErr { if err == nil { t.Fatalf("expected error, got nil") diff --git a/internal/scheduling/nova/plugins/weighers/kvm_failover_evacuation.go b/internal/scheduling/nova/plugins/weighers/kvm_failover_evacuation.go index 8e463ddc9..dcbcbf8bd 100644 --- a/internal/scheduling/nova/plugins/weighers/kvm_failover_evacuation.go +++ b/internal/scheduling/nova/plugins/weighers/kvm_failover_evacuation.go @@ -50,7 +50,7 @@ type KVMFailoverEvacuationStep struct { // Run the weigher step. // For evacuation requests, hosts matching a failover reservation where the VM is in Allocations get a higher weight. // For non-evacuation requests (e.g., live migration, rebuild), this weigher has no effect. -func (s *KVMFailoverEvacuationStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *KVMFailoverEvacuationStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) intent, err := request.GetIntent() diff --git a/internal/scheduling/nova/plugins/weighers/kvm_failover_evacuation_test.go b/internal/scheduling/nova/plugins/weighers/kvm_failover_evacuation_test.go index eb7925c71..0664e55d4 100644 --- a/internal/scheduling/nova/plugins/weighers/kvm_failover_evacuation_test.go +++ b/internal/scheduling/nova/plugins/weighers/kvm_failover_evacuation_test.go @@ -4,7 +4,6 @@ package weighers import ( - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -265,7 +264,7 @@ func TestKVMFailoverEvacuationStep_Run(t *testing.T) { step.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build() step.Options = tt.opts - result, err := step.Run(slog.Default(), tt.request, lib.Options{}) + result, err := step.Run(slog.Default(), tt.request) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/weighers/kvm_failover_reservation_consolidation.go b/internal/scheduling/nova/plugins/weighers/kvm_failover_reservation_consolidation.go index 754db1be4..727afce33 100644 --- a/internal/scheduling/nova/plugins/weighers/kvm_failover_reservation_consolidation.go +++ b/internal/scheduling/nova/plugins/weighers/kvm_failover_reservation_consolidation.go @@ -78,7 +78,7 @@ type KVMFailoverReservationConsolidationStep struct { // Run the weigher step. // For reserve_for_failover requests, hosts are scored based on existing failover reservation density // and same-spec diversity. For all other request types, this weigher has no effect. -func (s *KVMFailoverReservationConsolidationStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *KVMFailoverReservationConsolidationStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) intent, err := request.GetIntent() diff --git a/internal/scheduling/nova/plugins/weighers/kvm_failover_reservation_consolidation_test.go b/internal/scheduling/nova/plugins/weighers/kvm_failover_reservation_consolidation_test.go index a065d1ef8..62d69d319 100644 --- a/internal/scheduling/nova/plugins/weighers/kvm_failover_reservation_consolidation_test.go +++ b/internal/scheduling/nova/plugins/weighers/kvm_failover_reservation_consolidation_test.go @@ -4,7 +4,6 @@ package weighers import ( - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "math" "testing" @@ -257,7 +256,7 @@ func TestKVMFailoverReservationConsolidationStep_Run(t *testing.T) { step.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build() step.Options = tt.opts - result, err := step.Run(slog.Default(), tt.request, lib.Options{}) + result, err := step.Run(slog.Default(), tt.request) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/weighers/kvm_instance_group_soft_affinity.go b/internal/scheduling/nova/plugins/weighers/kvm_instance_group_soft_affinity.go index 95c35fe92..5f13897f0 100644 --- a/internal/scheduling/nova/plugins/weighers/kvm_instance_group_soft_affinity.go +++ b/internal/scheduling/nova/plugins/weighers/kvm_instance_group_soft_affinity.go @@ -26,7 +26,7 @@ type KVMInstanceGroupSoftAffinityStep struct { lib.BaseWeigher[api.ExternalSchedulerRequest, lib.EmptyFilterWeigherPipelineStepOpts] } -func (s *KVMInstanceGroupSoftAffinityStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *KVMInstanceGroupSoftAffinityStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) result.Statistics["affinity"] = s.PrepareStats(request, "float") diff --git a/internal/scheduling/nova/plugins/weighers/kvm_instance_group_soft_affinity_test.go b/internal/scheduling/nova/plugins/weighers/kvm_instance_group_soft_affinity_test.go index b0817b737..fcf13f86b 100644 --- a/internal/scheduling/nova/plugins/weighers/kvm_instance_group_soft_affinity_test.go +++ b/internal/scheduling/nova/plugins/weighers/kvm_instance_group_soft_affinity_test.go @@ -4,7 +4,6 @@ package weighers import ( - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -347,7 +346,7 @@ func TestKVMInstanceGroupSoftAffinityStep_Run(t *testing.T) { step := &KVMInstanceGroupSoftAffinityStep{} step.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build() - result, err := step.Run(slog.Default(), tt.request, lib.Options{}) + result, err := step.Run(slog.Default(), tt.request) if tt.wantErr { if err == nil { t.Fatalf("expected error, got nil") diff --git a/internal/scheduling/nova/plugins/weighers/kvm_prefer_smaller_hosts.go b/internal/scheduling/nova/plugins/weighers/kvm_prefer_smaller_hosts.go index 88fbd2ca9..b65a5f75f 100644 --- a/internal/scheduling/nova/plugins/weighers/kvm_prefer_smaller_hosts.go +++ b/internal/scheduling/nova/plugins/weighers/kvm_prefer_smaller_hosts.go @@ -57,7 +57,7 @@ type KVMPreferSmallerHostsStep struct { } // Run this weigher in the pipeline after filters have been executed. -func (s *KVMPreferSmallerHostsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *KVMPreferSmallerHostsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) result.Statistics["small host score"] = s.PrepareStats(request, "float") diff --git a/internal/scheduling/nova/plugins/weighers/kvm_prefer_smaller_hosts_test.go b/internal/scheduling/nova/plugins/weighers/kvm_prefer_smaller_hosts_test.go index 6f306b414..2ab2deb89 100644 --- a/internal/scheduling/nova/plugins/weighers/kvm_prefer_smaller_hosts_test.go +++ b/internal/scheduling/nova/plugins/weighers/kvm_prefer_smaller_hosts_test.go @@ -4,7 +4,6 @@ package weighers import ( - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "strings" "testing" @@ -600,7 +599,7 @@ func TestKVMPreferSmallerHostsStep_Run(t *testing.T) { step.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build() step.Options = tt.opts - result, err := step.Run(slog.Default(), tt.request, lib.Options{}) + result, err := step.Run(slog.Default(), tt.request) if tt.wantErr { if err == nil { t.Fatalf("expected error, got nil") diff --git a/internal/scheduling/nova/plugins/weighers/vmware_anti_affinity_noisy_projects.go b/internal/scheduling/nova/plugins/weighers/vmware_anti_affinity_noisy_projects.go index 59a485d07..b2b886d49 100644 --- a/internal/scheduling/nova/plugins/weighers/vmware_anti_affinity_noisy_projects.go +++ b/internal/scheduling/nova/plugins/weighers/vmware_anti_affinity_noisy_projects.go @@ -52,7 +52,7 @@ func (s *VMwareAntiAffinityNoisyProjectsStep) Init(ctx context.Context, client c } // Downvote the hosts a project is currently running on if it's noisy. -func (s *VMwareAntiAffinityNoisyProjectsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *VMwareAntiAffinityNoisyProjectsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) result.Statistics["avg cpu usage of this project"] = s.PrepareStats(request, "%") diff --git a/internal/scheduling/nova/plugins/weighers/vmware_anti_affinity_noisy_projects_test.go b/internal/scheduling/nova/plugins/weighers/vmware_anti_affinity_noisy_projects_test.go index 887982049..304ab0612 100644 --- a/internal/scheduling/nova/plugins/weighers/vmware_anti_affinity_noisy_projects_test.go +++ b/internal/scheduling/nova/plugins/weighers/vmware_anti_affinity_noisy_projects_test.go @@ -5,7 +5,6 @@ package weighers import ( "context" - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "strings" "testing" @@ -274,7 +273,7 @@ func TestVMwareAntiAffinityNoisyProjectsStep_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result, err := step.Run(slog.Default(), tt.request, lib.Options{}) + result, err := step.Run(slog.Default(), tt.request) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/weighers/vmware_avoid_long_term_contended_hosts.go b/internal/scheduling/nova/plugins/weighers/vmware_avoid_long_term_contended_hosts.go index 8f46dc5d8..14905cf00 100644 --- a/internal/scheduling/nova/plugins/weighers/vmware_avoid_long_term_contended_hosts.go +++ b/internal/scheduling/nova/plugins/weighers/vmware_avoid_long_term_contended_hosts.go @@ -61,7 +61,7 @@ func (s *VMwareAvoidLongTermContendedHostsStep) Init(ctx context.Context, client } // Downvote hosts that are highly contended. -func (s *VMwareAvoidLongTermContendedHostsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *VMwareAvoidLongTermContendedHostsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) result.Statistics["avg cpu contention"] = s.PrepareStats(request, "%") diff --git a/internal/scheduling/nova/plugins/weighers/vmware_avoid_long_term_contended_hosts_test.go b/internal/scheduling/nova/plugins/weighers/vmware_avoid_long_term_contended_hosts_test.go index e3a62423c..5a69cdaf7 100644 --- a/internal/scheduling/nova/plugins/weighers/vmware_avoid_long_term_contended_hosts_test.go +++ b/internal/scheduling/nova/plugins/weighers/vmware_avoid_long_term_contended_hosts_test.go @@ -5,7 +5,6 @@ package weighers import ( "context" - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "strings" "testing" @@ -257,7 +256,7 @@ func TestVMwareAvoidLongTermContendedHostsStep_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result, err := step.Run(slog.Default(), tt.request, lib.Options{}) + result, err := step.Run(slog.Default(), tt.request) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/weighers/vmware_avoid_short_term_contended_hosts.go b/internal/scheduling/nova/plugins/weighers/vmware_avoid_short_term_contended_hosts.go index f2d7896f0..fd9e81335 100644 --- a/internal/scheduling/nova/plugins/weighers/vmware_avoid_short_term_contended_hosts.go +++ b/internal/scheduling/nova/plugins/weighers/vmware_avoid_short_term_contended_hosts.go @@ -61,7 +61,7 @@ func (s *VMwareAvoidShortTermContendedHostsStep) Init(ctx context.Context, clien } // Downvote hosts that are highly contended. -func (s *VMwareAvoidShortTermContendedHostsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *VMwareAvoidShortTermContendedHostsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) result.Statistics["avg cpu contention"] = s.PrepareStats(request, "%") diff --git a/internal/scheduling/nova/plugins/weighers/vmware_avoid_short_term_contended_hosts_test.go b/internal/scheduling/nova/plugins/weighers/vmware_avoid_short_term_contended_hosts_test.go index 9de020c95..0dfe280d0 100644 --- a/internal/scheduling/nova/plugins/weighers/vmware_avoid_short_term_contended_hosts_test.go +++ b/internal/scheduling/nova/plugins/weighers/vmware_avoid_short_term_contended_hosts_test.go @@ -5,7 +5,6 @@ package weighers import ( "context" - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "strings" "testing" @@ -257,7 +256,7 @@ func TestVMwareAvoidShortTermContendedHostsStep_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result, err := step.Run(slog.Default(), tt.request, lib.Options{}) + result, err := step.Run(slog.Default(), tt.request) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/nova/plugins/weighers/vmware_binpack.go b/internal/scheduling/nova/plugins/weighers/vmware_binpack.go index adb6f86bc..217dc7d7f 100644 --- a/internal/scheduling/nova/plugins/weighers/vmware_binpack.go +++ b/internal/scheduling/nova/plugins/weighers/vmware_binpack.go @@ -85,7 +85,7 @@ func (s *VMwareBinpackStep) Init(ctx context.Context, client client.Client, weig } // Run this weigher in the pipeline after filters have been executed. -func (s *VMwareBinpackStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *VMwareBinpackStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) result.Statistics["binpack score"] = s.PrepareStats(request, "float") diff --git a/internal/scheduling/nova/plugins/weighers/vmware_binpack_test.go b/internal/scheduling/nova/plugins/weighers/vmware_binpack_test.go index d274ef40d..cdca6c569 100644 --- a/internal/scheduling/nova/plugins/weighers/vmware_binpack_test.go +++ b/internal/scheduling/nova/plugins/weighers/vmware_binpack_test.go @@ -4,7 +4,6 @@ package weighers import ( - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -212,7 +211,7 @@ func TestVMwareBinpackStep_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result, err := step.Run(slog.Default(), tt.request, lib.Options{}) + result, err := step.Run(slog.Default(), tt.request) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/internal/scheduling/pods/plugins/filters/filter_node_affinity.go b/internal/scheduling/pods/plugins/filters/filter_node_affinity.go index c8953ec1b..996897bdb 100644 --- a/internal/scheduling/pods/plugins/filters/filter_node_affinity.go +++ b/internal/scheduling/pods/plugins/filters/filter_node_affinity.go @@ -27,7 +27,7 @@ func (f *NodeAffinityFilter) Validate(ctx context.Context, params v1alpha1.Param return nil } -func (NodeAffinityFilter) Run(traceLog *slog.Logger, request pods.PodPipelineRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { +func (NodeAffinityFilter) Run(traceLog *slog.Logger, request pods.PodPipelineRequest) (*lib.FilterWeigherPipelineStepResult, error) { activations := make(map[string]float64) stats := make(map[string]lib.FilterWeigherPipelineStepStatistics) diff --git a/internal/scheduling/pods/plugins/filters/filter_node_affinity_test.go b/internal/scheduling/pods/plugins/filters/filter_node_affinity_test.go index 1442994c2..0172d60d7 100644 --- a/internal/scheduling/pods/plugins/filters/filter_node_affinity_test.go +++ b/internal/scheduling/pods/plugins/filters/filter_node_affinity_test.go @@ -4,7 +4,6 @@ package filters import ( - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -400,7 +399,7 @@ func TestNodeAffinityFilter_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { filter := &NodeAffinityFilter{} - result, err := filter.Run(slog.Default(), tt.request, lib.Options{}) + result, err := filter.Run(slog.Default(), tt.request) if err != nil { t.Errorf("expected Run() to succeed, got error: %v", err) diff --git a/internal/scheduling/pods/plugins/filters/filter_node_available.go b/internal/scheduling/pods/plugins/filters/filter_node_available.go index cfbccfa28..2d6e11d22 100644 --- a/internal/scheduling/pods/plugins/filters/filter_node_available.go +++ b/internal/scheduling/pods/plugins/filters/filter_node_available.go @@ -26,7 +26,7 @@ func (f *NodeAvailableFilter) Validate(ctx context.Context, params v1alpha1.Para return nil } -func (NodeAvailableFilter) Run(traceLog *slog.Logger, request pods.PodPipelineRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { +func (NodeAvailableFilter) Run(traceLog *slog.Logger, request pods.PodPipelineRequest) (*lib.FilterWeigherPipelineStepResult, error) { activations := make(map[string]float64) stats := make(map[string]lib.FilterWeigherPipelineStepStatistics) diff --git a/internal/scheduling/pods/plugins/filters/filter_node_available_test.go b/internal/scheduling/pods/plugins/filters/filter_node_available_test.go index e9966d594..3eac7873c 100644 --- a/internal/scheduling/pods/plugins/filters/filter_node_available_test.go +++ b/internal/scheduling/pods/plugins/filters/filter_node_available_test.go @@ -4,7 +4,6 @@ package filters import ( - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -310,7 +309,7 @@ func TestNodeAvailableFilter_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { filter := &NodeAvailableFilter{} - result, err := filter.Run(slog.Default(), tt.request, lib.Options{}) + result, err := filter.Run(slog.Default(), tt.request) if err != nil { t.Errorf("expected Run() to succeed, got error: %v", err) diff --git a/internal/scheduling/pods/plugins/filters/filter_node_capacity.go b/internal/scheduling/pods/plugins/filters/filter_node_capacity.go index 6d2f0eae7..cfceb8835 100644 --- a/internal/scheduling/pods/plugins/filters/filter_node_capacity.go +++ b/internal/scheduling/pods/plugins/filters/filter_node_capacity.go @@ -27,7 +27,7 @@ func (f *NodeCapacityFilter) Validate(ctx context.Context, params v1alpha1.Param return nil } -func (NodeCapacityFilter) Run(traceLog *slog.Logger, request pods.PodPipelineRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { +func (NodeCapacityFilter) Run(traceLog *slog.Logger, request pods.PodPipelineRequest) (*lib.FilterWeigherPipelineStepResult, error) { activations := make(map[string]float64) stats := make(map[string]lib.FilterWeigherPipelineStepStatistics) diff --git a/internal/scheduling/pods/plugins/filters/filter_node_capacity_test.go b/internal/scheduling/pods/plugins/filters/filter_node_capacity_test.go index 950adace7..543b4561d 100644 --- a/internal/scheduling/pods/plugins/filters/filter_node_capacity_test.go +++ b/internal/scheduling/pods/plugins/filters/filter_node_capacity_test.go @@ -4,7 +4,6 @@ package filters import ( - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -352,7 +351,7 @@ func TestNodeCapacityFilter_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { filter := &NodeCapacityFilter{} - result, err := filter.Run(slog.Default(), tt.request, lib.Options{}) + result, err := filter.Run(slog.Default(), tt.request) if err != nil { t.Errorf("expected Run() to succeed, got error: %v", err) diff --git a/internal/scheduling/pods/plugins/filters/filter_noop.go b/internal/scheduling/pods/plugins/filters/filter_noop.go index fda0510f6..e0666537b 100644 --- a/internal/scheduling/pods/plugins/filters/filter_noop.go +++ b/internal/scheduling/pods/plugins/filters/filter_noop.go @@ -31,7 +31,7 @@ func (f *NoopFilter) Validate(ctx context.Context, params v1alpha1.Parameters) e // not in the map are considered as filtered out. // Provide a traceLog that contains the global request id and should // be used to log the step's execution. -func (NoopFilter) Run(traceLog *slog.Logger, request pods.PodPipelineRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { +func (NoopFilter) Run(traceLog *slog.Logger, request pods.PodPipelineRequest) (*lib.FilterWeigherPipelineStepResult, error) { activations := make(map[string]float64, len(request.Nodes)) stats := make(map[string]lib.FilterWeigherPipelineStepStatistics) // Usually you would do some filtering here, or adjust the weights. diff --git a/internal/scheduling/pods/plugins/filters/filter_noop_test.go b/internal/scheduling/pods/plugins/filters/filter_noop_test.go index 71e22f72f..dcdd90a69 100644 --- a/internal/scheduling/pods/plugins/filters/filter_noop_test.go +++ b/internal/scheduling/pods/plugins/filters/filter_noop_test.go @@ -4,7 +4,6 @@ package filters import ( - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -86,7 +85,7 @@ func TestNoopFilter_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { filter := &NoopFilter{} - result, err := filter.Run(slog.Default(), tt.request, lib.Options{}) + result, err := filter.Run(slog.Default(), tt.request) if err != nil { t.Errorf("expected Run() to succeed, got error: %v", err) diff --git a/internal/scheduling/pods/plugins/filters/filter_taint.go b/internal/scheduling/pods/plugins/filters/filter_taint.go index f349c5213..5a54d1cb6 100644 --- a/internal/scheduling/pods/plugins/filters/filter_taint.go +++ b/internal/scheduling/pods/plugins/filters/filter_taint.go @@ -26,7 +26,7 @@ func (f *TaintFilter) Validate(ctx context.Context, params v1alpha1.Parameters) return nil } -func (TaintFilter) Run(traceLog *slog.Logger, request pods.PodPipelineRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { +func (TaintFilter) Run(traceLog *slog.Logger, request pods.PodPipelineRequest) (*lib.FilterWeigherPipelineStepResult, error) { activations := make(map[string]float64) stats := make(map[string]lib.FilterWeigherPipelineStepStatistics) diff --git a/internal/scheduling/pods/plugins/filters/filter_taint_test.go b/internal/scheduling/pods/plugins/filters/filter_taint_test.go index 258f78ef0..1d248b685 100644 --- a/internal/scheduling/pods/plugins/filters/filter_taint_test.go +++ b/internal/scheduling/pods/plugins/filters/filter_taint_test.go @@ -4,7 +4,6 @@ package filters import ( - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "testing" @@ -253,7 +252,7 @@ func TestTaintFilter_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { filter := &TaintFilter{} - result, err := filter.Run(slog.Default(), tt.request, lib.Options{}) + result, err := filter.Run(slog.Default(), tt.request) if err != nil { t.Errorf("expected Run() to succeed, got error: %v", err) diff --git a/internal/scheduling/pods/plugins/weighers/binpack.go b/internal/scheduling/pods/plugins/weighers/binpack.go index d0c8e2cf9..07d310fb8 100644 --- a/internal/scheduling/pods/plugins/weighers/binpack.go +++ b/internal/scheduling/pods/plugins/weighers/binpack.go @@ -31,7 +31,7 @@ type BinpackingStep struct { lib.BaseWeigher[api.PodPipelineRequest, BinpackingStepOpts] } -func (s *BinpackingStep) Run(traceLog *slog.Logger, request api.PodPipelineRequest, opts lib.Options) (*lib.FilterWeigherPipelineStepResult, error) { +func (s *BinpackingStep) Run(traceLog *slog.Logger, request api.PodPipelineRequest) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) podResources := helpers.GetPodResourceRequests(request.Pod) diff --git a/internal/scheduling/pods/plugins/weighers/binpack_test.go b/internal/scheduling/pods/plugins/weighers/binpack_test.go index 82838909f..198e110c1 100644 --- a/internal/scheduling/pods/plugins/weighers/binpack_test.go +++ b/internal/scheduling/pods/plugins/weighers/binpack_test.go @@ -4,7 +4,6 @@ package weighers import ( - "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" "log/slog" "math" "testing" @@ -258,7 +257,7 @@ func TestBinpackingStep_Run(t *testing.T) { }, } - result, err := tt.step.Run(slog.Default(), tt.request, lib.Options{}) + result, err := tt.step.Run(slog.Default(), tt.request) if err != nil { t.Fatalf("expected no error, got %v", err) } From c2f0b5698e58c420efeff49fea986219465dcd1c Mon Sep 17 00:00:00 2001 From: mblos Date: Wed, 6 May 2026 13:04:36 +0200 Subject: [PATCH 8/8] comments --- internal/scheduling/lib/filter_weigher_pipeline.go | 6 +++++- .../scheduling/lib/filter_weigher_pipeline_test.go | 10 ++-------- internal/scheduling/lib/options.go | 3 --- .../nova/filter_weigher_pipeline_controller.go | 2 ++ 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/internal/scheduling/lib/filter_weigher_pipeline.go b/internal/scheduling/lib/filter_weigher_pipeline.go index e2b9ce468..d12b566ad 100644 --- a/internal/scheduling/lib/filter_weigher_pipeline.go +++ b/internal/scheduling/lib/filter_weigher_pipeline.go @@ -306,8 +306,12 @@ func (p *filterWeigherPipeline[RequestType]) Run(request RequestType) (v1alpha1. traceLog.Info("scheduler: trimming candidate list", "maxCandidates", opts.MaxCandidates, "before", len(hosts)) hosts = hosts[:opts.MaxCandidates] // Drop trimmed hosts from outWeights so AggregatedOutWeights stays consistent. + kept := make(map[string]struct{}, len(hosts)) + for _, h := range hosts { + kept[h] = struct{}{} + } for host := range outWeights { - if !slices.Contains(hosts, host) { + if _, ok := kept[host]; !ok { delete(outWeights, host) } } diff --git a/internal/scheduling/lib/filter_weigher_pipeline_test.go b/internal/scheduling/lib/filter_weigher_pipeline_test.go index 54251731c..a110aeec3 100644 --- a/internal/scheduling/lib/filter_weigher_pipeline_test.go +++ b/internal/scheduling/lib/filter_weigher_pipeline_test.go @@ -7,6 +7,7 @@ import ( "context" "log/slog" "math" + "slices" "testing" "github.com/cobaltcore-dev/cortex/api/v1alpha1" @@ -415,14 +416,7 @@ func TestPipeline_MaxCandidates(t *testing.T) { if tt.maxCandidates > 0 && len(result.OrderedHosts) <= tt.maxCandidates { // AggregatedOutWeights must only contain returned hosts. for host := range result.AggregatedOutWeights { - found := false - for _, h := range result.OrderedHosts { - if h == host { - found = true - break - } - } - if !found { + if !slices.Contains(result.OrderedHosts, host) { t.Errorf("AggregatedOutWeights contains trimmed host %s", host) } } diff --git a/internal/scheduling/lib/options.go b/internal/scheduling/lib/options.go index 44445cf6d..c4e43080b 100644 --- a/internal/scheduling/lib/options.go +++ b/internal/scheduling/lib/options.go @@ -12,9 +12,6 @@ import ( // Options configure the behavior of a single pipeline run at call time. // These are distinct from per-step YAML options (FilterWeigherPipelineStepOpts), // which are static and set when the pipeline is initialized. -// -// Consumed by steps: ReadOnly, LockReservations, AssumeEmptyHosts, IgnoredReservationTypes. -// Consumed by the controller after pipeline.Run(): RecordHistory, CreateInflight. type Options struct { // ReadOnly means the pipeline run does not modify shared scheduling state (reservations, // history, inflight records). Concurrent read-only runs are safe under a shared read lock. diff --git a/internal/scheduling/nova/filter_weigher_pipeline_controller.go b/internal/scheduling/nova/filter_weigher_pipeline_controller.go index 703f6d23f..40252fdc2 100644 --- a/internal/scheduling/nova/filter_weigher_pipeline_controller.go +++ b/internal/scheduling/nova/filter_weigher_pipeline_controller.go @@ -84,6 +84,8 @@ func (c *FilterWeigherPipelineController) Reconcile(ctx context.Context, req ctr // Process the decision from the API. Should create and return the updated decision. func (c *FilterWeigherPipelineController) ProcessNewDecisionFromAPI(ctx context.Context, decision *v1alpha1.Decision) error { + // Read-only runs share the cached decision state; no re-fetch needed because they + // don't observe writes from concurrent exclusive-lock runs. if c.peekReadOnly(decision) { c.processMu.RLock() defer c.processMu.RUnlock()