Skip to content

Commit a43fa93

Browse files
authored
Merge pull request #1371 from yue9944882/spring-auto-configure-dep
Feat(Spring-Integration): Support auto-configure to shorten configuration steps
2 parents 93e04b7 + a89faa8 commit a43fa93

15 files changed

+300
-128
lines changed

docs/java-controller-tutorial-rewrite-rs-controller.md

Lines changed: 97 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,33 @@ plugins are present in the [pom.xml](https://github.com/yue9944882/replicaset-co
4646
</plugin>
4747
```
4848

49+
#### Introducing Spring Integration Dependency
50+
51+
Adding the following dependency to your `pom.xml`.
52+
53+
```xml
54+
<dependency>
55+
<groupId>io.kubernetes</groupId>
56+
<artifactId>client-java-spring-integration</artifactId>
57+
<version>${ >= 11.0.0 recommended}</version>
58+
</dependency>
59+
```
60+
61+
The dependency will auto-configure the necessary configuration beans for injecting informers
62+
and reconcilers to your application. And note that you can disable the configuration beans by
63+
setting the following properties in your spring context:
64+
65+
66+
```text
67+
kubernetes.informer.enabled=false # disables informer injection
68+
kubernetes.reconciler.enabled=false # disables reconciler injection
69+
```
70+
4971
#### Adding a Main Class
5072

5173
So that the project can be packaged as a executable jar. See the example class [here](https://github.com/yue9944882/replicaset-controller/blob/master/src/main/java/com/github/yue9944882/kubernetes/Application.java).
5274

53-
#### Loading Post-Processor Spring Beans
75+
##### Loading Post-Processor Spring Beans (Required only if you're using < 11.0.0 releases)
5476

5577
Adding the following annotation to whatever Java class under spring context, so that processors can be activated.
5678

@@ -60,10 +82,6 @@ Adding the following annotation to whatever Java class under spring context, so
6082

6183
Corresponding example is available [here](https://github.com/yue9944882/replicaset-controller/blob/master/src/main/java/com/github/yue9944882/kubernetes/config/ControllerConfiguration.java#L20).
6284

63-
__NOTE__: In the future releases (already landed on master, will release after 11.0.0), you will be able to activate the processors by configuration-
64-
beans. To previewing the feature, you can take a glance at the test codes for [KubernetesReconcilerConfigurer](https://github.com/kubernetes-client/java/blob/351ffa13d49cb76445788d78bf83e03a5edf139a/spring/src/test/java/io/kubernetes/client/spring/extended/controller/KubernetesReconcilerCreatorTest.java#L75-L79) and
65-
[KubernetesInformerConfigurer](https://github.com/kubernetes-client/java/blob/351ffa13d49cb76445788d78bf83e03a5edf139a/spring/src/test/java/io/kubernetes/client/spring/extended/controller/KubernetesInformerCreatorTest.java#L73-L76).
66-
6785
#### Declaring Your Informer Factory
6886

6987
You're supposed to create a new class (can be a inner-class) extending `io.kubernetes.client.informer.SharedInformerFactory`,
@@ -99,10 +117,11 @@ The registered informer-factory won't be running unless you explcitly calls `sta
99117
the method is the trigger to run the controller, so hold it carefully until you're ready :). In the example project, the
100118
informer-factory was started inside `ControllerManager#run`.
101119

102-
The [KubernetesInformerFactoryProcessor](https://github.com/kubernetes-client/java/blob/master/spring/src/main/java/io/kubernetes/client/spring/extended/controller/KubernetesInformerFactoryProcessor.java)
103-
will be parsing the `@KubernetesInformers` annotation on informer-factory class and then register `SharedInformer` and
104-
`Lister` beans for the kubernetes resource classes. You can easily acquire them by declaring them as parameters in the
105-
bean method. See this example source to see the per-resource informer/lister bean registration [here](https://github.com/yue9944882/replicaset-controller/blob/c8dda02fe444d7154117b9bf0583557502694e1b/src/main/java/com/github/yue9944882/kubernetes/config/ControllerConfiguration.java#L35-L43).
120+
As a deeper insight, it's the [KubernetesInformerFactoryProcessor](https://github.com/kubernetes-client/java/blob/master/spring/src/main/java/io/kubernetes/client/spring/extended/controller/KubernetesInformerFactoryProcessor.java)
121+
parsing the `@KubernetesInformers` annotation on informer-factory class and then register `SharedInformer` and
122+
`Lister` beans for the kubernetes resource classes. You can easily acquire them by `@Autowired` (for >= 11.0.0 release)
123+
or declaring them as parameters in the bean method. See this example source to see the per-resource informer/lister
124+
bean registration [here](https://github.com/yue9944882/replicaset-controller/blob/c8dda02fe444d7154117b9bf0583557502694e1b/src/main/java/com/github/yue9944882/kubernetes/config/ControllerConfiguration.java#L35-L43).
106125

107126

108127
#### Declaring Your Reconciler
@@ -126,10 +145,78 @@ the watch connections are managed by informer-factory and they're multiplex'd. S
126145
to the watch connection. For more detail, take a look at the example code [here](https://github.com/yue9944882/replicaset-controller/blob/c8dda02fe444d7154117b9bf0583557502694e1b/src/main/java/com/github/yue9944882/kubernetes/ReplicaSetReconciler.java#L27-L40).
127146

128147

148+
#### Injecting Informer/Lister using @Autowired (Optional for >= 11.0.0 releases)
149+
150+
You can easily acquire `SharedInformer` and `Lister` instances via `@Autowired` annotations instead of passing them from
151+
the bean constructor, see the following example:
152+
153+
```java
154+
public class ReplicaSetReconciler implements Reconciler {
155+
@Autowired private SharedInformer<V1Pod> podInformer;
156+
@Autowired private Lister<V1Pod> podLister;
157+
@Autowired private SharedInformer<V1Node> nodeInformer;
158+
@Autowired private Lister<V1Node> nodeLister;
159+
...
160+
}
161+
```
162+
163+
Note that the type parameter for the informer and lister i.e. the kubernetes api resource type must be declared in your
164+
`@KubernetesInformers` annotation.
165+
166+
#### Setting Event-Filter or ReadyFunc for your reconciler
167+
168+
Both the event-filter method and ready-func method is supposed to be "public" access.
169+
170+
```java
171+
public class ReplicaSetReconciler implements Reconciler {
172+
...
173+
@AddWatchEventFilter(apiTypeClass = V1Pod.class)
174+
public boolean onAddFilter(V1Pod pod) {
175+
return true; // returns true to handle the event
176+
}
177+
178+
@UpdateWatchEventFilter(apiTypeClass = V1Pod.class)
179+
public boolean onUpdateFilter(V1Pod oldPod, V1Pod newPod) {
180+
return true; // returns true to handle the event
181+
}
182+
183+
@DeleteWatchEventFilter(apiTypeClass = V1Pod.class)
184+
public boolean onDeleteFilter(V1Pod pod) {
185+
return true; // returns true to handle the event
186+
}
187+
188+
@KubernetesReconcilerReadyFunc
189+
public boolean podInformerCacheReady() {
190+
return podInformer.hasSynced(); // return true if reconciler is ready to run.
191+
}
192+
..
193+
}
194+
```
195+
196+
#### Creating Your Controller Bean (Required for >= 11.0.0 releases)
197+
198+
The controller bean is the entry bean to run your reconciler class definition. You're supposed to create a controller
199+
bean instance using `KubernetesControllerFactory`:
200+
201+
202+
```java
203+
@Configuration
204+
public class MyConfiguration {
205+
...
206+
@Bean
207+
public KubernetesControllerFactory replicasetController(
208+
SharedInformerFactory sharedInformerFactory,
209+
Reconciler reconciler) {
210+
return new KubernetesControllerFactory(sharedInformerFactory, reconciler);
211+
}
212+
...
213+
}
214+
```
215+
129216
#### Running the Reconcilers
130217

131218
Now both the informer-factory and the reconciler are set, the last step is to stitch them together by adding a starter
132-
(or runner) bean implemeting `InitializingBean` as is shown in the following approaches:
219+
(or a `CommandLineRunner`) or a bean implemeting `InitializingBean` as is shown in the following approaches:
133220

134221

135222
- (Option 1) Run it immediately.

examples/src/main/java/io/kubernetes/client/examples/SpringControllerExample.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,9 @@ public CommandLineRunner commandLineRunner(
6767
// *REQUIRED*
6868
// Configurer components that registers informers to the informer-factory in the context.
6969
@Bean
70-
public KubernetesInformerConfigurer kubernetesInformerConfigurer(ApiClient apiClient) {
71-
return new KubernetesInformerConfigurer(apiClient);
70+
public KubernetesInformerConfigurer kubernetesInformerConfigurer(
71+
ApiClient apiClient, SharedInformerFactory sharedInformerFactory) {
72+
return new KubernetesInformerConfigurer(apiClient, sharedInformerFactory);
7273
}
7374

7475
// *REQUIRED*
@@ -126,7 +127,6 @@ public static class MySharedInformerFactory extends SharedInformerFactory {}
126127
// io.kubernetes.client.extended.controller.Controller}
127128
// with the name specified and registering it to the spring bean-factory.
128129
@KubernetesReconciler(
129-
value = "node-printing-controller",
130130
watches =
131131
@KubernetesReconcilerWatches({
132132
@KubernetesReconcilerWatch(

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

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

15+
import io.kubernetes.client.informer.SharedInformerFactory;
1516
import io.kubernetes.client.openapi.ApiClient;
1617
import org.springframework.beans.factory.FactoryBean;
1718

@@ -23,14 +24,17 @@ public class KubernetesInformerConfigurer
2324
implements FactoryBean<KubernetesInformerFactoryProcessor> {
2425

2526
private final ApiClient apiClient;
27+
private final SharedInformerFactory sharedInformerFactory;
2628

27-
public KubernetesInformerConfigurer(ApiClient apiClient) {
29+
public KubernetesInformerConfigurer(
30+
ApiClient apiClient, SharedInformerFactory sharedInformerFactory) {
2831
this.apiClient = apiClient;
32+
this.sharedInformerFactory = sharedInformerFactory;
2933
}
3034

3135
@Override
3236
public KubernetesInformerFactoryProcessor getObject() throws Exception {
33-
return new KubernetesInformerFactoryProcessor(apiClient);
37+
return new KubernetesInformerFactoryProcessor(apiClient, sharedInformerFactory);
3438
}
3539

3640
@Override

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

Lines changed: 5 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,11 @@
1919
import io.kubernetes.client.openapi.ApiClient;
2020
import io.kubernetes.client.spring.extended.controller.annotation.KubernetesInformer;
2121
import io.kubernetes.client.spring.extended.controller.annotation.KubernetesInformers;
22-
import io.kubernetes.client.util.ClientBuilder;
2322
import io.kubernetes.client.util.generic.GenericKubernetesApi;
24-
import java.io.IOException;
2523
import java.time.Duration;
26-
import java.util.Map;
27-
import java.util.Optional;
2824
import org.slf4j.Logger;
2925
import org.slf4j.LoggerFactory;
3026
import org.springframework.beans.BeansException;
31-
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
3227
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
3328
import org.springframework.beans.factory.support.AbstractBeanDefinition;
3429
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
@@ -57,53 +52,22 @@ public class KubernetesInformerFactoryProcessor
5752

5853
private BeanDefinitionRegistry beanDefinitionRegistry;
5954

60-
private ApiClient apiClient = null;
55+
private final ApiClient apiClient;
56+
private final SharedInformerFactory sharedInformerFactory;
6157

62-
public KubernetesInformerFactoryProcessor(ApiClient apiClient) {
58+
public KubernetesInformerFactoryProcessor(
59+
ApiClient apiClient, SharedInformerFactory sharedInformerFactory) {
6360
this.apiClient = apiClient;
61+
this.sharedInformerFactory = sharedInformerFactory;
6462
}
6563

6664
@Override
6765
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
6866
throws BeansException {
69-
Optional<String> sharedInformerFactoryBeanName;
70-
try {
71-
Map<String, SharedInformerFactory> sharedInformerFactories =
72-
beanFactory.getBeansOfType(SharedInformerFactory.class);
73-
if (sharedInformerFactories.size() > 1) {
74-
log.warn("More than sharedInformerFactory registered..");
75-
return;
76-
}
77-
sharedInformerFactoryBeanName = sharedInformerFactories.keySet().stream().findFirst();
78-
} catch (NoSuchBeanDefinitionException e) {
79-
// should never happen..
80-
log.error("No sharedInformerFactory bean registered..");
81-
return;
82-
}
83-
84-
if (!sharedInformerFactoryBeanName.isPresent()) {
85-
log.info("No sharedInformerFactory selected, skipping informers constructing..");
86-
return;
87-
}
88-
89-
if (this.apiClient == null) {
90-
try {
91-
this.apiClient = beanFactory.getBean(ApiClient.class);
92-
} catch (NoSuchBeanDefinitionException e) {
93-
log.info("No ApiClient bean found, falling-back to default initialization..");
94-
try {
95-
this.apiClient = ClientBuilder.standard().build();
96-
} catch (IOException ex) {
97-
log.error("failed initializing ApiClient", ex);
98-
return;
99-
}
100-
}
101-
}
10267

10368
this.apiClient.setHttpClient(
10469
this.apiClient.getHttpClient().newBuilder().readTimeout(Duration.ZERO).build());
10570

106-
SharedInformerFactory sharedInformerFactory = beanFactory.getBean(SharedInformerFactory.class);
10771
KubernetesInformers kubernetesInformers =
10872
sharedInformerFactory.getClass().getAnnotation(KubernetesInformers.class);
10973
if (kubernetesInformers == null || kubernetesInformers.value().length == 0) {

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

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

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

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,6 @@ public int getOrder() {
5858
@Override
5959
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
6060
throws BeansException {
61-
if (this.sharedInformerFactory == null) {
62-
this.sharedInformerFactory = beanFactory.getBean(SharedInformerFactory.class);
63-
}
6461
String[] names = beanFactory.getBeanNamesForType(Reconciler.class);
6562
for (String name : names) {
6663
Reconciler reconciler = (Reconciler) beanFactory.getBean(name);

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,12 @@
3434
/**
3535
* The name of the Kubernetes Reconciler.
3636
*
37+
* <p>NOTE: No longer in use for version >= 11.0.0
38+
*
3739
* @return the string
3840
*/
39-
String value();
41+
@Deprecated
42+
String value() default "";
4043

4144
/**
4245
* Watches kubernetes resources.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
Copyright 2020 The Kubernetes Authors.
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
package io.kubernetes.client.spring.extended.controller.config;
14+
15+
import java.lang.annotation.Documented;
16+
import java.lang.annotation.ElementType;
17+
import java.lang.annotation.Inherited;
18+
import java.lang.annotation.Retention;
19+
import java.lang.annotation.RetentionPolicy;
20+
import java.lang.annotation.Target;
21+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
22+
23+
@Target(ElementType.TYPE)
24+
@Retention(RetentionPolicy.RUNTIME)
25+
@Documented
26+
@Inherited
27+
@ConditionalOnProperty(value = "kubernetes.actuator.prometheus.enabled")
28+
public @interface ConditionalOnActuatorPrometheusEndpointEnabled {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
Copyright 2020 The Kubernetes Authors.
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
package io.kubernetes.client.spring.extended.controller.config;
14+
15+
import java.lang.annotation.Documented;
16+
import java.lang.annotation.ElementType;
17+
import java.lang.annotation.Inherited;
18+
import java.lang.annotation.Retention;
19+
import java.lang.annotation.RetentionPolicy;
20+
import java.lang.annotation.Target;
21+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
22+
23+
@Target(ElementType.TYPE)
24+
@Retention(RetentionPolicy.RUNTIME)
25+
@Documented
26+
@Inherited
27+
@ConditionalOnProperty(value = "kubernetes.informer.enabled", matchIfMissing = true)
28+
public @interface ConditionalOnKubernetesInformerEnabled {}

0 commit comments

Comments
 (0)