From 6580343a6f592b1bb1c54399ecb15cb033ae9cf4 Mon Sep 17 00:00:00 2001 From: Malte Viering Date: Mon, 11 May 2026 11:36:16 +0000 Subject: [PATCH] feat(quota): track instance count (VM count) per project/AZ Add _instances resource tracking to the quota controller alongside the existing _ram and _cores resources. Each VM counts as 1 instance per project/AZ/flavorGroup. Changes: - computeTotalUsage: accumulate hw_version__instances (+1 per VM) - accumulateAddedVM: add instances delta (+1) for new VMs - accumulateRemovedVM: add instances decrement (-1) for deleted VMs - Add EnableHVDiff config flag (default: true) to disable incremental HV diff calculation when needed (e.g. for debugging or QA) - Update unit tests and integration tests with instances expectations Since there are no CommittedResources for instances, PaygUsage for instances naturally equals TotalUsage (no CR deduction needed). To disable the HV diff watcher, set enableHVDiff: false in the quota controller config. Only periodic full reconciles will update TotalUsage. --- .../scheduling/reservations/quota/config.go | 18 + .../reservations/quota/controller.go | 17 +- .../reservations/quota/controller_test.go | 20 +- .../reservations/quota/integration_test.go | 350 +++++++++++------- 4 files changed, 261 insertions(+), 144 deletions(-) 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}, }, }, },