Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
Neutron (number of Provider Network Fixed IPs)
Swift/S3 (Storage in GiB)
Manila (Storage in GiB)
Volume types

```
select vl.project_id, vt.name, sum(vl.size) from volumes vl left join volume_types vt on vl.volume_type_id = vt.id group by project_id, volume_type_id;
openstack_project_volume_size_gb{volume_type="SSD"}
```
78 changes: 78 additions & 0 deletions exporters/nova_trait.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package exporters

import (
"database/sql"
"log"
"strings"

"github.com/prometheus/client_golang/prometheus"
)

type NovaTraitUsageExporter struct {
db *sql.DB
trait string
vcpus *prometheus.Desc
instances *prometheus.Desc
}

func NewNovaTraitUsageExporter(db *sql.DB, trait string) (*NovaTraitUsageExporter, error) {
return &NovaTraitUsageExporter{
db: db,
trait: trait,
vcpus: prometheus.NewDesc(
"openstack_project_vcpus_trait__"+strings.ToLower(trait),
"Total number of vcpus per OpenStack project for instances with image trait "+trait,
[]string{"project_id"}, nil,
),
instances: prometheus.NewDesc(
"openstack_project_instances_trait__"+strings.ToLower(trait),
"Total number of instances per OpenStack project with image trait "+trait,
[]string{"project_id"}, nil,
),
}, nil
}

func (e *NovaTraitUsageExporter) Describe(ch chan<- *prometheus.Desc) {
ch <- e.vcpus
ch <- e.instances
}

func (e *NovaTraitUsageExporter) Collect(ch chan<- prometheus.Metric) {
e.collectMetrics(ch)
}

func (e *NovaTraitUsageExporter) collectMetrics(ch chan<- prometheus.Metric) {
rows, err := e.db.Query("SELECT i.project_id AS project_id, COUNT(i.id) AS total_instances, SUM(vcpus) AS total_vcpus FROM instances i INNER JOIN instance_system_metadata m on i.uuid = m.instance_uuid WHERE i.deleted = 0 AND m.key = ? and m.value = 'required' GROUP BY project_id", "image_trait:"+e.trait)
if err != nil {
log.Println("Error querying Nova database:", err)
return
}
defer rows.Close()

for rows.Next() {
var projectID string
var totalVcpus float64
var totalInstances float64
if err := rows.Scan(&projectID, &totalVcpus, &totalInstances); err != nil {
log.Println("Error scanning Nova row:", err)
continue
}

ch <- prometheus.MustNewConstMetric(
e.vcpus,
prometheus.GaugeValue,
totalVcpus,
projectID,
)

ch <- prometheus.MustNewConstMetric(
e.instances,
prometheus.GaugeValue,
totalInstances,
projectID,
)
}
if err := rows.Err(); err != nil {
log.Println("Error in Nova result set:", err)
}
}
47 changes: 47 additions & 0 deletions exporters/nova_trait_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package exporters

import (
"strings"
"testing"

"github.com/DATA-DOG/go-sqlmock"
"github.com/prometheus/client_golang/prometheus/testutil"
)

func TestNovaTraitUsageExporter(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("Failed to create sqlmock: %v", err)
}
defer db.Close()

rows := sqlmock.NewRows([]string{"project_id", "total_instances", "total_vcpus"}).
AddRow("c352b0ed-30ca-4634-9c2d-1947efc29096", 0, 0).
AddRow("6ee08ba2-2ca1-4c91-b139-4bf0dbaa4096", 4, 5)
mock.ExpectQuery("SELECT").WillReturnRows(rows)

exporter, err := NewNovaTraitUsageExporter(db, "CUSTOM_TRAIT")
if err != nil {
t.Fatalf("Failed to create NewNovaTraitUsageExporter: %v", err)
}

expectedMetrics := `
# HELP openstack_project_instances_trait__custom_trait Total number of instances per OpenStack project with image trait CUSTOM_TRAIT
# TYPE openstack_project_instances_trait__custom_trait gauge
openstack_project_instances_trait__custom_trait{project_id="6ee08ba2-2ca1-4c91-b139-4bf0dbaa4096"} 5
openstack_project_instances_trait__custom_trait{project_id="c352b0ed-30ca-4634-9c2d-1947efc29096"} 0
# HELP openstack_project_vcpus_trait__custom_trait Total number of vcpus per OpenStack project for instances with image trait CUSTOM_TRAIT
# TYPE openstack_project_vcpus_trait__custom_trait gauge
openstack_project_vcpus_trait__custom_trait{project_id="6ee08ba2-2ca1-4c91-b139-4bf0dbaa4096"} 4
openstack_project_vcpus_trait__custom_trait{project_id="c352b0ed-30ca-4634-9c2d-1947efc29096"} 0

`

if err := testutil.CollectAndCompare(exporter, strings.NewReader(expectedMetrics)); err != nil {
t.Errorf("unexpected collecting result:\n%s", err)
}

if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("There were unfulfilled expectations: %s", err)
}
}
21 changes: 14 additions & 7 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,21 @@ func main() {
}

enabledExporters := map[string]bool{
"cinder": GetBoolEnv("CINDER_ENABLED", true),
"nova": GetBoolEnv("NOVA_ENABLED", true),
"neutron": GetBoolEnv("NEUTRON_ENABLED", true),
"designate": GetBoolEnv("DESIGNATE_ENABLED", true),
"octavia": GetBoolEnv("OCTAVIA_ENABLED", true),
"manila": GetBoolEnv("MANILA_ENABLED", false),
"cinder": GetBoolEnv("CINDER_ENABLED", true),
"nova": GetBoolEnv("NOVA_ENABLED", true),
"nova-trait": GetBoolEnv("NOVA_TRAIT_ENABLED", false),
"neutron": GetBoolEnv("NEUTRON_ENABLED", true),
"designate": GetBoolEnv("DESIGNATE_ENABLED", true),
"octavia": GetBoolEnv("OCTAVIA_ENABLED", true),
"manila": GetBoolEnv("MANILA_ENABLED", false),
}

for name, enabled := range enabledExporters {
if !enabled {
continue
}

dsn := baseDSN + "/" + name
dsn := baseDSN + "/" + strings.Split(name, "-")[0]

db, err := sql.Open("mysql", dsn)
if err != nil {
Expand All @@ -61,6 +62,12 @@ func main() {
exporter, err = exporters.NewCinderUsageExporter(db)
case "nova":
exporter, err = exporters.NewNovaUsageExporter(db)
case "nova-trait":
trait, exists := os.LookupEnv("NOVA_TRAIT")
if !exists {
log.Fatalf("NOVA_TRAIT not set")
}
exporter, err = exporters.NewNovaTraitUsageExporter(db, trait)
case "neutron":
exporter, err = exporters.NewNeutronUsageExporter(db)
case "designate":
Expand Down
Loading