Skip to content
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

Register ControllerFactory bean definition as opposite to bean instance #1516

Merged
merged 1 commit into from Feb 7, 2021

Conversation

asavov
Copy link
Contributor

@asavov asavov commented Jan 30, 2021

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

@k8s-ci-robot k8s-ci-robot added the do-not-merge/invalid-commit-message Indicates that a PR should not merge because it has an invalid commit message. label Jan 30, 2021
@k8s-ci-robot
Copy link
Contributor

Thanks for your pull request. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

📝 Please follow instructions at https://git.k8s.io/community/CLA.md#the-contributor-license-agreement to sign the CLA.

It may take a couple minutes for the CLA signature to be fully registered; after that, please reply here with a new comment and we'll verify. Thanks.


Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. I understand the commands that are listed here.

@k8s-ci-robot k8s-ci-robot added the cncf-cla: no Indicates the PR's author has not signed the CNCF CLA. label Jan 30, 2021
@k8s-ci-robot
Copy link
Contributor

Welcome @asavov!

It looks like this is your first PR to kubernetes-client/java 🎉. Please refer to our pull request process documentation to help your PR have a smooth ride to approval.

You will be prompted by a bot to use commands during the review process. Do not be afraid to follow the prompts! It is okay to experiment. Here is the bot commands documentation.

You can also check if kubernetes-client/java has its own contribution guidelines.

You may want to refer to our testing guide if you run into trouble with your tests not passing.

If you are having difficulty getting your pull request seen, please follow the recommended escalation practices. Also, for tips and tricks in the contribution process you may want to read the Kubernetes contributor cheat sheet. We want to make sure your contribution gets all the attention it needs!

Thank you, and welcome to Kubernetes. 😃

@k8s-ci-robot k8s-ci-robot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Jan 30, 2021
Copy link
Member

@yue9944882 yue9944882 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/lgtm

@asavov can you remove @Autowired from the commit message, otherwise it will lead to spammy github references on the page

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Feb 1, 2021
@brendandburns
Copy link
Contributor

Looks like you need to run mvn spotless:apply to fix formatting.

@k8s-ci-robot k8s-ci-robot removed lgtm "Looks good to me", indicates that a PR is ready to be merged. do-not-merge/invalid-commit-message Indicates that a PR should not merge because it has an invalid commit message. labels Feb 2, 2021
@asavov
Copy link
Contributor Author

asavov commented Feb 2, 2021

/lgtm

@asavov can you remove @Autowired from the commit message, otherwise it will lead to spammy github references on the page

Done with updated commit.

@asavov
Copy link
Contributor Author

asavov commented Feb 2, 2021

Looks like you need to run mvn spotless:apply to fix formatting.

Done. And now seems ok.

@asavov
Copy link
Contributor Author

asavov commented Feb 2, 2021

@dsyer @brendanburns https://github.com/kubernetes-client/java/pull/1516/checks has failed. Not sure it's related to this PR. How to Re-run it through UI? Or should I upload new "dummy" commit?

@asavov
Copy link
Contributor Author

asavov commented Feb 2, 2021

You just signed CNCF Individual Contributor License Agreement. Seems this one is also checked.

@k8s-ci-robot k8s-ci-robot added cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. and removed cncf-cla: no Indicates the PR's author has not signed the CNCF CLA. labels Feb 2, 2021
@dsyer
Copy link
Contributor

dsyer commented Feb 2, 2021

Looking good. Sorry for the CLA hassle (at least you only have to do it once).

I would make a couple of small (but could be important) changes. I don’t see a need to inject the SharedInformerFactory and it will cause the same kind of instantiation cascade as the getBean() calls we were trying to avoid. Also we might need to be more defensive about the downcast of the BeanFactory.

@asavov
Copy link
Contributor Author

asavov commented Feb 2, 2021

Looking good. Sorry for the CLA hassle (at least you only have to do it once).

No worries at all. It's done.

