Skip to content

Commit 1b25c52

Browse files
committed
feature: support namespace-scoped informer
1 parent 3cf16a9 commit 1b25c52

File tree

12 files changed

+206
-101
lines changed

12 files changed

+206
-101
lines changed

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,12 @@
306306
<version>4.0.3</version>
307307
<scope>test</scope>
308308
</dependency>
309+
<dependency>
310+
<groupId>org.assertj</groupId>
311+
<artifactId>assertj-core</artifactId>
312+
<version>3.18.1</version>
313+
<scope>test</scope>
314+
</dependency>
309315

310316
</dependencies>
311317
</dependencyManagement>

spring/src/main/java/io/kubernetes/client/spring/extended/controller/KubernetesInformerFactoryProcessor.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@
2424
import org.slf4j.Logger;
2525
import org.slf4j.LoggerFactory;
2626
import org.springframework.beans.BeansException;
27+
import org.springframework.beans.factory.annotation.Autowired;
2728
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
2829
import org.springframework.beans.factory.support.AbstractBeanDefinition;
2930
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
3031
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
3132
import org.springframework.beans.factory.support.RootBeanDefinition;
3233
import org.springframework.core.Ordered;
3334
import org.springframework.core.ResolvableType;
34-
import org.springframework.stereotype.Component;
3535

3636
/**
3737
* The type Kubernetes informer factory processor which basically does the following things:
@@ -41,7 +41,6 @@
4141
* io.kubernetes.client.spring.extended.controller.annotation.KubernetesInformers}, instantiates and
4242
* injects informers to spring context with the underlying constructing process hidden from users.
4343
*/
44-
@Component
4544
public class KubernetesInformerFactoryProcessor
4645
implements BeanDefinitionRegistryPostProcessor, Ordered {
4746

@@ -55,6 +54,7 @@ public class KubernetesInformerFactoryProcessor
5554
private final ApiClient apiClient;
5655
private final SharedInformerFactory sharedInformerFactory;
5756

57+
@Autowired
5858
public KubernetesInformerFactoryProcessor(
5959
ApiClient apiClient, SharedInformerFactory sharedInformerFactory) {
6060
this.apiClient = apiClient;
@@ -85,7 +85,10 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
8585
apiClient);
8686
SharedIndexInformer sharedIndexInformer =
8787
sharedInformerFactory.sharedIndexInformerFor(
88-
api, kubernetesInformer.apiTypeClass(), kubernetesInformer.resyncPeriodMillis());
88+
api,
89+
kubernetesInformer.apiTypeClass(),
90+
kubernetesInformer.resyncPeriodMillis(),
91+
kubernetesInformer.namespace());
8992
ResolvableType informerType =
9093
ResolvableType.forClassWithGenerics(
9194
SharedInformer.class, kubernetesInformer.apiTypeClass());

spring/src/main/java/io/kubernetes/client/spring/extended/controller/KubernetesReconcilerProcessor.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
2727
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
2828
import org.springframework.core.Ordered;
29-
import org.springframework.stereotype.Component;
3029

3130
/**
3231
* Scans and processes {@link
@@ -35,7 +34,6 @@
3534
* <p>It will create a {@link io.kubernetes.client.extended.controller.Controller} for every
3635
* reconciler instances registered in the spring bean-factory.
3736
*/
38-
@Component
3937
public class KubernetesReconcilerProcessor implements BeanFactoryPostProcessor, Ordered {
4038

4139
private static final Logger log = LoggerFactory.getLogger(KubernetesReconcilerProcessor.class);

spring/src/main/java/io/kubernetes/client/spring/extended/controller/annotation/KubernetesInformer.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import io.kubernetes.client.common.KubernetesObject;
1717
import io.kubernetes.client.openapi.models.V1Namespace;
1818
import io.kubernetes.client.openapi.models.V1NamespaceList;
19+
import io.kubernetes.client.util.Namespaces;
1920
import java.lang.annotation.ElementType;
2021
import java.lang.annotation.Retention;
2122
import java.lang.annotation.RetentionPolicy;
@@ -58,4 +59,11 @@
5859
* @return the long
5960
*/
6061
long resyncPeriodMillis() default 0;
62+
63+
/**
64+
* Target namespace to list-watch, by default it will be cluster-scoped.
65+
*
66+
* @return the string
67+
*/
68+
String namespace() default Namespaces.NAMESPACE_ALL;
6169
}

