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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions helm/bundles/cortex-nova/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ cortex-scheduling-controllers:
conf:
<<: *cortexConf
leaderElectionID: cortex-nova-scheduling
# OpenStack projects that use experimental features.
experimentalProjectIDs: []
monitoring:
labels:
<<: *cortexMonitoringLabels
Expand Down
24 changes: 19 additions & 5 deletions internal/scheduling/nova/external_scheduler_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand All @@ -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(),
Expand Down Expand Up @@ -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")
Expand Down
40 changes: 28 additions & 12 deletions internal/scheduling/nova/external_scheduler_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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)

Expand All @@ -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")
}
Expand All @@ -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()
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -285,6 +279,7 @@ func TestHTTPAPI_NovaExternalScheduler(t *testing.T) {
},
}

config := HTTPAPIConfig{}
api := NewAPI(config, delegate).(*httpAPI)

var body *strings.Reader
Expand Down Expand Up @@ -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 {
Expand All @@ -338,6 +331,7 @@ func TestHTTPAPI_NovaExternalScheduler_DecisionCreation(t *testing.T) {
},
}

config := HTTPAPIConfig{}
api := NewAPI(config, delegate).(*httpAPI)

requestData := novaapi.ExternalSchedulerRequest{
Expand Down Expand Up @@ -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 {
Expand All @@ -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{
Expand Down