From c85603c764bbc1098b69e17419bac6828513d48c Mon Sep 17 00:00:00 2001 From: csviri Date: Tue, 22 Mar 2022 14:06:36 +0100 Subject: [PATCH 1/9] docs: dependent resources and other improvements --- docs/documentation/dependent-resources.md | 33 ++++++++++++++++++++++- docs/documentation/features.md | 12 ++++----- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/docs/documentation/dependent-resources.md b/docs/documentation/dependent-resources.md index 24b32ea0cd..7ba94eab3f 100644 --- a/docs/documentation/dependent-resources.md +++ b/docs/documentation/dependent-resources.md @@ -111,8 +111,39 @@ JOSDK will take the appropriate steps to wire everything together and call your This makes sense in most use cases where the logic associated with the primary resource is usually limited to status handling based on the state of the secondary resources. This behavior and automated handling is referred to as "managed" because the `DependentResource` -implementations are managed by JOSDK. +implementations are managed by JOSDK. See related sample: + +```java + +@ControllerConfiguration( + labelSelector = SELECTOR, + dependents = { + @Dependent(type = ConfigMapDependentResource.class), + @Dependent(type = DeploymentDependentResource.class), + @Dependent(type = ServiceDependentResource.class) + }) +public class WebPageManagedDependentsReconciler + implements Reconciler, ErrorStatusHandler { + + // emitted code + + @Override + public UpdateControl reconcile(WebPage webPage, Context context) + throws Exception { + simulateErrorIfRequested(webPage); + + final var name = context.getSecondaryResource(ConfigMap.class).orElseThrow() + .getMetadata().getName(); + webPage.setStatus(createStatus(name)); + return UpdateControl.updateStatus(webPage); + } + +} +``` + ## Standalone Dependent Resources + + ## Other Dependent Resources features \ No newline at end of file diff --git a/docs/documentation/features.md b/docs/documentation/features.md index 7b6143ca02..1b9c534f46 100644 --- a/docs/documentation/features.md +++ b/docs/documentation/features.md @@ -37,11 +37,11 @@ execution. [Kubernetes finalizers](https://kubernetes.io/docs/concepts/overview/working-with-objects/finalizers/) make sure that a reconciliation happens when a custom resource is instructed to be deleted. Typical case when it's useful, when an operator is down (pod not running). Without a finalizer the reconciliation - thus the cleanup - -i.e. [`Cleaner.cleanup(...)`](https://github.com/java-operator-sdk/java-operator-sdk/blob/b82c1f106968cb3eb18835c5e9cd1e4d5c40362e/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Cleaner.java#L28-L28) +i.e. [`Cleaner.cleanup(...)`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Cleaner.java#L28) would not happen if a custom resource is deleted. -To use finalizers the reconciler have to implement [`Cleaner

`](https://github.com/java-operator-sdk/java-operator-sdk/blob/b82c1f106968cb3eb18835c5e9cd1e4d5c40362e/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Cleaner.java) interface. -In other words, finalizer is added if the `Reconciler` implements `Cleaner` interface. If not, no +To use finalizers the reconciler have to implement [`Cleaner

