From 4a030d5a7ae4d820f6cbd291491639013809bac9 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 15 Jun 2017 16:29:46 +0100 Subject: [PATCH] Drop support for auto-configuring an embedded Elasticsearch node Elastic have announced [1] that embedded Elasticsearch is no longer supported. This commit brings us into line with that announcement by removing the auto-configuration that would create an Elasticsearch Node and NodeClient. To use the Elasticsearch auto-configuration, a user must now provide the address of one or more cluster nodes (via the spring.elastisearch.cluster-nodes property) which will then be used to create a TransportClient. See gh-9374 [1] https://www.elastic.co/blog/elasticsearch-the-server --- ...HealthIndicatorAutoConfigurationTests.java | 2 +- .../ElasticsearchAutoConfiguration.java | 132 +----------------- .../ElasticsearchProperties.java | 3 +- .../SpringBootWebSecurityConfiguration.java | 4 + .../ElasticsearchAutoConfigurationTests.java | 84 +++-------- ...asticsearchDataAutoConfigurationTests.java | 66 +++++---- .../ElasticsearchNodeTemplate.java | 111 +++++++++++++++ ...rchRepositoriesAutoConfigurationTests.java | 51 +++---- .../jest/JestAutoConfigurationTests.java | 105 ++++---------- .../main/asciidoc/spring-boot-features.adoc | 24 +--- .../pom.xml | 6 - .../src/main/resources/application.properties | 7 +- .../SampleElasticsearchApplicationTests.java | 27 +++- 13 files changed, 268 insertions(+), 354 deletions(-) create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchNodeTemplate.java diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfigurationTests.java index 9cfdaa2110..98bc14741b 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfigurationTests.java @@ -441,7 +441,7 @@ public class HealthIndicatorAutoConfigurationTests { @Test public void elasticsearchHealthIndicator() { TestPropertyValues - .of("spring.data.elasticsearch.properties.path.home:target", + .of("spring.data.elasticsearch.cluster-nodes:localhost:0", "management.health.diskspace.enabled:false") .applyTo(this.context); this.context.register(JestClientConfiguration.class, JestAutoConfiguration.class, diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchAutoConfiguration.java index 7fcf0fa377..f64a7a1e2a 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchAutoConfiguration.java @@ -16,34 +16,18 @@ package org.springframework.boot.autoconfigure.data.elasticsearch; -import java.io.Closeable; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; import java.util.Properties; -import java.util.Set; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.elasticsearch.client.Client; import org.elasticsearch.client.transport.TransportClient; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.node.InternalSettingsPreparer; -import org.elasticsearch.node.Node; -import org.elasticsearch.plugins.Plugin; -import org.springframework.beans.factory.DisposableBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.data.elasticsearch.client.NodeClientFactoryBean; import org.springframework.data.elasticsearch.client.TransportClientFactoryBean; -import org.springframework.util.ClassUtils; -import org.springframework.util.StringUtils; /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration @@ -55,101 +39,25 @@ import org.springframework.util.StringUtils; * @since 1.1.0 */ @Configuration -@ConditionalOnClass({ Client.class, TransportClientFactoryBean.class, - NodeClientFactoryBean.class }) +@ConditionalOnClass({ Client.class, TransportClientFactoryBean.class }) +@ConditionalOnProperty(prefix = "spring.data.elasticsearch", name = "cluster-nodes", matchIfMissing = false) @EnableConfigurationProperties(ElasticsearchProperties.class) -public class ElasticsearchAutoConfiguration implements DisposableBean { - - private static final Map DEFAULTS; - - static { - Map defaults = new LinkedHashMap<>(); - defaults.put("http.enabled", String.valueOf(false)); - defaults.put("transport.type", "local"); - defaults.put("path.home", System.getProperty("user.dir")); - DEFAULTS = Collections.unmodifiableMap(defaults); - } - - private static final Set TRANSPORT_PLUGINS; - - static { - Set plugins = new LinkedHashSet<>(); - plugins.add("org.elasticsearch.transport.Netty4Plugin"); - plugins.add("org.elasticsearch.transport.Netty3Plugin"); - TRANSPORT_PLUGINS = Collections.unmodifiableSet(plugins); - } - - private static final Log logger = LogFactory - .getLog(ElasticsearchAutoConfiguration.class); +public class ElasticsearchAutoConfiguration { private final ElasticsearchProperties properties; - private Closeable closeable; - public ElasticsearchAutoConfiguration(ElasticsearchProperties properties) { this.properties = properties; } @Bean @ConditionalOnMissingBean - public Client elasticsearchClient() { - try { - return createClient(); - } - catch (Exception ex) { - throw new IllegalStateException(ex); - } - } - - private Client createClient() throws Exception { - if (StringUtils.hasLength(this.properties.getClusterNodes())) { - return createTransportClient(); - } - return createNodeClient(); - } - - private Client createNodeClient() throws Exception { - Settings.Builder settings = Settings.builder(); - for (Map.Entry entry : DEFAULTS.entrySet()) { - if (!this.properties.getProperties().containsKey(entry.getKey())) { - settings.put(entry.getKey(), entry.getValue()); - } - } - settings.put(this.properties.getProperties()); - settings.put("cluster.name", this.properties.getClusterName()); - Node node = createNode(settings.build()); - this.closeable = node; - node.start(); - return node.client(); - } - - private Node createNode(Settings settings) { - Collection> plugins = findPlugins(); - if (plugins.isEmpty()) { - return new Node(settings); - } - return new PluggableNode(settings, plugins); - } - - @SuppressWarnings("unchecked") - private Collection> findPlugins() { - for (String candidate : TRANSPORT_PLUGINS) { - if (ClassUtils.isPresent(candidate, null)) { - Class pluginClass = (Class) ClassUtils - .resolveClassName(candidate, null); - return Collections.singleton(pluginClass); - } - } - return Collections.emptySet(); - } - - private Client createTransportClient() throws Exception { + public TransportClient elasticsearchClient() throws Exception { TransportClientFactoryBean factory = new TransportClientFactoryBean(); factory.setClusterNodes(this.properties.getClusterNodes()); factory.setProperties(createProperties()); factory.afterPropertiesSet(); TransportClient client = factory.getObject(); - this.closeable = client; return client; } @@ -160,34 +68,4 @@ public class ElasticsearchAutoConfiguration implements DisposableBean { return properties; } - @Override - public void destroy() throws Exception { - if (this.closeable != null) { - try { - if (logger.isInfoEnabled()) { - logger.info("Closing Elasticsearch client"); - } - this.closeable.close(); - } - catch (final Exception ex) { - if (logger.isErrorEnabled()) { - logger.error("Error closing Elasticsearch client: ", ex); - } - } - } - } - - /** - * {@link Node} subclass to support {@link Plugin Plugins}. - */ - private static class PluggableNode extends Node { - - PluggableNode(Settings preparedSettings, - Collection> classpathPlugins) { - super(InternalSettingsPreparer.prepareEnvironment(preparedSettings, null), - classpathPlugins); - } - - } - } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchProperties.java index a9436e75b5..bd8ff36ca4 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchProperties.java @@ -37,8 +37,7 @@ public class ElasticsearchProperties { private String clusterName = "elasticsearch"; /** - * Comma-separated list of cluster node addresses. If not specified, starts a client - * node. + * Comma-separated list of cluster node addresses. */ private String clusterNodes; diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfiguration.java index cd243eaf03..261b6dadd7 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfiguration.java @@ -230,6 +230,10 @@ public class SpringBootWebSecurityConfiguration { @Override protected void configure(HttpSecurity http) throws Exception { + http.authorizeRequests() + .requestMatchers( + (request) -> request.getHeader("Host").equals("whatever")) + .permitAll(); http.requestMatcher(new RequestMatcher() { @Override public boolean matches(HttpServletRequest request) { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchAutoConfigurationTests.java index 1af05a8789..14731341c9 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchAutoConfigurationTests.java @@ -16,15 +16,17 @@ package org.springframework.boot.autoconfigure.data.elasticsearch; +import java.util.List; + +import org.assertj.core.api.Assertions; import org.elasticsearch.client.Client; -import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.client.transport.TransportClient; +import org.elasticsearch.cluster.node.DiscoveryNode; import org.junit.After; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.springframework.beans.factory.BeanCreationException; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -38,6 +40,7 @@ import static org.mockito.Mockito.mock; * Tests for {@link ElasticsearchAutoConfiguration}. * * @author Phillip Webb + * @author Andy Wilkinson */ public class ElasticsearchAutoConfigurationTests { @@ -46,57 +49,11 @@ public class ElasticsearchAutoConfigurationTests { private AnnotationConfigApplicationContext context; - @Before - public void preventElasticsearchFromConfiguringNetty() { - System.setProperty("es.set.netty.runtime.available.processors", "false"); - } - @After public void close() { if (this.context != null) { this.context.close(); } - System.clearProperty("es.set.netty.runtime.available.processors"); - } - - @Test - public void createNodeClientWithDefaults() { - this.context = new AnnotationConfigApplicationContext(); - TestPropertyValues - .of("spring.data.elasticsearch.properties.monitor.process.refresh_interval:2s", - "spring.data.elasticsearch.properties.path.home:target") - .applyTo(this.context); - this.context.register(PropertyPlaceholderAutoConfiguration.class, - ElasticsearchAutoConfiguration.class); - this.context.refresh(); - assertThat(this.context.getBeanNamesForType(Client.class).length).isEqualTo(1); - NodeClient client = (NodeClient) this.context.getBean(Client.class); - assertThat(client.settings().get("monitor.process.refresh_interval")) - .isEqualTo("2s"); - assertThat(client.settings().get("transport.type")).isEqualTo("local"); - assertThat(client.settings().get("http.enabled")).isEqualTo("false"); - } - - @Test - public void createNodeClientWithOverrides() { - this.context = new AnnotationConfigApplicationContext(); - TestPropertyValues - .of("spring.data.elasticsearch.properties.monitor.process.refresh_interval:2s", - "spring.data.elasticsearch.properties.path.home:target", - "spring.data.elasticsearch.properties.transport.type:local", - "spring.data.elasticsearch.properties.node.data:true", - "spring.data.elasticsearch.properties.http.enabled:true") - .applyTo(this.context); - this.context.register(PropertyPlaceholderAutoConfiguration.class, - ElasticsearchAutoConfiguration.class); - this.context.refresh(); - assertThat(this.context.getBeanNamesForType(Client.class).length).isEqualTo(1); - NodeClient client = (NodeClient) this.context.getBean(Client.class); - assertThat(client.settings().get("monitor.process.refresh_interval")) - .isEqualTo("2s"); - assertThat(client.settings().get("transport.type")).isEqualTo("local"); - assertThat(client.settings().get("node.data")).isEqualTo("true"); - assertThat(client.settings().get("http.enabled")).isEqualTo("true"); } @Test @@ -106,25 +63,28 @@ public class ElasticsearchAutoConfigurationTests { PropertyPlaceholderAutoConfiguration.class, ElasticsearchAutoConfiguration.class); this.context.refresh(); - assertThat(this.context.getBeanNamesForType(Client.class).length).isEqualTo(1); - assertThat(this.context.getBean("myClient")) + Assertions.assertThat(this.context.getBeanNamesForType(Client.class).length) + .isEqualTo(1); + Assertions.assertThat(this.context.getBean("myClient")) .isSameAs(this.context.getBean(Client.class)); } @Test public void createTransportClient() throws Exception { - // We don't have a local elasticsearch server so use an address that's missing - // a port and check the exception this.context = new AnnotationConfigApplicationContext(); - TestPropertyValues - .of("spring.data.elasticsearch.cluster-nodes:localhost", - "spring.data.elasticsearch.properties.path.home:target") - .applyTo(this.context); - this.context.register(PropertyPlaceholderAutoConfiguration.class, - ElasticsearchAutoConfiguration.class); - this.thrown.expect(BeanCreationException.class); - this.thrown.expectMessage("port"); - this.context.refresh(); + new ElasticsearchNodeTemplate().doWithNode((node) -> { + TestPropertyValues + .of("spring.data.elasticsearch.cluster-nodes:localhost:" + + node.getTcpPort(), + "spring.data.elasticsearch.properties.path.home:target/es/client") + .applyTo(this.context); + this.context.register(PropertyPlaceholderAutoConfiguration.class, + ElasticsearchAutoConfiguration.class); + this.context.refresh(); + List connectedNodes = this.context + .getBean(TransportClient.class).connectedNodes(); + assertThat(connectedNodes).hasSize(1); + }); } @Configuration diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchDataAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchDataAutoConfigurationTests.java index fc5b687c71..48ee3833a7 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchDataAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchDataAutoConfigurationTests.java @@ -48,43 +48,55 @@ public class ElasticsearchDataAutoConfigurationTests { @Test public void templateExists() { this.context = new AnnotationConfigApplicationContext(); - TestPropertyValues - .of("spring.data.elasticsearch.properties.path.data:target/data", - "spring.data.elasticsearch.properties.path.logs:target/logs") - .applyTo(this.context); - this.context.register(PropertyPlaceholderAutoConfiguration.class, - ElasticsearchAutoConfiguration.class, - ElasticsearchDataAutoConfiguration.class); - this.context.refresh(); - assertHasSingleBean(ElasticsearchTemplate.class); + new ElasticsearchNodeTemplate().doWithNode((node) -> { + TestPropertyValues + .of("spring.data.elasticsearch.properties.path.data:target/data", + "spring.data.elasticsearch.properties.path.logs:target/logs", + "spring.data.elasticsearch.cluster-nodes:localhost:" + + node.getTcpPort()) + .applyTo(this.context); + this.context.register(PropertyPlaceholderAutoConfiguration.class, + ElasticsearchAutoConfiguration.class, + ElasticsearchDataAutoConfiguration.class); + this.context.refresh(); + assertHasSingleBean(ElasticsearchTemplate.class); + }); } @Test public void mappingContextExists() { this.context = new AnnotationConfigApplicationContext(); - TestPropertyValues - .of("spring.data.elasticsearch.properties.path.data:target/data", - "spring.data.elasticsearch.properties.path.logs:target/logs") - .applyTo(this.context); - this.context.register(PropertyPlaceholderAutoConfiguration.class, - ElasticsearchAutoConfiguration.class, - ElasticsearchDataAutoConfiguration.class); - this.context.refresh(); - assertHasSingleBean(SimpleElasticsearchMappingContext.class); + new ElasticsearchNodeTemplate().doWithNode((node) -> { + TestPropertyValues + .of("spring.data.elasticsearch.properties.path.data:target/data", + "spring.data.elasticsearch.properties.path.logs:target/logs", + "spring.data.elasticsearch.cluster-nodes:localhost:" + + node.getTcpPort()) + .applyTo(this.context); + this.context.register(PropertyPlaceholderAutoConfiguration.class, + ElasticsearchAutoConfiguration.class, + ElasticsearchDataAutoConfiguration.class); + this.context.refresh(); + assertHasSingleBean(SimpleElasticsearchMappingContext.class); + }); } @Test public void converterExists() { this.context = new AnnotationConfigApplicationContext(); - TestPropertyValues - .of("spring.data.elasticsearch.properties.path.data:target/data", - "spring.data.elasticsearch.properties.path.logs:target/logs") - .applyTo(this.context); - this.context.register(PropertyPlaceholderAutoConfiguration.class, - ElasticsearchAutoConfiguration.class, - ElasticsearchDataAutoConfiguration.class); - this.context.refresh(); - assertHasSingleBean(ElasticsearchConverter.class); + new ElasticsearchNodeTemplate().doWithNode((node) -> { + TestPropertyValues + .of("spring.data.elasticsearch.properties.path.data:target/data", + "spring.data.elasticsearch.properties.path.logs:target/logs", + "spring.data.elasticsearch.cluster-nodes:localhost:" + + node.getTcpPort()) + .applyTo(this.context); + this.context.register(PropertyPlaceholderAutoConfiguration.class, + ElasticsearchAutoConfiguration.class, + ElasticsearchDataAutoConfiguration.class); + this.context.refresh(); + assertHasSingleBean(ElasticsearchConverter.class); + }); } private void assertHasSingleBean(Class type) { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchNodeTemplate.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchNodeTemplate.java new file mode 100644 index 0000000000..649f3cb463 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchNodeTemplate.java @@ -0,0 +1,111 @@ +/* + * Copyright 2012-2017 the original author or 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 org.springframework.boot.autoconfigure.data.elasticsearch; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.function.Consumer; + +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.node.InternalSettingsPreparer; +import org.elasticsearch.node.Node; +import org.elasticsearch.node.NodeValidationException; +import org.elasticsearch.transport.Netty4Plugin; +import org.elasticsearch.transport.Transport; + +/** + * Helper class for managing an Elasticsearch {@link Node}. + * + * @author Andy Wilkinson + */ +public class ElasticsearchNodeTemplate { + + public void doWithNode(Consumer consumer) { + System.setProperty("es.set.netty.runtime.available.processors", "false"); + Node node = null; + try { + node = startNode(); + consumer.accept(new ElasticsearchNode(node)); + } + catch (Exception ex) { + throw new RuntimeException(ex); + } + finally { + if (node != null) { + try { + node.close(); + } + catch (Exception ex) { + // Continue + } + } + System.clearProperty("es.set.netty.runtime.available.processors"); + } + } + + private Node startNode() throws NodeValidationException { + Node node = new NettyTransportNode(); + node.start(); + return node; + } + + private static final class NettyTransportNode extends Node { + + private NettyTransportNode() { + super(InternalSettingsPreparer.prepareEnvironment(Settings.builder() + .put("path.home", "target/es/node").put("transport.type", "netty4") + .put("http.enabled", true).put("node.portsfile", true) + .put("http.port", 0).put("transport.tcp.port", 0).build(), null), + Arrays.asList(Netty4Plugin.class)); + new File("target/es/node/logs").mkdirs(); + } + } + + public final class ElasticsearchNode { + + private final Node node; + + private ElasticsearchNode(Node node) { + this.node = node; + } + + public int getTcpPort() { + return this.node.injector().getInstance(Transport.class).boundAddress() + .publishAddress().getPort(); + } + + public int getHttpPort() { + try { + for (String line : Files + .readAllLines(Paths.get("target/es/node/logs/http.ports"))) { + if (line.startsWith("127.0.0.1")) { + return Integer.parseInt(line.substring(line.indexOf(":") + 1)); + } + } + throw new IllegalStateException("HTTP port not found"); + } + catch (IOException ex) { + throw new IllegalStateException("Failed to read HTTP port", ex); + } + } + + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchRepositoriesAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchRepositoriesAutoConfigurationTests.java index b2b1e4b5b2..9d03a098a9 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchRepositoriesAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchRepositoriesAutoConfigurationTests.java @@ -23,6 +23,7 @@ import org.junit.Test; import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.data.alt.elasticsearch.CityElasticsearchDbRepository; +import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchNodeTemplate.ElasticsearchNode; import org.springframework.boot.autoconfigure.data.elasticsearch.city.City; import org.springframework.boot.autoconfigure.data.elasticsearch.city.CityRepository; import org.springframework.boot.autoconfigure.data.empty.EmptyDataPackage; @@ -37,6 +38,7 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link ElasticsearchRepositoriesAutoConfiguration}. * * @author Phillip Webb + * @author Andy Wilkinson */ public class ElasticsearchRepositoriesAutoConfigurationTests { @@ -49,46 +51,45 @@ public class ElasticsearchRepositoriesAutoConfigurationTests { @Test public void testDefaultRepositoryConfiguration() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - addElasticsearchProperties(this.context); - this.context.register(TestConfiguration.class, - ElasticsearchAutoConfiguration.class, - ElasticsearchRepositoriesAutoConfiguration.class, - ElasticsearchDataAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); - assertThat(this.context.getBean(CityRepository.class)).isNotNull(); - assertThat(this.context.getBean(Client.class)).isNotNull(); + new ElasticsearchNodeTemplate().doWithNode((node) -> { + load(TestConfiguration.class, node); + assertThat(this.context.getBean(CityRepository.class)).isNotNull(); + assertThat(this.context.getBean(Client.class)).isNotNull(); + }); + } @Test public void testNoRepositoryConfiguration() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - addElasticsearchProperties(this.context); - this.context.register(EmptyConfiguration.class, - ElasticsearchAutoConfiguration.class, - ElasticsearchRepositoriesAutoConfiguration.class, - ElasticsearchDataAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); - assertThat(this.context.getBean(Client.class)).isNotNull(); + new ElasticsearchNodeTemplate().doWithNode((address) -> { + load(EmptyConfiguration.class, address); + assertThat(this.context.getBean(Client.class)).isNotNull(); + }); } @Test public void doesNotTriggerDefaultRepositoryDetectionIfCustomized() { + new ElasticsearchNodeTemplate().doWithNode((address) -> { + load(CustomizedConfiguration.class, address); + assertThat(this.context.getBean(CityElasticsearchDbRepository.class)) + .isNotNull(); + }); + } + + private void load(Class config, ElasticsearchNode node) { this.context = new AnnotationConfigApplicationContext(); - addElasticsearchProperties(this.context); - this.context.register(CustomizedConfiguration.class, - ElasticsearchAutoConfiguration.class, + addElasticsearchProperties(this.context, node); + this.context.register(config, ElasticsearchAutoConfiguration.class, ElasticsearchRepositoriesAutoConfiguration.class, ElasticsearchDataAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); this.context.refresh(); - assertThat(this.context.getBean(CityElasticsearchDbRepository.class)).isNotNull(); } - private void addElasticsearchProperties(AnnotationConfigApplicationContext context) { - TestPropertyValues.of("spring.data.elasticsearch.properties.path.home:target") + private void addElasticsearchProperties(AnnotationConfigApplicationContext context, + ElasticsearchNode node) { + TestPropertyValues.of("spring.data.elasticsearch.properties.path.home:target", + "spring.data.elasticsearch.cluster-nodes:localhost:" + node.getTcpPort()) .applyTo(context); } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/jest/JestAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/jest/JestAutoConfigurationTests.java index c59cbf939c..1296709c35 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/jest/JestAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/jest/JestAutoConfigurationTests.java @@ -16,21 +16,18 @@ package org.springframework.boot.autoconfigure.elasticsearch.jest; -import java.io.File; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.Arrays; import java.util.HashMap; import java.util.Map; import com.google.gson.Gson; +import io.searchbox.action.Action; import io.searchbox.client.JestClient; +import io.searchbox.client.JestResult; import io.searchbox.client.config.HttpClientConfig; import io.searchbox.client.http.JestHttpClient; import io.searchbox.core.Index; import io.searchbox.core.Search; -import org.elasticsearch.client.node.NodeClient; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.junit.After; @@ -39,16 +36,10 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.springframework.beans.BeansException; -import org.springframework.beans.FatalBeanException; import org.springframework.beans.factory.BeanCreationException; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration; +import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchNodeTemplate; import org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration; import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -122,22 +113,31 @@ public class JestAutoConfigurationTests { @Test public void jestCanCommunicateWithElasticsearchInstance() throws IOException { - new File("target/elastic/logs").mkdirs(); - load(HttpPortConfiguration.class, - "spring.data.elasticsearch.properties.path.home:target/elastic", - "spring.data.elasticsearch.properties.http.enabled:true", - "spring.data.elasticsearch.properties.http.port:0", - "spring.data.elasticsearch.properties.node.portsfile:true"); - JestClient client = this.context.getBean(JestClient.class); - Map source = new HashMap<>(); - source.put("a", "alpha"); - source.put("b", "bravo"); - Index index = new Index.Builder(source).index("foo").type("bar").build(); - client.execute(index); - SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); - searchSourceBuilder.query(QueryBuilders.matchQuery("a", "alpha")); - assertThat(client.execute(new Search.Builder(searchSourceBuilder.toString()) - .addIndex("foo").build()).getResponseCode()).isEqualTo(200); + new ElasticsearchNodeTemplate().doWithNode((node) -> { + load("spring.elasticsearch.jest.uris=http://localhost:" + node.getHttpPort()); + JestClient client = this.context.getBean(JestClient.class); + Map source = new HashMap<>(); + source.put("a", "alpha"); + source.put("b", "bravo"); + Index index = new Index.Builder(source).index("foo").type("bar").build(); + execute(client, index); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(QueryBuilders.matchQuery("a", "alpha")); + assertThat( + execute(client, + new Search.Builder(searchSourceBuilder.toString()) + .addIndex("foo").build()).getResponseCode()) + .isEqualTo(200); + }); + } + + private JestResult execute(JestClient client, Action action) { + try { + return client.execute(action); + } + catch (IOException ex) { + throw new RuntimeException(ex); + } } private void load(String... environment) { @@ -199,53 +199,4 @@ public class JestAutoConfigurationTests { } - @Configuration - @Import(ElasticsearchAutoConfiguration.class) - static class HttpPortConfiguration { - - @Bean - public static BeanPostProcessor portPropertyConfigurer() { - return new PortPropertyConfigurer(); - } - - private static final class PortPropertyConfigurer - implements BeanPostProcessor, ApplicationContextAware { - - private ConfigurableApplicationContext applicationContext; - - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) - throws BeansException { - if (bean instanceof NodeClient) { - this.applicationContext.getBean(JestProperties.class) - .setUris(Arrays.asList("http://localhost:" + readHttpPort())); - } - return bean; - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) - throws BeansException { - this.applicationContext = (ConfigurableApplicationContext) applicationContext; - } - - private int readHttpPort() { - try { - for (String line : Files - .readAllLines(Paths.get("target/elastic/logs/http.ports"))) { - if (line.startsWith("127.0.0.1")) { - return Integer - .parseInt(line.substring(line.indexOf(":") + 1)); - } - } - throw new FatalBeanException("HTTP port not found"); - } - catch (IOException ex) { - throw new FatalBeanException("Failed to read HTTP port", ex); - } - } - } - - } - } diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 03206604bc..4a37450bb6 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -3712,19 +3712,10 @@ To take full control over the registration, define a `JestClient` bean. [[boot-features-connecting-to-elasticsearch-spring-data]] ==== Connecting to Elasticsearch using Spring Data -You can inject an auto-configured `ElasticsearchTemplate` or Elasticsearch `Client` -instance as you would any other Spring Bean. By default the instance will embed a -local in-memory server (a `Node` in Elasticsearch terms) and use the current working -directory as the home directory for the server. In this setup, the first thing to do -is to tell Elasticsearch where to store its files: - -[source,properties,indent=0] ----- - spring.data.elasticsearch.properties.path.home=/foo/bar ----- - -Alternatively, you can switch to a remote server (i.e. a `TransportClient`) by setting -`spring.data.elasticsearch.cluster-nodes` to a comma-separated '`host:port`' list. +To connect to Elasticsearch you must provide the address of one or more cluster nodes. +The address can be specified by setting the `spring.data.elasticsearch.cluster-nodes` +property to a comma-separated '`host:port`' list. With this configuration in place, an +`ElasticsearchTemplate` or `TransportClient` can be injected like any other Spring bean: [source,properties,indent=0] ---- @@ -3736,9 +3727,8 @@ Alternatively, you can switch to a remote server (i.e. a `TransportClient`) by s @Component public class MyBean { - private ElasticsearchTemplate template; + private final ElasticsearchTemplate template; - @Autowired public MyBean(ElasticsearchTemplate template) { this.template = template; } @@ -3748,8 +3738,8 @@ Alternatively, you can switch to a remote server (i.e. a `TransportClient`) by s } ---- -If you add a `@Bean` of your own of type `ElasticsearchTemplate` it will replace the -default. +If you add your own `ElasticsearchTemplate` or `TransportClient` `@Bean` it will +replace the default. diff --git a/spring-boot-samples/spring-boot-sample-data-elasticsearch/pom.xml b/spring-boot-samples/spring-boot-sample-data-elasticsearch/pom.xml index f94889361a..d94a26bbc2 100644 --- a/spring-boot-samples/spring-boot-sample-data-elasticsearch/pom.xml +++ b/spring-boot-samples/spring-boot-sample-data-elasticsearch/pom.xml @@ -27,12 +27,6 @@ org.springframework.boot spring-boot-starter-data-elasticsearch - - - net.java.dev.jna - jna - runtime - org.springframework.boot diff --git a/spring-boot-samples/spring-boot-sample-data-elasticsearch/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-data-elasticsearch/src/main/resources/application.properties index f67c555e68..b9b59fd85b 100644 --- a/spring-boot-samples/spring-boot-sample-data-elasticsearch/src/main/resources/application.properties +++ b/spring-boot-samples/spring-boot-sample-data-elasticsearch/src/main/resources/application.properties @@ -1,6 +1 @@ -# -# Home directory of the embedded Elasticsearch instance. Default to the -# current working directory. -# -spring.data.elasticsearch.properties.path.home=target/elastic -spring.data.elasticsearch.properties.transport.tcp.connect_timeout=120s +spring.data.elasticsearch.cluster-nodes=localhost:9200 diff --git a/spring-boot-samples/spring-boot-sample-data-elasticsearch/src/test/java/sample/data/elasticsearch/SampleElasticsearchApplicationTests.java b/spring-boot-samples/spring-boot-sample-data-elasticsearch/src/test/java/sample/data/elasticsearch/SampleElasticsearchApplicationTests.java index a54119e955..f7fb8fdcba 100644 --- a/spring-boot-samples/spring-boot-sample-data-elasticsearch/src/test/java/sample/data/elasticsearch/SampleElasticsearchApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-data-elasticsearch/src/test/java/sample/data/elasticsearch/SampleElasticsearchApplicationTests.java @@ -18,6 +18,8 @@ package sample.data.elasticsearch; import java.io.File; +import org.assertj.core.api.Assertions; +import org.elasticsearch.client.transport.NoNodeAvailableException; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; @@ -28,8 +30,6 @@ import org.junit.runners.model.Statement; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.test.rule.OutputCapture; -import static org.assertj.core.api.Assertions.assertThat; - /** * Tests for {@link SampleElasticsearchApplication}. * @@ -45,9 +45,28 @@ public class SampleElasticsearchApplicationTests { @Test public void testDefaultSettings() throws Exception { - new SpringApplicationBuilder(SampleElasticsearchApplication.class).run(); + try { + new SpringApplicationBuilder(SampleElasticsearchApplication.class).run(); + } + catch (Exception ex) { + if (!elasticsearchRunning(ex)) { + return; + } + throw ex; + } String output = this.outputCapture.toString(); - assertThat(output).contains("firstName='Alice', lastName='Smith'"); + Assertions.assertThat(output).contains("firstName='Alice', lastName='Smith'"); + } + + private boolean elasticsearchRunning(Exception ex) { + Throwable candidate = ex; + while (candidate != null) { + if (candidate instanceof NoNodeAvailableException) { + return false; + } + candidate = candidate.getCause(); + } + return true; } static class SkipOnWindows implements TestRule {