Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow multiple matchers for NetObserv multi-tenancy (NETOBSERV-171) #440

Merged
merged 9 commits into from
Mar 10, 2023

Conversation

jotak
Copy link
Contributor

@jotak jotak commented Jan 18, 2023

JIRA: https://issues.redhat.com/browse/NETOBSERV-171

In short, without this PR, only one matcher injection for multi-tenancy is allowed: e.g. namespace=foo
NetObserv is an observability solution dedicated to the network, that stores flow logs in Loki. Implementing multi-tenancy for NetObserv requires injecting more complex matchers for handling flow logs sources and destinations: e.g.
src-namespace=foo OR dst-namespace=foo (as a single query injection).

Changes:

  • Updated result data from OPA Authz (OPA breaking change)
  • Query injection needs to handle several matchers, and logical OR
  • Fix string escaping issue in Loki reencoder
  • Add a few tests
  • As an aside, allow building with podman

Related PRs:

@jotak
Copy link
Contributor Author

jotak commented Jan 18, 2023

/cc @periklis

Comment on lines 75 to 76
expr.Walk(func(expr interface{}) {
switch le := expr.(type) {
case *logqlv2.StreamMatcherExpr:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW considering the observatorium/opa-openshift#15 draft PR it looks like the opa.matcher (considering we can only do vector or vector with stream label matcher in LogQL) is overloaded. The label.Matchers should maybe include a field that tells if they are going to be appended as label selectors in StreamMatcherExpr or as line filters., e.g.:

	expr.Walk(func(expr interface{}) {
		switch le := expr.(type) {
		case *logqlv2.StreamMatcherExpr:
			le.AppendMatchers(lm)
		case *logqlv2.LineFilterExpr: // I believe our syntax does not support this case yet
			if mInfo.LogicalOp == logicalOr {
				le.AppendORMatchers(mInfo.Matchers)
			} else {
				le.AppendMatchers(mInfo.Matchers)
			}
		default:
			// Do nothing
		}
	})

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, or we do line filters anyway, regardless if it's vectors or not.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking on the StreamMatchersExpr.String() implementation below I see that you circuit break with a closing curly brace in case of an or-Matcher and move forward with a line filter. This looks very to me. In the above sample I propose to clearly render or-Matchers as line filters, e.g. by appending a new line filter with or-s at the end of the query.

PS: I mention Line Filter above, but I am not sure if it the right expression. IIRC logical expressions on matchers are supported only on label filters and not line filters. Please consider the parser tests for the right expression.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried your suggested approach at first but I hit an issue: if I remember correctly, Line filters "walker" is only invoked if there are actually line filters in the initial expression, it isn't invoked at all if there are none. Or maybe I didn't proceed like what you were thinking about... I can retry and let you see the issue if you want

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the approach you suggest would work but requires a little more refactoring of the traversal pattern

logql/v2/parser_test.go Outdated Show resolved Hide resolved
JIRA: https://issues.redhat.com/browse/NETOBSERV-171

- Updated result data from OPA Authz
- Query injection needs to handle several matchers, and logical OR (this
  is WIP)
- Update tests
It produces valid logql, however the parser is currently failing to
parse it :(
- Use strconv.Quote to preserve string escaping
- Add test
- Makefile: allow building with podman
@jotak jotak changed the title NETOBSERV-171 PoC multi-tenancy (WIP) Allow multiple matchers for NetObserv multi-tenancy (NETOBSERV-171) Feb 17, 2023
@jotak jotak marked this pull request as ready for review February 17, 2023 15:26
@jotak
Copy link
Contributor Author

jotak commented Feb 17, 2023

@periklis this one is ready for review

Copy link
Contributor

@periklis periklis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general the approach looks good to me. However I suggest we keep rendering rendering stream matchers independently from label filters with logical expressions.

Comment on lines 75 to 76
expr.Walk(func(expr interface{}) {
switch le := expr.(type) {
case *logqlv2.StreamMatcherExpr:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking on the StreamMatchersExpr.String() implementation below I see that you circuit break with a closing curly brace in case of an or-Matcher and move forward with a line filter. This looks very to me. In the above sample I propose to clearly render or-Matchers as line filters, e.g. by appending a new line filter with or-s at the end of the query.

PS: I mention Line Filter above, but I am not sure if it the right expression. IIRC logical expressions on matchers are supported only on label filters and not line filters. Please consider the parser tests for the right expression.

- Grammar has to be updated so that LogQueryExpr doesn't translate into
  a simple StreamMatcherExpr when there is no lof pipeline
- New func LogQueryExpr.AppendPipelineMatchers that injects "or" matchers
- update tests
Copy link
Contributor

@periklis periklis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general looks good to me. I will add my approve once I have tested this in companion with observatorium-api on our Loki Operator release branches.

logql/v2/ast.go Outdated
@@ -61,17 +61,15 @@ func (s *StreamMatcherExpr) Walk(fn WalkFn) {
func (s *StreamMatcherExpr) String() string {
var sb strings.Builder

sb.WriteString("{")

sb.WriteRune('{')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the value of this? I am just curious.

Copy link
Contributor Author

@jotak jotak Feb 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume it's supposed to be more optimized but I admit I have no certitude I could not find a clear statement on that while googling, and doing a quick benchmark isn't conclusive. I can revert then ...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's keep it the old way

logql/v2/ast.go Show resolved Hide resolved
Comment on lines 1532 to 1540
func TestQuotesEncode(t *testing.T) {
expr, err := ParseExpr(`{app="test"}`)
require.NoError(t, err)
require.Equal(t, `{app="test"}`, expr.String())

expr, err = ParseExpr("{app=\"test\"}|~`key\":\"val\"`")
require.NoError(t, err)
require.Equal(t, `{app="test"} |~ "key\":\"val\""`, expr.String())
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we eliminate testify usage here? This repo is under shared maintenance with many teams and we don't have any vote on changing the way we write tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw: I tend to write flat tests rather than test cases arrays (aka table-driven tests), that's a personal preference, but since the norm here seems to be test cases arrays, I can definitely comply with it.

Comment on lines 485 to 504
{
input: `{first="value"} |~ "key\":\"val\""`,
expr: &LogQueryExpr{
filter: LogPipelineExpr{
&LogFilterExpr{
filter: "|~",
value: "key\":\"val\"",
},
},
left: &StreamMatcherExpr{
matchers: []*labels.Matcher{
{
Type: labels.MatchEqual,
Name: "first",
Value: "value",
},
},
},
},
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the value added by this test case not covered by any of the existing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's indeed similar to TestQuotesEncode below except this one tests double-quotes whereas the other one tests backquotes. I can move it to the TestQuotesEncode test, maybe it will be more clear & consistent

Comment on lines 389 to 418
{
input: `{first="value"} | second=~"foo|bar" or third=~"foo|bar"`,
expr: &LogQueryExpr{
filter: LogPipelineExpr{
&LogLabelFilterExpr{
labelName: "second",
comparisonOp: "=~",
labelValue: "foo|bar",
right: []*LogLabelFilterExpr{
{
labelName: "third",
comparisonOp: "=~",
labelValue: "foo|bar",
isNested: true,
chainOp: "or",
},
},
},
},
left: &StreamMatcherExpr{
matchers: []*labels.Matcher{
{
Type: labels.MatchEqual,
Name: "first",
Value: "value",
},
},
},
},
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this test case already covered by the next one? Or is this all about testing the strconv.Quote? If yes, on the last question then we can drop this test as per strconv.Quote having enough tests in the Golang std library.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I wrote this test because it was the closest I had from the actual netobserv queries with tenancy matchers. So it helped me while doing the implementation. But you're right that it overlaps with other tests. I can remove it.

Copy link
Contributor

@periklis periklis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still looks good to me

Copy link
Contributor

@periklis periklis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving this PR as is right now. Waiting for merge until I get a green light from OpenShift Logging release manager as per main branch being used for multiple releases right now (i.e. OpenShift Logging 5.5, 5.6 and upcoming 5.7).

@periklis periklis merged commit 37022fd into observatorium:main Mar 10, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants