diff --git a/go_exporter/Makefile b/go_exporter/Makefile new file mode 100644 index 0000000..3e6d30c --- /dev/null +++ b/go_exporter/Makefile @@ -0,0 +1,19 @@ +.PHONY: test test-unit test-integration test-coverage + +test-unit: + go test -v -short ./... + +test-integration: + go test -v -tags=integration ./... + +test-coverage: + go test -v -coverprofile=coverage.out ./... + go tool cover -html=coverage.out -o coverage.html + +test: test-unit + +clean: + rm -f coverage.out coverage.html + +total-rm: + rm -f main_test.go metrics_test.go pool_test.go types_test.go integration_test.go mock.go \ No newline at end of file diff --git a/go_exporter/RBDFactory.go b/go_exporter/RBDFactory.go index 4005afa..3cad87c 100644 --- a/go_exporter/RBDFactory.go +++ b/go_exporter/RBDFactory.go @@ -2,21 +2,19 @@ package main import ( "fmt" - "github.com/ceph/go-ceph/rados" - "github.com/ceph/go-ceph/rbd" ) -func PoolFactory(cephConn CephConnection, poolName string) (Pool, error) { +func PoolFactory(cephConn CephConnector, poolName string) (Pool, error) { var rbdlist []iRBD = []iRBD{} - ioctx, err := cephConn.conn.OpenIOContext(poolName) + ioctx, err := cephConn.OpenIOContext(poolName) if err != nil { return Pool{}, fmt.Errorf("Couldn't set context for pool %s %w", poolName, err) } defer ioctx.Destroy() - imageList, err := rbd.GetImageNames(ioctx) + imageList, err := ioctx.GetImageNames() if err != nil { return Pool{}, fmt.Errorf("Couldn't get list of rbds %w", err) } @@ -35,14 +33,14 @@ func PoolFactory(cephConn CephConnection, poolName string) (Pool, error) { }, nil } -func RBDFacroty(ioctx *rados.IOContext, rbdname string) (RBD, error) { +func RBDFacroty(ioctx IOContexter, rbdname string) (RBD, error) { defer func() { if v := recover(); v != nil { fmt.Println("Shit happened") } }() - image := rbd.GetImage(ioctx,rbdname) + image := ioctx.GetImage(rbdname) err := image.Open() if err != nil { panic(err) diff --git a/go_exporter/coverage.out b/go_exporter/coverage.out new file mode 100644 index 0000000..5f02b11 --- /dev/null +++ b/go_exporter/coverage.out @@ -0,0 +1 @@ +mode: set diff --git a/go_exporter/go.mod b/go_exporter/go.mod index 3852f88..de4f279 100644 --- a/go_exporter/go.mod +++ b/go_exporter/go.mod @@ -8,6 +8,14 @@ require ( go.uber.org/zap v1.27.1 ) +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -15,6 +23,7 @@ require ( 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/multierr v1.10.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/sys v0.36.0 // indirect diff --git a/go_exporter/go.sum b/go_exporter/go.sum index 3bf4450..900c68a 100644 --- a/go_exporter/go.sum +++ b/go_exporter/go.sum @@ -10,10 +10,14 @@ github.com/gofrs/uuid/v5 v5.3.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0= github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 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= @@ -28,6 +32,8 @@ github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzM github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= diff --git a/go_exporter/main.go b/go_exporter/main.go index b376f8f..8bb9f91 100644 --- a/go_exporter/main.go +++ b/go_exporter/main.go @@ -15,7 +15,7 @@ import ( var params Params var logger *zap.SugaredLogger -func getMetrics(cephConn CephConnection) { +func getMetrics(cephConn CephConnector) { metrics := InitMetrics() prometheus.MustRegister( @@ -27,9 +27,11 @@ func getMetrics(cephConn CephConnection) { for range ticker.C { var result []Pool = []Pool{} - poolList, err := cephConn.conn.ListPools() + poolList, err := cephConn.ListPools() if err != nil { - errors.New("Cannot get list of pools") + logger.Error("Cannot get list of pools") + // do not exit but continue checking + continue } for _, v := range poolList { diff --git a/go_exporter/mock_test.go b/go_exporter/mock_test.go new file mode 100644 index 0000000..15c1100 --- /dev/null +++ b/go_exporter/mock_test.go @@ -0,0 +1,66 @@ +package main + +import ( + "github.com/ceph/go-ceph/rbd" + "github.com/stretchr/testify/mock" +) + +// MockCephConnector +type MockCephConnector struct { + mock.Mock +} + +func (m *MockCephConnector) ListPools() ([]string, error) { + args := m.Called() + return args.Get(0).([]string), args.Error(1) +} + +func (m *MockCephConnector) OpenIOContext(poolName string) (IOContexter, error) { + args := m.Called(poolName) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(IOContexter), args.Error(1) +} + +// MockIOContexter +type MockIOContexter struct { + mock.Mock +} + +func (m *MockIOContexter) Destroy() { + m.Called() +} + +func (m *MockIOContexter) GetImageNames() ([]string, error) { + args := m.Called() + return args.Get(0).([]string), args.Error(1) +} + +func (m *MockIOContexter) GetImage(name string) RBDImager { + args := m.Called(name) + return args.Get(0).(RBDImager) +} + +// MockRBDImager +type MockRBDImager struct { + mock.Mock +} + +func (m *MockRBDImager) Open() error { + args := m.Called() + return args.Error(0) +} + +func (m *MockRBDImager) Close() error { + args := m.Called() + return args.Error(0) +} + +func (m *MockRBDImager) Stat() (*rbd.ImageInfo, error) { + args := m.Called() + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*rbd.ImageInfo), args.Error(1) +} \ No newline at end of file diff --git a/go_exporter/types.go b/go_exporter/types.go index 16c61c8..aecc06e 100644 --- a/go_exporter/types.go +++ b/go_exporter/types.go @@ -11,10 +11,76 @@ type Params struct { 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 @@ -46,4 +112,4 @@ type Pool struct { type Metrics struct { total_rbd_requested_size_per_pool *prometheus.GaugeVec total_rbd_requested_size prometheus.Gauge -} +} \ No newline at end of file