Skip to content

Commit 57bd0c3

Browse files
committed
refactor: allow optional retry
1 parent 82345d2 commit 57bd0c3

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

operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.util.HashMap;
77
import java.util.List;
88
import java.util.Map;
9+
import java.util.Optional;
910
import java.util.function.Consumer;
1011
import java.util.stream.Collectors;
1112
import java.util.stream.Stream;
@@ -136,7 +137,7 @@ protected void before(ExtensionContext context) {
136137
}
137138

138139
if (ref.retry != null) {
139-
oconfig.withRetry(ref.retry);
140+
ref.retry.ifPresentOrElse(r -> oconfig.withRetry(r), () -> oconfig.withoutRetry());
140141
}
141142
if (ref.controllerConfigurationOverrider != null) {
142143
ref.controllerConfigurationOverrider.accept(oconfig);
@@ -197,7 +198,6 @@ protected void after(ExtensionContext context) {
197198
localPortForwards.clear();
198199
}
199200

200-
@SuppressWarnings("rawtypes")
201201
public static class Builder extends AbstractBuilder<Builder> {
202202
private final List<ReconcilerSpec> reconcilers;
203203
private final List<PortForwardSpec> portForwards;
@@ -213,30 +213,37 @@ protected Builder() {
213213

214214
public Builder withReconciler(
215215
Reconciler value, Consumer<ControllerConfigurationOverrider> configurationOverrider) {
216-
return withReconciler(value, null, configurationOverrider);
216+
return withReconciler(value, (Retry) null, configurationOverrider);
217217
}
218218

219219
public Builder withReconciler(
220220
Reconciler value,
221221
Retry retry,
222222
Consumer<ControllerConfigurationOverrider> configurationOverrider) {
223+
return withReconciler(value, Optional.ofNullable(null), configurationOverrider);
224+
}
225+
226+
public Builder withReconciler(
227+
Reconciler value,
228+
Optional<Retry> retry,
229+
Consumer<ControllerConfigurationOverrider> configurationOverrider) {
223230
reconcilers.add(new ReconcilerSpec(value, retry, configurationOverrider));
224231
return this;
225232
}
226233

227-
@SuppressWarnings("rawtypes")
228234
public Builder withReconciler(Reconciler value) {
229-
reconcilers.add(new ReconcilerSpec(value, null));
230-
return this;
235+
return withReconciler(value, (Retry) null);
231236
}
232237

233-
@SuppressWarnings("rawtypes")
234238
public Builder withReconciler(Reconciler value, Retry retry) {
239+
return withReconciler(value, Optional.ofNullable(retry));
240+
}
241+
242+
public Builder withReconciler(Reconciler value, Optional<Retry> retry) {
235243
reconcilers.add(new ReconcilerSpec(value, retry));
236244
return this;
237245
}
238246

239-
@SuppressWarnings("rawtypes")
240247
public Builder withReconciler(Class<? extends Reconciler> value) {
241248
try {
242249
reconcilers.add(new ReconcilerSpec(value.getConstructor().newInstance(), null));
@@ -318,16 +325,16 @@ public int getLocalPort() {
318325
@SuppressWarnings("rawtypes")
319326
private static class ReconcilerSpec {
320327
final Reconciler reconciler;
321-
final Retry retry;
328+
final Optional<Retry> retry;
322329
final Consumer<ControllerConfigurationOverrider> controllerConfigurationOverrider;
323330

324-
public ReconcilerSpec(Reconciler reconciler, Retry retry) {
331+
public ReconcilerSpec(Reconciler reconciler, Optional<Retry> retry) {
325332
this(reconciler, retry, null);
326333
}
327334

328335
public ReconcilerSpec(
329336
Reconciler reconciler,
330-
Retry retry,
337+
Optional<Retry> retry,
331338
Consumer<ControllerConfigurationOverrider> controllerConfigurationOverrider) {
332339
this.reconciler = reconciler;
333340
this.retry = retry;

operator-framework/src/test/java/io/javaoperatorsdk/operator/ErrorStatusHandlerLastAttemptIT.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package io.javaoperatorsdk.operator;
22

3+
import java.util.Optional;
34
import java.util.concurrent.TimeUnit;
45

56
import org.junit.jupiter.api.Test;
67
import org.junit.jupiter.api.extension.RegisterExtension;
78

89
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
910
import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension;
10-
import io.javaoperatorsdk.operator.processing.retry.GenericRetry;
1111
import io.javaoperatorsdk.operator.sample.errorstatushandler.ErrorStatusHandlerTestCustomResource;
1212
import io.javaoperatorsdk.operator.sample.errorstatushandler.ErrorStatusHandlerTestReconciler;
1313

@@ -22,14 +22,13 @@ class ErrorStatusHandlerLastAttemptIT {
2222
@RegisterExtension
2323
LocallyRunOperatorExtension operator =
2424
LocallyRunOperatorExtension.builder()
25-
.withReconciler(reconciler,
26-
new GenericRetry().setMaxAttempts(MAX_RETRY_ATTEMPTS).withLinearRetry())
25+
.withReconciler(reconciler, Optional.empty())
2726
.build();
2827

2928
@Test
3029
void testErrorMessageSetEventually() {
3130
ErrorStatusHandlerTestCustomResource resource =
32-
operator.create(ErrorStatusHandlerTestCustomResource.class, createCustomResource());
31+
operator.create(createCustomResource());
3332

3433
await()
3534
.atMost(10, TimeUnit.SECONDS)

0 commit comments

Comments
 (0)