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

storage/inmem: allow opt out of object roundtrip #5015

Merged

Conversation

srenatus
Copy link
Contributor

This is a continuance of @willbeason's work in #4709.

It will allow everyone to use an in-memory store without roundtripping. Before, we had been roundtripping every object written to that store through json, to canonicalize its data type. However, for many code paths, this isn't necessary. Notably, when our own http handlers take in JSON through encoding/json and unmarshal it, it'll always be in the right format, and the extra roundtrip can be avoided.

This roundtrip would also create a deep copy of the object, and with the opt-out, that no longer happens.

Fixes #4708.

@srenatus srenatus force-pushed the sr+wb/storage/opt-out-of-roundtrip branch 7 times, most recently from ca89e34 to 7128f18 Compare August 16, 2022 11:58
@srenatus srenatus marked this pull request as ready for review August 16, 2022 12:12
@srenatus
Copy link
Contributor Author

@ashutosh-narkar I've requested your review because I had changed some things in the bundle plugin tests. I think we're clear -- whenever a bundle is read, it'll go through encoding/json, and thus only have "safe" types, like, using []interface{}{"string1", "string2"} and not []string{"string1", "string2"}. It's only the test setup that necessitated the changes.

Copy link
Contributor Author

@srenatus srenatus left a comment

Choose a reason for hiding this comment

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

comments

