Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
api/vmrule: adds validation webhook (#479)
it allows to check syntax for VMRules and mitigate possible errors with it vmalert #471
- Loading branch information
Showing
15 changed files
with
409 additions
and
923 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
package v1beta1 | ||
|
||
import ( | ||
"fmt" | ||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/config" | ||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/notifier" | ||
"gopkg.in/yaml.v2" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
"net/url" | ||
ctrl "sigs.k8s.io/controller-runtime" | ||
logf "sigs.k8s.io/controller-runtime/pkg/log" | ||
"sigs.k8s.io/controller-runtime/pkg/webhook" | ||
"sync" | ||
) | ||
|
||
var initVMAlertNotifier sync.Once | ||
|
||
// log is for logging in this package. | ||
var vmrulelog = logf.Log.WithName("vmrule-resource") | ||
|
||
func (r *VMRule) SetupWebhookWithManager(mgr ctrl.Manager) error { | ||
return ctrl.NewWebhookManagedBy(mgr). | ||
For(r). | ||
Complete() | ||
} | ||
|
||
// +kubebuilder:webhook:verbs=create;update,admissionReviewVersions=v1,sideEffects=none,path=/validate-operator-victoriametrics-com-v1beta1-vmrule,mutating=false,failurePolicy=fail,groups=operator.victoriametrics.com,resources=vmrules,versions=v1beta1,name=vvmrule.kb.io | ||
var _ webhook.Validator = &VMRule{} | ||
|
||
func (r *VMRule) sanityCheck() error { | ||
initVMAlertNotifier.Do(func() { | ||
u, _ := url.Parse("https://victoriametrics.com/") | ||
notifier.InitTemplateFunc(u) | ||
}) | ||
|
||
uniqNames := make(map[string]struct{}) | ||
for i := range r.Spec.Groups { | ||
group := &r.Spec.Groups[i] | ||
errContext := fmt.Sprintf("VMRule: %s/%s group: %s", r.Namespace, r.Name, group.Name) | ||
if _, ok := uniqNames[group.Name]; ok { | ||
return fmt.Errorf("duplicate group name: %s", errContext) | ||
} | ||
uniqNames[group.Name] = struct{}{} | ||
groupBytes, err := yaml.Marshal(group) | ||
if err != nil { | ||
return fmt.Errorf("cannot marshal %s, err: %w", errContext, err) | ||
} | ||
var vmalertGroup config.Group | ||
if err := yaml.Unmarshal(groupBytes, &vmalertGroup); err != nil { | ||
return fmt.Errorf("cannot parse vmalert group %s, err: %w, r: \n%s", errContext, err, string(groupBytes)) | ||
} | ||
if err := vmalertGroup.Validate(true, true); err != nil { | ||
return fmt.Errorf("validation failed for %s err: %w", errContext, err) | ||
} | ||
} | ||
vmrulelog.Info("successfully validated rule", "name", r.Name) | ||
return nil | ||
} | ||
|
||
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type | ||
func (r *VMRule) ValidateCreate() error { | ||
vmrulelog.Info("validate create", "name", r.Name) | ||
// skip validation, if object has annotation. | ||
if mustSkipValidation(r) { | ||
return nil | ||
} | ||
return r.sanityCheck() | ||
} | ||
|
||
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type | ||
func (r *VMRule) ValidateUpdate(old runtime.Object) error { | ||
vmrulelog.Info("validate update", "name", r.Name) | ||
if mustSkipValidation(r) { | ||
return nil | ||
} | ||
return r.sanityCheck() | ||
} | ||
|
||
// ValidateDelete implements webhook.Validator so a webhook will be registered for the type | ||
func (r *VMRule) ValidateDelete() error { | ||
// noop | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
package v1beta1 | ||
|
||
import ( | ||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"testing" | ||
) | ||
|
||
func TestVMRule_sanityCheck(t *testing.T) { | ||
type fields struct { | ||
TypeMeta v1.TypeMeta | ||
ObjectMeta v1.ObjectMeta | ||
Spec VMRuleSpec | ||
Status VMRuleStatus | ||
} | ||
tests := []struct { | ||
name string | ||
fields fields | ||
wantErr bool | ||
}{ | ||
{ | ||
name: "simple check", | ||
fields: fields{ | ||
Spec: VMRuleSpec{ | ||
Groups: []RuleGroup{ | ||
{ | ||
Name: "group name", | ||
Rules: []Rule{ | ||
{ | ||
Alert: "hosts down", | ||
Expr: "up == 0", | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
{ | ||
name: "duplicate groups", | ||
fields: fields{ | ||
Spec: VMRuleSpec{ | ||
Groups: []RuleGroup{ | ||
{ | ||
Name: "group name", | ||
Rules: []Rule{ | ||
{ | ||
Alert: "hosts down", | ||
Expr: "up == 0", | ||
}, | ||
}, | ||
}, | ||
{ | ||
Name: "group name", | ||
Rules: []Rule{ | ||
{ | ||
Alert: "hosts down", | ||
Expr: "up == 0", | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
wantErr: true, | ||
}, | ||
{ | ||
name: "incorrect expr", | ||
fields: fields{ | ||
Spec: VMRuleSpec{ | ||
Groups: []RuleGroup{ | ||
{ | ||
Name: "group name", | ||
Rules: []Rule{ | ||
{ | ||
Alert: "hosts down", | ||
Expr: "asf124qaf(fa!@$qrfz", | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
wantErr: true, | ||
}, | ||
{ | ||
name: "incorrect annotation", | ||
fields: fields{ | ||
Spec: VMRuleSpec{ | ||
Groups: []RuleGroup{ | ||
{ | ||
Name: "group name", | ||
Rules: []Rule{ | ||
{ | ||
Alert: "hosts down", | ||
Expr: "asf124qaf()", | ||
Annotations: map[string]string{ | ||
"name": "{{$BadSyntax}}", | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
wantErr: true, | ||
}, | ||
{ | ||
name: "correct annotation with query", | ||
fields: fields{ | ||
Spec: VMRuleSpec{ | ||
Groups: []RuleGroup{ | ||
{ | ||
Name: "group name", | ||
Rules: []Rule{ | ||
{ | ||
Alert: "hosts down", | ||
Expr: "down == 1", | ||
Annotations: map[string]string{ | ||
"name": "{{ range printf \"alertmanager_config_hash{namespace=\\\"%s\\\",service=\\\"%s\\\"}\" $labels.namespace $labels.service | query }}\n Configuration hash for pod {{ .Labels.pod }} is \"{{ printf \"%.f\" .Value }}\"\n {{ end }}", | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
r := &VMRule{ | ||
TypeMeta: tt.fields.TypeMeta, | ||
ObjectMeta: tt.fields.ObjectMeta, | ||
Spec: tt.fields.Spec, | ||
Status: tt.fields.Status, | ||
} | ||
if err := r.sanityCheck(); (err != nil) != tt.wantErr { | ||
t.Errorf("unexpected error: %v", err) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.