From 61333ed157105acaef1ef560d0af01b78beb11d6 Mon Sep 17 00:00:00 2001 From: Markus Wieland Date: Wed, 6 May 2026 11:01:34 +0200 Subject: [PATCH 1/8] refactor: rename resource commitments kpi --- helm/bundles/cortex-nova/templates/kpis.yaml | 5 ++-- ...ments.go => vmware_project_commitments.go} | 24 +++++++++---------- ....go => vmware_project_commitments_test.go} | 10 ++++---- internal/knowledge/kpis/supported_kpis.go | 6 ++--- 4 files changed, 23 insertions(+), 22 deletions(-) rename internal/knowledge/kpis/plugins/infrastructure/{vmware_resource_commitments.go => vmware_project_commitments.go} (89%) rename internal/knowledge/kpis/plugins/infrastructure/{vmware_resource_commitments_test.go => vmware_project_commitments_test.go} (98%) diff --git a/helm/bundles/cortex-nova/templates/kpis.yaml b/helm/bundles/cortex-nova/templates/kpis.yaml index 5ce7c41f2..7c70455d8 100644 --- a/helm/bundles/cortex-nova/templates/kpis.yaml +++ b/helm/bundles/cortex-nova/templates/kpis.yaml @@ -192,15 +192,16 @@ spec: apiVersion: cortex.cloud/v1alpha1 kind: KPI metadata: - name: vmware-resource-commitments + name: vmware-project-commitments spec: schedulingDomain: nova - impl: vmware_resource_commitments_kpi + impl: vmware_project_commitments_kpi dependencies: datasources: - name: nova-servers - name: nova-flavors - name: limes-project-commitments + - name: identity-domains description: | This KPI tracks the resource commitments of projects running VMs on VMware hosts. --- diff --git a/internal/knowledge/kpis/plugins/infrastructure/vmware_resource_commitments.go b/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments.go similarity index 89% rename from internal/knowledge/kpis/plugins/infrastructure/vmware_resource_commitments.go rename to internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments.go index 0d3d5d3ed..41ad70a55 100644 --- a/internal/knowledge/kpis/plugins/infrastructure/vmware_resource_commitments.go +++ b/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments.go @@ -24,7 +24,7 @@ import ( // For general purpose workloads its not possible to differentiate the cpu architecture. To avoid weird behavior in a dashboard we don't export this label for the metric. // For HANA flavors the cpu architecture is part of the flavor name (_v2 suffix for sapphire rapids, without suffix for cascade lake). // For both types of workload however we can not determine on which host the commitment is fulfilled. -type VMwareResourceCommitmentsKPI struct { +type VMwareProjectCommitmentsKPI struct { // BaseKPI provides common fields and methods for all KPIs, such as database connection and Kubernetes client. plugins.BaseKPI[struct{}] @@ -32,11 +32,11 @@ type VMwareResourceCommitmentsKPI struct { unusedHanaCommittedResourcesPerProject *prometheus.Desc } -func (k *VMwareResourceCommitmentsKPI) GetName() string { +func (k *VMwareProjectCommitmentsKPI) GetName() string { return "vmware_resource_commitments_kpi" } -func (k *VMwareResourceCommitmentsKPI) Init(dbConn *db.DB, c client.Client, opts conf.RawOpts) error { +func (k *VMwareProjectCommitmentsKPI) Init(dbConn *db.DB, c client.Client, opts conf.RawOpts) error { if err := k.BaseKPI.Init(dbConn, c, opts); err != nil { return err } @@ -54,12 +54,12 @@ func (k *VMwareResourceCommitmentsKPI) Init(dbConn *db.DB, c client.Client, opts return nil } -func (k *VMwareResourceCommitmentsKPI) Describe(ch chan<- *prometheus.Desc) { +func (k *VMwareProjectCommitmentsKPI) Describe(ch chan<- *prometheus.Desc) { ch <- k.unusedGeneralPurposeCommitmentsPerProject ch <- k.unusedHanaCommittedResourcesPerProject } -func (k *VMwareResourceCommitmentsKPI) Collect(ch chan<- prometheus.Metric) { +func (k *VMwareProjectCommitmentsKPI) Collect(ch chan<- prometheus.Metric) { if k.DB == nil { return } @@ -75,7 +75,7 @@ func (k *VMwareResourceCommitmentsKPI) Collect(ch chan<- prometheus.Metric) { } // getFlavorsByName loads all flavors and returns them keyed by name. -func (k *VMwareResourceCommitmentsKPI) getFlavorsByName() (map[string]nova.Flavor, error) { +func (k *VMwareProjectCommitmentsKPI) getFlavorsByName() (map[string]nova.Flavor, error) { var flavors []nova.Flavor if _, err := k.DB.Select(&flavors, "SELECT * FROM "+nova.Flavor{}.TableName()); err != nil { return nil, err @@ -88,7 +88,7 @@ func (k *VMwareResourceCommitmentsKPI) getFlavorsByName() (map[string]nova.Flavo } // getGeneralPurposeCommitments loads confirmed/guaranteed cores and ram commitments. -func (k *VMwareResourceCommitmentsKPI) getGeneralPurposeCommitments() ([]limes.Commitment, error) { +func (k *VMwareProjectCommitmentsKPI) getGeneralPurposeCommitments() ([]limes.Commitment, error) { var commitments []limes.Commitment if _, err := k.DB.Select(&commitments, ` SELECT * FROM `+limes.Commitment{}.TableName()+` @@ -103,7 +103,7 @@ func (k *VMwareResourceCommitmentsKPI) getGeneralPurposeCommitments() ([]limes.C // getGeneralPurposeServers loads running non-HANA servers for general purpose usage accounting. // KVM-specific flavors are filtered out in Go since SQL LIKE cannot express the segment-exact pattern. -func (k *VMwareResourceCommitmentsKPI) getGeneralPurposeServers() ([]nova.Server, error) { +func (k *VMwareProjectCommitmentsKPI) getGeneralPurposeServers() ([]nova.Server, error) { var servers []nova.Server if _, err := k.DB.Select(&servers, ` SELECT * FROM `+nova.Server{}.TableName()+` @@ -122,7 +122,7 @@ func (k *VMwareResourceCommitmentsKPI) getGeneralPurposeServers() ([]nova.Server } // getHanaInstanceCommitments loads confirmed/guaranteed HANA instance commitments. -func (k *VMwareResourceCommitmentsKPI) getHanaInstanceCommitments() ([]limes.Commitment, error) { +func (k *VMwareProjectCommitmentsKPI) getHanaInstanceCommitments() ([]limes.Commitment, error) { var commitments []limes.Commitment if _, err := k.DB.Select(&commitments, ` SELECT * FROM `+limes.Commitment{}.TableName()+` @@ -136,7 +136,7 @@ func (k *VMwareResourceCommitmentsKPI) getHanaInstanceCommitments() ([]limes.Com } // getRunningHanaServers loads all running HANA VMware servers (KVM HANA flavors excluded in Go). -func (k *VMwareResourceCommitmentsKPI) getRunningHanaServers() ([]nova.Server, error) { +func (k *VMwareProjectCommitmentsKPI) getRunningHanaServers() ([]nova.Server, error) { var servers []nova.Server if _, err := k.DB.Select(&servers, ` SELECT * FROM `+nova.Server{}.TableName()+` @@ -156,7 +156,7 @@ func (k *VMwareResourceCommitmentsKPI) getRunningHanaServers() ([]nova.Server, e // collectGeneralPurpose computes and emits unused general purpose committed resources per project. // Unused = committed - in-use (clamped to zero; zero values are not emitted). -func (k *VMwareResourceCommitmentsKPI) collectGeneralPurpose(ch chan<- prometheus.Metric, flavorsByName map[string]nova.Flavor) { +func (k *VMwareProjectCommitmentsKPI) collectGeneralPurpose(ch chan<- prometheus.Metric, flavorsByName map[string]nova.Flavor) { commitments, err := k.getGeneralPurposeCommitments() if err != nil { slog.Error("vmware_resource_commitments: failed to load gp commitments", "err", err) @@ -213,7 +213,7 @@ func (k *VMwareResourceCommitmentsKPI) collectGeneralPurpose(ch chan<- prometheu // collectHana computes and emits unused committed HANA instance resources per project. // Each HANA instance commitment is compared against running servers; the remainder is // translated to cpu/ram/disk capacity using the flavor spec. -func (k *VMwareResourceCommitmentsKPI) collectHana(ch chan<- prometheus.Metric, flavorsByName map[string]nova.Flavor) { +func (k *VMwareProjectCommitmentsKPI) collectHana(ch chan<- prometheus.Metric, flavorsByName map[string]nova.Flavor) { commitments, err := k.getHanaInstanceCommitments() if err != nil { slog.Error("vmware_resource_commitments: failed to load hana commitments", "err", err) diff --git a/internal/knowledge/kpis/plugins/infrastructure/vmware_resource_commitments_test.go b/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments_test.go similarity index 98% rename from internal/knowledge/kpis/plugins/infrastructure/vmware_resource_commitments_test.go rename to internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments_test.go index 6616dc558..20746c16f 100644 --- a/internal/knowledge/kpis/plugins/infrastructure/vmware_resource_commitments_test.go +++ b/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments_test.go @@ -34,7 +34,7 @@ func setupResourceCommitmentsDB(t *testing.T) (testDB *db.DB, cleanup func()) { // segment since the descriptor does not include that label. func collectResourceCommitmentsMetrics(t *testing.T, testDB *db.DB) map[string]float64 { t.Helper() - kpi := &VMwareResourceCommitmentsKPI{} + kpi := &VMwareProjectCommitmentsKPI{} if err := kpi.Init(testDB, nil, conf.NewRawOpts("{}")); err != nil { t.Fatalf("failed to init KPI: %v", err) } @@ -70,16 +70,16 @@ func hKey(az, cpuArch, resource, projectID string) string { return "cortex_vmware_commitments_hana_resources|" + az + "|" + cpuArch + "|" + resource + "|" + projectID } -func TestVMwareResourceCommitmentsKPI_Init(t *testing.T) { +func TestVMwareProjectCommitmentsKPI_Init(t *testing.T) { dbEnv := testlibDB.SetupDBEnv(t) testDB := db.DB{DbMap: dbEnv.DbMap} defer dbEnv.Close() - kpi := &VMwareResourceCommitmentsKPI{} + kpi := &VMwareProjectCommitmentsKPI{} if err := kpi.Init(&testDB, nil, conf.NewRawOpts("{}")); err != nil { t.Fatalf("expected no error, got %v", err) } } -func TestVMwareResourceCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { +func TestVMwareProjectCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { tests := []struct { name string commitments []limes.Commitment @@ -303,7 +303,7 @@ func TestVMwareResourceCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { } } -func TestVMwareResourceCommitmentsKPI_Collect_HANA(t *testing.T) { +func TestVMwareProjectCommitmentsKPI_Collect_HANA(t *testing.T) { tests := []struct { name string commitments []limes.Commitment diff --git a/internal/knowledge/kpis/supported_kpis.go b/internal/knowledge/kpis/supported_kpis.go index 63a35866b..26f3ae0f7 100644 --- a/internal/knowledge/kpis/supported_kpis.go +++ b/internal/knowledge/kpis/supported_kpis.go @@ -23,9 +23,9 @@ var supportedKPIs = map[string]plugins.KPI{ "vm_commitments_kpi": &compute.VMCommitmentsKPI{}, "vm_faults_kpi": &compute.VMFaultsKPI{}, - "vmware_project_utilization_kpi": &infrastructure.VMwareProjectUtilizationKPI{}, - "vmware_resource_commitments_kpi": &infrastructure.VMwareResourceCommitmentsKPI{}, - "vmware_host_capacity_kpi": &infrastructure.VMwareHostCapacityKPI{}, + "vmware_project_utilization_kpi": &infrastructure.VMwareProjectUtilizationKPI{}, + "vmware_project_commitments_kpi": &infrastructure.VMwareProjectCommitmentsKPI{}, + "vmware_host_capacity_kpi": &infrastructure.VMwareHostCapacityKPI{}, "netapp_storage_pool_cpu_usage_kpi": &storage.NetAppStoragePoolCPUUsageKPI{}, From 7e840a316bd3e18c1874f4e7233c1beebacc408c Mon Sep 17 00:00:00 2001 From: Markus Wieland Date: Wed, 6 May 2026 11:34:23 +0200 Subject: [PATCH 2/8] feat: vmware project commitments now include domain_id --- .../vmware_project_commitments.go | 49 ++++- .../vmware_project_commitments_test.go | 196 ++++++++++++------ 2 files changed, 179 insertions(+), 66 deletions(-) diff --git a/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments.go b/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments.go index 41ad70a55..9154b271f 100644 --- a/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments.go +++ b/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments.go @@ -7,6 +7,7 @@ import ( "log/slog" "strings" + "github.com/cobaltcore-dev/cortex/internal/knowledge/datasources/plugins/openstack/identity" "github.com/cobaltcore-dev/cortex/internal/knowledge/datasources/plugins/openstack/limes" "github.com/cobaltcore-dev/cortex/internal/knowledge/datasources/plugins/openstack/nova" "github.com/cobaltcore-dev/cortex/internal/knowledge/db" @@ -44,12 +45,12 @@ func (k *VMwareProjectCommitmentsKPI) Init(dbConn *db.DB, c client.Client, opts k.unusedGeneralPurposeCommitmentsPerProject = prometheus.NewDesc( "cortex_vmware_commitments_general_purpose", "Committed general purpose resources that are currently unused. CPU (resource=cpu) in vCPUs, memory (resource=ram) in bytes.", - []string{"availability_zone", "resource", "project_id"}, nil, + []string{"availability_zone", "resource", "project_id", "project_name", "domain_id", "domain_name"}, nil, ) k.unusedHanaCommittedResourcesPerProject = prometheus.NewDesc( "cortex_vmware_commitments_hana_resources", "Total committed HANA instances capacity that is currently unused, translated to resources. CPU in vCPUs, memory and disk in bytes.", - []string{"availability_zone", "cpu_architecture", "resource", "project_id"}, nil, + []string{"availability_zone", "cpu_architecture", "resource", "project_id", "project_name", "domain_id", "domain_name"}, nil, ) return nil } @@ -70,8 +71,14 @@ func (k *VMwareProjectCommitmentsKPI) Collect(ch chan<- prometheus.Metric) { return } - k.collectGeneralPurpose(ch, flavorsByName) - k.collectHana(ch, flavorsByName) + projects, err := k.getProjectsWithDomains() + if err != nil { + slog.Error("vmware_resource_commitments: failed to load projects with domains", "err", err) + return + } + + k.collectGeneralPurpose(ch, flavorsByName, projects) + k.collectHana(ch, flavorsByName, projects) } // getFlavorsByName loads all flavors and returns them keyed by name. @@ -156,7 +163,7 @@ func (k *VMwareProjectCommitmentsKPI) getRunningHanaServers() ([]nova.Server, er // collectGeneralPurpose computes and emits unused general purpose committed resources per project. // Unused = committed - in-use (clamped to zero; zero values are not emitted). -func (k *VMwareProjectCommitmentsKPI) collectGeneralPurpose(ch chan<- prometheus.Metric, flavorsByName map[string]nova.Flavor) { +func (k *VMwareProjectCommitmentsKPI) collectGeneralPurpose(ch chan<- prometheus.Metric, flavorsByName map[string]nova.Flavor, projects map[string]projectWithDomain) { commitments, err := k.getGeneralPurposeCommitments() if err != nil { slog.Error("vmware_resource_commitments: failed to load gp commitments", "err", err) @@ -201,11 +208,12 @@ func (k *VMwareProjectCommitmentsKPI) collectGeneralPurpose(ch chan<- prometheus if unused <= 0 { continue } + project := projects[key.projectID] ch <- prometheus.MustNewConstMetric( k.unusedGeneralPurposeCommitmentsPerProject, prometheus.GaugeValue, unused, - key.az, key.resource, key.projectID, + key.az, key.resource, key.projectID, project.ProjectName, project.DomainID, project.DomainName, ) } } @@ -213,7 +221,7 @@ func (k *VMwareProjectCommitmentsKPI) collectGeneralPurpose(ch chan<- prometheus // collectHana computes and emits unused committed HANA instance resources per project. // Each HANA instance commitment is compared against running servers; the remainder is // translated to cpu/ram/disk capacity using the flavor spec. -func (k *VMwareProjectCommitmentsKPI) collectHana(ch chan<- prometheus.Metric, flavorsByName map[string]nova.Flavor) { +func (k *VMwareProjectCommitmentsKPI) collectHana(ch chan<- prometheus.Metric, flavorsByName map[string]nova.Flavor, projects map[string]projectWithDomain) { commitments, err := k.getHanaInstanceCommitments() if err != nil { slog.Error("vmware_resource_commitments: failed to load hana commitments", "err", err) @@ -261,11 +269,36 @@ func (k *VMwareProjectCommitmentsKPI) collectHana(ch chan<- prometheus.Metric, f } for key, value := range totals { + project := projects[key.projectID] ch <- prometheus.MustNewConstMetric( k.unusedHanaCommittedResourcesPerProject, prometheus.GaugeValue, value, - key.az, key.cpuArch, key.resource, key.projectID, + key.az, key.cpuArch, key.resource, key.projectID, project.ProjectName, project.DomainID, project.DomainName, ) } } + +type projectWithDomain struct { + ProjectID string `db:"project_id"` + ProjectName string `db:"project_name"` + DomainID string `db:"domain_id"` + DomainName string `db:"domain_name"` +} + +func (k *VMwareProjectCommitmentsKPI) getProjectsWithDomains() (map[string]projectWithDomain, error) { + var projects []projectWithDomain + if _, err := k.DB.Select(&projects, ` + SELECT p.id AS project_id, p.name AS project_name, COALESCE(d.id, '') AS domain_id, COALESCE(d.name, '') AS domain_name + FROM `+identity.Project{}.TableName()+` p + LEFT JOIN `+identity.Domain{}.TableName()+` d ON p.domain_id = d.id + `); err != nil { + return nil, err + } + + projectMap := make(map[string]projectWithDomain, len(projects)) + for _, p := range projects { + projectMap[p.ProjectID] = p + } + return projectMap, nil +} diff --git a/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments_test.go b/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments_test.go index 20746c16f..2d398922e 100644 --- a/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments_test.go +++ b/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments_test.go @@ -6,6 +6,7 @@ package infrastructure import ( "testing" + "github.com/cobaltcore-dev/cortex/internal/knowledge/datasources/plugins/openstack/identity" "github.com/cobaltcore-dev/cortex/internal/knowledge/datasources/plugins/openstack/limes" "github.com/cobaltcore-dev/cortex/internal/knowledge/datasources/plugins/openstack/nova" "github.com/cobaltcore-dev/cortex/internal/knowledge/db" @@ -23,6 +24,8 @@ func setupResourceCommitmentsDB(t *testing.T) (testDB *db.DB, cleanup func()) { testDB.AddTable(limes.Commitment{}), testDB.AddTable(nova.Server{}), testDB.AddTable(nova.Flavor{}), + testDB.AddTable(identity.Project{}), + testDB.AddTable(identity.Domain{}), ); err != nil { t.Fatalf("failed to create tables: %v", err) } @@ -30,8 +33,8 @@ func setupResourceCommitmentsDB(t *testing.T) (testDB *db.DB, cleanup func()) { } // collectResourceCommitmentsMetrics runs the KPI and returns all emitted metrics keyed by -// "metricName|az|cpu_architecture|resource|project_id". GP metrics have an empty cpu_architecture -// segment since the descriptor does not include that label. +// "metricName|az|cpu_architecture|resource|project_id|project_name|domain_id|domain_name". +// GP metrics have an empty cpu_architecture segment since the descriptor does not include that label. func collectResourceCommitmentsMetrics(t *testing.T, testDB *db.DB) map[string]float64 { t.Helper() kpi := &VMwareProjectCommitmentsKPI{} @@ -53,7 +56,7 @@ func collectResourceCommitmentsMetrics(t *testing.T, testDB *db.DB) map[string]f lbls[lp.GetName()] = lp.GetValue() } name := getMetricName(m.Desc().String()) - key := name + "|" + lbls["availability_zone"] + "|" + lbls["cpu_architecture"] + "|" + lbls["resource"] + "|" + lbls["project_id"] + key := name + "|" + lbls["availability_zone"] + "|" + lbls["cpu_architecture"] + "|" + lbls["resource"] + "|" + lbls["project_id"] + "|" + lbls["project_name"] + "|" + lbls["domain_id"] + "|" + lbls["domain_name"] result[key] = pm.GetGauge().GetValue() } return result @@ -61,13 +64,13 @@ func collectResourceCommitmentsMetrics(t *testing.T, testDB *db.DB) map[string]f // gpKey builds the expected map key for a general-purpose metric. // cpu_architecture is always empty because the GP metric descriptor omits that label. -func gpKey(az, resource, projectID string) string { - return "cortex_vmware_commitments_general_purpose|" + az + "||" + resource + "|" + projectID +func gpKey(az, resource, projectID, projectName, domainID, domainName string) string { + return "cortex_vmware_commitments_general_purpose|" + az + "||" + resource + "|" + projectID + "|" + projectName + "|" + domainID + "|" + domainName } // hKey builds the expected map key for a HANA metric. -func hKey(az, cpuArch, resource, projectID string) string { - return "cortex_vmware_commitments_hana_resources|" + az + "|" + cpuArch + "|" + resource + "|" + projectID +func hKey(az, cpuArch, resource, projectID, projectName, domainID, domainName string) string { + return "cortex_vmware_commitments_hana_resources|" + az + "|" + cpuArch + "|" + resource + "|" + projectID + "|" + projectName + "|" + domainID + "|" + domainName } func TestVMwareProjectCommitmentsKPI_Init(t *testing.T) { @@ -79,12 +82,20 @@ func TestVMwareProjectCommitmentsKPI_Init(t *testing.T) { t.Fatalf("expected no error, got %v", err) } } + func TestVMwareProjectCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { + // Reusable project/domain entries for test cases that need them. + p1 := identity.Project{ID: "p1", Name: "project-one", DomainID: "d1", Enabled: true} + p2 := identity.Project{ID: "p2", Name: "project-two", DomainID: "d1", Enabled: true} + d1 := identity.Domain{ID: "d1", Name: "domain-one", Enabled: true} + tests := []struct { name string commitments []limes.Commitment servers []nova.Server flavors []nova.Flavor + projects []identity.Project + domains []identity.Domain want map[string]float64 }{ { @@ -96,8 +107,10 @@ func TestVMwareProjectCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { commitments: []limes.Commitment{ {ID: 1, UUID: "c1", ServiceType: "compute", ResourceName: "cores", AvailabilityZone: "az1", Amount: 10, Status: "confirmed", ProjectID: "p1"}, }, + projects: []identity.Project{p1}, + domains: []identity.Domain{d1}, want: map[string]float64{ - gpKey("az1", "cpu", "p1"): 10, + gpKey("az1", "cpu", "p1", "project-one", "d1", "domain-one"): 10, }, }, { @@ -105,8 +118,10 @@ func TestVMwareProjectCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { commitments: []limes.Commitment{ {ID: 1, UUID: "c1", ServiceType: "compute", ResourceName: "ram", AvailabilityZone: "az1", Amount: 1024, Unit: "MiB", Status: "confirmed", ProjectID: "p1"}, }, + projects: []identity.Project{p1}, + domains: []identity.Domain{d1}, want: map[string]float64{ - gpKey("az1", "ram", "p1"): 1024 * 1024 * 1024, + gpKey("az1", "ram", "p1", "project-one", "d1", "domain-one"): 1024 * 1024 * 1024, }, }, { @@ -114,8 +129,10 @@ func TestVMwareProjectCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { commitments: []limes.Commitment{ {ID: 1, UUID: "c1", ServiceType: "compute", ResourceName: "ram", AvailabilityZone: "az1", Amount: 2, Unit: "GiB", Status: "confirmed", ProjectID: "p1"}, }, + projects: []identity.Project{p1}, + domains: []identity.Domain{d1}, want: map[string]float64{ - gpKey("az1", "ram", "p1"): 2 * 1024 * 1024 * 1024, + gpKey("az1", "ram", "p1", "project-one", "d1", "domain-one"): 2 * 1024 * 1024 * 1024, }, }, { @@ -130,8 +147,10 @@ func TestVMwareProjectCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { flavors: []nova.Flavor{ {ID: "f1", Name: "small", VCPUs: 3, RAM: 0, Disk: 0}, }, + projects: []identity.Project{p1}, + domains: []identity.Domain{d1}, want: map[string]float64{ - gpKey("az1", "cpu", "p1"): 4, // 10 - 2×3 = 4 + gpKey("az1", "cpu", "p1", "project-one", "d1", "domain-one"): 4, // 10 - 2×3 = 4 }, }, { @@ -145,7 +164,9 @@ func TestVMwareProjectCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { flavors: []nova.Flavor{ {ID: "f1", Name: "small", VCPUs: 4, RAM: 0, Disk: 0}, }, - want: map[string]float64{}, + projects: []identity.Project{p1}, + domains: []identity.Domain{d1}, + want: map[string]float64{}, }, { name: "over-used cpu produces no metric", @@ -158,7 +179,9 @@ func TestVMwareProjectCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { flavors: []nova.Flavor{ {ID: "f1", Name: "large", VCPUs: 8, RAM: 0, Disk: 0}, }, - want: map[string]float64{}, + projects: []identity.Project{p1}, + domains: []identity.Domain{d1}, + want: map[string]float64{}, }, { name: "hana servers not counted against gp commitments", @@ -171,8 +194,10 @@ func TestVMwareProjectCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { flavors: []nova.Flavor{ {ID: "f1", Name: "hana_small", VCPUs: 8, RAM: 0, Disk: 0}, }, + projects: []identity.Project{p1}, + domains: []identity.Domain{d1}, want: map[string]float64{ - gpKey("az1", "cpu", "p1"): 10, + gpKey("az1", "cpu", "p1", "project-one", "d1", "domain-one"): 10, }, }, { @@ -186,8 +211,10 @@ func TestVMwareProjectCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { flavors: []nova.Flavor{ {ID: "f1", Name: "m1_k_small", VCPUs: 4, RAM: 0, Disk: 0}, }, + projects: []identity.Project{p1}, + domains: []identity.Domain{d1}, want: map[string]float64{ - gpKey("az1", "cpu", "p1"): 10, + gpKey("az1", "cpu", "p1", "project-one", "d1", "domain-one"): 10, }, }, { @@ -203,8 +230,10 @@ func TestVMwareProjectCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { flavors: []nova.Flavor{ {ID: "f1", Name: "small", VCPUs: 2, RAM: 0, Disk: 0}, }, + projects: []identity.Project{p1}, + domains: []identity.Domain{d1}, want: map[string]float64{ - gpKey("az1", "cpu", "p1"): 8, // only 1 ACTIVE × 2 subtracted + gpKey("az1", "cpu", "p1", "project-one", "d1", "domain-one"): 8, // only 1 ACTIVE × 2 subtracted }, }, { @@ -212,8 +241,10 @@ func TestVMwareProjectCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { commitments: []limes.Commitment{ {ID: 1, UUID: "c1", ServiceType: "compute", ResourceName: "cores", AvailabilityZone: "az1", Amount: 5, Status: "guaranteed", ProjectID: "p1"}, }, + projects: []identity.Project{p1}, + domains: []identity.Domain{d1}, want: map[string]float64{ - gpKey("az1", "cpu", "p1"): 5, + gpKey("az1", "cpu", "p1", "project-one", "d1", "domain-one"): 5, }, }, { @@ -221,14 +252,18 @@ func TestVMwareProjectCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { commitments: []limes.Commitment{ {ID: 1, UUID: "c1", ServiceType: "compute", ResourceName: "cores", AvailabilityZone: "az1", Amount: 100, Status: "pending", ProjectID: "p1"}, }, - want: map[string]float64{}, + projects: []identity.Project{p1}, + domains: []identity.Domain{d1}, + want: map[string]float64{}, }, { name: "non-compute service type excluded", commitments: []limes.Commitment{ {ID: 1, UUID: "c1", ServiceType: "network", ResourceName: "cores", AvailabilityZone: "az1", Amount: 100, Status: "confirmed", ProjectID: "p1"}, }, - want: map[string]float64{}, + projects: []identity.Project{p1}, + domains: []identity.Domain{d1}, + want: map[string]float64{}, }, { name: "multiple commitments per project and AZ summed", @@ -238,10 +273,12 @@ func TestVMwareProjectCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { {ID: 3, UUID: "c3", ServiceType: "compute", ResourceName: "cores", AvailabilityZone: "az2", Amount: 20, Status: "confirmed", ProjectID: "p1"}, {ID: 4, UUID: "c4", ServiceType: "compute", ResourceName: "cores", AvailabilityZone: "az1", Amount: 8, Status: "confirmed", ProjectID: "p2"}, }, + projects: []identity.Project{p1, p2}, + domains: []identity.Domain{d1}, want: map[string]float64{ - gpKey("az1", "cpu", "p1"): 15, - gpKey("az2", "cpu", "p1"): 20, - gpKey("az1", "cpu", "p2"): 8, + gpKey("az1", "cpu", "p1", "project-one", "d1", "domain-one"): 15, + gpKey("az2", "cpu", "p1", "project-one", "d1", "domain-one"): 20, + gpKey("az1", "cpu", "p2", "project-two", "d1", "domain-one"): 8, }, }, { @@ -256,9 +293,11 @@ func TestVMwareProjectCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { flavors: []nova.Flavor{ {ID: "f1", Name: "medium", VCPUs: 2, RAM: 256, Disk: 0}, }, + projects: []identity.Project{p1}, + domains: []identity.Domain{d1}, want: map[string]float64{ - gpKey("az1", "cpu", "p1"): 6, // 8 - 1×2 - gpKey("az1", "ram", "p1"): (512 - 256) * 1024 * 1024, // 512MiB - 256MB (flavor.RAM is in MB) + gpKey("az1", "cpu", "p1", "project-one", "d1", "domain-one"): 6, // 8 - 1×2 + gpKey("az1", "ram", "p1", "project-one", "d1", "domain-one"): (512 - 256) * 1024 * 1024, // 512MiB - 256MB (flavor.RAM is in MB) }, }, } @@ -278,6 +317,12 @@ func TestVMwareProjectCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { for i := range tt.flavors { rows = append(rows, &tt.flavors[i]) } + for i := range tt.projects { + rows = append(rows, &tt.projects[i]) + } + for i := range tt.domains { + rows = append(rows, &tt.domains[i]) + } if len(rows) > 0 { if err := testDB.Insert(rows...); err != nil { t.Fatalf("failed to insert test data: %v", err) @@ -304,11 +349,18 @@ func TestVMwareProjectCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { } func TestVMwareProjectCommitmentsKPI_Collect_HANA(t *testing.T) { + // Reusable project/domain entries for test cases that need them. + p1 := identity.Project{ID: "p1", Name: "project-one", DomainID: "d1", Enabled: true} + p2 := identity.Project{ID: "p2", Name: "project-two", DomainID: "d1", Enabled: true} + d1 := identity.Domain{ID: "d1", Name: "domain-one", Enabled: true} + tests := []struct { name string commitments []limes.Commitment servers []nova.Server flavors []nova.Flavor + projects []identity.Project + domains []identity.Domain want map[string]float64 }{ { @@ -323,10 +375,12 @@ func TestVMwareProjectCommitmentsKPI_Collect_HANA(t *testing.T) { flavors: []nova.Flavor{ {ID: "f1", Name: "hana_c128_m1600", VCPUs: 128, RAM: 1638400, Disk: 100}, }, + projects: []identity.Project{p1}, + domains: []identity.Domain{d1}, want: map[string]float64{ - hKey("az1", "cascade-lake", "cpu", "p1"): 2 * 128, - hKey("az1", "cascade-lake", "ram", "p1"): 2 * 1638400 * 1024 * 1024, - hKey("az1", "cascade-lake", "disk", "p1"): 2 * 100 * 1024 * 1024 * 1024, + hKey("az1", "cascade-lake", "cpu", "p1", "project-one", "d1", "domain-one"): 2 * 128, + hKey("az1", "cascade-lake", "ram", "p1", "project-one", "d1", "domain-one"): 2 * 1638400 * 1024 * 1024, + hKey("az1", "cascade-lake", "disk", "p1", "project-one", "d1", "domain-one"): 2 * 100 * 1024 * 1024 * 1024, }, }, { @@ -340,10 +394,12 @@ func TestVMwareProjectCommitmentsKPI_Collect_HANA(t *testing.T) { flavors: []nova.Flavor{ {ID: "f1", Name: "hana_c128_m1600", VCPUs: 128, RAM: 1638400, Disk: 100}, }, + projects: []identity.Project{p1}, + domains: []identity.Domain{d1}, want: map[string]float64{ - hKey("az1", "cascade-lake", "cpu", "p1"): 2 * 128, - hKey("az1", "cascade-lake", "ram", "p1"): 2 * 1638400 * 1024 * 1024, - hKey("az1", "cascade-lake", "disk", "p1"): 2 * 100 * 1024 * 1024 * 1024, + hKey("az1", "cascade-lake", "cpu", "p1", "project-one", "d1", "domain-one"): 2 * 128, + hKey("az1", "cascade-lake", "ram", "p1", "project-one", "d1", "domain-one"): 2 * 1638400 * 1024 * 1024, + hKey("az1", "cascade-lake", "disk", "p1", "project-one", "d1", "domain-one"): 2 * 100 * 1024 * 1024 * 1024, }, }, { @@ -358,7 +414,9 @@ func TestVMwareProjectCommitmentsKPI_Collect_HANA(t *testing.T) { flavors: []nova.Flavor{ {ID: "f1", Name: "hana_small", VCPUs: 64, RAM: 819200, Disk: 50}, }, - want: map[string]float64{}, + projects: []identity.Project{p1}, + domains: []identity.Domain{d1}, + want: map[string]float64{}, }, { name: "over-used hana produces no metric", @@ -372,7 +430,9 @@ func TestVMwareProjectCommitmentsKPI_Collect_HANA(t *testing.T) { flavors: []nova.Flavor{ {ID: "f1", Name: "hana_small", VCPUs: 64, RAM: 819200, Disk: 50}, }, - want: map[string]float64{}, + projects: []identity.Project{p1}, + domains: []identity.Domain{d1}, + want: map[string]float64{}, }, { name: "sapphire-rapids arch from _v2 suffix", @@ -382,10 +442,12 @@ func TestVMwareProjectCommitmentsKPI_Collect_HANA(t *testing.T) { flavors: []nova.Flavor{ {ID: "f1", Name: "hana_c256_m3200_v2", VCPUs: 256, RAM: 3276800, Disk: 200}, }, + projects: []identity.Project{p1}, + domains: []identity.Domain{d1}, want: map[string]float64{ - hKey("az1", "sapphire-rapids", "cpu", "p1"): 256, - hKey("az1", "sapphire-rapids", "ram", "p1"): 3276800 * 1024 * 1024, - hKey("az1", "sapphire-rapids", "disk", "p1"): 200 * 1024 * 1024 * 1024, + hKey("az1", "sapphire-rapids", "cpu", "p1", "project-one", "d1", "domain-one"): 256, + hKey("az1", "sapphire-rapids", "ram", "p1", "project-one", "d1", "domain-one"): 3276800 * 1024 * 1024, + hKey("az1", "sapphire-rapids", "disk", "p1", "project-one", "d1", "domain-one"): 200 * 1024 * 1024 * 1024, }, }, { @@ -398,13 +460,15 @@ func TestVMwareProjectCommitmentsKPI_Collect_HANA(t *testing.T) { {ID: "f1", Name: "hana_c128_m1600", VCPUs: 128, RAM: 1638400, Disk: 100}, {ID: "f2", Name: "hana_c128_m1600_v2", VCPUs: 128, RAM: 1638400, Disk: 100}, }, + projects: []identity.Project{p1}, + domains: []identity.Domain{d1}, want: map[string]float64{ - hKey("az1", "cascade-lake", "cpu", "p1"): 2 * 128, - hKey("az1", "cascade-lake", "ram", "p1"): 2 * 1638400 * 1024 * 1024, - hKey("az1", "cascade-lake", "disk", "p1"): 2 * 100 * 1024 * 1024 * 1024, - hKey("az1", "sapphire-rapids", "cpu", "p1"): 1 * 128, - hKey("az1", "sapphire-rapids", "ram", "p1"): 1 * 1638400 * 1024 * 1024, - hKey("az1", "sapphire-rapids", "disk", "p1"): 1 * 100 * 1024 * 1024 * 1024, + hKey("az1", "cascade-lake", "cpu", "p1", "project-one", "d1", "domain-one"): 2 * 128, + hKey("az1", "cascade-lake", "ram", "p1", "project-one", "d1", "domain-one"): 2 * 1638400 * 1024 * 1024, + hKey("az1", "cascade-lake", "disk", "p1", "project-one", "d1", "domain-one"): 2 * 100 * 1024 * 1024 * 1024, + hKey("az1", "sapphire-rapids", "cpu", "p1", "project-one", "d1", "domain-one"): 1 * 128, + hKey("az1", "sapphire-rapids", "ram", "p1", "project-one", "d1", "domain-one"): 1 * 1638400 * 1024 * 1024, + hKey("az1", "sapphire-rapids", "disk", "p1", "project-one", "d1", "domain-one"): 1 * 100 * 1024 * 1024 * 1024, }, }, { @@ -416,7 +480,9 @@ func TestVMwareProjectCommitmentsKPI_Collect_HANA(t *testing.T) { flavors: []nova.Flavor{ {ID: "f1", Name: "hana_k_large", VCPUs: 64, RAM: 819200, Disk: 50}, }, - want: map[string]float64{}, + projects: []identity.Project{p1}, + domains: []identity.Domain{d1}, + want: map[string]float64{}, }, { name: "DELETED and ERROR hana servers excluded from running count", @@ -431,10 +497,12 @@ func TestVMwareProjectCommitmentsKPI_Collect_HANA(t *testing.T) { flavors: []nova.Flavor{ {ID: "f1", Name: "hana_small", VCPUs: 64, RAM: 819200, Disk: 50}, }, + projects: []identity.Project{p1}, + domains: []identity.Domain{d1}, want: map[string]float64{ - hKey("az1", "cascade-lake", "cpu", "p1"): 2 * 64, // 3 committed - 1 ACTIVE = 2 unused - hKey("az1", "cascade-lake", "ram", "p1"): 2 * 819200 * 1024 * 1024, - hKey("az1", "cascade-lake", "disk", "p1"): 2 * 50 * 1024 * 1024 * 1024, + hKey("az1", "cascade-lake", "cpu", "p1", "project-one", "d1", "domain-one"): 2 * 64, // 3 committed - 1 ACTIVE = 2 unused + hKey("az1", "cascade-lake", "ram", "p1", "project-one", "d1", "domain-one"): 2 * 819200 * 1024 * 1024, + hKey("az1", "cascade-lake", "disk", "p1", "project-one", "d1", "domain-one"): 2 * 50 * 1024 * 1024 * 1024, }, }, { @@ -445,10 +513,12 @@ func TestVMwareProjectCommitmentsKPI_Collect_HANA(t *testing.T) { flavors: []nova.Flavor{ {ID: "f1", Name: "hana_small", VCPUs: 64, RAM: 819200, Disk: 50}, }, + projects: []identity.Project{p1}, + domains: []identity.Domain{d1}, want: map[string]float64{ - hKey("az1", "cascade-lake", "cpu", "p1"): 64, - hKey("az1", "cascade-lake", "ram", "p1"): 819200 * 1024 * 1024, - hKey("az1", "cascade-lake", "disk", "p1"): 50 * 1024 * 1024 * 1024, + hKey("az1", "cascade-lake", "cpu", "p1", "project-one", "d1", "domain-one"): 64, + hKey("az1", "cascade-lake", "ram", "p1", "project-one", "d1", "domain-one"): 819200 * 1024 * 1024, + hKey("az1", "cascade-lake", "disk", "p1", "project-one", "d1", "domain-one"): 50 * 1024 * 1024 * 1024, }, }, { @@ -456,7 +526,9 @@ func TestVMwareProjectCommitmentsKPI_Collect_HANA(t *testing.T) { commitments: []limes.Commitment{ {ID: 1, UUID: "h1", ServiceType: "compute", ResourceName: "instances_hana_nonexistent", AvailabilityZone: "az1", Amount: 2, Status: "confirmed", ProjectID: "p1"}, }, - want: map[string]float64{}, + projects: []identity.Project{p1}, + domains: []identity.Domain{d1}, + want: map[string]float64{}, }, { name: "multiple projects and AZs aggregated per bucket", @@ -468,16 +540,18 @@ func TestVMwareProjectCommitmentsKPI_Collect_HANA(t *testing.T) { flavors: []nova.Flavor{ {ID: "f1", Name: "hana_small", VCPUs: 64, RAM: 819200, Disk: 50}, }, + projects: []identity.Project{p1, p2}, + domains: []identity.Domain{d1}, want: map[string]float64{ - hKey("az1", "cascade-lake", "cpu", "p1"): 2 * 64, - hKey("az1", "cascade-lake", "ram", "p1"): 2 * 819200 * 1024 * 1024, - hKey("az1", "cascade-lake", "disk", "p1"): 2 * 50 * 1024 * 1024 * 1024, - hKey("az2", "cascade-lake", "cpu", "p1"): 3 * 64, - hKey("az2", "cascade-lake", "ram", "p1"): 3 * 819200 * 1024 * 1024, - hKey("az2", "cascade-lake", "disk", "p1"): 3 * 50 * 1024 * 1024 * 1024, - hKey("az1", "cascade-lake", "cpu", "p2"): 1 * 64, - hKey("az1", "cascade-lake", "ram", "p2"): 1 * 819200 * 1024 * 1024, - hKey("az1", "cascade-lake", "disk", "p2"): 1 * 50 * 1024 * 1024 * 1024, + hKey("az1", "cascade-lake", "cpu", "p1", "project-one", "d1", "domain-one"): 2 * 64, + hKey("az1", "cascade-lake", "ram", "p1", "project-one", "d1", "domain-one"): 2 * 819200 * 1024 * 1024, + hKey("az1", "cascade-lake", "disk", "p1", "project-one", "d1", "domain-one"): 2 * 50 * 1024 * 1024 * 1024, + hKey("az2", "cascade-lake", "cpu", "p1", "project-one", "d1", "domain-one"): 3 * 64, + hKey("az2", "cascade-lake", "ram", "p1", "project-one", "d1", "domain-one"): 3 * 819200 * 1024 * 1024, + hKey("az2", "cascade-lake", "disk", "p1", "project-one", "d1", "domain-one"): 3 * 50 * 1024 * 1024 * 1024, + hKey("az1", "cascade-lake", "cpu", "p2", "project-two", "d1", "domain-one"): 1 * 64, + hKey("az1", "cascade-lake", "ram", "p2", "project-two", "d1", "domain-one"): 1 * 819200 * 1024 * 1024, + hKey("az1", "cascade-lake", "disk", "p2", "project-two", "d1", "domain-one"): 1 * 50 * 1024 * 1024 * 1024, }, }, } @@ -497,6 +571,12 @@ func TestVMwareProjectCommitmentsKPI_Collect_HANA(t *testing.T) { for i := range tt.flavors { rows = append(rows, &tt.flavors[i]) } + for i := range tt.projects { + rows = append(rows, &tt.projects[i]) + } + for i := range tt.domains { + rows = append(rows, &tt.domains[i]) + } if len(rows) > 0 { if err := testDB.Insert(rows...); err != nil { t.Fatalf("failed to insert test data: %v", err) From 566838ffd8d573672d21a23de7ac45738fd81700 Mon Sep 17 00:00:00 2001 From: Markus Wieland Date: Wed, 6 May 2026 11:41:51 +0200 Subject: [PATCH 3/8] feat: add identity-projects to vmware project commitments dependencies --- helm/bundles/cortex-nova/templates/kpis.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/helm/bundles/cortex-nova/templates/kpis.yaml b/helm/bundles/cortex-nova/templates/kpis.yaml index 7c70455d8..df9ea8d4d 100644 --- a/helm/bundles/cortex-nova/templates/kpis.yaml +++ b/helm/bundles/cortex-nova/templates/kpis.yaml @@ -202,6 +202,7 @@ spec: - name: nova-flavors - name: limes-project-commitments - name: identity-domains + - name: identity-projects description: | This KPI tracks the resource commitments of projects running VMs on VMware hosts. --- From 5512bfaedac66cbc6cc51324318ec7614321a543 Mon Sep 17 00:00:00 2001 From: Markus Wieland Date: Wed, 6 May 2026 11:42:33 +0200 Subject: [PATCH 4/8] refactor: rename vmware resource commitments KPI to vmware project commitments KPI --- .../kpis/plugins/infrastructure/vmware_project_commitments.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments.go b/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments.go index 9154b271f..c467604b6 100644 --- a/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments.go +++ b/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments.go @@ -34,7 +34,7 @@ type VMwareProjectCommitmentsKPI struct { } func (k *VMwareProjectCommitmentsKPI) GetName() string { - return "vmware_resource_commitments_kpi" + return "vmware_project_commitments_kpi" } func (k *VMwareProjectCommitmentsKPI) Init(dbConn *db.DB, c client.Client, opts conf.RawOpts) error { From b28cc181964d4887926827f5077cc6acd7954adc Mon Sep 17 00:00:00 2001 From: Markus Wieland Date: Wed, 6 May 2026 12:02:04 +0200 Subject: [PATCH 5/8] refactor: simplify gpKey and hKey functions to use projectWithDomain struct --- .../vmware_project_commitments_test.go | 98 ++++++++++--------- 1 file changed, 51 insertions(+), 47 deletions(-) diff --git a/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments_test.go b/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments_test.go index 2d398922e..3f4818230 100644 --- a/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments_test.go +++ b/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments_test.go @@ -64,13 +64,13 @@ func collectResourceCommitmentsMetrics(t *testing.T, testDB *db.DB) map[string]f // gpKey builds the expected map key for a general-purpose metric. // cpu_architecture is always empty because the GP metric descriptor omits that label. -func gpKey(az, resource, projectID, projectName, domainID, domainName string) string { - return "cortex_vmware_commitments_general_purpose|" + az + "||" + resource + "|" + projectID + "|" + projectName + "|" + domainID + "|" + domainName +func gpKey(az, resource string, p projectWithDomain) string { + return "cortex_vmware_commitments_general_purpose|" + az + "||" + resource + "|" + p.ProjectID + "|" + p.ProjectName + "|" + p.DomainID + "|" + p.DomainName } // hKey builds the expected map key for a HANA metric. -func hKey(az, cpuArch, resource, projectID, projectName, domainID, domainName string) string { - return "cortex_vmware_commitments_hana_resources|" + az + "|" + cpuArch + "|" + resource + "|" + projectID + "|" + projectName + "|" + domainID + "|" + domainName +func hKey(az, cpuArch, resource string, p projectWithDomain) string { + return "cortex_vmware_commitments_hana_resources|" + az + "|" + cpuArch + "|" + resource + "|" + p.ProjectID + "|" + p.ProjectName + "|" + p.DomainID + "|" + p.DomainName } func TestVMwareProjectCommitmentsKPI_Init(t *testing.T) { @@ -88,6 +88,8 @@ func TestVMwareProjectCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { p1 := identity.Project{ID: "p1", Name: "project-one", DomainID: "d1", Enabled: true} p2 := identity.Project{ID: "p2", Name: "project-two", DomainID: "d1", Enabled: true} d1 := identity.Domain{ID: "d1", Name: "domain-one", Enabled: true} + pd1 := projectWithDomain{ProjectID: "p1", ProjectName: "project-one", DomainID: "d1", DomainName: "domain-one"} + pd2 := projectWithDomain{ProjectID: "p2", ProjectName: "project-two", DomainID: "d1", DomainName: "domain-one"} tests := []struct { name string @@ -110,7 +112,7 @@ func TestVMwareProjectCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { projects: []identity.Project{p1}, domains: []identity.Domain{d1}, want: map[string]float64{ - gpKey("az1", "cpu", "p1", "project-one", "d1", "domain-one"): 10, + gpKey("az1", "cpu", pd1): 10, }, }, { @@ -121,7 +123,7 @@ func TestVMwareProjectCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { projects: []identity.Project{p1}, domains: []identity.Domain{d1}, want: map[string]float64{ - gpKey("az1", "ram", "p1", "project-one", "d1", "domain-one"): 1024 * 1024 * 1024, + gpKey("az1", "ram", pd1): 1024 * 1024 * 1024, }, }, { @@ -132,7 +134,7 @@ func TestVMwareProjectCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { projects: []identity.Project{p1}, domains: []identity.Domain{d1}, want: map[string]float64{ - gpKey("az1", "ram", "p1", "project-one", "d1", "domain-one"): 2 * 1024 * 1024 * 1024, + gpKey("az1", "ram", pd1): 2 * 1024 * 1024 * 1024, }, }, { @@ -150,7 +152,7 @@ func TestVMwareProjectCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { projects: []identity.Project{p1}, domains: []identity.Domain{d1}, want: map[string]float64{ - gpKey("az1", "cpu", "p1", "project-one", "d1", "domain-one"): 4, // 10 - 2×3 = 4 + gpKey("az1", "cpu", pd1): 4, // 10 - 2×3 = 4 }, }, { @@ -197,7 +199,7 @@ func TestVMwareProjectCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { projects: []identity.Project{p1}, domains: []identity.Domain{d1}, want: map[string]float64{ - gpKey("az1", "cpu", "p1", "project-one", "d1", "domain-one"): 10, + gpKey("az1", "cpu", pd1): 10, }, }, { @@ -214,7 +216,7 @@ func TestVMwareProjectCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { projects: []identity.Project{p1}, domains: []identity.Domain{d1}, want: map[string]float64{ - gpKey("az1", "cpu", "p1", "project-one", "d1", "domain-one"): 10, + gpKey("az1", "cpu", pd1): 10, }, }, { @@ -233,7 +235,7 @@ func TestVMwareProjectCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { projects: []identity.Project{p1}, domains: []identity.Domain{d1}, want: map[string]float64{ - gpKey("az1", "cpu", "p1", "project-one", "d1", "domain-one"): 8, // only 1 ACTIVE × 2 subtracted + gpKey("az1", "cpu", pd1): 8, // only 1 ACTIVE × 2 subtracted }, }, { @@ -244,7 +246,7 @@ func TestVMwareProjectCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { projects: []identity.Project{p1}, domains: []identity.Domain{d1}, want: map[string]float64{ - gpKey("az1", "cpu", "p1", "project-one", "d1", "domain-one"): 5, + gpKey("az1", "cpu", pd1): 5, }, }, { @@ -276,9 +278,9 @@ func TestVMwareProjectCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { projects: []identity.Project{p1, p2}, domains: []identity.Domain{d1}, want: map[string]float64{ - gpKey("az1", "cpu", "p1", "project-one", "d1", "domain-one"): 15, - gpKey("az2", "cpu", "p1", "project-one", "d1", "domain-one"): 20, - gpKey("az1", "cpu", "p2", "project-two", "d1", "domain-one"): 8, + gpKey("az1", "cpu", pd1): 15, + gpKey("az2", "cpu", pd1): 20, + gpKey("az1", "cpu", pd2): 8, }, }, { @@ -296,8 +298,8 @@ func TestVMwareProjectCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { projects: []identity.Project{p1}, domains: []identity.Domain{d1}, want: map[string]float64{ - gpKey("az1", "cpu", "p1", "project-one", "d1", "domain-one"): 6, // 8 - 1×2 - gpKey("az1", "ram", "p1", "project-one", "d1", "domain-one"): (512 - 256) * 1024 * 1024, // 512MiB - 256MB (flavor.RAM is in MB) + gpKey("az1", "cpu", pd1): 6, // 8 - 1×2 + gpKey("az1", "ram", pd1): (512 - 256) * 1024 * 1024, // 512MiB - 256MB (flavor.RAM is in MB) }, }, } @@ -353,6 +355,8 @@ func TestVMwareProjectCommitmentsKPI_Collect_HANA(t *testing.T) { p1 := identity.Project{ID: "p1", Name: "project-one", DomainID: "d1", Enabled: true} p2 := identity.Project{ID: "p2", Name: "project-two", DomainID: "d1", Enabled: true} d1 := identity.Domain{ID: "d1", Name: "domain-one", Enabled: true} + pd1 := projectWithDomain{ProjectID: "p1", ProjectName: "project-one", DomainID: "d1", DomainName: "domain-one"} + pd2 := projectWithDomain{ProjectID: "p2", ProjectName: "project-two", DomainID: "d1", DomainName: "domain-one"} tests := []struct { name string @@ -378,9 +382,9 @@ func TestVMwareProjectCommitmentsKPI_Collect_HANA(t *testing.T) { projects: []identity.Project{p1}, domains: []identity.Domain{d1}, want: map[string]float64{ - hKey("az1", "cascade-lake", "cpu", "p1", "project-one", "d1", "domain-one"): 2 * 128, - hKey("az1", "cascade-lake", "ram", "p1", "project-one", "d1", "domain-one"): 2 * 1638400 * 1024 * 1024, - hKey("az1", "cascade-lake", "disk", "p1", "project-one", "d1", "domain-one"): 2 * 100 * 1024 * 1024 * 1024, + hKey("az1", "cascade-lake", "cpu", pd1): 2 * 128, + hKey("az1", "cascade-lake", "ram", pd1): 2 * 1638400 * 1024 * 1024, + hKey("az1", "cascade-lake", "disk", pd1): 2 * 100 * 1024 * 1024 * 1024, }, }, { @@ -397,9 +401,9 @@ func TestVMwareProjectCommitmentsKPI_Collect_HANA(t *testing.T) { projects: []identity.Project{p1}, domains: []identity.Domain{d1}, want: map[string]float64{ - hKey("az1", "cascade-lake", "cpu", "p1", "project-one", "d1", "domain-one"): 2 * 128, - hKey("az1", "cascade-lake", "ram", "p1", "project-one", "d1", "domain-one"): 2 * 1638400 * 1024 * 1024, - hKey("az1", "cascade-lake", "disk", "p1", "project-one", "d1", "domain-one"): 2 * 100 * 1024 * 1024 * 1024, + hKey("az1", "cascade-lake", "cpu", pd1): 2 * 128, + hKey("az1", "cascade-lake", "ram", pd1): 2 * 1638400 * 1024 * 1024, + hKey("az1", "cascade-lake", "disk", pd1): 2 * 100 * 1024 * 1024 * 1024, }, }, { @@ -445,9 +449,9 @@ func TestVMwareProjectCommitmentsKPI_Collect_HANA(t *testing.T) { projects: []identity.Project{p1}, domains: []identity.Domain{d1}, want: map[string]float64{ - hKey("az1", "sapphire-rapids", "cpu", "p1", "project-one", "d1", "domain-one"): 256, - hKey("az1", "sapphire-rapids", "ram", "p1", "project-one", "d1", "domain-one"): 3276800 * 1024 * 1024, - hKey("az1", "sapphire-rapids", "disk", "p1", "project-one", "d1", "domain-one"): 200 * 1024 * 1024 * 1024, + hKey("az1", "sapphire-rapids", "cpu", pd1): 256, + hKey("az1", "sapphire-rapids", "ram", pd1): 3276800 * 1024 * 1024, + hKey("az1", "sapphire-rapids", "disk", pd1): 200 * 1024 * 1024 * 1024, }, }, { @@ -463,12 +467,12 @@ func TestVMwareProjectCommitmentsKPI_Collect_HANA(t *testing.T) { projects: []identity.Project{p1}, domains: []identity.Domain{d1}, want: map[string]float64{ - hKey("az1", "cascade-lake", "cpu", "p1", "project-one", "d1", "domain-one"): 2 * 128, - hKey("az1", "cascade-lake", "ram", "p1", "project-one", "d1", "domain-one"): 2 * 1638400 * 1024 * 1024, - hKey("az1", "cascade-lake", "disk", "p1", "project-one", "d1", "domain-one"): 2 * 100 * 1024 * 1024 * 1024, - hKey("az1", "sapphire-rapids", "cpu", "p1", "project-one", "d1", "domain-one"): 1 * 128, - hKey("az1", "sapphire-rapids", "ram", "p1", "project-one", "d1", "domain-one"): 1 * 1638400 * 1024 * 1024, - hKey("az1", "sapphire-rapids", "disk", "p1", "project-one", "d1", "domain-one"): 1 * 100 * 1024 * 1024 * 1024, + hKey("az1", "cascade-lake", "cpu", pd1): 2 * 128, + hKey("az1", "cascade-lake", "ram", pd1): 2 * 1638400 * 1024 * 1024, + hKey("az1", "cascade-lake", "disk", pd1): 2 * 100 * 1024 * 1024 * 1024, + hKey("az1", "sapphire-rapids", "cpu", pd1): 1 * 128, + hKey("az1", "sapphire-rapids", "ram", pd1): 1 * 1638400 * 1024 * 1024, + hKey("az1", "sapphire-rapids", "disk", pd1): 1 * 100 * 1024 * 1024 * 1024, }, }, { @@ -500,9 +504,9 @@ func TestVMwareProjectCommitmentsKPI_Collect_HANA(t *testing.T) { projects: []identity.Project{p1}, domains: []identity.Domain{d1}, want: map[string]float64{ - hKey("az1", "cascade-lake", "cpu", "p1", "project-one", "d1", "domain-one"): 2 * 64, // 3 committed - 1 ACTIVE = 2 unused - hKey("az1", "cascade-lake", "ram", "p1", "project-one", "d1", "domain-one"): 2 * 819200 * 1024 * 1024, - hKey("az1", "cascade-lake", "disk", "p1", "project-one", "d1", "domain-one"): 2 * 50 * 1024 * 1024 * 1024, + hKey("az1", "cascade-lake", "cpu", pd1): 2 * 64, // 3 committed - 1 ACTIVE = 2 unused + hKey("az1", "cascade-lake", "ram", pd1): 2 * 819200 * 1024 * 1024, + hKey("az1", "cascade-lake", "disk", pd1): 2 * 50 * 1024 * 1024 * 1024, }, }, { @@ -516,9 +520,9 @@ func TestVMwareProjectCommitmentsKPI_Collect_HANA(t *testing.T) { projects: []identity.Project{p1}, domains: []identity.Domain{d1}, want: map[string]float64{ - hKey("az1", "cascade-lake", "cpu", "p1", "project-one", "d1", "domain-one"): 64, - hKey("az1", "cascade-lake", "ram", "p1", "project-one", "d1", "domain-one"): 819200 * 1024 * 1024, - hKey("az1", "cascade-lake", "disk", "p1", "project-one", "d1", "domain-one"): 50 * 1024 * 1024 * 1024, + hKey("az1", "cascade-lake", "cpu", pd1): 64, + hKey("az1", "cascade-lake", "ram", pd1): 819200 * 1024 * 1024, + hKey("az1", "cascade-lake", "disk", pd1): 50 * 1024 * 1024 * 1024, }, }, { @@ -543,15 +547,15 @@ func TestVMwareProjectCommitmentsKPI_Collect_HANA(t *testing.T) { projects: []identity.Project{p1, p2}, domains: []identity.Domain{d1}, want: map[string]float64{ - hKey("az1", "cascade-lake", "cpu", "p1", "project-one", "d1", "domain-one"): 2 * 64, - hKey("az1", "cascade-lake", "ram", "p1", "project-one", "d1", "domain-one"): 2 * 819200 * 1024 * 1024, - hKey("az1", "cascade-lake", "disk", "p1", "project-one", "d1", "domain-one"): 2 * 50 * 1024 * 1024 * 1024, - hKey("az2", "cascade-lake", "cpu", "p1", "project-one", "d1", "domain-one"): 3 * 64, - hKey("az2", "cascade-lake", "ram", "p1", "project-one", "d1", "domain-one"): 3 * 819200 * 1024 * 1024, - hKey("az2", "cascade-lake", "disk", "p1", "project-one", "d1", "domain-one"): 3 * 50 * 1024 * 1024 * 1024, - hKey("az1", "cascade-lake", "cpu", "p2", "project-two", "d1", "domain-one"): 1 * 64, - hKey("az1", "cascade-lake", "ram", "p2", "project-two", "d1", "domain-one"): 1 * 819200 * 1024 * 1024, - hKey("az1", "cascade-lake", "disk", "p2", "project-two", "d1", "domain-one"): 1 * 50 * 1024 * 1024 * 1024, + hKey("az1", "cascade-lake", "cpu", pd1): 2 * 64, + hKey("az1", "cascade-lake", "ram", pd1): 2 * 819200 * 1024 * 1024, + hKey("az1", "cascade-lake", "disk", pd1): 2 * 50 * 1024 * 1024 * 1024, + hKey("az2", "cascade-lake", "cpu", pd1): 3 * 64, + hKey("az2", "cascade-lake", "ram", pd1): 3 * 819200 * 1024 * 1024, + hKey("az2", "cascade-lake", "disk", pd1): 3 * 50 * 1024 * 1024 * 1024, + hKey("az1", "cascade-lake", "cpu", pd2): 1 * 64, + hKey("az1", "cascade-lake", "ram", pd2): 1 * 819200 * 1024 * 1024, + hKey("az1", "cascade-lake", "disk", pd2): 1 * 50 * 1024 * 1024 * 1024, }, }, } From 33ed41df6a610875708dcc81808e94eabf56225a Mon Sep 17 00:00:00 2001 From: Markus Wieland Date: Wed, 6 May 2026 12:06:42 +0200 Subject: [PATCH 6/8] refactor: update log messages to use vmware_project_commitments for consistency --- .../infrastructure/vmware_project_commitments.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments.go b/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments.go index c467604b6..14744b205 100644 --- a/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments.go +++ b/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments.go @@ -67,13 +67,13 @@ func (k *VMwareProjectCommitmentsKPI) Collect(ch chan<- prometheus.Metric) { flavorsByName, err := k.getFlavorsByName() if err != nil { - slog.Error("vmware_resource_commitments: failed to load flavors", "err", err) + slog.Error("vmware_project_commitments: failed to load flavors", "err", err) return } projects, err := k.getProjectsWithDomains() if err != nil { - slog.Error("vmware_resource_commitments: failed to load projects with domains", "err", err) + slog.Error("vmware_project_commitments: failed to load projects with domains", "err", err) return } @@ -166,12 +166,12 @@ func (k *VMwareProjectCommitmentsKPI) getRunningHanaServers() ([]nova.Server, er func (k *VMwareProjectCommitmentsKPI) collectGeneralPurpose(ch chan<- prometheus.Metric, flavorsByName map[string]nova.Flavor, projects map[string]projectWithDomain) { commitments, err := k.getGeneralPurposeCommitments() if err != nil { - slog.Error("vmware_resource_commitments: failed to load gp commitments", "err", err) + slog.Error("vmware_project_commitments: failed to load gp commitments", "err", err) return } servers, err := k.getGeneralPurposeServers() if err != nil { - slog.Error("vmware_resource_commitments: failed to load gp servers", "err", err) + slog.Error("vmware_project_commitments: failed to load gp servers", "err", err) return } @@ -185,7 +185,7 @@ func (k *VMwareProjectCommitmentsKPI) collectGeneralPurpose(ch chan<- prometheus case "ram": bytes, err := bytesFromUnit(float64(c.Amount), c.Unit) if err != nil { - slog.Warn("vmware_resource_commitments: unknown ram unit", "unit", c.Unit, "err", err) + slog.Warn("vmware_project_commitments: unknown ram unit", "unit", c.Unit, "err", err) continue } committed[gpKey{c.ProjectID, c.AvailabilityZone, "ram"}] += bytes @@ -196,7 +196,7 @@ func (k *VMwareProjectCommitmentsKPI) collectGeneralPurpose(ch chan<- prometheus for _, s := range servers { flavor, ok := flavorsByName[s.FlavorName] if !ok { - slog.Warn("vmware_resource_commitments: gp flavor not found", "flavor", s.FlavorName) + slog.Warn("vmware_project_commitments: gp flavor not found", "flavor", s.FlavorName) continue } used[gpKey{s.TenantID, s.OSEXTAvailabilityZone, "cpu"}] += float64(flavor.VCPUs) From 1b6b517c31822764a19c71a26a9e3f5d41b440cd Mon Sep 17 00:00:00 2001 From: Markus Wieland Date: Wed, 6 May 2026 12:08:19 +0200 Subject: [PATCH 7/8] refactor: rename resource commitments functions to project commitments for consistency --- .../vmware_project_commitments_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments_test.go b/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments_test.go index 3f4818230..8978f76a9 100644 --- a/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments_test.go +++ b/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments_test.go @@ -16,7 +16,7 @@ import ( prometheusgo "github.com/prometheus/client_model/go" ) -func setupResourceCommitmentsDB(t *testing.T) (testDB *db.DB, cleanup func()) { +func setupProjectCommitmentsDB(t *testing.T) (testDB *db.DB, cleanup func()) { t.Helper() dbEnv := testlibDB.SetupDBEnv(t) testDB = &db.DB{DbMap: dbEnv.DbMap} @@ -32,10 +32,10 @@ func setupResourceCommitmentsDB(t *testing.T) (testDB *db.DB, cleanup func()) { return testDB, dbEnv.Close } -// collectResourceCommitmentsMetrics runs the KPI and returns all emitted metrics keyed by +// collectProjectCommitmentsMetrics runs the KPI and returns all emitted metrics keyed by // "metricName|az|cpu_architecture|resource|project_id|project_name|domain_id|domain_name". // GP metrics have an empty cpu_architecture segment since the descriptor does not include that label. -func collectResourceCommitmentsMetrics(t *testing.T, testDB *db.DB) map[string]float64 { +func collectProjectCommitmentsMetrics(t *testing.T, testDB *db.DB) map[string]float64 { t.Helper() kpi := &VMwareProjectCommitmentsKPI{} if err := kpi.Init(testDB, nil, conf.NewRawOpts("{}")); err != nil { @@ -306,7 +306,7 @@ func TestVMwareProjectCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - testDB, cleanup := setupResourceCommitmentsDB(t) + testDB, cleanup := setupProjectCommitmentsDB(t) defer cleanup() var rows []any @@ -331,7 +331,7 @@ func TestVMwareProjectCommitmentsKPI_Collect_GeneralPurpose(t *testing.T) { } } - got := collectResourceCommitmentsMetrics(t, testDB) + got := collectProjectCommitmentsMetrics(t, testDB) if len(got) != len(tt.want) { t.Errorf("expected %d metrics, got %d: %v", len(tt.want), len(got), got) @@ -562,7 +562,7 @@ func TestVMwareProjectCommitmentsKPI_Collect_HANA(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - testDB, cleanup := setupResourceCommitmentsDB(t) + testDB, cleanup := setupProjectCommitmentsDB(t) defer cleanup() var rows []any @@ -587,7 +587,7 @@ func TestVMwareProjectCommitmentsKPI_Collect_HANA(t *testing.T) { } } - got := collectResourceCommitmentsMetrics(t, testDB) + got := collectProjectCommitmentsMetrics(t, testDB) if len(got) != len(tt.want) { t.Errorf("expected %d metrics, got %d: %v", len(tt.want), len(got), got) From 4e372cbb57a92c981aea2277dae50297f3a8e471 Mon Sep 17 00:00:00 2001 From: Markus Wieland Date: Wed, 6 May 2026 14:14:05 +0200 Subject: [PATCH 8/8] lint --- internal/knowledge/kpis/supported_kpis.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/knowledge/kpis/supported_kpis.go b/internal/knowledge/kpis/supported_kpis.go index 73e36045e..64d8828cb 100644 --- a/internal/knowledge/kpis/supported_kpis.go +++ b/internal/knowledge/kpis/supported_kpis.go @@ -23,10 +23,10 @@ var supportedKPIs = map[string]plugins.KPI{ "vm_commitments_kpi": &compute.VMCommitmentsKPI{}, "vm_faults_kpi": &compute.VMFaultsKPI{}, - "kvm_project_utilization_kpi": &infrastructure.KVMProjectUtilizationKPI{}, - "vmware_project_utilization_kpi": &infrastructure.VMwareProjectUtilizationKPI{}, - "vmware_project_commitments_kpi": &infrastructure.VMwareProjectCommitmentsKPI{}, - "vmware_host_capacity_kpi": &infrastructure.VMwareHostCapacityKPI{}, + "kvm_project_utilization_kpi": &infrastructure.KVMProjectUtilizationKPI{}, + "vmware_project_utilization_kpi": &infrastructure.VMwareProjectUtilizationKPI{}, + "vmware_project_commitments_kpi": &infrastructure.VMwareProjectCommitmentsKPI{}, + "vmware_host_capacity_kpi": &infrastructure.VMwareHostCapacityKPI{}, "netapp_storage_pool_cpu_usage_kpi": &storage.NetAppStoragePoolCPUUsageKPI{},