Skip to content
This repository has been archived by the owner on Mar 19, 2024. It is now read-only.

Extract binding logic into Binder #332

Merged
merged 12 commits into from
Sep 1, 2022
Merged

Extract binding logic into Binder #332

merged 12 commits into from
Sep 1, 2022

Conversation

nathancoleman
Copy link
Member

@nathancoleman nathancoleman commented Aug 30, 2022

Changes proposed in this PR:

This is a portion of the refactor changes from #163. The end result of this PR is that the logic for binding xRoutes to Gateways is abstracted out into Binder. You create a Binder using binder.NewBinder( <Gateway> ) and then use it to bind routes by calling <Binder>.Bind( <Route> ). Similar to other recent changes, K8sGateway.Bind( ) will proxy through to Binder.Bind().

How I've tested this PR:

🤖 tests pass

How I expect reviewers to test this PR:

Checklist:

  • Tests added
  • CHANGELOG entry added

    Run make changelog-entry for guidance in authoring a changelog entry, and
    commit the resulting file, which should have a name matching your PR number.
    Entries should use imperative present tense (e.g. Add support for...)

@nathancoleman nathancoleman added pr/no-changelog Skip the CI check that requires a changelog entry pr/run-conformance labels Aug 30, 2022
Also restructures some code to favor early returns over nested blocks, adds some comments, etc.
When a resource is removed out-of-band, such as via the Consul API, the in-memory store is unaware of it. Since the store still contains the deleted resource, the background sync will always early-out and thus we lose the self-healing benefit that the background sync provides.
@nathancoleman nathancoleman marked this pull request as ready for review September 1, 2022 19:41
@@ -222,7 +198,7 @@ func (s *Store) UpsertRoute(ctx context.Context, route store.Route, updateCondit

// bind to gateways
for _, gateway := range s.gateways {
gateway.TryBind(ctx, route)
gateway.Bind(ctx, route)
}

// sync the gateways to consul and route statuses to k8s
Copy link
Contributor

Choose a reason for hiding this comment

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

Just for posterity sake, the optimization of only syncing based off of ingress gateway changes comes from trying to ensure the Sync call below this doesn't hammer Consul every time any resource is reconciled. In the last part of the refactor we do some smarter dirty checking to make sure we're only updating gateways whose state values have actually changed from their last stored state, so even if we temporarily un-gate the syncs an over-sync to Consul right now, it should be fixed soon enough.

@nathancoleman and I talked about that offline.

Copy link
Member Author

@nathancoleman nathancoleman left a comment

Choose a reason for hiding this comment

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

Personal review

)

type syncState struct {
ingress *api.IngressGatewayConfigEntry
Copy link
Member Author

Choose a reason for hiding this comment

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

The changes in this file are mostly around storing the ingress in our in-memory state store

}

return nil
return true, nil
Copy link
Member Author

Choose a reason for hiding this comment

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

Return whether the sync actually completed or not, matches the changes to the interface in core/interfaces.go

@@ -85,7 +86,12 @@ func (r *RequestHandler) OnStreamRequest(streamID int64, req *discovery.Discover
resources := req.GetResourceNames()

// check to make sure we're actually authorized to do this
allowed, err := r.registry.CanFetchSecrets(ctx, GatewayFromContext(ctx), resources)
gateway, err := r.store.GetGateway(ctx, GatewayFromContext(ctx))
Copy link
Member Author

Choose a reason for hiding this comment

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

This aligns with our store transitioning to just handling CRUD operations and nothing more

Copy link
Contributor

Choose a reason for hiding this comment

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

💯

}

return listeners
return newBinder(g.client, g.Gateway, g.GatewayState).Bind(ctx, k8sRoute)
Copy link
Member Author

Choose a reason for hiding this comment

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

Here's where we consume the new Binder to bind the route to the gateway

@@ -197,32 +190,16 @@ func (r *K8sRoute) Validate(ctx context.Context) error {
return r.validator.Validate(ctx, r.RouteState, r.Route)
}

func (r *K8sRoute) OnBindFailed(err error, gateway store.Gateway) {
Copy link
Member Author

Choose a reason for hiding this comment

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

These moved onto the RouteState at internal/k8s/reconciler/state/route.go

@@ -58,22 +57,6 @@ func (s *Store) GatewayExists(ctx context.Context, id core.GatewayID) (bool, err
return found, nil
}

func (s *Store) CanFetchSecrets(ctx context.Context, id core.GatewayID, secrets []string) (bool, error) {
Copy link
Member Author

Choose a reason for hiding this comment

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

This moved onto the store.Gateway interface to limit the amount of logic the store has that isn't related to storing things

Comment on lines -153 to +138
if err := syncGroup.Wait().ErrorOrNil(); err != nil {
return err
}
return nil

return syncGroup.Wait().ErrorOrNil()
Copy link
Member Author

Choose a reason for hiding this comment

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

Just an opportunity for simplification that I noticed

Comment on lines -178 to -183
// Force each gateway to sync its state even though listeners
// on the gateway may not be marked as needing a sync right now
for _, gateway := range s.gateways {
gateway.needsSync = true
}

Copy link
Member Author

Choose a reason for hiding this comment

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

The sync is not gated the way it was before, so there's no need to "force the sync gate open" as this was doing before. The loop just needs to call sync. As Andrew mentions in a comment below in this file, we'll be adding some dirty checking logic in a followup PR to prevent an endless loop of reconciliation.

Copy link
Contributor

@andrewstucki andrewstucki left a comment

Choose a reason for hiding this comment

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

This is awesome that we're starting to get quite a bit closer, enough to actually drop some of the store implementation and old interfaces at this point. I can kind of see the end in sight 😅

Feel free to drop the Listener* stuff at this point in the store package, or you can in a subsequent PR 🤷

@@ -85,7 +86,12 @@ func (r *RequestHandler) OnStreamRequest(streamID int64, req *discovery.Discover
resources := req.GetResourceNames()

// check to make sure we're actually authorized to do this
allowed, err := r.registry.CanFetchSecrets(ctx, GatewayFromContext(ctx), resources)
gateway, err := r.store.GetGateway(ctx, GatewayFromContext(ctx))
Copy link
Contributor

Choose a reason for hiding this comment

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

💯

// Bind will attempt to bind the provided route to all listeners on the Gateway and
// remove the route from any listeners that the route should no longer be bound to.
// The latter is important for scenarios such as the route's parent changing.
func (b *binder) Bind(ctx context.Context, route *K8sRoute) []string {
Copy link
Contributor

Choose a reason for hiding this comment

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

I like this simplification 👍

ShouldBind(route Route) bool
Bind(ctx context.Context, route Route) []string
Remove(ctx context.Context, id string) error
Resolve() core.ResolvedGateway
Copy link
Contributor

Choose a reason for hiding this comment

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

This is great that we're getting a lot closer

internal/store/interfaces.go Show resolved Hide resolved
OnGatewayRemoved(gateway Gateway)
}

// Route should be implemented by all route
// source integrations
type Route interface {
ID() string
Resolve(listener Listener) core.ResolvedRoute
Copy link
Contributor

Choose a reason for hiding this comment

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

💯

@@ -1,153 +0,0 @@
package memory
Copy link
Contributor

Choose a reason for hiding this comment

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

🎤 💧

Copy link
Member Author

Choose a reason for hiding this comment

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

🤣 🤣 🤣

@nathancoleman nathancoleman merged commit 8f90401 into main Sep 1, 2022
@nathancoleman nathancoleman deleted the binder branch September 1, 2022 21:07
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
pr/no-changelog Skip the CI check that requires a changelog entry refactor
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants