Skip to content

Commit 9f27ac9

Browse files
committed
unit tests
Signed-off-by: Attila Mészáros <[email protected]>
1 parent 9a2d5a3 commit 9f27ac9

File tree

2 files changed

+115
-3
lines changed

2 files changed

+115
-3
lines changed

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
* updates is to store state is resource status.
1919
*
2020
* <p>The way the framework handles this is with retryable updates with optimistic locking, and
21-
* caches the updated resource from the response in an overlay cache on top of the Informer behind.
21+
* caches the updated resource from the response in an overlay cache on top of the Informer cache.
2222
* If the update fails, it reads the primary resource and applies the modifications again and
2323
* retries the update.
2424
*/
@@ -168,13 +168,14 @@ public static <P extends HasMetadata> P updateAndCacheResource(
168168
if (e.getCode() != 409 && e.getCode() != 422) {
169169
throw e;
170170
}
171-
if (retryIndex >= maxRetry) {
171+
if (retryIndex > maxRetry) {
172172
log.warn("Retry exhausted, last desired resource: {}", modified);
173173
throw new OperatorException(
174174
"Exceeded maximum ("
175175
+ maxRetry
176176
+ ") retry attempts to patch resource: "
177-
+ ResourceID.fromResource(primary));
177+
+ ResourceID.fromResource(primary),
178+
e);
178179
}
179180
log.debug(
180181
"Retrying patch for resource name: {}, namespace: {}; HTTP code: {}",
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package io.javaoperatorsdk.operator.api.reconciler;
2+
3+
import java.util.function.UnaryOperator;
4+
5+
import org.junit.jupiter.api.BeforeEach;
6+
import org.junit.jupiter.api.Test;
7+
8+
import io.fabric8.kubernetes.client.KubernetesClient;
9+
import io.fabric8.kubernetes.client.KubernetesClientException;
10+
import io.fabric8.kubernetes.client.dsl.MixedOperation;
11+
import io.fabric8.kubernetes.client.dsl.Resource;
12+
import io.javaoperatorsdk.operator.OperatorException;
13+
import io.javaoperatorsdk.operator.TestUtils;
14+
import io.javaoperatorsdk.operator.processing.event.EventSourceRetriever;
15+
import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerEventSource;
16+
import io.javaoperatorsdk.operator.sample.simple.TestCustomResource;
17+
18+
import static io.javaoperatorsdk.operator.api.reconciler.PrimaryUpdateAndCacheUtils.DEFAULT_MAX_RETRY;
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
import static org.junit.jupiter.api.Assertions.assertThrows;
21+
import static org.mockito.ArgumentMatchers.any;
22+
import static org.mockito.Mockito.mock;
23+
import static org.mockito.Mockito.times;
24+
import static org.mockito.Mockito.verify;
25+
import static org.mockito.Mockito.when;
26+
27+
class PrimaryUpdateAndCacheUtilsTest {
28+
29+
Context<TestCustomResource> context = mock(Context.class);
30+
KubernetesClient client = mock(KubernetesClient.class);
31+
Resource resource = mock(Resource.class);
32+
33+
@BeforeEach
34+
void setup() {
35+
when(context.getClient()).thenReturn(client);
36+
var esr = mock(EventSourceRetriever.class);
37+
when(context.eventSourceRetriever()).thenReturn(esr);
38+
when(esr.getControllerEventSource()).thenReturn(mock(ControllerEventSource.class));
39+
var mixedOp = mock(MixedOperation.class);
40+
when(client.resources(any())).thenReturn(mixedOp);
41+
when(mixedOp.inNamespace(any())).thenReturn(mixedOp);
42+
when(mixedOp.withName(any())).thenReturn(resource);
43+
when(resource.get()).thenReturn(TestUtils.testCustomResource1());
44+
}
45+
46+
@Test
47+
void handlesUpdate() {
48+
var updated =
49+
PrimaryUpdateAndCacheUtils.updateAndCacheResource(
50+
TestUtils.testCustomResource1(),
51+
context,
52+
r -> {
53+
var res = TestUtils.testCustomResource1();
54+
// setting this to null to test if value set in the implementation
55+
res.getMetadata().setResourceVersion(null);
56+
res.getSpec().setValue("updatedValue");
57+
return res;
58+
},
59+
r -> {
60+
// checks if the resource version is set from the original resource
61+
assertThat(r.getMetadata().getResourceVersion()).isEqualTo("1");
62+
var res = TestUtils.testCustomResource1();
63+
res.setSpec(r.getSpec());
64+
res.getMetadata().setResourceVersion("2");
65+
return res;
66+
});
67+
68+
assertThat(updated.getMetadata().getResourceVersion()).isEqualTo("2");
69+
assertThat(updated.getSpec().getValue()).isEqualTo("updatedValue");
70+
}
71+
72+
@Test
73+
void retriesConflicts() {
74+
var updateOperation = mock(UnaryOperator.class);
75+
76+
when(updateOperation.apply(any()))
77+
.thenThrow(new KubernetesClientException("", 409, null))
78+
.thenReturn(TestUtils.testCustomResource1());
79+
80+
var updated =
81+
PrimaryUpdateAndCacheUtils.updateAndCacheResource(
82+
TestUtils.testCustomResource1(),
83+
context,
84+
r -> {
85+
var res = TestUtils.testCustomResource1();
86+
res.getSpec().setValue("updatedValue");
87+
return res;
88+
},
89+
updateOperation);
90+
91+
assertThat(updated).isNotNull();
92+
verify(resource, times(1)).get();
93+
}
94+
95+
@Test
96+
void throwsIfRetryExhausted() {
97+
var updateOperation = mock(UnaryOperator.class);
98+
99+
when(updateOperation.apply(any())).thenThrow(new KubernetesClientException("", 409, null));
100+
101+
assertThrows(
102+
OperatorException.class,
103+
() ->
104+
PrimaryUpdateAndCacheUtils.updateAndCacheResource(
105+
TestUtils.testCustomResource1(),
106+
context,
107+
UnaryOperator.identity(),
108+
updateOperation));
109+
verify(resource, times(DEFAULT_MAX_RETRY)).get();
110+
}
111+
}

0 commit comments

Comments
 (0)