mirror of https://github.com/grafana/loki
feat: Introduce policy stream mapping (#15982)
**What this PR does / why we need it**: Introduces the idea of policies to Loki, which are recognizable based on the given stream selectors. This is an improved version of https://github.com/grafana/loki/pull/15561 and built on top of https://github.com/grafana/loki/pull/15875. A policy mapping can be configured the following way: ```yaml 12345: policy_stream_mapping: policy6: - selector: `{env="prod"}` priority: 2 - selector: `{env=~"prod|staging"}` priority: 1 - selector: `{team="finance"}` priority: 4 policy7: - selector: `{env=~"prod|dev"}` priority: 3 ``` With that configuration, pushes to tenant `12345` with the labels `{env="prod", team="finance"}` would be assigned to policy6 because the third mapping for policy6 matches these labels and has higher priority than any other matching.pull/15934/merge
parent
2587f3425d
commit
5c8e832260
@ -0,0 +1,47 @@ |
||||
package validation |
||||
|
||||
import "github.com/prometheus/prometheus/model/labels" |
||||
|
||||
type PriorityStream struct { |
||||
Priority int `yaml:"priority" json:"priority" doc:"description=The larger the value, the higher the priority."` |
||||
Selector string `yaml:"selector" json:"selector" doc:"description=Stream selector expression."` |
||||
Matchers []*labels.Matcher `yaml:"-" json:"-"` // populated during validation.
|
||||
} |
||||
|
||||
func (p *PriorityStream) Matches(lbs labels.Labels) bool { |
||||
for _, m := range p.Matchers { |
||||
if !m.Matches(lbs.Get(m.Name)) { |
||||
return false |
||||
} |
||||
} |
||||
return true |
||||
} |
||||
|
||||
type PolicyStreamMapping map[string][]*PriorityStream |
||||
|
||||
func (p *PolicyStreamMapping) PolicyFor(lbs labels.Labels) string { |
||||
var ( |
||||
matchedPolicy *PriorityStream |
||||
found bool |
||||
matchedPolicyName string |
||||
) |
||||
|
||||
for policyName, policyStreams := range *p { |
||||
for _, policyStream := range policyStreams { |
||||
if found && policyStream.Priority <= matchedPolicy.Priority { |
||||
// Even if a match occurs it won't have a higher priority than the current matched policy.
|
||||
continue |
||||
} |
||||
|
||||
if !policyStream.Matches(lbs) { |
||||
continue |
||||
} |
||||
|
||||
found = true |
||||
matchedPolicy = policyStream |
||||
matchedPolicyName = policyName |
||||
} |
||||
} |
||||
|
||||
return matchedPolicyName |
||||
} |
@ -0,0 +1,105 @@ |
||||
package validation |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"github.com/prometheus/prometheus/model/labels" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func Test_PolicyStreamMapping_PolicyFor(t *testing.T) { |
||||
mapping := PolicyStreamMapping{ |
||||
"policy1": []*PriorityStream{ |
||||
{ |
||||
Selector: `{foo="bar"}`, |
||||
Priority: 2, |
||||
Matchers: []*labels.Matcher{ |
||||
labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"), |
||||
}, |
||||
}, |
||||
}, |
||||
"policy2": []*PriorityStream{ |
||||
{ |
||||
Selector: `{foo="bar", daz="baz"}`, |
||||
Priority: 1, |
||||
Matchers: []*labels.Matcher{ |
||||
labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"), |
||||
labels.MustNewMatcher(labels.MatchEqual, "daz", "baz"), |
||||
}, |
||||
}, |
||||
}, |
||||
"policy3": []*PriorityStream{ |
||||
{ |
||||
Selector: `{qyx="qzx", qox="qox"}`, |
||||
Priority: 1, |
||||
Matchers: []*labels.Matcher{ |
||||
labels.MustNewMatcher(labels.MatchEqual, "qyx", "qzx"), |
||||
labels.MustNewMatcher(labels.MatchEqual, "qox", "qox"), |
||||
}, |
||||
}, |
||||
}, |
||||
"policy4": []*PriorityStream{ |
||||
{ |
||||
Selector: `{qyx="qzx", qox="qox"}`, |
||||
Priority: 1, |
||||
Matchers: []*labels.Matcher{ |
||||
labels.MustNewMatcher(labels.MatchEqual, "qyx", "qzx"), |
||||
labels.MustNewMatcher(labels.MatchEqual, "qox", "qox"), |
||||
}, |
||||
}, |
||||
}, |
||||
"policy5": []*PriorityStream{ |
||||
{ |
||||
Selector: `{qab=~"qzx.*"}`, |
||||
Priority: 1, |
||||
Matchers: []*labels.Matcher{ |
||||
labels.MustNewMatcher(labels.MatchRegexp, "qab", "qzx.*"), |
||||
}, |
||||
}, |
||||
}, |
||||
"policy6": []*PriorityStream{ |
||||
{ |
||||
Selector: `{env="prod"}`, |
||||
Priority: 2, |
||||
Matchers: []*labels.Matcher{ |
||||
labels.MustNewMatcher(labels.MatchEqual, "env", "prod"), |
||||
}, |
||||
}, |
||||
{ |
||||
Selector: `{env=~"prod|staging"}`, |
||||
Priority: 1, |
||||
Matchers: []*labels.Matcher{ |
||||
labels.MustNewMatcher(labels.MatchRegexp, "env", "prod|staging"), |
||||
}, |
||||
}, |
||||
{ |
||||
Selector: `{team="finance"}`, |
||||
Priority: 4, |
||||
Matchers: []*labels.Matcher{ |
||||
labels.MustNewMatcher(labels.MatchEqual, "team", "finance"), |
||||
}, |
||||
}, |
||||
}, |
||||
"policy7": []*PriorityStream{ |
||||
{ |
||||
Selector: `{env=~"prod|dev"}`, |
||||
Priority: 3, |
||||
Matchers: []*labels.Matcher{ |
||||
labels.MustNewMatcher(labels.MatchRegexp, "env", "prod|dev"), |
||||
}, |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
require.Equal(t, "policy1", mapping.PolicyFor(labels.FromStrings("foo", "bar"))) |
||||
// matches both policy2 and policy1 but policy1 has higher priority.
|
||||
require.Equal(t, "policy1", mapping.PolicyFor(labels.FromStrings("foo", "bar", "daz", "baz"))) |
||||
// matches policy3 and policy4 but policy3 appears first.
|
||||
require.Equal(t, "policy3", mapping.PolicyFor(labels.FromStrings("qyx", "qzx", "qox", "qox"))) |
||||
// matches no policy.
|
||||
require.Equal(t, "", mapping.PolicyFor(labels.FromStrings("foo", "fooz", "daz", "qux", "quux", "corge"))) |
||||
// matches policy5 through regex.
|
||||
require.Equal(t, "policy5", mapping.PolicyFor(labels.FromStrings("qab", "qzxqox"))) |
||||
|
||||
require.Equal(t, "policy6", mapping.PolicyFor(labels.FromStrings("env", "prod", "team", "finance"))) |
||||
} |
Loading…
Reference in new issue