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
18 changes: 18 additions & 0 deletions internal/scheduling/reservations/quota/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ type QuotaControllerConfig struct {
// CRStateFilter defines which CommittedResource states to include
// when summing cr_actual_usage. Default: ["confirmed", "guaranteed"]
CRStateFilter []v1alpha1.CommitmentStatus `json:"crStateFilter"`

// EnableHVDiff enables incremental TotalUsage updates via HV instance diffs.
// When false, only periodic full reconciles update TotalUsage (safer but slower convergence).
// Default: true.
EnableHVDiff *bool `json:"enableHVDiff,omitempty"`
}

// ApplyDefaults fills in any unset values with defaults.
Expand All @@ -30,15 +35,28 @@ func (c *QuotaControllerConfig) ApplyDefaults() {
if len(c.CRStateFilter) == 0 {
c.CRStateFilter = defaults.CRStateFilter
}
if c.EnableHVDiff == nil {
c.EnableHVDiff = defaults.EnableHVDiff
}
}

// IsHVDiffEnabled returns whether incremental HV diff is enabled.
func (c *QuotaControllerConfig) IsHVDiffEnabled() bool {
if c.EnableHVDiff == nil {
return true // default: enabled
}
return *c.EnableHVDiff
}

// DefaultQuotaControllerConfig returns a default configuration.
func DefaultQuotaControllerConfig() QuotaControllerConfig {
enableHVDiff := true
return QuotaControllerConfig{
FullReconcileInterval: metav1.Duration{Duration: 5 * time.Minute},
CRStateFilter: []v1alpha1.CommitmentStatus{
v1alpha1.CommitmentStatusConfirmed,
v1alpha1.CommitmentStatusGuaranteed,
},
EnableHVDiff: &enableHVDiff,
}
}
17 changes: 16 additions & 1 deletion internal/scheduling/reservations/quota/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ func (c *QuotaController) accumulateAddedVM(

delta.addIncrement(commitments.ResourceNameRAM(groupName), vm.AvailabilityZone, ramUnits)
delta.addIncrement(commitments.ResourceNameCores(groupName), vm.AvailabilityZone, coresAmount)
delta.addIncrement(commitments.ResourceNameInstances(groupName), vm.AvailabilityZone, 1)
}

// isVMNewSinceLastReconcile checks if a VM was created after the last full reconcile.
Expand Down Expand Up @@ -536,6 +537,7 @@ func (c *QuotaController) accumulateRemovedVM(

delta.addDecrement(commitments.ResourceNameRAM(groupName), info.AvailabilityZone, ramUnits)
delta.addDecrement(commitments.ResourceNameCores(groupName), info.AvailabilityZone, coresAmount)
delta.addDecrement(commitments.ResourceNameInstances(groupName), info.AvailabilityZone, 1)
}

// applyDeltaAndUpdateStatus applies batched deltas to ALL per-AZ ProjectQuota CRDs for a project.
Expand Down Expand Up @@ -648,8 +650,12 @@ func (c *QuotaController) SetupWithManager(mgr ctrl.Manager) error {
}

// SetupHVWatcher sets up a separate controller to watch HV CRD changes
// for incremental TotalUsage updates.
// for incremental TotalUsage updates. Skipped if EnableHVDiff is false.
func (c *QuotaController) SetupHVWatcher(mgr ctrl.Manager) error {
if !c.Config.IsHVDiffEnabled() {
log.Info("HV diff watcher disabled by config (enableHVDiff=false)")
return nil
}
return ctrl.NewControllerManagedBy(mgr).
Named("quota-hv-watcher").
WatchesRawSource(source.Kind(
Expand Down Expand Up @@ -726,6 +732,7 @@ func (c *QuotaController) computeTotalUsage(

ramResourceName := commitments.ResourceNameRAM(groupName)
coresResourceName := commitments.ResourceNameCores(groupName)
instancesResourceName := commitments.ResourceNameInstances(groupName)

ramUnits, coresAmount := vmResourceUnits(vm.Resources)

Expand All @@ -748,6 +755,14 @@ func (c *QuotaController) computeTotalUsage(
}
coresUsage[vm.AvailabilityZone] += coresAmount
result[vm.ProjectID][coresResourceName] = coresUsage

// Accumulate instances usage for this project + AZ (1 per VM)
instancesUsage := result[vm.ProjectID][instancesResourceName]
if instancesUsage == nil {
instancesUsage = make(map[string]int64)
}
instancesUsage[vm.AvailabilityZone]++
result[vm.ProjectID][instancesResourceName] = instancesUsage
}

return result
Expand Down
20 changes: 17 additions & 3 deletions internal/scheduling/reservations/quota/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ func TestComputeTotalUsage(t *testing.T) {

result := ctrl.computeTotalUsage(vms, flavorToGroup, flavorGroups)

// project-a: hana_v2 in az-1: (32768+65536)/1024 = 96 GiB RAM, 8+16=24 cores
// project-a: hana_v2 in az-2: 32768/1024 = 32 GiB RAM, 8 cores
// project-a: hana_v2 in az-1: (32768+65536)/1024 = 96 GiB RAM, 8+16=24 cores, 2 instances
// project-a: hana_v2 in az-2: 32768/1024 = 32 GiB RAM, 8 cores, 1 instance
projectA := result["project-a"]
if projectA == nil {
t.Fatal("expected project-a in results")
Expand All @@ -116,7 +116,15 @@ func TestComputeTotalUsage(t *testing.T) {
t.Errorf("expected project-a az-2 hana_v2_cores = 8, got %d", coresUsage["az-2"])
}

// project-b: general in az-1: 4096/1024 = 4 GiB RAM, 2 cores
instancesUsage := projectA["hw_version_hana_v2_instances"]
if instancesUsage["az-1"] != 2 {
t.Errorf("expected project-a az-1 hana_v2_instances = 2, got %d", instancesUsage["az-1"])
}
if instancesUsage["az-2"] != 1 {
t.Errorf("expected project-a az-2 hana_v2_instances = 1, got %d", instancesUsage["az-2"])
}

// project-b: general in az-1: 4096/1024 = 4 GiB RAM, 2 cores, 1 instance
projectB := result["project-b"]
if projectB == nil {
t.Fatal("expected project-b in results")
Expand All @@ -127,6 +135,9 @@ func TestComputeTotalUsage(t *testing.T) {
if projectB["hw_version_general_cores"]["az-1"] != 2 {
t.Errorf("expected project-b az-1 general_cores = 2, got %d", projectB["hw_version_general_cores"]["az-1"])
}
if projectB["hw_version_general_instances"]["az-1"] != 1 {
t.Errorf("expected project-b az-1 general_instances = 1, got %d", projectB["hw_version_general_instances"]["az-1"])
}

// project-c: unknown flavor → not in results
if _, exists := result["project-c"]; exists {
Expand Down Expand Up @@ -560,6 +571,9 @@ func TestAccumulateAddedVM_KnownFlavor(t *testing.T) {
if delta.increments["hw_version_hana_v2_cores"]["az-1"] != 8 {
t.Errorf("expected cores increment = 8, got %d", delta.increments["hw_version_hana_v2_cores"]["az-1"])
}
if delta.increments["hw_version_hana_v2_instances"]["az-1"] != 1 {
t.Errorf("expected instances increment = 1, got %d", delta.increments["hw_version_hana_v2_instances"]["az-1"])
}
}

// mockVMSource is a test helper for VMSource.
Expand Down
Loading