Skip to content

Commit cf97936

Browse files
committed
refactor: allow optional retry
1 parent 66ee61a commit cf97936

File tree

13 files changed

+122
-41
lines changed

13 files changed

+122
-41
lines changed

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AnnotationControllerConfiguration.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter;
3131
import io.javaoperatorsdk.operator.processing.event.source.filter.OnDeleteFilter;
3232
import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter;
33+
import io.javaoperatorsdk.operator.processing.retry.NoRetry;
3334
import io.javaoperatorsdk.operator.processing.retry.Retry;
3435

3536
import static io.javaoperatorsdk.operator.api.reconciler.Constants.DEFAULT_NAMESPACES_SET;
@@ -150,10 +151,14 @@ public RateLimiter getRateLimiter() {
150151
}
151152

152153
@Override
153-
public Retry getRetry() {
154+
public Optional<Retry> getRetry() {
154155
final Class<? extends Retry> retryClass = annotation.retry();
155-
return Utils.instantiateAndConfigureIfNeeded(retryClass, Retry.class,
156-
Utils.contextFor(this, null, null), this::configureFromAnnotatedReconciler);
156+
if (NoRetry.class.equals(retryClass)) {
157+
return Optional.empty();
158+
}
159+
160+
return Optional.of(Utils.instantiateAndConfigureIfNeeded(retryClass, Retry.class,
161+
Utils.contextFor(this, null, null), this::configureFromAnnotatedReconciler));
157162
}
158163

159164

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,11 @@ default boolean isGenerationAware() {
3636

3737
String getAssociatedReconcilerClassName();
3838

39-
default Retry getRetry() {
39+
default Optional<Retry> getRetry() {
4040
final var configuration = getRetryConfiguration();
41-
return !RetryConfiguration.DEFAULT.equals(configuration)
41+
return Optional.of(!RetryConfiguration.DEFAULT.equals(configuration)
4242
? GenericRetry.fromConfiguration(configuration)
43-
: GenericRetry.DEFAULT; // NOSONAR
43+
: GenericRetry.DEFAULT); // NOSONAR
4444
}
4545

4646
/**

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public class ControllerConfigurationOverrider<R extends HasMetadata> {
2929
private String finalizer;
3030
private boolean generationAware;
3131
private Set<String> namespaces;
32-
private Retry retry;
32+
private Optional<Retry> retry;
3333
private String labelSelector;
3434
private ResourceEventFilter<R> customResourcePredicate;
3535
private final ControllerConfiguration<R> original;
@@ -113,12 +113,17 @@ public ControllerConfigurationOverrider<R> watchingAllNamespaces() {
113113
*/
114114
@Deprecated(forRemoval = true)
115115
public ControllerConfigurationOverrider<R> withRetry(RetryConfiguration retry) {
116-
this.retry = GenericRetry.fromConfiguration(retry);
116+
this.retry = Optional.of(GenericRetry.fromConfiguration(retry));
117117
return this;
118118
}
119119

120120
public ControllerConfigurationOverrider<R> withRetry(Retry retry) {
121-
this.retry = retry;
121+
this.retry = Optional.of(retry);
122+
return this;
123+
}
124+
125+
public ControllerConfigurationOverrider<R> withoutRetry() {
126+
this.retry = Optional.empty();
122127
return this;
123128
}
124129

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public class DefaultControllerConfiguration<R extends HasMetadata>
2626
private final String crdName;
2727
private final String finalizer;
2828
private final boolean generationAware;
29-
private final Retry retry;
29+
private final Optional<Retry> retry;
3030
private final ResourceEventFilter<R> resourceEventFilter;
3131
private final List<DependentResourceSpec> dependents;
3232
private final Duration reconciliationMaxInterval;
@@ -40,7 +40,7 @@ public DefaultControllerConfiguration(
4040
String finalizer,
4141
boolean generationAware,
4242
Set<String> namespaces,
43-
Retry retry,
43+
Optional<Retry> retry,
4444
String labelSelector,
4545
ResourceEventFilter<R> resourceEventFilter,
4646
Class<R> resourceClass,
@@ -93,7 +93,7 @@ public String getAssociatedReconcilerClassName() {
9393
}
9494

9595
@Override
96-
public Retry getRetry() {
96+
public Optional<Retry> getRetry() {
9797
return retry;
9898
}
9999

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public class EventProcessor<R extends HasMetadata> implements EventHandler, Life
4040
private volatile boolean running;
4141
private final ControllerConfiguration<?> controllerConfiguration;
4242
private final ReconciliationDispatcher<R> reconciliationDispatcher;
43-
private final Retry retry;
43+
private final Optional<Retry> retry;
4444
private final ExecutorService executor;
4545
private final Metrics metrics;
4646
private final Cache<R> cache;
@@ -346,7 +346,7 @@ private ResourceState getOrInitRetryExecution(ExecutionScope<R> executionScope)
346346
final var state = resourceStateManager.getOrCreate(executionScope.getResourceID());
347347
RetryExecution retryExecution = state.getRetry();
348348
if (retryExecution == null) {
349-
retryExecution = retry.initExecution();
349+
retryExecution = retry.map(r -> r.initExecution()).orElseThrow();
350350
state.setRetry(retryExecution);
351351
}
352352
return state;
@@ -367,7 +367,7 @@ private void unsetUnderExecution(ResourceID resourceID) {
367367
}
368368

369369
private boolean isRetryConfigured() {
370-
return retry != null;
370+
return retry.isPresent();
371371
}
372372

373373
@Override

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,19 @@ private PostExecutionControl<P> handleErrorStatusHandler(P resource, P originalR
165165
Exception e) throws Exception {
166166
if (isErrorStatusHandlerPresent()) {
167167
try {
168-
RetryInfo retryInfo =
169-
context.getRetryInfo().orElse(controller.getConfiguration().getRetry().initExecution());
168+
RetryInfo retryInfo = context.getRetryInfo().orElse(new RetryInfo() {
169+
@Override
170+
public int getAttemptCount() {
171+
return 0;
172+
}
173+
174+
@Override
175+
public boolean isLastAttempt() {
176+
// on first try, we can only rely on the configured behavior
177+
// if enabled, will at least produce one RetryExecution
178+
return !controller.getConfiguration().getRetry().isPresent();
179+
}
180+
});
170181
((DefaultContext<P>) context).setRetryInfo(retryInfo);
171182
var errorStatusUpdateControl = ((ErrorStatusHandler<P>) controller.getReconciler())
172183
.updateErrorStatus(resource, context, e);

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/retry/GenericRetry.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ public static GenericRetry defaultLimitedExponentialRetry() {
1515
return (GenericRetry) DEFAULT;
1616
}
1717

18+
/**
19+
* @deprecated Use the {@link NoRetry} annotation instead or override configuration with
20+
* withoutRetry.
21+
*/
22+
@Deprecated(forRemoval = true)
1823
public static GenericRetry noRetry() {
1924
return new GenericRetry().setMaxAttempts(0);
2025
}
@@ -94,6 +99,11 @@ public GenericRetry withLinearRetry() {
9499

95100
@Override
96101
public void initFrom(GradualRetry configuration) {
102+
if (configuration.maxAttempts() == 0) {
103+
throw new IllegalArgumentException(
104+
"maxAttempts must be non-zero. Use NoRetry to disable retry.");
105+
}
106+
97107
this.initialInterval = configuration.initialInterval();
98108
this.maxAttempts = configuration.maxAttempts();
99109
this.intervalMultiplier = configuration.intervalMultiplier();
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package io.javaoperatorsdk.operator.processing.retry;
2+
3+
public class NoRetry implements Retry {
4+
5+
/* should not be instanciated */
6+
private NoRetry() {}
7+
8+
@Override
9+
public RetryExecution initExecution() {
10+
return null;
11+
}
12+
}

operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/EventProcessorTest.java

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,13 @@ void setup() {
7373
when(eventSourceManagerMock.getControllerResourceEventSource())
7474
.thenReturn(controllerResourceEventSourceMock);
7575
eventProcessor =
76-
spy(new EventProcessor(controllerConfiguration(null, rateLimiterMock),
76+
spy(new EventProcessor(controllerConfiguration(Optional.empty(), rateLimiterMock),
7777
reconciliationDispatcherMock,
7878
eventSourceManagerMock, null));
7979
eventProcessor.start();
8080
eventProcessorWithRetry =
8181
spy(new EventProcessor(
82-
controllerConfiguration(GenericRetry.defaultLimitedExponentialRetry(),
82+
controllerConfiguration(Optional.of(GenericRetry.defaultLimitedExponentialRetry()),
8383
rateLimiterMock),
8484
reconciliationDispatcherMock, eventSourceManagerMock, null));
8585
eventProcessorWithRetry.start();
@@ -131,6 +131,20 @@ void schedulesAnEventRetryOnException() {
131131
eq(RetryConfiguration.DEFAULT_INITIAL_INTERVAL));
132132
}
133133

134+
@Test
135+
void doNotSchedulesAnEventRetryOnExceptionIfNoRetry() {
136+
TestCustomResource customResource = testCustomResource();
137+
138+
ExecutionScope executionScope = new ExecutionScope(customResource, null);
139+
PostExecutionControl postExecutionControl =
140+
PostExecutionControl.exceptionDuringExecution(new RuntimeException("test"));
141+
142+
eventProcessor.eventProcessingFinished(executionScope, postExecutionControl);
143+
144+
verify(retryTimerEventSourceMock, times(1))
145+
.scheduleOnce(eq(ResourceID.fromResource(customResource)), eq(1000L));
146+
}
147+
134148
@Test
135149
void executesTheControllerInstantlyAfterErrorIfNewEventsReceived() {
136150
Event event = prepareCREvent();
@@ -264,7 +278,7 @@ void cancelScheduleOnceEventsOnSuccessfulExecution() {
264278
void startProcessedMarkedEventReceivedBefore() {
265279
var crID = new ResourceID("test-cr", TEST_NAMESPACE);
266280
eventProcessor =
267-
spy(new EventProcessor(controllerConfiguration(null,
281+
spy(new EventProcessor(controllerConfiguration(Optional.empty(),
268282
LinearRateLimiter.deactivatedRateLimiter()), reconciliationDispatcherMock,
269283
eventSourceManagerMock,
270284
metricsMock));
@@ -393,7 +407,7 @@ void schedulesRetryForMarReconciliationIntervalIfRetryExhausted() {
393407
Retry retry = mock(Retry.class);
394408
when(retry.initExecution()).thenReturn(mockRetryExecution);
395409
eventProcessorWithRetry =
396-
spy(new EventProcessor(controllerConfiguration(retry,
410+
spy(new EventProcessor(controllerConfiguration(Optional.of(retry),
397411
LinearRateLimiter.deactivatedRateLimiter()), reconciliationDispatcherMock,
398412
eventSourceManagerMock,
399413
metricsMock));
@@ -448,7 +462,7 @@ private void overrideData(ResourceID id, HasMetadata applyTo) {
448462
applyTo.getMetadata().setNamespace(id.getNamespace().orElse(null));
449463
}
450464

451-
ControllerConfiguration controllerConfiguration(Retry retry, RateLimiter rateLimiter) {
465+
ControllerConfiguration controllerConfiguration(Optional<Retry> retry, RateLimiter rateLimiter) {
452466
ControllerConfiguration res = mock(ControllerConfiguration.class);
453467
when(res.getName()).thenReturn("Test");
454468
when(res.getRetry()).thenReturn(retry);

operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ private <R extends HasMetadata> ReconciliationDispatcher<R> init(R customResourc
110110
when(configuration.getFinalizerName()).thenReturn(DEFAULT_FINALIZER);
111111
when(configuration.getName()).thenReturn("EventDispatcherTestController");
112112
when(configuration.getResourceClass()).thenReturn(resourceClass);
113-
when(configuration.getRetry()).thenReturn(new GenericRetry());
113+
when(configuration.getRetry()).thenReturn(Optional.of(new GenericRetry()));
114114
when(configuration.maxReconciliationInterval())
115115
.thenReturn(Optional.of(Duration.ofHours(RECONCILIATION_MAX_INTERVAL)));
116116

0 commit comments

Comments
 (0)