@@ -18,7 +18,7 @@ import (
"github.com/open-policy-agent/opa/loader"
"github.com/open-policy-agent/opa/rego"
"github.com/open-policy-agent/opa/storage"
"github.com/open-policy-agent/opa/storage/inmem"
inmem "github.com/open-policy-agent/opa/storage/inmem/test"
"github.com/open-policy-agent/opa/util"
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've flicked all (or most) tests to use the non-roundtripping inmem store. It just goes through smoothly, so we're probably OK wrt how it works in OPA-as-server.

@@ -55,7 +55,13 @@ func (l *Result) Compiler() (*ast.Compiler, error) {

// Store returns a Store object with the documents from this loader result.
func (l *Result) Store() (storage.Store, error) {
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 couldn't find this one being used in our code bases, but I figured for completeness' sake, we could still add 👇

@@ -402,7 +402,7 @@ func TestPluginOneShotDeltaBundle(t *testing.T) {
p2 := bundle.PatchOperation{
Op: "upsert",
Path: "/a/foo",
Value: []string{"hello", "world"},
Value: []interface{}{"hello", "world"},
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Before, this would be roundtripped though json, and it would be turned into []interface{} (see playground). Now, we're feeding in the proper value types.

In real life, this shouldn't matter, since when bundles are read, their Values should come from a json string unmarshalled using encoding/json, so we'd only ever get []interface{}, never []string, there.

Copy link
Contributor

Choose a reason for hiding this comment

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

The Go Playground example you included nicely illustrates your point. This change makes sense.

Comment on lines 446 to +452
data, err := manager.Store.Read(ctx, txn, storage.Path{})
expData := util.MustUnmarshalJSON([]byte(`{"a": {"baz": "bux", "foo": ["hello", "world"]}, "system": {"bundles": {"test-bundle": {"etag": "foo", "manifest": {"revision": "delta", "roots": ["a"]}}}}}`))

if err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(data, expData) {
t.Fatalf("Bad data content. Exp:\n%v\n\nGot:\n\n%v", expData, data)
}
expData := util.MustUnmarshalJSON([]byte(`{"a": {"baz": "bux", "foo": ["hello", "world"]}, "system": {"bundles": {"test-bundle": {"etag": "foo", "manifest": {"revision": "delta", "roots": ["a"]}}}}}`))
if !reflect.DeepEqual(data, expData) {
t.Fatalf("Bad data content. Exp:\n%#v\n\nGot:\n\n%#v", expData, data)
Copy link
Contributor

Choose a reason for hiding this comment

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

This was a small, but good fix. 👍

The err check is for the Store a few lines up, and MustUnmarshalJSON doesn't return an error, so an independent check for its error condition (wrong result) is put in a few lines down. This is clearer than the "chained-if" style that was there before, I think.

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 that's something a linter should be able to catch, anyways. If the if's branch breaks the control flow (like, return, or t.Fatal or panic), then there's no need to wrap the other conditionals into else. But maybe it's just a pet peeve of mine.

Comment on lines 9 to 18
// Callers should disable use this if they can guarantee all objects passed to
// Write() are serializable to JSON. Failing to do so may result in undefined
// behavior, including panics.
//
// If setting to false consider deep-copying any objects passed to Write unless
// they can guarantee the objects will not be mutated after being written.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// Callers should disable use this if they can guarantee all objects passed to
// Write() are serializable to JSON. Failing to do so may result in undefined
// behavior, including panics.
//
// If setting to false consider deep-copying any objects passed to Write unless
// they can guarantee the objects will not be mutated after being written.
// Callers should disable this if they cannot guarantee all objects passed to
// Write() are serializable to JSON. Failing to do so may result in undefined
// behavior, including panics.
//
// If setting to false, callers should consider deep-copying any objects passed
// to Write, unless they can guarantee the objects will not be mutated after
// being written to the store.

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've updated the comment in a later commit -- what do you think about that one?

Copy link
Contributor

Choose a reason for hiding this comment

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

One second... I'll go see the latest revisions. 😄

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm a little confused by the "they" on line 23. I assume it's referring to the caller, and that they have a responsibility to ensure no mutations are happening to objects going into the store?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They need to ensure that if the object are mutated (in the store) it won't matter for them. Basically "hand them over"... I'm grateful for all suggestions here 😃

Copy link
Contributor

Choose a reason for hiding this comment

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

I added some extra words in the suggested change block to make it clear it's the caller we're talking about, because when I hear "consider <action>" by itself, I usually parse that as a command directed at me, instead of another target.

I know I'm bikeshedding a bit there. 🚲 It's probably fine, but clarity is always preferred in comments that may be there for a bit. 😄

@srenatus
Copy link
Contributor Author

Ideally, the code examples in the inmem/example_test.go would be updated, too. But I didn't get to that, and didn't want to push this further 😅

@philipaconrad
Copy link
Contributor

Looks like DCO blew up because of a change in email identity of the committer. 🙁

@srenatus I think your GMail and Styra emails got mixed somewhere in the commit history, and confused the bot.

@philipaconrad
Copy link
Contributor

Ideally, the code examples in the inmem/example_test.go would be updated, too. But I didn't get to that, and didn't want to push this further sweat_smile

Understandable. I don't see anything worrying in these commits, but definitely ping @ashutosh-narkar again if they haven't had time to see these changes.

I'm good to mark Approve in a bit once tests go green. 👍

Comment on lines 17 to 18
// If setting to false consider deep-copying any objects passed to Write unless
// they can guarantee the objects will not be mutated after being written.
Copy link
Contributor

@philipaconrad philipaconrad Aug 17, 2022

Choose a reason for hiding this comment

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

Suggested change
// If setting to false consider deep-copying any objects passed to Write unless
// they can guarantee the objects will not be mutated after being written.
// If setting to false, callers should consider deep-copying any objects passed to
// Write() unless they can guarantee the objects will not be mutated after being written.

[nit] The "they" or "consider" blocks feel a little ambiguous, unless we tether at least on of them to an obvious subject somewhere nearby in the sentence.

philipaconrad
philipaconrad previously approved these changes Aug 17, 2022
Copy link
Contributor

@philipaconrad philipaconrad left a comment

Choose a reason for hiding this comment

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

I've only got [nit]-level suggestions on this PR. The rest looks great! 😄

Marking Approve, so that @srenatus can get it merged when they have time to look at it tomorrow.

@srenatus srenatus dismissed stale reviews from ashutosh-narkar and philipaconrad via 8362bb2 August 18, 2022 05:21
@srenatus srenatus force-pushed the sr+wb/storage/opt-out-of-roundtrip branch from 6456a23 to 8362bb2 Compare August 18, 2022 05:21
@srenatus
Copy link
Contributor Author

I'll update the comment and merge this!

Add option to inmem.store which allows disabling the round-tripping
through JSON when adding data to the store.

This option is intended for callers who can guarantee the objects they
pass to Write are JSON objects, and have properly ensured the object
will be only be accessed by store once added.

Fixes open-policy-agent#4708.

This is continuance of open-policy-agent#4709,
adding these bits:

* storage/inmem: backwards-compat nitpicks, test adaptations

  I might have overshot here, but adding variable-length function parameters
  is not a backwards-compatible move. Concretely, if you had been using code like

      var x func() storage.Store = inmem.New

  going from New() to New(...Opts) would break it.

* storage/inmem: use it where possible without roundtrip

* storage/inmem: deal with nil map

  It looks like this is something the roundtrip had guarded us from.
  Now, we'll explicitly check this.

  This came up when running the bundle tests with roundtripping disabled.

* loader: add StoreWithOpts convenience method

Co-authored-by: Will Beason <willbeason@google.com>
Co-authored-by: Philip Conrad <conradp@chariot-chaser.net>
Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
@srenatus srenatus force-pushed the sr+wb/storage/opt-out-of-roundtrip branch from 8362bb2 to 72d8d50 Compare August 18, 2022 06:30
@srenatus srenatus merged commit 137d7b6 into open-policy-agent:main Aug 18, 2022
@srenatus srenatus deleted the sr+wb/storage/opt-out-of-roundtrip branch August 18, 2022 06:38
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.

Allow for adding objects to storage without round-tripping through JSON
3 participants