Skip to content

Commit c4b6991

Browse files
committed
wip
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
1 parent d91f05e commit c4b6991

7 files changed

Lines changed: 259 additions & 179 deletions

File tree

‎operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerManager.java‎

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,12 @@ public void changeNamespaces(Set<String> namespaces) {
119119
log.debug("Stopped informer {} for namespaces: {}", this, sourcesToRemove);
120120
// todo remove event handler
121121
// remove from pool
122-
sourcesToRemove.forEach(k -> sources.remove(k).stop());
122+
sourcesToRemove.forEach(
123+
k -> {
124+
informerPool.releaseInformer(
125+
configuration.getInformerConfig().getName(), getClassifier(k));
126+
sources.remove(k);
127+
});
123128

124129
var newNamespaces =
125130
namespaces.stream().filter(ns -> !sources.containsKey(ns)).collect(Collectors.toList());
@@ -134,7 +139,6 @@ public void changeNamespaces(Set<String> namespaces) {
134139
newNamespaces.stream(),
135140
ns -> {
136141
final var source = createEventSourceForNamespace(ns);
137-
source.start();
138142
log.debug("Registered new {} -> {} for namespace: {}", this, source, ns);
139143
return null;
140144
},
@@ -143,19 +147,9 @@ public void changeNamespaces(Set<String> namespaces) {
143147

144148
private InformerWrapper<R> createEventSourceForNamespace(String namespaceIdentifier) {
145149
final InformerWrapper<R> source;
146-
147-
InformerClassifier<R> classifier =
148-
new InformerClassifier<>(
149-
configuration.getInformerConfig().getLabelSelector(),
150-
configuration.getInformerConfig().getShardSelector(),
151-
namespaceIdentifier,
152-
configuration.getResourceClass(),
153-
configuration.getInformerConfig().getFieldSelector(),
154-
configuration.getInformerConfig().getInformerListLimit(),
155-
configuration.getInformerConfig().getItemStore());
150+
InformerClassifier<R> classifier = getClassifier(namespaceIdentifier);
156151
var informer =
157152
informerPool.getInformer(configuration.getInformerConfig().getName(), classifier);
158-
159153
source =
160154
new InformerWrapper<>(
161155
informer, namespaceIdentifier, controllerConfiguration.getConfigurationService());
@@ -165,6 +159,17 @@ private InformerWrapper<R> createEventSourceForNamespace(String namespaceIdentif
165159
return source;
166160
}
167161

162+
private InformerClassifier<R> getClassifier(String namespaceIdentifier) {
163+
return new InformerClassifier<>(
164+
configuration.getInformerConfig().getLabelSelector(),
165+
configuration.getInformerConfig().getShardSelector(),
166+
namespaceIdentifier,
167+
configuration.getResourceClass(),
168+
configuration.getInformerConfig().getFieldSelector(),
169+
configuration.getInformerConfig().getInformerListLimit(),
170+
configuration.getInformerConfig().getItemStore());
171+
}
172+
168173
@Override
169174
public void stop() {
170175
sources.forEach(

‎operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerWrapper.java‎

Lines changed: 2 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,18 @@
1818
import java.util.List;
1919
import java.util.Map;
2020
import java.util.Optional;
21-
import java.util.concurrent.ExecutionException;
22-
import java.util.concurrent.TimeUnit;
23-
import java.util.concurrent.TimeoutException;
2421
import java.util.function.Function;
2522
import java.util.function.Predicate;
2623
import java.util.stream.Stream;
2724

2825
import org.slf4j.Logger;
2926
import org.slf4j.LoggerFactory;
3027

31-
import io.fabric8.kubernetes.api.model.GenericKubernetesResource;
3228
import io.fabric8.kubernetes.api.model.HasMetadata;
3329
import io.fabric8.kubernetes.client.informers.ResourceEventHandler;
3430
import io.fabric8.kubernetes.client.informers.SharedIndexInformer;
3531
import io.fabric8.kubernetes.client.informers.cache.Cache;
3632
import io.javaoperatorsdk.operator.OperatorException;
37-
import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
3833
import io.javaoperatorsdk.operator.api.config.ConfigurationService;
3934
import io.javaoperatorsdk.operator.health.InformerHealthIndicator;
4035
import io.javaoperatorsdk.operator.health.Status;
@@ -64,56 +59,7 @@ public InformerWrapper(
6459

6560
// todo sync running multiple informers
6661
@Override
67-
public void start() {
68-
// change thread name for easier debugging
69-
final var thread = Thread.currentThread();
70-
final var name = thread.getName();
71-
try {
72-
thread.setName(informerInfo() + " " + thread.getId());
73-
final var resourceName = informer.getApiTypeClass().getSimpleName();
74-
log.debug(
75-
"Starting informer for namespace: {} resource: {}", namespaceIdentifier, resourceName);
76-
var start = informer.start();
77-
// note that in case we don't put here timeout and stopOnInformerErrorDuringStartup is
78-
// false, and there is a rbac issue the get never returns; therefore operator never really
79-
// starts
80-
log.trace(
81-
"Waiting informer to start namespace: {} resource: {}",
82-
namespaceIdentifier,
83-
resourceName);
84-
start
85-
.toCompletableFuture()
86-
.get(configurationService.cacheSyncTimeout().toMillis(), TimeUnit.MILLISECONDS);
87-
log.debug(
88-
"Started informer for namespace: {} resource: {}", namespaceIdentifier, resourceName);
89-
} catch (TimeoutException | ExecutionException e) {
90-
if (configurationService.stopOnInformerErrorDuringStartup()) {
91-
log.error("Informer startup error. Operator will be stopped. Informer: {}", informer, e);
92-
throw new OperatorException(e);
93-
} else {
94-
log.warn("Informer startup error. Will periodically retry. Informer: {}", informer, e);
95-
}
96-
} catch (InterruptedException e) {
97-
thread.interrupt();
98-
throw new IllegalStateException(e);
99-
} catch (Exception e) {
100-
ReconcilerUtilsInternal.handleKubernetesClientException(
101-
e, HasMetadata.getFullResourceName(informer.getApiTypeClass()));
102-
throw new OperatorException(
103-
"Couldn't start informer for " + versionedFullResourceName() + " resources", e);
104-
} finally {
105-
// restore original name
106-
thread.setName(name);
107-
}
108-
}
109-
110-
private String versionedFullResourceName() {
111-
final var apiTypeClass = informer.getApiTypeClass();
112-
if (apiTypeClass.isAssignableFrom(GenericKubernetesResource.class)) {
113-
return GenericKubernetesResource.class.getSimpleName();
114-
}
115-
return ReconcilerUtilsInternal.getResourceTypeNameWithVersion(apiTypeClass);
116-
}
62+
public void start() {}
11763

11864
@Override
11965
public void stop() throws OperatorException {
@@ -170,7 +116,7 @@ public String toString() {
170116
}
171117

172118
private String informerInfo() {
173-
return "InformerWrapper [" + informer.getApiTypeClass().getSimpleName() + "]";
119+
return "InformerWrapper [informerInfo" + informer.getApiTypeClass().getSimpleName() + "]";
174120
}
175121

176122
@Override

‎operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/pool/AbstractInformerPool.java‎

Lines changed: 179 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,182 @@
1515
*/
1616
package io.javaoperatorsdk.operator.processing.event.source.informer.pool;
1717

18-
public class AbstractInformerPool {}
18+
import java.util.Optional;
19+
import java.util.concurrent.ExecutionException;
20+
import java.util.concurrent.TimeUnit;
21+
import java.util.concurrent.TimeoutException;
22+
23+
import org.slf4j.Logger;
24+
import org.slf4j.LoggerFactory;
25+
26+
import io.fabric8.kubernetes.api.model.GenericKubernetesResource;
27+
import io.fabric8.kubernetes.api.model.HasMetadata;
28+
import io.fabric8.kubernetes.client.KubernetesClient;
29+
import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable;
30+
import io.fabric8.kubernetes.client.informers.ExceptionHandler;
31+
import io.fabric8.kubernetes.client.informers.SharedIndexInformer;
32+
import io.javaoperatorsdk.operator.OperatorException;
33+
import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
34+
import io.javaoperatorsdk.operator.api.config.ConfigurationService;
35+
36+
import static io.javaoperatorsdk.operator.api.reconciler.Constants.WATCH_ALL_NAMESPACES;
37+
38+
public abstract class AbstractInformerPool implements InformerPool {
39+
40+
private static final Logger log = LoggerFactory.getLogger(AbstractInformerPool.class);
41+
42+
protected KubernetesClient client;
43+
protected ConfigurationService configurationService;
44+
45+
public KubernetesClient getClient() {
46+
return client;
47+
}
48+
49+
public void setClient(KubernetesClient client) {
50+
this.client = client;
51+
}
52+
53+
public ConfigurationService getConfigurationService() {
54+
return configurationService;
55+
}
56+
57+
public void setConfigurationService(ConfigurationService configurationService) {
58+
this.configurationService = configurationService;
59+
}
60+
61+
@SuppressWarnings({"rawtypes", "unchecked"})
62+
protected SharedIndexInformer createInformer(InformerClassifier<?> classifier) {
63+
FilterWatchListDeletable filteredClient;
64+
65+
if (WATCH_ALL_NAMESPACES.equals(classifier.namespaceIdentifier())) {
66+
filteredClient = client.resources(classifier.resourceClass()).inAnyNamespace();
67+
68+
} else {
69+
filteredClient =
70+
client
71+
.resources(classifier.resourceClass())
72+
.inNamespace(classifier.namespaceIdentifier())
73+
.withLabelSelector(classifier.labelSelector())
74+
.withShardSelector(classifier.shardSelector());
75+
}
76+
77+
if (classifier.labelSelector() != null) {
78+
filteredClient =
79+
(FilterWatchListDeletable) filteredClient.withLabelSelector(classifier.labelSelector());
80+
}
81+
if (classifier.shardSelector() != null) {
82+
filteredClient =
83+
(FilterWatchListDeletable) filteredClient.withShardSelector(classifier.shardSelector());
84+
}
85+
86+
if (classifier.fieldSelector() != null && !classifier.fieldSelector().getFields().isEmpty()) {
87+
for (var f : classifier.fieldSelector().getFields()) {
88+
if (f.negated()) {
89+
filteredClient =
90+
(FilterWatchListDeletable) filteredClient.withoutField(f.path(), f.value());
91+
} else {
92+
filteredClient = (FilterWatchListDeletable) filteredClient.withField(f.path(), f.value());
93+
}
94+
}
95+
}
96+
97+
if (classifier.informerListLimit() != null) {
98+
filteredClient =
99+
(FilterWatchListDeletable) filteredClient.withLimit(classifier.informerListLimit());
100+
}
101+
102+
var informer = filteredClient.runnableInformer(0);
103+
104+
Optional.ofNullable(classifier.itemStore()).ifPresent(informer::itemStore);
105+
106+
configurationService
107+
.getInformerStoppedHandler()
108+
.ifPresent(
109+
ish -> {
110+
final var stopped = informer.stopped();
111+
if (stopped != null) {
112+
stopped.handle(
113+
(res, ex) -> {
114+
ish.onStop(informer, (Throwable) ex);
115+
return null;
116+
});
117+
} else {
118+
final var apiTypeClass = informer.getApiTypeClass();
119+
final var fullResourceName = HasMetadata.getFullResourceName(apiTypeClass);
120+
final var version = HasMetadata.getVersion(apiTypeClass);
121+
throw new IllegalStateException(
122+
"Cannot retrieve 'stopped' callback to listen to informer stopping for"
123+
+ " informer for "
124+
+ fullResourceName
125+
+ "/"
126+
+ version);
127+
}
128+
});
129+
if (!configurationService.stopOnInformerErrorDuringStartup()) {
130+
informer.exceptionHandler((b, t) -> !ExceptionHandler.isDeserializationException(t));
131+
}
132+
return informer;
133+
}
134+
135+
protected void start(
136+
SharedIndexInformer<? extends HasMetadata> informer,
137+
InformerClassifier<?> informerClassifier) {
138+
// change thread name for easier debugging
139+
final var thread = Thread.currentThread();
140+
final var name = thread.getName();
141+
try {
142+
thread.setName(
143+
"InformerInfo[informerInfo"
144+
+ informer.getApiTypeClass().getSimpleName()
145+
+ "]"
146+
+ " "
147+
+ thread.getId());
148+
final var resourceName = informer.getApiTypeClass().getSimpleName();
149+
log.debug(
150+
"Starting informer for namespace: {} resource: {}",
151+
informerClassifier.namespaceIdentifier(),
152+
resourceName);
153+
var start = informer.start();
154+
// note that in case we don't put here timeout and stopOnInformerErrorDuringStartup is
155+
// false, and there is a rbac issue the get never returns; therefore operator never really
156+
// starts
157+
log.trace(
158+
"Waiting informer to start namespace: {} resource: {}",
159+
informerClassifier.namespaceIdentifier(),
160+
resourceName);
161+
start
162+
.toCompletableFuture()
163+
.get(configurationService.cacheSyncTimeout().toMillis(), TimeUnit.MILLISECONDS);
164+
log.debug(
165+
"Started informer for namespace: {} resource: {}",
166+
informerClassifier.namespaceIdentifier(),
167+
resourceName);
168+
} catch (TimeoutException | ExecutionException e) {
169+
if (configurationService.stopOnInformerErrorDuringStartup()) {
170+
log.error("Informer startup error. Operator will be stopped. Informer: {}", informer, e);
171+
throw new OperatorException(e);
172+
} else {
173+
log.warn("Informer startup error. Will periodically retry. Informer: {}", informer, e);
174+
}
175+
} catch (InterruptedException e) {
176+
thread.interrupt();
177+
throw new IllegalStateException(e);
178+
} catch (Exception e) {
179+
ReconcilerUtilsInternal.handleKubernetesClientException(
180+
e, HasMetadata.getFullResourceName(informer.getApiTypeClass()));
181+
throw new OperatorException(
182+
"Couldn't start informer for " + versionedFullResourceName(informer) + " resources", e);
183+
} finally {
184+
// restore original name
185+
thread.setName(name);
186+
}
187+
}
188+
189+
private String versionedFullResourceName(SharedIndexInformer<? extends HasMetadata> informer) {
190+
final var apiTypeClass = informer.getApiTypeClass();
191+
if (apiTypeClass.isAssignableFrom(GenericKubernetesResource.class)) {
192+
return GenericKubernetesResource.class.getSimpleName();
193+
}
194+
return ReconcilerUtilsInternal.getResourceTypeNameWithVersion(apiTypeClass);
195+
}
196+
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/pool/NoOpInformerPool.java renamed to operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/pool/AlwaysCreateInformerPool.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,34 @@
1515
*/
1616
package io.javaoperatorsdk.operator.processing.event.source.informer.pool;
1717

18+
import java.util.Map;
19+
import java.util.concurrent.ConcurrentHashMap;
20+
1821
import io.fabric8.kubernetes.api.model.HasMetadata;
1922
import io.fabric8.kubernetes.client.informers.SharedIndexInformer;
2023

21-
public class NoOpInformerPool implements InformerPool {
24+
@SuppressWarnings({"unchecked", "rawtypes"})
25+
public class AlwaysCreateInformerPool extends AbstractInformerPool {
26+
27+
private Map<ClassifierWithName, SharedIndexInformer> informers = new ConcurrentHashMap();
2228

2329
@Override
2430
public <R extends HasMetadata> SharedIndexInformer<R> getInformer(
2531
String name, InformerClassifier<R> classifier) {
26-
return null;
32+
var informer = createInformer(classifier);
33+
start(informer, classifier);
34+
informers.put(new ClassifierWithName(name, classifier), informer);
35+
return informer;
2736
}
2837

38+
// todo controller name should be added here? for better naming => referencing and have guaranteed
39+
// uniqueness
2940
@Override
3041
public <R extends HasMetadata> void releaseInformer(
42+
String name, InformerClassifier<R> classifier) {
43+
informers.get(new ClassifierWithName(name, classifier)).stop();
44+
}
45+
46+
public record ClassifierWithName<R extends HasMetadata>(
3147
String name, InformerClassifier<R> classifier) {}
3248
}

0 commit comments

Comments
 (0)