diff --git a/.claude/agents/pull-request-creator.md b/.claude/agents/pull-request-creator.md index 94bd2d596..f5223ea40 100644 --- a/.claude/agents/pull-request-creator.md +++ b/.claude/agents/pull-request-creator.md @@ -61,9 +61,11 @@ If `git stash pop` reports conflicts, abort and surface them — something on th ``` git add -A -git commit -m "" +git commit -s -m "" ``` +Note: the `-s` adds a Signed-off-by trailer to the commit message. This is required for all commits in this project. + ## Step 5: Push ``` diff --git a/internal/scheduling/nova/plugins/filters/filter_image_properties.go b/internal/scheduling/nova/plugins/filters/filter_image_properties.go index 7ce083f87..2fe1ada44 100644 --- a/internal/scheduling/nova/plugins/filters/filter_image_properties.go +++ b/internal/scheduling/nova/plugins/filters/filter_image_properties.go @@ -6,8 +6,10 @@ package filters import ( "context" "log/slog" + "slices" 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" ) @@ -19,6 +21,26 @@ type FilterImagePropertiesStep struct { // Filter hosts based on image properties given in the request spec. func (s *FilterImagePropertiesStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) { result := s.IncludeAllHostsFromRequest(request) + + // Apply this filter to all requests, unless we know from the request's + // intent that image metadata is expected to not be set. + if intent, err := request.GetIntent(); err == nil { + intentsExpectedToNotHaveImageMeta := []v1alpha1.SchedulingIntent{ + // Cortex-internal intents in which scheduling requests are sent + // mainly based on flavor-related metadata, independent of the + // image that'll land there. + api.ReserveForFailoverIntent, + api.ReuseFailoverReservationIntent, + api.ReserveForCommittedResourceIntent, + api.CapacityProbeIntent, + } + if slices.Contains(intentsExpectedToNotHaveImageMeta, intent) { + traceLog.Debug("skipping filter: expected to have no image metadata", + "intent", intent) + return result, nil + } + } + // If the image properties indicate any other hypervisor type than kvm, // we filter out all known kvm hypervisors. hvType, err := request.Spec.Data.Image.Data.GetHypervisorType() @@ -32,6 +54,7 @@ func (s *FilterImagePropertiesStep) Run(traceLog *slog.Logger, request api.Exter result.Events = append(result.Events, "image_properties_hv_type_undetermined") return result, nil } + if hvType != api.NovaImageMetaHVTypeKVM { traceLog.Info("filtering out all known kvm hypervisors since image properties indicate a different hypervisor type", "image_hypervisor_type", hvType) @@ -45,6 +68,7 @@ func (s *FilterImagePropertiesStep) Run(traceLog *slog.Logger, request api.Exter traceLog.Debug("filtering host which is kvm hypervisor", "host", hv.Name) } } + return result, nil } diff --git a/internal/scheduling/nova/plugins/filters/filter_image_properties_test.go b/internal/scheduling/nova/plugins/filters/filter_image_properties_test.go index c4370e79e..64dd4bb26 100644 --- a/internal/scheduling/nova/plugins/filters/filter_image_properties_test.go +++ b/internal/scheduling/nova/plugins/filters/filter_image_properties_test.go @@ -27,6 +27,15 @@ func requestWith(hosts []string, properties map[string]any) api.ExternalSchedule return request } +// requestWithIntent builds an ExternalSchedulerRequest like requestWith, but +// additionally sets the _nova_check_type scheduler hint so GetIntent resolves +// to the given intent. +func requestWithIntent(hosts []string, properties map[string]any, checkType string) api.ExternalSchedulerRequest { + request := requestWith(hosts, properties) + request.Spec.Data.SchedulerHints = map[string]any{"_nova_check_type": checkType} + return request +} + func TestFilterImagePropertiesStep_Run(t *testing.T) { scheme := runtime.NewScheme() if err := hv1.AddToScheme(scheme); err != nil { @@ -81,6 +90,36 @@ func TestFilterImagePropertiesStep_Run(t *testing.T) { expectedHosts: []string{}, filteredHosts: []string{}, }, + { + name: "reserve_for_failover intent skips filter and keeps all hosts", + request: requestWithIntent([]string{"host1", "host2", "host3"}, map[string]any{"hypervisor_type": "vmware"}, "reserve_for_failover"), + expectedHosts: []string{"host1", "host2", "host3"}, + filteredHosts: []string{}, + }, + { + name: "reuse_failover_reservation intent skips filter and keeps all hosts", + request: requestWithIntent([]string{"host1", "host2", "host3"}, map[string]any{"hypervisor_type": "vmware"}, "reuse_failover_reservation"), + expectedHosts: []string{"host1", "host2", "host3"}, + filteredHosts: []string{}, + }, + { + name: "reserve_for_committed_resource intent skips filter and keeps all hosts", + request: requestWithIntent([]string{"host1", "host2", "host3"}, map[string]any{"hypervisor_type": "vmware"}, "reserve_for_committed_resource"), + expectedHosts: []string{"host1", "host2", "host3"}, + filteredHosts: []string{}, + }, + { + name: "capacity_probe intent skips filter and keeps all hosts", + request: requestWithIntent([]string{"host1", "host2", "host3"}, map[string]any{"hypervisor_type": "vmware"}, "capacity_probe"), + expectedHosts: []string{"host1", "host2", "host3"}, + filteredHosts: []string{}, + }, + { + name: "create intent still filters out known kvm hypervisors", + request: requestWithIntent([]string{"host1", "host2", "host3"}, map[string]any{"hypervisor_type": "vmware"}, "create"), + expectedHosts: []string{"host3"}, + filteredHosts: []string{"host1", "host2"}, + }, } for _, tt := range tests {