Ceph collectors and analyzers (#295)

* Ceph collectors and analyzers

* updating based on prior pr

* fixes

* fixes
This commit is contained in:
emosbaugh
2020-11-13 09:12:42 -08:00
committed by GitHub
parent fd6af65f7e
commit 2bf19eaddf
10 changed files with 642 additions and 0 deletions
+1
View File
@@ -51,4 +51,5 @@ require (
k8s.io/cli-runtime v0.18.0
k8s.io/client-go v0.18.2
sigs.k8s.io/controller-runtime v0.5.1-0.20200402191424-df180accb901
sigs.k8s.io/controller-tools v0.3.0 // indirect
)
+7
View File
@@ -191,6 +191,8 @@ github.com/go-redis/redis/v7 v7.2.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRf
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gobuffalo/flect v0.2.0 h1:EWCvMGGxOjsgwlWaP+f4+Hh6yrrte7JeFL2S6b+0hdM=
github.com/gobuffalo/flect v0.2.0/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -325,6 +327,7 @@ github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7
github.com/manifoldco/promptui v0.3.2 h1:rir7oByTERac6jhpHUPErHuopoRDvO3jxS+FdadEns8=
github.com/manifoldco/promptui v0.3.2/go.mod h1:8JU+igZ+eeiiRku4T5BjtKh2ms8sziGpSYl1gN8Bazw=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
@@ -688,6 +691,8 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966 h1:B0J02caTR6tpSJozBJyiAzT6CtBzjclw4pgm9gg8Ys0=
gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -732,6 +737,8 @@ k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0=
sigs.k8s.io/controller-runtime v0.5.1-0.20200402191424-df180accb901 h1:qwHvTyQQBjATQeKPdJ0TwcXdtScjyI6GuiAQ5CSqEvM=
sigs.k8s.io/controller-runtime v0.5.1-0.20200402191424-df180accb901/go.mod h1:j4echH3Y/UPHRpXS65rxGXujda8iWOheMQvDh1uNgaY=
sigs.k8s.io/controller-tools v0.3.0 h1:y3YD99XOyWaXkiF1kd41uRvfp/64teWcrEZFuHxPhJ4=
sigs.k8s.io/controller-tools v0.3.0/go.mod h1:enhtKGfxZD1GFEoMgP8Fdbu+uKQ/cq1/WGJhdVChfvI=
sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
+14
View File
@@ -251,6 +251,20 @@ func Analyze(analyzer *troubleshootv1beta2.Analyze, getFile getCollectedFileCont
}
return []*AnalyzeResult{result}, nil
}
if analyzer.CephStatus != nil {
isExcluded, err := isExcluded(analyzer.CephStatus.Exclude)
if err != nil {
return nil, err
}
if isExcluded {
return nil, nil
}
result, err := cephStatus(analyzer.CephStatus, getFile)
if err != nil {
return nil, err
}
return []*AnalyzeResult{result}, nil
}
return nil, errors.New("invalid analyzer")
}
+175
View File
@@ -0,0 +1,175 @@
package analyzer
import (
"encoding/json"
"path"
"strings"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
)
type CephHealth string
const (
CephHealthOK CephHealth = "HEALTH_OK"
CephHealthWarn CephHealth = "HEALTH_WARN"
CephHealthErr CephHealth = "HEALTH_ERR"
)
func (a CephHealth) Compare(b CephHealth) int {
if a == b {
return 0
}
switch a {
case CephHealthOK:
return 1
case CephHealthWarn:
switch b {
case CephHealthOK:
return -1
case CephHealthErr:
return 1
}
return 1
case CephHealthErr:
switch b {
case CephHealthOK, CephHealthWarn:
return -1
}
return 1
default:
return -1
}
}
var CephStatusDefaultOutcomes = []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
Message: "Ceph is healthy",
},
},
{
Warn: &troubleshootv1beta2.SingleOutcome{
Message: "Ceph status is HEALTH_WARN",
URI: "https://rook.io/docs/rook/v1.4/ceph-common-issues.html",
},
},
{
Fail: &troubleshootv1beta2.SingleOutcome{
Message: "Ceph status is HEALTH_ERR",
URI: "https://rook.io/docs/rook/v1.4/ceph-common-issues.html",
},
},
}
func cephStatus(analyzer *troubleshootv1beta2.CephStatusAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
fileName := path.Join(collect.GetCephCollectorFilepath(analyzer.CollectorName, analyzer.Namespace), "status.json")
collected, err := getCollectedFileContents(fileName)
if err != nil {
return nil, errors.Wrap(err, "failed to read collected ceph status")
}
title := analyzer.CheckName
if title == "" {
title = "Ceph Status"
}
analyzeResult := &AnalyzeResult{
Title: title,
IconKey: "rook", // maybe this should be ceph?
IconURI: "https://troubleshoot.sh/images/analyzer-icons/rook.svg?w=11&h=16",
}
status := struct {
Health struct {
Status string `json:"status"`
} `json:"health"`
}{}
if err := json.Unmarshal(collected, &status); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal status.json")
}
if len(analyzer.Outcomes) == 0 {
analyzer.Outcomes = CephStatusDefaultOutcomes
}
for _, outcome := range analyzer.Outcomes {
if outcome.Fail != nil {
if outcome.Fail.When == "" {
outcome.Fail.When = string(CephHealthErr)
}
match, err := compareCephStatus(status.Health.Status, outcome.Fail.When)
if err != nil {
return nil, errors.Wrap(err, "failed to compare ceph status")
} else if match {
analyzeResult.IsFail = true
analyzeResult.Message = outcome.Fail.Message
analyzeResult.URI = outcome.Fail.URI
return analyzeResult, nil
}
} else if outcome.Warn != nil {
if outcome.Warn.When == "" {
outcome.Warn.When = string(CephHealthWarn)
}
match, err := compareCephStatus(status.Health.Status, outcome.Warn.When)
if err != nil {
return nil, errors.Wrap(err, "failed to compare ceph status")
} else if match {
analyzeResult.IsWarn = true
analyzeResult.Message = outcome.Warn.Message
analyzeResult.URI = outcome.Warn.URI
return analyzeResult, nil
}
} else if outcome.Pass != nil {
if outcome.Pass.When == "" {
outcome.Pass.When = string(CephHealthOK)
}
match, err := compareCephStatus(status.Health.Status, outcome.Pass.When)
if err != nil {
return nil, errors.Wrap(err, "failed to compare ceph status")
} else if match {
analyzeResult.IsPass = true
analyzeResult.Message = outcome.Pass.Message
analyzeResult.URI = outcome.Pass.URI
return analyzeResult, nil
}
}
}
return analyzeResult, nil
}
func compareCephStatus(actual, when string) (bool, error) {
parts := strings.Split(strings.TrimSpace(when), " ")
if len(parts) == 1 {
value := strings.TrimSpace(parts[0])
return value == actual, nil
}
if len(parts) != 2 {
return false, errors.New("unable to parse when range")
}
operator := strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])
compareResult := CephHealth(actual).Compare(CephHealth(value))
switch operator {
case "=", "==", "===":
return compareResult == 0, nil
case "<":
return compareResult == -1, nil
case ">":
return compareResult == 1, nil
case "<=":
return compareResult <= 0, nil
case ">=":
return compareResult >= 0, nil
default:
return false, errors.New("unknown operator")
}
}
+204
View File
@@ -0,0 +1,204 @@
package analyzer
import (
"fmt"
"testing"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.undefinedlabs.com/scopeagent"
)
func Test_cephStatus(t *testing.T) {
tests := []struct {
name string
analyzer troubleshootv1beta2.CephStatusAnalyze
expectResult AnalyzeResult
filePath, file string
}{
{
name: "pass case",
analyzer: troubleshootv1beta2.CephStatusAnalyze{},
expectResult: AnalyzeResult{
IsPass: true,
IsWarn: false,
IsFail: false,
Title: "Ceph Status",
Message: "Ceph is healthy",
IconKey: "rook",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/rook.svg?w=11&h=16",
},
filePath: "ceph/status.json",
file: `{
"fsid": "96a8178c-6aa2-4adf-a309-9e8869a79611",
"health": {
"status": "HEALTH_OK"
}
}`,
},
{
name: "warn case",
analyzer: troubleshootv1beta2.CephStatusAnalyze{},
expectResult: AnalyzeResult{
IsPass: false,
IsWarn: true,
IsFail: false,
Title: "Ceph Status",
Message: "Ceph status is HEALTH_WARN",
URI: "https://rook.io/docs/rook/v1.4/ceph-common-issues.html",
IconKey: "rook",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/rook.svg?w=11&h=16",
},
filePath: "ceph/status.json",
file: `{
"fsid": "96a8178c-6aa2-4adf-a309-9e8869a79611",
"health": {
"status": "HEALTH_WARN"
}
}`,
},
{
name: "fail case",
analyzer: troubleshootv1beta2.CephStatusAnalyze{},
expectResult: AnalyzeResult{
IsPass: false,
IsWarn: false,
IsFail: true,
Title: "Ceph Status",
Message: "Ceph status is HEALTH_ERR",
URI: "https://rook.io/docs/rook/v1.4/ceph-common-issues.html",
IconKey: "rook",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/rook.svg?w=11&h=16",
},
filePath: "ceph/status.json",
file: `{
"fsid": "96a8178c-6aa2-4adf-a309-9e8869a79611",
"health": {
"status": "HEALTH_ERR"
}
}`,
},
{
name: "CollectorName and Namespace",
analyzer: troubleshootv1beta2.CephStatusAnalyze{
CollectorName: "custom-namespace",
Namespace: "namespace",
},
expectResult: AnalyzeResult{
IsPass: true,
IsWarn: false,
IsFail: false,
Title: "Ceph Status",
Message: "Ceph is healthy",
IconKey: "rook",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/rook.svg?w=11&h=16",
},
filePath: "custom-namespace/namespace/ceph/status.json",
file: `{
"fsid": "96a8178c-6aa2-4adf-a309-9e8869a79611",
"health": {
"status": "HEALTH_OK"
}
}`,
},
{
name: "outcomes when",
analyzer: troubleshootv1beta2.CephStatusAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "== HEALTH_OK",
Message: "custom message OK",
URI: "custom uri OK",
},
},
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "<= HEALTH_WARN",
Message: "custom message WARN",
URI: "custom uri WARN",
},
},
},
},
expectResult: AnalyzeResult{
IsPass: false,
IsWarn: false,
IsFail: true,
Title: "Ceph Status",
Message: "custom message WARN",
URI: "custom uri WARN",
IconKey: "rook",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/rook.svg?w=11&h=16",
},
filePath: "ceph/status.json",
file: `{
"fsid": "96a8178c-6aa2-4adf-a309-9e8869a79611",
"health": {
"status": "HEALTH_WARN"
}
}`,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
scopetest := scopeagent.StartTest(t)
defer scopetest.End()
req := require.New(t)
getFile := func(n string) ([]byte, error) {
assert.Equal(t, n, test.filePath)
return []byte(test.file), nil
}
actual, err := cephStatus(&test.analyzer, getFile)
req.NoError(err)
assert.Equal(t, test.expectResult, *actual)
})
}
}
func Test_compareCephStatus(t *testing.T) {
tests := []struct {
actual string
when string
want bool
wantErr bool
}{
{
actual: "HEALTH_OK",
when: "HEALTH_OK",
want: true,
},
{
actual: "HEALTH_OK",
when: "HEALTH_WARN",
want: false,
},
{
actual: "HEALTH_OK",
when: "<= HEALTH_WARN",
want: false,
},
{
actual: "HEALTH_OK",
when: ">= HEALTH_WARN",
want: true,
},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("%s %s", tt.actual, tt.when), func(t *testing.T) {
got, err := compareCephStatus(tt.actual, tt.when)
if (err != nil) != tt.wantErr {
t.Errorf("compareCephStatus() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("compareCephStatus() = %v, want %v", got, tt.want)
}
})
}
}
@@ -121,6 +121,13 @@ type CollectdAnalyze struct {
CollectorName string `json:"collectorName" yaml:"collectorName"`
}
type CephStatusAnalyze struct {
AnalyzeMeta `json:",inline" yaml:",inline"`
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
CollectorName string `json:"collectorName,omitempty" yaml:"collectorName,omitempty"`
Namespace string `json:"namespace" yaml:"namespace"`
}
type AnalyzeMeta struct {
CheckName string `json:"checkName,omitempty" yaml:"checkName,omitempty"`
Exclude multitype.BoolOrString `json:"exclude,omitempty" yaml:"exclude,omitempty"`
@@ -142,4 +149,5 @@ type Analyze struct {
Postgres *DatabaseAnalyze `json:"postgres,omitempty" yaml:"postgres,omitempty"`
Mysql *DatabaseAnalyze `json:"mysql,omitempty" yaml:"mysql,omitempty"`
Redis *DatabaseAnalyze `json:"redis,omitempty" yaml:"redis,omitempty"`
CephStatus *CephStatusAnalyze `json:"cephStatus,omitempty" yaml:"cephStatus,omitempty"`
}
@@ -133,6 +133,13 @@ type Collectd struct {
HostPath string `json:"hostPath" yaml:"hostPath"`
}
type Ceph struct {
CollectorMeta `json:",inline" yaml:",inline"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Namespace string `json:"namespace" yaml:"namespace"`
Timeout string `json:"timeout,omitempty" yaml:"timeout,omitempty"`
}
type Collect struct {
ClusterInfo *ClusterInfo `json:"clusterInfo,omitempty" yaml:"clusterInfo,omitempty"`
ClusterResources *ClusterResources `json:"clusterResources,omitempty" yaml:"clusterResources,omitempty"`
@@ -147,6 +154,7 @@ type Collect struct {
Mysql *Database `json:"mysql,omitempty" yaml:"mysql,omitempty"`
Redis *Database `json:"redis,omitempty" yaml:"redis,omitempty"`
Collectd *Collectd `json:"collectd,omitempty" yaml:"collectd,omitempty"`
Ceph *Ceph `json:"ceph,omitempty" yaml:"ceph,omitempty"`
}
func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSubjectAccessReviewSpec {
@@ -350,6 +358,10 @@ func (c *Collect) GetName() string {
collector = "collectd"
name = c.Collectd.CollectorName
}
if c.Ceph != nil {
collector = "ceph"
name = c.Ceph.CollectorName
}
if collector == "" {
return "<none>"
@@ -127,6 +127,11 @@ func (in *Analyze) DeepCopyInto(out *Analyze) {
*out = new(DatabaseAnalyze)
(*in).DeepCopyInto(*out)
}
if in.CephStatus != nil {
in, out := &in.CephStatus, &out.CephStatus
*out = new(CephStatusAnalyze)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Analyze.
@@ -282,6 +287,49 @@ func (in *AnalyzerStatus) DeepCopy() *AnalyzerStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Ceph) DeepCopyInto(out *Ceph) {
*out = *in
out.CollectorMeta = in.CollectorMeta
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Ceph.
func (in *Ceph) DeepCopy() *Ceph {
if in == nil {
return nil
}
out := new(Ceph)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CephStatusAnalyze) DeepCopyInto(out *CephStatusAnalyze) {
*out = *in
out.AnalyzeMeta = in.AnalyzeMeta
if in.Outcomes != nil {
in, out := &in.Outcomes, &out.Outcomes
*out = make([]*Outcome, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Outcome)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CephStatusAnalyze.
func (in *CephStatusAnalyze) DeepCopy() *CephStatusAnalyze {
if in == nil {
return nil
}
out := new(CephStatusAnalyze)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClusterInfo) DeepCopyInto(out *ClusterInfo) {
*out = *in
@@ -409,6 +457,11 @@ func (in *Collect) DeepCopyInto(out *Collect) {
*out = new(Collectd)
(*in).DeepCopyInto(*out)
}
if in.Ceph != nil {
in, out := &in.Ceph, &out.Ceph
*out = new(Ceph)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Collect.
+159
View File
@@ -0,0 +1,159 @@
package collect
import (
"context"
"fmt"
"path"
"strings"
multierror "github.com/hashicorp/go-multierror"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes"
)
const (
DefaultCephNamespace = "rook-ceph"
)
type CephCommand struct {
ID string
Command []string
Args []string
}
var CephCommands = []CephCommand{
{
ID: "status",
Command: []string{"ceph", "status"},
Args: []string{"-f", "json-pretty"},
},
{
ID: "fs",
Command: []string{"ceph", "fs", "status"},
Args: []string{"-f", "json-pretty"},
},
{
ID: "fs-ls",
Command: []string{"ceph", "fs", "ls"},
Args: []string{"-f", "json-pretty"},
},
{
ID: "osd-status",
Command: []string{"ceph", "osd", "status"},
Args: []string{"-f", "json-pretty"},
},
{
ID: "osd-tree",
Command: []string{"ceph", "osd", "tree"},
Args: []string{"-f", "json-pretty"},
},
{
ID: "osd-pool",
Command: []string{"ceph", "osd", "pool", "ls", "detail"},
Args: []string{"-f", "json-pretty"},
},
{
ID: "health",
Command: []string{"ceph", "health", "detail"},
Args: []string{"-f", "json-pretty"},
},
{
ID: "auth",
Command: []string{"ceph", "auth", "ls"},
Args: []string{"-f", "json-pretty"},
},
}
func Ceph(c *Collector, cephCollector *troubleshootv1beta2.Ceph) (map[string][]byte, error) {
ctx := context.TODO()
if cephCollector.Namespace == "" {
cephCollector.Namespace = DefaultCephNamespace
}
pod, err := findRookCephToolsPod(ctx, c, cephCollector.Namespace)
if err != nil {
return nil, err
}
final := map[string][]byte{}
var multiErr *multierror.Error
for _, command := range CephCommands {
results, err := cephCommandExec(ctx, c, cephCollector, pod, command)
multiErr = multierror.Append(multiErr, errors.Wrapf(err, "failed to exec command %s", command.ID))
for fileName, output := range results {
final[fileName] = output
}
}
return final, nil
}
func cephCommandExec(ctx context.Context, c *Collector, cephCollector *troubleshootv1beta2.Ceph, pod *corev1.Pod, command CephCommand) (map[string][]byte, error) {
execCollector := &troubleshootv1beta2.Exec{
Selector: labelsToSelector(pod.Labels),
Namespace: pod.Namespace,
Command: command.Command,
Args: command.Args,
Timeout: cephCollector.Timeout,
}
results, err := Exec(c, execCollector)
if err != nil {
return nil, err
}
final := map[string][]byte{}
for filename, result := range results {
pathPrefix := GetCephCollectorFilepath(cephCollector.Name, cephCollector.Namespace)
switch {
case strings.HasSuffix(filename, "-stdout.txt"):
final[path.Join(pathPrefix, fmt.Sprintf("%s.json", command.ID))] = result
case strings.HasSuffix(filename, "-stderr.txt"):
final[path.Join(pathPrefix, fmt.Sprintf("%s-stderr.json", command.ID))] = result
case strings.HasSuffix(filename, "-errors.json"):
final[path.Join(pathPrefix, fmt.Sprintf("%s-errors.json", command.ID))] = result
}
}
return final, nil
}
func findRookCephToolsPod(ctx context.Context, c *Collector, namespace string) (*corev1.Pod, error) {
client, err := kubernetes.NewForConfig(c.ClientConfig)
if err != nil {
return nil, errors.Wrap(err, "failed to create kubernetes client")
}
pods, _ := listPodsInSelectors(ctx, client, namespace, []string{"app=rook-ceph-tools"})
if len(pods) > 0 {
return &pods[0], nil
}
pods, _ = listPodsInSelectors(ctx, client, namespace, []string{"app=rook-ceph-operator"})
if len(pods) > 0 {
return &pods[0], nil
}
return nil, errors.New("rook ceph tools pod not found")
}
func GetCephCollectorFilepath(name, namespace string) string {
parts := []string{}
if name != "" {
parts = append(parts, name)
}
if namespace != "" && namespace != DefaultCephNamespace {
parts = append(parts, namespace)
}
parts = append(parts, "ceph")
return path.Join(parts...)
}
func labelsToSelector(labels map[string]string) []string {
selector := []string{}
for key, value := range labels {
selector = append(selector, fmt.Sprintf("%s=%s", key, value))
}
return selector
}
+9
View File
@@ -168,6 +168,15 @@ func (c *Collector) RunCollectorSync(globalRedactors []*troubleshootv1beta2.Reda
return
}
result, err = Collectd(c, c.Collect.Collectd)
} else if c.Collect.Ceph != nil {
isExcludedResult, err = isExcluded(c.Collect.Ceph.Exclude)
if err != nil {
return nil, err
}
if isExcludedResult {
return nil, nil
}
result, err = Ceph(c, c.Collect.Ceph)
} else {
err = errors.New("no spec found to run")
return