Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion central/metrics/aggregator/common/condition.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"math"
"slices"
"strconv"
"strings"

"github.com/pkg/errors"
"github.com/stackrox/rox/pkg/glob"
Expand All @@ -15,9 +16,16 @@ type operator string
const (
opZ operator = ""
opEQ operator = "="
opNE operator = "!="
opGT operator = ">"
opGE operator = ">="
opLT operator = "<"
opLE operator = "<="

opOR operator = "OR"
)

var knownOperators = []operator{opEQ}
var knownOperators = []operator{opEQ, opNE, opGT, opGE, opLT, opLE, opOR}

type Condition struct {
op operator
Expand Down Expand Up @@ -53,6 +61,10 @@ func (c *Condition) validate() error {
case !slices.Contains(knownOperators, c.op):
return fmt.Errorf("operator in %q is not one of %q", c, knownOperators)
// Test argument:
case c.op == opOR:
if len(c.arg) > 0 {
return fmt.Errorf("unexpected argument in %q", c)
}
case len(c.arg) == 0:
return fmt.Errorf("missing argument in %q", c)
case !c.isFloatArg() && !c.isGlobArg():
Expand Down Expand Up @@ -94,6 +106,18 @@ func (c *Condition) compareStrings(a, b string) bool {
return a != "" && b == ""
case opEQ:
return glob.Pattern(b).Ptr().Match(a)
case opNE:
return !glob.Pattern(b).Ptr().Match(a)
case opGT:
return strings.Compare(strings.ToLower(a), strings.ToLower(b)) > 0
case opGE:
return strings.EqualFold(a, b) ||
strings.Compare(strings.ToLower(a), strings.ToLower(b)) > 0
case opLT:
return strings.Compare(strings.ToLower(a), strings.ToLower(b)) < 0
case opLE:
return strings.EqualFold(a, b) ||
strings.Compare(strings.ToLower(a), strings.ToLower(b)) < 0
}
return false
}
Expand All @@ -103,6 +127,16 @@ func (c *Condition) compareFloats(a, b float64) bool {
switch c.op {
case opEQ:
return math.Abs(a-b) <= epsilon
case opNE:
return math.Abs(a-b) > epsilon
case opGT:
return a > b
case opGE:
return a >= b
case opLT:
return a < b
case opLE:
return a <= b
}
return false
}
49 changes: 47 additions & 2 deletions central/metrics/aggregator/common/condition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ func Test_condition_match(t *testing.T) {
ok := (&Condition{"=", "value"}).match(value)
assert.True(t, ok)
assert.Equal(t, "value", value)

value = labels["number"]
ok = (&Condition{">=", "1.0"}).match(value)
assert.True(t, ok)
assert.Equal(t, "3.4", value)
})

