|
| 1 | +--- |
| 2 | +title: Version 5 Released! |
| 3 | +date: 2024-09-21 |
| 4 | +--- |
| 5 | + |
| 6 | +We are excited to announce that Java Operator SDK v5 has been released. This significant effort contains |
| 7 | +various features and enhancements accumulated since the last major releases and required changes in our APIs. |
| 8 | +Within this post, we will go through all the main changes and help you upgrade to this new version, and provide |
| 9 | +a rationale behind the changes if necessary. |
| 10 | + |
| 11 | +We will omit descriptions of changes that are trivial to update from the source code; feel free to contact |
| 12 | +us if you have some trouble with updates. |
| 13 | + |
| 14 | +## Various Changes |
| 15 | + |
| 16 | +- From this release, the minimal Java version is 17. |
| 17 | +- Various deprecated APIs are removed. The migration should be trivial. |
| 18 | + |
| 19 | +## Naming changes |
| 20 | + |
| 21 | +TODO add handy diff links here |
| 22 | + |
| 23 | +## Changes in low-level APIs |
| 24 | + |
| 25 | +### Server Side Apply |
| 26 | + |
| 27 | +[Server Side Apply](https://kubernetes.io/docs/reference/using-api/server-side-apply/) is now a first-class citizen in the framework and |
| 28 | +the default approach for patching the status resource. That means patching the resource or it's status through `UpdateControl` and adding |
| 29 | +the finalizer in the background. |
| 30 | + |
| 31 | +Migration from a non-SSA based patching to an SSA based one can be problematic. Make sure you test the transition when you migrate from older version of the frameworks. |
| 32 | +To continue to use a non-SSA based on, set [ConfigurationService.useSSAToPatchPrimaryResource](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java#L462) to `false`. |
| 33 | + |
| 34 | +See some identified problematic migration cases and how to handle them in [StatusPatchSSAMigrationIT](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuspatchnonlocking/StatusPatchSSAMigrationIT.java). |
| 35 | + |
| 36 | +TODO using new instance to update status always, |
| 37 | + |
| 38 | +### Event Sources related changes |
| 39 | + |
| 40 | +#### Multi-cluster support in InformerEventSource |
| 41 | + |
| 42 | +`InformerEventSource` now supports watching remote clusters. You can simply pass an `KubernetesClient` that is |
| 43 | +initialized to connect to a different cluster where the controller runs. See [InformerEventSourceConfiguration.withKubernetesClient](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerEventSourceConfiguration.java) |
| 44 | + |
| 45 | +Such an informer behaves exactly as a normal one. Obviously, owner references won't work, so you have to specify a `SecondaryToPrimaryMapper` (probably based on labels or annotations). |
| 46 | + |
| 47 | +See related integration test [here](https://github.com/operator-framework/java-operator-sdk/tree/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster) |
| 48 | + |
| 49 | +#### SecondaryToPrimaryMapper now checks resource types |
| 50 | + |
| 51 | +The owner reference based mappers are now checking the type (`kind` and `apiVersion`) of the resource when resolving the mapping. This is important |
| 52 | +since a resource may have owner references to a different resource type with the same name. |
| 53 | + |
| 54 | +See implementation details [here](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/Mappers.java#L74-L75) |
| 55 | + |
| 56 | +#### InformerEventSource-related reactors |
| 57 | + |
| 58 | +There are multiple smaller changes to `InformerEventSource` and related classes: |
| 59 | + |
| 60 | +1. `InformerConfiguration` is renamed to [`InformerEventSourceConfiguration'](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerEventSourceConfiguration.java) |
| 61 | +2. `InformerEventSourceConfiguration` doesn't require `EventSourceContext` to be initialized anymore. |
| 62 | + |
| 63 | +#### All EventSource is now a ResourceEventSource |
| 64 | + |
| 65 | +The [`EventSource`](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/EventSource.java) abstraction is now always aware of the resources and |
| 66 | +handles accessing (the cached) resources, filtering, and additional capabilities. Before v5, such capabilities were present only in a sub-class called `ResourceEventSource`, |
| 67 | +but we decided to merge and remove `ResourceEventSource` since this has a nice impact on other parts of the system in terms of architecture. |
| 68 | + |
| 69 | +If you still need to create an `EventSource` that does only the triggering, see [TimerEventSource](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/timer/TimerEventSource.java) as an example. |
| 70 | + |
| 71 | +#### Naming event sources |
| 72 | + |
| 73 | +The `name` is now directly property of the [`EventSource`](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/EventSource.java#L45). |
| 74 | + |
| 75 | +This results in nicer internal structures. For example, if a DependentResource provides an EventSource, we have more options to set the name for it. |
| 76 | + |
| 77 | +### ControllerConfiguration annotation related changes |
| 78 | + |
| 79 | +You no longer have to annotate the reconciler with `@ControllerConfiguration` annotation. |
| 80 | +This annotation is (one) way to override the default properties of a controller. |
| 81 | +If the annotation is not present, the default values from the annotation are used. |
| 82 | + |
| 83 | +PR: https://github.com/operator-framework/java-operator-sdk/pull/2203 |
| 84 | + |
| 85 | +In addition to that, the informer-related configurations are now extracted into |
| 86 | +a separate [`@Informer`](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/Informer.java) annotation within [`@ControllerConfiguration`](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java#L24). Hopefully this makes the underlying components more explicit |
| 87 | +and easier to understand. Note that the same `@Informer` annotation is used when configuring a managed `KubernetesDependentResource` with |
| 88 | +[`KubernetesDependent`](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependent.java#L33) annotation. |
| 89 | + |
| 90 | + |
| 91 | +### EventSourceInitializer and ErrorStatusHandler are removed |
| 92 | + |
| 93 | +Both the `EventSourceIntializer` and `ErrorStatusHandler` interfaces are removed, and their methods are moved directly |
| 94 | +under [`Reconciler`](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Reconciler.java#L30-L56). |
| 95 | + |
| 96 | +If possible, we try to avoid such marker interfaces since it is hard to deduce related usage just by looking at the source code. |
| 97 | +You can now simply override those methods when implementing the `Reconciler` interface. |
| 98 | + |
| 99 | +### Cloning accessing secondary resources |
| 100 | + |
| 101 | +When accessing the secondary resources using [`Context.getSecondaryResource(s)(...)`](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java#L19-L29), the resources are no longer cloned by default, since |
| 102 | +cloning could have an impact on performance. Note that means that these POJOs should be used only for "read-only"; any changes |
| 103 | +are now made directly to the cached resource. This should be avoided since the same resource instance may be present for other reconciliation cycles and would |
| 104 | +no longer represent the state on the server. |
| 105 | + |
| 106 | +If you want to still clone resource by default, set [ConfigurationService.cloneSecondaryResourcesWhenGettingFromCache](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java#L484) to `true`. |
| 107 | + |
| 108 | + |
| 109 | +### Remove automated observed generation handling |
| 110 | + |
| 111 | +The automatic observed generation handling feature was removed since it is trivial to implement inside the reconciler, but it made |
| 112 | +the implementation much more complex, especially if the framework would have to support it both for served side apply and client side apply. |
| 113 | + |
| 114 | +You can check a sample implementation how to do it manually in this [integration test](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/manualobservedgeneration/). |
| 115 | + |
| 116 | +## Dependent Resource related changes |
| 117 | + |
| 118 | +### ResourceDescriminator is removed and related changes |
| 119 | + |
| 120 | +The primary reason `ResourceDiscriminator` was introduced is to cover the case when there are |
| 121 | +more dependent resources for that same type, so there was a need a generic mechanism to |
| 122 | +associate the resources from API served with the related dependent resource. |
| 123 | +This mechanism is now improved with a more lightweight approach, that made the `ResourceDiscriminator` |
| 124 | +obsolete. |
| 125 | + |
| 126 | +As a replacement, the dependent resource will select the target resource based on the desired state. |
| 127 | +See the generic implementation in [`AbstractDependentResource`](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java#L135-L144). |
| 128 | +Calculating the desired state can be costly and might depend on other resources. For `KubernetesDependentResource` |
| 129 | +is usually enough to provide the name and namespace (if namespace-scoped) of the target resource, therefore |
| 130 | +in case the desired state is more heavy weight, in order to provide the ID of the target resource you might |
| 131 | +override [`ResourceID managedSecondaryResourceID()`](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java#L234-L244) method. |
| 132 | + |
| 133 | +TODO add sample. |
| 134 | + |
| 135 | +### Read-only bulk dependent resources |
| 136 | + |
| 137 | +Read-only bulk dependent resources are now supported; this was a request from multiple users, but it required changes to the underlying APIs. |
| 138 | +Please check the documentation for further details. |
| 139 | + |
| 140 | +See also the related [integration test](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/bulkdependent/readonly). |
| 141 | + |
| 142 | + |
| 143 | +### Multiple Dependents with Activation Condition |
| 144 | + |
| 145 | +Until now, activation conditions had a limitation that only one condition was allowed for a specific resource type. |
| 146 | +For example, two ConfigMap dependent resources were not allowed, both with activation conditions. The underlying issue |
| 147 | +was with the informer registration process. When an activation condition is evaluated as "met" in the background, |
| 148 | +the informer is registered dynamically for the target resource type. However, we need to avoid registering multiple |
| 149 | +informers of the same kind. To prevent this the dependent resource must specify the [name of the informer](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/multipledependentwithactivation/ConfigMapDependentResource2.java#L12). |
| 150 | + |
| 151 | +See the complete example [here](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/multipledependentwithactivation). |
| 152 | + |
| 153 | + |
| 154 | +### The getSecondaryResource() is Activation condition aware |
| 155 | + |
| 156 | +When there is an activation condition for a resource type, it might or might not be met based that |
| 157 | +an informer might be registered for the target kind. However, when calling `Context.getSecondaryResource` |
| 158 | +and its alternatives; it behaves differently if there is an Informer registered or not. Thus, normally |
| 159 | +throws an exception if there is no registered informer for the target type. For resources |
| 160 | +with activation condition, this might be confusing however. Therefore, if a dependent resource for a type with an activation |
| 161 | +condition is present, it always behaves as there is an Informer registered. |
| 162 | + |
| 163 | +See related [issue](https://github.com/operator-framework/java-operator-sdk/issues/2198) for details. |
| 164 | + |
| 165 | +## Workflow related changes |
| 166 | + |
| 167 | +### Explicit workflow invocation |
| 168 | + |
| 169 | +You can explicitly invoke managed workflows during reconciliation and/or cleanup, until now the execution always happened |
| 170 | +before the `reconcile(...)` (respectively before `cleanup(...)`). This mean you can do all kind of operations - typically validations - |
| 171 | +before executing the workflow. Sample: |
| 172 | + |
| 173 | +```java |
| 174 | +@Workflow(explicitInvocation = true, |
| 175 | + dependents = @Dependent(type = ConfigMapDependent.class)) |
| 176 | +@ControllerConfiguration |
| 177 | +public class WorkflowExplicitCleanupReconciler |
| 178 | + implements Reconciler<WorkflowExplicitCleanupCustomResource>, |
| 179 | + Cleaner<WorkflowExplicitCleanupCustomResource> { |
| 180 | + |
| 181 | + @Override |
| 182 | + public UpdateControl<WorkflowExplicitCleanupCustomResource> reconcile( |
| 183 | + WorkflowExplicitCleanupCustomResource resource, |
| 184 | + Context<WorkflowExplicitCleanupCustomResource> context) { |
| 185 | + |
| 186 | + context.managedWorkflowAndDependentResourceContext().reconcileManagedWorkflow(); |
| 187 | + |
| 188 | + return UpdateControl.noUpdate(); |
| 189 | + } |
| 190 | + |
| 191 | + @Override |
| 192 | + public DeleteControl cleanup(WorkflowExplicitCleanupCustomResource resource, |
| 193 | + Context<WorkflowExplicitCleanupCustomResource> context) { |
| 194 | + |
| 195 | + context.managedWorkflowAndDependentResourceContext().cleanupManageWorkflow(); |
| 196 | + // this can be checked |
| 197 | + // context.managedWorkflowAndDependentResourceContext().getWorkflowCleanupResult() |
| 198 | + return DeleteControl.defaultDelete(); |
| 199 | + } |
| 200 | +} |
| 201 | +``` |
| 202 | + |
| 203 | +To turn on this mode of execution, set [`explicitInvocation`](https://github.com/operator-framework/java-operator-sdk/blob/664cb7109fe62f9822997d578ae7f57f17ef8c26/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Workflow.java#L26) flag to true in managed workflow definition. |
| 204 | + |
| 205 | +See the following integration tests for [invocation](https://github.com/operator-framework/java-operator-sdk/blob/664cb7109fe62f9822997d578ae7f57f17ef8c26/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowexplicitinvocation) and [cleanup](https://github.com/operator-framework/java-operator-sdk/blob/664cb7109fe62f9822997d578ae7f57f17ef8c26/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowexplicitcleanup). |
| 206 | + |
| 207 | +### Explicit exception handling |
| 208 | + |
| 209 | +If an exception happens during reconciliation of a workflow, the framework automatically throws it further. |
| 210 | +You can now set [`handleExceptionsInReconciler`](https://github.com/operator-framework/java-operator-sdk/blob/664cb7109fe62f9822997d578ae7f57f17ef8c26/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Workflow.java#L40) to true for a workflow and check the thrown exceptions explicitly |
| 211 | +in the execution results. |
| 212 | + |
| 213 | +```java |
| 214 | +@Workflow(handleExceptionsInReconciler = true, |
| 215 | + dependents = @Dependent(type = ConfigMapDependent.class)) |
| 216 | +@ControllerConfiguration |
| 217 | +public class HandleWorkflowExceptionsInReconcilerReconciler |
| 218 | + implements Reconciler<HandleWorkflowExceptionsInReconcilerCustomResource>, |
| 219 | + Cleaner<HandleWorkflowExceptionsInReconcilerCustomResource> { |
| 220 | + |
| 221 | + private volatile boolean errorsFoundInReconcilerResult = false; |
| 222 | + private volatile boolean errorsFoundInCleanupResult = false; |
| 223 | + |
| 224 | + @Override |
| 225 | + public UpdateControl<HandleWorkflowExceptionsInReconcilerCustomResource> reconcile( |
| 226 | + HandleWorkflowExceptionsInReconcilerCustomResource resource, |
| 227 | + Context<HandleWorkflowExceptionsInReconcilerCustomResource> context) { |
| 228 | + |
| 229 | + errorsFoundInReconcilerResult = context.managedWorkflowAndDependentResourceContext() |
| 230 | + .getWorkflowReconcileResult().erroredDependentsExist(); |
| 231 | + |
| 232 | + // check errors here: |
| 233 | + Map<DependentResource, Exception> errors = context.getErroredDependents(); |
| 234 | + |
| 235 | + return UpdateControl.noUpdate(); |
| 236 | + } |
| 237 | +``` |
| 238 | + |
| 239 | +See integration test [here](https://github.com/operator-framework/java-operator-sdk/blob/664cb7109fe62f9822997d578ae7f57f17ef8c26/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowsilentexceptionhandling). |
| 240 | + |
| 241 | +### @Workflow annotation |
| 242 | + |
| 243 | +The managed workflow definition is now a separate `@Workflow` annotation; it is no longer part of `@ControllerConfiguration`. |
| 244 | + |
| 245 | +See sample usage [here](https://github.com/operator-framework/java-operator-sdk/blob/664cb7109fe62f9822997d578ae7f57f17ef8c26/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageManagedDependentsReconciler.java#L14-L20) |
| 246 | + |
| 247 | +### CRDPresentActivationCondition |
| 248 | + |
| 249 | +Activation conditions are typically used to check if the cluster has specific capabilities (e.g., is cert-manager available). |
| 250 | +Such a check can be done by verifying if a particular custom resource definition (CRD) is present on the cluster. You |
| 251 | +can now use the generic [`CRDPresentActivationCondition`](https://github.com/operator-framework/java-operator-sdk/blob/664cb7109fe62f9822997d578ae7f57f17ef8c26/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/CRDPresentActivationCondition.java) for this |
| 252 | +purpose, it will check if the CRD of a target resource type of a dependent resource exists on the cluster. |
| 253 | + |
| 254 | +See usage in integration test [here](https://github.com/operator-framework/java-operator-sdk/blob/refs/heads/next/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/crdpresentactivation). |
| 255 | + |
| 256 | +## Experimental |
| 257 | + |
| 258 | +### Check if the following reconciliation is imminent |
| 259 | + |
| 260 | +You can now check if the subsequent reconciliation will happen right after the current one. In other words |
| 261 | +If we have already received a new event triggering the reconciliation for the actual resource. |
| 262 | +This information is available from the [context](https://github.com/operator-framework/java-operator-sdk/blob/664cb7109fe62f9822997d578ae7f57f17ef8c26/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java#L69). |
| 263 | + |
| 264 | +Note that this could be useful, for example, in situations when a heavy task would be repeated in the follow-up reconciliation. In the current |
| 265 | +reconciliation, you can check this flag and return, don't do the heavy task twice. Note that this is a semi-experimental feature, so please let us know |
| 266 | +if you found this helpful. |
| 267 | + |
| 268 | +```java |
| 269 | +@Override |
| 270 | +public UpdateControl<NextReconciliationImminentCustomResource> reconcile(MyCustomResource resource, Context<MyCustomResource> context) { |
| 271 | + |
| 272 | + if (context.isNextReconciliationImminent()) { |
| 273 | + // your logic, maybe return? |
| 274 | + } |
| 275 | +} |
| 276 | +``` |
| 277 | + |
| 278 | +See related [integration test](https://github.com/operator-framework/java-operator-sdk/blob/664cb7109fe62f9822997d578ae7f57f17ef8c26/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/nextreconciliationimminent). |
| 279 | + |
| 280 | + |
0 commit comments