From aa27bb02f2692d682cf6d334dd7e3c08baeb43c0 Mon Sep 17 00:00:00 2001 From: Alexander Savov Date: Sat, 30 Jan 2021 21:49:45 +0200 Subject: [PATCH] Register ControllerFactory bean definition as opposite to bean instance With this change KubernetesReconcilerProcessor conforms to BeanFactoryPostProcessor contract to register BeanDefinitions and not to manipulate bean instances. As a result Reconciler beans are: 1) not created at all by postProcessBeanFactory (which was the core issue) 2) participate in Spring container management hooks, and hence autowired --- .../KubernetesReconcilerProcessor.java | 43 +++---- .../KubernetesReconcilerProcessorTest.java | 108 ++++++++++++++++++ 2 files changed, 122 insertions(+), 29 deletions(-) create mode 100644 spring/src/test/java/io/kubernetes/client/spring/extended/controller/KubernetesReconcilerProcessorTest.java diff --git a/spring/src/main/java/io/kubernetes/client/spring/extended/controller/KubernetesReconcilerProcessor.java b/spring/src/main/java/io/kubernetes/client/spring/extended/controller/KubernetesReconcilerProcessor.java index 04184a564d..e5872d57d4 100644 --- a/spring/src/main/java/io/kubernetes/client/spring/extended/controller/KubernetesReconcilerProcessor.java +++ b/spring/src/main/java/io/kubernetes/client/spring/extended/controller/KubernetesReconcilerProcessor.java @@ -12,19 +12,14 @@ */ package io.kubernetes.client.spring.extended.controller; -import io.kubernetes.client.extended.controller.Controller; -import io.kubernetes.client.extended.controller.ControllerManager; import io.kubernetes.client.extended.controller.reconciler.Reconciler; import io.kubernetes.client.informer.SharedInformerFactory; -import io.kubernetes.client.spring.extended.controller.annotation.KubernetesReconciler; import io.kubernetes.client.spring.extended.controller.factory.KubernetesControllerFactory; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.core.Ordered; /** @@ -36,13 +31,7 @@ */ public class KubernetesReconcilerProcessor implements BeanFactoryPostProcessor, Ordered { - private static final Logger log = LoggerFactory.getLogger(KubernetesReconcilerProcessor.class); - - private ControllerManager controllerManager; - - private ExecutorService controllerManagerDaemon = Executors.newSingleThreadExecutor(); - - private SharedInformerFactory sharedInformerFactory; + private final SharedInformerFactory sharedInformerFactory; public KubernetesReconcilerProcessor(SharedInformerFactory sharedInformerFactory) { this.sharedInformerFactory = sharedInformerFactory; @@ -54,20 +43,16 @@ public int getOrder() { } @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) - throws BeansException { - String[] names = beanFactory.getBeanNamesForType(Reconciler.class); - for (String name : names) { - Reconciler reconciler = (Reconciler) beanFactory.getBean(name); - KubernetesReconciler kubernetesReconciler = - reconciler.getClass().getAnnotation(KubernetesReconciler.class); - String reconcilerName = kubernetesReconciler.value(); - - KubernetesControllerFactory controllerFactory = - new KubernetesControllerFactory(sharedInformerFactory, reconciler); - - Controller controller = controllerFactory.getObject(); - beanFactory.registerSingleton(reconcilerName, controller); + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { + for (String reconcilerName : beanFactory.getBeanNamesForType(Reconciler.class)) { + BeanDefinition controllerFactoryBeanDefinition = + BeanDefinitionBuilder.genericBeanDefinition(KubernetesControllerFactory.class) + .addConstructorArgValue(sharedInformerFactory) + .addConstructorArgReference(reconcilerName) + .getBeanDefinition(); + + ((DefaultListableBeanFactory) beanFactory) + .registerBeanDefinition(reconcilerName + "Controller", controllerFactoryBeanDefinition); } } } diff --git a/spring/src/test/java/io/kubernetes/client/spring/extended/controller/KubernetesReconcilerProcessorTest.java b/spring/src/test/java/io/kubernetes/client/spring/extended/controller/KubernetesReconcilerProcessorTest.java new file mode 100644 index 0000000000..c1ca52a0b8 --- /dev/null +++ b/spring/src/test/java/io/kubernetes/client/spring/extended/controller/KubernetesReconcilerProcessorTest.java @@ -0,0 +1,108 @@ +/* +Copyright 2020 The Kubernetes Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package io.kubernetes.client.spring.extended.controller; + +import static org.junit.Assert.assertNotNull; + +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.openapi.models.V1Pod; +import io.kubernetes.client.openapi.models.V1PodList; +import io.kubernetes.client.spring.extended.controller.annotation.GroupVersionResource; +import io.kubernetes.client.spring.extended.controller.annotation.KubernetesInformer; +import io.kubernetes.client.spring.extended.controller.annotation.KubernetesInformers; +import io.kubernetes.client.spring.extended.controller.annotation.KubernetesReconciler; +import io.kubernetes.client.spring.extended.controller.annotation.KubernetesReconcilerWatches; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = KubernetesReconcilerProcessorTest.App.class) +public class KubernetesReconcilerProcessorTest { + + @SpringBootApplication + static class App { + + @Bean + KubernetesReconcilerProcessor reconcilerProcessorUnderTesting( + SharedInformerFactory sharedInformerFactory) { + return new KubernetesReconcilerProcessor(sharedInformerFactory); + } + + @Bean + SharedInformerFactory sharedInformerFactory() { + return new TestSharedInformerFactory(); + } + + @KubernetesInformers({ + @KubernetesInformer( + apiTypeClass = V1Pod.class, + apiListTypeClass = V1PodList.class, + groupVersionResource = @GroupVersionResource(resourcePlural = "pods")) + }) + static class TestSharedInformerFactory extends SharedInformerFactory {} + + @Bean("testReconciler1") + TestReconciler testReconciler1ToBeInjected() { + return new TestReconciler(); + } + + @Bean("testReconciler2") + TestReconciler testReconciler2ToBeInjected() { + return new TestReconciler(); + } + } + + @KubernetesReconciler(watches = @KubernetesReconcilerWatches()) + static class TestReconciler implements Reconciler { + + @Autowired private SharedInformer informerToBeInjected; + + @Override + public Result reconcile(Request request) { + return new Result(false); + } + } + + @Autowired + @Qualifier("testReconciler1Controller") + private Controller testController1CreatedByReconcilerProcessor; + + @Autowired + @Qualifier("testReconciler2Controller") + private Controller testController2CreatedByReconcilerProcessor; + + @Autowired + @Qualifier("testReconciler1") + private TestReconciler testReconciler1ToBeInjected; + + @Autowired + @Qualifier("testReconciler2") + private TestReconciler testReconciler2ToBeInjected; + + @Test + public void testAutowiredFieldsOfReconcilerBeansAreSet() { + assertNotNull(testReconciler1ToBeInjected.informerToBeInjected); + assertNotNull(testReconciler2ToBeInjected.informerToBeInjected); + } +}