t.Run("test missing label", func(t *testing.T) {
Expand Down Expand Up @@ -62,6 +67,43 @@ func Test_condition_match(t *testing.T) {
{"string", Condition{"=", "*2"}, false},
{"number", Condition{"=", "3.40.1"}, false},
{"bool", Condition{"=", "true"}, false},

{"string", Condition{"!=", "value1"}, true},
{"string", Condition{"!=", "*2"}, true},
{"number", Condition{"!=", "3.5"}, true},
{"bool", Condition{"!=", "true"}, true},

{"string", Condition{"!=", "value"}, false},
{"string", Condition{"!=", "*alu?"}, false},
{"number", Condition{"!=", "3.4"}, false},
{"bool", Condition{"!=", "false"}, false},

{"number", Condition{">", "3.0"}, true},
{"number", Condition{">", "0"}, true},
{"number", Condition{">", "-1"}, true},
{"number", Condition{">=", "3.4"}, true},

{"number", Condition{">", "3.4"}, false},
{"number", Condition{">", "34"}, false},
{"number", Condition{">", "+4334"}, false},
{"number", Condition{">=", "3.41"}, false},

{"number", Condition{"<", "3.41"}, true},
{"number", Condition{"<", "34"}, true},
{"number", Condition{"<", "+4334"}, true},
{"number", Condition{"<=", "3.4"}, true},

{"number", Condition{"<", "3.4"}, false},
{"number", Condition{"<", "0"}, false},
{"number", Condition{"<", "-1"}, false},
{"number", Condition{"<=", "3.3"}, false},

// string comparison:
{"number", Condition{"<", "3.a"}, true},
{"number", Condition{"!=", "3,4"}, true},
{"string", Condition{">", "val"}, true},
{"string", Condition{"<=", "value1"}, true},
{"number", Condition{"<", ">3.4"}, true},
} {
value := labels[c.label]
actual := c.cond.match(value)
Expand All @@ -80,14 +122,17 @@ func Test_validate(t *testing.T) {
}
cases := []testCase{
// NOK:
{Condition{op: "op"}, `operator in "op" is not one of ["="]`},
{Condition{op: "op"}, `operator in "op" is not one of ["=" "!=" ">" ">=" "<" "<=" "OR"]`},
{Condition{op: "="}, "missing argument in \"=\""},
{Condition{op: "?", arg: "arg"}, `operator in "?arg" is not one of ["="]`},
{Condition{op: "?", arg: "arg"}, `operator in "?arg" is not one of ["=" "!=" ">" ">=" "<" "<=" "OR"]`},
{Condition{op: "=", arg: "[a-"}, "cannot parse the argument in \"=[a-\""},
{Condition{arg: "arg"}, "missing operator in \"arg\""},
{Condition{op: "OR", arg: "arg"}, "unexpected argument in \"ORarg\""},
// OK:
{Condition{}, "empty operator"},
{Condition{op: "OR"}, ""},
{Condition{op: "=", arg: "arg"}, ""},
{Condition{op: ">=", arg: "4.5"}, ""},
{Condition{op: "=", arg: "def"}, ""},
}
for _, c := range cases {
Expand Down
2 changes: 1 addition & 1 deletion central/metrics/aggregator/common/config_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,6 @@ func Test_parseErrors(t *testing.T) {
},
}
labelExpression, err = ParseMetricLabels(config, testLabelOrder)
assert.Equal(t, `invalid configuration: failed to parse a condition for metric "metric1" with label "test": operator in "smoothy" is not one of ["="]`, err.Error())
assert.Equal(t, `invalid configuration: failed to parse a condition for metric "metric1" with label "test": operator in "smoothy" is not one of ["=" "!=" ">" ">=" "<" "<=" "OR"]`, err.Error())
assert.Empty(t, labelExpression)
}
30 changes: 27 additions & 3 deletions central/metrics/aggregator/common/expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,34 @@ package common
type Expression []*Condition

func (expr Expression) match(value string) bool {
if len(expr) == 0 {
return true
}
for _, group := range expr.splitByOR() {
matched := true
for _, cond := range group {
if !cond.match(value) {
matched = false
break
}
}
if matched {
return true
}
}
return false
}

func (expr Expression) splitByOR() []Expression {
var groups []Expression
current := []*Condition{}
for _, cond := range expr {
if !cond.match(value) {
return false
if cond.op == opOR {
groups = append(groups, current)
current = []*Condition{}
} else {
current = append(current, cond)
}
}
return true
return append(groups, current)
}
39 changes: 39 additions & 0 deletions central/metrics/aggregator/common/expression_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,43 @@ func Test_expression(t *testing.T) {
&Condition{"=", "{wrong,value}"},
}
assert.True(t, e.match("value"))

e = Expression{
&Condition{"!=", "value"},
}
assert.False(t, e.match("value"))

e = Expression{
&Condition{">", "3"},
&Condition{"<", "5"},
}
assert.True(t, e.match("4"))
assert.False(t, e.match("3"))
assert.False(t, e.match("5"))

e = Expression{
&Condition{"<", "3"},
&Condition{">", "5"},
}
assert.False(t, e.match("4"))
assert.False(t, e.match("1"))
assert.False(t, e.match("6"))

e = Expression{
&Condition{">", "3"},
&Condition{"<", "5"},
&Condition{op: "OR"},
&Condition{">", "30"},
&Condition{"<", "50"},
&Condition{op: "OR"},
&Condition{">", "300"},
&Condition{"<", "500"},
}

for _, value := range []string{"4", "40", "400"} {
assert.True(t, e.match(value))
}
for _, value := range []string{"3", "5", "30", "50", "300", "500"} {
assert.False(t, e.match(value))
}
}