Skip to content

Commit

Permalink
feat: add new validator hasAllowedEvaluationInterval (#47)
Browse files Browse the repository at this point in the history
Signed-off-by: Martin Chodur <[email protected]>
  • Loading branch information
FUSAKLA authored Feb 28, 2024
1 parent bc4b62c commit bb282dd
Show file tree
Hide file tree
Showing 10 changed files with 91 additions and 4 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

- Added new validation rule scope `Group` to validate the rule group itself (not the rules in it).
- Added new `Group` scope validator `hasAllowedEvaluationInterval` to check if the rule group has the `interval` in the configured range and possibility to enforce it to be configured.
- CHANGED: The validator `allowedSourceTenants` is now allowed only in the `Group` scope validation rules.

## [v2.7.1] - 2024-02-01
Expand Down
16 changes: 16 additions & 0 deletions docs/validations.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
- [`hasSourceTenantsForMetrics`](#hassourcetenantsformetrics)
- [Groups](#groups)
- [`hasValidSourceTenants`](#hasvalidsourcetenants)
- [`hasAllowedEvaluationInterval`](#hasallowedevaluationinterval)

## Labels

Expand Down Expand Up @@ -309,6 +310,8 @@ params:
## Groups
Validators that are designed to validate the group itself. Not the rules within it.

:warning: Can be used only with the `Group` scope.

### `hasValidSourceTenants`

Fails if the rule group has other than than the configured source tenants.
Expand All @@ -317,3 +320,16 @@ Fails if the rule group has other than than the configured source tenants.
params:
allowedSourceTenants: [ "foo", "bar" ]
```

### `hasAllowedEvaluationInterval`

Fails if the rule group has the `interval` out of the configured range.
By default it will ignore, if the group does not have the interval configured.
You can enforce it to be set by setting the `mustBeSet` to true.

```yaml
params:
minimum: <duration> # Optional, default is 0
maximum: <duration> # Optional, default is infinity
mustBeSet: <bool> # Optional, default is false
```
1 change: 1 addition & 0 deletions examples/human_readable.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ <h2><a href="#check-metric-name">check-metric-name</a></h2>
<h2><a href="#check-groups">check-groups</a></h2>
<ul>
<li>Group does not have other <code>source_tenants</code> than: <code>tenant1</code>, <code>tenant2</code>, <code>k8s</code></li>
<li>Group evaluation interval is between <code>20s</code> and <code>10m0s</code> if set</li>
</ul>

<h2><a href="#another-checks">another-checks</a></h2>
Expand Down
1 change: 1 addition & 0 deletions examples/human_readable.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Validation rules:

check-groups
- Group does not have other `source_tenants` than: `tenant1`, `tenant2`, `k8s`
- Group evaluation interval is between `20s` and `10m0s` if set

another-checks
- All rules labels does not have empty values
Expand Down
1 change: 1 addition & 0 deletions examples/rules/rules.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
groups:
- name: group1
partial_response_strategy: abort
interval: 1m
rules:
# foo bar
- record: recorded_metrics
Expand Down
5 changes: 5 additions & 0 deletions examples/validation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,8 @@ validationRules:
- type: hasAllowedSourceTenants
params:
allowedSourceTenants: ["tenant1", "tenant2", "k8s"]
- type: hasAllowedEvaluationInterval
params:
minimum: "20s"
maximum: "10m"
intervalMustBeSet: false
4 changes: 2 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,11 @@ rulesIteration:
newRule := validationrule.New(validationRule.Name, validationRule.Scope)
for _, validatorConfig := range validationRule.Validations {
if err := validator.KnownValidators(validationRule.Scope, []string{validatorConfig.ValidatorType}); err != nil {
return nil, err
return nil, fmt.Errorf("loading config for validator `%s` in the `%s` rule: %w", validatorConfig.ValidatorType, validationRule.Name, err)
}
newValidator, err := validator.NewFromConfig(validationRule.Scope, validatorConfig)
if err != nil {
return nil, fmt.Errorf("loading validator config: %w", err)
return nil, fmt.Errorf("loading config for validator `%s` in the `%s` rule: %w", validatorConfig.ValidatorType, validationRule.Name, err)
}
if newValidator == nil {
continue
Expand Down
3 changes: 2 additions & 1 deletion pkg/validator/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ var registeredRuleValidators = map[string]validatorCreator{
}

var registeredGroupValidators = map[string]validatorCreator{
"hasAllowedSourceTenants": newHasAllowedSourceTenants,
"hasAllowedSourceTenants": newHasAllowedSourceTenants,
"hasAllowedEvaluationInterval": newHasAllowedEvaluationInterval,
}

var registeredValidators = map[string]validatorCreator{}
Expand Down
55 changes: 55 additions & 0 deletions pkg/validator/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package validator
import (
"fmt"
"strings"
"time"

"github.com/fusakla/promruval/v2/pkg/prometheus"
"github.com/fusakla/promruval/v2/pkg/unmarshaler"
Expand Down Expand Up @@ -41,3 +42,57 @@ func (h hasAllowedSourceTenants) Validate(group unmarshaler.RuleGroup, _ rulefmt
}
return []error{fmt.Errorf("group has invalid source_tenants: `%s`", strings.Join(invalidTenants, "`,`"))}
}

func newHasAllowedEvaluationInterval(paramsConfig yaml.Node) (Validator, error) {
params := struct {
MinimumEvaluationInterval time.Duration `yaml:"minimum"`
MaximumEvaluationInterval time.Duration `yaml:"maximum"`
MustBeSet bool `yaml:"intervalMustBeSet"`
}{}
if err := paramsConfig.Decode(&params); err != nil {
return nil, err
}
if params.MinimumEvaluationInterval > params.MaximumEvaluationInterval {
return nil, fmt.Errorf("minimum is greater than maximum")
}
if params.MaximumEvaluationInterval == 0 && params.MinimumEvaluationInterval == 0 {
return nil, fmt.Errorf("at least one of the `minimum` or `maximum` must be set")
}
if params.MaximumEvaluationInterval == 0 {
params.MaximumEvaluationInterval = time.Duration(1<<63 - 1)

}
return &hasAllowedEvaluationInterval{minimum: params.MinimumEvaluationInterval, maximum: params.MaximumEvaluationInterval, mustBeSet: params.MustBeSet}, nil
}

type hasAllowedEvaluationInterval struct {
minimum time.Duration
maximum time.Duration
mustBeSet bool
}

func (h hasAllowedEvaluationInterval) String() string {
text := fmt.Sprintf("evaluation interval is between `%s` and `%s`", h.minimum, h.maximum)
if h.mustBeSet {
text += " and must be set"
} else {
text += " if set"
}
return text
}

func (h hasAllowedEvaluationInterval) Validate(group unmarshaler.RuleGroup, _ rulefmt.Rule, _ *prometheus.Client) []error {
if group.Interval == 0 {
if h.mustBeSet {
return []error{fmt.Errorf("evaluation interval must be set")}
}
return []error{}
}
if h.minimum != 0 && time.Duration(group.Interval) < h.minimum {
return []error{fmt.Errorf("evaluation interval %s is less than `%s`", group.Interval, h.minimum)}
}
if h.maximum != 0 && time.Duration(group.Interval) > h.maximum {
return []error{fmt.Errorf("evaluation interval %s is greater than `%s`", group.Interval, h.maximum)}
}
return []error{}
}
7 changes: 7 additions & 0 deletions pkg/validator/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,13 @@ var testCases = []struct {
{name: "allowedSourceTenantsAndGroupSourceTenantsWithOneTenant", validator: hasAllowedSourceTenants{allowedSourceTenants: []string{"tenant1"}}, group: unmarshaler.RuleGroup{SourceTenants: []string{"tenant1"}}, rule: rulefmt.Rule{Expr: `up{foo="bar"}`}, expectedErrors: 0},
{name: "allowedSourceTenantsAndGroupSourceTenantsWithMultipleTenants", validator: hasAllowedSourceTenants{allowedSourceTenants: []string{"tenant1", "tenant2"}}, group: unmarshaler.RuleGroup{SourceTenants: []string{"tenant1", "tenant2"}}, rule: rulefmt.Rule{Expr: `up{foo="bar"}`}, expectedErrors: 0},
{name: "allowedSourceTenantsAndGroupSourceTenantsWithMultipleTenantsAndOneIsMissing", validator: hasAllowedSourceTenants{allowedSourceTenants: []string{"tenant1", "tenant2"}}, group: unmarshaler.RuleGroup{SourceTenants: []string{"tenant1", "tenant3"}}, rule: rulefmt.Rule{Expr: `up{foo="bar"}`}, expectedErrors: 1},

// hasAllowedEvaluationInterval
{name: "validInterval", validator: hasAllowedEvaluationInterval{minimum: time.Second, maximum: time.Minute, mustBeSet: true}, group: unmarshaler.RuleGroup{Interval: model.Duration(time.Minute)}, expectedErrors: 0},
{name: "unsetRequiredInterval", validator: hasAllowedEvaluationInterval{minimum: time.Second, maximum: time.Minute, mustBeSet: true}, group: unmarshaler.RuleGroup{Interval: 0}, expectedErrors: 1},
{name: "unsetNotRequiredInterval", validator: hasAllowedEvaluationInterval{minimum: time.Second, maximum: time.Minute, mustBeSet: false}, group: unmarshaler.RuleGroup{Interval: 0}, expectedErrors: 0},
{name: "tooShortInterval", validator: hasAllowedEvaluationInterval{minimum: time.Minute, maximum: time.Hour, mustBeSet: true}, group: unmarshaler.RuleGroup{Interval: model.Duration(time.Second)}, expectedErrors: 1},
{name: "tooHighInterval", validator: hasAllowedEvaluationInterval{minimum: time.Minute, maximum: time.Hour, mustBeSet: true}, group: unmarshaler.RuleGroup{Interval: model.Duration(time.Hour * 2)}, expectedErrors: 1},
}

func Test(t *testing.T) {
Expand Down

0 comments on commit bb282dd

Please sign in to comment.