-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
perf: optimize activation of bundles with no inter-bundle path overlap #7155
base: main
Are you sure you want to change the base?
Changes from 9 commits
8fbdcdd
99c92e4
fb7e889
9710eef
f790369
3b1a948
7b6b251
5cd286b
2309403
c5c1dfa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1954,6 +1954,9 @@ bar.baz contains "quz" if true`, | |
"mod8.rego": `package badrules.complete_partial | ||
p := 1 | ||
p[r] := 2 if { r := "foo" }`, | ||
|
||
"mod9.rego": `package anotherbadrules.dataoverlap | ||
p { true }`, | ||
}) | ||
|
||
c.WithPathConflictsCheck(func(path []string) (bool, error) { | ||
|
@@ -1963,7 +1966,7 @@ p[r] := 2 if { r := "foo" }`, | |
return false, fmt.Errorf("unexpected error") | ||
} | ||
return false, nil | ||
}) | ||
}).WithPathConflictsCheckRoots([]string{"badrules"}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps this is deserving of its own test, so that we assert the behavior of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will do. I'll add a new unit test for |
||
|
||
compileStages(c, c.checkRuleConflicts) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ | |
package ast | ||
|
||
import ( | ||
"slices" | ||
"strings" | ||
) | ||
|
||
|
@@ -18,8 +19,36 @@ func CheckPathConflicts(c *Compiler, exists func([]string) (bool, error)) Errors | |
return nil | ||
} | ||
|
||
for _, node := range root.Children { | ||
errs = append(errs, checkDocumentConflicts(node, exists, nil)...) | ||
if len(c.pathConflictCheckRoots) == 0 || slices.Contains(c.pathConflictCheckRoots, "") { | ||
for _, child := range root.Children { | ||
errs = append(errs, checkDocumentConflicts(child, exists, nil)...) | ||
} | ||
return errs | ||
} | ||
|
||
for _, rootPath := range c.pathConflictCheckRoots { | ||
// traverse AST from `path` to go to the new root | ||
paths := strings.Split(rootPath, "/") | ||
node := root | ||
nodeNotFound := false | ||
for _, key := range paths { | ||
child := node.Child(String(key)) | ||
if child == nil { | ||
nodeNotFound = true | ||
break | ||
} | ||
node = child | ||
} | ||
|
||
if nodeNotFound { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: we could just do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch! |
||
// could not find the node from the AST (e.g. `path` is from a data file) | ||
// then no conflict is possible | ||
continue | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we have a test case that covers this scenario? If not, to be exhaustive in our testing, we should add one. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. It's implicitly tested with the |
||
} | ||
|
||
for _, child := range node.Children { | ||
errs = append(errs, checkDocumentConflicts(child, exists, paths)...) | ||
} | ||
} | ||
|
||
return errs | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6574,6 +6574,194 @@ func TestGetNormalizedBundleName(t *testing.T) { | |
} | ||
} | ||
|
||
func TestBundleActivationWithRootOverlap(t *testing.T) { | ||
ctx := context.Background() | ||
plugin := getPluginWithExistingLoadedBundle( | ||
t, | ||
"policy-bundle", | ||
[]string{"foo/bar"}, | ||
nil, | ||
[]testModule{ | ||
{ | ||
Path: "foo/bar/bar.rego", | ||
Data: `package foo.bar | ||
result := true`, | ||
}, | ||
}, | ||
) | ||
|
||
bundleName := "new-bundle" | ||
plugin.status[bundleName] = &Status{Name: bundleName, Metrics: metrics.New()} | ||
plugin.downloaders[bundleName] = download.New(download.Config{}, plugin.manager.Client(""), bundleName) | ||
|
||
b := getTestBundleWithData( | ||
[]string{"foo/bar/baz"}, | ||
[]byte(`{"foo": {"bar": 1, "baz": "qux"}}`), | ||
nil, | ||
) | ||
|
||
b.Manifest.Init() | ||
plugin.oneShot(ctx, bundleName, download.Update{Bundle: &b, Metrics: metrics.New(), Size: snapshotBundleSize}) | ||
|
||
// "foo/bar" and "foo/bar/baz" overlap with each other; activation will fail | ||
status, ok := plugin.status[bundleName] | ||
if !ok { | ||
t.Fatalf("Expected to find status for %s, found nil", bundleName) | ||
} | ||
if status.Code != errCode { | ||
t.Fatalf("Expected status code to be %s, found %s", errCode, status.Code) | ||
} | ||
if !strings.Contains(status.Message, "detected overlapping") { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: but we say One way to make sure the check and error message always are in agreement across code changes is to extract the expected value into a var, e.g.: if exp := "detected overlapping roots"; !strings.Contains(status.Message, exp) {
t.Fatalf(`Expected status message to contain "%s", found %s`, exp, status.Message)
} |
||
t.Fatalf(`Expected status message to contain "detected overlapping roots", found %s`, status.Message) | ||
} | ||
} | ||
|
||
func TestBundleActivationWithNoManifestRootsButWithPathConflict(t *testing.T) { | ||
ctx := context.Background() | ||
plugin := getPluginWithExistingLoadedBundle( | ||
t, | ||
"policy-bundle", | ||
[]string{"foo/bar"}, | ||
nil, | ||
[]testModule{ | ||
{ | ||
Path: "foo/bar/bar.rego", | ||
Data: `package foo.bar | ||
result := true`, | ||
}, | ||
}, | ||
) | ||
|
||
bundleName := "new-bundle" | ||
plugin.status[bundleName] = &Status{Name: bundleName, Metrics: metrics.New()} | ||
plugin.downloaders[bundleName] = download.New(download.Config{}, plugin.manager.Client(""), bundleName) | ||
|
||
b := getTestBundleWithData( | ||
nil, | ||
[]byte(`{"foo": {"bar": 1, "baz": "qux"}}`), | ||
nil, | ||
) | ||
|
||
b.Manifest.Init() | ||
plugin.oneShot(ctx, bundleName, download.Update{Bundle: &b, Metrics: metrics.New(), Size: snapshotBundleSize}) | ||
|
||
// new bundle has path "foo/bar" which overlaps with existing bundle with path "foo/bar"; activation will fail | ||
status, ok := plugin.status[bundleName] | ||
if !ok { | ||
t.Fatalf("Expected to find status for %s, found nil", bundleName) | ||
} | ||
if status.Code != errCode { | ||
t.Fatalf("Expected status code to be %s, found %s", errCode, status.Code) | ||
} | ||
if !strings.Contains(status.Message, "detected overlapping") { | ||
t.Fatalf(`Expected status message to contain "detected overlapping roots", found %s`, status.Message) | ||
} | ||
} | ||
|
||
func TestBundleActivationWithNoManifestRootsOverlap(t *testing.T) { | ||
ctx := context.Background() | ||
plugin := getPluginWithExistingLoadedBundle( | ||
t, | ||
"policy-bundle", | ||
[]string{"foo/bar"}, | ||
nil, | ||
[]testModule{ | ||
{ | ||
Path: "foo/bar/bar.rego", | ||
Data: `package foo.bar | ||
result := true`, | ||
}, | ||
}, | ||
) | ||
|
||
bundleName := "new-bundle" | ||
plugin.status[bundleName] = &Status{Name: bundleName, Metrics: metrics.New()} | ||
plugin.downloaders[bundleName] = download.New(download.Config{}, plugin.manager.Client(""), bundleName) | ||
|
||
b := getTestBundleWithData( | ||
[]string{"foo/baz"}, | ||
nil, | ||
[]testModule{ | ||
{ | ||
Path: "foo/bar/baz.rego", | ||
Data: `package foo.baz | ||
result := true`, | ||
}, | ||
}, | ||
) | ||
|
||
b.Manifest.Init() | ||
plugin.oneShot(ctx, bundleName, download.Update{Bundle: &b, Metrics: metrics.New(), Size: snapshotBundleSize}) | ||
|
||
status, ok := plugin.status[bundleName] | ||
if !ok { | ||
t.Fatalf("Expected to find status for %s, found nil", bundleName) | ||
} | ||
if status.Code != "" { | ||
t.Fatalf("Expected status code to be empty, found %s", status.Code) | ||
} | ||
} | ||
|
||
type testModule struct { | ||
Path string | ||
Data string | ||
} | ||
|
||
func getTestBundleWithData(roots []string, data []byte, modules []testModule) bundle.Bundle { | ||
b := bundle.Bundle{} | ||
|
||
if len(roots) > 0 { | ||
b.Manifest = bundle.Manifest{Roots: &roots} | ||
} | ||
|
||
if len(data) > 0 { | ||
b.Data = util.MustUnmarshalJSON(data).(map[string]interface{}) | ||
} | ||
|
||
for _, m := range modules { | ||
if len(m.Data) > 0 { | ||
b.Modules = append(b.Modules, | ||
bundle.ModuleFile{ | ||
Path: m.Path, | ||
Parsed: ast.MustParseModule(m.Data), | ||
Raw: []byte(m.Data), | ||
}, | ||
) | ||
} | ||
} | ||
|
||
b.Manifest.Init() | ||
|
||
return b | ||
} | ||
|
||
func getPluginWithExistingLoadedBundle(t *testing.T, bundleName string, roots []string, data []byte, modules []testModule) *Plugin { | ||
ctx := context.Background() | ||
store := inmem.NewWithOpts(inmem.OptRoundTripOnWrite(false), inmem.OptReturnASTValuesOnRead(true)) | ||
manager := getTestManagerWithOpts(nil, store) | ||
plugin := New(&Config{}, manager) | ||
plugin.status[bundleName] = &Status{Name: bundleName, Metrics: metrics.New()} | ||
plugin.downloaders[bundleName] = download.New(download.Config{}, plugin.manager.Client(""), bundleName) | ||
|
||
ensurePluginState(t, plugin, plugins.StateNotReady) | ||
|
||
b := getTestBundleWithData(roots, data, modules) | ||
|
||
plugin.oneShot(ctx, bundleName, download.Update{Bundle: &b, Metrics: metrics.New(), Size: snapshotBundleSize}) | ||
|
||
ensurePluginState(t, plugin, plugins.StateOK) | ||
|
||
if status, ok := plugin.status[bundleName]; !ok { | ||
t.Fatalf("Expected to find status for %s, found nil", bundleName) | ||
} else if status.Type != bundle.SnapshotBundleType { | ||
t.Fatalf("Expected snapshot bundle but got %v", status.Type) | ||
} else if status.Size != snapshotBundleSize { | ||
t.Fatalf("Expected snapshot bundle size %d but got %d", snapshotBundleSize, status.Size) | ||
} | ||
|
||
return plugin | ||
} | ||
|
||
func writeTestBundleToDisk(t *testing.T, srcDir string, signed bool) bundle.Bundle { | ||
t.Helper() | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The compiler is agnostic to concepts such as bundle activation. I suggest we drop the second sentence, or reformulate it to say something general about performance optimization. E.g.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should also mention the expected format of the paths:
/
-separated,data
root document excluded.