Skip to content

Commit 01e2c0f

Browse files
committed
feat: record desired state in Context
Signed-off-by: Chris Laprun <[email protected]>
1 parent 0dee1bf commit 01e2c0f

File tree

8 files changed

+61
-30
lines changed

8 files changed

+61
-30
lines changed

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
package io.javaoperatorsdk.operator.api.reconciler;
22

3+
import java.util.Map;
34
import java.util.Optional;
45
import java.util.Set;
6+
import java.util.concurrent.ConcurrentHashMap;
57
import java.util.concurrent.ExecutorService;
8+
import java.util.function.Function;
69
import java.util.stream.Collectors;
710
import java.util.stream.Stream;
811

912
import io.fabric8.kubernetes.api.model.HasMetadata;
1013
import io.fabric8.kubernetes.client.KubernetesClient;
1114
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
15+
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
1216
import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DefaultManagedWorkflowAndDependentResourceContext;
1317
import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.ManagedWorkflowAndDependentResourceContext;
1418
import io.javaoperatorsdk.operator.processing.Controller;
@@ -24,6 +28,7 @@ public class DefaultContext<P extends HasMetadata> implements Context<P> {
2428
private final ControllerConfiguration<P> controllerConfiguration;
2529
private final DefaultManagedWorkflowAndDependentResourceContext<P>
2630
defaultManagedDependentResourceContext;
31+
private final Map<DependentResource<?, P>, Object> desiredStates = new ConcurrentHashMap<>();
2732

2833
public DefaultContext(RetryInfo retryInfo, Controller<P> controller, P primaryResource) {
2934
this.retryInfo = retryInfo;
@@ -123,4 +128,12 @@ public DefaultContext<P> setRetryInfo(RetryInfo retryInfo) {
123128
this.retryInfo = retryInfo;
124129
return this;
125130
}
131+
132+
@SuppressWarnings("unchecked")
133+
public <R> R desiredStateFor(
134+
DependentResource<R, P> dependentResource, Function<P, R> desiredStateComputer) {
135+
return (R)
136+
desiredStates.computeIfAbsent(
137+
dependentResource, ignored -> desiredStateComputer.apply(getPrimaryResource()));
138+
}
126139
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import io.fabric8.kubernetes.api.model.HasMetadata;
1010
import io.javaoperatorsdk.operator.api.reconciler.Context;
11+
import io.javaoperatorsdk.operator.api.reconciler.DefaultContext;
1112
import io.javaoperatorsdk.operator.api.reconciler.Ignore;
1213
import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter;
1314
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
@@ -70,7 +71,7 @@ protected ReconcileResult<R> reconcile(P primary, R actualResource, Context<P> c
7071
if (creatable() || updatable()) {
7172
if (actualResource == null) {
7273
if (creatable) {
73-
var desired = desired(primary, context);
74+
var desired = doDesired(context);
7475
throwIfNull(desired, primary, "Desired");
7576
logForOperation("Creating", primary, desired);
7677
var createdResource = handleCreate(desired, primary, context);
@@ -80,7 +81,7 @@ protected ReconcileResult<R> reconcile(P primary, R actualResource, Context<P> c
8081
if (updatable()) {
8182
final Matcher.Result<R> match = match(actualResource, primary, context);
8283
if (!match.matched()) {
83-
final var desired = match.computedDesired().orElseGet(() -> desired(primary, context));
84+
final var desired = match.computedDesired().orElseGet(() -> doDesired(context));
8485
throwIfNull(desired, primary, "Desired");
8586
logForOperation("Updating", primary, desired);
8687
var updatedResource = handleUpdate(actualResource, desired, primary, context);
@@ -112,7 +113,6 @@ protected ReconcileResult<R> reconcile(P primary, R actualResource, Context<P> c
112113

113114
@Override
114115
public Optional<R> getSecondaryResource(P primary, Context<P> context) {
115-
116116
var secondaryResources = context.getSecondaryResources(resourceType());
117117
if (secondaryResources.isEmpty()) {
118118
return Optional.empty();
@@ -136,7 +136,7 @@ public Optional<R> getSecondaryResource(P primary, Context<P> context) {
136136
*/
137137
protected Optional<R> selectTargetSecondaryResource(
138138
Set<R> secondaryResources, P primary, Context<P> context) {
139-
R desired = desired(primary, context);
139+
R desired = doDesired(context);
140140
var targetResources = secondaryResources.stream().filter(r -> r.equals(desired)).toList();
141141
if (targetResources.size() > 1) {
142142
throw new IllegalStateException(
@@ -205,6 +205,12 @@ protected R desired(P primary, Context<P> context) {
205205
+ " updated");
206206
}
207207

208+
protected R doDesired(Context<P> context) {
209+
assert context instanceof DefaultContext<P>;
210+
DefaultContext<P> defaultContext = (DefaultContext<P>) context;
211+
return defaultContext.desiredStateFor(this, p -> desired(p, defaultContext));
212+
}
213+
208214
public void delete(P primary, Context<P> context) {
209215
dependentResourceReconciler.delete(primary, context);
210216
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractExternalDependentResource.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ protected void handleExplicitStateCreation(P primary, R created, Context<P> cont
8383

8484
@Override
8585
public Matcher.Result<R> match(R resource, P primary, Context<P> context) {
86-
var desired = desired(primary, context);
86+
var desired = doDesired(context);
8787
return Matcher.Result.computed(resource.equals(desired), desired);
8888
}
8989

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResourceReconciler.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import io.javaoperatorsdk.operator.api.reconciler.Ignore;
1212
import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter;
1313
import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult;
14-
import io.javaoperatorsdk.operator.processing.dependent.Matcher.Result;
1514

1615
class BulkDependentResourceReconciler<R, P extends HasMetadata>
1716
implements DependentResourceReconciler<R, P> {

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ public static <R extends HasMetadata, P extends HasMetadata> Matcher.Result<R> m
123123
Context<P> context,
124124
boolean labelsAndAnnotationsEquality,
125125
String... ignorePaths) {
126-
final var desired = dependentResource.desired(primary, context);
126+
final var desired = dependentResource.doDesired(context);
127127
return match(desired, actualResource, labelsAndAnnotationsEquality, context, ignorePaths);
128128
}
129129

@@ -135,7 +135,7 @@ public static <R extends HasMetadata, P extends HasMetadata> Matcher.Result<R> m
135135
boolean specEquality,
136136
boolean labelsAndAnnotationsEquality,
137137
String... ignorePaths) {
138-
final var desired = dependentResource.desired(primary, context);
138+
final var desired = dependentResource.doDesired(context);
139139
return match(
140140
desired, actualResource, labelsAndAnnotationsEquality, specEquality, context, ignorePaths);
141141
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public R update(R actual, R desired, P primary, Context<P> context) {
109109

110110
@Override
111111
public Result<R> match(R actualResource, P primary, Context<P> context) {
112-
final var desired = desired(primary, context);
112+
final var desired = doDesired(context);
113113
return match(actualResource, desired, primary, context);
114114
}
115115

@@ -288,16 +288,16 @@ protected Optional<R> selectTargetSecondaryResource(
288288
* @return id of the target managed resource
289289
*/
290290
protected ResourceID targetSecondaryResourceID(P primary, Context<P> context) {
291-
return ResourceID.fromResource(desired(primary, context));
291+
return ResourceID.fromResource(doDesired(context));
292292
}
293293

294294
protected boolean addOwnerReference() {
295295
return garbageCollected;
296296
}
297297

298298
@Override
299-
protected R desired(P primary, Context<P> context) {
300-
return super.desired(primary, context);
299+
protected R doDesired(Context<P> context) {
300+
return super.doDesired(context);
301301
}
302302

303303
@Override

operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResourceTest.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import io.fabric8.kubernetes.api.model.ConfigMap;
88
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
99
import io.javaoperatorsdk.operator.api.reconciler.Context;
10+
import io.javaoperatorsdk.operator.api.reconciler.DefaultContext;
1011
import io.javaoperatorsdk.operator.sample.simple.TestCustomResource;
1112

1213
import static org.junit.jupiter.api.Assertions.*;
@@ -15,15 +16,18 @@
1516

1617
class AbstractDependentResourceTest {
1718

19+
private static final TestCustomResource PRIMARY = new TestCustomResource();
20+
private static final DefaultContext<TestCustomResource> CONTEXT =
21+
new DefaultContext<>(mock(), mock(), PRIMARY);
22+
1823
@Test
1924
void throwsExceptionIfDesiredIsNullOnCreate() {
2025
TestDependentResource testDependentResource = new TestDependentResource();
2126
testDependentResource.setSecondary(null);
2227
testDependentResource.setDesired(null);
2328

2429
assertThrows(
25-
DependentResourceException.class,
26-
() -> testDependentResource.reconcile(new TestCustomResource(), null));
30+
DependentResourceException.class, () -> testDependentResource.reconcile(PRIMARY, CONTEXT));
2731
}
2832

2933
@Test
@@ -33,8 +37,7 @@ void throwsExceptionIfDesiredIsNullOnUpdate() {
3337
testDependentResource.setDesired(null);
3438

3539
assertThrows(
36-
DependentResourceException.class,
37-
() -> testDependentResource.reconcile(new TestCustomResource(), null));
40+
DependentResourceException.class, () -> testDependentResource.reconcile(PRIMARY, CONTEXT));
3841
}
3942

4043
@Test
@@ -44,8 +47,7 @@ void throwsExceptionIfCreateReturnsNull() {
4447
testDependentResource.setDesired(configMap());
4548

4649
assertThrows(
47-
DependentResourceException.class,
48-
() -> testDependentResource.reconcile(new TestCustomResource(), null));
50+
DependentResourceException.class, () -> testDependentResource.reconcile(PRIMARY, CONTEXT));
4951
}
5052

5153
@Test
@@ -55,8 +57,7 @@ void throwsExceptionIfUpdateReturnsNull() {
5557
testDependentResource.setDesired(configMap());
5658

5759
assertThrows(
58-
DependentResourceException.class,
59-
() -> testDependentResource.reconcile(new TestCustomResource(), null));
60+
DependentResourceException.class, () -> testDependentResource.reconcile(PRIMARY, CONTEXT));
6061
}
6162

6263
private ConfigMap configMap() {

operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcherTest.java

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,48 @@
33
import java.util.Map;
44
import java.util.Optional;
55

6-
import org.junit.jupiter.api.BeforeAll;
76
import org.junit.jupiter.api.Test;
87

98
import io.fabric8.kubernetes.api.model.*;
109
import io.fabric8.kubernetes.api.model.apps.Deployment;
1110
import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder;
1211
import io.fabric8.kubernetes.api.model.apps.DeploymentStatusBuilder;
12+
import io.fabric8.kubernetes.client.KubernetesClient;
1313
import io.javaoperatorsdk.operator.MockKubernetesClient;
1414
import io.javaoperatorsdk.operator.ReconcilerUtils;
1515
import io.javaoperatorsdk.operator.api.reconciler.Context;
16+
import io.javaoperatorsdk.operator.api.reconciler.DefaultContext;
1617

1718
import static io.javaoperatorsdk.operator.processing.dependent.kubernetes.GenericKubernetesResourceMatcher.match;
1819
import static org.assertj.core.api.Assertions.assertThat;
1920
import static org.mockito.Mockito.mock;
20-
import static org.mockito.Mockito.when;
2121

2222
@SuppressWarnings({"unchecked"})
2323
class GenericKubernetesResourceMatcherTest {
2424

25-
private static final Context context = mock(Context.class);
25+
private static final Context context = new TestContext();
26+
27+
private static class TestContext extends DefaultContext<HasMetadata> {
28+
private final KubernetesClient client = MockKubernetesClient.client(HasMetadata.class);
29+
30+
public TestContext() {
31+
this(null);
32+
}
33+
34+
public TestContext(HasMetadata primary) {
35+
super(mock(), mock(), primary);
36+
}
37+
38+
@Override
39+
public KubernetesClient getClient() {
40+
return client;
41+
}
42+
}
2643

2744
Deployment actual = createDeployment();
2845
Deployment desired = createDeployment();
2946
TestDependentResource dependentResource = new TestDependentResource(desired);
3047

31-
@BeforeAll
32-
static void setUp() {
33-
final var client = MockKubernetesClient.client(HasMetadata.class);
34-
when(context.getClient()).thenReturn(client);
35-
}
36-
3748
@Test
3849
void matchesTrivialCases() {
3950
assertThat(GenericKubernetesResourceMatcher.match(desired, actual, context).matched()).isTrue();
@@ -62,9 +73,10 @@ void matchesWithStrongSpecEquality() {
6273
@Test
6374
void doesNotMatchRemovedValues() {
6475
actual = createDeployment();
76+
final var localContext = new TestContext(createPrimary("removed"));
6577
assertThat(
6678
GenericKubernetesResourceMatcher.match(
67-
dependentResource.desired(createPrimary("removed"), null), actual, context)
79+
dependentResource.doDesired(localContext), actual, localContext)
6880
.matched())
6981
.withFailMessage("Removing values in metadata should lead to a mismatch")
7082
.isFalse();

0 commit comments

Comments
 (0)