-
Notifications
You must be signed in to change notification settings - Fork 193
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
Extend the event.Handler API to provide common state info to implementations #2803
Conversation
🤖 Created branch: z_pr2803/tpantelis/ext_event_hndlr_api |
3a3d814
to
172115c
Compare
3013f75
to
58fbddc
Compare
58fbddc
to
a23e4b7
Compare
pkg/event/controller/controller.go
Outdated
@@ -43,15 +44,36 @@ type specification struct { | |||
Namespace string | |||
} | |||
|
|||
type handlerStateType struct { |
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.
I found this odd on first read — Type
suggests this describes a type, but it’s really the anchor for an implementation of HandlerState
, so how about handlerStateImpl
?
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.
We could just name it handlerState
but it's nice to distinguish it from variables of the same name. I've seen the use of Type
in K8s code so I borrowed it (I've used it before as well) - it is actually describing the type. Impl
seems Java-ish but I can change it - I'm fine either way (or name it handlerState
).
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.
Right, I didn’t bother mentioning handlerState
because of the variable name conflict! I prefer Impl
personally, especially as more type parameters start appearing in various places. The naming in Kubernetes seems to be shifting too, at least in discussions I’ve been having recently people talk about Impl
, and it’s about 50/50 now.
if err := c.handlers.LocalEndpointCreated(endpoint); err != nil { | ||
return err //nolint:wrapcheck // Let the caller wrap it | ||
if endpoint.Spec.Hostname == c.hostname { | ||
c.handlerState.isOnGateway.Store(true) |
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.
Reads go through IsOnGateway()
, so it feels unbalanced to directly access the atomic here.
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.
isOnGateway
is private so it really doesn't matter either way. But to balance it we can either add a private function to wrap isOnGateway.Store
or not use IsOnGateway()
internally.
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.
I agree it’s not important, but a private function would be good (not exposing the atomic for reads — I’d rather keep this encapsulated so we can change the implementation easily).
type Handler interface { | ||
// Init is called once on startup to let the handler initialize any state it needs. | ||
Init() error | ||
|
||
// SetHandlerState is called once on startup after Init with the HandlerState that can be used to access global data from event callbacks. | ||
SetState(handlerCtx HandlerState) |
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.
Is it a context, or a state?
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.
Other than the amount of churn involved, is there a reason to have a separate SetState
method instead of adding the handler state to Init
?
Currently the handlers are set up when the registry is constructed, and then controller construction sets the shared handler state. But AFAICT nothing would stop a handler being added after that, and then it wouldn’t be given the shared state...
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.
Is it a context, or a state?
I went back and forth on that. I finally went with state b/c context is usually used per function and this state persists.
I thought about passing it via Init
but that is called before the controller is initialized and it's the controller that owns/implements the state. The registry and controller are completely decoupled. But we could have the registry construct and start the controller - we'd have to have a public method on the controller to get the HandlerState
but that seemed awkward, unless we have the controller called Init
. Also, each Handler
impl would have to explicitly store the state, presumably call SetState
on the HandlerBase
, but this seemed awkward to me. That's why I went with this approach.
But AFAICT nothing would stop a handler being added after that, and then it wouldn’t be given the shared state...
You're right but that would problematic b/c eventHandlers
in Registry
is not synchronized. The Registry
is designed to add all the handlers first before starting the controller. We could force this by passing all the handlers via NewRegistry
and removing AddHandlers
. I can do that in a followup.
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.
Right, thanks for the explanation, that makes sense. I like the idea of dropping AddHandlers
.
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.
I like the idea of dropping AddHandlers
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.
SetState(handlerCtx HandlerState) | |
SetState(handlerState HandlerState) |
if you respin this.
vtepIP, kp.State().IsOnGateway()) | ||
|
||
if kp.isGatewayNode { | ||
if kp.State().IsOnGateway() { |
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.
This suggests a small (pre-existing) race here, but it’s protected by the lock. However to make the correctness more obvious it would be better to store the current return value of IsOnGateway()
and use that both for logging and decision-marking (so it’s apparent that the branch taken matches the log message).
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.
sure, for readability.
a23e4b7
to
57cdf7e
Compare
The controller was using the syncMutex to protect access to the isGatewayNode field, although not really necessary since the callbacks emanate from the same syncer on the same thread. In addition, some handlers employ their own mutex across multiple callbacks, although unnecessary in most cases. However a hidden caveat with the handler API is that while the Endpoint-related events (ie LocalEndpointCreated, TransitionToGateway etc) are single-threaded wrt one another and don't need synchronization, the Mode-related events emanate from a separate syncer and thread. So if data is accessed across Endpoint and Node events then synchronization is needed. Rather then having each handler implementation be aware of this behavior, it is simpler and safer to have the event controller use the syncMutex to synchronization access to all the callbacks and remove the synchronization in the handlers. Signed-off-by: Tom Pantelis <[email protected]>
This will enable event handlers to query common global state from event callback methods. This will replace the state currently being maintained by many handlers. The state instance is set once for each handler instead of passed to each event callback method to enable the state to be accessed outside of the event callbacks. The HandlerBase implements SetState. Signed-off-by: Tom Pantelis <[email protected]>
Signed-off-by: Tom Pantelis <[email protected]>
...for reuse in other event handler tests. Signed-off-by: Tom Pantelis <[email protected]>
Also added unit tests. Signed-off-by: Tom Pantelis <[email protected]>
Signed-off-by: Tom Pantelis <[email protected]>
Signed-off-by: Tom Pantelis <[email protected]>
57cdf7e
to
adae9bd
Compare
Also added unit tests. Signed-off-by: Tom Pantelis <[email protected]>
Also added unit tests. Signed-off-by: Tom Pantelis <[email protected]>
This enables unit tests to mock the behavior. Signed-off-by: Tom Pantelis <[email protected]>
Define a function so that unit tests can provide a fake implementation. Also consolidate the NewHandler parameters into a HandlerConfig struct. Signed-off-by: Tom Pantelis <[email protected]>
Signed-off-by: Tom Pantelis <[email protected]>
...as AwaitRoutes checks for Dst IP. Also rename AwaitRoutes to AwaitDstRoutes. Signed-off-by: Tom Pantelis <[email protected]>
...instead of hard-coded strings. Signed-off-by: Tom Pantelis <[email protected]>
Signed-off-by: Tom Pantelis <[email protected]>
Signed-off-by: Tom Pantelis <[email protected]>
The mutex was kept to provide atomicity when updating the gateway and host network dataplane since these are also invoked via the route config syncer which runs on its own thread. The synchronization may actually not be necessary but it's better to be safe and the overhead in negligible. Signed-off-by: Tom Pantelis <[email protected]>
This ensures all handlers are registered before starting the controller. Signed-off-by: Tom Pantelis <[email protected]>
adae9bd
to
60437de
Compare
type Handler interface { | ||
// Init is called once on startup to let the handler initialize any state it needs. | ||
Init() error | ||
|
||
// SetHandlerState is called once on startup after Init with the HandlerState that can be used to access global data from event callbacks. | ||
SetState(handlerCtx HandlerState) |
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.
SetState(handlerCtx HandlerState) | |
SetState(handlerState HandlerState) |
if you respin this.
"github.com/submariner-io/admiral/pkg/slices" | ||
) | ||
|
||
type OVSDBClient struct { |
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.
Writing OVSDB test code, that takes us back!
🤖 Closed branches: [z_pr2803/tpantelis/ext_event_hndlr_api] |
See the individual for details. To summarize:
HandlerState
that contains global state common to all event handlers, specifically IsOnGateway andGetRemoteEndpoints
which is provided to the handlers via aSetState
method.HandlerState
rather than maintaining such state themselves.Fixes #2623