Skip to content

Adding example for spring-based controller construction #898

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ We prepared a few examples for common use-cases which are shown below:
Build a controller reconciling the state of world by list-watching one or multiple resources.
- ([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):
Leader election utilities to help implement HA controllers.
- ([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):
Building a kubernetes controller based on spring framework's bean injection.


__list all pods__:
Expand Down
5 changes: 5 additions & 0 deletions examples/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
<artifactId>client-java-extended</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.kubernetes</groupId>
<artifactId>client-java-spring-integration</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.kubernetes</groupId>
<artifactId>client-java-proto</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package io.kubernetes.client.examples;

import io.kubernetes.client.extended.controller.Controller;
import io.kubernetes.client.extended.controller.reconciler.Reconciler;
import io.kubernetes.client.extended.controller.reconciler.Request;
import io.kubernetes.client.extended.controller.reconciler.Result;
import io.kubernetes.client.informer.SharedInformer;
import io.kubernetes.client.informer.SharedInformerFactory;
import io.kubernetes.client.informer.cache.Lister;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.models.V1Node;
import io.kubernetes.client.openapi.models.V1NodeList;
import io.kubernetes.client.openapi.models.V1Pod;
import io.kubernetes.client.openapi.models.V1PodList;
import io.kubernetes.client.spring.extended.controller.annotation.*;
import io.kubernetes.client.util.ClientBuilder;
import java.io.IOException;
import java.time.Duration;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@SpringBootApplication
public class SpringControllerExample {

public static void main(String[] args) {
SpringApplication.run(SpringControllerExample.class, args);
}

@Configuration
@ComponentScan(
basePackages = "io.kubernetes.client.spring.extended.controller"
) // Scanning beans under this package is *REQUIRED* for informers/reconciler injections.
public static class AppConfig {

@Bean
public CommandLineRunner commandLineRunner(
SharedInformerFactory sharedInformerFactory,
@Qualifier("node-printing-controller") Controller nodePrintingController) {
return args -> {
System.out.println("starting informers..");
sharedInformerFactory.startAllRegisteredInformers();

System.out.println("running controller..");
nodePrintingController.run();
};
}

// *OPTIONAL*
// Injecting and customize your ApiClient, if not specified, fallbacks to {@link
// io.kubernetes.client.util.ClientBuilder#standard}
@Bean
public ApiClient myApiClient() throws IOException {
ApiClient apiClient = ClientBuilder.standard().build();
return apiClient.setHttpClient(
apiClient.getHttpClient().newBuilder().readTimeout(Duration.ZERO).build());
}

// *REQUIRED*
// Injecting your SharedInformerFactory class annotated `@KubernetesInformers`
@Bean("sharedInformerFactory")
public SharedInformerFactory sharedInformerFactory() {
return new MySharedInformerFactory();
}

@Bean
public NodePrintingReconciler nodePrintingReconciler(
Lister<V1Pod> podLister, Lister<V1Node> nodeLister, SharedInformer<V1Node> nodeInformer) {
return new NodePrintingReconciler(podLister, nodeLister, nodeInformer);
}
}

@KubernetesInformers({ // Defining what resources is the informer-factory actually watching.
@KubernetesInformer(
apiTypeClass = V1Node.class,
apiListTypeClass = V1NodeList.class,
groupVersionResource =
@GroupVersionResource(apiGroup = "", apiVersion = "v1", resourcePlural = "nodes"),
resyncPeriodMillis = 60 * 1000L
),
@KubernetesInformer(
apiTypeClass = V1Pod.class,
apiListTypeClass = V1PodList.class,
groupVersionResource =
@GroupVersionResource(apiGroup = "", apiVersion = "v1", resourcePlural = "pods")
),
})
public static class MySharedInformerFactory extends SharedInformerFactory {}

// As long as a reconciler bean attached `@KubernetesReconciler` detected in the context, we will
// be automatically creating a conresponding controller bean implementing {@link
// io.kubernetes.client.extended.controller.Controller}
// with the name specified and registering it to the spring bean-factory.
@KubernetesReconciler(
value = "node-printing-controller",
watches =
@KubernetesReconcilerWatches({
@KubernetesReconcilerWatch(
apiTypeClass = V1Node.class,
resyncPeriodMillis = 60 * 1000L // fully resync every 1 minute
),
})
)
public static class NodePrintingReconciler implements Reconciler {

public NodePrintingReconciler(
Lister<V1Pod> podLister, Lister<V1Node> nodeLister, SharedInformer<V1Node> nodeInformer) {
this.nodeLister = nodeLister;
this.podLister = podLister;
this.nodeInformer = nodeInformer;
}

private SharedInformer<V1Node> nodeInformer;

private Lister<V1Node> nodeLister;

private Lister<V1Pod> podLister;

// *OPTIONAL*
// If you feed like hold the controller from running util some condition..
@KubernetesReconcilerReadyFunc
boolean informerReady() {
return nodeInformer.hasSynced();
}

@Override
public Result reconcile(Request request) {
V1Node node = nodeLister.get(request.getName());
System.out.println("triggered reconciling " + node.getMetadata().getName());
return new Result(false);
}
}
}
5 changes: 5 additions & 0 deletions spring/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@
<artifactId>spring-boot</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>${spring.boot.version}</version>
</dependency>

<dependency>
<groupId>junit</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@
import io.kubernetes.client.util.ClientBuilder;
import io.kubernetes.client.util.Watchable;
import java.io.IOException;
import java.time.Duration;
import java.util.Map;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.Ordered;
import org.springframework.core.ResolvableType;
import org.springframework.stereotype.Component;
Expand All @@ -37,13 +41,16 @@
* injects informers to spring context with the underlying constructing process hidden from users.
*/
@Component
public class KubernetesInformerFactoryProcessor implements BeanFactoryPostProcessor, Ordered {
public class KubernetesInformerFactoryProcessor
implements BeanDefinitionRegistryPostProcessor, Ordered {

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

public static final int ORDER = 0;

private BeanDefinitionRegistry beanDefinitionRegistry;

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
Expand Down Expand Up @@ -79,6 +86,8 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
return;
}
}
apiClient.setHttpClient(
apiClient.getHttpClient().newBuilder().readTimeout(Duration.ZERO).build());

SharedInformerFactory sharedInformerFactory = beanFactory.getBean(SharedInformerFactory.class);
KubernetesInformers kubernetesInformers =
Expand Down Expand Up @@ -124,17 +133,35 @@ public Watchable watch(CallGeneratorParams params) throws ApiException {
ResolvableType informerType =
ResolvableType.forClassWithGenerics(
SharedInformer.class, kubernetesInformer.apiTypeClass());
beanFactory.registerResolvableDependency(informerType.resolve(), sharedIndexInformer);
RootBeanDefinition informerBean = new RootBeanDefinition();
informerBean.setTargetType(informerType);
informerBean.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
informerBean.setAutowireCandidate(true);
String informerBeanName = informerType.toString();
this.beanDefinitionRegistry.registerBeanDefinition(informerBeanName, informerBean);
beanFactory.registerSingleton(informerBeanName, sharedIndexInformer);

Lister lister = new Lister(sharedIndexInformer.getIndexer());
ResolvableType listerType =
ResolvableType.forClassWithGenerics(Lister.class, kubernetesInformer.apiTypeClass());
beanFactory.registerResolvableDependency(listerType.resolve(), lister);
RootBeanDefinition listerBean = new RootBeanDefinition();
listerBean.setTargetType(listerType);
listerBean.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
listerBean.setAutowireCandidate(true);
String listerBeanName = listerType.toString();
this.beanDefinitionRegistry.registerBeanDefinition(listerBeanName, listerBean);
beanFactory.registerSingleton(listerBeanName, lister);
}
}

@Override
public int getOrder() {
return 0;
}

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
throws BeansException {
this.beanDefinitionRegistry = registry;
}
}