Skip to content

Commit 22ccb38

Browse files
committed
feat: EventSource are now registered with a name
1 parent 93169f0 commit 22ccb38

File tree

11 files changed

+151
-96
lines changed

11 files changed

+151
-96
lines changed

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

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

3-
import java.util.List;
3+
import java.util.Map;
44

55
import io.fabric8.kubernetes.api.model.HasMetadata;
66
import io.javaoperatorsdk.operator.processing.event.source.EventSource;
@@ -20,6 +20,6 @@ public interface EventSourceInitializer<P extends HasMetadata> {
2020
* sources
2121
* @return list of event sources to register
2222
*/
23-
List<EventSource> prepareEventSources(EventSourceContext<P> context);
23+
Map<String, EventSource> prepareEventSources(EventSourceContext<P> context);
2424

2525
}

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

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,11 @@
4040
import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DependentResourceConfigurator;
4141
import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.KubernetesClientAware;
4242
import io.javaoperatorsdk.operator.processing.event.EventSourceManager;
43-
import io.javaoperatorsdk.operator.processing.event.source.EventSource;
4443

4544
@SuppressWarnings({"unchecked", "rawtypes"})
4645
@Ignore
47-
public class Controller<P extends HasMetadata> implements Reconciler<P>, Cleaner<P>,
48-
LifecycleAware, EventSourceInitializer<P> {
46+
public class Controller<P extends HasMetadata>
47+
implements Reconciler<P>, Cleaner<P>, LifecycleAware {
4948

5049
private static final Logger log = LoggerFactory.getLogger(Controller.class);
5150

@@ -194,20 +193,21 @@ public UpdateControl<P> execute() throws Exception {
194193
});
195194
}
196195

