Skip to content

Commit 9e4e60d

Browse files
committed
feat: retrieve secondary resources from Context
1 parent 0c5d589 commit 9e4e60d

File tree

17 files changed

+133
-28
lines changed

17 files changed

+133
-28
lines changed
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
package io.javaoperatorsdk.operator.api.reconciler;
22

3+
import java.util.Optional;
4+
35
import io.fabric8.kubernetes.api.model.HasMetadata;
46
import io.javaoperatorsdk.operator.processing.event.source.EventSourceRegistry;
5-
import java.util.Optional;
67

78
public interface Context<P extends HasMetadata> {
89

910
Optional<RetryInfo> getRetryInfo();
1011

1112
EventSourceRegistry<P> getEventSourceRegistry();
1213

14+
<T extends HasMetadata> T getSecondaryResourceAssociatedWith(Class<T> expectedType,
15+
String... qualifier);
1316
}
Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
package io.javaoperatorsdk.operator.api.reconciler;
22

3+
import java.util.Optional;
4+
35
import io.fabric8.kubernetes.api.model.HasMetadata;
6+
import io.javaoperatorsdk.operator.processing.Controller;
7+
import io.javaoperatorsdk.operator.processing.event.ResourceID;
48
import io.javaoperatorsdk.operator.processing.event.source.EventSourceRegistry;
5-
import java.util.Optional;
69