spring/src/test/java/io/kubernetes/client/spring/extended/controller/KubernetesInformerCreatorTest.java

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,7 @@
2828
import io.kubernetes.client.informer.SharedInformerFactory;
2929
import io.kubernetes.client.informer.cache.Lister;
3030
import io.kubernetes.client.openapi.ApiClient;
31-
import io.kubernetes.client.openapi.models.V1ConfigMap;
32-
import io.kubernetes.client.openapi.models.V1ConfigMapList;
33-
import io.kubernetes.client.openapi.models.V1ListMeta;
34-
import io.kubernetes.client.openapi.models.V1ObjectMeta;
35-
import io.kubernetes.client.openapi.models.V1Pod;
36-
import io.kubernetes.client.openapi.models.V1PodList;
31+
import io.kubernetes.client.openapi.models.*;
3732
import io.kubernetes.client.spring.extended.controller.annotation.GroupVersionResource;
3833
import io.kubernetes.client.spring.extended.controller.annotation.KubernetesInformer;
3934
import io.kubernetes.client.spring.extended.controller.annotation.KubernetesInformers;
@@ -43,21 +38,21 @@
4338
import org.junit.Test;
4439
import org.junit.runner.RunWith;
4540
import org.springframework.beans.factory.annotation.Autowired;
41+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
42+
import org.springframework.boot.autoconfigure.SpringBootApplication;
4643
import org.springframework.boot.test.context.SpringBootTest;
47-
import org.springframework.boot.test.context.TestConfiguration;
4844
import org.springframework.context.annotation.Bean;
49-
import org.springframework.context.annotation.Import;
5045
import org.springframework.test.context.junit4.SpringRunner;
5146

