-
Notifications
You must be signed in to change notification settings - Fork 218
Introduce ConfiguredController
concept, decouple Metrics code from business logic
#517
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
Conversation
56fcc0a
to
ccfd6c7
Compare
operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java
Outdated
Show resolved
Hide resolved
ConfiguredController
conceptConfiguredController
concept, decouple Metrics code from business logic
operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java
Outdated
Show resolved
Hide resolved
operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java
Outdated
Show resolved
Hide resolved
import io.javaoperatorsdk.operator.processing.event.DefaultEventSourceManager; | ||
import io.javaoperatorsdk.operator.processing.event.EventSourceManager; | ||
|
||
public class ConfiguredController<R extends CustomResource<?, ?>> implements ResourceController<R>, |
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 don't think this should implement ResourceController, couldn't we just get the controller when it's needed instead of delegating and implementing this interface. I would rather see this class which just couples these objects. Implementing this interface does not feels right, it gives this class a mixed responsibility.
In other words this principle loosly applies (in regard of interfaces):
https://en.wikipedia.org/wiki/Composition_over_inheritance
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're using composition here and to me, it makes sense to have that class implement ResourceController
. We're not inheriting anything, just stating that this class behaves for all intents and purposes like a ResourceController
. What are the disadvantages of having the class extend the interface? Implementing the interface allows to pass the object around anywhere a ResourceController
is used currently which simplifies the current code, imo. The whole idea of this class is that it is a ResourceController
that also manages its configuration (instead of asking a ConfigurationService
for it).
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 is probably more a philosphical question, so also a point of view how we are looking at this class. So if we take a look on it as a "proxy" class then yes you are right there is no harm to implement it. If we take a look on this as a wrapper that just collects some related classes then it is a smell.
So it's all about how we express notions in OOP. But actually you are probably right that we can look on this as a "proxy" class.
this.controllers = new ArrayList<>(); | ||
this.started = false; | ||
this.metrics = metrics; | ||
DefaultEventHandler.setEventMonitor(new EventMonitor() { |
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 think this would be better to have these metrics per controller. (Something to think about in the future)
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.
Might make sense, indeed, though we can side-step the issue by adding the controller's name to the counter tags so that we can only have one handler and still be able to filter per controller. Having a different monitor per controller would only make sense if we wanted to track different metrics per controller (which we indeed might).
registerEventSource( | ||
CUSTOM_RESOURCE_EVENT_SOURCE_NAME, new CustomResourceEventSource<>(client, configuration)); | ||
public DefaultEventSourceManager(ConfiguredController<R> controller) { | ||
this(new DefaultEventHandler<>(controller), 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.
This is just a remark for the future, maybe to discuss separately. It's little bit messy how we wire up and/or instantiate these object DefaultEventHandler here and the DefaultEventSourceManager in the Configured controller. Those should be probable more explicit somewhere, making the code more readable, since these two classes are quite fundamental. Now it's quite hard to trace back, what is instantiated where.
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.
Yes, this still needs to be cleaned up but this PR is already doing too much as it stands so that work should be done in subsequent PRs.
Made some comments, but in general I like the idea of this ConfiguredController, so we break down the large chunk of code from Operator class to separate parts. These classes in it are feels cohesive. |
@@ -62,63 +64,61 @@ public ConfigurationService getConfigurationService() { | |||
*/ | |||
@SuppressWarnings("unchecked") | |||
public void start() { | |||
synchronized (lock) { |
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.
don't we need to keep this lock ? if sop is invoked on a separate thread it may not perform any cleanup but controller may be running
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.
Do you have examples / use cases where close
might be invoked asynchronously?
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.
🤔 Actually, thinking about it some more, if a shutdown hook is installed, then, indeed, close
will be called from a different thread so we do need to re-instate synchronization.
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.
But that would be still just 1 thread. Is there still a situation where can be a race condition? By definition this should not be called by multiple threads.
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.
it depends who invokes the shutdown process, i.e. in spring & quarkus this is done by the underlying runtime ad we don't know if this happen in the same thread or not.
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.
If the operator app is stopped during the start process then it's possible that there would two different threads accessing the started
and controllers
fields at the same time.
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.
That is true.
public abstract class AbstractControllerConfiguration<R extends CustomResource> | ||
import io.fabric8.kubernetes.client.CustomResource; | ||
|
||
public abstract class AbstractControllerConfiguration<R extends CustomResource<?, ?>> |
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.
wonder if this need to be an abstract class or just have some more params to set the crd class name and the configuration service
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… the reason why it's abstract is that it was needed like that at some point for the Quarkus extension. Maybe it doesn't need to be abstract anymore. Let me check.
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.
It appears it's not needed anymore. I have local changes to this PR to account for changes made for #499 that I intend to merge first. I will make the changes to this PR after the other one is merged.
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.
🤔 Cleaning this up properly, though, would require a breaking API change because, obviously, the class shouldn't be still named AbstractControllerConfiguration
if it's not abstract anymore. Then again #499 is also breaking the API so… 🤷🏼
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 fixed the backwards compatibility issue but name has to stay as-is for now.
On a more general note, the integration tests seem to be particularly flaky with this PR so there may something more to it than just flakiness. 😢 |
Introduced ConfiguredController to gather in one entity ResourceController, its associated configuration and Kubernetes client so that they can be passed around more easily in an always-associated manner (as, right now, configuration can be disassociated from the controller). Another aspect is that it allows us to simplify the management of the lifecycle as ConfiguredControllers can be started/stopped and the Operator doesn't need to bother about the details.
A global EventMonitor is set when the Operator is created based on the Metrics instance provided by the ConfigurationService.
This is meant to avoid diamond inheritance issues where we could have conflicting version of the method if a class implements several interfaces with a close method.
Introduce ControllerExecution interface to encapsulate a controller call that can be timed. Move logic of call to ConfiguredController where it's more logical so that Metrics class doesn't have any business logic anymore.
Note that this shouldn't happen during normal execution but can happen during tests.
Obviously, it should be renamed to something else since it's not abstract anymore but doing so would break backwards compatibility so best left for some later time.
bf2af63
to
d3920bc
Compare
@csviri can we merge this? |
Yes, LGTM |
...tor-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/EventDispatcher.java
Outdated
Show resolved
Hide resolved
096c41b
to
8b3e22c
Compare
It seems like using replaceStatus is causing issues with integration tests. patchStatus would work but this will be discussed in a subsequent issue / PR.
8b3e22c
to
fcef377
Compare
The idea is to expand on the
ControllerRef
class to associate a controller with its configuration and propagate this entity instead of passing the controller, the configuration and very often the kube client to each object that needs that information.See commit message on 1c47ecf for more details.
I've also taken the opportunity to clean up things from the Metrics class so that it's better decoupled from the business logic.
Fixes #496.