Skip to content

Commit f9f7b52

Browse files
authored
Persistent state dependent resources (#1543)
1 parent 371c347 commit f9f7b52

19 files changed

+749
-31
lines changed

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

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -110,38 +110,38 @@ private void logForOperation(String operation, P primary, R desired) {
110110
}
111111

112112
protected R handleCreate(R desired, P primary, Context<P> context) {
113-
ResourceID resourceID = ResourceID.fromResource(primary);
114113
R created = creator.create(desired, primary, context);
115114
throwIfNull(created, primary, "Created resource");
116-
onCreated(resourceID, created);
115+
onCreated(primary, created, context);
117116
return created;
118117
}
119118

120119
/**
121120
* Allows subclasses to perform additional processing (e.g. caching) on the created resource if
122121
* needed.
123122
*
124-
* @param primaryResourceId the {@link ResourceID} of the primary resource associated with the
125-
* newly created resource
123+
* @param primary the {@link ResourceID} of the primary resource associated with the newly created
124+
* resource
126125
* @param created the newly created resource
126+
* @param context
127127
*/
128-
protected abstract void onCreated(ResourceID primaryResourceId, R created);
128+
protected abstract void onCreated(P primary, R created, Context<P> context);
129129

130130
/**
131131
* Allows subclasses to perform additional processing on the updated resource if needed.
132132
*
133-
* @param primaryResourceId the {@link ResourceID} of the primary resource associated with the
134-
* newly updated resource
133+
* @param primary the {@link ResourceID} of the primary resource associated with the newly updated
134+
* resource
135135
* @param updated the updated resource
136136
* @param actual the resource as it was before the update
137+
* @param context
137138
*/
138-
protected abstract void onUpdated(ResourceID primaryResourceId, R updated, R actual);
139+
protected abstract void onUpdated(P primary, R updated, R actual, Context<P> context);
139140

140141
protected R handleUpdate(R actual, R desired, P primary, Context<P> context) {
141-
ResourceID resourceID = ResourceID.fromResource(primary);
142142
R updated = updater.update(actual, desired, primary, context);
143143
throwIfNull(updated, primary, "Updated resource");
144-
onUpdated(resourceID, updated, actual);
144+
onUpdated(primary, updated, actual, context);
145145
return updated;
146146
}
147147

@@ -154,8 +154,9 @@ public void delete(P primary, Context<P> context) {
154154
dependentResourceReconciler.delete(primary, context);
155155
}
156156

157-
protected void handleDelete(P primary, Context<P> context) {
158-
throw new IllegalStateException("delete method be implemented if Deleter trait is supported");
157+
protected void handleDelete(P primary, R secondary, Context<P> context) {
158+
throw new IllegalStateException(
159+
"handleDelete method must be implemented if Deleter trait is supported");
159160
}
160161

161162
public void setResourceDiscriminator(

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.util.Optional;
44

55
import io.fabric8.kubernetes.api.model.HasMetadata;
6+
import io.javaoperatorsdk.operator.api.reconciler.Context;
67
import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext;
78
import io.javaoperatorsdk.operator.api.reconciler.Ignore;
89
import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceNotFoundException;
@@ -94,15 +95,17 @@ public Optional<ResourceEventSource<R, P>> eventSource() {
9495
return Optional.ofNullable(eventSource);
9596
}
9697

97-
protected void onCreated(ResourceID primaryResourceId, R created) {
98+
protected void onCreated(P primary, R created, Context<P> context) {
9899
if (isCacheFillerEventSource) {
99-
recentOperationCacheFiller().handleRecentResourceCreate(primaryResourceId, created);
100+
recentOperationCacheFiller().handleRecentResourceCreate(ResourceID.fromResource(primary),
101+
created);
100102
}
101103
}
102104

103-
protected void onUpdated(ResourceID primaryResourceId, R updated, R actual) {
105+
protected void onUpdated(P primary, R updated, R actual, Context<P> context) {
104106
if (isCacheFillerEventSource) {
105-
recentOperationCacheFiller().handleRecentResourceUpdate(primaryResourceId, updated, actual);
107+
recentOperationCacheFiller().handleRecentResourceUpdate(ResourceID.fromResource(primary),
108+
updated, actual);
106109
}
107110
}
108111

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package io.javaoperatorsdk.operator.processing.dependent;
2+
3+
import io.fabric8.kubernetes.api.model.HasMetadata;
4+
import io.fabric8.kubernetes.client.KubernetesClient;
5+
import io.javaoperatorsdk.operator.api.reconciler.Context;
6+
import io.javaoperatorsdk.operator.api.reconciler.dependent.RecentOperationCacheFiller;
7+
import io.javaoperatorsdk.operator.processing.event.EventSourceRetriever;
8+
import io.javaoperatorsdk.operator.processing.event.ResourceID;
9+
import io.javaoperatorsdk.operator.processing.event.source.ResourceEventSource;
10+
import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource;
11+
12+
public abstract class AbstractExternalDependentResource<R, P extends HasMetadata, T extends ResourceEventSource<R, P>>
13+
extends AbstractEventSourceHolderDependentResource<R, P, T> {
14+
15+
private final boolean isDependentResourceWithExplicitState =
16+
this instanceof DependentResourceWithExplicitState;
17+
private final boolean isBulkDependentResource = this instanceof BulkDependentResource;
18+
@SuppressWarnings("rawtypes")
19+
private DependentResourceWithExplicitState dependentResourceWithExplicitState;
20+
private InformerEventSource<?, P> externalStateEventSource;
21+
private KubernetesClient kubernetesClient;
22+
23+
@SuppressWarnings("unchecked")
24+
protected AbstractExternalDependentResource(Class<R> resourceType) {
25+
super(resourceType);
26+
if (isDependentResourceWithExplicitState) {
27+
dependentResourceWithExplicitState = (DependentResourceWithExplicitState<R, P, ?>) this;
28+
}
29+
}
30+
31+
@Override
32+
@SuppressWarnings("unchecked")
33+
public void resolveEventSource(EventSourceRetriever<P> eventSourceRetriever) {
34+
super.resolveEventSource(eventSourceRetriever);
35+
if (isDependentResourceWithExplicitState) {
36+
externalStateEventSource = (InformerEventSource<?, P>) dependentResourceWithExplicitState
37+
.eventSourceName()
38+
.map(n -> eventSourceRetriever
39+
.getResourceEventSourceFor(dependentResourceWithExplicitState.stateResourceClass(),
40+
(String) n))
41+
.orElseGet(() -> eventSourceRetriever
42+
.getResourceEventSourceFor(
43+
(Class<R>) dependentResourceWithExplicitState.stateResourceClass()));
44+
}
45+
46+
}
47+
48+
@Override
49+
protected void onCreated(P primary, R created, Context<P> context) {
50+
super.onCreated(primary, created, context);
51+
if (this instanceof DependentResourceWithExplicitState) {
52+
handleExplicitStateCreation(primary, created, context);
53+
}
54+
}
55+
56+
@Override
57+
public void delete(P primary, Context<P> context) {
58+
if (isDependentResourceWithExplicitState && !isBulkDependentResource) {
59+
var secondary = getSecondaryResource(primary, context);
60+
super.delete(primary, context);
61+
// deletes the state after the resource is deleted
62+
handleExplicitStateDelete(primary, secondary.orElse(null), context);
63+
} else {
64+
super.delete(primary, context);
65+
}
66+
}
67+
68+
@SuppressWarnings("unchecked")
69+
private void handleExplicitStateDelete(P primary, R secondary, Context<P> context) {
70+
var res = dependentResourceWithExplicitState.stateResource(primary, secondary);
71+
dependentResourceWithExplicitState.getKubernetesClient().resource(res).delete();
72+
}
73+
74+
@SuppressWarnings({"rawtypes", "unchecked"})
75+
protected void handleExplicitStateCreation(P primary, R created, Context<P> context) {
76+
var resource = dependentResourceWithExplicitState.stateResource(primary, created);
77+
var stateResource =
78+
dependentResourceWithExplicitState.getKubernetesClient().resource(resource).create();
79+
if (externalStateEventSource != null) {
80+
((RecentOperationCacheFiller) externalStateEventSource)
81+
.handleRecentResourceCreate(ResourceID.fromResource(primary), stateResource);
82+
}
83+
}
84+
85+
86+
@SuppressWarnings("unchecked")
87+
public void deleteTargetResource(P primary, R resource, String key,
88+
Context<P> context) {
89+
if (isDependentResourceWithExplicitState) {
90+
getKubernetesClient()
91+
.resource(dependentResourceWithExplicitState.stateResource(primary, resource))
92+
.delete();
93+
}
94+
handleDeleteTargetResource(primary, resource, key, context);
95+
}
96+
97+
public void handleDeleteTargetResource(P primary, R resource, String key,
98+
Context<P> context) {
99+
throw new IllegalStateException("Override this method in case you manage an bulk resource");
100+
}
101+
102+
@SuppressWarnings("rawtypes")
103+
protected InformerEventSource getExternalStateEventSource() {
104+
return externalStateEventSource;
105+
}
106+
107+
/**
108+
* It's here just to manage the explicit state resource in case the dependent resource implements
109+
* {@link RecentOperationCacheFiller}.
110+
*
111+
* @return kubernetes client.
112+
*/
113+
public KubernetesClient getKubernetesClient() {
114+
return kubernetesClient;
115+
}
116+
117+
public void setKubernetesClient(KubernetesClient kubernetesClient) {
118+
this.kubernetesClient = kubernetesClient;
119+
}
120+
}

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

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

1615
class BulkDependentResourceReconciler<R, P extends HasMetadata>
1716
implements DependentResourceReconciler<R, P> {
@@ -97,13 +96,13 @@ public Result<R> match(R resource, P primary, Context<P> context) {
9796
}
9897

9998
@Override
100-
protected void onCreated(ResourceID primaryResourceId, R created) {
101-
asAbstractDependentResource().onCreated(primaryResourceId, created);
99+
protected void onCreated(P primary, R created, Context<P> context) {
100+
asAbstractDependentResource().onCreated(primary, created, context);
102101
}
103102

104103
@Override
105-
protected void onUpdated(ResourceID primaryResourceId, R updated, R actual) {
106-
asAbstractDependentResource().onUpdated(primaryResourceId, updated, actual);
104+
protected void onUpdated(P primary, R updated, R actual, Context<P> context) {
105+
asAbstractDependentResource().onUpdated(primary, updated, actual, context);
107106
}
108107

109108
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package io.javaoperatorsdk.operator.processing.dependent;
2+
3+
import java.util.Optional;
4+
5+
import io.fabric8.kubernetes.api.model.HasMetadata;
6+
import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter;
7+
import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.KubernetesClientAware;
8+
9+
/**
10+
* Handles external resources where in order to address the resource additional information or
11+
* persistent state (usually the ID of the resource) is needed to access the current state. These
12+
* are non Kubernetes resources which when created their ID is generated, so cannot be determined
13+
* based only on primary resources. In order to manage such dependent resource use this interface
14+
* for a resource that extends {@link AbstractExternalDependentResource}.
15+
*/
16+
public interface DependentResourceWithExplicitState<R, P extends HasMetadata, S extends HasMetadata>
17+
extends Creator<R, P>, Deleter<P>, KubernetesClientAware {
18+
19+
/**
20+
* Only needs to be implemented if multiple event sources are present for the target resource
21+
* class.
22+
*
23+
* @return name of the event source to access the state resources.
24+
*/
25+
default Optional<String> eventSourceName() {
26+
return Optional.empty();
27+
}
28+
29+
/**
30+
* Class of the state resource.
31+
*/
32+
Class<S> stateResourceClass();
33+
34+
/** State resource which contains the target state. Usually an ID to address the resource */
35+
S stateResource(P primary, R resource);
36+
37+
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public ReconcileResult<R> reconcile(P primary, Context<P> context) {
2121

2222
@Override
2323
public void delete(P primary, Context<P> context) {
24-
instance.handleDelete(primary, context);
24+
var secondary = instance.getSecondaryResource(primary, context);
25+
instance.handleDelete(primary, secondary.orElse(null), context);
2526
}
2627
}

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@
22

33
import io.fabric8.kubernetes.api.model.HasMetadata;
44
import io.javaoperatorsdk.operator.api.reconciler.Ignore;
5-
import io.javaoperatorsdk.operator.processing.dependent.AbstractEventSourceHolderDependentResource;
5+
import io.javaoperatorsdk.operator.processing.dependent.AbstractExternalDependentResource;
66
import io.javaoperatorsdk.operator.processing.event.source.CacheKeyMapper;
77
import io.javaoperatorsdk.operator.processing.event.source.ExternalResourceCachingEventSource;
88

99
@Ignore
1010
public abstract class AbstractPollingDependentResource<R, P extends HasMetadata>
11-
extends
12-
AbstractEventSourceHolderDependentResource<R, P, ExternalResourceCachingEventSource<R, P>>
11+
extends AbstractExternalDependentResource<R, P, ExternalResourceCachingEventSource<R, P>>
1312
implements CacheKeyMapper<R> {
1413

1514
public static final int DEFAULT_POLLING_PERIOD = 5000;

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,10 @@ public Result<R> match(R actualResource, R desired, P primary, Context<P> contex
153153
return GenericKubernetesResourceMatcher.match(desired, actualResource, false);
154154
}
155155

156-
protected void handleDelete(P primary, Context<P> context) {
157-
var resource = getSecondaryResource(primary, context);
158-
resource.ifPresent(r -> client.resource(r).delete());
156+
protected void handleDelete(P primary, R secondary, Context<P> context) {
157+
if (secondary != null) {
158+
client.resource(secondary).delete();
159+
}
159160
}
160161

161162
@SuppressWarnings("unused")

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
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.processing.event.ResourceID;
1110
import io.javaoperatorsdk.operator.sample.simple.TestCustomResource;
1211

1312
import static org.junit.jupiter.api.Assertions.*;
@@ -85,10 +84,12 @@ public Optional<ConfigMap> getSecondaryResource(TestCustomResource primary,
8584
}
8685

8786
@Override
88-
protected void onCreated(ResourceID primaryResourceId, ConfigMap created) {}
87+
protected void onCreated(TestCustomResource primary, ConfigMap created,
88+
Context<TestCustomResource> context) {}
8989

9090
@Override
91-
protected void onUpdated(ResourceID primaryResourceId, ConfigMap updated, ConfigMap actual) {}
91+
protected void onUpdated(TestCustomResource primary, ConfigMap updated, ConfigMap actual,
92+
Context<TestCustomResource> context) {}
9293

9394
@Override
9495
protected ConfigMap desired(TestCustomResource primary, Context<TestCustomResource> context) {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import static org.awaitility.Awaitility.await;
1717

1818
class EventSourceIT {
19+
1920
@RegisterExtension
2021
LocallyRunOperatorExtension operator =
2122
LocallyRunOperatorExtension.builder().withReconciler(EventSourceTestCustomReconciler.class)

0 commit comments

Comments
 (0)