ceph_exporter/collectors/conn.go
Cody Breedlove 6856d7cda9 CEPH-114 Added expansion_factor metric
Added the expansion factor metric which will compute the multiplier for data expansion for EC clusters. Will return the pool size if it is not an EC pool
2020-06-16 14:06:42 -04:00

183 lines
5.2 KiB
Go

// Copyright 2016 DigitalOcean
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package collectors
import (
"encoding/json"
"fmt"
"github.com/ceph/go-ceph/rados"
)
// Conn interface implements only necessary methods that are used
// in this repository of *rados.Conn. This keeps rest of the implementation
// clean and *rados.Conn doesn't need to show up everywhere (it being
// more of an implementation detail in reality). Also it makes mocking
// easier for unit-testing the collectors.
type Conn interface {
ReadDefaultConfigFile() error
Connect() error
Shutdown()
MonCommand([]byte) ([]byte, string, error)
PGCommand([]byte, []byte) ([]byte, string, error)
}
// Verify that *rados.Conn implements Conn correctly.
var _ Conn = &rados.Conn{}
// NoopConn is the stub we use for mocking rados Conn. Unit testing
// each individual collectors becomes a lot easier after that.
// TODO: both output and cmdOut provide the command outputs for "go test", but
// we can deprecate output, because cmdOut is able to hold the outputs we desire
// for multiple commands for "go test".
type NoopConn struct {
output string // deprecated
cmdOut []map[string]string
iteration int
}
// The stub we use for testing should also satisfy the interface properties.
var _ Conn = &NoopConn{}
// NewNoopConn returns an instance of *NoopConn. The string that we want output
// at the end of the command we issue to Ceph is fixed and should be specified
// in the only input parameter.
func NewNoopConn(output string) *NoopConn {
return &NoopConn{output: output}
}
// NewNoopConnWithCmdOut returns an instance of *NoopConn. The string that we
// want output at the end of the command we issue to Ceph can be various and
// should be specified by the map in the only input parameter.
func NewNoopConnWithCmdOut(cmdOut []map[string]string) *NoopConn {
return &NoopConn{
cmdOut: cmdOut,
iteration: 0,
}
}
// IncIteration increments iteration by 1.
func (n *NoopConn) IncIteration() {
n.iteration++
}
// ReadDefaultConfigFile does not need to return an error. It satisfies
// rados.Conn's function with the same prototype.
func (n *NoopConn) ReadDefaultConfigFile() error {
return nil
}
// Connect does not need to return an error. It satisfies
// rados.Conn's function with the same prototype.
func (n *NoopConn) Connect() error {
return nil
}
// Shutdown satisfies rados.Conn's function prototype.
func (n *NoopConn) Shutdown() {}
// MonCommand returns the provided output string to NoopConn as is, making
// it seem like it actually ran something and produced that string as a result.
func (n *NoopConn) MonCommand(args []byte) ([]byte, string, error) {
// Unmarshal the input command and see if we need to intercept
cmd := map[string]interface{}{}
err := json.Unmarshal(args, &cmd)
if err != nil {
return []byte(n.output), "", err
}
// Intercept and mock the output
switch prefix := cmd["prefix"]; prefix {
case "pg dump":
val, ok := cmd["dumpcontents"]
if !ok {
break
}
dc, ok := val.([]interface{})
if !ok || len(dc) == 0 {
break
}
switch dc[0] {
case "pgs_brief":
return []byte(n.cmdOut[n.iteration]["ceph pg dump pgs_brief"]), "", nil
}
case "osd tree":
val, ok := cmd["states"]
if !ok {
break
}
st, ok := val.([]interface{})
if !ok || len(st) == 0 {
break
}
switch st[0] {
case "down":
return []byte(n.cmdOut[n.iteration]["ceph osd tree down"]), "", nil
}
case "osd df":
return []byte(n.cmdOut[n.iteration]["ceph osd df"]), "", nil
case "osd perf":
return []byte(n.cmdOut[n.iteration]["ceph osd perf"]), "", nil
case "osd dump":
return []byte(n.cmdOut[n.iteration]["ceph osd dump"]), "", nil
case "osd erasure-code-profile get ec-4-2":
ec42Return :=
`{
"crush-device-class": "",
"crush-failure-domain": "host",
"crush-root": "objectdata",
"jerasure-per-chunk-alignment": "false",
"k": "4",
"m": "2",
"plugin": "jerasure",
"technique": "reed_sol_van",
"w": "8"
}`
return []byte(ec42Return), "", nil
case "osd erasure-code-profile get replicated-ruleset":
return []byte("{}"), "", nil
}
return []byte(n.output), "", nil
}
// PGCommand returns the provided output string to NoopConn as is, making
// it seem like it actually ran something and produced that string as a result.
func (n *NoopConn) PGCommand(pgid, args []byte) ([]byte, string, error) {
// Unmarshal the input command and see if we need to intercept
cmd := map[string]interface{}{}
err := json.Unmarshal(args, &cmd)
if err != nil {
return []byte(n.output), "", err
}
// Intercept and mock the output
switch prefix := cmd["prefix"]; prefix {
case "query":
return []byte(n.cmdOut[n.iteration][fmt.Sprintf("ceph tell %s query", string(pgid))]), "", nil
}
return []byte(n.output), "", nil
}