Skip to content

Commit 2b8f594

Browse files
authored
Merge pull request #898 from yue9944882/example/spring-injection
Adding example for spring-based controller construction
2 parents 28cfba9 + 2ab6665 commit 2b8f594

File tree

5 files changed

+180
-4
lines changed

5 files changed

+180
-4
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ We prepared a few examples for common use-cases which are shown below:
102102
Build a controller reconciling the state of world by list-watching one or multiple resources.
103103
- ([6.0.0+](https://github.com/kubernetes-client/java/tree/client-java-parent-6.0.0)) [LeaderElectionExample](https://github.com/kubernetes-client/java/blob/master/examples/src/main/java/io/kubernetes/client/examples/LeaderElectionExample.java):
104104
Leader election utilities to help implement HA controllers.
105+
- ([9.0.0+](https://github.com/kubernetes-client/java/tree/client-java-parent-9.0.0)) [SpringIntegrationControllerExample](https://github.com/kubernetes-client/java/blob/master/examples/src/main/java/io/kubernetes/client/examples/SpringControllerExample.java):
106+
Building a kubernetes controller based on spring framework's bean injection.
105107

106108

107109
__list all pods__:

examples/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@
2626
<artifactId>client-java-extended</artifactId>
2727
<version>${project.version}</version>
2828
</dependency>
29+
<dependency>
30+
<groupId>io.kubernetes</groupId>
31+
<artifactId>client-java-spring-integration</artifactId>
32+
<version>${project.version}</version>
33+
</dependency>
2934
<dependency>
3035
<groupId>io.kubernetes</groupId>
3136
<artifactId>client-java-proto</artifactId>
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package io.kubernetes.client.examples;
2+
3+
import io.kubernetes.client.extended.controller.Controller;
4+
import io.kubernetes.client.extended.controller.reconciler.Reconciler;
5+
import io.kubernetes.client.extended.controller.reconciler.Request;
6+
import io.kubernetes.client.extended.controller.reconciler.Result;
7+
import io.kubernetes.client.informer.SharedInformer;
8+
import io.kubernetes.client.informer.SharedInformerFactory;
9+
import io.kubernetes.client.informer.cache.Lister;
10+
import io.kubernetes.client.openapi.ApiClient;
11+
import io.kubernetes.client.openapi.models.V1Node;
12+
import io.kubernetes.client.openapi.models.V1NodeList;
13+
import io.kubernetes.client.openapi.models.V1Pod;
14+
import io.kubernetes.client.openapi.models.V1PodList;
15+
import io.kubernetes.client.spring.extended.controller.annotation.*;
16+
import io.kubernetes.client.util.ClientBuilder;
17+
import java.io.IOException;
18+
import java.time.Duration;
19+
import org.springframework.beans.factory.annotation.Qualifier;
20+
import org.springframework.boot.CommandLineRunner;
21+
import org.springframework.boot.SpringApplication;
22+
import org.springframework.boot.autoconfigure.SpringBootApplication;
23+
import org.springframework.context.annotation.Bean;
24+
import org.springframework.context.annotation.ComponentScan;
25+
import org.springframework.context.annotation.Configuration;
26+
27+
@SpringBootApplication
28+
public class SpringControllerExample {
29+
30+
public static void main(String[] args) {
31+
SpringApplication.run(SpringControllerExample.class, args);
32+
}
33+
34+
@Configuration
35+
@ComponentScan(
36+
basePackages = "io.kubernetes.client.spring.extended.controller"
37+
) // Scanning beans under this package is *REQUIRED* for informers/reconciler injections.
38+
public static class AppConfig {
39+
40+
@Bean
41+
public CommandLineRunner commandLineRunner(
42+
SharedInformerFactory sharedInformerFactory,
43+
@Qualifier("node-printing-controller") Controller nodePrintingController) {
44+
return args -> {
45+
System.out.println("starting informers..");
46+
sharedInformerFactory.startAllRegisteredInformers();
47+
48+
System.out.println("running controller..");
49+
nodePrintingController.run();
50+
};
51+
}
52+
53+
// *OPTIONAL*
54+
// Injecting and customize your ApiClient, if not specified, fallbacks to {@link
55+
// io.kubernetes.client.util.ClientBuilder#standard}
56+
@Bean
57+
public ApiClient myApiClient() throws IOException {
58+
ApiClient apiClient = ClientBuilder.standard().build();
59+
return apiClient.setHttpClient(
60+
apiClient.getHttpClient().newBuilder().readTimeout(Duration.ZERO).build());
61+
}
62+
63+
// *REQUIRED*
64+
// Injecting your SharedInformerFactory class annotated `@KubernetesInformers`
65+
@Bean("sharedInformerFactory")
66+
public SharedInformerFactory sharedInformerFactory() {
67+
return new MySharedInformerFactory();
68+
}
69+
70+
@Bean
71+
public NodePrintingReconciler nodePrintingReconciler(
72+
Lister<V1Pod> podLister, Lister<V1Node> nodeLister, SharedInformer<V1Node> nodeInformer) {
73+
return new NodePrintingReconciler(podLister, nodeLister, nodeInformer);
74+
}
75+
}
76+
77+
@KubernetesInformers({ // Defining what resources is the informer-factory actually watching.
78+
@KubernetesInformer(
79+
apiTypeClass = V1Node.class,
80+
apiListTypeClass = V1NodeList.class,
81+
groupVersionResource =
82+
@GroupVersionResource(apiGroup = "", apiVersion = "v1", resourcePlural = "nodes"),
83+
resyncPeriodMillis = 60 * 1000L
84+
),
85+
@KubernetesInformer(
86+
apiTypeClass = V1Pod.class,
87+
apiListTypeClass = V1PodList.class,
88+
groupVersionResource =
89+
@GroupVersionResource(apiGroup = "", apiVersion = "v1", resourcePlural = "pods")
90+
),
91+
})
92+
public static class MySharedInformerFactory extends SharedInformerFactory {}
93+
94+
// As long as a reconciler bean attached `@KubernetesReconciler` detected in the context, we will
95+
// be automatically creating a conresponding controller bean implementing {@link
96+
// io.kubernetes.client.extended.controller.Controller}
97+
// with the name specified and registering it to the spring bean-factory.
98+
@KubernetesReconciler(
99+
value = "node-printing-controller",
100+
watches =
101+
@KubernetesReconcilerWatches({
102+
@KubernetesReconcilerWatch(
103+
apiTypeClass = V1Node.class,
104+
resyncPeriodMillis = 60 * 1000L // fully resync every 1 minute
105+
),
106+
})
107+
)
108+
public static class NodePrintingReconciler implements Reconciler {
109+
110+
public NodePrintingReconciler(
111+
Lister<V1Pod> podLister, Lister<V1Node> nodeLister, SharedInformer<V1Node> nodeInformer) {
112+
this.nodeLister = nodeLister;
113+
this.podLister = podLister;
114+
this.nodeInformer = nodeInformer;
115+
}
116+
117+
private SharedInformer<V1Node> nodeInformer;
118+
119+
private Lister<V1Node> nodeLister;
120+
121+
private Lister<V1Pod> podLister;
122+
123+
// *OPTIONAL*
124+
// If you feed like hold the controller from running util some condition..
125+
@KubernetesReconcilerReadyFunc
126+
boolean informerReady() {
127+
return nodeInformer.hasSynced();
128+
}
129+
130+
@Override
131+
public Result reconcile(Request request) {
132+
V1Node node = nodeLister.get(request.getName());
133+
System.out.println("triggered reconciling " + node.getMetadata().getName());
134+
return new Result(false);
135+
}
136+
}
137+
}

spring/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@
2929
<artifactId>spring-boot</artifactId>
3030
<version>${spring.boot.version}</version>
3131
</dependency>
32+
<dependency>
33+
<groupId>org.springframework.boot</groupId>
34+
<artifactId>spring-boot-autoconfigure</artifactId>
35+
<version>${spring.boot.version}</version>
36+
</dependency>
3237

3338
<dependency>
3439
<groupId>junit</groupId>

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

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,18 @@
1616
import io.kubernetes.client.util.ClientBuilder;
1717
import io.kubernetes.client.util.Watchable;
1818
import java.io.IOException;
19+
import java.time.Duration;
1920
import java.util.Map;
2021
import java.util.Optional;
2122
import org.slf4j.Logger;
2223
import org.slf4j.LoggerFactory;
2324
import org.springframework.beans.BeansException;
2425
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
25-
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
2626
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
27+
import org.springframework.beans.factory.support.AbstractBeanDefinition;
28+
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
29+
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
30+
import org.springframework.beans.factory.support.RootBeanDefinition;
2731
import org.springframework.core.Ordered;
2832
import org.springframework.core.ResolvableType;
2933
import org.springframework.stereotype.Component;
@@ -37,13 +41,16 @@
3741
* injects informers to spring context with the underlying constructing process hidden from users.
3842
*/
3943
@Component
40-
public class KubernetesInformerFactoryProcessor implements BeanFactoryPostProcessor, Ordered {
44+
public class KubernetesInformerFactoryProcessor
45+
implements BeanDefinitionRegistryPostProcessor, Ordered {
4146

4247
private static final Logger log =
4348
LoggerFactory.getLogger(KubernetesInformerFactoryProcessor.class);
4449

4550
public static final int ORDER = 0;
4651

52+
private BeanDefinitionRegistry beanDefinitionRegistry;
53+
4754
@Override
4855
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
4956
throws BeansException {
@@ -79,6 +86,8 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
7986
return;
8087
}
8188
}
89+
apiClient.setHttpClient(
90+
apiClient.getHttpClient().newBuilder().readTimeout(Duration.ZERO).build());
8291

8392
SharedInformerFactory sharedInformerFactory = beanFactory.getBean(SharedInformerFactory.class);
8493
KubernetesInformers kubernetesInformers =
@@ -124,17 +133,35 @@ public Watchable watch(CallGeneratorParams params) throws ApiException {
124133
ResolvableType informerType =
125134
ResolvableType.forClassWithGenerics(
126135
SharedInformer.class, kubernetesInformer.apiTypeClass());
127-
beanFactory.registerResolvableDependency(informerType.resolve(), sharedIndexInformer);
136+
RootBeanDefinition informerBean = new RootBeanDefinition();
137+
informerBean.setTargetType(informerType);
138+
informerBean.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
139+
informerBean.setAutowireCandidate(true);
140+
String informerBeanName = informerType.toString();
141+
this.beanDefinitionRegistry.registerBeanDefinition(informerBeanName, informerBean);
142+
beanFactory.registerSingleton(informerBeanName, sharedIndexInformer);
128143

129144
Lister lister = new Lister(sharedIndexInformer.getIndexer());
130145
ResolvableType listerType =
131146
ResolvableType.forClassWithGenerics(Lister.class, kubernetesInformer.apiTypeClass());
132-
beanFactory.registerResolvableDependency(listerType.resolve(), lister);
147+
RootBeanDefinition listerBean = new RootBeanDefinition();
148+
listerBean.setTargetType(listerType);
149+
listerBean.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
150+
listerBean.setAutowireCandidate(true);
151+
String listerBeanName = listerType.toString();
152+
this.beanDefinitionRegistry.registerBeanDefinition(listerBeanName, listerBean);
153+
beanFactory.registerSingleton(listerBeanName, lister);
133154
}
134155
}
135156

136157
@Override
137158
public int getOrder() {
138159
return 0;
139160
}
161+
162+
@Override
163+
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
164+
throws BeansException {
165+
this.beanDefinitionRegistry = registry;
166+
}
140167
}

0 commit comments

Comments
 (0)