I don’t see a need to inject the SharedInformerFactory and it will cause the same kind of instantiation cascade as the getBean() calls we were trying to avoid.

  static class TestReconciler implements Reconciler {

    @Autowired private SharedInformer<V1Pod> informerToBeInjected;

Do you refer this snippet? The whole idea is to be able to do exactly this logic: autowire other beans, benefiting from Spring container. I just used SharedInformer, cause it's natural fit, still it might be Env, or AppCtx or @value(${some.prop}). Would provided more details, cause maybe I miss something.

Also we might need to be more defensive about the downcast of the BeanFactory.

The way to go is to extend from BeanDefinitionRegistryPostProcessor and implement postProcessBeanDefinitionRegistry. Then the cast is not needed any more. I'm ok to do it, if you think that's the right approach.

@dsyer
Copy link
Contributor

dsyer commented Feb 2, 2021

BeanDefinitionRegistryPostProcessor would probably be better. The informer factory I mentioned wasn’t in a test though, I thought I saw it being injected directly into the post processor.

@asavov
Copy link
Contributor Author

asavov commented Feb 2, 2021

BeanDefinitionRegistryPostProcessor would probably be better. The informer factory I mentioned wasn’t in a test though, I thought I saw it being injected directly into the post processor.

public class KubernetesReconcilerProcessor implements BeanFactoryPostProcessor, Ordered {

  private final SharedInformerFactory sharedInformerFactory;

  public KubernetesReconcilerProcessor(SharedInformerFactory sharedInformerFactory) {
    this.sharedInformerFactory = sharedInformerFactory;
  }

You are absolutely right. Hmm. What's the Spring/right way for a BeanFactoryPostProcessor to get this information? A pointer to valid example within a Spring component would definitely help.

@dsyer
Copy link
Contributor

dsyer commented Feb 2, 2021

One way would be to set a constructor arg ref (not value) if you can get the bean name by type for the SharedInformerFactory from the ListableBeanFactory. If you look at callers of BeanDefinitionBuilder.addConstructorArgReference() you will find existing examples. Some of them work by using a naming convention - that's the simplest - and some have more elaborate ways of looking up the bean definition name for the reference. Or you can use a Supplier to defer the computation. I think this is safe (to create a Bar from an existing Foo):

	@Bean
	public static BeanDefinitionRegistryPostProcessor processor() {
		return new BeanDefinitionRegistryPostProcessor() {

			private ConfigurableListableBeanFactory beanFactory;

			@Override
			public void postProcessBeanFactory(
					ConfigurableListableBeanFactory beanFactory) throws BeansException {
				this.beanFactory = beanFactory;
			}

			@Override
			public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
					throws BeansException {
				BeanDefinitionBuilder definition = BeanDefinitionBuilder
						.genericBeanDefinition(Bar.class,
								() -> new Bar(beanFactory.getBean(Foo.class)));
				registry.registerBeanDefinition("bar", definition.getBeanDefinition());
			}
		};
	}

@asavov
Copy link
Contributor Author

asavov commented Feb 2, 2021

Looking at the code of KubernetesInformerFactoryProcessor and KubernetesReconcilerProcessor I came to a conclusion (maybe I'm wrong) that something very generic is mis-implemented. Those classes are both BeanDefinitionRegistryPostProcessor, which implies they work/manipulate on BeanDefinitions or refs or lazy suppliers, still they both accept ready Bean Instances as constr params (ApiClient and SharedInformerFactory).
How about having some live chat to further discuss this or ... cause if we want to impl it right it seems like a breaking change. Or maybe I lack enough knowledge on the subject.

@dsyer
Copy link
Contributor

dsyer commented Feb 2, 2021

Those two post processors are unrelated to this change though, right? They need to be fixed in probably almost identical ways, if we decide we want to keep them, but I don't think we have to interrupt this PR process to deal with that.

@asavov
Copy link
Contributor Author

asavov commented Feb 2, 2021

Those two post processors are unrelated to this change though, right? They need to be fixed in probably almost identical ways, if we decide we want to keep them, but I don't think we have to interrupt this PR process to deal with that.

I miss that, cause the focus of the change is KubernetesReconcilerProcessor. Or? The idea is to introduce a new player ?

@dsyer
Copy link
Contributor

dsyer commented Feb 2, 2021

I'm sorry, you are right. You fixed one of them in this PR, though? So we can leave the other one for another effort?

@asavov
Copy link
Contributor Author

asavov commented Feb 3, 2021

I'm sorry, you are right. You fixed one of them in this PR, though? So we can leave the other one for another effort?

Should I go with current version (if approved) OR remove

  public KubernetesReconcilerProcessor(SharedInformerFactory sharedInformerFactory) {
    this.sharedInformerFactory = sharedInformerFactory;
  }

(causing SharedInformerFactory to be pre-instantiated) and default to the SINGLETON SharedInformerFactory instance to be used/selected by KubernetesReconcilerProcessor#postProcessBeanFactory. Thus no bean instantiation at least by KubernetesReconcilerProcessor, based on some defaults, let's say one-and-only-one instance of SharedInformerFactory.

  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    for (String reconcilerName : beanFactory.getBeanNamesForType(Reconciler.class)) {
      BeanDefinition controllerFactoryBeanDefinition =
          BeanDefinitionBuilder.genericBeanDefinition(KubernetesControllerFactory.class)
              .addConstructorArgReference(<the name of singleton shared informer factory>)
              .addConstructorArgReference(reconcilerName)
              .getBeanDefinition();

      // Replace this with call to BDRegistry passed by BeanDefinitionRegistryPostProcessor
      ((DefaultListableBeanFactory) beanFactory)
          .registerBeanDefinition(reconcilerName + "Controller", controllerFactoryBeanDefinition);
    }
  }

@dsyer
Copy link
Contributor

dsyer commented Feb 3, 2021

That would work. We'd have to be sure about the bean name. The lazy instantiation alternative with a Supplier is better though IMO (no reflection).

@asavov
Copy link
Contributor Author

asavov commented Feb 3, 2021

@dsyer Does this version satisfy-them-all ? :)

public class KubernetesReconcilerProcessor implements BeanDefinitionRegistryPostProcessor, Ordered {

  public static final String DEFAULT_SHARED_INFORMER_FACTORY_BEAN_NAME = "sharedInformerFactory";

  private final SharedInformerFactory sharedInformerFactory;

  private final String sharedInformerFactoryBeanName;

  private ConfigurableListableBeanFactory beanFactory;

  @Deprecated
  // TODO: Should we keep this for backward compatibility?
  public KubernetesReconcilerProcessor(SharedInformerFactory sharedInformerFactory) {
    this.sharedInformerFactory = sharedInformerFactory;
    this.sharedInformerFactoryBeanName = null;
  }

  public KubernetesReconcilerProcessor() {
    this(DEFAULT_SHARED_INFORMER_FACTORY_BEAN_NAME);
  }

  public KubernetesReconcilerProcessor(String sharedInformerFactoryBeanName) {
    notNull(sharedInformerFactoryBeanName, "SharedInformerFactory bean name is required");
    this.sharedInformerFactory = null;
    this.sharedInformerFactoryBeanName = DEFAULT_SHARED_INFORMER_FACTORY_BEAN_NAME;
  }

  @Override
  public int getOrder() {
    return KubernetesInformerFactoryProcessor.ORDER + 1;
  }

  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    this.beanFactory = beanFactory;
  }

  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    for (String reconcilerName : beanFactory.getBeanNamesForType(Reconciler.class)) {

      Supplier<KubernetesControllerFactory> kubernetesControllerFactorySupplier;

      if (sharedInformerFactory != null) {
        kubernetesControllerFactorySupplier =
            () ->
                new KubernetesControllerFactory(
                    sharedInformerFactory, beanFactory.getBean(reconcilerName, Reconciler.class));
      } else {
        kubernetesControllerFactorySupplier =
            () ->
                new KubernetesControllerFactory(
                    beanFactory.getBean(sharedInformerFactoryBeanName, SharedInformerFactory.class),
                    beanFactory.getBean(reconcilerName, Reconciler.class));
      }

      BeanDefinition controllerFactoryBeanDefinition =
          BeanDefinitionBuilder.genericBeanDefinition(
                  KubernetesControllerFactory.class, kubernetesControllerFactorySupplier)
              .getBeanDefinition();

      registry.registerBeanDefinition(
          reconcilerName + "Controller", controllerFactoryBeanDefinition);
    }
  }
}

