This commit is contained in:
a.pivkin
2025-12-22 07:57:42 +03:00
parent af4a1c46e1
commit 2e19663a63
37 changed files with 935 additions and 258 deletions
+64
View File
@@ -0,0 +1,64 @@
package RBDFactory
import (
"fmt"
"rbd_exporter/logger"
"rbd_exporter/mytypes"
)
func PoolFactory(cephConn mytypes.CephConnector, poolName string) (mytypes.Pool, error) {
var rbdlist []mytypes.IRBD = []mytypes.IRBD{}
ioctx, err := cephConn.OpenIOContext(poolName)
if err != nil {
return mytypes.Pool{}, fmt.Errorf("Couldn't set context for pool %s %w", poolName, err)
}
defer ioctx.Destroy()
imageList, err := ioctx.GetImageNames()
if err != nil {
return mytypes.Pool{}, fmt.Errorf("Couldn't get list of rbds %w", err)
}
for _, rbdname := range imageList {
stat, err := RBDFacroty(ioctx, rbdname)
if err != nil {
fmt.Errorf("Coundn't get stat from disk %s %w", rbdname, err)
panic(err)
}
rbdlist = append(rbdlist, stat)
}
return mytypes.Pool{
Name: poolName,
HasRBD: len(imageList) != 0,
RBDlist: rbdlist,
}, nil
}
func RBDFacroty(ioctx mytypes.IOContexter, rbdname string) (mytypes.RBD, error) {
defer func() {
if v := recover(); v != nil {
logger.Logger.Errorf("No such RBD exists (probably just deleted)")
}
}()
image := ioctx.GetImage(rbdname)
err := image.Open()
if err != nil {
panic(err)
// logger.Logger.Errorf("Shit happened in RBDFactory")
}
defer image.Close()
info,err := image.Stat()
if err != nil {
return mytypes.RBD{},fmt.Errorf("Couldn't get stats for image %s %w",rbdname,err)
}
return mytypes.RBD{
Name: rbdname,
ImageInfo: *info,
},nil
}
+39
View File
@@ -0,0 +1,39 @@
package connection
import (
"fmt"
"rbd_exporter/mytypes"
"github.com/ceph/go-ceph/rados"
)
func Connect(params mytypes.Params) (_ mytypes.CephConnection, err error) {
var cephConn mytypes.CephConnection = mytypes.CephConnection{}
defer func() {
if err != nil {
err = fmt.Errorf("Error in func connect() %w", err)
}
}()
cephConn.Conn, err = rados.NewConnWithClusterAndUser("ceph", "client.admin")
if err != nil {
return mytypes.CephConnection{}, err
}
err = cephConn.Conn.ReadConfigFile(params.Config)
if err != nil {
return mytypes.CephConnection{}, err
}
err = cephConn.Conn.SetConfigOption("keyring", params.Keyring)
if err != nil {
return mytypes.CephConnection{}, err
}
err = cephConn.Conn.Connect()
if err != nil {
return mytypes.CephConnection{}, err
}
return cephConn, nil
}
+29
View File
@@ -0,0 +1,29 @@
module rbd_exporter
go 1.25.5
require github.com/prometheus/client_golang v1.23.2
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.uber.org/mock v0.6.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/ceph/go-ceph v0.37.0
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/stretchr/testify v1.11.1
go.uber.org/zap v1.27.1
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/sys v0.38.0 // indirect
google.golang.org/protobuf v1.36.8 // indirect
)
+64
View File
@@ -0,0 +1,64 @@
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/ceph/go-ceph v0.37.0 h1:KXliBe3ZDr3/AtfY7n9d1MG7ippYNCVhMPcAgm05CFI=
github.com/ceph/go-ceph v0.37.0/go.mod h1:3y2tOlITlyuVFhy8v6PpCEfjMwKPfXJiH0/2hKZZQRE=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+10
View File
@@ -0,0 +1,10 @@
package logger
import "go.uber.org/zap"
var Logger *zap.SugaredLogger
// SetLogger is called from init()
func SetLogger(l *zap.SugaredLogger) {
Logger = l
}
+81
View File
@@ -0,0 +1,81 @@
package main
import (
"errors"
"flag"
"fmt"
"net/http"
"os"
"rbd_exporter/mytypes"
"rbd_exporter/metrics"
"rbd_exporter/logger"
"rbd_exporter/connection"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var params mytypes.Params
var mainlogger *zap.SugaredLogger
// Here I initialize logger
func loggerInit() *zap.SugaredLogger {
config := zap.NewDevelopmentConfig()
config.EncoderConfig.TimeKey = "timestamp"
config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
logger, err := config.Build()
if err != nil {
panic(fmt.Sprintf("Logger set up failed: %v", err))
}
defer logger.Sync()
return logger.Sugar()
}
// This func runs even before main()
func init() {
config_file := flag.String("config", "/etc/ceph/ceph.conf", "placement of ceph config file")
_keyring := flag.String("keyring", "/etc/ceph/ceph.client.admin.keyring", "placement of ceph keyring file")
flag.Parse()
params.Config = *config_file
params.Keyring = *_keyring
mainlogger = loggerInit()
logger.SetLogger(mainlogger)
mainlogger.Info("Setting up logger is complete successfully")
mainlogger.Info("Registering prom metrics")
}
func main() {
cephConn, err := connection.Connect(params)
if err != nil {
fmt.Println(err)
if wrapped := errors.Unwrap(err); wrapped != nil {
fmt.Println(wrapped)
}
os.Exit(1)
}
defer cephConn.Conn.Shutdown()
mainlogger.Info("Successfully connected")
http.Handle("/metrics", promhttp.Handler())
// HTTP runs in separate thread cuz it blocks futher execution of main
go func() {
mainlogger.Info("Starting http server")
// Here I check for errors if HTTP fails
if err := http.ListenAndServe(":9040", nil); err != nil {
mainlogger.Fatalf("HTTP server failed to start %v", err)
}
mainlogger.Info("HTTP server started")
}()
go metrics.GetMetrics(cephConn)
select {}
}
+83
View File
@@ -0,0 +1,83 @@
package metrics
import (
"fmt"
"rbd_exporter/RBDFactory"
"rbd_exporter/mytypes"
"rbd_exporter/logger"
"time"
"github.com/prometheus/client_golang/prometheus"
)
func GetMetrics(cephConn mytypes.CephConnector) {
metrics := InitMetrics()
prometheus.MustRegister(
metrics.Total_rbd_requested_size_per_pool,
)
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()
for range ticker.C {
var result []mytypes.Pool = []mytypes.Pool{}
poolList, err := cephConn.ListPools()
if err != nil {
logger.Logger.Error("Cannot get list of pools")
// do not exit but continue checking
continue
}
for _, v := range poolList {
x, _ := RBDFactory.PoolFactory(cephConn, v)
result = append(result, x)
}
metrics.Total_rbd_requested_size_per_pool.Reset()
for _,v := range result {
if !v.HasRBD {continue}
FillMetrics(v,metrics)
}
fmt.Println("====================")
// fmt.Println(result)
}
}
func InitMetrics() *mytypes.Metrics {
m := &mytypes.Metrics{
Total_rbd_requested_size_per_pool: prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "total_rbd_requested_size_per_pool",
Help: "total size of all requested RBDs in a specific pool",
},
[]string{"poolname"},
),
// total_rbd_requested_size: prometheus.NewGauge(
// prometheus.GaugeOpts{
// Name: "total_rbd_requested_size",
// Help: "total size of all RBDs in the cluster",
// },
// ),
}
return m
}
func FillMetrics(pool mytypes.Pool,metrics *mytypes.Metrics) {
var totalSizePerPool uint64 = 0
logger.Logger.Debugf("Processing pool %s",pool.Name)
for _,v := range pool.RBDlist {
totalSizePerPool += uint64(v.GetSize())
}
logger.Logger.Debugf("Total size of RBDs in pool %s is %d",pool.Name,totalSizePerPool)
metrics.Total_rbd_requested_size_per_pool.WithLabelValues(pool.Name,
).Set(
float64(totalSizePerPool))
}
@@ -0,0 +1,256 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: mytypes/types.go
//
// Generated by this command:
//
// mockgen -source=mytypes/types.go -destination=mytypes/types_mockgen_mock.go -package=mocks
//
// Package mocks is a generated GoMock package.
package mocks
import (
mytypes "rbd_exporter/mytypes"
reflect "reflect"
rbd "github.com/ceph/go-ceph/rbd"
gomock "go.uber.org/mock/gomock"
)
// MockCephConnector is a mock of CephConnector interface.
type MockCephConnector struct {
ctrl *gomock.Controller
recorder *MockCephConnectorMockRecorder
isgomock struct{}
}
// MockCephConnectorMockRecorder is the mock recorder for MockCephConnector.
type MockCephConnectorMockRecorder struct {
mock *MockCephConnector
}
// NewMockCephConnector creates a new mock instance.
func NewMockCephConnector(ctrl *gomock.Controller) *MockCephConnector {
mock := &MockCephConnector{ctrl: ctrl}
mock.recorder = &MockCephConnectorMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockCephConnector) EXPECT() *MockCephConnectorMockRecorder {
return m.recorder
}
// ListPools mocks base method.
func (m *MockCephConnector) ListPools() ([]string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListPools")
ret0, _ := ret[0].([]string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ListPools indicates an expected call of ListPools.
func (mr *MockCephConnectorMockRecorder) ListPools() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListPools", reflect.TypeOf((*MockCephConnector)(nil).ListPools))
}
// OpenIOContext mocks base method.
func (m *MockCephConnector) OpenIOContext(poolName string) (mytypes.IOContexter, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "OpenIOContext", poolName)
ret0, _ := ret[0].(mytypes.IOContexter)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// OpenIOContext indicates an expected call of OpenIOContext.
func (mr *MockCephConnectorMockRecorder) OpenIOContext(poolName any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OpenIOContext", reflect.TypeOf((*MockCephConnector)(nil).OpenIOContext), poolName)
}
// MockIOContexter is a mock of IOContexter interface.
type MockIOContexter struct {
ctrl *gomock.Controller
recorder *MockIOContexterMockRecorder
isgomock struct{}
}
// MockIOContexterMockRecorder is the mock recorder for MockIOContexter.
type MockIOContexterMockRecorder struct {
mock *MockIOContexter
}
// NewMockIOContexter creates a new mock instance.
func NewMockIOContexter(ctrl *gomock.Controller) *MockIOContexter {
mock := &MockIOContexter{ctrl: ctrl}
mock.recorder = &MockIOContexterMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockIOContexter) EXPECT() *MockIOContexterMockRecorder {
return m.recorder
}
// Destroy mocks base method.
func (m *MockIOContexter) Destroy() {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Destroy")
}
// Destroy indicates an expected call of Destroy.
func (mr *MockIOContexterMockRecorder) Destroy() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Destroy", reflect.TypeOf((*MockIOContexter)(nil).Destroy))
}
// GetImage mocks base method.
func (m *MockIOContexter) GetImage(name string) mytypes.RBDImager {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetImage", name)
ret0, _ := ret[0].(mytypes.RBDImager)
return ret0
}
// GetImage indicates an expected call of GetImage.
func (mr *MockIOContexterMockRecorder) GetImage(name any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetImage", reflect.TypeOf((*MockIOContexter)(nil).GetImage), name)
}
// GetImageNames mocks base method.
func (m *MockIOContexter) GetImageNames() ([]string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetImageNames")
ret0, _ := ret[0].([]string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetImageNames indicates an expected call of GetImageNames.
func (mr *MockIOContexterMockRecorder) GetImageNames() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetImageNames", reflect.TypeOf((*MockIOContexter)(nil).GetImageNames))
}
// MockRBDImager is a mock of RBDImager interface.
type MockRBDImager struct {
ctrl *gomock.Controller
recorder *MockRBDImagerMockRecorder
isgomock struct{}
}
// MockRBDImagerMockRecorder is the mock recorder for MockRBDImager.
type MockRBDImagerMockRecorder struct {
mock *MockRBDImager
}
// NewMockRBDImager creates a new mock instance.
func NewMockRBDImager(ctrl *gomock.Controller) *MockRBDImager {
mock := &MockRBDImager{ctrl: ctrl}
mock.recorder = &MockRBDImagerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockRBDImager) EXPECT() *MockRBDImagerMockRecorder {
return m.recorder
}
// Close mocks base method.
func (m *MockRBDImager) Close() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Close")
ret0, _ := ret[0].(error)
return ret0
}
// Close indicates an expected call of Close.
func (mr *MockRBDImagerMockRecorder) Close() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockRBDImager)(nil).Close))
}
// Open mocks base method.
func (m *MockRBDImager) Open() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Open")
ret0, _ := ret[0].(error)
return ret0
}
// Open indicates an expected call of Open.
func (mr *MockRBDImagerMockRecorder) Open() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Open", reflect.TypeOf((*MockRBDImager)(nil).Open))
}
// Stat mocks base method.
func (m *MockRBDImager) Stat() (*rbd.ImageInfo, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Stat")
ret0, _ := ret[0].(*rbd.ImageInfo)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Stat indicates an expected call of Stat.
func (mr *MockRBDImagerMockRecorder) Stat() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stat", reflect.TypeOf((*MockRBDImager)(nil).Stat))
}
// MockIRBD is a mock of IRBD interface.
type MockIRBD struct {
ctrl *gomock.Controller
recorder *MockIRBDMockRecorder
isgomock struct{}
}
// MockIRBDMockRecorder is the mock recorder for MockIRBD.
type MockIRBDMockRecorder struct {
mock *MockIRBD
}
// NewMockIRBD creates a new mock instance.
func NewMockIRBD(ctrl *gomock.Controller) *MockIRBD {
mock := &MockIRBD{ctrl: ctrl}
mock.recorder = &MockIRBDMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockIRBD) EXPECT() *MockIRBDMockRecorder {
return m.recorder
}
// GetName mocks base method.
func (m *MockIRBD) GetName() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetName")
ret0, _ := ret[0].(string)
return ret0
}
// GetName indicates an expected call of GetName.
func (mr *MockIRBDMockRecorder) GetName() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetName", reflect.TypeOf((*MockIRBD)(nil).GetName))
}
// GetSize mocks base method.
func (m *MockIRBD) GetSize() int64 {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetSize")
ret0, _ := ret[0].(int64)
return ret0
}
// GetSize indicates an expected call of GetSize.
func (mr *MockIRBDMockRecorder) GetSize() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSize", reflect.TypeOf((*MockIRBD)(nil).GetSize))
}
+115
View File
@@ -0,0 +1,115 @@
package mytypes
import (
"github.com/ceph/go-ceph/rados"
"github.com/ceph/go-ceph/rbd"
"github.com/prometheus/client_golang/prometheus"
)
type Params struct {
Config string
Keyring string
}
type CephConnector interface {
ListPools() ([]string,error)
OpenIOContext(poolName string) (IOContexter, error)
}
type CephConnection struct {
Conn *rados.Conn
}
// Here I do implement CephConnector interface
func (cc CephConnection) ListPools() ([]string,error) {
return cc.Conn.ListPools()
}
// Here I do implement CephConnector interface
func (cc CephConnection) OpenIOContext(poolName string) (IOContexter,error) {
ioctx,err := cc.Conn.OpenIOContext(poolName)
if err != nil {
return nil,err
}
return IOContextWrapper{ioctx: ioctx},nil
}
type IOContexter interface {
Destroy()
GetImageNames() ([]string, error)
GetImage(name string) RBDImager
}
type IOContextWrapper struct {
ioctx *rados.IOContext
}
func (iocw IOContextWrapper) Destroy() {
iocw.ioctx.Destroy()
}
func (iocw IOContextWrapper) GetImageNames() ([]string,error){
return rbd.GetImageNames(iocw.ioctx)
}
func (iocw IOContextWrapper) GetImage(rbdName string) RBDImager {
img := rbd.GetImage(iocw.ioctx,rbdName)
return RBDImageWrapper{image: img}
}
type RBDImager interface {
Open() error
Close() error
Stat() (*rbd.ImageInfo,error)
}
type RBDImageWrapper struct {
image *rbd.Image
}
func (riw RBDImageWrapper) Open() error {
return riw.image.Open()
}
func (riw RBDImageWrapper) Close() error {
return riw.image.Close()
}
func (riw RBDImageWrapper) Stat() (*rbd.ImageInfo,error) {
return riw.image.Stat()
}
type IRBD interface {
GetName() string
GetSize() int64
}
type RBDUsage struct {
Images []RBD
}
type RBD struct {
Name string
rbd.ImageInfo
}
func (r RBD) GetName() string {
return r.Name
}
func (r RBD) GetSize() int64 {
return int64(r.Size)
}
type Pool struct {
Name string
HasRBD bool
RBDlist []IRBD
}
type Metrics struct {
Total_rbd_requested_size_per_pool *prometheus.GaugeVec
Total_rbd_requested_size prometheus.Gauge
}
+73
View File
@@ -0,0 +1,73 @@
package tests
import (
"rbd_exporter/RBDFactory"
mock "rbd_exporter/mytypes/mock"
"testing"
"github.com/ceph/go-ceph/rbd"
gomock "go.uber.org/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRBDFactory(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
MockIOContexter := mock.NewMockIOContexter(mockCtrl)
MockRBDImager := mock.NewMockRBDImager(mockCtrl)
rbdName := "test-rbd"
expectedImageInfo := &rbd.ImageInfo{
Size: 1488,
}
MockIOContexter.EXPECT().GetImage(rbdName).Return(MockRBDImager)
MockRBDImager.EXPECT().Open().Return(nil)
MockRBDImager.EXPECT().Close().Return(nil)
MockRBDImager.EXPECT().Stat().Return(expectedImageInfo,nil)
result,err := RBDFactory.RBDFacroty(MockIOContexter,rbdName)
require.NoError(t, err)
assert.Equal(t, rbdName, result.Name)
assert.Equal(t, expectedImageInfo.Size, result.ImageInfo.Size)
}
func TestPoolFactory(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockCephConn := mock.NewMockCephConnector(mockCtrl)
mockIOCtx := mock.NewMockIOContexter(mockCtrl)
mockImage := mock.NewMockRBDImager(mockCtrl)
poolName := "test-pool"
imageNames := []string{"rbd1", "rbd2"}
expectedImageInfo := &rbd.ImageInfo{Size: 1024}
mockCephConn.EXPECT().OpenIOContext(poolName).Return(mockIOCtx,nil)
mockIOCtx.EXPECT().GetImageNames().Return(imageNames,nil)
mockIOCtx.EXPECT().Destroy()
for _, imageName := range imageNames {
mockIOCtx.EXPECT().GetImage(imageName).Return(mockImage)
mockImage.EXPECT().Open().Return(nil)
mockImage.EXPECT().Close().Return(nil)
mockImage.EXPECT().Stat().Return(expectedImageInfo, nil)
}
result, err := RBDFactory.PoolFactory(mockCephConn, poolName)
require.NoError(t, err)
assert.Equal(t, poolName, result.Name)
assert.True(t, result.HasRBD)
assert.Len(t, result.RBDlist, 2)
assert.Equal(t, "rbd1", result.RBDlist[0].GetName())
assert.Equal(t, "rbd2", result.RBDlist[1].GetName())
}