Skip to content

Commit ab23a2c

Browse files
authored
feat: add a DependentResource implementation that's also an EventSource (#1094)
* feat: add a DependentResource implementation that's also an EventSource The benefit of this change is multi-fold: - simplifies AbstractDependentResource which doesn't have to handle cases it should not be concerned about - allows to share more code with AbstractSimpleDependentResource - enforces that the same name is used for the DependentResource and EventSource even in the standalone mode when using one of the provided implementations * feat: make exception message more informative * feat: improve logging * feat: introduce ResourceOwner to be unify type and resource retrieval * refactor: rename methods so that it's clearer they're not about caching * refactor: rename methods less ambiguously
1 parent 5b1e333 commit ab23a2c

30 files changed

+310
-256
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public Optional<RetryInfo> getRetryInfo() {
3232
public <T> Optional<T> getSecondaryResource(Class<T> expectedType, String eventSourceName) {
3333
return controller.getEventSourceManager()
3434
.getResourceEventSourceFor(expectedType, eventSourceName)
35-
.flatMap(es -> es.getAssociated(primaryResource));
35+
.flatMap(es -> es.getAssociatedResource(primaryResource));
3636
}
3737

3838
@Override

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@
1414
public interface EventSourceInitializer<P extends HasMetadata> {
1515

1616
/**
17-
* Prepares a list of {@link EventSource} implementations to be registered by the SDK.
17+
* Prepares a map of {@link EventSource} implementations keyed by the name with which they need to
18+
* be registered by the SDK.
1819
*
1920
* @param context a {@link EventSourceContext} providing access to information useful to event
2021
* sources
21-
* @return list of event sources to register
22+
* @return a map of event sources to register
2223
*/
2324
Map<String, EventSource> prepareEventSources(EventSourceContext<P> context);
2425

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import io.fabric8.kubernetes.api.model.HasMetadata;
44

5-
@SuppressWarnings("rawtypes")
65
public class UpdateControl<T extends HasMetadata> extends BaseControl<UpdateControl<T>> {
76

87
private final T resource;
@@ -32,8 +31,7 @@ public static <T extends HasMetadata> UpdateControl<T> updateResource(T customRe
3231
return new UpdateControl<>(customResource, false, true);
3332
}
3433

35-
public static <T extends HasMetadata> UpdateControl<T> updateStatus(
36-
T customResource) {
34+
public static <T extends HasMetadata> UpdateControl<T> updateStatus(T customResource) {
3735
return new UpdateControl<>(customResource, true, false);
3836
}
3937

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

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
package io.javaoperatorsdk.operator.api.reconciler.dependent;
22

3-
import java.util.Optional;
4-
53
import io.fabric8.kubernetes.api.model.HasMetadata;
64
import io.javaoperatorsdk.operator.api.reconciler.Context;
5+
import io.javaoperatorsdk.operator.processing.ResourceOwner;
76

87
/**
98
* An interface to implement and provide dependent resource support.
109
*
1110
* @param <R> the dependent resource type
1211
* @param <P> the associated primary resource type
1312
*/
14-
public interface DependentResource<R, P extends HasMetadata> {
13+
public interface DependentResource<R, P extends HasMetadata> extends ResourceOwner<R, P> {
1514

1615
/**
1716
* Reconciles the dependent resource given the desired primary state
@@ -22,25 +21,6 @@ public interface DependentResource<R, P extends HasMetadata> {
2221
*/
2322
ReconcileResult<R> reconcile(P primary, Context<P> context);
2423

25-
/**
26-
* The intention with get resource is to return the actual state of the resource. Usually from a
27-
* local cache, what was updated after the reconciliation, or typically from the event source that
28-
* caches the resources.
29-
*
30-
* @param primaryResource the primary resource for which we want to retrieve the secondary
31-
* resource
32-
* @return an {@link Optional} containing the secondary resource or {@link Optional#empty()} if it
33-
* doesn't exist
34-
*/
35-
Optional<R> getResource(P primaryResource);
36-
37-
/**
38-
* Retrieves the resource type associated with this DependentResource
39-
*
40-
* @return the resource type associated with this DependentResource
41-
*/
42-
Class<R> resourceType();
43-
4424
/**
4525
* Computes a default name for the specified DependentResource class
4626
*

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,4 @@ public interface EventSourceProvider<P extends HasMetadata> {
1010
* @return the initiated event source.
1111
*/
1212
EventSource initEventSource(EventSourceContext<P> context);
13-
14-
EventSource getEventSource();
1513
}

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ public UpdateControl<P> execute() throws Exception {
205205
if (!exceptions.isEmpty()) {
206206
throw new AggregatedOperatorException("One or more DependentResource(s) failed:\n" +
207207
exceptions.stream()
208-
.map(e -> "\t\t- " + e.getMessage())
208+
.map(Controller.this::createExceptionInformation)
209209
.collect(Collectors.joining("\n")),
210210
exceptions);
211211
}
@@ -215,6 +215,24 @@ public UpdateControl<P> execute() throws Exception {
215215
});
216216
}
217217

218+
private String createExceptionInformation(Exception e) {
219+
final var exceptionLocation = Optional.ofNullable(e.getCause())
220+
.map(Throwable::getStackTrace)
221+
.filter(stackTrace -> stackTrace.length > 0)
222+
.map(stackTrace -> {
223+
int i = 0;
224+
while (i < stackTrace.length) {
225+
final var moduleName = stackTrace[i].getModuleName();
226+
if (!"java.base".equals(moduleName)) {
227+
return " at: " + stackTrace[i].toString();
228+
}
229+
i++;
230+
}
231+
return "";
232+
});
233+
return "\t\t- " + e.getMessage() + exceptionLocation.orElse("");
234+
}
235+
218236
public void initAndRegisterEventSources(EventSourceContext<P> context) {
219237
dependents.entrySet().stream()
220238
.filter(drEntry -> drEntry.getValue() instanceof EventSourceProvider)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package io.javaoperatorsdk.operator.processing;
2+
3+
import java.util.Optional;
4+
5+
import io.fabric8.kubernetes.api.model.HasMetadata;
6+
7+
public interface ResourceOwner<R, P extends HasMetadata> {
8+
9+
/**
10+
* Retrieves the resource type associated with this ResourceOwner
11+
*
12+
* @return the resource type associated with this ResourceOwner
13+
*/
14+
Class<R> resourceType();
15+
16+
/**
17+
* Retrieves the resource associated with the specified primary one, returning the actual state of
18+
* the resource. Typically, this state might come from a local cache, updated after
19+
* reconciliation.
20+
*
21+
* @param primary the primary resource for which we want to retrieve the secondary resource
22+
* @return an {@link Optional} containing the secondary resource or {@link Optional#empty()} if it
23+
* doesn't exist
24+
*/
25+
Optional<R> getAssociatedResource(P primary);
26+
}

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

Lines changed: 37 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@
77
import io.javaoperatorsdk.operator.api.reconciler.Context;
88
import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter;
99
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
10-
import io.javaoperatorsdk.operator.api.reconciler.dependent.EventSourceProvider;
11-
import io.javaoperatorsdk.operator.api.reconciler.dependent.RecentOperationCacheFiller;
12-
import io.javaoperatorsdk.operator.api.reconciler.dependent.RecentOperationEventFilter;
1310
import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult;
1411
import io.javaoperatorsdk.operator.processing.event.ResourceID;
1512

@@ -31,14 +28,12 @@ public AbstractDependentResource() {
3128

3229
@Override
3330
public ReconcileResult<R> reconcile(P primary, Context<P> context) {
34-
var maybeActual = getResource(primary);
31+
var maybeActual = getAssociatedResource(primary);
3532
if (creatable || updatable) {
3633
if (maybeActual.isEmpty()) {
3734
if (creatable) {
3835
var desired = desired(primary, context);
39-
log.info("Creating {} for primary {}", desired.getClass().getSimpleName(),
40-
ResourceID.fromResource(primary));
41-
log.debug("Creating dependent {} for primary {}", desired, primary);
36+
logForOperation("Creating", primary, desired);
4237
var createdResource = handleCreate(desired, primary, context);
4338
return ReconcileResult.resourceCreated(createdResource);
4439
}
@@ -48,9 +43,7 @@ public ReconcileResult<R> reconcile(P primary, Context<P> context) {
4843
final var match = updater.match(actual, primary, context);
4944
if (!match.matched()) {
5045
final var desired = match.computedDesired().orElse(desired(primary, context));
51-
log.info("Updating {} for primary {}", desired.getClass().getSimpleName(),
52-
ResourceID.fromResource(primary));
53-
log.debug("Updating dependent {} for primary {}", desired, primary);
46+
logForOperation("Updating", primary, desired);
5447
var updatedResource = handleUpdate(actual, desired, primary, context);
5548
return ReconcileResult.resourceUpdated(updatedResource);
5649
}
@@ -66,91 +59,47 @@ public ReconcileResult<R> reconcile(P primary, Context<P> context) {
6659
return ReconcileResult.noOperation(maybeActual.orElse(null));
6760
}
6861

69-
protected R handleCreate(R desired, P primary, Context<P> context) {
70-
ResourceID resourceID = ResourceID.fromResource(primary);
71-
R created = null;
72-
try {
73-
prepareEventFiltering(desired, resourceID);
74-
created = creator.create(desired, primary, context);
75-
cacheAfterCreate(resourceID, created);
76-
return created;
77-
} catch (RuntimeException e) {
78-
cleanupAfterEventFiltering(desired, resourceID, created);
79-
throw e;
80-
}
81-
}
82-
83-
private void cleanupAfterEventFiltering(R desired, ResourceID resourceID, R created) {
84-
if (isFilteringEventSource()) {
85-
eventSourceAsRecentOperationEventFilter()
86-
.cleanupOnCreateOrUpdateEventFiltering(resourceID, created);
87-
}
88-
}
89-
90-
private void cacheAfterCreate(ResourceID resourceID, R created) {
91-
if (isRecentOperationCacheFiller()) {
92-
eventSourceAsRecentOperationCacheFiller().handleRecentResourceCreate(resourceID, created);
93-
}
62+
private void logForOperation(String operation, P primary, R desired) {
63+
final var desiredDesc = desired instanceof HasMetadata
64+
? "'" + ((HasMetadata) desired).getMetadata().getName() + "' "
65+
+ ((HasMetadata) desired).getKind()
66+
: desired.getClass().getSimpleName();
67+
log.info("{} {} for primary {}", operation, desiredDesc, ResourceID.fromResource(primary));
68+
log.debug("{} dependent {} for primary {}", operation, desired, primary);
9469
}
9570

96-
private void cacheAfterUpdate(R actual, ResourceID resourceID, R updated) {
97-
if (isRecentOperationCacheFiller()) {
98-
eventSourceAsRecentOperationCacheFiller().handleRecentResourceUpdate(resourceID, updated,
99-
actual);
100-
}
71+
protected R handleCreate(R desired, P primary, Context<P> context) {
72+
ResourceID resourceID = ResourceID.fromResource(primary);
73+
R created = creator.create(desired, primary, context);
74+
onCreated(resourceID, created);
75+
return created;
10176
}
10277

103-
private void prepareEventFiltering(R desired, ResourceID resourceID) {
104-
if (isFilteringEventSource()) {
105-
eventSourceAsRecentOperationEventFilter().prepareForCreateOrUpdateEventFiltering(resourceID,
106-
desired);
107-
}
108-
}
78+
/**
79+
* Allows sub-classes to perform additional processing (e.g. caching) on the created resource if
80+
* needed.
81+
*
82+
* @param primaryResourceId the {@link ResourceID} of the primary resource associated with the
83+
* newly created resource
84+
* @param created the newly created resource
85+
*/
86+
protected abstract void onCreated(ResourceID primaryResourceId, R created);
87+
88+
/**
89+
* Allows sub-classes to perform additional processing on the updated resource if needed.
90+
*
91+
* @param primaryResourceId the {@link ResourceID} of the primary resource associated with the
92+
* newly updated resource
93+
* @param updated the updated resource
94+
* @param actual the resource as it was before the update
95+
*/
96+
protected abstract void onUpdated(ResourceID primaryResourceId, R updated, R actual);
10997

11098
protected R handleUpdate(R actual, R desired, P primary, Context<P> context) {
11199
ResourceID resourceID = ResourceID.fromResource(primary);
112-
R updated = null;
113-
try {
114-
prepareEventFiltering(desired, resourceID);
115-
updated = updater.update(actual, desired, primary, context);
116-
cacheAfterUpdate(actual, resourceID, updated);
117-
return updated;
118-
} catch (RuntimeException e) {
119-
cleanupAfterEventFiltering(desired, resourceID, updated);
120-
throw e;
121-
}
122-
}
123-
124-
@SuppressWarnings("unchecked")
125-
private RecentOperationEventFilter<R> eventSourceAsRecentOperationEventFilter() {
126-
return (RecentOperationEventFilter<R>) ((EventSourceProvider<P>) this).getEventSource();
127-
}
128-
129-
@SuppressWarnings("unchecked")
130-
private RecentOperationCacheFiller<R> eventSourceAsRecentOperationCacheFiller() {
131-
return (RecentOperationCacheFiller<R>) ((EventSourceProvider<P>) this).getEventSource();
132-
}
133-
134-
@SuppressWarnings("unchecked")
135-
// this cannot be done in constructor since event source might be initialized later
136-
protected boolean isFilteringEventSource() {
137-
if (this instanceof EventSourceProvider) {
138-
final var eventSource = ((EventSourceProvider<P>) this).getEventSource();
139-
return eventSource instanceof RecentOperationEventFilter;
140-
} else {
141-
return false;
142-
}
143-
}
144-
145-
@SuppressWarnings("unchecked")
146-
// this cannot be done in constructor since event source might be initialized later
147-
protected boolean isRecentOperationCacheFiller() {
148-
if (this instanceof EventSourceProvider) {
149-
final var eventSource = ((EventSourceProvider<P>) this).getEventSource();
150-
return eventSource instanceof RecentOperationCacheFiller;
151-
} else {
152-
return false;
153-
}
100+
R updated = updater.update(actual, desired, primary, context);
101+
onUpdated(resourceID, updated, actual);
102+
return updated;
154103
}
155104

156105
protected R desired(P primary, Context<P> context) {

0 commit comments

Comments
 (0)