Skip to content

Add support for Elasticsearch Sniffer #24340

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

Closed
wants to merge 4 commits into from
Closed

Add support for Elasticsearch Sniffer #24340

wants to merge 4 commits into from

Conversation

somayaj
Copy link
Contributor

@somayaj somayaj commented Dec 5, 2020

Added autoconfig support and tests for the elastic search sniffer.

Added autoconfig support and tests for the elastic search sniffer.
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Dec 5, 2020
@scottfrederick scottfrederick added type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged labels Dec 9, 2020
@scottfrederick scottfrederick added this to the 2.5.x milestone Dec 9, 2020
@scottfrederick scottfrederick changed the title Provide out-of-the-box support for Elasticsearch Sniffer #24174 Provide out-of-the-box support for Elasticsearch Sniffer Dec 9, 2020
@snicoll snicoll changed the title Provide out-of-the-box support for Elasticsearch Sniffer Add support for Elasticsearch Sniffer Dec 21, 2020
Copy link
Member

@snicoll snicoll left a comment

Choose a reason for hiding this comment

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

Thanks for the PR, they are a number of things to reconsider. Can you please have a look?

@ConditionalOnClass(RestHighLevelClient.class)
@ConditionalOnMissingBean(RestClient.class)
@EnableConfigurationProperties(ElasticsearchSnifferProperties.class)
public class ElasticsearchSnifferAutoConfiguration {
Copy link
Member

Choose a reason for hiding this comment

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

I don't think it should be a separate auto-configuration. Rather it should be part of what we already auto-configure.

*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestHighLevelClient.class)
@ConditionalOnMissingBean(RestClient.class)
Copy link
Member

Choose a reason for hiding this comment

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

The purpose of the sniffer is to work based on a RestClient but this auto-configuration doesn't do anything if such bean is present?

