diff --git a/internal/scheduling/reservations/quota/config.go b/internal/scheduling/reservations/quota/config.go index b7314f595..706936872 100644 --- a/internal/scheduling/reservations/quota/config.go +++ b/internal/scheduling/reservations/quota/config.go @@ -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. @@ -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, } } diff --git a/internal/scheduling/reservations/quota/controller.go b/internal/scheduling/reservations/quota/controller.go index f374080bc..2aff199e3 100644 --- a/internal/scheduling/reservations/quota/controller.go +++ b/internal/scheduling/reservations/quota/controller.go @@ -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. @@ -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. @@ -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( @@ -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) @@ -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 diff --git a/internal/scheduling/reservations/quota/controller_test.go b/internal/scheduling/reservations/quota/controller_test.go index b5b724647..2becea2db 100644 --- a/internal/scheduling/reservations/quota/controller_test.go +++ b/internal/scheduling/reservations/quota/controller_test.go @@ -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") @@ -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") @@ -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 { @@ -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. diff --git a/internal/scheduling/reservations/quota/integration_test.go b/internal/scheduling/reservations/quota/integration_test.go index dbe174f69..d960ab396 100644 --- a/internal/scheduling/reservations/quota/integration_test.go +++ b/internal/scheduling/reservations/quota/integration_test.go @@ -47,27 +47,33 @@ func TestIntegration(t *testing.T) { // project-b: general az-1: 4096/1024 = 4 GiB, 2 cores ExpectedTotalUsage: map[string]map[string]map[string]int64{ "project-a": { - "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, - "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, + "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, + "hw_version_hana_v2_instances": {"az-1": 2, "az-2": 1}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, "project-b": { - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, }, // No CRs -> PaygUsage == TotalUsage ExpectedPaygUsage: map[string]map[string]map[string]int64{ "project-a": { - "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, - "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, + "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, + "hw_version_hana_v2_instances": {"az-1": 2, "az-2": 1}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, "project-b": { - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, }, }, @@ -94,10 +100,12 @@ func TestIntegration(t *testing.T) { Type: "full_reconcile", ExpectedTotalUsage: map[string]map[string]map[string]int64{ "project-a": { - "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, - "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, + "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, + "hw_version_hana_v2_instances": {"az-1": 2, "az-2": 1}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, }, // PaygUsage = TotalUsage - CRUsage @@ -106,10 +114,12 @@ func TestIntegration(t *testing.T) { // general: no CRs so PaygUsage == TotalUsage ExpectedPaygUsage: map[string]map[string]map[string]int64{ "project-a": { - "hw_version_hana_v2_ram": {"az-1": 94, "az-2": 32}, - "hw_version_hana_v2_cores": {"az-1": 14, "az-2": 8}, - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_hana_v2_ram": {"az-1": 94, "az-2": 32}, + "hw_version_hana_v2_cores": {"az-1": 14, "az-2": 8}, + "hw_version_hana_v2_instances": {"az-1": 2, "az-2": 1}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, }, }, @@ -129,10 +139,12 @@ func TestIntegration(t *testing.T) { Type: "full_reconcile", ExpectedTotalUsage: map[string]map[string]map[string]int64{ "project-a": { - "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, - "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, + "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, + "hw_version_hana_v2_instances": {"az-1": 2, "az-2": 1}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, }, }, @@ -163,10 +175,12 @@ func TestIntegration(t *testing.T) { // +32 GiB RAM (32768/1024), +8 cores ExpectedTotalUsage: map[string]map[string]map[string]int64{ "project-a": { - "hw_version_hana_v2_ram": {"az-1": 128, "az-2": 32}, - "hw_version_hana_v2_cores": {"az-1": 32, "az-2": 8}, - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_hana_v2_ram": {"az-1": 128, "az-2": 32}, + "hw_version_hana_v2_cores": {"az-1": 32, "az-2": 8}, + "hw_version_hana_v2_instances": {"az-1": 3, "az-2": 1}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, }, }, @@ -186,10 +200,12 @@ func TestIntegration(t *testing.T) { Type: "full_reconcile", ExpectedTotalUsage: map[string]map[string]map[string]int64{ "project-a": { - "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, - "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, + "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, + "hw_version_hana_v2_instances": {"az-1": 2, "az-2": 1}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, }, }, @@ -203,10 +219,12 @@ func TestIntegration(t *testing.T) { // Should NOT increment -- vm-1 CreatedAt is 2025-12-01 which is before reconcile time ExpectedTotalUsage: map[string]map[string]map[string]int64{ "project-a": { - "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, - "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, + "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, + "hw_version_hana_v2_instances": {"az-1": 2, "az-2": 1}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, }, }, @@ -239,10 +257,12 @@ func TestIntegration(t *testing.T) { Type: "full_reconcile", ExpectedTotalUsage: map[string]map[string]map[string]int64{ "project-a": { - "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, - "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, + "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, + "hw_version_hana_v2_instances": {"az-1": 2, "az-2": 1}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, }, }, @@ -263,10 +283,12 @@ func TestIntegration(t *testing.T) { // Decrement: -32 GiB RAM, -8 cores in az-1 ExpectedTotalUsage: map[string]map[string]map[string]int64{ "project-a": { - "hw_version_hana_v2_ram": {"az-1": 64, "az-2": 32}, - "hw_version_hana_v2_cores": {"az-1": 16, "az-2": 8}, - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_hana_v2_ram": {"az-1": 64, "az-2": 32}, + "hw_version_hana_v2_cores": {"az-1": 16, "az-2": 8}, + "hw_version_hana_v2_instances": {"az-1": 1, "az-2": 1}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, }, }, @@ -288,10 +310,12 @@ func TestIntegration(t *testing.T) { Type: "full_reconcile", ExpectedTotalUsage: map[string]map[string]map[string]int64{ "project-a": { - "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, - "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, + "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, + "hw_version_hana_v2_instances": {"az-1": 2, "az-2": 1}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, }, }, @@ -309,10 +333,12 @@ func TestIntegration(t *testing.T) { // vm-1: IsServerActive=true, so NOT decremented ExpectedTotalUsage: map[string]map[string]map[string]int64{ "project-a": { - "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, - "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, + "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, + "hw_version_hana_v2_instances": {"az-1": 2, "az-2": 1}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, }, }, @@ -336,10 +362,12 @@ func TestIntegration(t *testing.T) { Type: "full_reconcile", ExpectedPaygUsage: map[string]map[string]map[string]int64{ "project-a": { - "hw_version_hana_v2_ram": {"az-1": 95, "az-2": 32}, // 96-1=95 - "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_hana_v2_ram": {"az-1": 95, "az-2": 32}, // 96-1=95 + "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, + "hw_version_hana_v2_instances": {"az-1": 2, "az-2": 1}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, }, }, @@ -350,10 +378,12 @@ func TestIntegration(t *testing.T) { UsedAmount: 3, ExpectedPaygUsage: map[string]map[string]map[string]int64{ "project-a": { - "hw_version_hana_v2_ram": {"az-1": 93, "az-2": 32}, // 96-3=93 - "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_hana_v2_ram": {"az-1": 93, "az-2": 32}, // 96-3=93 + "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, + "hw_version_hana_v2_instances": {"az-1": 2, "az-2": 1}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, }, }, @@ -402,14 +432,17 @@ func TestIntegration(t *testing.T) { Type: "full_reconcile", ExpectedTotalUsage: map[string]map[string]map[string]int64{ "project-a": { - "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, - "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, + "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, + "hw_version_hana_v2_instances": {"az-1": 2, "az-2": 1}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, "project-b": { - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, }, }, @@ -418,14 +451,17 @@ func TestIntegration(t *testing.T) { Type: "full_reconcile", ExpectedTotalUsage: map[string]map[string]map[string]int64{ "project-a": { - "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, - "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, + "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, + "hw_version_hana_v2_instances": {"az-1": 2, "az-2": 1}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, "project-b": { - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, }, }, @@ -450,10 +486,12 @@ func TestIntegration(t *testing.T) { // PaygUsage == TotalUsage because pending CRs are excluded ExpectedPaygUsage: map[string]map[string]map[string]int64{ "project-a": { - "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, - "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, + "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, + "hw_version_hana_v2_instances": {"az-1": 2, "az-2": 1}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, }, }, @@ -473,10 +511,12 @@ func TestIntegration(t *testing.T) { Type: "full_reconcile", ExpectedTotalUsage: map[string]map[string]map[string]int64{ "project-a": { - "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, - "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, + "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, + "hw_version_hana_v2_instances": {"az-1": 2, "az-2": 1}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, }, }, @@ -508,10 +548,12 @@ func TestIntegration(t *testing.T) { // TotalUsage now has phantom's contribution (drift) ExpectedTotalUsage: map[string]map[string]map[string]int64{ "project-a": { - "hw_version_hana_v2_ram": {"az-1": 128, "az-2": 32}, // 96+32 drift - "hw_version_hana_v2_cores": {"az-1": 32, "az-2": 8}, // 24+8 drift - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_hana_v2_ram": {"az-1": 128, "az-2": 32}, // 96+32 drift + "hw_version_hana_v2_cores": {"az-1": 32, "az-2": 8}, // 24+8 drift + "hw_version_hana_v2_instances": {"az-1": 3, "az-2": 1}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, }, }, @@ -522,10 +564,12 @@ func TestIntegration(t *testing.T) { OverrideVMs: baseVMsPtr(), ExpectedTotalUsage: map[string]map[string]map[string]int64{ "project-a": { - "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, // corrected - "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, // corrected - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, // corrected + "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, // corrected + "hw_version_hana_v2_instances": {"az-1": 2, "az-2": 1}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, }, }, @@ -561,14 +605,17 @@ func TestIntegration(t *testing.T) { Type: "full_reconcile", ExpectedTotalUsage: map[string]map[string]map[string]int64{ "project-a": { - "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, - "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, + "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, + "hw_version_hana_v2_instances": {"az-1": 2, "az-2": 1}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, "project-b": { - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, }, }, @@ -598,10 +645,12 @@ func TestIntegration(t *testing.T) { ), ExpectedTotalUsage: map[string]map[string]map[string]int64{ "project-a": { - "hw_version_hana_v2_ram": {"az-1": 128, "az-2": 32}, - "hw_version_hana_v2_cores": {"az-1": 32, "az-2": 8}, - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_hana_v2_ram": {"az-1": 128, "az-2": 32}, + "hw_version_hana_v2_cores": {"az-1": 32, "az-2": 8}, + "hw_version_hana_v2_instances": {"az-1": 3, "az-2": 1}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, }, }, @@ -629,8 +678,9 @@ func TestIntegration(t *testing.T) { ), ExpectedTotalUsage: map[string]map[string]map[string]int64{ "project-b": { - "hw_version_general_ram": {"az-1": 8}, // 4+4 drift - "hw_version_general_cores": {"az-1": 4}, // 2+2 drift + "hw_version_general_ram": {"az-1": 8}, // 4+4 drift + "hw_version_general_cores": {"az-1": 4}, // 2+2 drift + "hw_version_general_instances": {"az-1": 2}, // 1+1 drift }, }, }, @@ -663,10 +713,12 @@ func TestIntegration(t *testing.T) { ), ExpectedTotalUsage: map[string]map[string]map[string]int64{ "project-a": { - "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, // 128-32=96 - "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, // 32-8=24 - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, // 128-32=96 + "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, // 32-8=24 + "hw_version_hana_v2_instances": {"az-1": 2, "az-2": 1}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, }, }, @@ -692,14 +744,17 @@ func TestIntegration(t *testing.T) { }, ExpectedTotalUsage: map[string]map[string]map[string]int64{ "project-a": { - "hw_version_hana_v2_ram": {"az-1": 128, "az-2": 32}, // corrected up - "hw_version_hana_v2_cores": {"az-1": 32, "az-2": 8}, // corrected up - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_hana_v2_ram": {"az-1": 128, "az-2": 32}, // corrected up + "hw_version_hana_v2_cores": {"az-1": 32, "az-2": 8}, // corrected up + "hw_version_hana_v2_instances": {"az-1": 3, "az-2": 1}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, "project-b": { - "hw_version_general_ram": {"az-1": 4}, // corrected down - "hw_version_general_cores": {"az-1": 2}, // corrected down + "hw_version_general_ram": {"az-1": 4}, // corrected down + "hw_version_general_cores": {"az-1": 2}, // corrected down + "hw_version_general_instances": {"az-1": 1}, }, }, }, @@ -730,10 +785,12 @@ func TestIntegration(t *testing.T) { // vm-1 migrated, NOT decremented -- totals unchanged ExpectedTotalUsage: map[string]map[string]map[string]int64{ "project-a": { - "hw_version_hana_v2_ram": {"az-1": 128, "az-2": 32}, - "hw_version_hana_v2_cores": {"az-1": 32, "az-2": 8}, - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_hana_v2_ram": {"az-1": 128, "az-2": 32}, + "hw_version_hana_v2_cores": {"az-1": 32, "az-2": 8}, + "hw_version_hana_v2_instances": {"az-1": 3, "az-2": 1}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, }, }, @@ -743,14 +800,17 @@ func TestIntegration(t *testing.T) { Type: "full_reconcile", ExpectedTotalUsage: map[string]map[string]map[string]int64{ "project-a": { - "hw_version_hana_v2_ram": {"az-1": 128, "az-2": 32}, - "hw_version_hana_v2_cores": {"az-1": 32, "az-2": 8}, - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_hana_v2_ram": {"az-1": 128, "az-2": 32}, + "hw_version_hana_v2_cores": {"az-1": 32, "az-2": 8}, + "hw_version_hana_v2_instances": {"az-1": 3, "az-2": 1}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, "project-b": { - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, }, }, @@ -770,18 +830,22 @@ func TestIntegration(t *testing.T) { // Only az-1 data should be written (az-2 CRD doesn't exist) ExpectedTotalUsage: map[string]map[string]map[string]int64{ "project-a": { - "hw_version_hana_v2_ram": {"az-1": 96}, - "hw_version_hana_v2_cores": {"az-1": 24}, - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_hana_v2_ram": {"az-1": 96}, + "hw_version_hana_v2_cores": {"az-1": 24}, + "hw_version_hana_v2_instances": {"az-1": 2}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, }, ExpectedPaygUsage: map[string]map[string]map[string]int64{ "project-a": { - "hw_version_hana_v2_ram": {"az-1": 96}, - "hw_version_hana_v2_cores": {"az-1": 24}, - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_hana_v2_ram": {"az-1": 96}, + "hw_version_hana_v2_cores": {"az-1": 24}, + "hw_version_hana_v2_instances": {"az-1": 2}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, }, }, @@ -812,14 +876,17 @@ func TestIntegration(t *testing.T) { // Verify TotalUsage is correctly computed from VMs ExpectedTotalUsage: map[string]map[string]map[string]int64{ "project-a": { - "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, - "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_hana_v2_ram": {"az-1": 96, "az-2": 32}, + "hw_version_hana_v2_cores": {"az-1": 24, "az-2": 8}, + "hw_version_hana_v2_instances": {"az-1": 2, "az-2": 1}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, "project-b": { - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, }, // Verify PaygUsage = TotalUsage - CRUsage per AZ @@ -827,14 +894,17 @@ func TestIntegration(t *testing.T) { // az-2: hana_v2_ram: 32-3=29, hana_v2_cores: 8-0=8 ExpectedPaygUsage: map[string]map[string]map[string]int64{ "project-a": { - "hw_version_hana_v2_ram": {"az-1": 91, "az-2": 29}, - "hw_version_hana_v2_cores": {"az-1": 20, "az-2": 8}, - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_hana_v2_ram": {"az-1": 91, "az-2": 29}, + "hw_version_hana_v2_cores": {"az-1": 20, "az-2": 8}, + "hw_version_hana_v2_instances": {"az-1": 2, "az-2": 1}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, "project-b": { - "hw_version_general_ram": {"az-1": 4}, - "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_ram": {"az-1": 4}, + "hw_version_general_cores": {"az-1": 2}, + "hw_version_general_instances": {"az-1": 1}, }, }, },