@dsyer
Copy link
Contributor

dsyer commented Feb 3, 2021

That would work, but I don't see why we need to keep the old constructor, or the code path with if (sharedInformerFactory != null).

@asavov
Copy link
Contributor Author

asavov commented Feb 3, 2021

That would work, but I don't see why we need to keep the old constructor, or the code path with if (sharedInformerFactory != null).

If backward compatibility is not an issue - my pleasure to simplify it :)

@dsyer
Copy link
Contributor

dsyer commented Feb 3, 2021

IMO no-one is using this class directly - it's all via autoconfiguration. Plus although it still says 11.0.1-SNAPSHOT in the top level POM, I know the next release will be 12.0.0 because of the Joda time changes. So I don't think we should care about backwards compatibility here. @yue9944882 would have to say for sure, but she also said that other day that this whole module is considered a prototype still, so breaking changes are expected.

@asavov
Copy link
Contributor Author

asavov commented Feb 3, 2021

IMO no-one is using this class directly - it's all via autoconfiguration. Plus although it still says 11.0.1-SNAPSHOT in the top level POM, I know the next release will be 12.0.0 because of the Joda time changes. So I don't think we should care about backwards compatibility here. @yue9944882 would have to say for sure, but she also said that other day that this whole module is considered a prototype still, so breaking changes are expected.

