Skip to content

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

Merged
merged 23 commits into from
Aug 31, 2021

Conversation

metacosm
Copy link
Collaborator

@metacosm metacosm commented Aug 26, 2021

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.

@metacosm metacosm changed the title Introduce ConfiguredController concept Introduce ConfiguredController concept, decouple Metrics code from business logic Aug 27, 2021
@metacosm metacosm marked this pull request as ready for review August 27, 2021 12:06
import io.javaoperatorsdk.operator.processing.event.DefaultEventSourceManager;
import io.javaoperatorsdk.operator.processing.event.EventSourceManager;

public class ConfiguredController<R extends CustomResource<?, ?>> implements ResourceController<R>,
Copy link
Collaborator

@csviri csviri Aug 29, 2021

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

Copy link
Collaborator Author

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).

Copy link
Collaborator

@csviri csviri Aug 30, 2021

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() {
Copy link
Collaborator

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)

Copy link
Collaborator Author

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);
Copy link
Collaborator

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.

Copy link
Collaborator Author

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.

@csviri
Copy link
Collaborator

csviri commented Aug 29, 2021

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) {
Copy link
Collaborator

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

Copy link
Collaborator Author

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?

Copy link
Collaborator Author

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.

Copy link
Contributor

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.

Copy link
Collaborator

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.

Copy link
Collaborator Author

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.

Copy link
Collaborator

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<?, ?>>
Copy link
Collaborator

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

Copy link
Collaborator Author

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.

Copy link
Collaborator Author

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.

Copy link
Collaborator Author

@metacosm metacosm Aug 31, 2021

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… 🤷🏼

Copy link
Collaborator Author

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.

@metacosm
Copy link
Collaborator Author

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.
@metacosm metacosm force-pushed the configured-controller branch from bf2af63 to d3920bc Compare August 31, 2021 09:56
@metacosm
Copy link
Collaborator Author

@csviri can we merge this?

@csviri
Copy link
Collaborator

csviri commented Aug 31, 2021

@csviri can we merge this?

Yes, LGTM

@metacosm metacosm force-pushed the configured-controller branch from 096c41b to 8b3e22c Compare August 31, 2021 15:42
It seems like using replaceStatus is causing issues with integration
tests. patchStatus would work but this will be discussed in a
subsequent issue / PR.
@metacosm metacosm force-pushed the configured-controller branch from 8b3e22c to fcef377 Compare August 31, 2021 15:43
@metacosm metacosm merged commit aff192e into master Aug 31, 2021
@metacosm metacosm deleted the configured-controller branch August 31, 2021 16:04
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.

Use ControllerRef internally instead of ResourceController
4 participants