Skip to content

Commit 3b99f78

Browse files
committed
Integration tests
Signed-off-by: Attila Mészáros <[email protected]>
1 parent 00fd9e6 commit 3b99f78

13 files changed

+561
-21
lines changed

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

+123-5
Original file line numberDiff line numberDiff line change
@@ -13,30 +13,67 @@
1313
import io.javaoperatorsdk.operator.api.reconciler.support.PrimaryResourceCache;
1414
import io.javaoperatorsdk.operator.processing.event.ResourceID;
1515

16-
// todo javadoc
1716
public class PrimaryUpdateAndCacheUtils {
1817

1918
private PrimaryUpdateAndCacheUtils() {}
2019

2120
private static final Logger log = LoggerFactory.getLogger(PrimaryUpdateAndCacheUtils.class);
2221

23-
public static <P extends HasMetadata> P updateAndCacheStatus(P primary, Context<P> context) {
22+
/**
23+
* Makes sure that the up-to-date primary resource will be present during the next reconciliation.
24+
* Using update (PUT) method.
25+
*
26+
* @param primary resource
27+
* @param context of reconciliation
28+
* @return updated resource
29+
* @param <P> primary resource type
30+
*/
31+
public static <P extends HasMetadata> P updateAndCacheStatusWithLock(
32+
P primary, Context<P> context) {
2433
return patchAndCacheStatusWithLock(
2534
primary, context, (p, c) -> c.resource(primary).updateStatus());
2635
}
2736

37+
/**
38+
* Makes sure that the up-to-date primary resource will be present during the next reconciliation.
39+
* Using JSON Merge patch.
40+
*
41+
* @param primary resource
42+
* @param context of reconciliation
43+
* @return updated resource
44+
* @param <P> primary resource type
45+
*/
2846
public static <P extends HasMetadata> P patchAndCacheStatusWithLock(
2947
P primary, Context<P> context) {
3048
return patchAndCacheStatusWithLock(
3149
primary, context, (p, c) -> c.resource(primary).patchStatus());
3250
}
3351

52+
/**
53+
* Makes sure that the up-to-date primary resource will be present during the next reconciliation.
54+
* Using JSON Patch.
55+
*
56+
* @param primary resource
57+
* @param context of reconciliation
58+
* @return updated resource
59+
* @param <P> primary resource type
60+
*/
3461
public static <P extends HasMetadata> P editAndCacheStatusWithLock(
3562
P primary, Context<P> context, UnaryOperator<P> operation) {
3663
return patchAndCacheStatusWithLock(
3764
primary, context, (p, c) -> c.resource(primary).editStatus(operation));
3865
}
3966

67+
/**
68+
* Makes sure that the up-to-date primary resource will be present during the next reconciliation.
69+
*
70+
* @param primary resource
71+
* @param context of reconciliation
72+
* @param patch free implementation of cache - make sure you use optimistic locking during the
73+
* update
74+
* @return the updated resource.
75+
* @param <P> primary resource type
76+
*/
4077
public static <P extends HasMetadata> P patchAndCacheStatusWithLock(
4178
P primary, Context<P> context, BiFunction<P, KubernetesClient, P> patch) {
4279
checkResourceVersionPresent(primary);
@@ -48,6 +85,16 @@ public static <P extends HasMetadata> P patchAndCacheStatusWithLock(
4885
return null;
4986
}
5087

88+
/**
89+
* Makes sure that the up-to-date primary resource will be present during the next reconciliation.
90+
* Using Server Side Apply.
91+
*
92+
* @param primary resource
93+
* @param freshResourceWithStatus - fresh resource with target state
94+
* @param context of reconciliation
95+
* @return the updated resource.
96+
* @param <P> primary resource type
97+
*/
5198
public static <P extends HasMetadata> P ssaPatchAndCacheStatusWithLock(
5299
P primary, P freshResourceWithStatus, Context<P> context) {
53100
checkResourceVersionPresent(freshResourceWithStatus);
@@ -70,15 +117,26 @@ public static <P extends HasMetadata> P ssaPatchAndCacheStatusWithLock(
70117
return res;
71118
}
72119

120+
/**
121+
* Patches the resource and adds it to the {@link PrimaryResourceCache} provided. Optimistic
122+
* locking is not required.
123+
*
124+
* @param primary resource
125+
* @param freshResourceWithStatus - fresh resource with target state
126+
* @param context of reconciliation
127+
* @param cache - resource cache managed by user
128+
* @return the updated resource.
129+
* @param <P> primary resource type
130+
*/
73131
public static <P extends HasMetadata> P ssaPatchAndCacheStatus(
74-
P primary, P freshResource, Context<P> context, PrimaryResourceCache<P> cache) {
75-
logWarnIfResourceVersionPresent(freshResource);
132+
P primary, P freshResourceWithStatus, Context<P> context, PrimaryResourceCache<P> cache) {
133+
logWarnIfResourceVersionPresent(freshResourceWithStatus);
76134
return patchAndCacheStatus(
77135
primary,
78136
context.getClient(),
79137
cache,
80138
(P p, KubernetesClient c) ->
81-
c.resource(freshResource)
139+
c.resource(freshResourceWithStatus)
82140
.subresource("status")
83141
.patch(
84142
new PatchContext.Builder()
@@ -88,6 +146,66 @@ public static <P extends HasMetadata> P ssaPatchAndCacheStatus(
88146
.build()));
89147
}
90148

149+
/**
150+
* Patches the resource with JSON Patch and adds it to the {@link PrimaryResourceCache} provided.
151+
* Optimistic locking is not required.
152+
*
153+
* @param primary resource*
154+
* @param context of reconciliation
155+
* @param cache - resource cache managed by user
156+
* @return the updated resource.
157+
* @param <P> primary resource type
158+
*/
159+
public static <P extends HasMetadata> P edithAndCacheStatus(
160+
P primary, Context<P> context, PrimaryResourceCache<P> cache, UnaryOperator<P> operation) {
161+
logWarnIfResourceVersionPresent(primary);
162+
return patchAndCacheStatus(
163+
primary,
164+
context.getClient(),
165+
cache,
166+
(P p, KubernetesClient c) -> c.resource(primary).editStatus(operation));
167+
}
168+
169+
/**
170+
* Patches the resource with JSON Merge patch and adds it to the {@link PrimaryResourceCache}
171+
* provided. Optimistic locking is not required.
172+
*
173+
* @param primary resource*
174+
* @param context of reconciliation
175+
* @param cache - resource cache managed by user
176+
* @return the updated resource.
177+
* @param <P> primary resource type
178+
*/
179+
public static <P extends HasMetadata> P patchAndCacheStatus(
180+
P primary, Context<P> context, PrimaryResourceCache<P> cache) {
181+
logWarnIfResourceVersionPresent(primary);
182+
return patchAndCacheStatus(
183+
primary,
184+
context.getClient(),
185+
cache,
186+
(P p, KubernetesClient c) -> c.resource(primary).patchStatus());
187+
}
188+
189+
/**
190+
* Updates the resource and adds it to the {@link PrimaryResourceCache} provided. Optimistic
191+
* locking is not required.
192+
*
193+
* @param primary resource*
194+
* @param context of reconciliation
195+
* @param cache - resource cache managed by user
196+
* @return the updated resource.
197+
* @param <P> primary resource type
198+
*/
199+
public static <P extends HasMetadata> P updateAndCacheStatus(
200+
P primary, Context<P> context, PrimaryResourceCache<P> cache) {
201+
logWarnIfResourceVersionPresent(primary);
202+
return patchAndCacheStatus(
203+
primary,
204+
context.getClient(),
205+
cache,
206+
(P p, KubernetesClient c) -> c.resource(primary).updateStatus());
207+
}
208+
91209
public static <P extends HasMetadata> P patchAndCacheStatus(
92210
P primary,
93211
KubernetesClient client,

operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/reconciler/support/PrimaryResourceCacheTest.java

+39-16
Original file line numberDiff line numberDiff line change
@@ -10,30 +10,53 @@
1010

1111
class PrimaryResourceCacheTest {
1212

13+
PrimaryResourceCache<TestCustomResource> versionParsingCache =
14+
new PrimaryResourceCache<>(
15+
new PrimaryResourceCache.ResourceVersionParsingEvictionPredicate<>());
16+
1317
@Test
14-
void flowWithResourceVersionParsingEvictionPredicate() {
15-
var cache =
16-
new PrimaryResourceCache<TestCustomResource>(
17-
new PrimaryResourceCache.ResourceVersionParsingEvictionPredicate<>());
18+
void returnsThePassedValueIfCacheIsEmpty() {
19+
var cr = customResource("1");
20+
21+
var res = versionParsingCache.getFreshResource(cr);
1822

19-
var newCR = customResource("2");
20-
var cr = cache.getFreshResource(newCR);
21-
assertThat(cr).isSameAs(newCR);
22-
// todo break these down by spec
23-
cache.cacheResource(newCR);
24-
cr = cache.getFreshResource(customResource("1"));
23+
assertThat(cr).isSameAs(res);
24+
}
2525

26-
assertThat(cr).isSameAs(newCR);
26+
@Test
27+
void returnsTheCachedIfNotEvictedAccordingToPredicate() {
28+
var cr = customResource("2");
2729

28-
var newestCR = customResource("3");
29-
cr = cache.getFreshResource(newestCR);
30+
versionParsingCache.cacheResource(cr);
3031

31-
assertThat(cr).isSameAs(newestCR);
32+
var res = versionParsingCache.getFreshResource(customResource("1"));
33+
assertThat(cr).isSameAs(res);
3234
}
3335

3436
@Test
35-
void customResourceSpecificEvictionPredicate() {
36-
// todo
37+
void ifMoreFreshPassedCachedIsEvicted() {
38+
var cr = customResource("2");
39+
versionParsingCache.cacheResource(cr);
40+
var newCR = customResource("3");
41+
42+
var res = versionParsingCache.getFreshResource(newCR);
43+
var resOnOlder = versionParsingCache.getFreshResource(cr);
44+
45+
assertThat(newCR).isSameAs(res);
46+
assertThat(resOnOlder).isSameAs(cr);
47+
assertThat(newCR).isNotSameAs(cr);
48+
}
49+
50+
@Test
51+
void cleanupRemovesCachedResources() {
52+
var cr = customResource("2");
53+
versionParsingCache.cacheResource(cr);
54+
55+
versionParsingCache.cleanup(customResource("3"));
56+
57+
var olderCR = customResource("1");
58+
var res = versionParsingCache.getFreshResource(olderCR);
59+
assertThat(olderCR).isSameAs(res);
3760
}
3861

3962
private TestCustomResource customResource(String resourceVersion) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package io.javaoperatorsdk.operator.baseapi.statuscache;
2+
3+
import java.util.Set;
4+
import java.util.Timer;
5+
import java.util.TimerTask;
6+
7+
import io.fabric8.kubernetes.api.model.HasMetadata;
8+
import io.javaoperatorsdk.operator.OperatorException;
9+
import io.javaoperatorsdk.operator.processing.event.Event;
10+
import io.javaoperatorsdk.operator.processing.event.ResourceID;
11+
import io.javaoperatorsdk.operator.processing.event.source.AbstractEventSource;
12+
import io.javaoperatorsdk.operator.processing.event.source.IndexerResourceCache;
13+
14+
public class PeriodicTriggerEventSource<P extends HasMetadata>
15+
extends AbstractEventSource<Void, P> {
16+
17+
public static final int DEFAULT_PERIOD = 30;
18+
private final Timer timer = new Timer();
19+
private final IndexerResourceCache<P> primaryCache;
20+
private final int period;
21+
22+
public PeriodicTriggerEventSource(IndexerResourceCache<P> primaryCache) {
23+
this(primaryCache, DEFAULT_PERIOD);
24+
}
25+
26+
public PeriodicTriggerEventSource(IndexerResourceCache<P> primaryCache, int period) {
27+
super(Void.class);
28+
this.primaryCache = primaryCache;
29+
this.period = period;
30+
}
31+
32+
@Override
33+
public Set<Void> getSecondaryResources(P primary) {
34+
return Set.of();
35+
}
36+
37+
@Override
38+
public void start() throws OperatorException {
39+
super.start();
40+
timer.schedule(
41+
new TimerTask() {
42+
@Override
43+
public void run() {
44+
primaryCache
45+
.list()
46+
.forEach(
47+
r -> {
48+
getEventHandler().handleEvent(new Event(ResourceID.fromResource(r)));
49+
});
50+
}
51+
},
52+
0,
53+
period);
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package io.javaoperatorsdk.operator.baseapi.statuscache.primarycache;
2+
3+
import io.fabric8.kubernetes.api.model.Namespaced;
4+
import io.fabric8.kubernetes.client.CustomResource;
5+
import io.fabric8.kubernetes.model.annotation.Group;
6+
import io.fabric8.kubernetes.model.annotation.ShortNames;
7+
import io.fabric8.kubernetes.model.annotation.Version;
8+
9+
@Group("sample.javaoperatorsdk")
10+
@Version("v1")
11+
@ShortNames("spc")
12+
public class StatusPatchPrimaryCacheCustomResource
13+
extends CustomResource<StatusPatchPrimaryCacheSpec, StatusPatchPrimaryCacheStatus>
14+
implements Namespaced {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package io.javaoperatorsdk.operator.baseapi.statuscache.primarycache;
2+
3+
import java.time.Duration;
4+
5+
import org.junit.jupiter.api.Test;
6+
import org.junit.jupiter.api.extension.RegisterExtension;
7+
8+
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
9+
import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension;
10+
11+
import static org.assertj.core.api.Assertions.assertThat;
12+
import static org.awaitility.Awaitility.await;
13+
14+
public class StatusPatchPrimaryCacheIT {
15+
16+
public static final String TEST_1 = "test1";
17+
18+
@RegisterExtension
19+
LocallyRunOperatorExtension extension =
20+
LocallyRunOperatorExtension.builder()
21+
.withReconciler(StatusPatchPrimaryCacheReconciler.class)
22+
.build();
23+
24+
@Test
25+
void testStatusAlwaysUpToDate() {
26+
var reconciler = extension.getReconcilerOfType(StatusPatchPrimaryCacheReconciler.class);
27+
28+
extension.create(testResource());
29+
30+
// the reconciliation id periodically triggered, the status values should be increasing
31+
// monotonically
32+
await()
33+
.pollDelay(Duration.ofSeconds(1))
34+
.pollInterval(Duration.ofMillis(30))
35+
.untilAsserted(
36+
() -> {
37+
assertThat(reconciler.errorPresent).isFalse();
38+
assertThat(reconciler.latestValue).isGreaterThan(10);
39+
});
40+
}
41+
42+
StatusPatchPrimaryCacheCustomResource testResource() {
43+
var res = new StatusPatchPrimaryCacheCustomResource();
44+
res.setMetadata(new ObjectMetaBuilder().withName(TEST_1).build());
45+
res.setSpec(new StatusPatchPrimaryCacheSpec());
46+
return res;
47+
}
48+
}

0 commit comments

Comments
 (0)