Skip to content
Open
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
5 changes: 5 additions & 0 deletions cmd/openstack-database-exporter/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ var (
"glance.database-url",
"Glance database connection URL (oslo.db format)",
).Envar("GLANCE_DATABASE_URL").String()
heatDatabaseURL = kingpin.Flag(
"heat.database-url",
"Heat database connection URL (oslo.db format)",
).Envar("HEAT_DATABASE_URL").String()
keystoneDatabaseURL = kingpin.Flag(
"keystone.database-url",
"Keystone database connection URL (oslo.db format)",
Expand Down Expand Up @@ -73,6 +77,7 @@ func main() {
reg := collector.NewRegistry(collector.Config{
CinderDatabaseURL: *cinderDatabaseURL,
GlanceDatabaseURL: *glanceDatabaseURL,
HeatDatabaseURL: *heatDatabaseURL,
KeystoneDatabaseURL: *keystoneDatabaseURL,
MagnumDatabaseURL: *magnumDatabaseURL,
ManilaDatabaseURL: *manilaDatabaseURL,
Expand Down
3 changes: 3 additions & 0 deletions internal/collector/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/vexxhost/openstack_database_exporter/internal/collector/cinder"
"github.com/vexxhost/openstack_database_exporter/internal/collector/glance"
"github.com/vexxhost/openstack_database_exporter/internal/collector/heat"
"github.com/vexxhost/openstack_database_exporter/internal/collector/keystone"
"github.com/vexxhost/openstack_database_exporter/internal/collector/magnum"
"github.com/vexxhost/openstack_database_exporter/internal/collector/manila"
Expand All @@ -22,6 +23,7 @@ const (
type Config struct {
CinderDatabaseURL string
GlanceDatabaseURL string
HeatDatabaseURL string
KeystoneDatabaseURL string
MagnumDatabaseURL string
ManilaDatabaseURL string
Expand All @@ -35,6 +37,7 @@ func NewRegistry(cfg Config, logger *slog.Logger) *prometheus.Registry {

cinder.RegisterCollectors(reg, cfg.CinderDatabaseURL, logger)
glance.RegisterCollectors(reg, cfg.GlanceDatabaseURL, logger)
heat.RegisterCollectors(reg, cfg.HeatDatabaseURL, logger)
keystone.RegisterCollectors(reg, cfg.KeystoneDatabaseURL, logger)
magnum.RegisterCollectors(reg, cfg.MagnumDatabaseURL, logger)
manila.RegisterCollectors(reg, cfg.ManilaDatabaseURL, logger)
Expand Down
30 changes: 30 additions & 0 deletions internal/collector/heat/heat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package heat

import (
"log/slog"

"github.com/prometheus/client_golang/prometheus"
"github.com/vexxhost/openstack_database_exporter/internal/db"
)

const (
Namespace = "openstack"
Subsystem = "orchestration"
)

func RegisterCollectors(registry *prometheus.Registry, databaseURL string, logger *slog.Logger) {
if databaseURL == "" {
logger.Info("Collector not loaded", "service", "heat", "reason", "database URL not configured")
return
}

conn, err := db.Connect(databaseURL)
if err != nil {
logger.Error("Failed to connect to database", "service", "heat", "error", err)
return
}

registry.MustRegister(NewOrchestrationCollector(conn, logger))

logger.Info("Registered collectors", "service", "heat")
}
54 changes: 54 additions & 0 deletions internal/collector/heat/orchestration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package heat

import (
"database/sql"
"log/slog"

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

var (
orchestrationUpDesc = prometheus.NewDesc(
prometheus.BuildFQName(Namespace, Subsystem, "up"),
"up",
nil,
nil,
)
)

type OrchestrationCollector struct {
logger *slog.Logger
stacksCollector *StacksCollector
}

func NewOrchestrationCollector(db *sql.DB, logger *slog.Logger) *OrchestrationCollector {
return &OrchestrationCollector{
logger: logger.With(
"namespace", Namespace,
"subsystem", Subsystem,
"collector", "orchestration",
),
stacksCollector: NewStacksCollector(db, logger),
}
}

func (c *OrchestrationCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- orchestrationUpDesc
c.stacksCollector.Describe(ch)
}

func (c *OrchestrationCollector) Collect(ch chan<- prometheus.Metric) {
var hasError bool

// Collect metrics from stacks collector and track errors
if err := c.stacksCollector.Collect(ch); err != nil {
hasError = true
}

// Emit single up metric based on overall success
upValue := float64(1)
if hasError {
upValue = float64(0)
}
ch <- prometheus.MustNewConstMetric(orchestrationUpDesc, prometheus.GaugeValue, upValue)
}
85 changes: 85 additions & 0 deletions internal/collector/heat/orchestration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package heat

import (
"database/sql"
"log/slog"
"regexp"
"testing"

"github.com/DATA-DOG/go-sqlmock"
heatdb "github.com/vexxhost/openstack_database_exporter/internal/db/heat"
"github.com/vexxhost/openstack_database_exporter/internal/testutil"
)

func TestOrchestrationCollector(t *testing.T) {
tests := []testutil.CollectorTestCase{
{
Name: "successful collection with stacks working",
SetupMock: func(mock sqlmock.Sqlmock) {
rows := sqlmock.NewRows([]string{
"id", "name", "status", "action", "tenant",
}).AddRow(
"stack-1", "test-stack", "CREATE_COMPLETE", "CREATE", "project-1",
)

mock.ExpectQuery(regexp.QuoteMeta(heatdb.GetStackMetrics)).WillReturnRows(rows)
},
ExpectedMetrics: `# HELP openstack_orchestration_stack_status stack_status
# TYPE openstack_orchestration_stack_status gauge
openstack_orchestration_stack_status{action="CREATE",id="stack-1",name="test-stack",project_id="project-1",status="CREATE_COMPLETE"} 5
# HELP openstack_orchestration_stack_status_counter stack_status_counter
# TYPE openstack_orchestration_stack_status_counter gauge
openstack_orchestration_stack_status_counter{status="ADOPT_COMPLETE"} 0
openstack_orchestration_stack_status_counter{status="ADOPT_FAILED"} 0
openstack_orchestration_stack_status_counter{status="ADOPT_IN_PROGRESS"} 0
openstack_orchestration_stack_status_counter{status="CHECK_COMPLETE"} 0
openstack_orchestration_stack_status_counter{status="CHECK_FAILED"} 0
openstack_orchestration_stack_status_counter{status="CHECK_IN_PROGRESS"} 0
openstack_orchestration_stack_status_counter{status="CREATE_COMPLETE"} 1
openstack_orchestration_stack_status_counter{status="CREATE_FAILED"} 0
openstack_orchestration_stack_status_counter{status="CREATE_IN_PROGRESS"} 0
openstack_orchestration_stack_status_counter{status="DELETE_COMPLETE"} 0
openstack_orchestration_stack_status_counter{status="DELETE_FAILED"} 0
openstack_orchestration_stack_status_counter{status="DELETE_IN_PROGRESS"} 0
openstack_orchestration_stack_status_counter{status="INIT_COMPLETE"} 0
openstack_orchestration_stack_status_counter{status="INIT_FAILED"} 0
openstack_orchestration_stack_status_counter{status="INIT_IN_PROGRESS"} 0
openstack_orchestration_stack_status_counter{status="RESUME_COMPLETE"} 0
openstack_orchestration_stack_status_counter{status="RESUME_FAILED"} 0
openstack_orchestration_stack_status_counter{status="RESUME_IN_PROGRESS"} 0
openstack_orchestration_stack_status_counter{status="ROLLBACK_COMPLETE"} 0
openstack_orchestration_stack_status_counter{status="ROLLBACK_FAILED"} 0
openstack_orchestration_stack_status_counter{status="ROLLBACK_IN_PROGRESS"} 0
openstack_orchestration_stack_status_counter{status="SNAPSHOT_COMPLETE"} 0
openstack_orchestration_stack_status_counter{status="SNAPSHOT_FAILED"} 0
openstack_orchestration_stack_status_counter{status="SNAPSHOT_IN_PROGRESS"} 0
openstack_orchestration_stack_status_counter{status="SUSPEND_COMPLETE"} 0
openstack_orchestration_stack_status_counter{status="SUSPEND_FAILED"} 0
openstack_orchestration_stack_status_counter{status="SUSPEND_IN_PROGRESS"} 0
openstack_orchestration_stack_status_counter{status="UPDATE_COMPLETE"} 0
openstack_orchestration_stack_status_counter{status="UPDATE_FAILED"} 0
openstack_orchestration_stack_status_counter{status="UPDATE_IN_PROGRESS"} 0
# HELP openstack_orchestration_total_stacks total_stacks
# TYPE openstack_orchestration_total_stacks gauge
openstack_orchestration_total_stacks 1
# HELP openstack_orchestration_up up
# TYPE openstack_orchestration_up gauge
openstack_orchestration_up 1
`,
},
{
Name: "stacks collector fails, up metric should be 0",
SetupMock: func(mock sqlmock.Sqlmock) {
mock.ExpectQuery(regexp.QuoteMeta(heatdb.GetStackMetrics)).WillReturnError(sql.ErrConnDone)
},
ExpectedMetrics: `# HELP openstack_orchestration_up up
# TYPE openstack_orchestration_up gauge
openstack_orchestration_up 0
`,
},
}

testutil.RunCollectorTests(t, tests, func(db *sql.DB, logger *slog.Logger) *OrchestrationCollector {
return NewOrchestrationCollector(db, logger)
})
}
Loading
Loading