Skip to content

Commit b981759

Browse files
authored
feat: patch does not do optimistic locking (#1210)
1 parent f6507c9 commit b981759

File tree

36 files changed

+425
-116
lines changed

36 files changed

+425
-116
lines changed

docs/documentation/architecture-and-internals.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ A typical workflows looks like following:
4747
4. Executor calls ReconcilerDispatcher which decides which method to execute of the reconciler. Let's say in this case it
4848
was `reconcile(...)`
4949
5. After reconciler execution the Dispatcher calls Kubernetes API server, since the `reconcile` method returned
50-
with `UpdateControl.updateStatus(...)` result.
50+
with `UpdateControl.patchStatus(...)` result.
5151
6. Now the dispatcher finishes the execution and calls back `EventProcessor` to finalize the execution.
5252
7. EventProcessor checks if there is no `reschedule` or `retry` required and if there are no subsequent events received
5353
for the custom resource

docs/documentation/dependent-resources.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ public class WebPageManagedDependentsReconciler
174174
final var name = context.getSecondaryResource(ConfigMap.class).orElseThrow()
175175
.getMetadata().getName();
176176
webPage.setStatus(createStatus(name));
177-
return UpdateControl.updateStatus(webPage);
177+
return UpdateControl.patchStatus(webPage);
178178
}
179179

180180
}
@@ -227,7 +227,7 @@ public class WebPageStandaloneDependentsReconciler
227227

228228
// 3.
229229
if (!isValidHtml(webPage.getHtml())) {
230-
return UpdateControl.updateStatus(setInvalidHtmlErrorMessage(webPage));
230+
return UpdateControl.patchStatus(setInvalidHtmlErrorMessage(webPage));
231231
}
232232

233233
// 4.
@@ -245,7 +245,7 @@ public class WebPageStandaloneDependentsReconciler
245245
// 6.
246246
webPage.setStatus(
247247
createStatus(configMapDR.getResource(webPage).orElseThrow().getMetadata().getName()));
248-
return UpdateControl.updateStatus(webPage);
248+
return UpdateControl.patchStatus(webPage);
249249
}
250250

