Cache discharge macaroons in cross model relations client #44

Merged
merged 2 commits into from Aug 9, 2017

Conversation

Projects
None yet
2 participants
Owner

wallyworld commented Aug 8, 2017

Description of change

Add a new expiring cache to hold macaroons. This is used by the cross model relations client so that any discharge macaroons for one api call can be reused with the next (until the macaroons expire and new ones are obtained). This avoids the need to discharge all the time.

QA steps

Run up a cmr scenario and ensure everything works as before - the change should be transparent.

This looks good! Some niggles on the fiddly concurrency stuff in MacaroonCache. And it'd be nice if you could extract out the repetitive macaroon handling a bit in the client facades (not sure how easy that is - and it might be be better in a separate PR anyway).

+ return NewClientWithCache(caller, NewMacaroonCache(clock.WallClock))
+}
+
+// NewClient creates a new client-side CrossModelRelations facade
@babbageclunk

babbageclunk Aug 8, 2017

The name in the doc comment doesn't match the function.

@wallyworld

wallyworld Aug 8, 2017

Owner

doh, fixed

@@ -53,6 +66,11 @@ func (c *Client) PublishRelationChange(change params.RemoteRelationChangeEvent)
args := params.RemoteRelationsChanges{
Changes: []params.RemoteRelationChangeEvent{change},
}
+ // Use any previously cached discharge macaroons.
+ if ms, ok := c.cache.Get(change.RelationToken); ok {
@babbageclunk

babbageclunk Aug 8, 2017

The repeated sequence of cache check and update (and the macaroon discharge retry loop each one wraps) is a bit worrying. It seems like what the methods are actually doing is lost in the boilerplate.
Is there a way we can abstract this pattern? Maybe using a closure to handle the differences between the cases? How do we do this in other macaroon-discharge handling code?

I guess you could argue that this is client facade code so it's where the boilerplate should be, though.

@wallyworld

wallyworld Aug 8, 2017

Owner

It's sorted of repeated but different. Different arg types and return value types etc so logic is slightly different. Some places it's the same. But the Go philosophy tends to be simplicity even if that means extra boilerplate. Trying to extract a closure would work for some of the methods but not all. So we would be adding cognitive overhead there. I erred on the side of simple to read code. I'll take another look to see if something simple and readable can be done.

api/crossmodelrelations/macarooncache.go
+
+// MacaroonCache contains macaroons which are removed at a specified interval.
+type MacaroonCache struct {
+ *cacheInternal
@babbageclunk

babbageclunk Aug 8, 2017

Why have the separate cacheInternal type here? (Rather than having the private fields of cacheInternal in here.)

Oh, I think I understand. It's so that pointers to the cacheInternal inside it that are held by goroutines, like the argument to expiryWorker.loop, won't keep the MacaroonCache instance alive, so the finalizer will be run even though that goroutine's still going. Is that right? It's worth a comment.

@wallyworld

wallyworld Aug 8, 2017

Owner

You are correct. I'll add a comment.

api/crossmodelrelations/macarooncache.go
+}
+
+func (w *expiryWorker) loop(c *cacheInternal) {
+ w.stop = make(chan bool)
@babbageclunk

babbageclunk Aug 8, 2017

Why create the stop chan here, rather than when the expiryWorker is created? I was worried that the finalizer would panic, but then I remembered that sending on a nil channel just blocks. Still, it seems like it would be less tricksy just to create it straight away.

@wallyworld

wallyworld Aug 8, 2017

Owner

Why? Because I did it wrongly. Fixed.

+ c.Assert(err, jc.ErrorIsNil)
+
+ cache.Upsert("token", macaroon.Slice{mac})
+ clock.Advance(6 * time.Minute)
@babbageclunk

babbageclunk Aug 8, 2017

Sometimes this won't test what it thinks it's testing - if the expiry goroutine hasn't gotten to the clock.After call by the time the test calls Advance it won't see the update. It won't be unreliable since the expiry check in Get will still expire it anyway, but this should probably still use WaitAdvance.

@wallyworld

wallyworld Aug 8, 2017

Owner

Oh thank you. I didn't know that. Trap for young players.

@wallyworld wallyworld merged commit 7bbd2f0 into wireup-cmr-macaroon-auth Aug 9, 2017

wallyworld added a commit that referenced this pull request Aug 9, 2017

Merge pull request #44 from wallyworld/crossmodel-api-macaroon-cache
Cache discharge macaroons in cross model relations client
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment