-
Notifications
You must be signed in to change notification settings - Fork 220
Dependent resources standalone mode #914
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
...ain/java/io/javaoperatorsdk/operator/api/reconciler/dependent/AbstractDependentResource.java
Show resolved
Hide resolved
@Override | ||
public void reconcile(P primary, Context context) { | ||
var actual = getResource(primary); | ||
var desired = desired(primary, context); |
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 should avoid creating the desired version if it's not needed because it could be costly.
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.
As far I can see it is always used on both branches.
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.
In this implementation, yes, that wasn't the case before :)
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.
Would still like to see this addressed if possible… In a subsequent PR, though.
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 can take a look together, not sure how you mean it.
...ain/java/io/javaoperatorsdk/operator/api/reconciler/dependent/AbstractDependentResource.java
Outdated
Show resolved
Hide resolved
.../io/javaoperatorsdk/operator/processing/dependent/KubernetesDependentResourceController.java
Show resolved
Hide resolved
.../io/javaoperatorsdk/operator/processing/dependent/KubernetesDependentResourceController.java
Outdated
Show resolved
Hide resolved
@Override | ||
public void createOrReplace(R dependentResource, Context context) { | ||
client.resource(dependentResource).createOrReplace(); | ||
public Optional<EventSource> eventSource(EventSourceContext<P> context) { |
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.
Why are we returning an optional? In what situation(s) would one not want to create an event source?
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.
For example, when the controller is just periodically checking some external resource(s). I think we should have this possibility to have it without event source, although most of the cases it will have.
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 you would still need an event source in that case, though… so I guess I don't understand how that's supposed to work.
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.
No, in that case, just the client would directly go to target API on reconcile.
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.
Still not sure how that would work…
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.
MySQL by the way work snow almost like this, the resource is not read from the informer but directly from server.
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.
So basically imagine that there will be just an operator that reconciles every 5 mins, all the resources. All external, so no caches locally. This can pretty much work without an event source.
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 but that's not the most common case and in the mean time, the most common cases is polluted by the Optional
handling… Let's see if we can address this in a subsequent PR.
A common use case is to provide an external identifier of the target, being a name/namespace for k8s. How will it behave if the spec change the name. Will it undo desired on the old one ans apply desired to new one ?
|
The default Could you maybe create a separate issue for your case, I'm not sure if understand, and if such naming is that common. Cannot be solved with labels more elegantly? |
I'm not sure I understand the use case here… Could you please detail it some more? |
operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Constants.java
Outdated
Show resolved
Hide resolved
operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtils.java
Show resolved
Hide resolved
operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtils.java
Outdated
Show resolved
Hide resolved
...o/javaoperatorsdk/operator/sample/standalonedependent/StandaloneDependentTestReconciler.java
Outdated
Show resolved
Hide resolved
...ors/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/MySQLSchemaReconciler.java
Outdated
Show resolved
Hide resolved
@csviri @metacosm You can look at https://github.com/redhat-developer/service-binding-operator and find some example here https://redhat-developer.github.io/service-binding-operator/userguide/creating-service-bindings/creating-service-binding.html
Basically, you're telling the operator to update the Deployment named This use case happens a lot with operator that patch other resources. You must tell the target to patch. |
In this case the old target is referenced in the status? Or we are just aware of the new spec? |
I would argue that this use case is not covered by the dependent resource support as it is as the indirection level makes it operator-specific to reason about what needs to be done in that case. I'm not sure how the Service Binding operator does when you modify the parameters of the initial binding… Note that having easier dependent resource support doesn't mean that you cannot write operators without it anymore! 😉 |
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've made some changes and there are some issues that need to be resolved but let's get this merged and address them in subsequent PRs. I've added more entries to #923 to reflect this fact.
This could be covered by a custom DependentResource implementation, where the |
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.
Sorry to be late to review, I had a pretty overload week at work and not much spare time.
|
||
@Override | ||
public void reconcile(P primary, Context context) { | ||
var actual = getResource(primary); |
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.
with Reconciler<T>
, I guess the practice is to get the actual frrom context::getSecondaryResource
. Shouldn't getResource have access to context ?
I like the way the caller of prepareEventSource
actually take ownership and we don't need to keep a reference to it. It make no doubt on who should control its lifecycle (start/stop).
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, it's other way around, to for now the event sources are caching the resource ( this is an interesting thing we might want to re-evaluate this later, to separate these two ). But basically at the end the dependent resource should manage it itself, either having an event source (or directly calling the target API). So IMHO it should not have the context in getResource() basically forcing this pattern. (See KubernetesDependentResource
)
|
||
@Override | ||
public void reconcile(P primary, Context context) { | ||
var actual = getResource(primary); |
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 would suggest to rename getResource
to be more after the version of the resource it should get like actual
/desired
variant.
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.
On level above on DependentResource we don't have desired, so it's really about reading the resource and the desired here is just a way to generically implement it. Hmm will think about it, so yes, it's always actual, not sure if needs to be that explicit.
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.
@metacosm what do you think?
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.
actual
could make sense if we had desired
at the same level, indeed. Maybe something that implies that the method is responsible for retrieving the resource more strongly? The previous implementation used getFor
for this purpose but it's not really better. How about fetchFrom(primary)
which would convey that the responsibility is to fetch the secondary based on the information provided by the primary?
|
||
@Override | ||
protected R desired(P primary, Context context) { | ||
if (desiredSupplier != null) { |
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.
throw in ctor ?
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.
Good point, removed!
this.client = client; | ||
} | ||
|
||
protected void postProcessDesired(R desired, P primary) { |
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 doesn't mean anything to me about when it will be called. what about beforeCreateOrReplace
.
Should it be two different methods ? addOwnerReference works because it is idemPotent.
Is it really needed in update case ? Spec is already the desired state, so, IMHO, it would mean that we want to update metadata but I don't see a reason for doing this on update.
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's really about filling some common data for the desired object created. But yes that maybe is better will change it. thx!
It's needed for now, there is a plan to (discuss first) to preserve labels and annotations and mainly finalizers (will actually do soon) because otherwise the update would remove this data from the resource.
Eventually this might improve with the followup compare issue. Ideally we would do server apply just fabric8 client does not support it yet.
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, I'm not too happy with that part either as this feels kind of awkward since to me this should be handled by the controller rather (i.e. this shouldn't be something that implementers of dependent resources should worry about). The controller should take these automated steps based on the dependent configuration (in this case, whether the dependent is supposed to be owned or not) and deal with owner references automatically.
} | ||
|
||
protected PrimaryResourcesRetriever<R> getDefaultPrimaryResourcesRetriever() { | ||
return Mappers.fromOwnerReference(); |
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.
What happens if owned
is false ? If true, is there any other recommended way to get the primary ? I think that ownership shouldn't be exposed as owned
and explicitDelete
. Either :
- the k8s dependent resource is owned and the primary R retriever shall be fromOwnerReference and delete is implicit (k8s garbage collector). Explicit/Implicit is left to the cluster admin with the Foreground/Background cascading deletion https://kubernetes.io/docs/concepts/architecture/garbage-collection/#cascading-deletion.
- or it is not owned, and a primary R retriever must be provided and delete is de facto explicit (foreground) but can be skip to be like --cascade=orphan. IMHO, this is not a best practice.
As there is two distinct behaviors, it can be exposed through two different classes ? It will be less prone to misconfiguration. But I see there is already a discussion around hierarchy vs configuration.
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.
Not sure if this two behaviors should be two classes, the current aim but we will refine this (just FYI, this is the first step), see issues (also pls comment in case ) for MVP: #923
Owner reference works just in same namespace, if some dependents are managed outside of namespace those needs to be explicitly created. Maybe it would make sense to subclass it yes. Definetely will make the config more explicit, this is quite vague this way, I agree.
} else { | ||
if (!match(actual.get(), desired, context)) { | ||
update(actual.get(), desired, primary, context); | ||
} |
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 match doesn't assert that it has been reconciled. The secondary R may exist prior to the primary one. We should be able to put in the context what has been observed to be desired, so that the reconciler can update the status if it is empty.
In the case of mysql schema operator, if the database exists when the primary is created on the cluster, the status is never updated. But, it tricky here because the user is not symetric due to password.
if we split, schema and user in two different dependent. Thus, User::match cannot be sure actual = desired because the password may not be the desired one. So if the user exist prior to the CR, either it should fail the reconcile or the password should be changed and secret updated/recreated.
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 match doesn't assert that it has been reconciled. The secondary R may exist prior to the primary one. We should be able to put in the context what has been observed to be desired, so that the reconciler can update the status if it is empty.
Not sure I'm following this. We read the R, and compare it to a target state, this is how P gets reconciled (at least this is a part of it).
In MySQL there is no update now available for the password. But the whole mysql we will revisit shortly.
import io.fabric8.kubernetes.api.model.HasMetadata; | ||
import io.javaoperatorsdk.operator.api.reconciler.Context; | ||
|
||
public abstract class AbstractDependentResource<R, P extends HasMetadata> |
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 it stays as a Factory (i.e. not exist = create) it should be renamed. And sharing version could be added (i.e. not exist = update status with missing target condition). The sharing version is another beast because, delete is about undoing what reconcile does not deleting. Again thinks of https://github.com/redhat-developer/service-binding-operator. When the CR is removed, the deployment is updated to suppress any env var that has been injected.
I don't if those two behaviors can be merge in one class
…operator-sdk/java-operator-sdk into dependent-resources-standalon-mode
SonarCloud Quality Gate failed. |
Idea for standalon mode, it's a skeleton only, base for discussions.