From a55dc0e060185db7a82c0d10011332005dd06e1a Mon Sep 17 00:00:00 2001 From: Markus Wieland Date: Wed, 6 May 2026 08:57:26 +0200 Subject: [PATCH 1/4] feat: add domain_id and name to vmware project capacity metrics Co-authored-by: Copilot --- .../vmware_project_utilization.go | 22 +- .../vmware_project_utilization_test.go | 248 ++++++++++++------ 2 files changed, 182 insertions(+), 88 deletions(-) diff --git a/internal/knowledge/kpis/plugins/infrastructure/vmware_project_utilization.go b/internal/knowledge/kpis/plugins/infrastructure/vmware_project_utilization.go index 16fcac857..aaab2718e 100644 --- a/internal/knowledge/kpis/plugins/infrastructure/vmware_project_utilization.go +++ b/internal/knowledge/kpis/plugins/infrastructure/vmware_project_utilization.go @@ -21,6 +21,8 @@ import ( type vmwareProjectInstanceCount struct { ProjectID string `db:"project_id"` ProjectName string `db:"project_name"` + DomainID string `db:"domain_id"` + DomainName string `db:"domain_name"` ComputeHost string `db:"compute_host"` FlavorName string `db:"flavor_name"` AvailabilityZone string `db:"availability_zone"` @@ -30,6 +32,8 @@ type vmwareProjectInstanceCount struct { type vmwareProjectCapacityUsage struct { ProjectID string `db:"project_id"` ProjectName string `db:"project_name"` + DomainID string `db:"domain_id"` + DomainName string `db:"domain_name"` ComputeHost string `db:"compute_host"` AvailabilityZone string `db:"availability_zone"` TotalVCPUs float64 `db:"total_vcpus"` @@ -60,12 +64,12 @@ func (k *VMwareProjectUtilizationKPI) Init(dbConn *db.DB, c client.Client, opts k.instanceCountPerProjectAndHostAndFlavor = prometheus.NewDesc( "cortex_vmware_project_instances", "Number of running instances per project, hypervisor, and flavor on VMware.", - append(vmwareHostLabels, "project_id", "project_name", "flavor_name"), nil, + append(vmwareHostLabels, "project_id", "project_name", "domain_id", "domain_name", "flavor_name"), nil, ) k.capacityUsagePerProjectAndHost = prometheus.NewDesc( "cortex_vmware_project_capacity_usage", "Resource capacity used by a project per VMware hypervisor and flavor. CPU in vCPUs, memory and disk in bytes.", - append(vmwareHostLabels, "project_id", "project_name", "resource"), nil, + append(vmwareHostLabels, "project_id", "project_name", "domain_id", "domain_name", "resource"), nil, ) return nil } @@ -96,7 +100,7 @@ func (k *VMwareProjectUtilizationKPI) Collect(ch chan<- prometheus.Metric) { continue } hostLabels := host.getHostLabels() - hostLabels = append(hostLabels, projectInstanceCount.ProjectID, projectInstanceCount.ProjectName, projectInstanceCount.FlavorName) + hostLabels = append(hostLabels, projectInstanceCount.ProjectID, projectInstanceCount.ProjectName, projectInstanceCount.DomainID, projectInstanceCount.DomainName, projectInstanceCount.FlavorName) ch <- prometheus.MustNewConstMetric(k.instanceCountPerProjectAndHostAndFlavor, prometheus.GaugeValue, projectInstanceCount.InstanceCount, hostLabels...) } @@ -113,7 +117,7 @@ func (k *VMwareProjectUtilizationKPI) Collect(ch chan<- prometheus.Metric) { continue } hostLabels := host.getHostLabels() - hostLabels = append(hostLabels, projectCapacityUsage.ProjectID, projectCapacityUsage.ProjectName) + hostLabels = append(hostLabels, projectCapacityUsage.ProjectID, projectCapacityUsage.ProjectName, projectCapacityUsage.DomainID, projectCapacityUsage.DomainName) memoryUsageBytes, err := bytesFromUnit(projectCapacityUsage.TotalRAMMB, "MB") if err != nil { @@ -167,6 +171,8 @@ func (k *VMwareProjectUtilizationKPI) queryProjectCapacityUsage() ([]vmwareProje SELECT s.tenant_id AS project_id, COALESCE(p.name, '') AS project_name, + COALESCE(d.id, '') AS domain_id, + COALESCE(d.name, '') AS domain_name, s.os_ext_srv_attr_host AS compute_host, s.os_ext_az_availability_zone AS availability_zone, COALESCE(SUM(f.vcpus), 0) AS total_vcpus, @@ -175,10 +181,11 @@ func (k *VMwareProjectUtilizationKPI) queryProjectCapacityUsage() ([]vmwareProje FROM ` + nova.Server{}.TableName() + ` s LEFT JOIN ` + nova.Flavor{}.TableName() + ` f ON s.flavor_name = f.name LEFT JOIN ` + identity.Project{}.TableName() + ` p ON p.id = s.tenant_id + LEFT JOIN ` + identity.Domain{}.TableName() + ` d ON d.id = p.domain_id WHERE s.status NOT IN ('DELETED', 'ERROR') AND s.os_ext_srv_attr_host LIKE '` + vmwareComputeHostPattern + `' AND s.os_ext_srv_attr_host NOT LIKE '` + vmwareIronicComputeHostPattern + `' - GROUP BY s.tenant_id, p.name, s.os_ext_srv_attr_host, s.os_ext_az_availability_zone + GROUP BY s.tenant_id, p.name, p.domain_id, d.name, s.os_ext_srv_attr_host, s.os_ext_az_availability_zone ` var usages []vmwareProjectCapacityUsage if _, err := k.DB.Select(&usages, query); err != nil { @@ -198,16 +205,19 @@ func (k *VMwareProjectUtilizationKPI) queryProjectInstanceCount() ([]vmwareProje SELECT s.tenant_id AS project_id, COALESCE(p.name, '') AS project_name, + COALESCE(p.domain_id, '') AS domain_id, + COALESCE(d.name, '') AS domain_name, s.os_ext_srv_attr_host AS compute_host, s.os_ext_az_availability_zone AS availability_zone, s.flavor_name, COUNT(*) AS instance_count FROM ` + nova.Server{}.TableName() + ` s LEFT JOIN ` + identity.Project{}.TableName() + ` p ON p.id = s.tenant_id + LEFT JOIN ` + identity.Domain{}.TableName() + ` d ON d.id = p.domain_id WHERE s.status NOT IN ('DELETED', 'ERROR') AND s.os_ext_srv_attr_host LIKE '` + vmwareComputeHostPattern + `' AND s.os_ext_srv_attr_host NOT LIKE '` + vmwareIronicComputeHostPattern + `' - GROUP BY s.tenant_id, p.name, s.os_ext_srv_attr_host, s.flavor_name, s.os_ext_az_availability_zone + GROUP BY s.tenant_id, p.name, d.id, d.name, s.os_ext_srv_attr_host, s.flavor_name, s.os_ext_az_availability_zone ` var usages []vmwareProjectInstanceCount if _, err := k.DB.Select(&usages, query); err != nil { diff --git a/internal/knowledge/kpis/plugins/infrastructure/vmware_project_utilization_test.go b/internal/knowledge/kpis/plugins/infrastructure/vmware_project_utilization_test.go index 4c43c893b..d6e650399 100644 --- a/internal/knowledge/kpis/plugins/infrastructure/vmware_project_utilization_test.go +++ b/internal/knowledge/kpis/plugins/infrastructure/vmware_project_utilization_test.go @@ -33,18 +33,22 @@ func buildMetricKey(name string, labels map[string]string) string { } } -func instanceMetric(computeHost, az, projectID, projectName, flavorName string, value float64) collectedVMwareMetric { +func instanceMetric(computeHost, az, projectID, projectName, domainID, domainName, flavorName string, value float64) collectedVMwareMetric { labels := mockVMwareHostLabels(computeHost, az) labels["project_id"] = projectID labels["project_name"] = projectName + labels["domain_id"] = domainID + labels["domain_name"] = domainName labels["flavor_name"] = flavorName return collectedVMwareMetric{Name: "cortex_vmware_project_instances", Labels: labels, Value: value} } -func capacityMetric(computeHost, az, projectID, projectName, resource string, value float64) collectedVMwareMetric { +func capacityMetric(computeHost, az, projectID, projectName, domainID, domainName, resource string, value float64) collectedVMwareMetric { labels := mockVMwareHostLabels(computeHost, az) labels["project_id"] = projectID labels["project_name"] = projectName + labels["domain_id"] = domainID + labels["domain_name"] = domainName labels["resource"] = resource return collectedVMwareMetric{Name: "cortex_vmware_project_capacity_usage", Labels: labels, Value: value} } @@ -132,6 +136,7 @@ func TestVMwareProjectUtilizationKPI_queryProjectInstanceCount(t *testing.T) { name string servers []nova.Server projects []identity.Project + domains []identity.Domain expectedCounts map[string]vmwareProjectInstanceCount }{ { @@ -139,9 +144,10 @@ func TestVMwareProjectUtilizationKPI_queryProjectInstanceCount(t *testing.T) { servers: []nova.Server{ {ID: "server-1", TenantID: "project-1", OSEXTSRVATTRHost: "nova-compute-1", FlavorName: "flavor-1", Status: "ACTIVE", OSEXTAvailabilityZone: "az1"}, }, - projects: []identity.Project{{ID: "project-1", Name: "Project One"}}, + projects: []identity.Project{{ID: "project-1", Name: "Project One", DomainID: "domain-1"}}, + domains: []identity.Domain{{ID: "domain-1", Name: "Domain One"}}, expectedCounts: map[string]vmwareProjectInstanceCount{ - "project-1|nova-compute-1|flavor-1|az1": {ProjectID: "project-1", ProjectName: "Project One", ComputeHost: "nova-compute-1", FlavorName: "flavor-1", AvailabilityZone: "az1", InstanceCount: 1}, + "project-1|nova-compute-1|flavor-1|az1": {ProjectID: "project-1", ProjectName: "Project One", DomainID: "domain-1", DomainName: "Domain One", ComputeHost: "nova-compute-1", FlavorName: "flavor-1", AvailabilityZone: "az1", InstanceCount: 1}, }, }, { @@ -153,14 +159,15 @@ func TestVMwareProjectUtilizationKPI_queryProjectInstanceCount(t *testing.T) { {ID: "server-4", TenantID: "project-2", OSEXTSRVATTRHost: "nova-compute-2", FlavorName: "flavor-2", Status: "ACTIVE", OSEXTAvailabilityZone: "az2"}, }, projects: []identity.Project{ - {ID: "project-1", Name: "Project One"}, - {ID: "project-2", Name: "Project Two"}, + {ID: "project-1", Name: "Project One", DomainID: "domain-1"}, + {ID: "project-2", Name: "Project Two", DomainID: "domain-1"}, }, + domains: []identity.Domain{{ID: "domain-1", Name: "Domain One"}}, expectedCounts: map[string]vmwareProjectInstanceCount{ - "project-1|nova-compute-1|flavor-1|az1": {ProjectID: "project-1", ProjectName: "Project One", ComputeHost: "nova-compute-1", FlavorName: "flavor-1", AvailabilityZone: "az1", InstanceCount: 1}, - "project-1|nova-compute-1|flavor-2|az1": {ProjectID: "project-1", ProjectName: "Project One", ComputeHost: "nova-compute-1", FlavorName: "flavor-2", AvailabilityZone: "az1", InstanceCount: 1}, - "project-2|nova-compute-2|flavor-1|az2": {ProjectID: "project-2", ProjectName: "Project Two", ComputeHost: "nova-compute-2", FlavorName: "flavor-1", AvailabilityZone: "az2", InstanceCount: 1}, - "project-2|nova-compute-2|flavor-2|az2": {ProjectID: "project-2", ProjectName: "Project Two", ComputeHost: "nova-compute-2", FlavorName: "flavor-2", AvailabilityZone: "az2", InstanceCount: 1}, + "project-1|nova-compute-1|flavor-1|az1": {ProjectID: "project-1", ProjectName: "Project One", DomainID: "domain-1", DomainName: "Domain One", ComputeHost: "nova-compute-1", FlavorName: "flavor-1", AvailabilityZone: "az1", InstanceCount: 1}, + "project-1|nova-compute-1|flavor-2|az1": {ProjectID: "project-1", ProjectName: "Project One", DomainID: "domain-1", DomainName: "Domain One", ComputeHost: "nova-compute-1", FlavorName: "flavor-2", AvailabilityZone: "az1", InstanceCount: 1}, + "project-2|nova-compute-2|flavor-1|az2": {ProjectID: "project-2", ProjectName: "Project Two", DomainID: "domain-1", DomainName: "Domain One", ComputeHost: "nova-compute-2", FlavorName: "flavor-1", AvailabilityZone: "az2", InstanceCount: 1}, + "project-2|nova-compute-2|flavor-2|az2": {ProjectID: "project-2", ProjectName: "Project Two", DomainID: "domain-1", DomainName: "Domain One", ComputeHost: "nova-compute-2", FlavorName: "flavor-2", AvailabilityZone: "az2", InstanceCount: 1}, }, }, { @@ -170,9 +177,10 @@ func TestVMwareProjectUtilizationKPI_queryProjectInstanceCount(t *testing.T) { {ID: "server-2", TenantID: "project-1", OSEXTSRVATTRHost: "node-3", FlavorName: "flavor-2", Status: "ACTIVE", OSEXTAvailabilityZone: "az1"}, {ID: "server-3", TenantID: "project-1", OSEXTSRVATTRHost: "nova-compute-ironic-1", FlavorName: "flavor-2", Status: "ACTIVE", OSEXTAvailabilityZone: "az1"}, }, - projects: []identity.Project{{ID: "project-1", Name: "Project One"}}, + projects: []identity.Project{{ID: "project-1", Name: "Project One", DomainID: "domain-1"}}, + domains: []identity.Domain{{ID: "domain-1", Name: "Domain One"}}, expectedCounts: map[string]vmwareProjectInstanceCount{ - "project-1|nova-compute-1|flavor-1|az1": {ProjectID: "project-1", ProjectName: "Project One", ComputeHost: "nova-compute-1", FlavorName: "flavor-1", AvailabilityZone: "az1", InstanceCount: 1}, + "project-1|nova-compute-1|flavor-1|az1": {ProjectID: "project-1", ProjectName: "Project One", DomainID: "domain-1", DomainName: "Domain One", ComputeHost: "nova-compute-1", FlavorName: "flavor-1", AvailabilityZone: "az1", InstanceCount: 1}, }, }, { @@ -182,9 +190,10 @@ func TestVMwareProjectUtilizationKPI_queryProjectInstanceCount(t *testing.T) { {ID: "server-2", TenantID: "project-1", OSEXTSRVATTRHost: "nova-compute-1", FlavorName: "flavor-2", Status: "ERROR", OSEXTAvailabilityZone: "az1"}, {ID: "server-3", TenantID: "project-1", OSEXTSRVATTRHost: "nova-compute-1", FlavorName: "flavor-3", Status: "ACTIVE", OSEXTAvailabilityZone: "az1"}, }, - projects: []identity.Project{{ID: "project-1", Name: "Project One"}}, + projects: []identity.Project{{ID: "project-1", Name: "Project One", DomainID: "domain-1"}}, + domains: []identity.Domain{{ID: "domain-1", Name: "Domain One"}}, expectedCounts: map[string]vmwareProjectInstanceCount{ - "project-1|nova-compute-1|flavor-3|az1": {ProjectID: "project-1", ProjectName: "Project One", ComputeHost: "nova-compute-1", FlavorName: "flavor-3", AvailabilityZone: "az1", InstanceCount: 1}, + "project-1|nova-compute-1|flavor-3|az1": {ProjectID: "project-1", ProjectName: "Project One", DomainID: "domain-1", DomainName: "Domain One", ComputeHost: "nova-compute-1", FlavorName: "flavor-3", AvailabilityZone: "az1", InstanceCount: 1}, }, }, { @@ -195,26 +204,40 @@ func TestVMwareProjectUtilizationKPI_queryProjectInstanceCount(t *testing.T) { {ID: "server-3", TenantID: "project-1", OSEXTSRVATTRHost: "nova-compute-2", FlavorName: "flavor-1", Status: "ACTIVE", OSEXTAvailabilityZone: "az2"}, {ID: "server-4", TenantID: "project-1", OSEXTSRVATTRHost: "nova-compute-2", FlavorName: "flavor-1", Status: "ACTIVE", OSEXTAvailabilityZone: "az2"}, }, - projects: []identity.Project{{ID: "project-1", Name: "Project One"}}, + projects: []identity.Project{{ID: "project-1", Name: "Project One", DomainID: "domain-1"}}, + domains: []identity.Domain{{ID: "domain-1", Name: "Domain One"}}, expectedCounts: map[string]vmwareProjectInstanceCount{ - "project-1|nova-compute-1|flavor-1|az1": {ProjectID: "project-1", ProjectName: "Project One", ComputeHost: "nova-compute-1", FlavorName: "flavor-1", AvailabilityZone: "az1", InstanceCount: 2}, - "project-1|nova-compute-2|flavor-1|az2": {ProjectID: "project-1", ProjectName: "Project One", ComputeHost: "nova-compute-2", FlavorName: "flavor-1", AvailabilityZone: "az2", InstanceCount: 2}, + "project-1|nova-compute-1|flavor-1|az1": {ProjectID: "project-1", ProjectName: "Project One", DomainID: "domain-1", DomainName: "Domain One", ComputeHost: "nova-compute-1", FlavorName: "flavor-1", AvailabilityZone: "az1", InstanceCount: 2}, + "project-1|nova-compute-2|flavor-1|az2": {ProjectID: "project-1", ProjectName: "Project One", DomainID: "domain-1", DomainName: "Domain One", ComputeHost: "nova-compute-2", FlavorName: "flavor-1", AvailabilityZone: "az2", InstanceCount: 2}, }, }, { - name: "missing project entry results in empty project_name", + name: "project references non-existent domain results in empty domain fields", + servers: []nova.Server{ + {ID: "server-1", TenantID: "project-1", OSEXTSRVATTRHost: "nova-compute-1", FlavorName: "flavor-1", Status: "ACTIVE", OSEXTAvailabilityZone: "az1"}, + }, + projects: []identity.Project{{ID: "project-1", Name: "Project One", DomainID: "domain-unknown"}}, + domains: []identity.Domain{}, + expectedCounts: map[string]vmwareProjectInstanceCount{ + "project-1|nova-compute-1|flavor-1|az1": {ProjectID: "project-1", ProjectName: "Project One", DomainID: "", DomainName: "", ComputeHost: "nova-compute-1", FlavorName: "flavor-1", AvailabilityZone: "az1", InstanceCount: 1}, + }, + }, + { + name: "missing project entry results in empty project_name and domain", servers: []nova.Server{ {ID: "server-1", TenantID: "project-1", OSEXTSRVATTRHost: "nova-compute-1", FlavorName: "flavor-1", Status: "ACTIVE", OSEXTAvailabilityZone: "az1"}, }, projects: []identity.Project{}, + domains: []identity.Domain{}, expectedCounts: map[string]vmwareProjectInstanceCount{ - "project-1|nova-compute-1|flavor-1|az1": {ProjectID: "project-1", ProjectName: "", ComputeHost: "nova-compute-1", FlavorName: "flavor-1", AvailabilityZone: "az1", InstanceCount: 1}, + "project-1|nova-compute-1|flavor-1|az1": {ProjectID: "project-1", ProjectName: "", DomainID: "", DomainName: "", ComputeHost: "nova-compute-1", FlavorName: "flavor-1", AvailabilityZone: "az1", InstanceCount: 1}, }, }, { name: "no instances returns empty result", servers: []nova.Server{}, - projects: []identity.Project{{ID: "project-1", Name: "Project One"}}, + projects: []identity.Project{{ID: "project-1", Name: "Project One", DomainID: "domain-1"}}, + domains: []identity.Domain{{ID: "domain-1", Name: "Domain One"}}, expectedCounts: map[string]vmwareProjectInstanceCount{}, }, } @@ -227,6 +250,7 @@ func TestVMwareProjectUtilizationKPI_queryProjectInstanceCount(t *testing.T) { if err := testDB.CreateTable( testDB.AddTable(nova.Server{}), testDB.AddTable(identity.Project{}), + testDB.AddTable(identity.Domain{}), ); err != nil { t.Fatalf("failed to create tables: %v", err) } @@ -238,6 +262,9 @@ func TestVMwareProjectUtilizationKPI_queryProjectInstanceCount(t *testing.T) { for i := range tt.projects { mockData = append(mockData, &tt.projects[i]) } + for i := range tt.domains { + mockData = append(mockData, &tt.domains[i]) + } if len(mockData) > 0 { if err := testDB.Insert(mockData...); err != nil { t.Fatalf("expected no error, got %v", err) @@ -277,6 +304,7 @@ func TestVMwareProjectUtilizationKPI_queryProjectCapacityUsage(t *testing.T) { name string servers []nova.Server projects []identity.Project + domains []identity.Domain flavors []nova.Flavor expectedUsages map[string]vmwareProjectCapacityUsage }{ @@ -285,10 +313,11 @@ func TestVMwareProjectUtilizationKPI_queryProjectCapacityUsage(t *testing.T) { servers: []nova.Server{ {ID: "server-1", TenantID: "project-1", OSEXTSRVATTRHost: "nova-compute-1", FlavorName: "flavor-1", Status: "ACTIVE", OSEXTAvailabilityZone: "az1"}, }, - projects: []identity.Project{{ID: "project-1", Name: "Project One"}}, + projects: []identity.Project{{ID: "project-1", Name: "Project One", DomainID: "domain-1"}}, + domains: []identity.Domain{{ID: "domain-1", Name: "Domain One"}}, flavors: []nova.Flavor{{ID: "f1", Name: "flavor-1", VCPUs: 2, RAM: 4096, Disk: 1}}, expectedUsages: map[string]vmwareProjectCapacityUsage{ - "project-1|nova-compute-1|az1": {ProjectID: "project-1", ProjectName: "Project One", ComputeHost: "nova-compute-1", AvailabilityZone: "az1", TotalVCPUs: 2, TotalRAMMB: 4096, TotalDiskGB: 1}, + "project-1|nova-compute-1|az1": {ProjectID: "project-1", ProjectName: "Project One", DomainID: "domain-1", DomainName: "Domain One", ComputeHost: "nova-compute-1", AvailabilityZone: "az1", TotalVCPUs: 2, TotalRAMMB: 4096, TotalDiskGB: 1}, }, }, { @@ -299,16 +328,17 @@ func TestVMwareProjectUtilizationKPI_queryProjectCapacityUsage(t *testing.T) { {ID: "server-3", TenantID: "project-2", OSEXTSRVATTRHost: "nova-compute-2", FlavorName: "flavor-1", Status: "ACTIVE", OSEXTAvailabilityZone: "az2"}, }, projects: []identity.Project{ - {ID: "project-1", Name: "Project One"}, - {ID: "project-2", Name: "Project Two"}, + {ID: "project-1", Name: "Project One", DomainID: "domain-1"}, + {ID: "project-2", Name: "Project Two", DomainID: "domain-1"}, }, + domains: []identity.Domain{{ID: "domain-1", Name: "Domain One"}}, flavors: []nova.Flavor{ {ID: "f1", Name: "flavor-1", VCPUs: 2, RAM: 4096, Disk: 1}, {ID: "f2", Name: "flavor-2", VCPUs: 4, RAM: 8192, Disk: 2}, }, expectedUsages: map[string]vmwareProjectCapacityUsage{ - "project-1|nova-compute-1|az1": {ProjectID: "project-1", ProjectName: "Project One", ComputeHost: "nova-compute-1", AvailabilityZone: "az1", TotalVCPUs: 6, TotalRAMMB: 12288, TotalDiskGB: 3}, - "project-2|nova-compute-2|az2": {ProjectID: "project-2", ProjectName: "Project Two", ComputeHost: "nova-compute-2", AvailabilityZone: "az2", TotalVCPUs: 2, TotalRAMMB: 4096, TotalDiskGB: 1}, + "project-1|nova-compute-1|az1": {ProjectID: "project-1", ProjectName: "Project One", DomainID: "domain-1", DomainName: "Domain One", ComputeHost: "nova-compute-1", AvailabilityZone: "az1", TotalVCPUs: 6, TotalRAMMB: 12288, TotalDiskGB: 3}, + "project-2|nova-compute-2|az2": {ProjectID: "project-2", ProjectName: "Project Two", DomainID: "domain-1", DomainName: "Domain One", ComputeHost: "nova-compute-2", AvailabilityZone: "az2", TotalVCPUs: 2, TotalRAMMB: 4096, TotalDiskGB: 1}, }, }, { @@ -316,10 +346,11 @@ func TestVMwareProjectUtilizationKPI_queryProjectCapacityUsage(t *testing.T) { servers: []nova.Server{ {ID: "server-1", TenantID: "project-1", OSEXTSRVATTRHost: "nova-compute-1", FlavorName: "flavor-missing", Status: "ACTIVE", OSEXTAvailabilityZone: "az1"}, }, - projects: []identity.Project{{ID: "project-1", Name: "Project One"}}, + projects: []identity.Project{{ID: "project-1", Name: "Project One", DomainID: "domain-1"}}, + domains: []identity.Domain{{ID: "domain-1", Name: "Domain One"}}, flavors: []nova.Flavor{{ID: "f1", Name: "flavor-1", VCPUs: 2, RAM: 4096, Disk: 1}}, expectedUsages: map[string]vmwareProjectCapacityUsage{ - "project-1|nova-compute-1|az1": {ProjectID: "project-1", ProjectName: "Project One", ComputeHost: "nova-compute-1", AvailabilityZone: "az1", TotalVCPUs: 0, TotalRAMMB: 0, TotalDiskGB: 0}, + "project-1|nova-compute-1|az1": {ProjectID: "project-1", ProjectName: "Project One", DomainID: "domain-1", DomainName: "Domain One", ComputeHost: "nova-compute-1", AvailabilityZone: "az1", TotalVCPUs: 0, TotalRAMMB: 0, TotalDiskGB: 0}, }, }, { @@ -327,7 +358,8 @@ func TestVMwareProjectUtilizationKPI_queryProjectCapacityUsage(t *testing.T) { servers: []nova.Server{ {ID: "server-1", TenantID: "project-1", OSEXTSRVATTRHost: "node-3", FlavorName: "flavor-1", Status: "ACTIVE", OSEXTAvailabilityZone: "az1"}, }, - projects: []identity.Project{{ID: "project-1", Name: "Project One"}}, + projects: []identity.Project{{ID: "project-1", Name: "Project One", DomainID: "domain-1"}}, + domains: []identity.Domain{{ID: "domain-1", Name: "Domain One"}}, flavors: []nova.Flavor{{ID: "f1", Name: "flavor-1", VCPUs: 2, RAM: 4096, Disk: 1}}, expectedUsages: map[string]vmwareProjectCapacityUsage{}, }, @@ -336,7 +368,8 @@ func TestVMwareProjectUtilizationKPI_queryProjectCapacityUsage(t *testing.T) { servers: []nova.Server{ {ID: "server-1", TenantID: "project-1", OSEXTSRVATTRHost: "nova-compute-1", FlavorName: "flavor-1", Status: "DELETED", OSEXTAvailabilityZone: "az1"}, }, - projects: []identity.Project{{ID: "project-1", Name: "Project One"}}, + projects: []identity.Project{{ID: "project-1", Name: "Project One", DomainID: "domain-1"}}, + domains: []identity.Domain{{ID: "domain-1", Name: "Domain One"}}, flavors: []nova.Flavor{{ID: "f1", Name: "flavor-1", VCPUs: 2, RAM: 4096, Disk: 1}}, expectedUsages: map[string]vmwareProjectCapacityUsage{}, }, @@ -344,8 +377,9 @@ func TestVMwareProjectUtilizationKPI_queryProjectCapacityUsage(t *testing.T) { name: "no instances returns empty capacity usage", servers: []nova.Server{}, projects: []identity.Project{ - {ID: "project-1", Name: "Project One"}, + {ID: "project-1", Name: "Project One", DomainID: "domain-1"}, }, + domains: []identity.Domain{{ID: "domain-1", Name: "Domain One"}}, flavors: []nova.Flavor{{ID: "f1", Name: "flavor-1", VCPUs: 2, RAM: 4096, Disk: 1}}, expectedUsages: map[string]vmwareProjectCapacityUsage{}, }, @@ -355,10 +389,11 @@ func TestVMwareProjectUtilizationKPI_queryProjectCapacityUsage(t *testing.T) { {ID: "server-1", TenantID: "project-1", OSEXTSRVATTRHost: "nova-compute-1", FlavorName: "flavor-1", Status: "ACTIVE", OSEXTAvailabilityZone: "az1"}, {ID: "server-2", TenantID: "project-1", OSEXTSRVATTRHost: "nova-compute-1", FlavorName: "flavor-1", Status: "ACTIVE", OSEXTAvailabilityZone: "az1"}, }, - projects: []identity.Project{{ID: "project-1", Name: "Project One"}}, + projects: []identity.Project{{ID: "project-1", Name: "Project One", DomainID: "domain-1"}}, + domains: []identity.Domain{{ID: "domain-1", Name: "Domain One"}}, flavors: []nova.Flavor{{ID: "f1", Name: "flavor-1", VCPUs: 2, RAM: 4096, Disk: 1}}, expectedUsages: map[string]vmwareProjectCapacityUsage{ - "project-1|nova-compute-1|az1": {ProjectID: "project-1", ProjectName: "Project One", ComputeHost: "nova-compute-1", AvailabilityZone: "az1", TotalVCPUs: 4, TotalRAMMB: 8192, TotalDiskGB: 2}, + "project-1|nova-compute-1|az1": {ProjectID: "project-1", ProjectName: "Project One", DomainID: "domain-1", DomainName: "Domain One", ComputeHost: "nova-compute-1", AvailabilityZone: "az1", TotalVCPUs: 4, TotalRAMMB: 8192, TotalDiskGB: 2}, }, }, { @@ -366,19 +401,33 @@ func TestVMwareProjectUtilizationKPI_queryProjectCapacityUsage(t *testing.T) { servers: []nova.Server{ {ID: "server-1", TenantID: "project-1", OSEXTSRVATTRHost: "nova-compute-ironic-1", FlavorName: "flavor-1", Status: "ACTIVE", OSEXTAvailabilityZone: "az1"}, }, - projects: []identity.Project{{ID: "project-1", Name: "Project One"}}, + projects: []identity.Project{{ID: "project-1", Name: "Project One", DomainID: "domain-1"}}, + domains: []identity.Domain{{ID: "domain-1", Name: "Domain One"}}, flavors: []nova.Flavor{{ID: "f1", Name: "flavor-1", VCPUs: 2, RAM: 4096, Disk: 1}}, expectedUsages: map[string]vmwareProjectCapacityUsage{}, }, { - name: "missing project entry results in empty project_name", + name: "project references non-existent domain results in empty domain fields", + servers: []nova.Server{ + {ID: "server-1", TenantID: "project-1", OSEXTSRVATTRHost: "nova-compute-1", FlavorName: "flavor-1", Status: "ACTIVE", OSEXTAvailabilityZone: "az1"}, + }, + projects: []identity.Project{{ID: "project-1", Name: "Project One", DomainID: "domain-unknown"}}, + domains: []identity.Domain{}, + flavors: []nova.Flavor{{ID: "f1", Name: "flavor-1", VCPUs: 2, RAM: 4096, Disk: 1}}, + expectedUsages: map[string]vmwareProjectCapacityUsage{ + "project-1|nova-compute-1|az1": {ProjectID: "project-1", ProjectName: "Project One", DomainID: "", DomainName: "", ComputeHost: "nova-compute-1", AvailabilityZone: "az1", TotalVCPUs: 2, TotalRAMMB: 4096, TotalDiskGB: 1}, + }, + }, + { + name: "missing project entry results in empty project_name and domain", servers: []nova.Server{ {ID: "server-1", TenantID: "project-1", OSEXTSRVATTRHost: "nova-compute-1", FlavorName: "flavor-1", Status: "ACTIVE", OSEXTAvailabilityZone: "az1"}, }, projects: []identity.Project{}, + domains: []identity.Domain{}, flavors: []nova.Flavor{{ID: "f1", Name: "flavor-1", VCPUs: 2, RAM: 4096, Disk: 1}}, expectedUsages: map[string]vmwareProjectCapacityUsage{ - "project-1|nova-compute-1|az1": {ProjectID: "project-1", ProjectName: "", ComputeHost: "nova-compute-1", AvailabilityZone: "az1", TotalVCPUs: 2, TotalRAMMB: 4096, TotalDiskGB: 1}, + "project-1|nova-compute-1|az1": {ProjectID: "project-1", ProjectName: "", DomainID: "", DomainName: "", ComputeHost: "nova-compute-1", AvailabilityZone: "az1", TotalVCPUs: 2, TotalRAMMB: 4096, TotalDiskGB: 1}, }, }, } @@ -392,6 +441,7 @@ func TestVMwareProjectUtilizationKPI_queryProjectCapacityUsage(t *testing.T) { if err := testDB.CreateTable( testDB.AddTable(nova.Server{}), testDB.AddTable(identity.Project{}), + testDB.AddTable(identity.Domain{}), testDB.AddTable(nova.Flavor{}), ); err != nil { t.Fatalf("failed to create tables: %v", err) @@ -404,6 +454,9 @@ func TestVMwareProjectUtilizationKPI_queryProjectCapacityUsage(t *testing.T) { for i := range tt.projects { mockData = append(mockData, &tt.projects[i]) } + for i := range tt.domains { + mockData = append(mockData, &tt.domains[i]) + } for i := range tt.flavors { mockData = append(mockData, &tt.flavors[i]) } @@ -446,6 +499,7 @@ func TestVMwareProjectUtilizationKPI_Collect(t *testing.T) { name string servers []nova.Server projects []identity.Project + domains []identity.Domain flavors []nova.Flavor hostDetails []compute.HostDetails expectedMetrics []collectedVMwareMetric @@ -455,16 +509,17 @@ func TestVMwareProjectUtilizationKPI_Collect(t *testing.T) { servers: []nova.Server{ {ID: "s1", TenantID: "project-1", OSEXTSRVATTRHost: "nova-compute-1", FlavorName: "flavor-1", Status: "ACTIVE", OSEXTAvailabilityZone: "az1"}, }, - projects: []identity.Project{{ID: "project-1", Name: "Project One"}}, + projects: []identity.Project{{ID: "project-1", Name: "Project One", DomainID: "domain-1"}}, + domains: []identity.Domain{{ID: "domain-1", Name: "Domain One"}}, flavors: []nova.Flavor{{ID: "f1", Name: "flavor-1", VCPUs: 2, RAM: 4096, Disk: 1}}, hostDetails: []compute.HostDetails{ {ComputeHost: "nova-compute-1", HypervisorFamily: hypervisorFamilyVMware, AvailabilityZone: "az1"}, }, expectedMetrics: []collectedVMwareMetric{ - instanceMetric("nova-compute-1", "az1", "project-1", "Project One", "flavor-1", 1), - capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "vcpu", 2), - capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "memory", 4096*1024*1024), - capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "disk", 1*1024*1024*1024), + instanceMetric("nova-compute-1", "az1", "project-1", "Project One", "domain-1", "Domain One", "flavor-1", 1), + capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "domain-1", "Domain One", "vcpu", 2), + capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "domain-1", "Domain One", "memory", 4096*1024*1024), + capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "domain-1", "Domain One", "disk", 1*1024*1024*1024), }, }, { @@ -475,9 +530,10 @@ func TestVMwareProjectUtilizationKPI_Collect(t *testing.T) { {ID: "s3", TenantID: "project-2", OSEXTSRVATTRHost: "nova-compute-2", FlavorName: "flavor-1", Status: "ACTIVE", OSEXTAvailabilityZone: "az2"}, }, projects: []identity.Project{ - {ID: "project-1", Name: "Project One"}, - {ID: "project-2", Name: "Project Two"}, + {ID: "project-1", Name: "Project One", DomainID: "domain-1"}, + {ID: "project-2", Name: "Project Two", DomainID: "domain-1"}, }, + domains: []identity.Domain{{ID: "domain-1", Name: "Domain One"}}, flavors: []nova.Flavor{ {ID: "f1", Name: "flavor-1", VCPUs: 2, RAM: 4096, Disk: 1}, {ID: "f2", Name: "flavor-2", VCPUs: 4, RAM: 8192, Disk: 2}, @@ -487,17 +543,17 @@ func TestVMwareProjectUtilizationKPI_Collect(t *testing.T) { {ComputeHost: "nova-compute-2", HypervisorFamily: hypervisorFamilyVMware, AvailabilityZone: "az2"}, }, expectedMetrics: []collectedVMwareMetric{ - instanceMetric("nova-compute-1", "az1", "project-1", "Project One", "flavor-1", 1), - instanceMetric("nova-compute-1", "az1", "project-1", "Project One", "flavor-2", 1), - instanceMetric("nova-compute-2", "az2", "project-2", "Project Two", "flavor-1", 1), + instanceMetric("nova-compute-1", "az1", "project-1", "Project One", "domain-1", "Domain One", "flavor-1", 1), + instanceMetric("nova-compute-1", "az1", "project-1", "Project One", "domain-1", "Domain One", "flavor-2", 1), + instanceMetric("nova-compute-2", "az2", "project-2", "Project Two", "domain-1", "Domain One", "flavor-1", 1), // nova-compute-1/project-1: 1*flavor-1 + 1*flavor-2 - capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "vcpu", 6), - capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "memory", 12288*1024*1024), - capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "disk", 3*1024*1024*1024), + capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "domain-1", "Domain One", "vcpu", 6), + capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "domain-1", "Domain One", "memory", 12288*1024*1024), + capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "domain-1", "Domain One", "disk", 3*1024*1024*1024), // nova-compute-2/project-2: 1*flavor-1 - capacityMetric("nova-compute-2", "az2", "project-2", "Project Two", "vcpu", 2), - capacityMetric("nova-compute-2", "az2", "project-2", "Project Two", "memory", 4096*1024*1024), - capacityMetric("nova-compute-2", "az2", "project-2", "Project Two", "disk", 1*1024*1024*1024), + capacityMetric("nova-compute-2", "az2", "project-2", "Project Two", "domain-1", "Domain One", "vcpu", 2), + capacityMetric("nova-compute-2", "az2", "project-2", "Project Two", "domain-1", "Domain One", "memory", 4096*1024*1024), + capacityMetric("nova-compute-2", "az2", "project-2", "Project Two", "domain-1", "Domain One", "disk", 1*1024*1024*1024), }, }, { @@ -507,16 +563,17 @@ func TestVMwareProjectUtilizationKPI_Collect(t *testing.T) { {ID: "s2", TenantID: "project-1", OSEXTSRVATTRHost: "node-3", FlavorName: "flavor-1", Status: "ACTIVE", OSEXTAvailabilityZone: "az1"}, {ID: "s3", TenantID: "project-1", OSEXTSRVATTRHost: "nova-compute-ironic-1", FlavorName: "flavor-1", Status: "ACTIVE", OSEXTAvailabilityZone: "az1"}, }, - projects: []identity.Project{{ID: "project-1", Name: "Project One"}}, + projects: []identity.Project{{ID: "project-1", Name: "Project One", DomainID: "domain-1"}}, + domains: []identity.Domain{{ID: "domain-1", Name: "Domain One"}}, flavors: []nova.Flavor{{ID: "f1", Name: "flavor-1", VCPUs: 2, RAM: 4096, Disk: 1}}, hostDetails: []compute.HostDetails{ {ComputeHost: "nova-compute-1", HypervisorFamily: hypervisorFamilyVMware, AvailabilityZone: "az1"}, }, expectedMetrics: []collectedVMwareMetric{ - instanceMetric("nova-compute-1", "az1", "project-1", "Project One", "flavor-1", 1), - capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "vcpu", 2), - capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "memory", 4096*1024*1024), - capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "disk", 1*1024*1024*1024), + instanceMetric("nova-compute-1", "az1", "project-1", "Project One", "domain-1", "Domain One", "flavor-1", 1), + capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "domain-1", "Domain One", "vcpu", 2), + capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "domain-1", "Domain One", "memory", 4096*1024*1024), + capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "domain-1", "Domain One", "disk", 1*1024*1024*1024), }, }, { @@ -526,7 +583,8 @@ func TestVMwareProjectUtilizationKPI_Collect(t *testing.T) { {ID: "s2", TenantID: "project-1", OSEXTSRVATTRHost: "nova-compute-1", FlavorName: "flavor-2", Status: "ERROR", OSEXTAvailabilityZone: "az1"}, {ID: "s3", TenantID: "project-1", OSEXTSRVATTRHost: "nova-compute-1", FlavorName: "flavor-3", Status: "ACTIVE", OSEXTAvailabilityZone: "az1"}, }, - projects: []identity.Project{{ID: "project-1", Name: "Project One"}}, + projects: []identity.Project{{ID: "project-1", Name: "Project One", DomainID: "domain-1"}}, + domains: []identity.Domain{{ID: "domain-1", Name: "Domain One"}}, flavors: []nova.Flavor{ {ID: "f1", Name: "flavor-1", VCPUs: 2, RAM: 4096, Disk: 1}, {ID: "f2", Name: "flavor-2", VCPUs: 4, RAM: 8192, Disk: 2}, @@ -536,10 +594,10 @@ func TestVMwareProjectUtilizationKPI_Collect(t *testing.T) { {ComputeHost: "nova-compute-1", HypervisorFamily: hypervisorFamilyVMware, AvailabilityZone: "az1"}, }, expectedMetrics: []collectedVMwareMetric{ - instanceMetric("nova-compute-1", "az1", "project-1", "Project One", "flavor-3", 1), - capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "vcpu", 8), - capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "memory", 16384*1024*1024), - capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "disk", 4*1024*1024*1024), + instanceMetric("nova-compute-1", "az1", "project-1", "Project One", "domain-1", "Domain One", "flavor-3", 1), + capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "domain-1", "Domain One", "vcpu", 8), + capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "domain-1", "Domain One", "memory", 16384*1024*1024), + capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "domain-1", "Domain One", "disk", 4*1024*1024*1024), }, }, { @@ -550,38 +608,58 @@ func TestVMwareProjectUtilizationKPI_Collect(t *testing.T) { {ID: "s3", TenantID: "project-1", OSEXTSRVATTRHost: "nova-compute-2", FlavorName: "flavor-1", Status: "ACTIVE", OSEXTAvailabilityZone: "az2"}, {ID: "s4", TenantID: "project-1", OSEXTSRVATTRHost: "nova-compute-2", FlavorName: "flavor-1", Status: "ACTIVE", OSEXTAvailabilityZone: "az2"}, }, - projects: []identity.Project{{ID: "project-1", Name: "Project One"}}, + projects: []identity.Project{{ID: "project-1", Name: "Project One", DomainID: "domain-1"}}, + domains: []identity.Domain{{ID: "domain-1", Name: "Domain One"}}, flavors: []nova.Flavor{{ID: "f1", Name: "flavor-1", VCPUs: 2, RAM: 4096, Disk: 1}}, hostDetails: []compute.HostDetails{ {ComputeHost: "nova-compute-1", HypervisorFamily: hypervisorFamilyVMware, AvailabilityZone: "az1"}, {ComputeHost: "nova-compute-2", HypervisorFamily: hypervisorFamilyVMware, AvailabilityZone: "az2"}, }, expectedMetrics: []collectedVMwareMetric{ - instanceMetric("nova-compute-1", "az1", "project-1", "Project One", "flavor-1", 2), - instanceMetric("nova-compute-2", "az2", "project-1", "Project One", "flavor-1", 2), - capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "vcpu", 4), - capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "memory", 2*4096*1024*1024), - capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "disk", 2*1024*1024*1024), - capacityMetric("nova-compute-2", "az2", "project-1", "Project One", "vcpu", 4), - capacityMetric("nova-compute-2", "az2", "project-1", "Project One", "memory", 2*4096*1024*1024), - capacityMetric("nova-compute-2", "az2", "project-1", "Project One", "disk", 2*1024*1024*1024), + instanceMetric("nova-compute-1", "az1", "project-1", "Project One", "domain-1", "Domain One", "flavor-1", 2), + instanceMetric("nova-compute-2", "az2", "project-1", "Project One", "domain-1", "Domain One", "flavor-1", 2), + capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "domain-1", "Domain One", "vcpu", 4), + capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "domain-1", "Domain One", "memory", 2*4096*1024*1024), + capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "domain-1", "Domain One", "disk", 2*1024*1024*1024), + capacityMetric("nova-compute-2", "az2", "project-1", "Project One", "domain-1", "Domain One", "vcpu", 4), + capacityMetric("nova-compute-2", "az2", "project-1", "Project One", "domain-1", "Domain One", "memory", 2*4096*1024*1024), + capacityMetric("nova-compute-2", "az2", "project-1", "Project One", "domain-1", "Domain One", "disk", 2*1024*1024*1024), + }, + }, + { + name: "project references non-existent domain results in empty domain labels", + servers: []nova.Server{ + {ID: "s1", TenantID: "project-1", OSEXTSRVATTRHost: "nova-compute-1", FlavorName: "flavor-1", Status: "ACTIVE", OSEXTAvailabilityZone: "az1"}, + }, + projects: []identity.Project{{ID: "project-1", Name: "Project One", DomainID: "domain-unknown"}}, + domains: []identity.Domain{}, + flavors: []nova.Flavor{{ID: "f1", Name: "flavor-1", VCPUs: 2, RAM: 4096, Disk: 1}}, + hostDetails: []compute.HostDetails{ + {ComputeHost: "nova-compute-1", HypervisorFamily: hypervisorFamilyVMware, AvailabilityZone: "az1"}, + }, + expectedMetrics: []collectedVMwareMetric{ + instanceMetric("nova-compute-1", "az1", "project-1", "Project One", "", "", "flavor-1", 1), + capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "", "", "vcpu", 2), + capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "", "", "memory", 4096*1024*1024), + capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "", "", "disk", 1*1024*1024*1024), }, }, { - name: "missing project entry results in empty project_name label", + name: "missing project entry results in empty project_name and domain labels", servers: []nova.Server{ {ID: "s1", TenantID: "project-1", OSEXTSRVATTRHost: "nova-compute-1", FlavorName: "flavor-1", Status: "ACTIVE", OSEXTAvailabilityZone: "az1"}, }, projects: []identity.Project{}, + domains: []identity.Domain{}, flavors: []nova.Flavor{{ID: "f1", Name: "flavor-1", VCPUs: 2, RAM: 4096, Disk: 1}}, hostDetails: []compute.HostDetails{ {ComputeHost: "nova-compute-1", HypervisorFamily: hypervisorFamilyVMware, AvailabilityZone: "az1"}, }, expectedMetrics: []collectedVMwareMetric{ - instanceMetric("nova-compute-1", "az1", "project-1", "", "flavor-1", 1), - capacityMetric("nova-compute-1", "az1", "project-1", "", "vcpu", 2), - capacityMetric("nova-compute-1", "az1", "project-1", "", "memory", 4096*1024*1024), - capacityMetric("nova-compute-1", "az1", "project-1", "", "disk", 1*1024*1024*1024), + instanceMetric("nova-compute-1", "az1", "project-1", "", "", "", "flavor-1", 1), + capacityMetric("nova-compute-1", "az1", "project-1", "", "", "", "vcpu", 2), + capacityMetric("nova-compute-1", "az1", "project-1", "", "", "", "memory", 4096*1024*1024), + capacityMetric("nova-compute-1", "az1", "project-1", "", "", "", "disk", 1*1024*1024*1024), }, }, { @@ -589,24 +667,26 @@ func TestVMwareProjectUtilizationKPI_Collect(t *testing.T) { servers: []nova.Server{ {ID: "s1", TenantID: "project-1", OSEXTSRVATTRHost: "nova-compute-1", FlavorName: "flavor-missing", Status: "ACTIVE", OSEXTAvailabilityZone: "az1"}, }, - projects: []identity.Project{{ID: "project-1", Name: "Project One"}}, + projects: []identity.Project{{ID: "project-1", Name: "Project One", DomainID: "domain-1"}}, + domains: []identity.Domain{{ID: "domain-1", Name: "Domain One"}}, flavors: []nova.Flavor{}, hostDetails: []compute.HostDetails{ {ComputeHost: "nova-compute-1", HypervisorFamily: hypervisorFamilyVMware, AvailabilityZone: "az1"}, }, expectedMetrics: []collectedVMwareMetric{ - instanceMetric("nova-compute-1", "az1", "project-1", "Project One", "flavor-missing", 1), - capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "vcpu", 0), - capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "memory", 0), - capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "disk", 0), + instanceMetric("nova-compute-1", "az1", "project-1", "Project One", "domain-1", "Domain One", "flavor-missing", 1), + capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "domain-1", "Domain One", "vcpu", 0), + capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "domain-1", "Domain One", "memory", 0), + capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "domain-1", "Domain One", "disk", 0), }, }, { name: "no instances produces no metrics", servers: []nova.Server{}, projects: []identity.Project{ - {ID: "project-1", Name: "Project One"}, + {ID: "project-1", Name: "Project One", DomainID: "domain-1"}, }, + domains: []identity.Domain{{ID: "domain-1", Name: "Domain One"}}, flavors: []nova.Flavor{ {ID: "f1", Name: "flavor-1", VCPUs: 2, RAM: 4096, Disk: 1}, }, @@ -626,6 +706,7 @@ func TestVMwareProjectUtilizationKPI_Collect(t *testing.T) { if err := testDB.CreateTable( testDB.AddTable(nova.Server{}), testDB.AddTable(identity.Project{}), + testDB.AddTable(identity.Domain{}), testDB.AddTable(nova.Flavor{}), ); err != nil { t.Fatalf("failed to create tables: %v", err) @@ -638,6 +719,9 @@ func TestVMwareProjectUtilizationKPI_Collect(t *testing.T) { for i := range tt.projects { mockData = append(mockData, &tt.projects[i]) } + for i := range tt.domains { + mockData = append(mockData, &tt.domains[i]) + } for i := range tt.flavors { mockData = append(mockData, &tt.flavors[i]) } From 4d1b2196a4cd2200c9eb0de12c1be78b77fc0a1a Mon Sep 17 00:00:00 2001 From: Markus Wieland Date: Wed, 6 May 2026 09:04:11 +0200 Subject: [PATCH 2/4] fix: update domain_id extraction in VMware project utilization tests Co-authored-by: Copilot --- .../vmware_project_utilization_test.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/internal/knowledge/kpis/plugins/infrastructure/vmware_project_utilization_test.go b/internal/knowledge/kpis/plugins/infrastructure/vmware_project_utilization_test.go index d6e650399..853b88d2c 100644 --- a/internal/knowledge/kpis/plugins/infrastructure/vmware_project_utilization_test.go +++ b/internal/knowledge/kpis/plugins/infrastructure/vmware_project_utilization_test.go @@ -219,7 +219,8 @@ func TestVMwareProjectUtilizationKPI_queryProjectInstanceCount(t *testing.T) { projects: []identity.Project{{ID: "project-1", Name: "Project One", DomainID: "domain-unknown"}}, domains: []identity.Domain{}, expectedCounts: map[string]vmwareProjectInstanceCount{ - "project-1|nova-compute-1|flavor-1|az1": {ProjectID: "project-1", ProjectName: "Project One", DomainID: "", DomainName: "", ComputeHost: "nova-compute-1", FlavorName: "flavor-1", AvailabilityZone: "az1", InstanceCount: 1}, + // The domain_id is extracted from the project record, so it should be "domain-unknown" even though there is no matching domain entry + "project-1|nova-compute-1|flavor-1|az1": {ProjectID: "project-1", ProjectName: "Project One", DomainID: "domain-unknown", DomainName: "", ComputeHost: "nova-compute-1", FlavorName: "flavor-1", AvailabilityZone: "az1", InstanceCount: 1}, }, }, { @@ -415,7 +416,8 @@ func TestVMwareProjectUtilizationKPI_queryProjectCapacityUsage(t *testing.T) { domains: []identity.Domain{}, flavors: []nova.Flavor{{ID: "f1", Name: "flavor-1", VCPUs: 2, RAM: 4096, Disk: 1}}, expectedUsages: map[string]vmwareProjectCapacityUsage{ - "project-1|nova-compute-1|az1": {ProjectID: "project-1", ProjectName: "Project One", DomainID: "", DomainName: "", ComputeHost: "nova-compute-1", AvailabilityZone: "az1", TotalVCPUs: 2, TotalRAMMB: 4096, TotalDiskGB: 1}, + // The domain_id is extracted from the project record, so it should be "domain-unknown" even though there is no matching domain entry + "project-1|nova-compute-1|az1": {ProjectID: "project-1", ProjectName: "Project One", DomainID: "domain-unknown", DomainName: "", ComputeHost: "nova-compute-1", AvailabilityZone: "az1", TotalVCPUs: 2, TotalRAMMB: 4096, TotalDiskGB: 1}, }, }, { @@ -638,10 +640,11 @@ func TestVMwareProjectUtilizationKPI_Collect(t *testing.T) { {ComputeHost: "nova-compute-1", HypervisorFamily: hypervisorFamilyVMware, AvailabilityZone: "az1"}, }, expectedMetrics: []collectedVMwareMetric{ - instanceMetric("nova-compute-1", "az1", "project-1", "Project One", "", "", "flavor-1", 1), - capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "", "", "vcpu", 2), - capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "", "", "memory", 4096*1024*1024), - capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "", "", "disk", 1*1024*1024*1024), + // The domain_id is extracted from the project record, so it should be "domain-unknown" even though there is no matching domain entry + instanceMetric("nova-compute-1", "az1", "project-1", "Project One", "domain-unknown", "", "flavor-1", 1), + capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "domain-unknown", "", "vcpu", 2), + capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "domain-unknown", "", "memory", 4096*1024*1024), + capacityMetric("nova-compute-1", "az1", "project-1", "Project One", "domain-unknown", "", "disk", 1*1024*1024*1024), }, }, { From d8d5c1e67cc2f0f59e9f9ba0aa2c12ef70918491 Mon Sep 17 00:00:00 2001 From: Markus Wieland Date: Wed, 6 May 2026 09:11:31 +0200 Subject: [PATCH 3/4] fix: update GROUP BY clause to include domain_id in VMware project instance count query Co-authored-by: Copilot --- .../kpis/plugins/infrastructure/vmware_project_utilization.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/knowledge/kpis/plugins/infrastructure/vmware_project_utilization.go b/internal/knowledge/kpis/plugins/infrastructure/vmware_project_utilization.go index aaab2718e..30b323164 100644 --- a/internal/knowledge/kpis/plugins/infrastructure/vmware_project_utilization.go +++ b/internal/knowledge/kpis/plugins/infrastructure/vmware_project_utilization.go @@ -217,7 +217,7 @@ func (k *VMwareProjectUtilizationKPI) queryProjectInstanceCount() ([]vmwareProje WHERE s.status NOT IN ('DELETED', 'ERROR') AND s.os_ext_srv_attr_host LIKE '` + vmwareComputeHostPattern + `' AND s.os_ext_srv_attr_host NOT LIKE '` + vmwareIronicComputeHostPattern + `' - GROUP BY s.tenant_id, p.name, d.id, d.name, s.os_ext_srv_attr_host, s.flavor_name, s.os_ext_az_availability_zone + GROUP BY s.tenant_id, p.name, p.domain_id, d.name, s.os_ext_srv_attr_host, s.flavor_name, s.os_ext_az_availability_zone ` var usages []vmwareProjectInstanceCount if _, err := k.DB.Select(&usages, query); err != nil { From eab5db5006c7adf3c3084cddc72c6e394470d4f6 Mon Sep 17 00:00:00 2001 From: Markus Wieland Date: Wed, 6 May 2026 09:13:51 +0200 Subject: [PATCH 4/4] fix: correct domain_id extraction in project capacity usage query --- .../kpis/plugins/infrastructure/vmware_project_utilization.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/knowledge/kpis/plugins/infrastructure/vmware_project_utilization.go b/internal/knowledge/kpis/plugins/infrastructure/vmware_project_utilization.go index 30b323164..bf9fb995b 100644 --- a/internal/knowledge/kpis/plugins/infrastructure/vmware_project_utilization.go +++ b/internal/knowledge/kpis/plugins/infrastructure/vmware_project_utilization.go @@ -171,7 +171,7 @@ func (k *VMwareProjectUtilizationKPI) queryProjectCapacityUsage() ([]vmwareProje SELECT s.tenant_id AS project_id, COALESCE(p.name, '') AS project_name, - COALESCE(d.id, '') AS domain_id, + COALESCE(p.domain_id, '') AS domain_id, COALESCE(d.name, '') AS domain_name, s.os_ext_srv_attr_host AS compute_host, s.os_ext_az_availability_zone AS availability_zone,