`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Cleaner.java) interface. +In other words, finalizer is added only if the `Reconciler` implements `Cleaner` interface. If not, no finalizer is added and/or removed. Finalizers are automatically added by the framework as the first step, thus after a custom resource is created, but @@ -69,7 +69,7 @@ When automatic finalizer handling is turned off, the `Reconciler.cleanup(...)` m case when a delete event received. So it does not make sense to implement this method and turn off finalizer at the same time. -## The `reconcile` and [`cleanup`](https://github.com/java-operator-sdk/java-operator-sdk/blob/b82c1f106968cb3eb18835c5e9cd1e4d5c40362e/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Cleaner.java) Methods on a [`Reconciler`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Reconciler.java) +## The [`reconcile`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Reconciler.java#L16) and [`cleanup`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Cleaner.java#L28) The lifecycle of a custom resource can be clearly separated into two phases from the perspective of an operator. When a custom resource is created or update, or on the other hand when the custom resource is deleted - or rather marked for @@ -160,7 +160,7 @@ In order to have this feature working: If these conditions are fulfilled and generation awareness not turned off, the observed generation is automatically set by the framework after the `reconcile` method is called. Note that the observed generation is updated also when `UpdateControl.noUpdate()` is returned from the reconciler. See this feature working in -the [WebPage example](https://github.com/java-operator-sdk/java-operator-sdk/blob/b91221bb54af19761a617bf18eef381e8ceb3b4c/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStatus.java#L5) +the [WebPage example](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStatus.java#L5) . ```java @@ -361,7 +361,7 @@ of requests to Kubernetes API server, and leads to faster reconciliation cycles. Note that when an operator starts and the first reconciliation is executed the caches are already populated for example for `InformerEventSource`. Currently, this is not true however for `PerResourceEventSource`, where the cache might or might not be populated. To handle this situation elegantly methods are provided which checks the object in cache, if -not found tries to get it from the supplier. See related [method](https://github.com/java-operator-sdk/java-operator-sdk/blob/e7fd79968a238d7e0acc446d949b83a06cea17b5/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/polling/PerResourcePollingEventSource.java#L145) +not found tries to get it from the supplier. See related [method](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/polling/PerResourcePollingEventSource.java#L146) . ### Registering Event Sources From b3f1115e3ac7ba290b9f8bf5afb81c81de7e0da9 Mon Sep 17 00:00:00 2001 From: csviri Date: Tue, 22 Mar 2022 15:01:44 +0100 Subject: [PATCH 2/9] wip --- docs/documentation/dependent-resources.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/documentation/dependent-resources.md b/docs/documentation/dependent-resources.md index 7ba94eab3f..b2229bdb80 100644 --- a/docs/documentation/dependent-resources.md +++ b/docs/documentation/dependent-resources.md @@ -7,10 +7,10 @@ permalink: /docs/dependent-resources # Dependent Resources -DISCLAIMER: The Dependent Resource support is relatively new and, while we strove to cover what we +DISCLAIMER: The Dependent Resource support is relatively new feature, while we strove to cover what we anticipate will be the most common use cases, the implementation is not simple and might still -evolve. As a result, some APIs could be still a subject of change in the future. However, -non-backwards compatible changes are expected to be trivial to adapt to. +evolve. As a result, some APIs could be a subject of change in the future. However, +non-backwards compatible changes are expected to be trivial to migrate to. ## Motivations and Goals @@ -32,7 +32,7 @@ A{Secondary resource exists?} A -- Yes --> match A -- No --> Create --> Done -match{Matches desired state as defined by primary?} +match{Matches desired state?} match -- Yes --> Done match -- No --> Update --> Done @@ -111,7 +111,7 @@ JOSDK will take the appropriate steps to wire everything together and call your This makes sense in most use cases where the logic associated with the primary resource is usually limited to status handling based on the state of the secondary resources. This behavior and automated handling is referred to as "managed" because the `DependentResource` -implementations are managed by JOSDK. See related sample: +implementations are managed by JOSDK. See [related sample](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageManagedDependentsReconciler.java): ```java @@ -141,9 +141,13 @@ public class WebPageManagedDependentsReconciler } ``` - ## Standalone Dependent Resources +To use dependent resources in more complex workflows, when the reconciliation requires additional logic, the standalone +mode is available. In practice this means that the developer is responsible to initializing and managing and +calling reconcile method. + + ## Other Dependent Resources features \ No newline at end of file From dab03feb01a5d577b76f9cfcaaddf73a4fd8cf3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Wed, 23 Mar 2022 12:56:20 +0100 Subject: [PATCH 3/9] Update docs/documentation/dependent-resources.md Co-authored-by: Chris Laprun --- docs/documentation/dependent-resources.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/documentation/dependent-resources.md b/docs/documentation/dependent-resources.md index b2229bdb80..2c3cc01920 100644 --- a/docs/documentation/dependent-resources.md +++ b/docs/documentation/dependent-resources.md @@ -125,7 +125,7 @@ implementations are managed by JOSDK. See [related sample](https://github.com/ja public class WebPageManagedDependentsReconciler implements Reconciler, ErrorStatusHandler { - // emitted code + // omitted code @Override public UpdateControl reconcile(WebPage webPage, Context context) From caf28159f4e117041175f27fa18d71a6598d90c3 Mon Sep 17 00:00:00 2001 From: csviri Date: Thu, 24 Mar 2022 13:40:00 +0100 Subject: [PATCH 4/9] docs: standalone dependent resource --- docs/documentation/dependent-resources.md | 77 ++++++++++++++++++++++- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/docs/documentation/dependent-resources.md b/docs/documentation/dependent-resources.md index 2c3cc01920..4a9da1a768 100644 --- a/docs/documentation/dependent-resources.md +++ b/docs/documentation/dependent-resources.md @@ -130,8 +130,7 @@ public class WebPageManagedDependentsReconciler @Override public UpdateControl reconcile(WebPage webPage, Context context) throws Exception { - simulateErrorIfRequested(webPage); - + final var name = context.getSecondaryResource(ConfigMap.class).orElseThrow() .getMetadata().getName(); webPage.setStatus(createStatus(name)); @@ -141,13 +140,85 @@ public class WebPageManagedDependentsReconciler } ``` +See the full source code of sample [here](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageManagedDependentsReconciler.java). + ## Standalone Dependent Resources To use dependent resources in more complex workflows, when the reconciliation requires additional logic, the standalone mode is available. In practice this means that the developer is responsible to initializing and managing and -calling reconcile method. +calling `reconcile` method. However, this gives possibility for developers to fully customize the workflow for +reconciliation. Like setting conditions (if creation of a resource is desired only in certain situations). Also, for +example if calling an API needs to happen if a service is already up and running +(think configuring a running DB instance). +The following sample is equivalent to the one above with managed dependent resources: + +```java +@ControllerConfiguration +public class WebPageStandaloneDependentsReconciler + implements Reconciler, ErrorStatusHandler, EventSourceInitializer { + + private KubernetesDependentResource configMapDR; + private KubernetesDependentResource deploymentDR; + private KubernetesDependentResource serviceDR; + + public WebPageStandaloneDependentsReconciler(KubernetesClient kubernetesClient) { + // 1. + createDependentResources(kubernetesClient); + } + + @Override + public List prepareEventSources(EventSourceContext context) { + // 2. + return List.of( + configMapDR.initEventSource(context), + deploymentDR.initEventSource(context), + serviceDR.initEventSource(context)); + } + + @Override + public UpdateControl reconcile(WebPage webPage, Context context) + throws Exception { + + // 3. + configMapDR.reconcile(webPage, context); + deploymentDR.reconcile(webPage, context); + serviceDR.reconcile(webPage, context); + + // 4. + webPage.setStatus( + createStatus(configMapDR.getResource(webPage).orElseThrow().getMetadata().getName())); + return UpdateControl.updateStatus(webPage); + } + + private void createDependentResources(KubernetesClient client) { + this.configMapDR = new ConfigMapDependentResource(); + this.configMapDR.setKubernetesClient(client); + configMapDR.configureWith(new KubernetesDependentResourceConfig() + .setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR)); + + this.deploymentDR = new DeploymentDependentResource(); + deploymentDR.setKubernetesClient(client); + deploymentDR.configureWith(new KubernetesDependentResourceConfig() + .setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR)); + + this.serviceDR = new ServiceDependentResource(); + serviceDR.setKubernetesClient(client); + serviceDR.configureWith(new KubernetesDependentResourceConfig() + .setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR)); + } + + // omitted code +} +``` +There are multiple things happening here: +1. Dependent resources are explicitly created, and can be access later by reference. +2. Event sources are produced by the dependent resources, but needs to be explicitly registered in this case. +3. Reconciliation is called explicitly, but here the workflow customization is fully in the hand of the developer. +4. Status is set in a different way, this is just an alternative way to show, that the actual state can be read + using the reference. This could be written in a same way as in the managed example. +See the full source code of sample [here](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStandaloneDependentsReconciler.java). ## Other Dependent Resources features \ No newline at end of file From 481a48c4c193811fa73c28fdd2457f3fdcd52e19 Mon Sep 17 00:00:00 2001 From: csviri Date: Thu, 24 Mar 2022 14:33:02 +0100 Subject: [PATCH 5/9] docs: additional features, reformat --- docs/documentation/dependent-resources.md | 298 +++++++++--------- .../dependent/DependentResource.java | 3 +- 2 files changed, 158 insertions(+), 143 deletions(-) diff --git a/docs/documentation/dependent-resources.md b/docs/documentation/dependent-resources.md index 4a9da1a768..cfb9dac262 100644 --- a/docs/documentation/dependent-resources.md +++ b/docs/documentation/dependent-resources.md @@ -7,21 +7,19 @@ permalink: /docs/dependent-resources # Dependent Resources -DISCLAIMER: The Dependent Resource support is relatively new feature, while we strove to cover what we -anticipate will be the most common use cases, the implementation is not simple and might still -evolve. As a result, some APIs could be a subject of change in the future. However, -non-backwards compatible changes are expected to be trivial to migrate to. +DISCLAIMER: The Dependent Resource support is relatively new feature, while we strove to cover what we anticipate will +be the most common use cases, the implementation is not simple and might still evolve. As a result, some APIs could be a +subject of change in the future. However, non-backwards compatible changes are expected to be trivial to migrate to. ## Motivations and Goals -Most operators need to deal with secondary resources when trying to realize the desired state -described by the primary resource it is in charge of. For example, the Kubernetes-native -`Deployment` controller needs to manage `ReplicaSet` instances as part of a `Deployment`'s -reconciliation process. In this instance, `ReplicatSet` is considered a secondary resource for -the `Deployment` controller. +Most operators need to deal with secondary resources when trying to realize the desired state described by the primary +resource it is in charge of. For example, the Kubernetes-native +`Deployment` controller needs to manage `ReplicaSet` instances as part of a `Deployment`'s reconciliation process. In +this instance, `ReplicatSet` is considered a secondary resource for the `Deployment` controller. -Controllers that deal with secondary resources typically need to perform the following steps, for -each secondary resource: +Controllers that deal with secondary resources typically need to perform the following steps, for each secondary +resource:

@@ -38,187 +36,203 @@ match -- No --> Update --> Done
-While these steps are not difficult in and of themselves, there are some subtleties that can lead to -bugs or sub-optimal code if not done right. As this process is pretty much similar for each -dependent resource, it makes sense for the SDK to offer some level of support to remove the -boilerplate code of these repetitive actions. It should be possible to handle common cases (such as -dealing with Kubernetes-native secondary resources) in a semi-declarative way with only a minimal -amount of code, JOSDK taking care of wiring everything accordingly. +While these steps are not difficult in and of themselves, there are some subtleties that can lead to bugs or sub-optimal +code if not done right. As this process is pretty much similar for each dependent resource, it makes sense for the SDK +to offer some level of support to remove the boilerplate code of these repetitive actions. It should be possible to +handle common cases (such as dealing with Kubernetes-native secondary resources) in a semi-declarative way with only a +minimal amount of code, JOSDK taking care of wiring everything accordingly. -Moreover, in order for your reconciler to get informed of events on these secondary resources, you -need to configure and create event sources and maintain them. JOSDK already makes it rather easy to -deal with these, but dependent resources makes it even simpler. +Moreover, in order for your reconciler to get informed of events on these secondary resources, you need to configure and +create event sources and maintain them. JOSDK already makes it rather easy to deal with these, but dependent resources +makes it even simpler. -Finally, there are also opportunities for the SDK to transparently add features that are even -trickier to get right, such as immediate caching of updated or created resources (so that your -reconciler doesn't need to wait for a cluster roundtrip to continue its work) and associated event -filtering (so that something your reconciler just changed doesn't re-trigger a reconciliation, for -example). +Finally, there are also opportunities for the SDK to transparently add features that are even trickier to get right, +such as immediate caching of updated or created resources (so that your reconciler doesn't need to wait for a cluster +roundtrip to continue its work) and associated event filtering (so that something your reconciler just changed doesn't +re-trigger a reconciliation, for example). ## Design ### `DependentResource` vs. `AbstractDependentResource` -The new `DependentResource` interface lies at the core of the design and strives to encapsulate the -logic that is required to reconcile the state of the associated secondary resource based on the -state of the primary one. For most cases, this logic will follow the flow expressed above and JOSDK -provides a very convenient implementation of this logic in the form of the -`AbstractDependentResource` class. If your logic doesn't fit this pattern, though, you can still -provide your own `reconcile` method implementation. While the benefits of using dependent resources -are less obvious in that case, this allows you to separate the logic necessary to deal with each -secondary resource in its own class that can then be tested in isolation via unit tests. You can -also use the declarative support with your own implementations as we shall see later on. - -`AbstractDependentResource` is designed so that classes extending it specify which functionality -they support by implementing trait interfaces. This design has been selected to express the fact -that not all secondary resources are completely under the control of the primary reconciler: some -dependent resources are only ever created or updated for example and we needed a way to let JOSDK -know when that is the case. We therefore provide trait interfaces: `Creator`, -`Updater` and `Deleter` to express that the `DependentResource` implementation will provide custom -functionality to create, update and delete its associated secondary resources, respectively. If -these traits are not implemented then parts of the logic described above is never triggered: if your -implementation doesn't implement `Creator`, for example, -`AbstractDependentResource` will never try to create the associated secondary resource, even if it -doesn't exist. It is possible to not implement any of these traits and therefore create read-only dependent resources that will trigger your -reconciler whenever a user interacts with them but that are never modified by your reconciler -itself. - -### Batteries included: convenient `DependentResource` implementations! +The +new [`DependentResource`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java)interface +lies at the core of the design and strives to encapsulate the logic that is required to reconcile the state of the +associated secondary resource based on the state of the primary one. For most cases, this logic will follow the flow +expressed above and JOSDK provides a very convenient implementation of this logic in the form of the +[`AbstractDependentResource`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java) +class. If your logic doesn't fit this pattern, though, you can still provide your +own `reconcile` method implementation. While the benefits of using dependent resources are less obvious in that case, +this allows you to separate the logic necessary to deal with each secondary resource in its own class that can then be +tested in isolation via unit tests. You can also use the declarative support with your own implementations as we shall +see later on. + +`AbstractDependentResource` is designed so that classes extending it specify which functionality they support by +implementing trait interfaces. This design has been selected to express the fact that not all secondary resources are +completely under the control of the primary reconciler: some dependent resources are only ever created or updated for +example and we needed a way to let JOSDK know when that is the case. We therefore provide trait interfaces: `Creator`, +`Updater` and `Deleter` to express that the `DependentResource` implementation will provide custom functionality to +create, update and delete its associated secondary resources, respectively. If these traits are not implemented then +parts of the logic described above is never triggered: if your implementation doesn't implement `Creator`, for example, +`AbstractDependentResource` will never try to create the associated secondary resource, even if it doesn't exist. It is +possible to not implement any of these traits and therefore create read-only dependent resources that will trigger your +reconciler whenever a user interacts with them but that are never modified by your reconciler itself. + +### Batteries included: convenient DependentResource implementations! JOSDK also offers several other convenient implementations building on top of `AbstractDependentResource` that you can use as starting points for your own implementations. -One such implementation is the `KubernetesDependentResource` class that makes it really easy to work -with Kubernetes-native resources. In this case, you usually only need to provide an -implementation for the `desired` method to tell JOSDK what the desired state of your secondary -resource should be based on the specified primary resource state. JOSDK takes care of everything -else using default implementations that you can override in case you need more precise control of -what's going on. +One such implementation is the `KubernetesDependentResource` class that makes it really easy to work with +Kubernetes-native resources. In this case, you usually only need to provide an implementation for the `desired` method +to tell JOSDK what the desired state of your secondary resource should be based on the specified primary resource state. +JOSDK takes care of everything else using default implementations that you can override in case you need more precise +control of what's going on. -We also provide implementations that makes it very easy to cache +We also provide implementations that make it very easy to cache (`AbstractCachingDependentResource`) or make it easy to poll for changes in external -resources (`PollingDependentResource`, `PerResourcePollingDependentResource`). All the provided -implementations can be found in the `io/javaoperatorsdk/operator/processing/dependent` package of -the `operator-framework-core` module. +resources (`PollingDependentResource`, `PerResourcePollingDependentResource`). All the provided implementations can be +found in the `io/javaoperatorsdk/operator/processing/dependent` package of the `operator-framework-core` module. ## Managed Dependent Resources -As mentioned previously, one goal of this implementation is to make it possible to -semi-declaratively create and wire dependent resources. You can annotate your reconciler with -`@Dependent` annotations that specify which `DependentResource` implementation it depends upon. -JOSDK will take the appropriate steps to wire everything together and call your -`DependentResource` implementations `reconcile` method before your primary resource is reconciled. -This makes sense in most use cases where the logic associated with the primary resource is usually -limited to status handling based on the state of the secondary resources. This behavior and -automated handling is referred to as "managed" because the `DependentResource` -implementations are managed by JOSDK. See [related sample](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageManagedDependentsReconciler.java): +As mentioned previously, one goal of this implementation is to make it possible to semi-declaratively create and wire +dependent resources. You can annotate your reconciler with +`@Dependent` annotations that specify which `DependentResource` implementation it depends upon. JOSDK will take the +appropriate steps to wire everything together and call your +`DependentResource` implementations `reconcile` method before your primary resource is reconciled. This makes sense in +most use cases where the logic associated with the primary resource is usually limited to status handling based on the +state of the secondary resources. This behavior and automated handling is referred to as "managed" because +the `DependentResource` +implementations are managed by JOSDK. +See [related sample](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageManagedDependentsReconciler.java): ```java @ControllerConfiguration( - labelSelector = SELECTOR, - dependents = { - @Dependent(type = ConfigMapDependentResource.class), - @Dependent(type = DeploymentDependentResource.class), - @Dependent(type = ServiceDependentResource.class) - }) + labelSelector = SELECTOR, + dependents = { + @Dependent(type = ConfigMapDependentResource.class), + @Dependent(type = DeploymentDependentResource.class), + @Dependent(type = ServiceDependentResource.class) + }) public class WebPageManagedDependentsReconciler - implements Reconciler, ErrorStatusHandler { + implements Reconciler, ErrorStatusHandler { // omitted code - + @Override public UpdateControl reconcile(WebPage webPage, Context context) throws Exception { - + final var name = context.getSecondaryResource(ConfigMap.class).orElseThrow() .getMetadata().getName(); webPage.setStatus(createStatus(name)); return UpdateControl.updateStatus(webPage); } - + } ``` -See the full source code of sample [here](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageManagedDependentsReconciler.java). +See the full source code of +sample [here](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageManagedDependentsReconciler.java) +. ## Standalone Dependent Resources To use dependent resources in more complex workflows, when the reconciliation requires additional logic, the standalone -mode is available. In practice this means that the developer is responsible to initializing and managing and -calling `reconcile` method. However, this gives possibility for developers to fully customize the workflow for -reconciliation. Like setting conditions (if creation of a resource is desired only in certain situations). Also, for -example if calling an API needs to happen if a service is already up and running -(think configuring a running DB instance). +mode is available. In practice this means that the developer is responsible to initializing and managing and +calling `reconcile` method. However, this gives possibility for developers to fully customize the workflow for +reconciliation. Like setting conditions (if creation of a resource is desired only in certain situations). Also, for +example if calling an API needs to happen if a service is already up and running +(think configuring a running DB instance). The following sample is equivalent to the one above with managed dependent resources: ```java + @ControllerConfiguration public class WebPageStandaloneDependentsReconciler - implements Reconciler, ErrorStatusHandler, EventSourceInitializer { - - private KubernetesDependentResource configMapDR; - private KubernetesDependentResource deploymentDR; - private KubernetesDependentResource serviceDR; - - public WebPageStandaloneDependentsReconciler(KubernetesClient kubernetesClient) { - // 1. - createDependentResources(kubernetesClient); - } - - @Override - public List prepareEventSources(EventSourceContext context) { - // 2. - return List.of( - configMapDR.initEventSource(context), - deploymentDR.initEventSource(context), - serviceDR.initEventSource(context)); - } - - @Override - public UpdateControl reconcile(WebPage webPage, Context context) - throws Exception { - - // 3. - configMapDR.reconcile(webPage, context); - deploymentDR.reconcile(webPage, context); - serviceDR.reconcile(webPage, context); - - // 4. - webPage.setStatus( - createStatus(configMapDR.getResource(webPage).orElseThrow().getMetadata().getName())); - return UpdateControl.updateStatus(webPage); - } - - private void createDependentResources(KubernetesClient client) { - this.configMapDR = new ConfigMapDependentResource(); - this.configMapDR.setKubernetesClient(client); - configMapDR.configureWith(new KubernetesDependentResourceConfig() - .setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR)); - - this.deploymentDR = new DeploymentDependentResource(); - deploymentDR.setKubernetesClient(client); - deploymentDR.configureWith(new KubernetesDependentResourceConfig() - .setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR)); - - this.serviceDR = new ServiceDependentResource(); - serviceDR.setKubernetesClient(client); - serviceDR.configureWith(new KubernetesDependentResourceConfig() - .setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR)); - } - - // omitted code + implements Reconciler, ErrorStatusHandler, EventSourceInitializer { + + private KubernetesDependentResource configMapDR; + private KubernetesDependentResource deploymentDR; + private KubernetesDependentResource serviceDR; + + public WebPageStandaloneDependentsReconciler(KubernetesClient kubernetesClient) { + // 1. + createDependentResources(kubernetesClient); + } + + @Override + public List prepareEventSources(EventSourceContext context) { + // 2. + return List.of( + configMapDR.initEventSource(context), + deploymentDR.initEventSource(context), + serviceDR.initEventSource(context)); + } + + @Override + public UpdateControl reconcile(WebPage webPage, Context context) + throws Exception { + + // 3. + configMapDR.reconcile(webPage, context); + deploymentDR.reconcile(webPage, context); + serviceDR.reconcile(webPage, context); + + // 4. + webPage.setStatus( + createStatus(configMapDR.getResource(webPage).orElseThrow().getMetadata().getName())); + return UpdateControl.updateStatus(webPage); + } + + private void createDependentResources(KubernetesClient client) { + this.configMapDR = new ConfigMapDependentResource(); + this.configMapDR.setKubernetesClient(client); + configMapDR.configureWith(new KubernetesDependentResourceConfig() + .setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR)); + + this.deploymentDR = new DeploymentDependentResource(); + deploymentDR.setKubernetesClient(client); + deploymentDR.configureWith(new KubernetesDependentResourceConfig() + .setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR)); + + this.serviceDR = new ServiceDependentResource(); + serviceDR.setKubernetesClient(client); + serviceDR.configureWith(new KubernetesDependentResourceConfig() + .setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR)); + } + + // omitted code } ``` There are multiple things happening here: + 1. Dependent resources are explicitly created, and can be access later by reference. 2. Event sources are produced by the dependent resources, but needs to be explicitly registered in this case. 3. Reconciliation is called explicitly, but here the workflow customization is fully in the hand of the developer. -4. Status is set in a different way, this is just an alternative way to show, that the actual state can be read - using the reference. This could be written in a same way as in the managed example. +4. Status is set in a different way, this is just an alternative way to show, that the actual state can be read using + the reference. This could be written in a same way as in the managed example. + +See the full source code of +sample [here](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStandaloneDependentsReconciler.java) +. + +## Other Dependent Resources features + +### Caching and Event Handling in [KubernetesDependentResource](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java) -See the full source code of sample [here](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStandaloneDependentsReconciler.java). +1. When a Kubernetes resource is created or updated the related informer (more precisely the `InformerEventSource`), +eventually will receive an event and will cache the up-to-date resource. However, there might be a small time window +when calling the `getResource()` of the dependent resource or getting the resource from the `EventSource` itself won't +return the fresh resource, since it's not received from the Kubernetes API. `KubernetesDependentResource` implementation +makes sure that it or the related `InformerEventSource` always return the up-to-date resource. -## Other Dependent Resources features \ No newline at end of file +2. Another feature of `KubernetesDependentResource` is to make sure that is a resource is created or updated during +the reconciliation, the later received related event will not trigger the reconciliation again. This is a small +optimization. For example if during a reconciliation a `ConfigMap` is updated using dependent resources, this won't +trigger a new reconciliation. It' does not need to, since the change in the `ConfigMap` is made by the reconciler, +and the fresh version is used further. \ No newline at end of file diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java index 761e3d2a64..d961b28095 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java @@ -23,7 +23,8 @@ public interface DependentResource { ReconcileResult reconcile(P primary, Context

context); /** - * Retrieves the dependent resource associated with the specified primary one + * The intention with get resource is to return the actual state of the resource. Usually from a local cache, + * what was updated after the reconciliation, or typically from the event source that caches the resources. * * @param primaryResource the primary resource for which we want to retrieve the secondary * resource From 444da0675404def76458b2aad7e26ea60e66c265 Mon Sep 17 00:00:00 2001 From: csviri Date: Thu, 24 Mar 2022 14:51:16 +0100 Subject: [PATCH 6/9] fix: format --- .../operator/api/reconciler/dependent/DependentResource.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java index d961b28095..e562f6f244 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResource.java @@ -23,8 +23,9 @@ public interface DependentResource { ReconcileResult reconcile(P primary, Context

context); /** - * The intention with get resource is to return the actual state of the resource. Usually from a local cache, - * what was updated after the reconciliation, or typically from the event source that caches the resources. + * The intention with get resource is to return the actual state of the resource. Usually from a + * local cache, what was updated after the reconciliation, or typically from the event source that + * caches the resources. * * @param primaryResource the primary resource for which we want to retrieve the secondary * resource From 7d95cc972f22a4372e0153ca0a442245443cd44a Mon Sep 17 00:00:00 2001 From: csviri Date: Thu, 24 Mar 2022 15:07:02 +0100 Subject: [PATCH 7/9] docs: wip --- docs/documentation/dependent-resources.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/documentation/dependent-resources.md b/docs/documentation/dependent-resources.md index cfb9dac262..1e6fa4178d 100644 --- a/docs/documentation/dependent-resources.md +++ b/docs/documentation/dependent-resources.md @@ -221,7 +221,7 @@ See the full source code of sample [here](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStandaloneDependentsReconciler.java) . -## Other Dependent Resources features +## Other Dependent Resource features ### Caching and Event Handling in [KubernetesDependentResource](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java) From a9aeb031a07517b9f9efa6382dd5e2143e0fd04c Mon Sep 17 00:00:00 2001 From: csviri Date: Thu, 24 Mar 2022 15:25:17 +0100 Subject: [PATCH 8/9] docs: added sample dependent implementation --- docs/documentation/dependent-resources.md | 40 ++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/docs/documentation/dependent-resources.md b/docs/documentation/dependent-resources.md index 1e6fa4178d..56a9bdbc1c 100644 --- a/docs/documentation/dependent-resources.md +++ b/docs/documentation/dependent-resources.md @@ -94,6 +94,44 @@ We also provide implementations that make it very easy to cache resources (`PollingDependentResource`, `PerResourcePollingDependentResource`). All the provided implementations can be found in the `io/javaoperatorsdk/operator/processing/dependent` package of the `operator-framework-core` module. +### Sample Kubernetes Dependent Resource + +A typical use case, when a Kubernetes resource is fully managed - Created, Read, Updated and Deleted (or set to be garbage +collected). The following example shows how to create a `Deployment` dependent resource: + +```java +@KubernetesDependent(labelSelector = WebPageManagedDependentsReconciler.SELECTOR) +class DeploymentDependentResource extends CRUKubernetesDependentResource { + + public DeploymentDependentResource() { + super(Deployment.class); + } + + @Override + protected Deployment desired(WebPage webPage, Context context) { + var deploymentName = deploymentName(webPage); + Deployment deployment = loadYaml(Deployment.class, getClass(), "deployment.yaml"); + deployment.getMetadata().setName(deploymentName); + deployment.getMetadata().setNamespace(webPage.getMetadata().getNamespace()); + deployment.getSpec().getSelector().getMatchLabels().put("app", deploymentName); + + deployment.getSpec().getTemplate().getMetadata().getLabels() + .put("app", deploymentName); + deployment.getSpec().getTemplate().getSpec().getVolumes().get(0) + .setConfigMap(new ConfigMapVolumeSourceBuilder().withName(configMapName(webPage)).build()); + return deployment; + } +} +``` + +The only thing needs to be done is to extend the `CRUKubernetesDependentResource` and specify the desired state. +Note that it is `CRU` instead of `CRUD`, since it not explicitly manages the delete operation. That is handled by +the Kubernetes garbage collector through owner references, what is automatically set to the resource. +`CRUKubernetesDependentResource` is only an adaptor class that already implements the`Creator` and `Updater` but +not the `Deleter` interface. + +See the full source code [here](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java). + ## Managed Dependent Resources As mentioned previously, one goal of this implementation is to make it possible to semi-declaratively create and wire @@ -211,7 +249,7 @@ public class WebPageStandaloneDependentsReconciler There are multiple things happening here: -1. Dependent resources are explicitly created, and can be access later by reference. +1. Dependent resources are explicitly created and can be access later by reference. 2. Event sources are produced by the dependent resources, but needs to be explicitly registered in this case. 3. Reconciliation is called explicitly, but here the workflow customization is fully in the hand of the developer. 4. Status is set in a different way, this is just an alternative way to show, that the actual state can be read using From 172381ee55bc04804ae9d59f23e72236f8fa4d3b Mon Sep 17 00:00:00 2001 From: csviri Date: Thu, 24 Mar 2022 15:58:24 +0100 Subject: [PATCH 9/9] docs: additional note --- docs/documentation/dependent-resources.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/documentation/dependent-resources.md b/docs/documentation/dependent-resources.md index 56a9bdbc1c..e8f3fbe8fe 100644 --- a/docs/documentation/dependent-resources.md +++ b/docs/documentation/dependent-resources.md @@ -130,6 +130,9 @@ the Kubernetes garbage collector through owner references, what is automatically `CRUKubernetesDependentResource` is only an adaptor class that already implements the`Creator` and `Updater` but not the `Deleter` interface. +The `@KuberentesDependent` annotation can be used to further configure **managed** dependent resource that are extending +`KubernetesDependentResource`. + See the full source code [here](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentDependentResource.java). ## Managed Dependent Resources