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
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"strings"

api "github.com/cobaltcore-dev/cortex/api/external/nova"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
"github.com/cobaltcore-dev/cortex/internal/scheduling/lib"
hv1 "github.com/cobaltcore-dev/openstack-hypervisor-operator/api/v1"
)
Expand All @@ -23,12 +22,11 @@ type FilterAggregateMetadata struct {
// the "filter_tenant_id" metadata key set.
func (s *FilterAggregateMetadata) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
result := s.IncludeAllHostsFromRequest(request)
// Failover and capacity probe calls are not placed on behalf of a tenant project;
if intent, err := request.GetIntent(); err == nil && slices.Contains([]v1alpha1.SchedulingIntent{
api.ReserveForFailoverIntent,
api.ReuseFailoverReservationIntent,
api.CapacityProbeIntent,
}, intent) {
// Capacity probe calls are synthetic and not placed on behalf of a tenant
// project, so they must ignore tenant pinning. Failover reservations DO respect
// tenant pinning: a reservation on a host the VM's project cannot use is dead
// weight and would be deleted at validation time.
if intent, err := request.GetIntent(); err == nil && intent == api.CapacityProbeIntent {
return result, nil
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,7 @@ func TestFilterAggregateMetadata_IndexRegistration(t *testing.T) {
}
}

func TestFilterAggregateMetadata_SkipsForNonPlacementIntent(t *testing.T) {
func TestFilterAggregateMetadata_SkipsForCapacityProbeIntent(t *testing.T) {
scheme := runtime.NewScheme()
if err := hv1.AddToScheme(scheme); err != nil {
t.Fatalf("failed to add hv1 to scheme: %v", err)
Expand All @@ -575,8 +575,47 @@ func TestFilterAggregateMetadata_SkipsForNonPlacementIntent(t *testing.T) {
step := &FilterAggregateMetadata{}
step.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build()

for _, intent := range []string{"reserve_for_failover", "reuse_failover_reservation", "capacity_probe"} {
request := api.ExternalSchedulerRequest{
Spec: api.NovaObject[api.NovaSpec]{
Data: api.NovaSpec{
ProjectID: "project-y",
SchedulerHints: map[string]any{"_nova_check_type": "capacity_probe"},
},
},
Hosts: []api.ExternalSchedulerHost{{ComputeHost: "host1"}, {ComputeHost: "host2"}},
}
result, err := step.Run(slog.Default(), request)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(result.Activations) != 2 {
t.Errorf("expected both hosts to pass, got %d", len(result.Activations))
}
}

func TestFilterAggregateMetadata_AppliesForFailoverIntents(t *testing.T) {
scheme := runtime.NewScheme()
if err := hv1.AddToScheme(scheme); err != nil {
t.Fatalf("failed to add hv1 to scheme: %v", err)
}
// host1 is pinned to project-x; request is project-y → host1 must be filtered out.
objects := []client.Object{
&hv1.Hypervisor{
ObjectMeta: metav1.ObjectMeta{Name: "host1"},
Status: hv1.HypervisorStatus{
Aggregates: []hv1.Aggregate{{
Name: "restricted",
Metadata: map[string]string{"filter_tenant_id": "project-x"},
}},
},
},
&hv1.Hypervisor{ObjectMeta: metav1.ObjectMeta{Name: "host2"}},
}

for _, intent := range []string{"reserve_for_failover", "reuse_failover_reservation"} {
t.Run(intent, func(t *testing.T) {
step := &FilterAggregateMetadata{}
step.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build()
request := api.ExternalSchedulerRequest{
Spec: api.NovaObject[api.NovaSpec]{
Data: api.NovaSpec{
Expand All @@ -590,8 +629,11 @@ func TestFilterAggregateMetadata_SkipsForNonPlacementIntent(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(result.Activations) != 2 {
t.Errorf("expected both hosts to pass, got %d", len(result.Activations))
if len(result.Activations) != 1 {
t.Errorf("expected only host2 to pass, got %d hosts", len(result.Activations))
}
if _, ok := result.Activations["host1"]; ok {
t.Errorf("expected host1 (pinned to project-x) to be filtered out for project-y")
}
})
}
Expand Down
Loading