Skip to content

Commit f69b343

Browse files
authored
Merge pull request #3856 from Sud0x67/useCachedInformer
Enable the reuse of cached informers on creation by `SharedInformerFactory`.
2 parents 9dcc17a + 8973a20 commit f69b343

File tree

2 files changed

+75
-28
lines changed

2 files changed

+75
-28
lines changed

util/src/main/java/io/kubernetes/client/informer/SharedInformerFactory.java

Lines changed: 47 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,24 @@
2727
import io.kubernetes.client.util.Watchable;
2828
import io.kubernetes.client.util.generic.GenericKubernetesApi;
2929
import io.kubernetes.client.util.generic.options.ListOptions;
30+
import okhttp3.Call;
31+
import org.apache.commons.collections4.MapUtils;
32+
3033
import java.lang.reflect.Type;
3134
import java.util.HashMap;
3235
import java.util.Map;
3336
import java.util.concurrent.ExecutorService;
3437
import java.util.concurrent.Executors;
3538
import java.util.concurrent.Future;
3639
import java.util.function.BiConsumer;
37-
import okhttp3.Call;
38-
import org.apache.commons.collections4.MapUtils;
3940

40-
/** SharedInformerFactory class constructs and caches informers for api types. */
41+
/**
42+
* SharedInformerFactory class constructs and caches informers for api types.
43+
* For each api type, only the first created informer is stored in the cache.
44+
* If reuseExistingCachedInformer is set to true, It returns the cached informer when creating
45+
* an informer for the same api type.
46+
* Otherwise, this factory creates a new informer on each invocation.
47+
*/
4148
public class SharedInformerFactory {
4249

4350
protected Map<Type, SharedIndexInformer> informers;
@@ -47,35 +54,54 @@ public class SharedInformerFactory {
4754
private ExecutorService informerExecutor;
4855

4956
private ApiClient apiClient;
57+
private boolean reuseExistingCachedInformer;
5058

5159
/** Constructor w/ default thread pool. */
5260
/** DEPRECATE: In favor of explicit apiClient constructor to avoid misguiding */
5361
@Deprecated
5462
public SharedInformerFactory() {
55-
this(Configuration.getDefaultApiClient().setReadTimeout(0), Executors.newCachedThreadPool());
63+
this(Configuration.getDefaultApiClient().setReadTimeout(0), Executors.newCachedThreadPool(), false);
5664
}
5765

58-
/** Constructor w/ api client specified and default thread pool. */
66+
/**
67+
* Constructor w/ api client specified, default thread pool and reuseExistingCachedInformer is false.
68+
*/
5969
public SharedInformerFactory(ApiClient apiClient) {
60-
this(apiClient, Executors.newCachedThreadPool());
70+
this(apiClient, false);
71+
}
72+
73+
/**
74+
* Constructor w/ api client specified and default thread pool
75+
* @param apiClient specific api client
76+
* @param reuseCache Determines whether to utilize an existing cached informer.
77+
* If true, the method returns the first registered informer for multiple creations of the same apiClassType,
78+
* else, each method calls results in the creation of a new informer,
79+
* while only the first informer is stored in the cache.
80+
*/
81+
public SharedInformerFactory(ApiClient apiClient, boolean reuseCache) {
82+
this(apiClient, Executors.newCachedThreadPool(), reuseCache);
6183
}
6284

6385
/**
6486
* Constructor w/ thread pool specified.
65-
*
66-
* @param threadPool specified thread pool
87+
* DEPRECATE: In favor of explicit apiClient constructor to avoid misguiding.
6788
*/
89+
@Deprecated
6890
public SharedInformerFactory(ExecutorService threadPool) {
69-
this(Configuration.getDefaultApiClient().setReadTimeout(0), threadPool);
91+
this(Configuration.getDefaultApiClient().setReadTimeout(0),threadPool, false);
7092
}
7193

7294
/**
7395
* Constructor w/ api client and thread pool specified.
7496
*
7597
* @param client specific api client
7698
* @param threadPool specified thread pool
99+
* @param reuseCache Determines whether to utilize an existing cached informer.
100+
* If true, the method returns the first registered informer for multiple creations of the same apiClassType,
101+
* else, each method calls results in the creation of a new informer,
102+
* while only the first informer is stored in the cache.
77103
*/
78-
public SharedInformerFactory(ApiClient client, ExecutorService threadPool) {
104+
public SharedInformerFactory(ApiClient client, ExecutorService threadPool, boolean reuseCache) {
79105
if (client.getReadTimeout() != 0) {
80106
throw new IllegalArgumentException("read timeout of ApiClient must be zero");
81107
}
@@ -84,6 +110,7 @@ public SharedInformerFactory(ApiClient client, ExecutorService threadPool) {
84110
informerExecutor = threadPool;
85111
informers = new HashMap<>();
86112
startedInformers = new HashMap<>();
113+
reuseExistingCachedInformer = reuseCache;
87114
}
88115

89116
/**
@@ -105,8 +132,7 @@ SharedIndexInformer<ApiType> sharedIndexInformerFor(
105132
}
106133

107134
/**
108-
* Constructs and returns a shared index informer w/ resync period specified. But the informer
109-
* cache will not be overwritten i.e. only the first registered informer will be kept.
135+
* Constructs and returns a shared index informer w/ resync period specified.
110136
*
111137
* @param <ApiType> the type parameter
112138
* @param <ApiListType> the type parameter
@@ -151,9 +177,7 @@ SharedIndexInformer<ApiType> sharedIndexInformerFor(
151177
}
152178

153179
/**
154-
* Constructs and returns a shared index informer by specifying lister-watcher. But the informer
155-
* cache will not be overwritten on multiple call w/ the the same apiTypeClass i.e. only the first
156-
* registered informer will be kept.
180+
* Constructs and returns a shared index informer by specifying lister-watcher.
157181
*
158182
* @param <ApiType> the type parameter
159183
* @param <ApiListType> the type parameter
@@ -176,18 +200,22 @@ SharedIndexInformer<ApiType> sharedIndexInformerFor(
176200
Class<ApiType> apiTypeClass,
177201
long resyncPeriodInMillis,
178202
BiConsumer<Class<ApiType>, Throwable> exceptionHandler) {
203+
Type apiType = TypeToken.get(apiTypeClass).getType();
204+
205+
if(informers.containsKey(apiType) && reuseExistingCachedInformer) {
206+
return informers.get(apiType);
207+
}
179208

180209
SharedIndexInformer<ApiType> informer =
181210
new DefaultSharedIndexInformer<>(
182211
apiTypeClass, listerWatcher, resyncPeriodInMillis, new Cache<>(), exceptionHandler);
183-
this.informers.putIfAbsent(TypeToken.get(apiTypeClass).getType(), informer);
212+
213+
this.informers.putIfAbsent(apiType, informer);
184214
return informer;
185215
}
186216

187217
/**
188-
* Constructs and returns a shared index informer by specifying a generic api instance. But the
189-
* informer cache will not be overwritten on multiple call w/ the the same apiTypeClass i.e. only
190-
* the first registered informer will be kept.
218+
* Constructs and returns a shared index informer by specifying a generic api instance.
191219
*
192220
* @param <ApiType> the type parameter
193221
* @param <ApiListType> the type parameter

util/src/test/java/io/kubernetes/client/informer/SharedInformerFactoryTest.java

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,7 @@
1212
*/
1313
package io.kubernetes.client.informer;
1414

15-
import static org.assertj.core.api.Assertions.assertThat;
16-
import static org.awaitility.Awaitility.await;
17-
import static org.mockito.Mockito.any;
18-
import static org.mockito.Mockito.atLeastOnce;
19-
import static org.mockito.Mockito.eq;
20-
import static org.mockito.Mockito.verify;
21-
import static org.mockito.Mockito.when;
22-
15+
import io.kubernetes.client.openapi.ApiClient;
2316
import io.kubernetes.client.openapi.ApiException;
2417
import io.kubernetes.client.openapi.apis.CoreV1Api;
2518
import io.kubernetes.client.openapi.models.V1ListMeta;
@@ -28,15 +21,26 @@
2821
import io.kubernetes.client.openapi.models.V1Pod;
2922
import io.kubernetes.client.openapi.models.V1PodList;
3023
import io.kubernetes.client.util.CallGeneratorParams;
24+
import io.kubernetes.client.util.Config;
3125
import io.kubernetes.client.util.generic.GenericKubernetesApi;
3226
import io.kubernetes.client.util.generic.KubernetesApiResponse;
3327
import io.kubernetes.client.util.generic.options.ListOptions;
34-
import java.time.Duration;
3528
import org.junit.jupiter.api.Test;
3629
import org.junit.jupiter.api.extension.ExtendWith;
3730
import org.mockito.Mock;
3831
import org.mockito.junit.jupiter.MockitoExtension;
3932

33+
import java.io.IOException;
34+
import java.time.Duration;
35+
36+
import static org.assertj.core.api.Assertions.assertThat;
37+
import static org.awaitility.Awaitility.await;
38+
import static org.mockito.Mockito.any;
39+
import static org.mockito.Mockito.atLeastOnce;
40+
import static org.mockito.Mockito.eq;
41+
import static org.mockito.Mockito.verify;
42+
import static org.mockito.Mockito.when;
43+
4044
@ExtendWith(MockitoExtension.class)
4145
class SharedInformerFactoryTest {
4246

@@ -96,4 +100,19 @@ void namespaceScopedNewInformerUsingGenericApi() {
96100
await().timeout(Duration.ofSeconds(2)).until(podInformer::hasSynced);
97101
verify(genericKubernetesApi, atLeastOnce()).list(eq("default"), any(ListOptions.class));
98102
}
103+
104+
@Test
105+
void createInformerMutipleTimesUseCache() throws IOException {
106+
ApiClient client = Config.defaultClient();
107+
SharedInformerFactory factory = new SharedInformerFactory(client, true);
108+
SharedInformer<V1Pod> podInformer = null;
109+
for (int i = 0; i < 10; i++) {
110+
podInformer =
111+
factory.sharedIndexInformerFor(genericKubernetesApi, V1Pod.class, 0, "default");
112+
}
113+
SharedInformer<V1Pod> cachedInformer = factory.getExistingSharedIndexInformer(V1Pod.class);
114+
assertThat(cachedInformer).isNotNull();
115+
assertThat(cachedInformer == podInformer).isTrue();
116+
}
117+
99118
}

0 commit comments

Comments
 (0)