5247
@RunWith(SpringRunner.class)
53-
@SpringBootTest
54-
@Import(KubernetesInformerCreatorTest.TestConfig.class)
48+
@SpringBootTest(classes = {KubernetesInformerCreatorTest.App.class})
5549
public class KubernetesInformerCreatorTest {
5650

5751
@Rule public WireMockRule wireMockRule = new WireMockRule(8188);
5852

59-
@TestConfiguration
60-
static class TestConfig {
53+
@SpringBootApplication
54+
@EnableAutoConfiguration
55+
static class App {
6156

6257
@Bean
6358
public ApiClient testingApiClient() {
@@ -70,12 +65,6 @@ public SharedInformerFactory sharedInformerFactory() {
7065
return new TestSharedInformerFactory();
7166
}
7267

73-
@Bean
74-
public KubernetesInformerConfigurer kubernetesInformerConfigurer(
75-
ApiClient apiClient, SharedInformerFactory sharedInformerFactory) {
76-
return new KubernetesInformerConfigurer(apiClient, sharedInformerFactory);
77-
}
78-
7968
@KubernetesInformers({
8069
@KubernetesInformer(
8170
apiTypeClass = V1Pod.class,
@@ -85,6 +74,7 @@ public KubernetesInformerConfigurer kubernetesInformerConfigurer(
8574
@KubernetesInformer(
8675
apiTypeClass = V1ConfigMap.class,
8776
apiListTypeClass = V1ConfigMapList.class,
77+
namespace = "default",
8878
groupVersionResource =
8979
@GroupVersionResource(
9080
apiGroup = "",
@@ -138,7 +128,7 @@ public void testInformerInjection() throws InterruptedException {
138128
.willReturn(aResponse().withStatus(200).withBody("{}")));
139129

140130
wireMockRule.stubFor(
141-
get(urlMatching("^/api/v1/configmaps.*"))
131+
get(urlMatching("^/api/v1/namespaces/default/configmaps.*"))
142132
.withQueryParam("watch", equalTo("false"))
143133
.willReturn(
144134
aResponse()
@@ -150,7 +140,7 @@ public void testInformerInjection() throws InterruptedException {
150140
.metadata(new V1ListMeta().resourceVersion("0"))
151141
.items(Arrays.asList(bar1))))));
152142
wireMockRule.stubFor(
153-
get(urlMatching("^/api/v1/configmaps.*"))
143+
get(urlMatching("^/api/v1/namespaces/default/configmaps.*"))
154144
.withQueryParam("watch", equalTo("true"))
155145
.willReturn(aResponse().withStatus(200).withBody("{}")));
156146

@@ -165,10 +155,10 @@ public void testInformerInjection() throws InterruptedException {
165155
getRequestedFor(urlPathEqualTo("/api/v1/pods")).withQueryParam("watch", equalTo("true")));
166156
verify(
167157
1,
168-
getRequestedFor(urlPathEqualTo("/api/v1/configmaps"))
158+
getRequestedFor(urlPathEqualTo("/api/v1/namespaces/default/configmaps"))
169159
.withQueryParam("watch", equalTo("false")));
170160
verify(
171-
getRequestedFor(urlPathEqualTo("/api/v1/configmaps"))
161+
getRequestedFor(urlPathEqualTo("/api/v1/namespaces/default/configmaps"))
172162
.withQueryParam("watch", equalTo("true")));
173163

174164
assertEquals(1, podLister.list().size());

spring/src/test/java/io/kubernetes/client/spring/extended/controller/KubernetesReconcilerCreatorTest.java

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,11 @@
2929
import io.kubernetes.client.informer.cache.DeltaFIFO;
3030
import io.kubernetes.client.informer.cache.Lister;
3131
import io.kubernetes.client.informer.impl.DefaultSharedIndexInformer;
32-
import io.kubernetes.client.openapi.models.V1ConfigMap;
33-
import io.kubernetes.client.openapi.models.V1ObjectMeta;
34-
import io.kubernetes.client.openapi.models.V1Pod;
35-
import io.kubernetes.client.openapi.models.V1PodList;
36-
import io.kubernetes.client.spring.extended.controller.annotation.AddWatchEventFilter;
37-
import io.kubernetes.client.spring.extended.controller.annotation.DeleteWatchEventFilter;
38-
import io.kubernetes.client.spring.extended.controller.annotation.KubernetesReconciler;
39-
import io.kubernetes.client.spring.extended.controller.annotation.KubernetesReconcilerReadyFunc;
40-
import io.kubernetes.client.spring.extended.controller.annotation.KubernetesReconcilerWatch;
41-
import io.kubernetes.client.spring.extended.controller.annotation.KubernetesReconcilerWatches;
42-
import io.kubernetes.client.spring.extended.controller.annotation.UpdateWatchEventFilter;
32+
import io.kubernetes.client.openapi.ApiClient;
33+
import io.kubernetes.client.openapi.models.*;
34+
import io.kubernetes.client.spring.extended.controller.annotation.*;
4335
import io.kubernetes.client.spring.extended.controller.factory.KubernetesControllerFactory;
36+
import io.kubernetes.client.util.ClientBuilder;
4437
import java.util.LinkedList;
4538
import java.util.function.Function;
4639
import javax.annotation.Resource;
@@ -49,21 +42,48 @@
4942
import org.junit.Test;
5043
import org.junit.runner.RunWith;
5144
import org.springframework.beans.factory.annotation.Autowired;
45+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
46+
import org.springframework.boot.autoconfigure.SpringBootApplication;
5247
import org.springframework.boot.test.context.SpringBootTest;
53-
import org.springframework.boot.test.context.TestConfiguration;
5448
import org.springframework.context.annotation.Bean;
55-
import org.springframework.context.annotation.Import;
5649
import org.springframework.test.context.junit4.SpringRunner;
5750

5851
@RunWith(SpringRunner.class)
59-
@SpringBootTest
60-
@Import(KubernetesInformerCreatorTest.TestConfig.class)
52+
@SpringBootTest(classes = {KubernetesReconcilerCreatorTest.App.class})
6153
public class KubernetesReconcilerCreatorTest {
6254

6355
@Rule public WireMockRule wireMockRule = new WireMockRule(8189);
6456

65-
@TestConfiguration
66-
static class TestConfig {
57+
@SpringBootApplication
58+
@EnableAutoConfiguration
59+
static class App {
60+
@Bean
61+
public ApiClient testingApiClient() {
62+
ApiClient apiClient = new ClientBuilder().setBasePath("http://localhost:" + 8188).build();
63+
return apiClient;
64+
}
65+
66+
@Bean
67+
public SharedInformerFactory sharedInformerFactory() {
68+
return new KubernetesInformerCreatorTest.App.TestSharedInformerFactory();
69+
}
70+
71+
@KubernetesInformers({
72+
@KubernetesInformer(
73+
apiTypeClass = V1Pod.class,
74+
apiListTypeClass = V1PodList.class,
75+
groupVersionResource =
76+
@GroupVersionResource(apiGroup = "", apiVersion = "v1", resourcePlural = "pods")),
77+
@KubernetesInformer(
78+
apiTypeClass = V1ConfigMap.class,
79+
apiListTypeClass = V1ConfigMapList.class,
80+
groupVersionResource =
81+
@GroupVersionResource(
82+
apiGroup = "",
83+
apiVersion = "v1",
84+
resourcePlural = "configmaps")),
85+
})
86+
static class TestSharedInformerFactory extends SharedInformerFactory {}
6787

6888
@Bean
6989
public TestReconciler testReconciler() {

spring/src/test/java/io/kubernetes/client/spring/extended/controller/TestApplication.java

Lines changed: 0 additions & 18 deletions
This file was deleted.

util/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@
110110
<artifactId>awaitility</artifactId>
111111
<scope>test</scope>
112112
</dependency>
113+
<dependency>
114+
<groupId>org.assertj</groupId>
115+
<artifactId>assertj-core</artifactId>
116+
<scope>test</scope>
117+
</dependency>
113118

114119
</dependencies>
115120
<build>

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

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,7 @@
1919
import io.kubernetes.client.openapi.ApiClient;
2020
import io.kubernetes.client.openapi.ApiException;
2121
import io.kubernetes.client.openapi.Configuration;
22-
import io.kubernetes.client.util.CallGenerator;
23-
import io.kubernetes.client.util.CallGeneratorParams;
24-
import io.kubernetes.client.util.Watch;
25-
import io.kubernetes.client.util.Watchable;
22+
import io.kubernetes.client.util.*;
2623
import io.kubernetes.client.util.generic.GenericKubernetesApi;
2724
import io.kubernetes.client.util.generic.options.ListOptions;
2825
import java.lang.reflect.Type;
@@ -162,7 +159,31 @@ SharedIndexInformer<ApiType> sharedIndexInformerFor(
162159
GenericKubernetesApi<ApiType, ApiListType> genericKubernetesApi,
163160
Class<ApiType> apiTypeClass,
164161
long resyncPeriodInMillis) {
165-
ListerWatcher<ApiType, ApiListType> listerWatcher = listerWatcherFor(genericKubernetesApi);
162+
return sharedIndexInformerFor(
163+
genericKubernetesApi, apiTypeClass, resyncPeriodInMillis, Namespaces.NAMESPACE_ALL);
164+
}
165+
166+
/**
167+
* Working the same as {@link SharedInformerFactory#sharedIndexInformerFor} above.
168+
*
169+
* <p>Constructs and returns a shared index informer for a specific namespace.
170+
*
171+
* @param <ApiType> the type parameter
172+
* @param <ApiListType> the type parameter
173+
* @param genericKubernetesApi the generic kubernetes api
174+
* @param apiTypeClass the api type class
175+
* @param resyncPeriodInMillis the resync period in millis
176+
* @param namespace the target namespace
177+
* @return the shared index informer
178+
*/
179+
public synchronized <ApiType extends KubernetesObject, ApiListType extends KubernetesListObject>
180+
SharedIndexInformer<ApiType> sharedIndexInformerFor(
181+
GenericKubernetesApi<ApiType, ApiListType> genericKubernetesApi,
182+
Class<ApiType> apiTypeClass,
183+
long resyncPeriodInMillis,
184+
String namespace) {
185+
ListerWatcher<ApiType, ApiListType> listerWatcher =
186+
listerWatcherFor(genericKubernetesApi, namespace);
166187
return sharedIndexInformerFor(listerWatcher, apiTypeClass, resyncPeriodInMillis);
167188
}
168189

@@ -197,28 +218,46 @@ public Watch<ApiType> watch(CallGeneratorParams params) throws ApiException {
197218

198219
private <ApiType extends KubernetesObject, ApiListType extends KubernetesListObject>
199220
ListerWatcher<ApiType, ApiListType> listerWatcherFor(
200-
GenericKubernetesApi<ApiType, ApiListType> genericKubernetesApi) {
221+
GenericKubernetesApi<ApiType, ApiListType> genericKubernetesApi, String namespace) {
201222
if (apiClient.getReadTimeout() > 0) {
202223
// set read timeout zero to ensure client doesn't time out
203224
apiClient.setReadTimeout(0);
204225
}
205226
// TODO: it seems read timeout is determined by genericKubernetesApi instead of above apiClient.
206227
return new ListerWatcher<ApiType, ApiListType>() {
207228
public ApiListType list(CallGeneratorParams params) throws ApiException {
208-
return genericKubernetesApi
209-
.list(
210-
new ListOptions() {
211-
{
212-
setResourceVersion(params.resourceVersion);
213-
setTimeoutSeconds(params.timeoutSeconds);
214-
}
215-
})
216-
.throwsApiException()
217-
.getObject();
229+
if (Namespaces.NAMESPACE_ALL.equals(namespace)) {
230+
return genericKubernetesApi
231+
.list(
232+
new ListOptions() {
233+
{
234+
setResourceVersion(params.resourceVersion);
235+
setTimeoutSeconds(params.timeoutSeconds);
236+
}
237+
})
238+
.throwsApiException()
239+
.getObject();
240+
} else {
241+
return genericKubernetesApi
242+
.list(
243+
namespace,
244+
new ListOptions() {
245+
{
246+
setResourceVersion(params.resourceVersion);
247+
setTimeoutSeconds(params.timeoutSeconds);
248+
}
249+
})
250+
.throwsApiException()
251+
.getObject();
252+
}
218253
}
219254

220255
public Watchable<ApiType> watch(CallGeneratorParams params) throws ApiException {
221-
return genericKubernetesApi.watch();
256+
if (Namespaces.NAMESPACE_ALL.equals(namespace)) {
257+
return genericKubernetesApi.watch();
258+
} else {
259+
return genericKubernetesApi.watch(namespace);
260+
}
222261
}
223262
};
224263
}

0 commit comments

Comments
 (0)