diff --git a/cmd/main.go b/cmd/main.go index cad93b9a1..9ceb90bf0 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -298,7 +298,8 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "DecisionReconciler") os.Exit(1) } - nova.NewAPI(config, decisionController).Init(mux) + httpAPIConf := conf.GetConfigOrDie[nova.HTTPAPIConfig]() + nova.NewAPI(httpAPIConf, decisionController).Init(mux) } if slices.Contains(config.EnabledControllers, "nova-deschedulings-pipeline-controller") { // Deschedulings controller diff --git a/helm/bundles/cortex-nova/values.yaml b/helm/bundles/cortex-nova/values.yaml index fcbce1cfd..3635ba5b0 100644 --- a/helm/bundles/cortex-nova/values.yaml +++ b/helm/bundles/cortex-nova/values.yaml @@ -96,6 +96,8 @@ cortex-scheduling-controllers: conf: <<: *cortexConf leaderElectionID: cortex-nova-scheduling + # OpenStack projects that use experimental features. + experimentalProjectIDs: [] monitoring: labels: <<: *cortexMonitoringLabels diff --git a/internal/scheduling/nova/external_scheduler_api.go b/internal/scheduling/nova/external_scheduler_api.go index 26083c481..0367f9d27 100644 --- a/internal/scheduling/nova/external_scheduler_api.go +++ b/internal/scheduling/nova/external_scheduler_api.go @@ -12,11 +12,11 @@ import ( "io" "log/slog" "net/http" + "slices" "strings" api "github.com/cobaltcore-dev/cortex/api/delegation/nova" "github.com/cobaltcore-dev/cortex/api/v1alpha1" - "github.com/cobaltcore-dev/cortex/pkg/conf" scheduling "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" corev1 "k8s.io/api/core/v1" @@ -26,6 +26,12 @@ import ( "sigs.k8s.io/controller-runtime/pkg/metrics" ) +// Custom configuration for the Nova external scheduler api. +type HTTPAPIConfig struct { + // OpenStack projects that use experimental features. + ExperimentalProjectIDs []string `json:"experimentalProjectIDs,omitempty"` +} + type HTTPAPIDelegate interface { // Process the decision from the API. Should create and return the updated decision. ProcessNewDecisionFromAPI(ctx context.Context, decision *v1alpha1.Decision) error @@ -37,12 +43,12 @@ type HTTPAPI interface { } type httpAPI struct { - config conf.Config + config HTTPAPIConfig monitor scheduling.APIMonitor delegate HTTPAPIDelegate } -func NewAPI(config conf.Config, delegate HTTPAPIDelegate) HTTPAPI { +func NewAPI(config HTTPAPIConfig, delegate HTTPAPIDelegate) HTTPAPI { return &httpAPI{ config: config, monitor: scheduling.NewSchedulerMonitor(), @@ -88,11 +94,19 @@ func (httpAPI *httpAPI) inferPipelineName(requestData api.ExternalSchedulerReque } switch strings.ToLower(hvType) { case "qemu", "ch": + enableAllFilters := false + // If the nova request matches a configurable openstack project, + // use a different pipeline that has all filters enabled. + if slices.Contains(httpAPI.config.ExperimentalProjectIDs, requestData.Spec.Data.ProjectID) { + enableAllFilters = true + } if requestData.Reservation { + enableAllFilters = true + } + if enableAllFilters { return "nova-external-scheduler-kvm-all-filters-enabled", nil - } else { - return "nova-external-scheduler-kvm", nil } + return "nova-external-scheduler-kvm", nil case "vmware vcenter server": if requestData.Reservation { return "", errors.New("reservations are not supported on vmware hypervisors") diff --git a/internal/scheduling/nova/external_scheduler_api_test.go b/internal/scheduling/nova/external_scheduler_api_test.go index 6ac7706ed..3fb735954 100644 --- a/internal/scheduling/nova/external_scheduler_api_test.go +++ b/internal/scheduling/nova/external_scheduler_api_test.go @@ -15,7 +15,6 @@ import ( novaapi "github.com/cobaltcore-dev/cortex/api/delegation/nova" "github.com/cobaltcore-dev/cortex/api/v1alpha1" - "github.com/cobaltcore-dev/cortex/pkg/conf" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -32,8 +31,8 @@ func (m *mockHTTPAPIDelegate) ProcessNewDecisionFromAPI(ctx context.Context, dec } func TestNewAPI(t *testing.T) { - config := conf.Config{SchedulingDomain: "test-operator"} delegate := &mockHTTPAPIDelegate{} + config := HTTPAPIConfig{} api := NewAPI(config, delegate) @@ -46,10 +45,6 @@ func TestNewAPI(t *testing.T) { t.Fatal("NewAPI did not return httpAPI type") } - if httpAPI.config.SchedulingDomain != "test-operator" { - t.Errorf("Expected scheduling domain 'test-operator', got %s", httpAPI.config.SchedulingDomain) - } - if httpAPI.delegate != delegate { t.Error("Delegate not set correctly") } @@ -60,8 +55,8 @@ func TestNewAPI(t *testing.T) { } func TestHTTPAPI_Init(t *testing.T) { - config := conf.Config{SchedulingDomain: "test-operator"} delegate := &mockHTTPAPIDelegate{} + config := HTTPAPIConfig{} api := NewAPI(config, delegate) mux := http.NewServeMux() @@ -79,8 +74,8 @@ func TestHTTPAPI_Init(t *testing.T) { } func TestHTTPAPI_canRunScheduler(t *testing.T) { - config := conf.Config{SchedulingDomain: "test-operator"} delegate := &mockHTTPAPIDelegate{} + config := HTTPAPIConfig{} api := NewAPI(config, delegate).(*httpAPI) tests := []struct { @@ -271,7 +266,6 @@ func TestHTTPAPI_NovaExternalScheduler(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - config := conf.Config{SchedulingDomain: "test-operator"} delegate := &mockHTTPAPIDelegate{ processDecisionFunc: func(ctx context.Context, decision *v1alpha1.Decision) error { if tt.processDecisionErr != nil { @@ -285,6 +279,7 @@ func TestHTTPAPI_NovaExternalScheduler(t *testing.T) { }, } + config := HTTPAPIConfig{} api := NewAPI(config, delegate).(*httpAPI) var body *strings.Reader @@ -324,8 +319,6 @@ func TestHTTPAPI_NovaExternalScheduler(t *testing.T) { } func TestHTTPAPI_NovaExternalScheduler_DecisionCreation(t *testing.T) { - config := conf.Config{SchedulingDomain: v1alpha1.SchedulingDomainNova} - var capturedDecision *v1alpha1.Decision delegate := &mockHTTPAPIDelegate{ processDecisionFunc: func(ctx context.Context, decision *v1alpha1.Decision) error { @@ -338,6 +331,7 @@ func TestHTTPAPI_NovaExternalScheduler_DecisionCreation(t *testing.T) { }, } + config := HTTPAPIConfig{} api := NewAPI(config, delegate).(*httpAPI) requestData := novaapi.ExternalSchedulerRequest{ @@ -395,8 +389,10 @@ func TestHTTPAPI_NovaExternalScheduler_DecisionCreation(t *testing.T) { } func TestHTTPAPI_inferPipelineName(t *testing.T) { - config := conf.Config{SchedulingDomain: "test-operator"} delegate := &mockHTTPAPIDelegate{} + config := HTTPAPIConfig{ + ExperimentalProjectIDs: []string{"my-experimental-project-id"}, + } api := NewAPI(config, delegate).(*httpAPI) tests := []struct { @@ -406,6 +402,26 @@ func TestHTTPAPI_inferPipelineName(t *testing.T) { expectErr bool errContains string }{ + { + name: "experimental project ID requesting kvm vm", + requestData: novaapi.ExternalSchedulerRequest{ + Spec: novaapi.NovaObject[novaapi.NovaSpec]{ + Data: novaapi.NovaSpec{ + ProjectID: "my-experimental-project-id", + Flavor: novaapi.NovaObject[novaapi.NovaFlavor]{ + Data: novaapi.NovaFlavor{ + ExtraSpecs: map[string]string{ + "capabilities:hypervisor_type": "qemu", + }, + }, + }, + }, + }, + Reservation: false, + }, + expectedResult: "nova-external-scheduler-kvm-all-filters-enabled", + expectErr: false, + }, { name: "qemu hypervisor without reservation", requestData: novaapi.ExternalSchedulerRequest{