static class SnifferBuilderConfiguration {

@Bean
SnifferBuilderCustomizer defaultSnifferBuilderCustomizer(ElasticsearchSnifferProperties properties) {
Copy link
Member

Choose a reason for hiding this comment

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

For simple use case like this one there is no need to create a customizer for what the auto-configuration does. We can smart small and configure the sniffer based on the properties only.

@Bean
Sniffer elasticsearchSnifferBuilder(ElasticsearchSnifferProperties properties,
RestClient elasticsearchRestClient) {
return Sniffer.builder(elasticsearchRestClient).build();
Copy link
Member

Choose a reason for hiding this comment

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

Spring Boot no longer exposes a RestClient. Rather, it is created internally as part of ElasticsearchRestClientAutoConfiguration which is where this code should go.

The documentation also states that order in which those are destroy is important:

It is important to close the Sniffer so that its background thread gets properly shutdown and all of its resources are released. The Sniffer object should have the same lifecycle as the RestClient and get closed right before the client:

/**
* Sniffer interval millis
*/
private Duration sniffIntervalMillis = Duration.ofMillis(1L);
Copy link
Member

Choose a reason for hiding this comment

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

A Duration property should not have mills (or any unit) in its name since users are free to chose a different unit if they want to.

Also, the default value is wrong, it should be 5 min.

/**
* Sniffer failure delay millis.
*/
private Duration sniffFailureDelayMillis = Duration.ofMillis(1L);
Copy link
Member

Choose a reason for hiding this comment

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

The default value is not 1ms but 1 minute.

*
*/
@FunctionalInterface
public interface SnifferBuilderCustomizer {
Copy link
Member

Choose a reason for hiding this comment

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

We don't need this for a first pass. Those who want to configure the NodesSniffer can do so with their own Sniffer instance.

* @author Asha Somayajula
*/
@Testcontainers(disabledWithoutDocker = true)
class ElasticsearchSnifferAutoConfigurationTests {
Copy link
Member

Choose a reason for hiding this comment

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

This should move to the main ElasticsearchRestClientAutoConfigurationTests. A test should be added with a filtered class loader on the sniffer library to validate things are working as expected.

@snicoll snicoll added the status: waiting-for-feedback We need additional information before we can continue label Dec 21, 2020
Copy link
Member

@snicoll snicoll left a comment

Choose a reason for hiding this comment

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

Thanks for the follow-up but some parts of my previous review were not processed. Can you please have a look again?

RestClient elasticSearchRestClient) {
return Sniffer.builder(elasticSearchRestClient)
.setSniffIntervalMillis(
Math.toIntExact(properties.getSniffInterval().getSeconds() * 1000))
Copy link
Member

Choose a reason for hiding this comment

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

This isn't necessary. The duration gives you a way to get the ms directly.

@@ -80,6 +83,17 @@ RestClientBuilder elasticsearchRestClientBuilder(ElasticsearchRestClientProperti
return builder;
}

@Bean
Sniffer elasticsearchSnifferBuilder(ElasticsearchSnifferProperties properties,
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't check that the sniffer library is on the classpath. If it isn't, this bean should not be defined at all. It's also missing a @ConditionalOnMissingBean in case the user has created their own.

*
*/
@ConfigurationProperties(prefix = "spring.elasticsearch.sniff")
public class ElasticsearchSnifferProperties {
Copy link
Member

@snicoll snicoll Dec 22, 2020

Choose a reason for hiding this comment

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

This should move to ElasticsearchRestClientProperties. There is no reason for those two attributes to be in their own type since they relate to Elasticsearch's rest support anyway.

Assert.assertNotNull(sniffer);
});
}

Copy link
Member

Choose a reason for hiding this comment

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

Two tests are missing here.

One that assert what happens when the library is not on the classpath, as I've indicated in my previous review.

One that assert that a custom Sniffer instance is used rather creating one here. This custom instance should probably have a dependency on the high level client we auto-configure to make this a bit more realistic.

Copy link
Member

@snicoll snicoll left a comment

Choose a reason for hiding this comment

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

Thanks for the update again but the tests you've added don't pass. Can you please build the module and make sure the tests are working before submitting further changes?

If you want me to take over, I can also do that.

@Conditional(IsSnifferAvailableOnClasspathCondition.class)
@ConditionalOnMissingBean(RestHighLevelClient.class)
Sniffer elasticsearchSnifferBuilder(ElasticsearchRestClientProperties properties,
RestClient elasticSearchRestClient) {
Copy link
Member

Choose a reason for hiding this comment

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

Have you run the test before pushing an update? As I've explained several times in this issue, we do not expose a RestClient so requiring a RestClient bean is never going to work.

@Bean
@Conditional(IsSnifferAvailableOnClasspathCondition.class)
@ConditionalOnMissingBean(RestHighLevelClient.class)
Sniffer elasticsearchSnifferBuilder(ElasticsearchRestClientProperties properties,
Copy link
Member

Choose a reason for hiding this comment

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

The builder in the name sounds unnecessary.

@@ -80,6 +84,19 @@ RestClientBuilder elasticsearchRestClientBuilder(ElasticsearchRestClientProperti
return builder;
}

@Bean
@Conditional(IsSnifferAvailableOnClasspathCondition.class)
Copy link
Member

Choose a reason for hiding this comment

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

That's not how classpath checks work. Can you please have a look to @ConditionalOnClass and how it's used in other parts of this codebase (in particular, it's not added on methods, check the Javadoc for more details).

/**
* Sniffer interval
*/
private Duration sniffInterval = Duration.ofMinutes(5L);
Copy link
Member

Choose a reason for hiding this comment

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

Those properties should be in a public static class Sniffer inner class. Check other @ConfigurationProperties for an example.

@@ -56,6 +56,14 @@
* Read timeout.
*/
private Duration readTimeout = Duration.ofSeconds(30);
/**
* Sniffer interval
Copy link
Member

Choose a reason for hiding this comment

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

Configuration property description ends with a dot. Please consider reviewing the description to better explain what that does.

@@ -97,4 +105,19 @@ public void setReadTimeout(Duration readTimeout) {
this.readTimeout = readTimeout;
}

public Duration getSniffInterval() {
return sniffInterval;
Copy link
Member

Choose a reason for hiding this comment

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

missing this.. This sort of things can be easily spot if you ran the build locally before pushing your changes.

}

public Duration getSniffFailureDelay() {
return sniffFailureDelay;
Copy link
Member

Choose a reason for hiding this comment

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

Ditto.

import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class IsSnifferAvailableOnClasspathCondition implements Condition {
Copy link
Member

Choose a reason for hiding this comment

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

This should go away. @ConditionalOnClass already does that.

this.contextRunner.run(
(context) -> assertThat(context).doesNotHaveBean(RestClient.class)
.hasSingleBean(RestHighLevelClient.class));
this.contextRunner.run(
Copy link
Member

Choose a reason for hiding this comment

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

A single test shouldn't run the context twice ideally.


@Test
void configureWithCustomSetIntervalProperties() {
this.contextRunner.withPropertyValues("spring.elasticsearch.rest.sniffInterval=15s, " +
Copy link
Member

Choose a reason for hiding this comment

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

15s, is not a valid duration. Did you forget to complete this test maybe?

@somayaj
Copy link
Contributor Author

somayaj commented Dec 23, 2020

Thanks for the feedback. I believe all of the changes requested are in place now.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Dec 23, 2020
@snicoll
Copy link
Member

snicoll commented Dec 23, 2020

I believe all of the changes requested are in place now.

Unfortunately, 4 of the 5 tests you've added are still failing for me. It might be because you don't have docker running locally and the tests are skipped for you (see @Testcontainers(disabledWithoutDocker = true) at the top of the test class).

Thank you very much for the follow-up and the feedback on the reviews. Unfortunately, there are quite a lot of pieces that still need more work and I've decided to take over. Please review 1fd17cf for more details. Thanks for the PR, in any case!

@snicoll snicoll closed this Dec 23, 2020
@snicoll snicoll removed this from the 2.5.x milestone Dec 23, 2020
@snicoll snicoll added status: declined A suggestion or change that we don't feel we should currently apply and removed status: feedback-provided Feedback has been provided type: enhancement A general enhancement labels Dec 23, 2020
@somayaj
Copy link
Contributor Author

somayaj commented Dec 23, 2020 via email

humaolin pushed a commit to humaolin/spring-boot that referenced this pull request May 7, 2022
humaolin pushed a commit to humaolin/spring-boot that referenced this pull request May 7, 2022
humaolin pushed a commit to humaolin/spring-boot that referenced this pull request May 7, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: declined A suggestion or change that we don't feel we should currently apply
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants