Skip to content

improve: remove ErrorStatusHandler interface #2438

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docsy/content/en/docs/migration/v5-0-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,5 @@ description: Migrating from v4.7 to v5.0
This also means, that `BulkDependentResource` now does not automatically implement `Creator` and `Deleter` as before.
Make sure to implement those interfaces in your bulk dependent resources. You can use also the new helper interface, the
[`CRUDBulkDependentResource`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/CRUDBulkDependentResource.java)
what also implement `BulkUpdater` interface.
what also implement `BulkUpdater` interface.
12. `ErrorStatusHandler` is deleted. Just delete the interface from your impl.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public class ErrorStatusUpdateControl<P extends HasMetadata>

private final P resource;
private boolean noRetry = false;
private final boolean defaultErrorProcessing;

public static <T extends HasMetadata> ErrorStatusUpdateControl<T> patchStatus(T resource) {
return new ErrorStatusUpdateControl<>(resource);
Expand All @@ -19,8 +20,21 @@ public static <T extends HasMetadata> ErrorStatusUpdateControl<T> noStatusUpdate
return new ErrorStatusUpdateControl<>(null);
}

/**
* No special processing of the error, the error will be thrown and default error handling will
* apply
*/
public static <T extends HasMetadata> ErrorStatusUpdateControl<T> defaultErrorProcessing() {
return new ErrorStatusUpdateControl<>(null, true);
}

private ErrorStatusUpdateControl(P resource) {
this(resource, false);
}

private ErrorStatusUpdateControl(P resource, boolean defaultErrorProcessing) {
this.resource = resource;
this.defaultErrorProcessing = defaultErrorProcessing;
}

/**
Expand All @@ -29,6 +43,9 @@ private ErrorStatusUpdateControl(P resource) {
* @return ErrorStatusUpdateControl
*/
public ErrorStatusUpdateControl<P> withNoRetry() {
if (defaultErrorProcessing) {
throw new IllegalStateException("Cannot set no-retry for default error processing");
}
this.noRetry = true;
return this;
}
Expand All @@ -41,6 +58,10 @@ public boolean isNoRetry() {
return noRetry;
}

public boolean isDefaultErrorProcessing() {
return defaultErrorProcessing;
}

/**
* If re-scheduled using this method, it is not considered as retry, it effectively cancels retry.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,28 @@ default List<EventSource> prepareEventSources(EventSourceContext<P> context) {
return Collections.emptyList();
}

/**
* <p>
* Reconciler can override this method in order to update the status sub-resource in the case an
* exception in thrown. In that case {@link #updateErrorStatus(HasMetadata, Context, Exception)}
* is called automatically.
* <p>
* The result of the method call is used to make a status update on the custom resource. This is
* always a sub-resource update request, so no update on custom resource itself (like spec of
* metadata) happens. Note that this update request will also produce an event, and will result in
* a reconciliation if the controller is not generation aware.
* <p>
* Note that the scope of this feature is only the reconcile method of the reconciler, since there
* should not be updates on custom resource after it is marked for deletion.
*
* @param resource to update the status on
* @param context the current context
* @param e exception thrown from the reconciler
* @return the updated resource
*/
default ErrorStatusUpdateControl<P> updateErrorStatus(P resource, Context<P> context,
Exception e) {
return ErrorStatusUpdateControl.defaultErrorProcessing();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.DefaultContext;
import io.javaoperatorsdk.operator.api.reconciler.DeleteControl;
import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusHandler;
import io.javaoperatorsdk.operator.api.reconciler.RetryInfo;
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
import io.javaoperatorsdk.operator.processing.Controller;
Expand Down Expand Up @@ -174,55 +173,46 @@ private PostExecutionControl<P> reconcileExecution(ExecutionScope<P> executionSc
private PostExecutionControl<P> handleErrorStatusHandler(P resource, P originalResource,
Context<P> context,
Exception e) throws Exception {
if (isErrorStatusHandlerPresent()) {
try {
RetryInfo retryInfo = context.getRetryInfo().orElseGet(() -> new RetryInfo() {
@Override
public int getAttemptCount() {
return 0;
}

@Override
public boolean isLastAttempt() {
// check also if the retry is limited to 0
return retryConfigurationHasZeroAttempts ||
controller.getConfiguration().getRetry() == null;
}
});
((DefaultContext<P>) context).setRetryInfo(retryInfo);
var errorStatusUpdateControl = ((ErrorStatusHandler<P>) controller.getReconciler())
.updateErrorStatus(resource, context, e);

P updatedResource = null;
if (errorStatusUpdateControl.getResource().isPresent()) {
updatedResource = customResourceFacade
.patchStatus(errorStatusUpdateControl.getResource().orElseThrow(), originalResource);
}
if (errorStatusUpdateControl.isNoRetry()) {
PostExecutionControl<P> postExecutionControl;
if (updatedResource != null) {
postExecutionControl =
PostExecutionControl.customResourceStatusPatched(updatedResource);
} else {
postExecutionControl = PostExecutionControl.defaultDispatch();
}
errorStatusUpdateControl.getScheduleDelay()
.ifPresent(postExecutionControl::withReSchedule);
return postExecutionControl;
}
} catch (RuntimeException ex) {
log.error("Error during error status handling.", ex);
RetryInfo retryInfo = context.getRetryInfo().orElseGet(() -> new RetryInfo() {
@Override
public int getAttemptCount() {
return 0;
}
}
throw e;
}

private boolean isErrorStatusHandlerPresent() {
return controller.getReconciler() instanceof ErrorStatusHandler;
}
@Override
public boolean isLastAttempt() {
// check also if the retry is limited to 0
return retryConfigurationHasZeroAttempts ||
controller.getConfiguration().getRetry() == null;
}
});
((DefaultContext<P>) context).setRetryInfo(retryInfo);
var errorStatusUpdateControl = controller.getReconciler()
.updateErrorStatus(resource, context, e);

private P patchStatusGenerationAware(P resource, P originalResource) {
return customResourceFacade.patchStatus(resource, originalResource);
if (errorStatusUpdateControl.isDefaultErrorProcessing()) {
throw e;
}

P updatedResource = null;
if (errorStatusUpdateControl.getResource().isPresent()) {
updatedResource = customResourceFacade
.patchStatus(errorStatusUpdateControl.getResource().orElseThrow(), originalResource);
}
if (errorStatusUpdateControl.isNoRetry()) {
PostExecutionControl<P> postExecutionControl;
if (updatedResource != null) {
postExecutionControl =
PostExecutionControl.customResourceStatusPatched(updatedResource);
} else {
postExecutionControl = PostExecutionControl.defaultDispatch();
}
errorStatusUpdateControl.getScheduleDelay()
.ifPresent(postExecutionControl::withReSchedule);
return postExecutionControl;
}
throw e;
}

private PostExecutionControl<P> createPostExecutionControl(P updatedCustomResource,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.function.Supplier;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.ArgumentMatchers;
import org.mockito.stubbing.Answer;

Expand All @@ -28,7 +28,6 @@
import io.javaoperatorsdk.operator.api.reconciler.Cleaner;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.DeleteControl;
import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusHandler;
import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusUpdateControl;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
import io.javaoperatorsdk.operator.api.reconciler.RetryInfo;
Expand Down Expand Up @@ -479,7 +478,7 @@ void callErrorStatusHandlerIfImplemented() {
reconciler.reconcile = (r, c) -> {
throw new IllegalStateException("Error Status Test");
};
reconciler.errorHandler = (r, ri, e) -> {
reconciler.errorHandler = () -> {
testCustomResource.getStatus().setConfigMapStatus(ERROR_MESSAGE);
return ErrorStatusUpdateControl.patchStatus(testCustomResource);
};
Expand All @@ -499,7 +498,7 @@ public boolean isLastAttempt() {
}).setResource(testCustomResource));

verify(customResourceFacade, times(1)).patchStatus(eq(testCustomResource), any());
verify(((ErrorStatusHandler) reconciler), times(1)).updateErrorStatus(eq(testCustomResource),
verify(reconciler, times(1)).updateErrorStatus(eq(testCustomResource),
any(), any());
}

Expand All @@ -510,15 +509,15 @@ void callErrorStatusHandlerEvenOnFirstError() {
reconciler.reconcile = (r, c) -> {
throw new IllegalStateException("Error Status Test");
};
reconciler.errorHandler = (r, ri, e) -> {
reconciler.errorHandler = () -> {
testCustomResource.getStatus().setConfigMapStatus(ERROR_MESSAGE);
return ErrorStatusUpdateControl.patchStatus(testCustomResource);
};

var postExecControl = reconciliationDispatcher.handleExecution(
new ExecutionScope(null).setResource(testCustomResource));
verify(customResourceFacade, times(1)).patchStatus(eq(testCustomResource), any());
verify(((ErrorStatusHandler) reconciler), times(1)).updateErrorStatus(eq(testCustomResource),
verify(reconciler, times(1)).updateErrorStatus(eq(testCustomResource),
any(), any());
assertThat(postExecControl.exceptionDuringExecution()).isTrue();
}
Expand All @@ -529,15 +528,15 @@ void errorHandlerCanInstructNoRetryWithUpdate() {
reconciler.reconcile = (r, c) -> {
throw new IllegalStateException("Error Status Test");
};
reconciler.errorHandler = (r, ri, e) -> {
reconciler.errorHandler = () -> {
testCustomResource.getStatus().setConfigMapStatus(ERROR_MESSAGE);
return ErrorStatusUpdateControl.patchStatus(testCustomResource).withNoRetry();
};

var postExecControl = reconciliationDispatcher.handleExecution(
new ExecutionScope(null).setResource(testCustomResource));

verify(((ErrorStatusHandler) reconciler), times(1)).updateErrorStatus(eq(testCustomResource),
verify(reconciler, times(1)).updateErrorStatus(eq(testCustomResource),
any(), any());
verify(customResourceFacade, times(1)).patchStatus(eq(testCustomResource), any());
assertThat(postExecControl.exceptionDuringExecution()).isFalse();
Expand All @@ -549,15 +548,15 @@ void errorHandlerCanInstructNoRetryNoUpdate() {
reconciler.reconcile = (r, c) -> {
throw new IllegalStateException("Error Status Test");
};
reconciler.errorHandler = (r, ri, e) -> {
reconciler.errorHandler = () -> {
testCustomResource.getStatus().setConfigMapStatus(ERROR_MESSAGE);
return ErrorStatusUpdateControl.<TestCustomResource>noStatusUpdate().withNoRetry();
};

var postExecControl = reconciliationDispatcher.handleExecution(
new ExecutionScope(null).setResource(testCustomResource));

verify(((ErrorStatusHandler) reconciler), times(1)).updateErrorStatus(eq(testCustomResource),
verify(reconciler, times(1)).updateErrorStatus(eq(testCustomResource),
any(), any());
verify(customResourceFacade, times(0)).patchStatus(eq(testCustomResource), any());
assertThat(postExecControl.exceptionDuringExecution()).isFalse();
Expand All @@ -570,13 +569,13 @@ void errorStatusHandlerCanPatchResource() {
throw new IllegalStateException("Error Status Test");
};
reconciler.errorHandler =
(r, ri, e) -> ErrorStatusUpdateControl.patchStatus(testCustomResource);
() -> ErrorStatusUpdateControl.patchStatus(testCustomResource);

reconciliationDispatcher.handleExecution(
new ExecutionScope(null).setResource(testCustomResource));

verify(customResourceFacade, times(1)).patchStatus(eq(testCustomResource), any());
verify(((ErrorStatusHandler) reconciler), times(1)).updateErrorStatus(eq(testCustomResource),
verify(reconciler, times(1)).updateErrorStatus(eq(testCustomResource),
any(), any());
}

Expand All @@ -592,16 +591,14 @@ void ifRetryLimitedToZeroMaxAttemptsErrorHandlerGetsCorrectLastAttempt() {
reconciler.reconcile = (r, c) -> {
throw new IllegalStateException("Error Status Test");
};
var mockErrorHandler = mock(ErrorStatusHandler.class);
when(mockErrorHandler.updateErrorStatus(any(), any(), any()))
.thenReturn(ErrorStatusUpdateControl.noStatusUpdate());
reconciler.errorHandler = mockErrorHandler;

reconciler.errorHandler = () -> ErrorStatusUpdateControl.noStatusUpdate();

reconciliationDispatcher.handleExecution(
new ExecutionScope(null).setResource(testCustomResource));

verify(mockErrorHandler, times(1)).updateErrorStatus(any(),
ArgumentMatchers.argThat((ArgumentMatcher<Context<TestCustomResource>>) context -> {
verify(reconciler, times(1)).updateErrorStatus(any(),
ArgumentMatchers.argThat(context -> {
var retryInfo = context.getRetryInfo().orElseThrow();
return retryInfo.isLastAttempt();
}), any());
Expand Down Expand Up @@ -651,7 +648,7 @@ void reSchedulesFromErrorHandler() {
throw new IllegalStateException("Error Status Test");
};
reconciler.errorHandler =
(r, ri, e) -> ErrorStatusUpdateControl.<TestCustomResource>noStatusUpdate()
() -> ErrorStatusUpdateControl.<TestCustomResource>noStatusUpdate()
.rescheduleAfter(delay);

var res = reconciliationDispatcher.handleExecution(
Expand Down Expand Up @@ -691,12 +688,11 @@ public <T extends HasMetadata> ExecutionScope<T> executionScopeWithCREvent(T res
}

private class TestReconciler
implements Reconciler<TestCustomResource>, Cleaner<TestCustomResource>,
ErrorStatusHandler<TestCustomResource> {
implements Reconciler<TestCustomResource>, Cleaner<TestCustomResource> {

private BiFunction<TestCustomResource, Context, UpdateControl<TestCustomResource>> reconcile;
private BiFunction<TestCustomResource, Context, DeleteControl> cleanup;
private ErrorStatusHandler<TestCustomResource> errorHandler;
private Supplier<ErrorStatusUpdateControl> errorHandler;

@Override
public UpdateControl<TestCustomResource> reconcile(TestCustomResource resource,
Expand All @@ -719,7 +715,7 @@ public DeleteControl cleanup(TestCustomResource resource, Context context) {
public ErrorStatusUpdateControl<TestCustomResource> updateErrorStatus(
TestCustomResource resource,
Context<TestCustomResource> context, Exception e) {
return errorHandler != null ? errorHandler.updateErrorStatus(resource, context, e)
return errorHandler != null ? errorHandler.get()
: ErrorStatusUpdateControl.noStatusUpdate();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@
dependsOn = SECRET_NAME)})
@ControllerConfiguration
public class DependentResourceCrossRefReconciler
implements Reconciler<DependentResourceCrossRefResource>,
ErrorStatusHandler<DependentResourceCrossRefResource> {
implements Reconciler<DependentResourceCrossRefResource> {

public static final String SECRET_NAME = "secret";
private final AtomicInteger numberOfExecutions = new AtomicInteger(0);
Expand Down
Loading
Loading