Skip to content
Closed
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 internal/collector/glance/glance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package glance

const (
Subsystem = "glance"
)
99 changes: 99 additions & 0 deletions internal/collector/glance/images.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package glance

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

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

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

imagesBytesDesc = prometheus.NewDesc(
prometheus.BuildFQName(collector.Namespace, Subsystem, "image_bytes"),
"image_bytes",
[]string{
"id",
"name",
"tenant_id",
},
nil,
)

imagesDesc = prometheus.NewDesc(
prometheus.BuildFQName(collector.Namespace, Subsystem, "images"),
"images",
nil,
nil,
)
)

type ImagesCollector struct {
db *sql.DB
queries *glancedb.Queries
logger *slog.Logger
}

func NewImagesCollector(db *sql.DB, logger *slog.Logger) *ImagesCollector {
return &ImagesCollector{
db: db,
queries: glancedb.New(db),
logger: logger.With(
"namespace", collector.Namespace,
"subsystem", Subsystem,
"collector", "images",
),
}
}

func (c *ImagesCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- imagesUpDesc
ch <- imagesBytesDesc
ch <- imagesDesc
}

func (c *ImagesCollector) Collect(ch chan<- prometheus.Metric) {
ctx := context.Background()

images, err := c.queries.GetAllImages(ctx)
if err != nil {
ch <- prometheus.MustNewConstMetric(imagesUpDesc, prometheus.GaugeValue, 0)

c.logger.Error("failed to query", "error", err)
return
}

for _, image := range images {
// Convert size from nullable int64 to float64, defaulting to 0 if null
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can drop this comment, it's pretty clear :)

sizeBytes := float64(0)
if image.Size.Valid {
sizeBytes = float64(image.Size.Int64)
}

ch <- prometheus.MustNewConstMetric(
imagesBytesDesc,
prometheus.GaugeValue,
sizeBytes,
image.ID,
image.Name.String,
image.Owner.String,
)
}

ch <- prometheus.MustNewConstMetric(
imagesDesc,
prometheus.GaugeValue,
float64(len(images)),
)

ch <- prometheus.MustNewConstMetric(imagesUpDesc, prometheus.GaugeValue, 1)
}
113 changes: 113 additions & 0 deletions internal/collector/glance/images_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package glance

import (
"database/sql"
"regexp"
"testing"
"time"

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

func TestImagesCollector(t *testing.T) {
tests := []testutil.CollectorTestCase{
{
Name: "successful collection with images",
SetupMock: func(mock sqlmock.Sqlmock) {
createdAt := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)
updatedAt := time.Date(2023, 1, 2, 0, 0, 0, 0, time.UTC)

rows := sqlmock.NewRows([]string{
"id", "name", "size", "status", "owner", "visibility",
"disk_format", "container_format", "checksum", "created_at",
"updated_at", "min_disk", "min_ram", "protected", "virtual_size",
"os_hidden", "os_hash_algo", "os_hash_value",
}).AddRow(
"781b3762-9469-4cec-b58d-3349e5de4e9c", "F17-x86_64-cfntools", 476704768, "active", "5ef70662f8b34079a6eddb8da9d75fe8", "public",
"qcow2", "bare", "1234567890abcdef", createdAt, updatedAt, 1, 512, false, nil,
false, nil, nil,
).AddRow(
"1bea47ed-f6a9-463b-b423-14b9cca9ad27", "cirros-0.3.2-x86_64-disk", 13167616, "active", "5ef70662f8b34079a6eddb8da9d75fe8", "public",
"qcow2", "bare", "abcdef1234567890", createdAt, updatedAt, 0, 64, false, nil,
false, nil, nil,
)

mock.ExpectQuery(regexp.QuoteMeta(glancedb.GetAllImages)).WillReturnRows(rows)
},
ExpectedMetrics: `# HELP openstack_glance_image_bytes image_bytes
# TYPE openstack_glance_image_bytes gauge
openstack_glance_image_bytes{id="1bea47ed-f6a9-463b-b423-14b9cca9ad27",name="cirros-0.3.2-x86_64-disk",tenant_id="5ef70662f8b34079a6eddb8da9d75fe8"} 1.3167616e+07
openstack_glance_image_bytes{id="781b3762-9469-4cec-b58d-3349e5de4e9c",name="F17-x86_64-cfntools",tenant_id="5ef70662f8b34079a6eddb8da9d75fe8"} 4.76704768e+08
Comment on lines +41 to +42
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think upstream tests have these flipped, I've wanted to try and keep them identical. Would it work to flip them?

# HELP openstack_glance_images images
# TYPE openstack_glance_images gauge
openstack_glance_images 2
# HELP openstack_glance_up up
# TYPE openstack_glance_up gauge
openstack_glance_up 1
`,
},
{
Name: "successful collection with no images",
SetupMock: func(mock sqlmock.Sqlmock) {
rows := sqlmock.NewRows([]string{
"id", "name", "size", "status", "owner", "visibility",
"disk_format", "container_format", "checksum", "created_at",
"updated_at", "min_disk", "min_ram", "protected", "virtual_size",
"os_hidden", "os_hash_algo", "os_hash_value",
})

mock.ExpectQuery(regexp.QuoteMeta(glancedb.GetAllImages)).WillReturnRows(rows)
},
ExpectedMetrics: `# HELP openstack_glance_images images
# TYPE openstack_glance_images gauge
openstack_glance_images 0
# HELP openstack_glance_up up
# TYPE openstack_glance_up gauge
openstack_glance_up 1
`,
},
{
Name: "handles null values gracefully",
SetupMock: func(mock sqlmock.Sqlmock) {
createdAt := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)

rows := sqlmock.NewRows([]string{
"id", "name", "size", "status", "owner", "visibility",
"disk_format", "container_format", "checksum", "created_at",
"updated_at", "min_disk", "min_ram", "protected", "virtual_size",
"os_hidden", "os_hash_algo", "os_hash_value",
}).AddRow(
"image-with-nulls", nil, nil, "active", nil, "private",
nil, nil, nil, createdAt, nil, 0, 0, false, nil,
false, nil, nil,
)

mock.ExpectQuery(regexp.QuoteMeta(glancedb.GetAllImages)).WillReturnRows(rows)
},
ExpectedMetrics: `# HELP openstack_glance_image_bytes image_bytes
# TYPE openstack_glance_image_bytes gauge
openstack_glance_image_bytes{id="image-with-nulls",name="",tenant_id=""} 0
# HELP openstack_glance_images images
# TYPE openstack_glance_images gauge
openstack_glance_images 1
# HELP openstack_glance_up up
# TYPE openstack_glance_up gauge
openstack_glance_up 1
`,
},
{
Name: "query error",
SetupMock: func(mock sqlmock.Sqlmock) {
mock.ExpectQuery(regexp.QuoteMeta(glancedb.GetAllImages)).WillReturnError(sql.ErrConnDone)
},
ExpectedMetrics: `# HELP openstack_glance_up up
# TYPE openstack_glance_up gauge
openstack_glance_up 0
`,
},
}

testutil.RunCollectorTests(t, tests, NewImagesCollector)
}
Loading
Loading