Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add optional alerts validations #4

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
115 changes: 115 additions & 0 deletions pkg/testutil/alert_custom_validation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package testutil

import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

promv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)

var _ = Describe("Custom Validators", func() {
var linter *Linter

BeforeEach(func() {
linter = New()
})

Context("Custom Alert Validations", func() {
It("should not return error if all custom validations were added and all exist", func() {
alert := &promv1.Rule{
Alert: "ExampleAlert",
Expr: intstr.FromString("sum(rate(http_requests_total[5m]))"),
Labels: map[string]string{
"severity": "critical",
"operator_health_impact": "critical",
"kubernetes_operator_part_of": "example_part_of",
"kubernetes_operator_component": "example_component",
},
Annotations: map[string]string{
"summary": "Example summary",
"description": "Example description",
"runbook_url": "example/runbook/url",
},
}
linter.AddCustomAlertValidations(ValidateAlertNameLength, ValidateAlertRunbookURLAnnotation,
ValidateAlertHealthImpactLabel, ValidateAlertPartOfAndComponentLabels)
problems := linter.LintAlert(alert)
Expect(problems).To(BeEmpty())
})

It("should return error if alert name length custom validation was added and alert name is too long", func() {
alert := &promv1.Rule{
Alert: "ExampleAlertWithVeryLongNameExtendedToMeetRequiredLength",
Expr: intstr.FromString("sum(rate(http_requests_total[5m]))"),
Labels: map[string]string{
"severity": "critical",
},
Annotations: map[string]string{
"summary": "Example summary",
"description": "Example description",
},
}
linter.AddCustomAlertValidations(ValidateAlertNameLength)
problems := linter.LintAlert(alert)
Expect(problems).To(HaveLen(1))
Expect(problems[0].Description).To(ContainSubstring("alert name exceeds 50 characters"))
})

It("should return error if runbook_url custom validation was added and is missing", func() {
alert := &promv1.Rule{
Alert: "ExampleAlert",
Expr: intstr.FromString("sum(rate(http_requests_total[5m]))"),
Labels: map[string]string{
"severity": "critical",
},
Annotations: map[string]string{
"summary": "Example summary",
"description": "Example description",
},
}
linter.AddCustomAlertValidations(ValidateAlertRunbookURLAnnotation)
problems := linter.LintAlert(alert)
Expect(problems).To(HaveLen(1))
Expect(problems[0].Description).To(ContainSubstring("alert must have a runbook_url annotation"))
})

It("should return error if operator_health_impact custom validation was added and is missing or invalid", func() {
alert := &promv1.Rule{
Alert: "ExampleAlert",
Expr: intstr.FromString("sum(rate(http_requests_total[5m]))"),
Labels: map[string]string{
"severity": "critical",
"operator_health_impact": "invalid_operator_health_impact",
},
Annotations: map[string]string{
"summary": "Example summary",
"description": "Example description",
},
}
linter.AddCustomAlertValidations(ValidateAlertHealthImpactLabel)
problems := linter.LintAlert(alert)
Expect(problems).To(HaveLen(1))
Expect(problems[0].Description).To(ContainSubstring("alert must have a operator_health_impact label with value critical, warning, or none"))
})

It("should return error if operator_part_of and operator_component custom validation was added and both are missing", func() {
alert := &promv1.Rule{
Alert: "ExampleAlert",
Expr: intstr.FromString("sum(rate(http_requests_total[5m]))"),
Labels: map[string]string{
"severity": "critical",
},
Annotations: map[string]string{
"summary": "Example summary",
"description": "Example description",
},
}
linter.AddCustomAlertValidations(ValidateAlertPartOfAndComponentLabels)
problems := linter.LintAlert(alert)
Expect(problems).To(HaveLen(2))
Expect(problems[0].Description).To(ContainSubstring("alert must have a kubernetes_operator_part_of label"))
Expect(problems[1].Description).To(ContainSubstring("alert must have a kubernetes_operator_component label"))
})
})
})
72 changes: 72 additions & 0 deletions pkg/testutil/alert_custom_validations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package testutil

import (
promv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
)

func ValidateAlertNameLength(alert *promv1.Rule) []Problem {
var result []Problem

if len(alert.Alert) > 50 {
result = append(result, Problem{
ResourceName: alert.Alert,
Description: "alert name exceeds 50 characters",
})
}

return result
}

func ValidateAlertRunbookURLAnnotation(alert *promv1.Rule) []Problem {
var result []Problem

runbookURL := alert.Annotations["runbook_url"]
if runbookURL == "" {
result = append(result, Problem{
ResourceName: alert.Alert,
Description: "alert must have a runbook_url annotation",
})
}

assafad marked this conversation as resolved.
Show resolved Hide resolved
return result
}

func ValidateAlertHealthImpactLabel(alert *promv1.Rule) []Problem {
var result []Problem

healthImpact := alert.Labels["operator_health_impact"]
if !isValidHealthImpact(healthImpact) {
result = append(result, Problem{
ResourceName: alert.Alert,
Description: "alert must have a operator_health_impact label with value critical, warning, or none",
})
}

assafad marked this conversation as resolved.
Show resolved Hide resolved
return result
}

func ValidateAlertPartOfAndComponentLabels(alert *promv1.Rule) []Problem {
var result []Problem

partOf := alert.Labels["kubernetes_operator_part_of"]
if partOf == "" {
result = append(result, Problem{
ResourceName: alert.Alert,
Description: "alert must have a kubernetes_operator_part_of label",
})
}

component := alert.Labels["kubernetes_operator_component"]
if component == "" {
result = append(result, Problem{
ResourceName: alert.Alert,
Description: "alert must have a kubernetes_operator_component label",
})
}

return result
}

func isValidHealthImpact(healthImpact string) bool {
return healthImpact == "critical" || healthImpact == "warning" || healthImpact == "none"
}
6 changes: 5 additions & 1 deletion pkg/testutil/alert_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func validateAlertHasSeverityLabel(alert *promv1.Rule) []Problem {
var result []Problem

severity := alert.Labels["severity"]
if severity == "" || (severity != "critical" && severity != "warning" && severity != "info") {
if !isValidSeverity(severity) {
result = append(result, Problem{
ResourceName: alert.Alert,
Description: "alert must have a severity label with value critical, warning, or info",
Expand Down Expand Up @@ -89,3 +89,7 @@ func isPascalCase(s string) bool {
pascalCaseRegex := regexp.MustCompile(pascalCasePattern)
return pascalCaseRegex.MatchString(s)
}

func isValidSeverity(severity string) bool {
return severity == "critical" || severity == "warning" || severity == "info"
}
5 changes: 2 additions & 3 deletions pkg/testutil/alert_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

"k8s.io/apimachinery/pkg/util/intstr"

promv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)

var _ = Describe("Validators", func() {
var _ = Describe("Default Validators", func() {
var linter *Linter

BeforeEach(func() {
Expand Down