251251
private void createDependentResources(KubernetesClient client) {

docs/documentation/faq.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Yes, this can be done using [`UpdateControl`](https://github.com/java-operator-s
2222
public UpdateControl<MyCustomResource> reconcile(
2323
EventSourceTestCustomResource resource, Context context) {
2424
...
25-
return UpdateControl.updateStatus(resource).rescheduleAfter(10, TimeUnit.SECONDS);
25+
return UpdateControl.patchStatus(resource).rescheduleAfter(10, TimeUnit.SECONDS);
2626
}
2727
```
2828

docs/documentation/features.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ reconciliation with a desired time delay.
9393
public UpdateControl<MyCustomResource> reconcile(
9494
EventSourceTestCustomResource resource, Context context) {
9595
...
96-
return UpdateControl.updateStatus(resource).rescheduleAfter(10, TimeUnit.SECONDS);
96+
return UpdateControl.patchStatus(resource).rescheduleAfter(10, TimeUnit.SECONDS);
9797
}
9898
```
9999

@@ -299,7 +299,7 @@ resource after it is marked for deletion.
299299
Retry can be skipped for the cases of unrecoverable errors:
300300

301301
```java
302-
ErrorStatusUpdateControl.updateStatus(customResource).withNoRetry();
302+
ErrorStatusUpdateControl.patchStatus(customResource).withNoRetry();
303303
```
304304

305305
### Correctness and Automatic Retries

docs/documentation/use-samples.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public class WebPageReconciler implements Reconciler<WebPage> {
7272
public UpdateControl<CustomService> reconcile(CustomService resource,
7373
Context context) {
7474
// ... your logic ...
75-
return UpdateControl.updateStatus(resource);
75+
return UpdateControl.patchStatus(resource);
7676
}
7777
}
7878
```

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ErrorStatusUpdateControl.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,25 @@
77
public class ErrorStatusUpdateControl<P extends HasMetadata> {
88

99
private final P resource;
10+
private final boolean patch;
1011
private boolean noRetry = false;
1112

13+
14+
public static <T extends HasMetadata> ErrorStatusUpdateControl<T> patchStatus(T resource) {
15+
return new ErrorStatusUpdateControl<>(resource, true);
16+
}
17+
1218
public static <T extends HasMetadata> ErrorStatusUpdateControl<T> updateStatus(T resource) {
13-
return new ErrorStatusUpdateControl<>(resource);
19+
return new ErrorStatusUpdateControl<>(resource, false);
1420
}
1521

1622
public static <T extends HasMetadata> ErrorStatusUpdateControl<T> noStatusUpdate() {
17-
return new ErrorStatusUpdateControl<>(null);
23+
return new ErrorStatusUpdateControl<>(null, true);
1824
}
1925

20-
private ErrorStatusUpdateControl(P resource) {
26+
private ErrorStatusUpdateControl(P resource, boolean patch) {
2127
this.resource = resource;
28+
this.patch = patch;
2229
}
2330

2431
/**
@@ -38,4 +45,8 @@ public Optional<P> getResource() {
3845
public boolean isNoRetry() {
3946
return noRetry;
4047
}
48+
49+
public boolean isPatch() {
50+
return patch;
51+
}
4152
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/UpdateControl.java

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,33 @@ public static <T extends HasMetadata> UpdateControl<T> updateResource(T customRe
3333
return new UpdateControl<>(customResource, false, true, false);
3434
}
3535

36-
public static <T extends HasMetadata> UpdateControl<T> updateStatus(T customResource) {
37-
return new UpdateControl<>(customResource, true, false, false);
38-
}
39-
40-
public static <T extends HasMetadata> UpdateControl<T> patchResource(T customResource) {
41-
return new UpdateControl<>(customResource, false, true, true);
42-
}
43-
36+
/**
37+
* Preferred way to update the status. It does not do optimistic locking.
38+
*
39+
* @param <T> resource type
40+
* @param customResource the custom resource with target status
41+
* @return UpdateControl instance
42+
*/
4443
public static <T extends HasMetadata> UpdateControl<T> patchStatus(T customResource) {
4544
return new UpdateControl<>(customResource, true, false, true);
4645
}
4746

47+
/**
48+
* Note that usually "patchStatus" is advised to be used instead of this method.
49+
* <p>
50+
* Updates the status with optimistic locking regarding current resource version reconciled. Note
51+
* that this also ensures that on next reconciliation is the most up-to-date custom resource is
52+
* used.
53+
* </p>
54+
*
55+
* @param <T> resource type
56+
* @param customResource the custom resource with target status
57+
* @return UpdateControl instance
58+
*/
59+
public static <T extends HasMetadata> UpdateControl<T> updateStatus(T customResource) {
60+
return new UpdateControl<>(customResource, true, false, false);
61+
}
62+
4863
/**
4964
* As a results of this there will be two call to K8S API. First the custom resource will be
5065
* updates then the status sub-resource.

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -200,10 +200,17 @@ void eventProcessingFinished(
200200
if (eventMarker.deleteEventPresent(resourceID)) {
201201
cleanupForDeletedEvent(executionScope.getResourceID());
202202
} else {
203-
postExecutionControl.getUpdatedCustomResource().ifPresent(
204-
r -> eventSourceManager.getControllerResourceEventSource().handleRecentResourceUpdate(
205-
ResourceID.fromResource(r), r,
206-
executionScope.getResource()));
203+
postExecutionControl
204+
.getUpdatedCustomResource()
205+
.ifPresent(
206+
r -> {
207+
if (!postExecutionControl.updateIsStatusPatch()) {
208+
eventSourceManager
209+
.getControllerResourceEventSource()
210+
.handleRecentResourceUpdate(
211+
ResourceID.fromResource(r), r, executionScope.getResource());
212+
}
213+
});
207214
if (eventMarker.eventPresent(resourceID)) {
208215
submitReconciliationExecution(resourceID);
209216
} else {

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/PostExecutionControl.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,35 +8,42 @@ final class PostExecutionControl<R extends HasMetadata> {
88

99
private final boolean onlyFinalizerHandled;
1010
private final R updatedCustomResource;
11+
private final boolean updateIsStatusPatch;
1112
private final Exception runtimeException;
1213

1314
private Long reScheduleDelay = null;
1415

1516
private PostExecutionControl(
1617
boolean onlyFinalizerHandled,
1718
R updatedCustomResource,
18-
Exception runtimeException) {
19+
boolean updateIsStatusPatch, Exception runtimeException) {
1920
this.onlyFinalizerHandled = onlyFinalizerHandled;
2021
this.updatedCustomResource = updatedCustomResource;
22+
this.updateIsStatusPatch = updateIsStatusPatch;
2123
this.runtimeException = runtimeException;
2224
}
2325

2426
public static <R extends HasMetadata> PostExecutionControl<R> onlyFinalizerAdded() {
25-
return new PostExecutionControl<>(true, null, null);
27+
return new PostExecutionControl<>(true, null, false, null);
2628
}
2729

2830
public static <R extends HasMetadata> PostExecutionControl<R> defaultDispatch() {
29-
return new PostExecutionControl<>(false, null, null);
31+
return new PostExecutionControl<>(false, null, false, null);
32+
}
33+
34+
public static <R extends HasMetadata> PostExecutionControl<R> customResourceStatusPatched(
35+
R updatedCustomResource) {
36+
return new PostExecutionControl<>(false, updatedCustomResource, true, null);
3037
}
3138

3239
public static <R extends HasMetadata> PostExecutionControl<R> customResourceUpdated(
3340
R updatedCustomResource) {
34-
return new PostExecutionControl<>(false, updatedCustomResource, null);
41+
return new PostExecutionControl<>(false, updatedCustomResource, false, null);
3542
}
3643

3744
public static <R extends HasMetadata> PostExecutionControl<R> exceptionDuringExecution(
3845
Exception exception) {
39-
return new PostExecutionControl<>(false, null, exception);
46+
return new PostExecutionControl<>(false, null, false, exception);
4047
}
4148

4249
public boolean isOnlyFinalizerHandled() {
@@ -68,6 +75,10 @@ public Optional<Long> getReScheduleDelay() {
6875
return Optional.ofNullable(reScheduleDelay);
6976
}
7077

78+
public boolean updateIsStatusPatch() {
79+
return updateIsStatusPatch;
80+
}
81+
7182
@Override
7283
public String toString() {
7384
return "PostExecutionControl{"

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import io.fabric8.kubernetes.client.CustomResource;
99
import io.fabric8.kubernetes.client.dsl.MixedOperation;
1010
import io.fabric8.kubernetes.client.dsl.Resource;
11+
import io.fabric8.kubernetes.client.dsl.internal.HasMetadataOperationsImpl;
1112
import io.javaoperatorsdk.operator.api.ObservedGenerationAware;
1213
import io.javaoperatorsdk.operator.api.config.ConfigurationServiceProvider;
1314
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
@@ -134,7 +135,7 @@ private PostExecutionControl<R> reconcileExecution(ExecutionScope<R> executionSc
134135
R updatedCustomResource = null;
135136
if (updateControl.isUpdateResourceAndStatus()) {
136137
updatedCustomResource =
137-
updateCustomResource(updateControl.getResource(), updateControl.isPatch());
138+
updateCustomResource(updateControl.getResource());
138139
updateControl
139140
.getResource()
140141
.getMetadata()
@@ -146,7 +147,7 @@ private PostExecutionControl<R> reconcileExecution(ExecutionScope<R> executionSc
146147
updateStatusGenerationAware(updateControl.getResource(), updateControl.isPatch());
147148
} else if (updateControl.isUpdateResource()) {
148149
updatedCustomResource =
149-
updateCustomResource(updateControl.getResource(), updateControl.isPatch());
150+
updateCustomResource(updateControl.getResource());
150151
if (shouldUpdateObservedGenerationAutomatically(updatedCustomResource)) {
151152
updatedCustomResource =
152153
updateStatusGenerationAware(originalResource, updateControl.isPatch());
@@ -181,13 +182,16 @@ public boolean isLastAttempt() {
181182

182183
R updatedResource = null;
183184
if (errorStatusUpdateControl.getResource().isPresent()) {
184-
updatedResource =
185-
customResourceFacade
185+
updatedResource = errorStatusUpdateControl.isPatch() ? customResourceFacade
186+
.patchStatus(errorStatusUpdateControl.getResource().orElseThrow())
187+
: customResourceFacade
186188
.updateStatus(errorStatusUpdateControl.getResource().orElseThrow());
187189
}
188190
if (errorStatusUpdateControl.isNoRetry()) {
189191
if (updatedResource != null) {
190-
return PostExecutionControl.customResourceUpdated(updatedResource);
192+
return errorStatusUpdateControl.isPatch()
193+
? PostExecutionControl.customResourceStatusPatched(updatedResource)
194+
: PostExecutionControl.customResourceUpdated(updatedResource);
191195
} else {
192196
return PostExecutionControl.defaultDispatch();
193197
}
@@ -212,6 +216,7 @@ private R updateStatusGenerationAware(R resource, boolean patch) {
212216
}
213217
}
214218

219+
@SuppressWarnings("rawtypes")
215220
private boolean shouldUpdateObservedGenerationAutomatically(R resource) {
216221
if (configuration().isGenerationAware() && resource instanceof CustomResource<?, ?>) {
217222
var customResource = (CustomResource) resource;
@@ -226,6 +231,7 @@ private boolean shouldUpdateObservedGenerationAutomatically(R resource) {
226231
return false;
227232
}
228233

234+
@SuppressWarnings("rawtypes")
229235
private void updateStatusObservedGenerationIfRequired(R resource) {
230236
if (configuration().isGenerationAware() && resource instanceof CustomResource<?, ?>) {
231237
var customResource = (CustomResource) resource;
@@ -242,7 +248,12 @@ private PostExecutionControl<R> createPostExecutionControl(R updatedCustomResour
242248
UpdateControl<R> updateControl) {
243249
PostExecutionControl<R> postExecutionControl;
244250
if (updatedCustomResource != null) {
245-
postExecutionControl = PostExecutionControl.customResourceUpdated(updatedCustomResource);
251+
if (updateControl.isUpdateStatus() && updateControl.isPatch()) {
252+
postExecutionControl =
253+
PostExecutionControl.customResourceStatusPatched(updatedCustomResource);
254+
} else {
255+
postExecutionControl = PostExecutionControl.customResourceUpdated(updatedCustomResource);
256+
}
246257
} else {
247258
postExecutionControl = PostExecutionControl.defaultDispatch();
248259
}
@@ -294,14 +305,10 @@ private void updateCustomResourceWithFinalizer(R resource) {
294305
customResourceFacade.replaceResourceWithLock(resource);
295306
}
296307

297-
private R updateCustomResource(R resource, boolean patch) {
308+
private R updateCustomResource(R resource) {
298309
log.debug("Updating resource: {} with version: {}", getUID(resource), getVersion(resource));
299310
log.trace("Resource before update: {}", resource);
300-
if (patch) {
301-
return customResourceFacade.patchResource(resource);
302-
} else {
303-
return customResourceFacade.replaceResourceWithLock(resource);
304-
}
311+
return customResourceFacade.replaceResourceWithLock(resource);
305312
}
306313

307314
private R removeFinalizer(R resource) {
@@ -340,27 +347,30 @@ public R replaceResourceWithLock(R resource) {
340347
.replace(resource);
341348
}
342349

343-
public R patchResource(R resource) {
344-
return resourceOperation
345-
.inNamespace(resource.getMetadata().getNamespace())
346-
.withName(getName(resource))
347-
.patch(resource);
348-
}
349-
350+
@SuppressWarnings({"rawtypes", "unchecked"})
350351
public R updateStatus(R resource) {
351352
log.trace("Updating status for resource: {}", resource);
352-
return resourceOperation
353+
HasMetadataOperationsImpl hasMetadataOperation = (HasMetadataOperationsImpl) resourceOperation
353354
.inNamespace(resource.getMetadata().getNamespace())
354355
.withName(getName(resource))
355-
.replaceStatus(resource);
356+
.lockResourceVersion(resource.getMetadata().getResourceVersion());
357+
return (R) hasMetadataOperation.replaceStatus(resource);
356358
}
357359

358360
public R patchStatus(R resource) {
359361
log.trace("Updating status for resource: {}", resource);
360-
return resourceOperation
361-
.inNamespace(resource.getMetadata().getNamespace())
362-
.withName(getName(resource))
363-
.patchStatus(resource);
362+
String resourceVersion = resource.getMetadata().getResourceVersion();
363+
try {
364+
// don't do optimistic locking on patch
365+
resource.getMetadata().setResourceVersion(null);
366+
return resourceOperation
367+
.inNamespace(resource.getMetadata().getNamespace())
368+
.withName(getName(resource))
369+
.patchStatus(resource);
370+
} finally {
371+
// restore initial resource version
372+
resource.getMetadata().setResourceVersion(resourceVersion);
373+
}
364374
}
365375
}
366376
}

0 commit comments

Comments
 (0)