PR update with new code as discussed.

@asavov asavov force-pushed the controllerFactory branch 2 times, most recently from c48a82b to c7ab7d6 Compare February 3, 2021 11:04
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
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
for (String reconcilerName : beanFactory.getBeanNamesForType(Reconciler.class)) {

Supplier<KubernetesControllerFactory> kubernetesControllerFactorySupplier =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this can be inlined. It's a matter of taste though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree it's opinionated. I prefer readability vs. compact code.
By the way the PR is finally green... :)

@dsyer
Copy link
Contributor

dsyer commented Feb 3, 2021

LGTM

@asavov
Copy link
Contributor Author

asavov commented Feb 3, 2021

LGTM

Nice! and thank you.

This is my first contribution, so the PR would be eventually merged by someone on kubernetes-client team with write access. No more actions on my site?

@dsyer
Copy link
Contributor

dsyer commented Feb 4, 2021

Yes, we have to wait for @brendanburns and @yue9944882 . If they have no more changes to request they can merge it without you doing anything else.

Copy link
Member

@yue9944882 yue9944882 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/lgtm
/approve

thanks @dsyer @asavov!

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Feb 7, 2021
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: asavov, yue9944882

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Feb 7, 2021
@k8s-ci-robot k8s-ci-robot merged commit 4681f03 into kubernetes-client:master Feb 7, 2021
@dsyer
Copy link
Contributor

dsyer commented Feb 8, 2021

@asavov are you going to do the same thing for KubernetesInformerFactoryProcessor?

@asavov
Copy link
Contributor Author

asavov commented Feb 8, 2021

KubernetesInformerFactoryProcessor

@dsyer I'd like to. Still at the moment not enough bandwidth.

@dsyer
Copy link
Contributor

dsyer commented Feb 8, 2021

See #1532 for my solution

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. lgtm "Looks good to me", indicates that a PR is ready to be merged. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants