diff --git a/pom.xml b/pom.xml index 81567db6..b53d6b9c 100644 --- a/pom.xml +++ b/pom.xml @@ -110,7 +110,7 @@ com.fasterxml.jackson.core jackson-core - 2.14.2 + 2.16.0 jakarta.json diff --git a/src/main/java/org/phoebus/channelfinder/ElasticConfig.java b/src/main/java/org/phoebus/channelfinder/ElasticConfig.java index d2dd0c24..f20a1204 100644 --- a/src/main/java/org/phoebus/channelfinder/ElasticConfig.java +++ b/src/main/java/org/phoebus/channelfinder/ElasticConfig.java @@ -17,6 +17,7 @@ import java.io.IOException; import java.io.InputStream; import java.text.MessageFormat; +import java.util.Arrays; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; @@ -29,8 +30,16 @@ import co.elastic.clients.elasticsearch.indices.ExistsRequest; import co.elastic.clients.transport.endpoints.BooleanResponse; import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.http.Header; import org.apache.http.HttpHost; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; +import org.apache.http.message.BasicHeader; import org.elasticsearch.client.RestClient; +import org.elasticsearch.client.RestClientBuilder; import org.phoebus.channelfinder.entity.Property; import org.phoebus.channelfinder.entity.Tag; import org.springframework.beans.factory.annotation.Value; @@ -60,14 +69,19 @@ public class ElasticConfig implements ServletContextListener { private ElasticsearchClient searchClient; private ElasticsearchClient indexClient; - private static final AtomicBoolean esInitialized = new AtomicBoolean(); - @Value("${elasticsearch.cluster.name:elasticsearch}") - private String clusterName; @Value("${elasticsearch.network.host:localhost}") private String host; + @Value("${elasticsearch.host_urls:http://localhost:9200}") + private String[] httpHostUrls; @Value("${elasticsearch.http.port:9200}") private int port; + @Value("${elasticsearch.authorization.header:}") + private String authorizationHeader; + @Value("${elasticsearch.authorization.username:}") + private String username; + @Value("${elasticsearch.authorization.password:}") + private String password; @Value("${elasticsearch.create.indices:true}") private String createIndices; @@ -98,11 +112,23 @@ public int getES_QUERY_SIZE() { .addMixIn(Property.class, Property.OnlyProperty.class); private static ElasticsearchClient createClient(ElasticsearchClient currentClient, ObjectMapper objectMapper, - String host, int port, String createIndices, ElasticConfig config) { + HttpHost[] httpHosts, String createIndices, ElasticConfig config) { ElasticsearchClient client; if (currentClient == null) { // Create the low-level client - RestClient httpClient = RestClient.builder(new HttpHost(host, port)).build(); + RestClientBuilder clientBuilder = RestClient.builder(httpHosts); + // Configure authentication + if (!config.authorizationHeader.isEmpty()) { + clientBuilder.setDefaultHeaders(new Header[] {new BasicHeader("Authorization", config.authorizationHeader)}); + if (!config.username.isEmpty() || !config.password.isEmpty()) { + logger.warning("elasticsearch.authorization_header is set, ignoring elasticsearch.username and elasticsearch.password."); + } + } else if (!config.username.isEmpty() || !config.password.isEmpty()) { + final CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(config.username, config.password)); + clientBuilder.setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)); + } + RestClient httpClient = clientBuilder.build(); // Create the Java API Client with the same low level client ElasticsearchTransport transport = new RestClientTransport(httpClient, new JacksonJsonpMapper(objectMapper)); @@ -111,22 +137,40 @@ private static ElasticsearchClient createClient(ElasticsearchClient currentClien } else { client = currentClient; } - esInitialized.set(!Boolean.parseBoolean(createIndices)); - if (esInitialized.compareAndSet(false, true)) { + if (Boolean.parseBoolean(createIndices)) { config.elasticIndexValidation(client); } return client; } + + private HttpHost[] getHttpHosts() { + boolean hostIsDefault = host.equals("localhost"); + boolean hostUrlsIsDefault = httpHostUrls.length == 1 && httpHostUrls[0].equals("http://localhost:9200"); + boolean portIsDefault = (port == 9200); + if (hostUrlsIsDefault && (!hostIsDefault || !portIsDefault)) { + logger.warning("Specifying elasticsearch.network.host and elasticsearch.http.port is deprecated, please consider using elasticsearch.host_urls instead."); + return new HttpHost[] {new HttpHost(host, port)}; + } else { + if (!hostIsDefault) { + logger.warning("Only one of elasticsearch.host_urls and elasticsearch.network.host can be set, ignoring elasticsearch.network.host."); + } + if (!portIsDefault) { + logger.warning("Only one of elasticsearch.host_urls and elasticsearch.http.port can be set, ignoring elasticsearch.http.port."); + } + return Arrays.stream(httpHostUrls).map(HttpHost::create).toArray(HttpHost[]::new); + } + } + @Bean({ "searchClient" }) public ElasticsearchClient getSearchClient() { - searchClient = createClient(searchClient, objectMapper, host, port, createIndices, this); + searchClient = createClient(searchClient, objectMapper, getHttpHosts(), createIndices, this); return searchClient; } @Bean({ "indexClient" }) public ElasticsearchClient getIndexClient() { - indexClient = createClient(indexClient, objectMapper, host, port, createIndices, this); + indexClient = createClient(indexClient, objectMapper, getHttpHosts(), createIndices, this); return indexClient; } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f692013f..6b15becc 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -65,43 +65,34 @@ tag-groups=cf-tags,USER ############################## Elastic Network And HTTP ############################### -# Elasticsearch, by default, binds itself to the 0.0.0.0 address, and listens -# on port [9200-9300] for HTTP traffic and on port [9300-9400] for node-to-node -# communication. (the range means that if the port is busy, it will automatically -# try the next port). +# Comma-separated list of URLs for the Elasticsearch hosts. All hosts listed +# here must belong to the same Elasticsearch cluster. +elasticsearch.host_urls=http://localhost:9200 -# Set the bind address specifically (IPv4 or IPv6): -# -# network.bind_host: 169.254.42.56 - -# Set the address other nodes will use to communicate with this node. If not -# set, it is automatically derived. It must point to an actual IP address. -# -# network.publish_host: 192.168.0.1 - -# Set both 'bind_host' and 'publish_host': -# -elasticsearch.network.host: localhost - -# Set a custom port for the node to node communication (9300 by default): -# -#elasticsearch.transport.tcp.port: 9300 - -# Enable compression for all communication between nodes (disabled by default): -# -#transport.tcp.compress: true +# Old way of configuring the Elasticsearch host. Deprecated in favor of +# elasticsearch.host_urls. +elasticsearch.network.host=localhost -# Set a custom port to listen for HTTP traffic: -# -elasticsearch.http.port: 9200 - -# Set a custom allowed content length: -# -#http.max_content_length: 100mb +# Old way of configuring the Elasticsearch HTTP port. Deprecated in favor of +# elasticsearch.host_urls. +elasticsearch.http.port=9200 -# Disable HTTP completely: +# Value of the Authorization header that is sent with requests to the +# Elasticsearch sever. This can be used for authentication using tokens or API +# keys. # -#http.enabled: false +# For example, for token authentication, set this to ?Bearer abcd1234?, where +# ?abcd1234? is the token. For API key authentication, set this to the Base64 +# encoded version of the concatenation of the API key ID and the API key +# secret, separated by a colon. See +# https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/8.12/_other_authentication_methods.html +# for details. +elasticsearch.authorization.header = + +# Username and password for authentication with the Elasticsearch server. This +# is only used if elasticsearch.authorization.header is not set. +elasticsearch.authorization.username = +elasticsearch.authorization.password = # Elasticsearch index names and types used by channelfinder, ensure that any changes here should be replicated in the mapping_definitions.sh elasticsearch.tag.index = cf_tags @@ -112,7 +103,7 @@ elasticsearch.channel.index = channelfinder elasticsearch.query.size = 10000 # Create the Channel Finder indices if they do not exist -elasticsearch.create.indices: true +elasticsearch.create.indices=true ############################## Service Info ############################### # ChannelFinder version as defined in the pom file diff --git a/src/test/resources/application_test.properties b/src/test/resources/application_test.properties index 78b60d53..2297af05 100644 --- a/src/test/resources/application_test.properties +++ b/src/test/resources/application_test.properties @@ -61,43 +61,9 @@ tag-groups=cf-tags ############################## Elastic Network And HTTP ############################### -# Elasticsearch, by default, binds itself to the 0.0.0.0 address, and listens -# on port [9200-9300] for HTTP traffic and on port [9300-9400] for node-to-node -# communication. (the range means that if the port is busy, it will automatically -# try the next port). - -# Set the bind address specifically (IPv4 or IPv6): -# -# network.bind_host: 169.254.42.56 - -# Set the address other nodes will use to communicate with this node. If not -# set, it is automatically derived. It must point to an actual IP address. -# -# network.publish_host: 192.168.0.1 - -# Set both 'bind_host' and 'publish_host': -# -elasticsearch.network.host: localhost - -# Set a custom port for the node to node communication (9300 by default): -# -#elasticsearch.transport.tcp.port: 9300 - -# Enable compression for all communication between nodes (disabled by default): -# -#transport.tcp.compress: true - -# Set a custom port to listen for HTTP traffic: -# -elasticsearch.http.port: 9200 - -# Set a custom allowed content length: -# -#http.max_content_length: 100mb - -# Disable HTTP completely: -# -#http.enabled: false +# Comma-separated list of URLs for the Elasticsearch hosts. All hosts listed +# here must belong to the same Elasticsearch cluster. +elasticsearch.host_urls=http://localhost:9200 # Elasticsearch index names and types used by channelfinder, ensure that any changes here should be replicated in the mapping_definitions.sh elasticsearch.tag.index = test_${random.int[1,1000]}_cf_tags @@ -108,7 +74,7 @@ elasticsearch.channel.index = test_${random.int[1,1000]}_channelfinder elasticsearch.query.size = 10000 # Create the Channel Finder indices if they do not exist -elasticsearch.create.indices: true +elasticsearch.create.indices = true ############################## Service Info ############################### # ChannelFinder version as defined in the pom file