diff --git a/internal/scheduling/nova/adapter.go b/internal/scheduling/compute/nova_adapter.go similarity index 83% rename from internal/scheduling/nova/adapter.go rename to internal/scheduling/compute/nova_adapter.go index ce236111d..035502d3e 100644 --- a/internal/scheduling/nova/adapter.go +++ b/internal/scheduling/compute/nova_adapter.go @@ -1,18 +1,17 @@ // Copyright SAP SE // SPDX-License-Identifier: Apache-2.0 -package nova +package compute 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/compute" ) // ToSchedulerRequest translates the Nova over-the-wire request into the typed, -// Nova-agnostic compute.PlacementRequest the pipeline consumes. This is the one +// Nova-agnostic PlacementRequest the pipeline consumes. This is the one // place that reads Nova's loosely-typed scheduler hints and flavor/image extra // specs; every downstream filter/weigher sees only resolved typed fields. // @@ -22,11 +21,11 @@ func ToSchedulerRequest( wire api.ExternalSchedulerRequest, intent v1alpha1.SchedulingIntent, pipeline string, -) compute.PlacementRequest { +) PlacementRequest { spec := wire.Spec.Data - req := compute.PlacementRequest{ + req := PlacementRequest{ Pipeline: pipeline, Hosts: toComputeHosts(wire.Hosts), Weights: wire.Weights, @@ -54,7 +53,7 @@ func ToSchedulerRequest( } // Flavor sizing. - req.Flavor = compute.FlavorSpec{ + req.Flavor = FlavorSpec{ VCPUs: spec.Flavor.Data.VCPUs, MemoryMB: spec.Flavor.Data.MemoryMB, } @@ -66,7 +65,7 @@ func ToSchedulerRequest( // preselection) is determined by the hypervisor family: cortex can enumerate // KVM hypervisors itself, whereas VMware placement relies on the host list // Nova sent in the request. - req.GatherAllCandidates = req.HypervisorFamily == compute.HypervisorFamilyKVM + req.GatherAllCandidates = req.HypervisorFamily == HypervisorFamilyKVM // Image. req.ImageHypervisorType = toImageHypervisorType(spec.Image.Data) @@ -83,26 +82,26 @@ func ToSchedulerRequest( return req } -func toComputeHosts(hosts []api.ExternalSchedulerHost) []compute.Host { - out := make([]compute.Host, len(hosts)) +func toComputeHosts(hosts []api.ExternalSchedulerHost) []Host { + out := make([]Host, len(hosts)) for i, h := range hosts { - out[i] = compute.Host{ComputeHost: h.ComputeHost, HypervisorHostname: h.HypervisorHostname} + out[i] = Host{ComputeHost: h.ComputeHost, HypervisorHostname: h.HypervisorHostname} } return out } // toHypervisorFamily resolves the coarse family from the wire hypervisor type. // It is empty when unresolved. -func toHypervisorFamily(wire api.ExternalSchedulerRequest) compute.HypervisorFamily { +func toHypervisorFamily(wire api.ExternalSchedulerRequest) HypervisorFamily { hv, err := wire.GetHypervisorType() if err != nil { return "" } switch hv { case api.HypervisorTypeQEMU, api.HypervisorTypeCH: - return compute.HypervisorFamilyKVM + return HypervisorFamilyKVM case api.HypervisorTypeVMware: - return compute.HypervisorFamilyVMware + return HypervisorFamilyVMware } return "" } @@ -110,7 +109,7 @@ func toHypervisorFamily(wire api.ExternalSchedulerRequest) compute.HypervisorFam // toImageHypervisorType maps the image-declared hypervisor type. It returns // ImageHypervisorTypeUnparseable when the image declared a type that could not // be parsed, distinct from ImageHypervisorTypeUnset when none was declared. -func toImageHypervisorType(image api.NovaImageMeta) compute.ImageHypervisorType { +func toImageHypervisorType(image api.NovaImageMeta) ImageHypervisorType { hv, err := image.GetHypervisorType() if err != nil { // GetHypervisorType errors both when nothing is declared and when a @@ -119,38 +118,38 @@ func toImageHypervisorType(image api.NovaImageMeta) compute.ImageHypervisorType if image.Properties.Data != nil { for _, key := range []string{"img_hv_type", "hypervisor_type"} { if _, ok := image.Properties.Data[key]; ok { - return compute.ImageHypervisorTypeUnparseable + return ImageHypervisorTypeUnparseable } } } - return compute.ImageHypervisorTypeUnset + return ImageHypervisorTypeUnset } switch hv { case api.NovaImageMetaHVTypeVMware: - return compute.ImageHypervisorTypeVMware + return ImageHypervisorTypeVMware case api.NovaImageMetaHVTypeBaremetal: - return compute.ImageHypervisorTypeBaremetal + return ImageHypervisorTypeBaremetal case api.NovaImageMetaHVTypeKVM: - return compute.ImageHypervisorTypeKVM + return ImageHypervisorTypeKVM } - return compute.ImageHypervisorTypeUnset + return ImageHypervisorTypeUnset } -func toRequestedDestination(rd *api.NovaObject[api.NovaRequestedDestination]) *compute.RequestedDestination { +func toRequestedDestination(rd *api.NovaObject[api.NovaRequestedDestination]) *RequestedDestination { if rd == nil { return nil } - return &compute.RequestedDestination{ + return &RequestedDestination{ Host: rd.Data.Host, Aggregates: rd.Data.Aggregates, } } -func toInstanceGroup(ig *api.NovaObject[api.NovaInstanceGroup]) *compute.InstanceGroupSpec { +func toInstanceGroup(ig *api.NovaObject[api.NovaInstanceGroup]) *InstanceGroupSpec { if ig == nil { return nil } - out := &compute.InstanceGroupSpec{ + out := &InstanceGroupSpec{ UUID: ig.Data.UUID, Policy: ig.Data.Policy, Hosts: ig.Data.Hosts, @@ -232,13 +231,13 @@ func SplitTraits(extraSpecs map[string]string) (required, forbidden []string) { } // SplitCapabilities extracts a flavor's "capabilities:" extra specs as typed -// compute.Capability equalities (prefix stripped). The "hypervisor_type" value is +// Capability equalities (prefix stripped). The "hypervisor_type" value is // canonicalized (qemu->QEMU, ch->CH) so it matches what hypervisors advertise // (filter_capabilities compares case-sensitively); all other values pass through // unchanged. If any value uses an unsupported comparison operator it returns nil so // the capabilities filter is skipped entirely (matching Nova behavior). -func SplitCapabilities(extraSpecs map[string]string) []compute.Capability { - var caps []compute.Capability +func SplitCapabilities(extraSpecs map[string]string) []Capability { + var caps []Capability for key, value := range extraSpecs { name, ok := strings.CutPrefix(key, "capabilities:") if !ok { @@ -252,7 +251,7 @@ func SplitCapabilities(extraSpecs map[string]string) []compute.Capability { if name == "hypervisor_type" { value = CanonicalHypervisorType(value) } - caps = append(caps, compute.Capability{Name: name, Value: value}) + caps = append(caps, Capability{Name: name, Value: value}) } return caps } diff --git a/internal/scheduling/nova/adapter_test.go b/internal/scheduling/compute/nova_adapter_test.go similarity index 84% rename from internal/scheduling/nova/adapter_test.go rename to internal/scheduling/compute/nova_adapter_test.go index d45a6659e..118951017 100644 --- a/internal/scheduling/nova/adapter_test.go +++ b/internal/scheduling/compute/nova_adapter_test.go @@ -1,14 +1,12 @@ // Copyright SAP SE // SPDX-License-Identifier: Apache-2.0 -package nova +package compute import ( "slices" "strings" "testing" - - "github.com/cobaltcore-dev/cortex/internal/scheduling/compute" ) func TestSplitTraits(t *testing.T) { @@ -27,23 +25,23 @@ func TestSplitTraits(t *testing.T) { } func TestSplitCapabilities(t *testing.T) { - sortCaps := func(c []compute.Capability) { - slices.SortFunc(c, func(a, b compute.Capability) int { return strings.Compare(a.Name, b.Name) }) + sortCaps := func(c []Capability) { + slices.SortFunc(c, func(a, b Capability) int { return strings.Compare(a.Name, b.Name) }) } cases := []struct { name string extraSpecs map[string]string - want []compute.Capability + want []Capability }{ { name: "canonicalizes lowercase hypervisor_type to match hosts", extraSpecs: map[string]string{"capabilities:hypervisor_type": "qemu"}, - want: []compute.Capability{{Name: "hypervisor_type", Value: "QEMU"}}, + want: []Capability{{Name: "hypervisor_type", Value: "QEMU"}}, }, { name: "ch canonicalized", extraSpecs: map[string]string{"capabilities:hypervisor_type": "ch"}, - want: []compute.Capability{{Name: "hypervisor_type", Value: "CH"}}, + want: []Capability{{Name: "hypervisor_type", Value: "CH"}}, }, { name: "non-hypervisor_type capabilities preserved verbatim", @@ -51,7 +49,7 @@ func TestSplitCapabilities(t *testing.T) { "capabilities:hypervisor_type": "QEMU", "capabilities:cpu_arch": "aarch64", }, - want: []compute.Capability{ + want: []Capability{ {Name: "cpu_arch", Value: "aarch64"}, {Name: "hypervisor_type", Value: "QEMU"}, }, diff --git a/internal/scheduling/nova/crs/recorder.go b/internal/scheduling/nova/crs/recorder.go index 31b1e0265..2c1d07374 100644 --- a/internal/scheduling/nova/crs/recorder.go +++ b/internal/scheduling/nova/crs/recorder.go @@ -11,7 +11,8 @@ import ( "github.com/cobaltcore-dev/cortex/api/v1alpha1" "github.com/cobaltcore-dev/cortex/internal/knowledge/extractor/plugins/compute" sched "github.com/cobaltcore-dev/cortex/internal/scheduling/compute" - "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations/accounting" + "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations" + "github.com/prometheus/client_golang/prometheus" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -37,12 +38,12 @@ var errFlavorNotInGroup = errors.New("flavor not in any group") // Returns errFlavorNotInGroup (PAYG) if the flavor is not in any group. // Returns a different error for transient failures (Knowledge CRD unavailable, etc). func (r *Recorder) resolveFlavorGroup(ctx context.Context, flavorName string) (string, *compute.FlavorInGroup, error) { - fgClient := accounting.FlavorGroupKnowledgeClient{Client: r.Client} + fgClient := reservations.FlavorGroupKnowledgeClient{Client: r.Client} flavorGroups, err := fgClient.GetAllFlavorGroups(ctx, nil) if err != nil { return "", nil, err } - groupName, flavor, err := accounting.FindFlavorInGroups(flavorName, flavorGroups) + groupName, flavor, err := reservations.FindFlavorInGroups(flavorName, flavorGroups) if err != nil { return "", nil, errFlavorNotInGroup } diff --git a/internal/scheduling/nova/external_scheduler_api.go b/internal/scheduling/nova/external_scheduler_api.go index 1e14a4ac5..08814e262 100644 --- a/internal/scheduling/nova/external_scheduler_api.go +++ b/internal/scheduling/nova/external_scheduler_api.go @@ -275,7 +275,7 @@ func (httpAPI *httpAPI) NovaExternalScheduler(w http.ResponseWriter, r *http.Req ctx := r.Context() // Translate the Nova wire request into the typed pipeline request. - schedReq := ToSchedulerRequest(requestData, intent, requestData.Pipeline) + schedReq := compute.ToSchedulerRequest(requestData, intent, requestData.Pipeline) // Side effects run as a single post-hook after the pipeline, under the // scheduler lock. It operates on the Nova wire request (which carries the diff --git a/internal/scheduling/reservations/capacity/controller.go b/internal/scheduling/reservations/capacity/controller.go index ec3d58388..4cba8577b 100644 --- a/internal/scheduling/reservations/capacity/controller.go +++ b/internal/scheduling/reservations/capacity/controller.go @@ -31,9 +31,7 @@ import ( "github.com/cobaltcore-dev/cortex/internal/knowledge/extractor/plugins/compute" sched "github.com/cobaltcore-dev/cortex/internal/scheduling/compute" caplib "github.com/cobaltcore-dev/cortex/internal/scheduling/lib/capacity" - "github.com/cobaltcore-dev/cortex/internal/scheduling/nova" "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations" - "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations/accounting" "github.com/cobaltcore-dev/cortex/pkg/multicluster" "github.com/go-logr/logr" ) @@ -227,7 +225,7 @@ func (c *Reconciler) reconcileAll(ctx context.Context) error { logger := LoggerFromContext(ctx) startTime := time.Now() - knowledge := &accounting.FlavorGroupKnowledgeClient{Client: c.client} + knowledge := &reservations.FlavorGroupKnowledgeClient{Client: c.client} flavorGroups, err := knowledge.GetAllFlavorGroups(ctx, nil) if err != nil { return fmt.Errorf("failed to get flavor groups: %w", err) @@ -829,7 +827,7 @@ func (c *Reconciler) probeScheduler( // filter_has_requested_traits/filter_capabilities would otherwise see empty // slices and become no-ops). No hypervisor-type default — only match when the // flavor names one. - requiredTraits, forbiddenTraits := nova.SplitTraits(flavor.ExtraSpecs) + requiredTraits, forbiddenTraits := sched.SplitTraits(flavor.ExtraSpecs) out, err := c.scheduler.RunWithoutLock(ctx, sched.PlacementRequest{ Pipeline: pipeline, GatherAllCandidates: true, @@ -844,7 +842,7 @@ func (c *Reconciler) probeScheduler( RequiredTraits: requiredTraits, ForbiddenTraits: forbiddenTraits, RequestedCapabilities: reservations.HypervisorTypeCapability(flavor.ExtraSpecs, false), - AcceleratorsRequested: nova.RequestsAccelerators(flavor.ExtraSpecs), + AcceleratorsRequested: sched.RequestsAccelerators(flavor.ExtraSpecs), Flavor: sched.FlavorSpec{ VCPUs: flavor.VCPUs, MemoryMB: flavor.MemoryMB, diff --git a/internal/scheduling/reservations/commitments/api/change_commitments.go b/internal/scheduling/reservations/commitments/api/change_commitments.go index df981e452..41df5ea9d 100644 --- a/internal/scheduling/reservations/commitments/api/change_commitments.go +++ b/internal/scheduling/reservations/commitments/api/change_commitments.go @@ -15,7 +15,6 @@ import ( "github.com/cobaltcore-dev/cortex/api/v1alpha1" "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations" - "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations/accounting" commitments "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations/commitments" "github.com/cobaltcore-dev/cortex/pkg/multicluster" "github.com/go-logr/logr" @@ -181,7 +180,7 @@ func (api *HTTPAPI) HandleChangeCommitments(w http.ResponseWriter, r *http.Reque } { - knowledge := &accounting.FlavorGroupKnowledgeClient{Client: api.client} + knowledge := &reservations.FlavorGroupKnowledgeClient{Client: api.client} if knowledgeCRD, err := knowledge.Get(ctx); err == nil && knowledgeCRD != nil { var currentVersion int64 = -1 if !knowledgeCRD.Status.LastContentChange.IsZero() { @@ -232,7 +231,7 @@ func (api *HTTPAPI) HandleChangeCommitments(w http.ResponseWriter, r *http.Reque } func (api *HTTPAPI) processCommitmentChanges(ctx context.Context, w http.ResponseWriter, logger logr.Logger, req liquid.CommitmentChangeRequest, resp *liquid.CommitmentChangeResponse) error { - knowledge := &accounting.FlavorGroupKnowledgeClient{Client: api.client} + knowledge := &reservations.FlavorGroupKnowledgeClient{Client: api.client} flavorGroups, err := knowledge.GetAllFlavorGroups(ctx, nil) if err != nil { logger.Info("failed to get flavor groups from knowledge extractor", "error", err) @@ -641,7 +640,7 @@ func rollbackCR(ctx context.Context, logger logr.Logger, k8sClient client.Client // performDryRun checks whether the net capacity delta in req can be placed by running // temporary probe CommittedResource CRDs through the controller, then always cleaning them up. func (api *HTTPAPI) performDryRun(ctx context.Context, logger logr.Logger, req liquid.CommitmentChangeRequest, resp *liquid.CommitmentChangeResponse) { - knowledge := &accounting.FlavorGroupKnowledgeClient{Client: api.client} + knowledge := &reservations.FlavorGroupKnowledgeClient{Client: api.client} flavorGroups, err := knowledge.GetAllFlavorGroups(ctx, nil) if err != nil { resp.RejectionReason = "dry run: caches not ready, please retry later" diff --git a/internal/scheduling/reservations/commitments/api/info.go b/internal/scheduling/reservations/commitments/api/info.go index 26993422e..9950ab610 100644 --- a/internal/scheduling/reservations/commitments/api/info.go +++ b/internal/scheduling/reservations/commitments/api/info.go @@ -14,7 +14,6 @@ import ( "time" "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations" - "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations/accounting" commitments "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations/commitments" "github.com/go-logr/logr" "github.com/google/uuid" @@ -106,7 +105,7 @@ func mibToGiB(v *uint64) *uint64 { // All flavor groups report usage; only those with fixed RAM/core ratio accept commitments. func (api *HTTPAPI) buildServiceInfo(ctx context.Context, logger logr.Logger) (liquid.ServiceInfo, error) { // Get all flavor groups from Knowledge CRDs - knowledge := &accounting.FlavorGroupKnowledgeClient{Client: api.client} + knowledge := &reservations.FlavorGroupKnowledgeClient{Client: api.client} flavorGroups, err := knowledge.GetAllFlavorGroups(ctx, nil) if err != nil { // Return -1 as version when knowledge is not ready diff --git a/internal/scheduling/reservations/commitments/api/quota.go b/internal/scheduling/reservations/commitments/api/quota.go index ff989fd32..59d759a9a 100644 --- a/internal/scheduling/reservations/commitments/api/quota.go +++ b/internal/scheduling/reservations/commitments/api/quota.go @@ -13,7 +13,7 @@ import ( "time" "github.com/cobaltcore-dev/cortex/api/v1alpha1" - "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations/accounting" + "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations" commitments "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations/commitments" "github.com/cobaltcore-dev/cortex/pkg/multicluster" "github.com/google/uuid" @@ -97,7 +97,7 @@ func (api *HTTPAPI) HandleQuota(w http.ResponseWriter, r *http.Request) { // Fetch flavor groups to determine per-resource RAM unit. // The ProjectQuota CRD stores RAM values in GiB; Limes sends in the declared unit // (slots for fixed-ratio groups, GiB for variable-ratio). Convert on receipt. - knowledge := &accounting.FlavorGroupKnowledgeClient{Client: api.client} + knowledge := &reservations.FlavorGroupKnowledgeClient{Client: api.client} flavorGroups, err := knowledge.GetAllFlavorGroups(r.Context(), nil) if err != nil { log.Info("flavor groups not available for quota unit conversion", "error", err.Error()) diff --git a/internal/scheduling/reservations/commitments/capacity.go b/internal/scheduling/reservations/commitments/capacity.go index b9f01f8c0..78381d0f5 100644 --- a/internal/scheduling/reservations/commitments/capacity.go +++ b/internal/scheduling/reservations/commitments/capacity.go @@ -15,7 +15,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "github.com/cobaltcore-dev/cortex/api/v1alpha1" - "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations/accounting" + "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations" ) // ErrCapacityNotReady is returned by CalculateCapacity when one or more FlavorGroupCapacity @@ -64,7 +64,7 @@ func NewCapacityCalculator(client client.Client, conf APIConfig) *CapacityCalcul // - Usage: always None. Limes derives project usage from the separate Report-Usage endpoint, so // a usage value here is unused and would only invite misinterpretation of reserved capacity. func (c *CapacityCalculator) CalculateCapacity(ctx context.Context, req liquid.ServiceCapacityRequest) (liquid.ServiceCapacityReport, error) { - knowledge := &accounting.FlavorGroupKnowledgeClient{Client: c.client} + knowledge := &reservations.FlavorGroupKnowledgeClient{Client: c.client} flavorGroups, err := knowledge.GetAllFlavorGroups(ctx, nil) if err != nil { return liquid.ServiceCapacityReport{}, fmt.Errorf("failed to get flavor groups: %w", err) diff --git a/internal/scheduling/reservations/commitments/committed_resource_controller.go b/internal/scheduling/reservations/commitments/committed_resource_controller.go index 9d15cd2ce..9a589bd10 100644 --- a/internal/scheduling/reservations/commitments/committed_resource_controller.go +++ b/internal/scheduling/reservations/commitments/committed_resource_controller.go @@ -24,7 +24,6 @@ import ( "github.com/cobaltcore-dev/cortex/api/v1alpha1" "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations" - "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations/accounting" "github.com/cobaltcore-dev/cortex/pkg/multicluster" ) @@ -441,7 +440,7 @@ func (r *CommittedResourceController) reconcileCoresHeadroom(ctx context.Context } func (r *CommittedResourceController) applyReservationState(ctx context.Context, logger logr.Logger, cr *v1alpha1.CommittedResource) (*ApplyResult, error) { - knowledge := &accounting.FlavorGroupKnowledgeClient{Client: r.Client} + knowledge := &reservations.FlavorGroupKnowledgeClient{Client: r.Client} flavorGroups, err := knowledge.GetAllFlavorGroups(ctx, nil) if err != nil { return nil, fmt.Errorf("flavor knowledge not ready: %w", err) @@ -640,7 +639,7 @@ func (r *CommittedResourceController) rollbackToAccepted(ctx context.Context, lo if cr.Status.AcceptedSpec == nil { return r.deleteChildReservations(ctx, cr) } - knowledge := &accounting.FlavorGroupKnowledgeClient{Client: r.Client} + knowledge := &reservations.FlavorGroupKnowledgeClient{Client: r.Client} flavorGroups, err := knowledge.GetAllFlavorGroups(ctx, nil) if err != nil { // Can't compute the rollback target — fall back to full delete rather than leaving diff --git a/internal/scheduling/reservations/commitments/reservation_controller.go b/internal/scheduling/reservations/commitments/reservation_controller.go index ddd889805..63b247342 100644 --- a/internal/scheduling/reservations/commitments/reservation_controller.go +++ b/internal/scheduling/reservations/commitments/reservation_controller.go @@ -33,9 +33,7 @@ import ( "github.com/cobaltcore-dev/cortex/api/scheduling" "github.com/cobaltcore-dev/cortex/api/v1alpha1" "github.com/cobaltcore-dev/cortex/internal/scheduling/compute" - "github.com/cobaltcore-dev/cortex/internal/scheduling/nova" "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations" - "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations/accounting" "github.com/cobaltcore-dev/cortex/pkg/keystone" "github.com/cobaltcore-dev/cortex/pkg/multicluster" "github.com/cobaltcore-dev/cortex/pkg/sso" @@ -239,7 +237,7 @@ func (r *CommitmentReservationController) Reconcile(ctx context.Context, req ctr } // Get flavor details from flavor group knowledge CRD - knowledge := &accounting.FlavorGroupKnowledgeClient{Client: r.Client} + knowledge := &reservations.FlavorGroupKnowledgeClient{Client: r.Client} flavorGroups, err := knowledge.GetAllFlavorGroups(ctx, nil) if err != nil { logger.V(1).Info("flavor knowledge not ready, requeueing", @@ -249,7 +247,7 @@ func (r *CommitmentReservationController) Reconcile(ctx context.Context, req ctr } // Search for the flavor across all flavor groups - flavorGroupName, flavorDetails, err := accounting.FindFlavorInGroups(resourceName, flavorGroups) + flavorGroupName, flavorDetails, err := reservations.FindFlavorInGroups(resourceName, flavorGroups) if err != nil { logger.Error(err, "flavor not found in any flavor group", "resourceName", resourceName) @@ -316,7 +314,7 @@ func (r *CommitmentReservationController) Reconcile(ctx context.Context, req ctr // request is built directly, so filter_has_requested_traits/filter_capabilities // would otherwise see empty slices and become no-ops). No hypervisor-type // default here — only match when the flavor names one. - requiredTraits, forbiddenTraits := nova.SplitTraits(flavorDetails.ExtraSpecs) + requiredTraits, forbiddenTraits := compute.SplitTraits(flavorDetails.ExtraSpecs) schedReq := compute.PlacementRequest{ Pipeline: pipelineName, GatherAllCandidates: true, @@ -332,7 +330,7 @@ func (r *CommitmentReservationController) Reconcile(ctx context.Context, req ctr RequiredTraits: requiredTraits, ForbiddenTraits: forbiddenTraits, RequestedCapabilities: reservations.HypervisorTypeCapability(flavorDetails.ExtraSpecs, false), - AcceleratorsRequested: nova.RequestsAccelerators(flavorDetails.ExtraSpecs), + AcceleratorsRequested: compute.RequestsAccelerators(flavorDetails.ExtraSpecs), Flavor: compute.FlavorSpec{ VCPUs: flavorDetails.VCPUs, MemoryMB: flavorDetails.MemoryMB, diff --git a/internal/scheduling/reservations/commitments/syncer.go b/internal/scheduling/reservations/commitments/syncer.go index 9e0175ff7..c40ec0a0e 100644 --- a/internal/scheduling/reservations/commitments/syncer.go +++ b/internal/scheduling/reservations/commitments/syncer.go @@ -13,7 +13,6 @@ import ( "github.com/cobaltcore-dev/cortex/api/v1alpha1" "github.com/cobaltcore-dev/cortex/internal/knowledge/extractor/plugins/compute" "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations" - "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations/accounting" "github.com/go-logr/logr" "github.com/sapcc/go-api-declarations/liquid" corev1 "k8s.io/api/core/v1" @@ -249,7 +248,7 @@ func (s *Syncer) SyncReservations(ctx context.Context) error { }() // Check if flavor group knowledge is ready - knowledge := &accounting.FlavorGroupKnowledgeClient{Client: s.Client} + knowledge := &reservations.FlavorGroupKnowledgeClient{Client: s.Client} knowledgeCRD, err := knowledge.Get(ctx) if err != nil { logger.Error(err, "failed to check flavor group knowledge readiness") diff --git a/internal/scheduling/reservations/commitments/usage.go b/internal/scheduling/reservations/commitments/usage.go index 6fcdd789c..34a909f72 100644 --- a/internal/scheduling/reservations/commitments/usage.go +++ b/internal/scheduling/reservations/commitments/usage.go @@ -14,7 +14,6 @@ import ( "github.com/cobaltcore-dev/cortex/api/v1alpha1" "github.com/cobaltcore-dev/cortex/internal/knowledge/extractor/plugins/compute" "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations" - "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations/accounting" hv1 "github.com/cobaltcore-dev/openstack-hypervisor-operator/api/v1" "github.com/go-logr/logr" "github.com/sapcc/go-api-declarations/liquid" @@ -100,7 +99,7 @@ func (c *UsageCalculator) CalculateUsage( allAZs []liquid.AvailabilityZone, ) (liquid.ServiceUsageReport, error) { - knowledge := &accounting.FlavorGroupKnowledgeClient{Client: c.client} + knowledge := &reservations.FlavorGroupKnowledgeClient{Client: c.client} flavorGroups, err := knowledge.GetAllFlavorGroups(ctx, nil) if err != nil { return liquid.ServiceUsageReport{}, fmt.Errorf("failed to get flavor groups: %w", err) diff --git a/internal/scheduling/reservations/commitments/usage_reconciler.go b/internal/scheduling/reservations/commitments/usage_reconciler.go index ad3068dc5..843bd8647 100644 --- a/internal/scheduling/reservations/commitments/usage_reconciler.go +++ b/internal/scheduling/reservations/commitments/usage_reconciler.go @@ -25,7 +25,6 @@ import ( "github.com/cobaltcore-dev/cortex/api/v1alpha1" "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations" - "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations/accounting" "github.com/cobaltcore-dev/cortex/pkg/multicluster" ) @@ -118,7 +117,7 @@ func (r *UsageReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl logger := log logger.V(1).Info("usage reconcile starting", "trigger", trigger, "generation", cr.Generation) - knowledge := &accounting.FlavorGroupKnowledgeClient{Client: r.Client} + knowledge := &reservations.FlavorGroupKnowledgeClient{Client: r.Client} flavorGroups, err := knowledge.GetAllFlavorGroups(ctx, nil) if err != nil { r.Monitor.reconcileDuration.WithLabelValues("error").Observe(time.Since(start).Seconds()) diff --git a/internal/scheduling/reservations/extra_specs.go b/internal/scheduling/reservations/extra_specs.go index 66e568585..1c2828d24 100644 --- a/internal/scheduling/reservations/extra_specs.go +++ b/internal/scheduling/reservations/extra_specs.go @@ -5,7 +5,6 @@ package reservations import ( "github.com/cobaltcore-dev/cortex/internal/scheduling/compute" - "github.com/cobaltcore-dev/cortex/internal/scheduling/nova" ) // The reservation controllers build the typed compute.PlacementRequest directly @@ -18,7 +17,7 @@ import ( // HypervisorTypeCapability returns the required (key=value) hypervisor capabilities // for a reservation scheduler request, so filter_capabilities keeps a workload off // incompatible hosts. It extracts ALL of the flavor's "capabilities:" extra -// specs (via nova.SplitCapabilities, which canonicalizes the hypervisor_type casing +// specs (via compute.SplitCapabilities, which canonicalizes the hypervisor_type casing // and drops everything on an unsupported operator) — not just hypervisor_type. // // When the flavor names no hypervisor type and defaultQEMU is set (the failover @@ -26,7 +25,7 @@ import ( // hypervisor_type is added. Without defaultQEMU, a missing hypervisor type adds no // hypervisor_type constraint. func HypervisorTypeCapability(extraSpecs map[string]string, defaultQEMU bool) []compute.Capability { - caps := nova.SplitCapabilities(extraSpecs) + caps := compute.SplitCapabilities(extraSpecs) if extraSpecs["capabilities:hypervisor_type"] == "" && defaultQEMU { caps = append(caps, compute.Capability{Name: "hypervisor_type", Value: "QEMU"}) } diff --git a/internal/scheduling/reservations/failover/controller.go b/internal/scheduling/reservations/failover/controller.go index 0d26b8c37..c0a80232f 100644 --- a/internal/scheduling/reservations/failover/controller.go +++ b/internal/scheduling/reservations/failover/controller.go @@ -16,7 +16,6 @@ import ( "github.com/cobaltcore-dev/cortex/internal/knowledge/extractor/plugins/compute" sched "github.com/cobaltcore-dev/cortex/internal/scheduling/compute" "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations" - "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations/accounting" "github.com/cobaltcore-dev/cortex/pkg/multicluster" hv1 "github.com/cobaltcore-dev/openstack-hypervisor-operator/api/v1" "github.com/google/uuid" @@ -348,7 +347,7 @@ func (c *FailoverReservationController) ReconcilePeriodic(ctx context.Context) ( // 6. Fetch flavor groups for reservation sizing (if configured) var flavorGroups map[string]compute.FlavorGroupFeature if c.Config.UseFlavorGroupResources { - knowledge := &accounting.FlavorGroupKnowledgeClient{Client: c.Client} + knowledge := &reservations.FlavorGroupKnowledgeClient{Client: c.Client} fg, err := knowledge.GetAllFlavorGroups(ctx, nil) if err != nil { logger.Info("flavor group knowledge not available, will fall back to VM resources for sizing", diff --git a/internal/scheduling/reservations/failover/helpers.go b/internal/scheduling/reservations/failover/helpers.go index 1a2ee46e6..0bd28bd9e 100644 --- a/internal/scheduling/reservations/failover/helpers.go +++ b/internal/scheduling/reservations/failover/helpers.go @@ -12,7 +12,6 @@ import ( "github.com/cobaltcore-dev/cortex/api/v1alpha1" "github.com/cobaltcore-dev/cortex/internal/knowledge/extractor/plugins/compute" "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations" - "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations/accounting" hv1 "github.com/cobaltcore-dev/openstack-hypervisor-operator/api/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -86,7 +85,7 @@ func resolveVMSpecForScheduling( logger := LoggerFromContext(ctx) if useFlavorGroupResources && flavorGroups != nil { - groupName, _, err := accounting.FindFlavorInGroups(vm.FlavorName, flavorGroups) + groupName, _, err := reservations.FindFlavorInGroups(vm.FlavorName, flavorGroups) if err == nil { fg := flavorGroups[groupName] largest := fg.LargestFlavor diff --git a/internal/scheduling/reservations/failover/integration_test.go b/internal/scheduling/reservations/failover/integration_test.go index 04f9f92e7..d1f69584f 100644 --- a/internal/scheduling/reservations/failover/integration_test.go +++ b/internal/scheduling/reservations/failover/integration_test.go @@ -1283,7 +1283,7 @@ func (api *testHTTPAPI) NovaExternalScheduler(w http.ResponseWriter, r *http.Req intent = novaapi.CreateIntent } - req := nova.ToSchedulerRequest(requestData, intent, pipelineName) + req := compute.ToSchedulerRequest(requestData, intent, pipelineName) out, err := api.scheduler.Run(r.Context(), req, nil, nil) if err != nil { http.Error(w, "failed to process scheduling decision", http.StatusInternalServerError) diff --git a/internal/scheduling/reservations/failover/reservation_scheduling.go b/internal/scheduling/reservations/failover/reservation_scheduling.go index dd6fceb03..d1a77d98f 100644 --- a/internal/scheduling/reservations/failover/reservation_scheduling.go +++ b/internal/scheduling/reservations/failover/reservation_scheduling.go @@ -14,7 +14,6 @@ import ( "github.com/cobaltcore-dev/cortex/api/scheduling" "github.com/cobaltcore-dev/cortex/api/v1alpha1" "github.com/cobaltcore-dev/cortex/internal/scheduling/compute" - "github.com/cobaltcore-dev/cortex/internal/scheduling/nova" "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations" hv1 "github.com/cobaltcore-dev/openstack-hypervisor-operator/api/v1" ) @@ -107,7 +106,7 @@ func (c *FailoverReservationController) queryHypervisorsFromScheduler(ctx contex hosts = append(hosts, compute.Host{ComputeHost: h.ComputeHost, HypervisorHostname: h.HypervisorHostname}) weights[h.ComputeHost] = 0.0 } - requiredTraits, forbiddenTraits := nova.SplitTraits(vm.FlavorExtraSpecs) + requiredTraits, forbiddenTraits := compute.SplitTraits(vm.FlavorExtraSpecs) schedReq := compute.PlacementRequest{ Pipeline: pipeline, GatherAllCandidates: true, @@ -125,7 +124,7 @@ func (c *FailoverReservationController) queryHypervisorsFromScheduler(ctx contex RequiredTraits: requiredTraits, ForbiddenTraits: forbiddenTraits, RequestedCapabilities: reservations.HypervisorTypeCapability(vm.FlavorExtraSpecs, true), - AcceleratorsRequested: nova.RequestsAccelerators(vm.FlavorExtraSpecs), + AcceleratorsRequested: compute.RequestsAccelerators(vm.FlavorExtraSpecs), Flavor: compute.FlavorSpec{ VCPUs: resSpec.VCPUs, MemoryMB: resSpec.MemoryMB, @@ -262,7 +261,7 @@ func (c *FailoverReservationController) validateVMViaSchedulerEvacuation( // match it in the reservation's allocations. KVM gathers all candidates; the // reservation host must come back ranked for the VM to be compatible. pipeline := InferFailoverPipeline(flavorExtraSpecs) - requiredTraits, forbiddenTraits := nova.SplitTraits(flavorExtraSpecs) + requiredTraits, forbiddenTraits := compute.SplitTraits(flavorExtraSpecs) evacuateOpts := DefaultFailoverOptions evacuateOpts.ReadOnly = true evacuateOpts.LockReservations = true @@ -282,7 +281,7 @@ func (c *FailoverReservationController) validateVMViaSchedulerEvacuation( RequiredTraits: requiredTraits, ForbiddenTraits: forbiddenTraits, RequestedCapabilities: reservations.HypervisorTypeCapability(flavorExtraSpecs, true), - AcceleratorsRequested: nova.RequestsAccelerators(flavorExtraSpecs), + AcceleratorsRequested: compute.RequestsAccelerators(flavorExtraSpecs), Flavor: compute.FlavorSpec{ VCPUs: vcpus, MemoryMB: memoryMB, diff --git a/internal/scheduling/reservations/failoverhost/pathfinding.go b/internal/scheduling/reservations/failoverhost/pathfinding.go index 2e592d18e..e61b6fde7 100644 --- a/internal/scheduling/reservations/failoverhost/pathfinding.go +++ b/internal/scheduling/reservations/failoverhost/pathfinding.go @@ -15,7 +15,6 @@ import ( "github.com/cobaltcore-dev/cortex/api/scheduling" "github.com/cobaltcore-dev/cortex/api/v1alpha1" "github.com/cobaltcore-dev/cortex/internal/scheduling/compute" - "github.com/cobaltcore-dev/cortex/internal/scheduling/nova" "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations" "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations/failover" hv1 "github.com/cobaltcore-dev/openstack-hypervisor-operator/api/v1" @@ -254,7 +253,7 @@ func (f *evacuationPathFinder) fetchSeedCandidates(ctx context.Context, seed See // considers compatible hosts (filter_has_requested_traits/filter_capabilities // would otherwise see empty slices and become no-ops). The seeder only emits // seeds whose flavor names a supported hypervisor type, so no default is needed. - requiredTraits, forbiddenTraits := nova.SplitTraits(seed.Flavor.ExtraSpecs) + requiredTraits, forbiddenTraits := compute.SplitTraits(seed.Flavor.ExtraSpecs) req := compute.PlacementRequest{ // Same HANA vs general-purpose split as regular failover placement. Pipeline: failover.InferFailoverPipeline(seed.Flavor.ExtraSpecs), @@ -282,7 +281,7 @@ func (f *evacuationPathFinder) fetchSeedCandidates(ctx context.Context, seed See // seeds whose flavor names a supported hypervisor type, so this is always // set (no default needed). RequestedCapabilities: reservations.HypervisorTypeCapability(seed.Flavor.ExtraSpecs, false), - AcceleratorsRequested: nova.RequestsAccelerators(seed.Flavor.ExtraSpecs), + AcceleratorsRequested: compute.RequestsAccelerators(seed.Flavor.ExtraSpecs), Flavor: compute.FlavorSpec{ VCPUs: seed.Flavor.VCPUs, MemoryMB: seed.Flavor.MemoryMB, diff --git a/internal/scheduling/reservations/accounting/flavor_groups.go b/internal/scheduling/reservations/flavor_groups.go similarity index 90% rename from internal/scheduling/reservations/accounting/flavor_groups.go rename to internal/scheduling/reservations/flavor_groups.go index 77a36d20c..f467cc665 100644 --- a/internal/scheduling/reservations/accounting/flavor_groups.go +++ b/internal/scheduling/reservations/flavor_groups.go @@ -1,11 +1,11 @@ // Copyright SAP SE // SPDX-License-Identifier: Apache-2.0 -// Package accounting holds flavor-group lookup over Knowledge CRDs, with no -// dependency on the scheduling pipeline, so it can live below both the reservation -// controllers and the Nova scheduler tree without forming an import cycle. The -// per-host capacity math lives in the sibling capacity package. -package accounting +// flavor_groups.go holds flavor-group lookup over Knowledge CRDs. It lives in the +// reservations package because every consumer (the reservation controllers and the +// Nova CR recorder) already depends on it; the per-host capacity math lives in the +// separate lib/capacity package. +package reservations import ( "context" diff --git a/internal/scheduling/reservations/quota/controller.go b/internal/scheduling/reservations/quota/controller.go index 46ea8dd1e..e6d6dcf0e 100644 --- a/internal/scheduling/reservations/quota/controller.go +++ b/internal/scheduling/reservations/quota/controller.go @@ -13,7 +13,6 @@ import ( "github.com/cobaltcore-dev/cortex/api/v1alpha1" "github.com/cobaltcore-dev/cortex/internal/knowledge/extractor/plugins/compute" "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations" - "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations/accounting" commitments "github.com/cobaltcore-dev/cortex/internal/scheduling/reservations/commitments" hv1 "github.com/cobaltcore-dev/openstack-hypervisor-operator/api/v1" "k8s.io/apimachinery/pkg/api/meta" @@ -75,7 +74,7 @@ func (c *QuotaController) ReconcilePeriodic(ctx context.Context) error { logger.Info("starting full quota reconcile") // Fetch flavor groups from Knowledge CRD - flavorGroupClient := &accounting.FlavorGroupKnowledgeClient{Client: c.Client} + flavorGroupClient := &reservations.FlavorGroupKnowledgeClient{Client: c.Client} flavorGroups, err := flavorGroupClient.GetAllFlavorGroups(ctx, nil) if err != nil { logger.Error(err, "failed to get flavor groups") @@ -229,7 +228,7 @@ func (c *QuotaController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl } // Fetch flavor groups for CRUsage computation - flavorGroupClient := &accounting.FlavorGroupKnowledgeClient{Client: c.Client} + flavorGroupClient := &reservations.FlavorGroupKnowledgeClient{Client: c.Client} flavorGroups, err := flavorGroupClient.GetAllFlavorGroups(ctx, nil) if err != nil { logger.Error(err, "failed to get flavor groups") @@ -268,7 +267,7 @@ func (c *QuotaController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl // a ProjectQuota is first created and has no persisted TotalUsage yet. func (c *QuotaController) computeTotalUsageForProject(ctx context.Context, projectID string) (map[string]map[string]int64, error) { // Fetch flavor groups from Knowledge CRD - flavorGroupClient := &accounting.FlavorGroupKnowledgeClient{Client: c.Client} + flavorGroupClient := &reservations.FlavorGroupKnowledgeClient{Client: c.Client} flavorGroups, err := flavorGroupClient.GetAllFlavorGroups(ctx, nil) if err != nil { return nil, fmt.Errorf("failed to get flavor groups: %w", err) @@ -363,7 +362,7 @@ func (c *QuotaController) ReconcileHVDiff(ctx context.Context, oldHV, newHV *hv1 logger.V(1).Info("HV instance diff detected", "added", len(added), "removed", len(removed)) // Get flavor groups for mapping - flavorGroupClient := &accounting.FlavorGroupKnowledgeClient{Client: c.Client} + flavorGroupClient := &reservations.FlavorGroupKnowledgeClient{Client: c.Client} flavorGroups, err := flavorGroupClient.GetAllFlavorGroups(ctx, nil) if err != nil { logger.Error(err, "failed to get flavor groups for incremental update")