Alerting: Improve validation of query and expressions on rule submit (#53258)

* Improve error messages of server-side expression 
* move validation of alert queries and a condition to eval package
This commit is contained in:
Yuriy Tseretyan
2022-09-21 15:14:11 -04:00
committed by GitHub
parent 879241a48f
commit 2d38664fe6
19 changed files with 311 additions and 146 deletions
+3 -3
View File
@@ -203,19 +203,19 @@ func UnmarshalConditionsCmd(rawQuery map[string]interface{}, refID string) (*Con
cond := condition{}
if i > 0 && cj.Operator.Type != "and" && cj.Operator.Type != "or" {
return nil, fmt.Errorf("classic condition %v operator must be `and` or `or`", i+1)
return nil, fmt.Errorf("condition %v operator must be `and` or `or`", i+1)
}
cond.Operator = cj.Operator.Type
if len(cj.Query.Params) == 0 || cj.Query.Params[0] == "" {
return nil, fmt.Errorf("classic condition %v is missing the query refID argument", i+1)
return nil, fmt.Errorf("condition %v is missing the query refID argument", i+1)
}
cond.QueryRefID = cj.Query.Params[0]
cond.Reducer = classicReducer(cj.Reducer.Type)
if !cond.Reducer.ValidReduceFunc() {
return nil, fmt.Errorf("reducer '%v' in condition %v is not a valid reducer", cond.Reducer, i+1)
return nil, fmt.Errorf("invalid reducer '%v' in condition %v", cond.Reducer, i+1)
}
cond.Evaluator, err = newAlertEvaluator(cj.Evaluator)
+20 -19
View File
@@ -2,6 +2,7 @@ package expr
import (
"context"
"errors"
"fmt"
"strings"
"time"
@@ -43,16 +44,16 @@ func NewMathCommand(refID, expr string) (*MathCommand, error) {
func UnmarshalMathCommand(rn *rawNode) (*MathCommand, error) {
rawExpr, ok := rn.Query["expression"]
if !ok {
return nil, fmt.Errorf("math command for refId %v is missing an expression", rn.RefID)
return nil, errors.New("command is missing an expression")
}
exprString, ok := rawExpr.(string)
if !ok {
return nil, fmt.Errorf("expected math command for refId %v expression to be a string, got %T", rn.RefID, rawExpr)
return nil, fmt.Errorf("math expression is expected to be a string, got %T", rawExpr)
}
gm, err := NewMathCommand(rn.RefID, exprString)
if err != nil {
return nil, fmt.Errorf("invalid math command type in '%v': %v", rn.RefID, err)
return nil, fmt.Errorf("invalid math command type: %w", err)
}
return gm, nil
}
@@ -96,21 +97,21 @@ func NewReduceCommand(refID, reducer, varToReduce string, mapper mathexp.ReduceM
func UnmarshalReduceCommand(rn *rawNode) (*ReduceCommand, error) {
rawVar, ok := rn.Query["expression"]
if !ok {
return nil, fmt.Errorf("no variable specified to reduce for refId %v", rn.RefID)
return nil, errors.New("no expression ID is specified to reduce. Must be a reference to an existing query or expression")
}
varToReduce, ok := rawVar.(string)
if !ok {
return nil, fmt.Errorf("expected reduce variable to be a string, got %T for refId %v", rawVar, rn.RefID)
return nil, fmt.Errorf("expression ID is expected to be a string, got %T", rawVar)
}
varToReduce = strings.TrimPrefix(varToReduce, "$")
rawReducer, ok := rn.Query["reducer"]
if !ok {
return nil, fmt.Errorf("no reducer specified for refId %v", rn.RefID)
return nil, errors.New("no reducer specified")
}
redFunc, ok := rawReducer.(string)
if !ok {
return nil, fmt.Errorf("expected reducer to be a string, got %T for refId %v", rawReducer, rn.RefID)
return nil, fmt.Errorf("expected reducer to be a string, got %T", rawReducer)
}
var mapper mathexp.ReduceMapper = nil
@@ -126,20 +127,20 @@ func UnmarshalReduceCommand(rn *rawNode) (*ReduceCommand, error) {
case "replaceNN":
valueStr, ok := s["replaceWithValue"]
if !ok {
return nil, fmt.Errorf("expected settings.replaceWithValue to be specified when mode is 'replaceNN' for refId %v", rn.RefID)
return nil, errors.New("setting replaceWithValue must be specified when mode is 'replaceNN'")
}
switch value := valueStr.(type) {
case float64:
mapper = mathexp.ReplaceNonNumberWithValue{Value: value}
default:
return nil, fmt.Errorf("expected settings.replaceWithValue to be a number, got %T for refId %v", value, rn.RefID)
return nil, fmt.Errorf("setting replaceWithValue must be a number, got %T", value)
}
default:
return nil, fmt.Errorf("reducer mode %s is not supported for refId %v. Supported only: [dropNN,replaceNN]", mode, rn.RefID)
return nil, fmt.Errorf("reducer mode '%s' is not supported. Supported only: [dropNN,replaceNN]", mode)
}
}
default:
return nil, fmt.Errorf("expected settings to be an object, got %T for refId %v", s, rn.RefID)
return nil, fmt.Errorf("field settings must be an object, got %T for refId %v", s, rn.RefID)
}
}
return NewReduceCommand(rn.RefID, redFunc, varToReduce, mapper)
@@ -211,40 +212,40 @@ func NewResampleCommand(refID, rawWindow, varToResample string, downsampler stri
func UnmarshalResampleCommand(rn *rawNode) (*ResampleCommand, error) {
rawVar, ok := rn.Query["expression"]
if !ok {
return nil, fmt.Errorf("no variable to resample for refId %v", rn.RefID)
return nil, errors.New("no expression ID to resample. must be a reference to an existing query or expression")
}
varToReduce, ok := rawVar.(string)
if !ok {
return nil, fmt.Errorf("expected resample input variable to be type string, but got type %T for refId %v", rawVar, rn.RefID)
return nil, fmt.Errorf("expected resample input variable to be type string, but got type %T", rawVar)
}
varToReduce = strings.TrimPrefix(varToReduce, "$")
varToResample := varToReduce
rawWindow, ok := rn.Query["window"]
if !ok {
return nil, fmt.Errorf("no time duration specified for the window in resample command for refId %v", rn.RefID)
return nil, errors.New("no time duration specified for the window in resample command")
}
window, ok := rawWindow.(string)
if !ok {
return nil, fmt.Errorf("expected resample window to be a string, got %T for refId %v", rawWindow, rn.RefID)
return nil, fmt.Errorf("resample window is expected to be a string, got %T", rawWindow)
}
rawDownsampler, ok := rn.Query["downsampler"]
if !ok {
return nil, fmt.Errorf("no downsampler function specified in resample command for refId %v", rn.RefID)
return nil, errors.New("no downsampler function specified in resample command")
}
downsampler, ok := rawDownsampler.(string)
if !ok {
return nil, fmt.Errorf("expected resample downsampler to be a string, got type %T for refId %v", downsampler, rn.RefID)
return nil, fmt.Errorf("expected resample downsampler to be a string, got type %T", downsampler)
}
rawUpsampler, ok := rn.Query["upsampler"]
if !ok {
return nil, fmt.Errorf("no downsampler specified in resample command for refId %v", rn.RefID)
return nil, errors.New("no upsampler specified in resample command")
}
upsampler, ok := rawUpsampler.(string)
if !ok {
return nil, fmt.Errorf("expected resample downsampler to be a string, got type %T for refId %v", upsampler, rn.RefID)
return nil, fmt.Errorf("expected resample downsampler to be a string, got type %T", upsampler)
}
return NewResampleCommand(rn.RefID, window, varToResample, downsampler, upsampler, rn.TimeRange)
+1 -1
View File
@@ -189,7 +189,7 @@ func buildGraphEdges(dp *simple.DirectedGraph, registry map[string]Node) error {
}
if neededNode.ID() == cmdNode.ID() {
return fmt.Errorf("can not add self referencing node for var '%v' ", neededVar)
return fmt.Errorf("expression '%v' cannot reference itself. Must be query or another expression", neededVar)
}
if cmdNode.CMDType == TypeClassicConditions {
+1 -1
View File
@@ -77,7 +77,7 @@ func TestServicebuildPipeLine(t *testing.T) {
},
},
},
expectErrContains: "self referencing node",
expectErrContains: "expression 'A' cannot reference itself. Must be query or another expression",
},
{
name: "missing dependency will error",
+1 -1
View File
@@ -123,7 +123,7 @@ func (s Series) Reduce(refID, rFunc string, mapper ReduceMapper) (Number, error)
floatField := Float64Field(*fVec)
reduceFunc, err := GetReduceFunc(rFunc)
if err != nil {
return number, err
return number, fmt.Errorf("invalid expression '%s': %w", refID, err)
}
f = reduceFunc(&floatField)
if f != nil && mapper != nil {
+3 -3
View File
@@ -99,7 +99,7 @@ func (gn *CMDNode) Execute(ctx context.Context, vars mathexp.Vars, s *Service) (
func buildCMDNode(dp *simple.DirectedGraph, rn *rawNode) (*CMDNode, error) {
commandType, err := rn.GetCommandType()
if err != nil {
return nil, fmt.Errorf("invalid expression command type in '%v'", rn.RefID)
return nil, fmt.Errorf("invalid command type in expression '%v': %w", rn.RefID, err)
}
node := &CMDNode{
@@ -120,10 +120,10 @@ func buildCMDNode(dp *simple.DirectedGraph, rn *rawNode) (*CMDNode, error) {
case TypeClassicConditions:
node.Command, err = classic.UnmarshalConditionsCmd(rn.Query, rn.RefID)
default:
return nil, fmt.Errorf("expression command type '%v' in '%v' not implemented", commandType, rn.RefID)
return nil, fmt.Errorf("expression command type '%v' in expression '%v' not implemented", commandType, rn.RefID)
}
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to parse expression '%v': %w", rn.RefID, err)
}
return node, nil