Skip to content

Commit ca96ed4

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

File tree

13 files changed

+209
-84
lines changed

13 files changed

+209
-84
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: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -43,21 +43,21 @@
4343
import org.junit.Test;
4444
import org.junit.runner.RunWith;
4545
import org.springframework.beans.factory.annotation.Autowired;
46+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
47+
import org.springframework.boot.autoconfigure.SpringBootApplication;
4648
import org.springframework.boot.test.context.SpringBootTest;
47-
import org.springframework.boot.test.context.TestConfiguration;
4849
import org.springframework.context.annotation.Bean;
49-
import org.springframework.context.annotation.Import;
5050
import org.springframework.test.context.junit4.SpringRunner;
5151

5252
@RunWith(SpringRunner.class)
53-
@SpringBootTest
54-
@Import(KubernetesInformerCreatorTest.TestConfig.class)
53+
@SpringBootTest(classes = {KubernetesInformerCreatorTest.App.class})
5554
public class KubernetesInformerCreatorTest {
5655

5756
@Rule public WireMockRule wireMockRule = new WireMockRule(8188);
5857

59-
@TestConfiguration
60-
static class TestConfig {
58+
@SpringBootApplication
59+
@EnableAutoConfiguration
60+
static class App {
6161

6262
@Bean
6363
public ApiClient testingApiClient() {
@@ -70,12 +70,6 @@ public SharedInformerFactory sharedInformerFactory() {
7070
return new TestSharedInformerFactory();
7171
}
7272

73-
@Bean
74-
public KubernetesInformerConfigurer kubernetesInformerConfigurer(
75-
ApiClient apiClient, SharedInformerFactory sharedInformerFactory) {
76-
return new KubernetesInformerConfigurer(apiClient, sharedInformerFactory);
77-
}
78-
7973
@KubernetesInformers({
8074
@KubernetesInformer(
8175
apiTypeClass = V1Pod.class,
@@ -85,6 +79,7 @@ public KubernetesInformerConfigurer kubernetesInformerConfigurer(
8579
@KubernetesInformer(
8680
apiTypeClass = V1ConfigMap.class,
8781
apiListTypeClass = V1ConfigMapList.class,
82+
namespace = "default",
8883
groupVersionResource =
8984
@GroupVersionResource(
9085
apiGroup = "",
@@ -138,7 +133,7 @@ public void testInformerInjection() throws InterruptedException {
138133
.willReturn(aResponse().withStatus(200).withBody("{}")));
139134

140135
wireMockRule.stubFor(
141-
get(urlMatching("^/api/v1/configmaps.*"))
136+
get(urlMatching("^/api/v1/namespaces/default/configmaps.*"))
142137
.withQueryParam("watch", equalTo("false"))
143138
.willReturn(
144139
aResponse()
@@ -150,7 +145,7 @@ public void testInformerInjection() throws InterruptedException {
150145
.metadata(new V1ListMeta().resourceVersion("0"))
151146
.items(Arrays.asList(bar1))))));
152147
wireMockRule.stubFor(
153-
get(urlMatching("^/api/v1/configmaps.*"))
148+
get(urlMatching("^/api/v1/namespaces/default/configmaps.*"))
154149
.withQueryParam("watch", equalTo("true"))
155150
.willReturn(aResponse().withStatus(200).withBody("{}")));
156151

@@ -165,10 +160,10 @@ public void testInformerInjection() throws InterruptedException {
165160
getRequestedFor(urlPathEqualTo("/api/v1/pods")).withQueryParam("watch", equalTo("true")));
166161
verify(
167162
1,
168-
getRequestedFor(urlPathEqualTo("/api/v1/configmaps"))
163+
getRequestedFor(urlPathEqualTo("/api/v1/namespaces/default/configmaps"))
169164
.withQueryParam("watch", equalTo("false")));
170165
verify(
171-
getRequestedFor(urlPathEqualTo("/api/v1/configmaps"))
166+
getRequestedFor(urlPathEqualTo("/api/v1/namespaces/default/configmaps"))
172167
.withQueryParam("watch", equalTo("true")));
173168

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

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

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

15-
import static org.junit.Assert.assertEquals;
16-
import static org.junit.Assert.assertNotNull;
17-
import static org.junit.Assert.fail;
15+
import static org.junit.Assert.*;
1816

1917
import com.github.tomakehurst.wiremock.junit.WireMockRule;
2018
import io.kubernetes.client.common.KubernetesObject;
@@ -29,18 +27,24 @@
2927
import io.kubernetes.client.informer.cache.DeltaFIFO;
3028
import io.kubernetes.client.informer.cache.Lister;
3129
import io.kubernetes.client.informer.impl.DefaultSharedIndexInformer;
30+
import io.kubernetes.client.openapi.ApiClient;
3231
import io.kubernetes.client.openapi.models.V1ConfigMap;
32+
import io.kubernetes.client.openapi.models.V1ConfigMapList;
3333
import io.kubernetes.client.openapi.models.V1ObjectMeta;
3434
import io.kubernetes.client.openapi.models.V1Pod;
3535
import io.kubernetes.client.openapi.models.V1PodList;
3636
import io.kubernetes.client.spring.extended.controller.annotation.AddWatchEventFilter;
3737
import io.kubernetes.client.spring.extended.controller.annotation.DeleteWatchEventFilter;
38+
import io.kubernetes.client.spring.extended.controller.annotation.GroupVersionResource;
39+
import io.kubernetes.client.spring.extended.controller.annotation.KubernetesInformer;
40+
import io.kubernetes.client.spring.extended.controller.annotation.KubernetesInformers;
3841
import io.kubernetes.client.spring.extended.controller.annotation.KubernetesReconciler;
3942
import io.kubernetes.client.spring.extended.controller.annotation.KubernetesReconcilerReadyFunc;
4043
import io.kubernetes.client.spring.extended.controller.annotation.KubernetesReconcilerWatch;
4144
import io.kubernetes.client.spring.extended.controller.annotation.KubernetesReconcilerWatches;
4245
import io.kubernetes.client.spring.extended.controller.annotation.UpdateWatchEventFilter;
4346
import io.kubernetes.client.spring.extended.controller.factory.KubernetesControllerFactory;
47+
import io.kubernetes.client.util.ClientBuilder;
4448
import java.util.LinkedList;
4549
import java.util.function.Function;
4650
import javax.annotation.Resource;
@@ -49,21 +53,48 @@
4953
import org.junit.Test;
5054
import org.junit.runner.RunWith;
5155
import org.springframework.beans.factory.annotation.Autowired;
56+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
57+
import org.springframework.boot.autoconfigure.SpringBootApplication;
5258
import org.springframework.boot.test.context.SpringBootTest;
53-
import org.springframework.boot.test.context.TestConfiguration;
5459
import org.springframework.context.annotation.Bean;
55-
import org.springframework.context.annotation.Import;
5660
import org.springframework.test.context.junit4.SpringRunner;
5761

5862
@RunWith(SpringRunner.class)
59-
@SpringBootTest
60-
@Import(KubernetesInformerCreatorTest.TestConfig.class)
63+
@SpringBootTest(classes = {KubernetesReconcilerCreatorTest.App.class})
6164
public class KubernetesReconcilerCreatorTest {
6265

6366
@Rule public WireMockRule wireMockRule = new WireMockRule(8189);
6467

65-
@TestConfiguration
66-
static class TestConfig {
68+
@SpringBootApplication
69+
@EnableAutoConfiguration
70+
static class App {
71+
@Bean
72+
public ApiClient testingApiClient() {
73+
ApiClient apiClient = new ClientBuilder().setBasePath("http://localhost:" + 8188).build();
74+
return apiClient;
75+
}
76+
77+
@Bean
78+
public SharedInformerFactory sharedInformerFactory() {
79+
return new KubernetesInformerCreatorTest.App.TestSharedInformerFactory();
80+
}
81+
82+
@KubernetesInformers({
83+
@KubernetesInformer(
84+
apiTypeClass = V1Pod.class,
85+
apiListTypeClass = V1PodList.class,
86+
groupVersionResource =
87+
@GroupVersionResource(apiGroup = "", apiVersion = "v1", resourcePlural = "pods")),
88+
@KubernetesInformer(
89+
apiTypeClass = V1ConfigMap.class,
90+
apiListTypeClass = V1ConfigMapList.class,
91+
groupVersionResource =
92+
@GroupVersionResource(
93+
apiGroup = "",
94+
apiVersion = "v1",
95+
resourcePlural = "configmaps")),
96+
})
97+
static class TestSharedInformerFactory extends SharedInformerFactory {}
6798

6899
@Bean
69100
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 & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import io.kubernetes.client.openapi.Configuration;
2222
import io.kubernetes.client.util.CallGenerator;
2323
import io.kubernetes.client.util.CallGeneratorParams;
24+
import io.kubernetes.client.util.Namespaces;
2425
import io.kubernetes.client.util.Watch;
2526
import io.kubernetes.client.util.Watchable;
2627
import io.kubernetes.client.util.generic.GenericKubernetesApi;
@@ -162,7 +163,31 @@ SharedIndexInformer<ApiType> sharedIndexInformerFor(
162163
GenericKubernetesApi<ApiType, ApiListType> genericKubernetesApi,
163164
Class<ApiType> apiTypeClass,
164165
long resyncPeriodInMillis) {
165-
ListerWatcher<ApiType, ApiListType> listerWatcher = listerWatcherFor(genericKubernetesApi);
166+
return sharedIndexInformerFor(
167+
genericKubernetesApi, apiTypeClass, resyncPeriodInMillis, Namespaces.NAMESPACE_ALL);
168+
}
169+
170+
/**
171+
* Working the same as {@link SharedInformerFactory#sharedIndexInformerFor} above.
172+
*
173+
* <p>Constructs and returns a shared index informer for a specific namespace.
174+
*
175+
* @param <ApiType> the type parameter
176+
* @param <ApiListType> the type parameter
177+
* @param genericKubernetesApi the generic kubernetes api
178+
* @param apiTypeClass the api type class
179+
* @param resyncPeriodInMillis the resync period in millis
180+
* @param namespace the target namespace
181+
* @return the shared index informer
182+
*/
183+
public synchronized <ApiType extends KubernetesObject, ApiListType extends KubernetesListObject>
184+
SharedIndexInformer<ApiType> sharedIndexInformerFor(
185+
GenericKubernetesApi<ApiType, ApiListType> genericKubernetesApi,
186+
Class<ApiType> apiTypeClass,
187+
long resyncPeriodInMillis,
188+
String namespace) {
189+
ListerWatcher<ApiType, ApiListType> listerWatcher =
190+
listerWatcherFor(genericKubernetesApi, namespace);
166191
return sharedIndexInformerFor(listerWatcher, apiTypeClass, resyncPeriodInMillis);
167192
}
168193

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

198223
private <ApiType extends KubernetesObject, ApiListType extends KubernetesListObject>
199224
ListerWatcher<ApiType, ApiListType> listerWatcherFor(
200-
GenericKubernetesApi<ApiType, ApiListType> genericKubernetesApi) {
225+
GenericKubernetesApi<ApiType, ApiListType> genericKubernetesApi, String namespace) {
201226
if (apiClient.getReadTimeout() > 0) {
202227
// set read timeout zero to ensure client doesn't time out
203228
apiClient.setReadTimeout(0);
204229
}
205230
// TODO: it seems read timeout is determined by genericKubernetesApi instead of above apiClient.
206231
return new ListerWatcher<ApiType, ApiListType>() {
207232
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();
233+
if (Namespaces.NAMESPACE_ALL.equals(namespace)) {
234+
return genericKubernetesApi
235+
.list(
236+
new ListOptions() {
237+
{
238+
setResourceVersion(params.resourceVersion);
239+
setTimeoutSeconds(params.timeoutSeconds);
240+
}
241+
})
242+
.throwsApiException()
243+
.getObject();
244+
} else {
245+
return genericKubernetesApi
246+
.list(
247+
namespace,
248+
new ListOptions() {
249+
{
250+
setResourceVersion(params.resourceVersion);
251+
setTimeoutSeconds(params.timeoutSeconds);
252+
}
253+
})
254+
.throwsApiException()
255+
.getObject();
256+
}
218257
}
219258

220259
public Watchable<ApiType> watch(CallGeneratorParams params) throws ApiException {
221-
return genericKubernetesApi.watch();
260+
if (Namespaces.NAMESPACE_ALL.equals(namespace)) {
261+
return genericKubernetesApi.watch();
262+
} else {
263+
return genericKubernetesApi.watch(namespace);
264+
}
222265
}
223266
};
224267
}

0 commit comments

Comments
 (0)