ClusterPodStatuses analyzer (#456)

* ClusterPodStatuses analyzer

Co-authored-by: divolgin <dmitriy@replicated.com>
This commit is contained in:
Salah Aldeen Al Saleh
2021-10-25 17:44:59 -07:00
committed by GitHub
parent 9cbdb70d16
commit 3d1d53ee9d
10 changed files with 539 additions and 0 deletions

View File

@@ -85,6 +85,52 @@ spec:
- namespace
- outcomes
type: object
clusterPodStatuses:
properties:
checkName:
type: string
exclude:
type: BoolString
namespaces:
items:
type: string
type: array
outcomes:
items:
properties:
fail:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
pass:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
warn:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
type: object
type: array
required:
- namespaces
- outcomes
type: object
clusterVersion:
properties:
checkName:

View File

@@ -85,6 +85,52 @@ spec:
- namespace
- outcomes
type: object
clusterPodStatuses:
properties:
checkName:
type: string
exclude:
type: BoolString
namespaces:
items:
type: string
type: array
outcomes:
items:
properties:
fail:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
pass:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
warn:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
type: object
type: array
required:
- namespaces
- outcomes
type: object
clusterVersion:
properties:
checkName:

View File

@@ -116,6 +116,52 @@ spec:
- namespace
- outcomes
type: object
clusterPodStatuses:
properties:
checkName:
type: string
exclude:
type: BoolString
namespaces:
items:
type: string
type: array
outcomes:
items:
properties:
fail:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
pass:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
warn:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
type: object
type: array
required:
- namespaces
- outcomes
type: object
clusterVersion:
properties:
checkName:

View File

@@ -205,6 +205,20 @@ func Analyze(analyzer *troubleshootv1beta2.Analyze, getFile getCollectedFileCont
}
return []*AnalyzeResult{result}, nil
}
if analyzer.ClusterPodStatuses != nil {
isExcluded, err := isExcluded(analyzer.ClusterPodStatuses.Exclude)
if err != nil {
return nil, err
}
if isExcluded {
return nil, nil
}
results, err := clusterPodStatuses(analyzer.ClusterPodStatuses, findFiles)
if err != nil {
return nil, err
}
return results, nil
}
if analyzer.ContainerRuntime != nil {
isExcluded, err := isExcluded(analyzer.ContainerRuntime.Exclude)
if err != nil {

View File

@@ -0,0 +1,130 @@
package analyzer
import (
"bytes"
"encoding/json"
"fmt"
"path/filepath"
"strings"
"text/template"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
corev1 "k8s.io/api/core/v1"
)
func clusterPodStatuses(analyzer *troubleshootv1beta2.ClusterPodStatuses, getChildCollectedFileContents func(string) (map[string][]byte, error)) ([]*AnalyzeResult, error) {
collected, err := getChildCollectedFileContents(filepath.Join("cluster-resources", "pods", "*.json"))
if err != nil {
return nil, errors.Wrap(err, "failed to read collected pods")
}
var pods []corev1.Pod
for fileName, fileContent := range collected {
podsNs := strings.TrimSuffix(fileName, ".json")
include := len(analyzer.Namespaces) == 0
for _, ns := range analyzer.Namespaces {
if ns == podsNs {
include = true
break
}
}
if include {
var nsPods []corev1.Pod
if err := json.Unmarshal(fileContent, &nsPods); err != nil {
return nil, errors.Wrapf(err, "failed to unmarshal pods list for namespace %s", podsNs)
}
pods = append(pods, nsPods...)
}
}
allResults := []*AnalyzeResult{}
for _, pod := range pods {
podResults := []*AnalyzeResult{}
for _, outcome := range analyzer.Outcomes {
r := AnalyzeResult{}
when := ""
if outcome.Fail != nil {
r.IsFail = true
r.Message = outcome.Fail.Message
r.URI = outcome.Fail.URI
when = outcome.Fail.When
} else if outcome.Warn != nil {
r.IsWarn = true
r.Message = outcome.Warn.Message
r.URI = outcome.Warn.URI
when = outcome.Warn.When
} else if outcome.Pass != nil {
r.IsPass = true
r.Message = outcome.Pass.Message
r.URI = outcome.Pass.URI
when = outcome.Pass.When
} else {
fmt.Println("error: found an empty outcome in a clusterPodStatuses analyzer") // don't stop
continue
}
parts := strings.Split(strings.TrimSpace(when), " ")
if len(parts) < 2 {
fmt.Printf("invalid 'when' format: %s\n", when) // don't stop
continue
}
match := false
switch parts[0] {
case "=", "==", "===":
match = parts[1] == string(pod.Status.Phase)
case "!=", "!==":
match = parts[1] != string(pod.Status.Phase)
}
if !match {
continue
}
r.Title = analyzer.CheckName
if r.Title == "" {
r.Title = "Pod {{ .Name }} status"
}
if r.Message == "" {
r.Message = "Pod {{ .Name }} status is {{ .Status.Phase }}"
}
tmpl := template.New("pod")
// template the title
titleTmpl, err := tmpl.Parse(r.Title)
if err != nil {
return nil, errors.Wrap(err, "failed to create new title template")
}
var t bytes.Buffer
err = titleTmpl.Execute(&t, pod)
if err != nil {
return nil, errors.Wrap(err, "failed to execute template")
}
r.Title = t.String()
// template the message
msgTmpl, err := tmpl.Parse(r.Message)
if err != nil {
return nil, errors.Wrap(err, "failed to create new title template")
}
var m bytes.Buffer
err = msgTmpl.Execute(&m, pod)
if err != nil {
return nil, errors.Wrap(err, "failed to execute template")
}
r.Message = m.String()
podResults = append(podResults, &r)
}
allResults = append(allResults, podResults...)
}
return allResults, nil
}

View File

@@ -63,6 +63,12 @@ type StatefulsetStatus struct {
Name string `json:"name" yaml:"name"`
}
type ClusterPodStatuses struct {
AnalyzeMeta `json:",inline" yaml:",inline"`
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
Namespaces []string `json:"namespaces" yaml:"namespaces"`
}
type ContainerRuntime struct {
AnalyzeMeta `json:",inline" yaml:",inline"`
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
@@ -162,6 +168,7 @@ type Analyze struct {
ImagePullSecret *ImagePullSecret `json:"imagePullSecret,omitempty" yaml:"imagePullSecret,omitempty"`
DeploymentStatus *DeploymentStatus `json:"deploymentStatus,omitempty" yaml:"deploymentStatus,omitempty"`
StatefulsetStatus *StatefulsetStatus `json:"statefulsetStatus,omitempty" yaml:"statefulsetStatus,omitempty"`
ClusterPodStatuses *ClusterPodStatuses `json:"clusterPodStatuses,omitempty" yaml:"clusterPodStatuses,omitempty"`
ContainerRuntime *ContainerRuntime `json:"containerRuntime,omitempty" yaml:"containerRuntime,omitempty"`
Distribution *Distribution `json:"distribution,omitempty" yaml:"distribution,omitempty"`
NodeResources *NodeResources `json:"nodeResources,omitempty" yaml:"nodeResources,omitempty"`

View File

@@ -97,6 +97,11 @@ func (in *Analyze) DeepCopyInto(out *Analyze) {
*out = new(StatefulsetStatus)
(*in).DeepCopyInto(*out)
}
if in.ClusterPodStatuses != nil {
in, out := &in.ClusterPodStatuses, &out.ClusterPodStatuses
*out = new(ClusterPodStatuses)
(*in).DeepCopyInto(*out)
}
if in.ContainerRuntime != nil {
in, out := &in.ContainerRuntime, &out.ContainerRuntime
*out = new(ContainerRuntime)
@@ -511,6 +516,38 @@ func (in *ClusterInfo) DeepCopy() *ClusterInfo {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClusterPodStatuses) DeepCopyInto(out *ClusterPodStatuses) {
*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)
}
}
}
if in.Namespaces != nil {
in, out := &in.Namespaces, &out.Namespaces
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterPodStatuses.
func (in *ClusterPodStatuses) DeepCopy() *ClusterPodStatuses {
if in == nil {
return nil
}
out := new(ClusterPodStatuses)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClusterResources) DeepCopyInto(out *ClusterResources) {
*out = *in

View File

@@ -93,6 +93,77 @@
}
}
},
"clusterPodStatuses": {
"type": "object",
"required": [
"namespaces",
"outcomes"
],
"properties": {
"checkName": {
"type": "string"
},
"exclude": {
"oneOf": [{"type": "string"},{"type": "boolean"}]
},
"namespaces": {
"type": "array",
"items": {
"type": "string"
}
},
"outcomes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"fail": {
"type": "object",
"properties": {
"message": {
"type": "string"
},
"uri": {
"type": "string"
},
"when": {
"type": "string"
}
}
},
"pass": {
"type": "object",
"properties": {
"message": {
"type": "string"
},
"uri": {
"type": "string"
},
"when": {
"type": "string"
}
}
},
"warn": {
"type": "object",
"properties": {
"message": {
"type": "string"
},
"uri": {
"type": "string"
},
"when": {
"type": "string"
}
}
}
}
}
}
}
},
"clusterVersion": {
"type": "object",
"required": [

View File

@@ -93,6 +93,77 @@
}
}
},
"clusterPodStatuses": {
"type": "object",
"required": [
"namespaces",
"outcomes"
],
"properties": {
"checkName": {
"type": "string"
},
"exclude": {
"oneOf": [{"type": "string"},{"type": "boolean"}]
},
"namespaces": {
"type": "array",
"items": {
"type": "string"
}
},
"outcomes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"fail": {
"type": "object",
"properties": {
"message": {
"type": "string"
},
"uri": {
"type": "string"
},
"when": {
"type": "string"
}
}
},
"pass": {
"type": "object",
"properties": {
"message": {
"type": "string"
},
"uri": {
"type": "string"
},
"when": {
"type": "string"
}
}
},
"warn": {
"type": "object",
"properties": {
"message": {
"type": "string"
},
"uri": {
"type": "string"
},
"when": {
"type": "string"
}
}
}
}
}
}
}
},
"clusterVersion": {
"type": "object",
"required": [

View File

@@ -139,6 +139,77 @@
}
}
},
"clusterPodStatuses": {
"type": "object",
"required": [
"namespaces",
"outcomes"
],
"properties": {
"checkName": {
"type": "string"
},
"exclude": {
"oneOf": [{"type": "string"},{"type": "boolean"}]
},
"namespaces": {
"type": "array",
"items": {
"type": "string"
}
},
"outcomes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"fail": {
"type": "object",
"properties": {
"message": {
"type": "string"
},
"uri": {
"type": "string"
},
"when": {
"type": "string"
}
}
},
"pass": {
"type": "object",
"properties": {
"message": {
"type": "string"
},
"uri": {
"type": "string"
},
"when": {
"type": "string"
}
}
},
"warn": {
"type": "object",
"properties": {
"message": {
"type": "string"
},
"uri": {
"type": "string"
},
"when": {
"type": "string"
}
}
}
}
}
}
}
},
"clusterVersion": {
"type": "object",
"required": [