mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-08-27 00:37:20 +00:00
Add ability for Cluster resources analyzer to do number and size comparison (#1210)
* make CR analyzer compare ints and sizes * schemas * typo * more comments * tests for cluster resources analyzer doing number/size comparisons * switch from humanize to k8s size parsing * schemas --------- Co-authored-by: Nathan Sullivan <nathans@replicated.com>
This commit is contained in:
co-authored by
Nathan Sullivan
parent
70f0e071dd
commit
0d1e23651a
@@ -47,7 +47,7 @@ func commonStatus(outcomes []*troubleshootv1beta2.Outcome, name string, iconKey
|
||||
}
|
||||
}
|
||||
|
||||
match, err := compareActualToWhen(outcome.Fail.When, readyReplicas, exists)
|
||||
match, err := compareActualToWhen(outcome.Fail.When, readyReplicas)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse fail range")
|
||||
}
|
||||
@@ -87,7 +87,7 @@ func commonStatus(outcomes []*troubleshootv1beta2.Outcome, name string, iconKey
|
||||
}
|
||||
}
|
||||
|
||||
match, err := compareActualToWhen(outcome.Warn.When, readyReplicas, exists)
|
||||
match, err := compareActualToWhen(outcome.Warn.When, readyReplicas)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse warn range")
|
||||
}
|
||||
@@ -127,7 +127,7 @@ func commonStatus(outcomes []*troubleshootv1beta2.Outcome, name string, iconKey
|
||||
}
|
||||
}
|
||||
|
||||
match, err := compareActualToWhen(outcome.Pass.When, readyReplicas, exists)
|
||||
match, err := compareActualToWhen(outcome.Pass.When, readyReplicas)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse pass range")
|
||||
}
|
||||
@@ -145,9 +145,8 @@ func commonStatus(outcomes []*troubleshootv1beta2.Outcome, name string, iconKey
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func compareActualToWhen(when string, actual int, exists bool) (bool, error) {
|
||||
func compareActualToWhen(when string, actual int) (bool, error) {
|
||||
parts := strings.Split(strings.TrimSpace(when), " ")
|
||||
|
||||
// we can make this a lot more flexible
|
||||
if len(parts) != 2 {
|
||||
return false, errors.New("unable to parse when range")
|
||||
|
||||
@@ -5,12 +5,14 @@ import (
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/constants"
|
||||
iutils "github.com/replicatedhq/troubleshoot/pkg/interfaceutils"
|
||||
"gopkg.in/yaml.v2"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
@@ -108,6 +110,133 @@ func FindResource(kind string, clusterScoped bool, namespace string, name string
|
||||
|
||||
}
|
||||
|
||||
func compareWhentoResource(w string, actual interface{}) (bool, error) {
|
||||
|
||||
// check our "when" has operators
|
||||
var whenSplit []string
|
||||
if strings.ContainsAny(w, "!=<>") {
|
||||
whenSplit = strings.Split(strings.TrimSpace(w), " ")
|
||||
} else {
|
||||
return false, errors.New("no operators found")
|
||||
}
|
||||
|
||||
// let's first check if we can cast "actual" as an int, that should inform us what comparison we're doing
|
||||
actualAsInt, ok := actual.(int)
|
||||
if ok {
|
||||
// it's an int! we can do integer comparison here
|
||||
// we're going to re-use an ill-fitting bit of code from the deployment analyzer for now
|
||||
return compareActualToWhen(w, actualAsInt)
|
||||
}
|
||||
|
||||
// if we've fallen through here we're going to have to try a bit harder to work out what we're comparing
|
||||
// let's try making it a string
|
||||
actualAsString, ok := actual.(string)
|
||||
if !ok {
|
||||
return false, errors.New("could not cast found value as string")
|
||||
}
|
||||
|
||||
// now we can try checking if it's a "quantity"
|
||||
actualASQuantity, err := resource.ParseQuantity(actualAsString)
|
||||
if err == nil {
|
||||
// it's probably a size, we can do some comparison here
|
||||
// but I'm being lazy here so we'll convert our last argument to an int and throw it back at our existing int comparison function
|
||||
whenAsQuantity, err := resource.ParseQuantity(whenSplit[1])
|
||||
if err != nil {
|
||||
// our when wasn't a size! naughty user
|
||||
return false, errors.New("Cannot compare size with not size")
|
||||
}
|
||||
whenIntAsString := strconv.FormatInt(whenAsQuantity.Value(), 10)
|
||||
// re-use that same compare function from earlier, might as well
|
||||
return compareActualToWhen(whenSplit[0]+" "+whenIntAsString, int(actualASQuantity.Value()))
|
||||
|
||||
}
|
||||
|
||||
return false, errors.New("could not match comparison method for result")
|
||||
}
|
||||
|
||||
func analyzeWhenField(actual interface{}, outcomes []*troubleshootv1beta2.Outcome, checkName string) (*AnalyzeResult, error) {
|
||||
|
||||
result := &AnalyzeResult{
|
||||
Title: checkName,
|
||||
IconKey: "kubernetes_text_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg?w=13&h=16",
|
||||
}
|
||||
|
||||
for _, outcome := range outcomes {
|
||||
if outcome.Fail != nil {
|
||||
if outcome.Fail.When != "" {
|
||||
compareResult, err := compareWhentoResource(outcome.Fail.When, actual)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to process when statement: %s", outcome.Fail.When)
|
||||
}
|
||||
if compareResult {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
return result, nil
|
||||
}
|
||||
} else {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
if outcome.Warn != nil {
|
||||
|
||||
if outcome.Warn.When != "" {
|
||||
compareResult, err := compareWhentoResource(outcome.Fail.When, actual)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to process when statement: %s", outcome.Warn.When)
|
||||
}
|
||||
if compareResult {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
return result, nil
|
||||
}
|
||||
} else {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
}
|
||||
if outcome.Pass != nil {
|
||||
|
||||
if outcome.Pass.When != "" {
|
||||
compareResult, err := compareWhentoResource(outcome.Pass.When, actual)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to process when statement: %s", outcome.Pass.When)
|
||||
}
|
||||
if compareResult {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
return result, nil
|
||||
}
|
||||
} else {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &AnalyzeResult{
|
||||
Title: checkName,
|
||||
IconKey: "kubernetes_text_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
|
||||
IsFail: true,
|
||||
Message: "Invalid analyzer",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *AnalyzeClusterResource) analyzeResource(analyzer *troubleshootv1beta2.ClusterResource, getFileContents getCollectedFileContents) (*AnalyzeResult, error) {
|
||||
selected, err := FindResource(analyzer.Kind, analyzer.ClusterScoped, analyzer.Namespace, analyzer.Name, getFileContents)
|
||||
if err != nil {
|
||||
@@ -168,6 +297,15 @@ func (a *AnalyzeClusterResource) analyzeResource(analyzer *troubleshootv1beta2.C
|
||||
if result != nil {
|
||||
return result, nil
|
||||
}
|
||||
} else {
|
||||
// fall through to comparing from the when key
|
||||
result, err := analyzeWhenField(actual, analyzer.Outcomes, a.Title())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if result != nil {
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
return &AnalyzeResult{
|
||||
|
||||
@@ -257,6 +257,74 @@ func Test_analyzeResource(t *testing.T) {
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg?w=13&h=16",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "pass-when-pvc-exists-and-is-at-least-4Gi",
|
||||
analyzer: troubleshootv1beta2.ClusterResource{
|
||||
AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{
|
||||
CheckName: "check-pvc-is-at-least-4Gi",
|
||||
},
|
||||
Kind: "PersistentVolumeClaim",
|
||||
Name: "data-postgresql-0",
|
||||
Namespace: "default",
|
||||
YamlPath: "spec.resources.requests.storage",
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: ">= 4Gi",
|
||||
Message: "pass",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "fail",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectResult: AnalyzeResult{
|
||||
IsPass: true,
|
||||
IsWarn: false,
|
||||
IsFail: false,
|
||||
Title: "check-pvc-is-at-least-4Gi",
|
||||
Message: "pass",
|
||||
IconKey: "kubernetes_text_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg?w=13&h=16",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "fail-when-pvc-exists-and-is-not-at-least-16Gi",
|
||||
analyzer: troubleshootv1beta2.ClusterResource{
|
||||
AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{
|
||||
CheckName: "check-pvc-is-at-least-16Gi",
|
||||
},
|
||||
Kind: "PersistentVolumeClaim",
|
||||
Name: "data-postgresql-0",
|
||||
Namespace: "default",
|
||||
YamlPath: "spec.resources.requests.storage",
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: ">= 16Gi",
|
||||
Message: "pass",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "fail",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectResult: AnalyzeResult{
|
||||
IsPass: false,
|
||||
IsWarn: false,
|
||||
IsFail: true,
|
||||
Title: "check-pvc-is-at-least-16Gi",
|
||||
Message: "fail",
|
||||
IconKey: "kubernetes_text_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg?w=13&h=16",
|
||||
},
|
||||
},
|
||||
}
|
||||
{
|
||||
for _, test := range tests {
|
||||
|
||||
Reference in New Issue
Block a user