197-
@Override
198-
public List<EventSource> prepareEventSources(EventSourceContext<P> context) {
199-
List<EventSource> sources = new LinkedList<>();
200-
dependents.values().stream()
201-
.filter(dependentResource -> dependentResource instanceof EventSourceProvider)
202-
.map(EventSourceProvider.class::cast)
203-
.map(provider -> provider.initEventSource(context))
204-
.forEach(sources::add);
196+
public void initAndRegisterEventSources(EventSourceContext<P> context) {
197+
dependents.entrySet().stream()
198+
.filter(drEntry -> drEntry.getValue() instanceof EventSourceProvider)
199+
.forEach(drEntry -> {
200+
final var provider = (EventSourceProvider) drEntry.getValue();
201+
final var source = provider.initEventSource(context);
202+
eventSourceManager.registerEventSource(drEntry.getKey(), source);
203+
});
205204

206205
// add manually defined event sources
207206
if (reconciler instanceof EventSourceInitializer) {
208-
sources.addAll(((EventSourceInitializer<P>) reconciler).prepareEventSources(context));
207+
final var provider = (EventSourceInitializer<P>) this.reconciler;
208+
final var ownSources = provider.prepareEventSources(context);
209+
ownSources.forEach(eventSourceManager::registerEventSource);
209210
}
210-
return sources;
211211
}
212212

213213
@Override
@@ -286,7 +286,7 @@ public void start() throws OperatorException {
286286
final var context = new EventSourceContext<>(
287287
eventSourceManager.getControllerResourceEventSource(), configuration, kubernetesClient);
288288

289-
prepareEventSources(context).forEach(eventSourceManager::registerEventSource);
289+
initAndRegisterEventSources(context);
290290

291291
eventSourceManager.start();
292292

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

Lines changed: 71 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
package io.javaoperatorsdk.operator.processing.event;
22

3-
import java.util.ArrayList;
4-
import java.util.Collection;
3+
import java.util.HashMap;
54
import java.util.Iterator;
65
import java.util.LinkedHashSet;
7-
import java.util.List;
6+
import java.util.Map;
87
import java.util.Objects;
98
import java.util.Optional;
109
import java.util.Set;
1110
import java.util.concurrent.ConcurrentNavigableMap;
1211
import java.util.concurrent.ConcurrentSkipListMap;
1312
import java.util.concurrent.locks.ReentrantLock;
1413
import java.util.stream.Collectors;
14+
import java.util.stream.Stream;
1515

1616
import org.slf4j.Logger;
1717
import org.slf4j.LoggerFactory;
@@ -87,7 +87,7 @@ public void start() {
8787
}
8888

8989
@SuppressWarnings("rawtypes")
90-
private void logEventSourceEvent(EventSource eventSource, String event) {
90+
private void logEventSourceEvent(NamedEventSource eventSource, String event) {
9191
if (log.isDebugEnabled()) {
9292
if (eventSource instanceof ResourceEventSource) {
9393
ResourceEventSource source = (ResourceEventSource) eventSource;
@@ -119,17 +119,24 @@ public void stop() {
119119
eventProcessor.stop();
120120
}
121121

122-
public final void registerEventSource(EventSource eventSource)
122+
public final void registerEventSource(EventSource eventSource) throws OperatorException {
123+
registerEventSource(null, eventSource);
124+
}
125+
126+
public final void registerEventSource(String name, EventSource eventSource)
123127
throws OperatorException {
124128
Objects.requireNonNull(eventSource, "EventSource must not be null");
125129
lock.lock();
126130
try {
127-
eventSources.add(eventSource);
131+
if (name == null || name.isBlank()) {
132+
name = EventSource.defaultNameFor(eventSource);
133+
}
134+
eventSources.add(name, eventSource);
128135
eventSource.setEventHandler(eventProcessor);
129136
} catch (IllegalStateException | MissingCRDException e) {
130137
throw e; // leave untouched
131138
} catch (Exception e) {
132-
throw new OperatorException("Couldn't register event source: " + eventSource.name() + " for "
139+
throw new OperatorException("Couldn't register event source: " + name + " for "
133140
+ controller.getConfiguration().getName() + " controller`", e);
134141
} finally {
135142
lock.unlock();
@@ -161,7 +168,9 @@ EventHandler getEventHandler() {
161168
}
162169

163170
Set<EventSource> getRegisteredEventSources() {
164-
return eventSources.all();
171+
return eventSources.flatMappedSources()
172+
.map(NamedEventSource::original)
173+
.collect(Collectors.toCollection(LinkedHashSet::new));
165174
}
166175

167176
public ControllerResourceEventSource<R> getControllerResourceEventSource() {
@@ -191,8 +200,46 @@ Controller<R> getController() {
191200
return controller;
192201
}
193202

194-
private static class EventSources<R extends HasMetadata> implements Iterable<EventSource> {
195-
private final ConcurrentNavigableMap<String, List<EventSource>> sources =
203+
static class NamedEventSource implements EventSource {
204+
private final EventSource original;
205+
private final String name;
206+
207+
private NamedEventSource(EventSource original, String name) {
208+
this.original = original;
209+
this.name = name;
210+
}
211+
212+
@Override
213+
public void start() throws OperatorException {
214+
original.start();
215+
}
216+
217+
@Override
218+
public void stop() throws OperatorException {
219+
original.stop();
220+
}
221+
222+
@Override
223+
public void setEventHandler(EventHandler handler) {
224+
original.setEventHandler(handler);
225+
}
226+
227+
public String name() {
228+
return name;
229+
}
230+
231+
@Override
232+
public String toString() {
233+
return original + " named: '" + name + "'}";
234+
}
235+
236+
public EventSource original() {
237+
return original;
238+
}
239+
}
240+
241+
private static class EventSources<R extends HasMetadata> implements Iterable<NamedEventSource> {
242+
private final ConcurrentNavigableMap<String, Map<String, EventSource>> sources =
196243
new ConcurrentSkipListMap<>();
197244
private final TimerEventSource<R> retryAndRescheduleTimerEventSource = new TimerEventSource<>();
198245
private ControllerResourceEventSource<R> controllerResourceEventSource;
@@ -208,34 +255,34 @@ TimerEventSource<R> retryEventSource() {
208255
}
209256

210257
@Override
211-
public Iterator<EventSource> iterator() {
212-
return sources.values().stream().flatMap(Collection::stream).iterator();
258+
public Iterator<NamedEventSource> iterator() {
259+
return flatMappedSources().iterator();
213260
}
214261

215-
public Set<EventSource> all() {
216-
return sources.values().stream().flatMap(Collection::stream)
217-
.collect(Collectors.toCollection(LinkedHashSet::new));
262+
private Stream<NamedEventSource> flatMappedSources() {
263+
return sources.values().stream().flatMap(c -> c.entrySet().stream()
264+
.map(esEntry -> new NamedEventSource(esEntry.getValue(), esEntry.getKey())));
218265
}
219266

220267
public void clear() {
221268
sources.clear();
222269
}
223270

224-
public boolean contains(EventSource source) {
271+
public boolean contains(String name, EventSource source) {
225272
final var eventSources = sources.get(keyFor(source));
226273
if (eventSources == null || eventSources.isEmpty()) {
227274
return false;
228275
}
229-
return findMatchingSource(name(source), eventSources).isPresent();
276+
return eventSources.containsKey(name);
230277
}
231278

232-
public void add(EventSource eventSource) {
233-
if (contains(eventSource)) {
279+
public void add(String name, EventSource eventSource) {
280+
if (contains(name, eventSource)) {
234281
throw new IllegalArgumentException("An event source is already registered for the "
235-
+ keyAsString(getDependentType(eventSource), name(eventSource))
282+
+ keyAsString(getDependentType(eventSource), name)
236283
+ " class/name combination");
237284
}
238-
sources.computeIfAbsent(keyFor(eventSource), k -> new ArrayList<>()).add(eventSource);
285+
sources.computeIfAbsent(keyFor(eventSource), k -> new HashMap<>()).put(name, eventSource);
239286
}
240287

241288
@SuppressWarnings("rawtypes")
@@ -245,10 +292,6 @@ private Class<?> getDependentType(EventSource source) {
245292
: source.getClass();
246293
}
247294

248-
private String name(EventSource source) {
249-
return source.name();
250-
}
251-
252295
private String keyFor(EventSource source) {
253296
return keyFor(getDependentType(source));
254297
}
@@ -278,15 +321,15 @@ public <S> ResourceEventSource<R, S> get(Class<S> dependentType, String name) {
278321
final var size = sourcesForType.size();
279322
final EventSource source;
280323
if (size == 1) {
281-
source = sourcesForType.get(0);
324+
source = sourcesForType.values().stream().findFirst().orElse(null);
282325
} else {
283326
if (name == null || name.isBlank()) {
284327
throw new IllegalArgumentException("There are multiple EventSources registered for type "
285328
+ dependentType.getCanonicalName()
286329
+ ", you need to provide a name to specify which EventSource you want to query. Known names: "
287-
+ sourcesForType.stream().map(this::name).collect(Collectors.joining(",")));
330+
+ String.join(",", sourcesForType.keySet()));
288331
}
289-
source = findMatchingSource(name, sourcesForType).orElse(null);
332+
source = sourcesForType.get(name);
290333

291334
if (source == null) {
292335
return null;
@@ -309,11 +352,6 @@ public <S> ResourceEventSource<R, S> get(Class<S> dependentType, String name) {
309352
return res;
310353
}
311354

312-
private Optional<EventSource> findMatchingSource(String name,
313-
List<EventSource> sourcesForType) {
314-
return sourcesForType.stream().filter(es -> name(es).equals(name)).findAny();
315-
}
316-
317355
@SuppressWarnings("rawtypes")
318356
private String keyAsString(Class dependentType, String name) {
319357
return name != null && name.length() > 0

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

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,15 @@
1212
*/
1313
public interface EventSource extends LifecycleAware {
1414

15-
/**
16-
* An optional name for your EventSource. This is only required if you need to register multiple
17-
* EventSources for the same resource type (e.g. {@code Deployment}).
18-
*
19-
* @return the name associated with this EventSource
20-
*/
21-
default String name() {
22-
return getClass().getCanonicalName();
23-
}
24-
2515
/**
2616
* Sets the {@link EventHandler} that is linked to your reconciler when this EventSource is
2717
* registered.
2818
*
2919
* @param handler the {@link EventHandler} associated with your reconciler
3020
*/
3121
void setEventHandler(EventHandler handler);
22+
23+
static String defaultNameFor(EventSource source) {
24+
return source.getClass().getCanonicalName();
25+
}
3226
}

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

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.javaoperatorsdk.operator.processing.event;
22

3-
import java.io.IOException;
43
import java.util.Iterator;
54
import java.util.Optional;
65
import java.util.Set;
@@ -30,6 +29,7 @@
3029
import static org.mockito.Mockito.verify;
3130
import static org.mockito.Mockito.when;
3231

32+
@SuppressWarnings({"rawtypes", "unchecked"})
3333
class EventSourceManagerTest {
3434

3535
private final EventProcessor eventHandler = mock(EventProcessor.class);
@@ -48,7 +48,7 @@ public void registersEventSource() {
4848
}
4949

5050
@Test
51-
public void closeShouldCascadeToEventSources() throws IOException {
51+
public void closeShouldCascadeToEventSources() {
5252
EventSource eventSource = mock(EventSource.class);
5353
EventSource eventSource2 = mock(TimerEventSource.class);
5454

@@ -97,23 +97,23 @@ void retrievingEventSourceForClassShouldWork() {
9797
@Test
9898
void shouldNotBePossibleToAddEventSourcesForSameTypeAndName() {
9999
EventSourceManager manager = initManager();
100+
final var name = "name1";
100101

101102
CachingEventSource eventSource = mock(CachingEventSource.class);
102103
when(eventSource.getResourceClass()).thenReturn(TestCustomResource.class);
103-
when(eventSource.name()).thenReturn("name1");
104-
manager.registerEventSource(eventSource);
104+
manager.registerEventSource(name, eventSource);
105105

106106
eventSource = mock(CachingEventSource.class);
107107
when(eventSource.getResourceClass()).thenReturn(TestCustomResource.class);
108-
when(eventSource.name()).thenReturn("name1");
109108
final var source = eventSource;
110109

111110
final var exception = assertThrows(OperatorException.class,
112-
() -> manager.registerEventSource(source));
111+
() -> manager.registerEventSource(name, source));
113112
final var cause = exception.getCause();
114113
assertTrue(cause instanceof IllegalArgumentException);
115114
assertThat(cause.getMessage()).contains(
116-
"An event source is already registered for the (io.javaoperatorsdk.operator.sample.simple.TestCustomResource, name1) class/name combination");
115+
"An event source is already registered for the (io.javaoperatorsdk.operator.sample.simple.TestCustomResource, "
116+
+ name + ") class/name combination");
117117
}
118118

119119
@Test
@@ -122,13 +122,11 @@ void retrievingAnEventSourceWhenMultipleAreRegisteredForATypeShouldRequireAQuali
122122

123123
CachingEventSource eventSource = mock(CachingEventSource.class);
124124
when(eventSource.getResourceClass()).thenReturn(TestCustomResource.class);
125-
when(eventSource.name()).thenReturn("name1");
126-
manager.registerEventSource(eventSource);
125+
manager.registerEventSource("name1", eventSource);
127126

128127
CachingEventSource eventSource2 = mock(CachingEventSource.class);
129128
when(eventSource2.getResourceClass()).thenReturn(TestCustomResource.class);
130-
when(eventSource2.name()).thenReturn("name2");
131-
manager.registerEventSource(eventSource2);
129+
manager.registerEventSource("name2", eventSource2);
132130

133131
final var exception = assertThrows(IllegalArgumentException.class,
134132
() -> manager.getResourceEventSourceFor(TestCustomResource.class));

0 commit comments

Comments
 (0)