diff --git a/internal/scheduling/reservations/commitments/state.go b/internal/scheduling/reservations/commitments/state.go index ba8d78861..e28e16472 100644 --- a/internal/scheduling/reservations/commitments/state.go +++ b/internal/scheduling/reservations/commitments/state.go @@ -136,8 +136,12 @@ type CommitmentState struct { } // FromCommitment converts Limes commitment to CommitmentState. +// ramUnitMiB is the size of one external RAM unit in MiB, as returned by FlavorGroupFeature.RAMUnitMiB(): +// - fixed-ratio flavor groups: SmallestFlavor.MemoryMB (1 unit = 1 smallest-flavor slot) +// - variable-ratio flavor groups: 1024 (1 unit = 1 GiB) func FromCommitment( commitment Commitment, + ramUnitMiB uint64, ) (*CommitmentState, error) { // Validate commitment UUID format if !commitmentUUIDPattern.MatchString(commitment.UUID) { @@ -149,9 +153,8 @@ func FromCommitment( return nil, err } - // Calculate total memory from commitment amount (1 GiB per unit) - const gibInBytes = int64(1) << 30 - totalMemoryBytes := int64(commitment.Amount) * gibInBytes //nolint:gosec // commitment amount from Limes API, bounded by quota limits + // Convert external unit to bytes: 1 unit = ramUnitMiB MiB (mirrors FromChangeCommitmentTargetState). + totalMemoryBytes := int64(commitment.Amount) * int64(ramUnitMiB) * (1 << 20) //nolint:gosec // bounded by quota limits // Set start time: use ConfirmedAt if available, otherwise CreatedAt var startTime *time.Time diff --git a/internal/scheduling/reservations/commitments/state_test.go b/internal/scheduling/reservations/commitments/state_test.go index 4fceccebe..3b1f4734e 100644 --- a/internal/scheduling/reservations/commitments/state_test.go +++ b/internal/scheduling/reservations/commitments/state_test.go @@ -36,10 +36,11 @@ func TestFromCommitment_CalculatesMemoryCorrectly(t *testing.T) { UUID: "test-uuid", ProjectID: "project-1", ResourceName: "hw_version_test-group_ram", - Amount: 5, // 5 multiples of smallest flavor + Amount: 5, } - state, err := FromCommitment(commitment) + // Variable-ratio group: RAMUnitMiB = 1024 (1 GiB per unit) + state, err := FromCommitment(commitment, 1024) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -55,11 +56,22 @@ func TestFromCommitment_CalculatesMemoryCorrectly(t *testing.T) { t.Errorf("expected FlavorGroupName test-group, got %s", state.FlavorGroupName) } - // Verify memory calculation: 5 GiB = 5 * 1<<30 bytes + // 5 units × 1024 MiB = 5 GiB expectedMemory := int64(5) * (1 << 30) if state.TotalMemoryBytes != expectedMemory { t.Errorf("expected memory %d, got %d", expectedMemory, state.TotalMemoryBytes) } + + // Fixed-ratio group: RAMUnitMiB = 8192 (8 GiB per unit, e.g. SmallestFlavor.MemoryMB = 8192) + stateFixed, err := FromCommitment(commitment, 8192) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + // 5 units × 8192 MiB = 40 GiB + expectedFixed := int64(5) * 8192 * (1 << 20) + if stateFixed.TotalMemoryBytes != expectedFixed { + t.Errorf("fixed-ratio: expected memory %d, got %d", expectedFixed, stateFixed.TotalMemoryBytes) + } } func TestFromCommitment_InvalidResourceName(t *testing.T) { @@ -70,7 +82,7 @@ func TestFromCommitment_InvalidResourceName(t *testing.T) { Amount: 1, } - _, err := FromCommitment(commitment) + _, err := FromCommitment(commitment, 1024) if err == nil { t.Fatal("expected error for invalid resource name, got nil") } diff --git a/internal/scheduling/reservations/commitments/syncer.go b/internal/scheduling/reservations/commitments/syncer.go index 939d1cfc2..a64ff409e 100644 --- a/internal/scheduling/reservations/commitments/syncer.go +++ b/internal/scheduling/reservations/commitments/syncer.go @@ -141,18 +141,23 @@ func (s *Syncer) getCommitmentStates(ctx context.Context, log logr.Logger, flavo continue } - // Validate unit matches between Limes commitment and Cortex (1 GiB per unit) - expectedUnit := liquid.UnitGibibytes.String() // "GiB" + // Validate unit matches the unit declared in the LIQUID ServiceInfo for this flavor group. + // Variable-ratio groups declare "GiB"; fixed-ratio groups declare "N GiB" (SmallestFlavor.MemoryMB MiB). + ramUnitMiB := flavorGroup.RAMUnitMiB() + expectedLiquidUnit, err := liquid.UnitMebibytes.MultiplyBy(ramUnitMiB) + if err != nil { + log.Error(err, "failed to compute expected RAM unit for flavor group", + "flavorGroup", flavorGroupName, + "ramUnitMiB", ramUnitMiB) + continue + } + expectedUnit := expectedLiquidUnit.String() if commitment.Unit != "" && commitment.Unit != expectedUnit { - // Unit mismatch: Limes has not yet updated this commitment to the new unit. - // Skip this commitment - trust what Cortex already has stored in CRDs. - // On the next sync cycle after Limes updates, this will be processed. - log.V(0).Info("WARNING: skipping commitment due to unit mismatch - Limes unit differs from Cortex flavor group, waiting for Limes to update", + log.V(0).Info("WARNING: skipping commitment with unexpected unit", "commitmentUUID", commitment.UUID, "flavorGroup", flavorGroupName, "limesUnit", commitment.Unit, - "expectedUnit", expectedUnit, - "smallestFlavorMemoryMB", flavorGroup.SmallestFlavor.MemoryMB) + "expectedUnit", expectedUnit) if s.monitor != nil { s.monitor.RecordCommitmentSkipped(SkipReasonUnitMismatch) } @@ -174,7 +179,7 @@ func (s *Syncer) getCommitmentStates(ctx context.Context, log logr.Logger, flavo } // Convert commitment to state using FromCommitment - state, err := FromCommitment(commitment) + state, err := FromCommitment(commitment, ramUnitMiB) if err != nil { log.Error(err, "failed to convert commitment to state", "id", id,