710
public class DefaultContext<P extends HasMetadata> implements Context<P> {
811

912
private final RetryInfo retryInfo;
10-
private final EventSourceRegistry<P> registry;
13+
private final Controller<P> controller;
14+
private final P primaryResource;
1115

12-
public DefaultContext(RetryInfo retryInfo,
13-
EventSourceRegistry<P> registry) {
16+
public DefaultContext(RetryInfo retryInfo, Controller<P> controller, P primaryResource) {
1417
this.retryInfo = retryInfo;
15-
this.registry = registry;
18+
this.controller = controller;
19+
this.primaryResource = primaryResource;
1620
}
1721

1822
@Override
@@ -22,6 +26,15 @@ public Optional<RetryInfo> getRetryInfo() {
2226

2327
@Override
2428
public EventSourceRegistry<P> getEventSourceRegistry() {
25-
return registry;
29+
return controller.getEventSourceRegistry();
30+
}
31+
32+
@Override
33+
public <T extends HasMetadata> T getSecondaryResourceAssociatedWith(Class<T> expectedType,
34+
String... qualifier) {
35+
return getEventSourceRegistry()
36+
.getResourceEventSourceFor(expectedType, qualifier)
37+
.getResourceCache()
38+
.get(ResourceID.fromResource(primaryResource)).orElse(null);
2639
}
2740
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public Controller(Reconciler<R> reconciler,
4545
}
4646

4747
@Override
48-
public DeleteControl cleanup(R resource, Context context) {
48+
public DeleteControl cleanup(R resource, Context<R> context) {
4949
return configuration.getConfigurationService().getMetrics().timeControllerExecution(
5050
new ControllerExecution<>() {
5151
@Override
@@ -71,7 +71,7 @@ public DeleteControl execute() {
7171
}
7272

7373
@Override
74-
public UpdateControl<R> reconcile(R resource, Context context) {
74+
public UpdateControl<R> reconcile(R resource, Context<R> context) {
7575
final var metrics = configuration.getConfigurationService().getMetrics();
7676

7777
configuration.getDependentResources().forEach(dependent -> {

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

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import io.javaoperatorsdk.operator.processing.event.source.ControllerResourceEventSource;
1818
import io.javaoperatorsdk.operator.processing.event.source.EventSource;
1919
import io.javaoperatorsdk.operator.processing.event.source.EventSourceRegistry;
20+
import io.javaoperatorsdk.operator.processing.event.source.ResourceEventSource;
2021
import io.javaoperatorsdk.operator.processing.event.source.TimerEventSource;
2122

2223
public class EventSourceManager<R extends HasMetadata>
@@ -25,7 +26,7 @@ public class EventSourceManager<R extends HasMetadata>
2526
private static final Logger log = LoggerFactory.getLogger(EventSourceManager.class);
2627

2728
private final ReentrantLock lock = new ReentrantLock();
28-
private final ConcurrentNavigableMap<String, EventSource> eventSources =
29+
private final ConcurrentNavigableMap<String, EventSource<R>> eventSources =
2930
new ConcurrentSkipListMap<>();
3031
private final EventProcessor<R> eventProcessor;
3132
private TimerEventSource<R> retryAndRescheduleTimerEventSource;
@@ -110,16 +111,36 @@ public final void registerEventSource(EventSource eventSource)
110111
}
111112

112113
private String keyFor(EventSource source) {
113-
var name = source.getClass().getName();
114+
String name;
115+
if (source instanceof ResourceEventSource) {
116+
ResourceEventSource resourceEventSource = (ResourceEventSource) source;
117+
final var configuration = resourceEventSource.getConfiguration();
118+
// todo: extract qualifier from configuration
119+
name = keyFor(configuration.getResourceClass());
120+
} else {
121+
name = keyFor(source.getResourceClass());
122+
}
123+
return name;
124+
}
125+
126+
private String keyFor(Class<?> dependentType, String... qualifier) {
127+
final var className = dependentType.getCanonicalName();
128+
var key = className;
129+
if (qualifier != null && qualifier.length > 0) {
130+
key += "-" + qualifier[0];
131+
}
132+
114133
// make sure we process controller and timer event sources first
115134
// this is needed so that these sources are set when informer sources start so that events can
116135
// properly be processed
117-
if (source == controllerResourceEventSource) {
118-
name = 0 + name;
119-
} else if (source == retryAndRescheduleTimerEventSource) {
120-
name = 1 + name;
136+
if (controllerResourceEventSource != null
137+
&& className.equals(controllerResourceEventSource.getResourceClass().getCanonicalName())) {
138+
key = 0 + key;
139+
} else if (retryAndRescheduleTimerEventSource != null && className
140+
.equals(retryAndRescheduleTimerEventSource.getResourceClass().getCanonicalName())) {
141+
key = 1 + key;
121142
}
122-
return name;
143+
return key;
123144
}
124145

125146
public void cleanupForCustomResource(ResourceID customResourceUid) {
@@ -134,7 +155,7 @@ public void cleanupForCustomResource(ResourceID customResourceUid) {
134155
}
135156

136157
@Override
137-
public Set<EventSource> getRegisteredEventSources() {
158+
public Set<EventSource<R>> getRegisteredEventSources() {
138159
return Set.copyOf(eventSources.values());
139160
}
140161

@@ -143,6 +164,38 @@ public ControllerResourceEventSource<R> getControllerResourceEventSource() {
143164
return controllerResourceEventSource;
144165
}
145166

167+
@Override
168+
public <S extends HasMetadata> ResourceEventSource<S, R> getResourceEventSourceFor(
169+
Class<S> dependentType,
170+
String... qualifier) {
171+
final var eventSource = eventSources.get(keyFor(dependentType, qualifier));
172+
if (eventSource == null) {
173+
return null;
174+
}
175+
if (!(eventSource instanceof ResourceEventSource)) {
176+
throw new IllegalArgumentException(eventSource + " associated with "
177+
+ keyAsString(dependentType, qualifier) + " is not a "
178+
+ ResourceEventSource.class.getSimpleName());
179+
}
180+
final var source = (ResourceEventSource<S, R>) eventSource;
181+
final var configuration = source.getConfiguration();
182+
final var resourceClass = configuration.getResourceClass();
183+
if (!resourceClass.isAssignableFrom(dependentType)) {
184+
throw new IllegalArgumentException(eventSource + " associated with "
185+
+ keyAsString(dependentType, qualifier)
186+
+ " is handling " + resourceClass.getName() + " resources but asked for "
187+
+ dependentType.getName());
188+
}
189+
return (ResourceEventSource<S, R>) eventSource;
190+
}
191+
192+
@SuppressWarnings("rawtypes")
193+
private String keyAsString(Class dependentType, String... qualifier) {
194+
return qualifier != null && qualifier.length > 0
195+
? "(" + dependentType.getName() + ", " + qualifier[0] + ")"
196+
: dependentType.getName();
197+
}
198+
146199
TimerEventSource<R> retryEventSource() {
147200
return retryAndRescheduleTimerEventSource;
148201
}

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,7 @@ private PostExecutionControl<R> handleDispatch(ExecutionScope<R> executionScope)
6767
return PostExecutionControl.defaultDispatch();
6868
}
6969

70-
Context<R> context =
71-
new DefaultContext<>(executionScope.getRetryInfo(), controller.getEventSourceRegistry());
70+
Context<R> context = new DefaultContext<>(executionScope.getRetryInfo(), controller, resource);
7271
if (markedForDeletion) {
7372
return handleCleanup(resource, context);
7473
} else {

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/AbstractEventSource.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ public abstract class AbstractEventSource<P extends HasMetadata> implements Even
77

88
private volatile EventHandler eventHandler;
99
private volatile EventSourceRegistry<P> eventSourceRegistry;
10+
private final Class<?> resourceClass;
11+
12+
protected AbstractEventSource(Class<?> resourceClass) {
13+
this.resourceClass = resourceClass;
14+
}
15+
16+
@Override
17+
public Class<?> getResourceClass() {
18+
return resourceClass;
19+
}
1020

1121
@Override
1222
public void setEventHandler(EventHandler eventHandler) {

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/AbstractResourceEventSource.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,19 @@ public abstract class AbstractResourceEventSource<T extends HasMetadata, U exten
3838
public AbstractResourceEventSource(U configuration,
3939
MixedOperation<T, KubernetesResourceList<T>, Resource<T>> client, Cloner cloner,
4040
EventSourceRegistry<P> registry) {
41+
super(configuration.getResourceClass());
4142
this.configuration = configuration;
4243
this.client = client;
4344
this.filter = initFilter(configuration);
4445
this.cloner = cloner;
4546
setEventRegistry(registry);
4647
}
4748

49+
@Override
50+
public U getConfiguration() {
51+
return configuration;
52+
}
53+
4854
protected abstract ResourceEventFilter<T, U> initFilter(U configuration);
4955

5056
protected abstract V wrapEventSource(

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/EventSource.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,6 @@ public interface EventSource<P extends HasMetadata> extends LifecycleAware {
1919
* @param customResourceUid - id of custom resource
2020
*/
2121
default void cleanupForResource(ResourceID customResourceUid) {}
22+
23+
Class<?> getResourceClass();
2224
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/EventSourceRegistry.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@ public interface EventSourceRegistry<T extends HasMetadata> {
1818
void registerEventSource(EventSource<? extends HasMetadata> eventSource)
1919
throws IllegalStateException, OperatorException;
2020

21-
Set<EventSource> getRegisteredEventSources();
21+
Set<EventSource<T>> getRegisteredEventSources();
2222

2323
ControllerResourceEventSource<T> getControllerResourceEventSource();
2424

25+
<R extends HasMetadata> ResourceEventSource<R, T> getResourceEventSourceFor(
26+
Class<R> dependentType,
27+
String... qualifier);
2528
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/InformerEventSource.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public InformerEventSource(SharedInformer<T> sharedInformer,
4646
AssociatedSecondaryRetriever<T, P> associatedWith,
4747
boolean skipUpdateEventPropagationIfNoChange,
4848
Cloner cloner) {
49+
super(sharedInformer.getApiTypeClass());
4950
this.sharedInformer = sharedInformer;
5051
this.associatedPrimaries = associatedPrimaries;
5152
this.skipUpdateEventPropagationIfNoChange = skipUpdateEventPropagationIfNoChange;
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package io.javaoperatorsdk.operator.processing.event.source;
22

33
import io.fabric8.kubernetes.api.model.HasMetadata;
4+
import io.javaoperatorsdk.operator.api.config.ResourceConfiguration;
45

56
public interface ResourceEventSource<T extends HasMetadata, P extends HasMetadata>
67
extends EventSource<P> {
78
ResourceCache<T> getResourceCache();
9+
10+
ResourceConfiguration<T, ? extends ResourceConfiguration> getConfiguration();
811
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/TimerEventSource.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ public class TimerEventSource<R extends HasMetadata> extends AbstractEventSource
2020
private final AtomicBoolean running = new AtomicBoolean();
2121
private final Map<ResourceID, EventProducerTimeTask> onceTasks = new ConcurrentHashMap<>();
2222

23+
public TimerEventSource() {
24+
// we need to return the source class so that it can be used to register the event source
25+
super(TimerEventSource.class);
26+
}
27+
2328

2429
public void scheduleOnce(R resource, long delay) {
2530
if (!running.get()) {

operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/EventSourceManagerTest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import static org.mockito.Mockito.mock;
1515
import static org.mockito.Mockito.times;
1616
import static org.mockito.Mockito.verify;
17+
import static org.mockito.Mockito.when;
1718

1819
class EventSourceManagerTest {
1920

@@ -23,6 +24,7 @@ class EventSourceManagerTest {
2324
@Test
2425
public void registersEventSource() {
2526
EventSource eventSource = mock(EventSource.class);
27+
when(eventSource.getResourceClass()).thenReturn(String.class);
2628

2729
eventSourceManager.registerEventSource(eventSource);
2830

@@ -35,7 +37,9 @@ public void registersEventSource() {
3537
@Test
3638
public void closeShouldCascadeToEventSources() throws IOException {
3739
EventSource eventSource = mock(EventSource.class);
40+
when(eventSource.getResourceClass()).thenReturn(String.class);
3841
EventSource eventSource2 = mock(EventSource.class);
42+
when(eventSource2.getResourceClass()).thenReturn(Object.class);
3943
eventSourceManager.registerEventSource(eventSource);
4044
eventSourceManager.registerEventSource(eventSource2);
4145

@@ -48,7 +52,9 @@ public void closeShouldCascadeToEventSources() throws IOException {
4852
@Test
4953
public void startCascadesToEventSources() {
5054
EventSource eventSource = mock(EventSource.class);
55+
when(eventSource.getResourceClass()).thenReturn(String.class);
5156
EventSource eventSource2 = mock(EventSource.class);
57+
when(eventSource2.getResourceClass()).thenReturn(Object.class);
5258
eventSourceManager.registerEventSource(eventSource);
5359
eventSourceManager.registerEventSource(eventSource2);
5460

@@ -62,6 +68,7 @@ public void startCascadesToEventSources() {
6268
public void deRegistersEventSources() {
6369
CustomResource customResource = TestUtils.testCustomResource();
6470
EventSource eventSource = mock(EventSource.class);
71+
when(eventSource.getResourceClass()).thenReturn(String.class);
6572
eventSourceManager.registerEventSource(eventSource);
6673

6774
eventSourceManager

operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ public boolean isLastAttempt() {
274274
ArgumentCaptor.forClass(Context.class);
275275
verify(reconciler, times(1))
276276
.reconcile(any(), contextArgumentCaptor.capture());
277-
Context context = contextArgumentCaptor.getValue();
277+
Context<?> context = contextArgumentCaptor.getValue();
278278
final var retryInfo = context.getRetryInfo().get();
279279
assertThat(retryInfo.getAttemptCount()).isEqualTo(2);
280280
assertThat(retryInfo.isLastAttempt()).isEqualTo(true);

operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/errorstatushandler/ErrorStatusHandlerTestReconciler.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ public class ErrorStatusHandlerTestReconciler
2222

2323
@Override
2424
public UpdateControl<ErrorStatusHandlerTestCustomResource> reconcile(
25-
ErrorStatusHandlerTestCustomResource resource, Context context) {
25+
ErrorStatusHandlerTestCustomResource resource,
26+
Context<ErrorStatusHandlerTestCustomResource> context) {
2627
var number = numberOfExecutions.addAndGet(1);
2728
var retryAttempt = -1;
2829
if (context.getRetryInfo().isPresent()) {

operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/retry/RetryTestCustomReconciler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public RetryTestCustomReconciler(int numberOfExecutionFails) {
3232

3333
@Override
3434
public UpdateControl<RetryTestCustomResource> reconcile(
35-
RetryTestCustomResource resource, Context context) {
35+
RetryTestCustomResource resource, Context<RetryTestCustomResource> context) {
3636
numberOfExecutions.addAndGet(1);
3737

3838
if (!resource.getMetadata().getFinalizers().contains(FINALIZER_NAME)) {

sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappReconciler.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
import okhttp3.Response;
3333

3434
@ControllerConfiguration(
35-
dependents = @DependentResourceConfiguration(creatable = false, resourceType = Tomcat.class,
35+
dependents = @DependentResourceConfiguration(
36+
creatable = false, resourceType = Tomcat.class,
3637
associatedPrimariesRetriever = WebappRetriever.class))
3738
public class WebappReconciler implements Reconciler<Webapp> {
3839

@@ -64,15 +65,13 @@ public Set<ResourceID> associatedPrimaryResources(Tomcat t,
6465
* change.
6566
*/
6667
@Override
67-
public UpdateControl<Webapp> reconcile(Webapp webapp, Context context) {
68+
public UpdateControl<Webapp> reconcile(Webapp webapp, Context<Webapp> context) {
6869
if (webapp.getStatus() != null
6970
&& Objects.equals(webapp.getSpec().getUrl(), webapp.getStatus().getDeployedArtifact())) {
7071
return UpdateControl.noUpdate();
7172
}
7273

73-
var tomcatClient = kubernetesClient.customResources(Tomcat.class);
74-
Tomcat tomcat = tomcatClient.inNamespace(webapp.getMetadata().getNamespace())
75-
.withName(webapp.getSpec().getTomcat()).get();
74+
Tomcat tomcat = context.getSecondaryResourceAssociatedWith(Tomcat.class);
7675
if (tomcat == null) {
7776
throw new IllegalStateException("Cannot find Tomcat " + webapp.getSpec().getTomcat()
7877
+ " for Webapp " + webapp.getMetadata().getName() + " in namespace "

0 commit comments

Comments
 (0)