diff --git a/core/src/main/java/org/elasticsearch/common/network/NetworkService.java b/core/src/main/java/org/elasticsearch/common/network/NetworkService.java
index 8eff70e7bd8..cd46d1416f4 100644
--- a/core/src/main/java/org/elasticsearch/common/network/NetworkService.java
+++ b/core/src/main/java/org/elasticsearch/common/network/NetworkService.java
@@ -27,7 +27,6 @@ import org.elasticsearch.common.unit.TimeValue;
import java.io.IOException;
import java.net.InetAddress;
-import java.net.UnknownHostException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
@@ -73,7 +72,7 @@ public class NetworkService extends AbstractComponent {
/**
* Resolves a custom value handling, return null if can't handle it.
*/
- InetAddress[] resolveIfPossible(String value);
+ InetAddress[] resolveIfPossible(String value) throws IOException;
}
private final List customNameResolvers = new CopyOnWriteArrayList<>();
@@ -162,7 +161,7 @@ public class NetworkService extends AbstractComponent {
return address;
}
- private InetAddress[] resolveInetAddress(String host) throws UnknownHostException, IOException {
+ private InetAddress[] resolveInetAddress(String host) throws IOException {
if ((host.startsWith("#") && host.endsWith("#")) || (host.startsWith("_") && host.endsWith("_"))) {
host = host.substring(1, host.length() - 1);
// allow custom resolvers to have special names
diff --git a/docs/plugins/cloud-gce.asciidoc b/docs/plugins/cloud-gce.asciidoc
index 3f5b9da45d0..5cb241ed505 100644
--- a/docs/plugins/cloud-gce.asciidoc
+++ b/docs/plugins/cloud-gce.asciidoc
@@ -46,6 +46,44 @@ discovery:
type: gce
--------------------------------------------------
+
+[IMPORTANT]
+.Binding the network host
+==============================================
+
+It's important to define `network.host` as by default it's bound to `localhost`.
+
+You can use {ref}/modules-network.html[core network host settings] or
+<>:
+
+==============================================
+
+[[discovery-gce-network-host]]
+==== GCE Network Host
+
+When the `cloud-gce` plugin is installed, the following are also allowed
+as valid network host settings:
+
+[cols="<,<",options="header",]
+|==================================================================
+|GCE Host Value |Description
+|`_gce:privateIp:X_` |The private IP address of the machine for a given network interface.
+|`_gce:hostname_` |The hostname of the machine.
+|`_gce_` |Same as `_gce:privateIp:0_` (recommended).
+|==================================================================
+
+Examples:
+
+[source,yaml]
+--------------------------------------------------
+# get the IP address from network interface 1
+network.host: _gce:privateIp:1_
+# shortcut for _gce:privateIp:0_
+network.host: _gce_
+# Using GCE internal hostname (recommended)
+network.host: _gce:hostname_
+--------------------------------------------------
+
[[cloud-gce-usage-discovery-short]]
===== How to start (short story)
diff --git a/docs/reference/modules/network.asciidoc b/docs/reference/modules/network.asciidoc
index 70b4d8c97f9..2e48438b33a 100644
--- a/docs/reference/modules/network.asciidoc
+++ b/docs/reference/modules/network.asciidoc
@@ -54,6 +54,10 @@ provided network interface. For example `_en0:ipv6_`.
When the `discovery-ec2` plugin is installed, you can use
{plugins}/discovery-ec2-discovery.html#discovery-ec2-network-host[ec2 specific host settings].
+When the `cloud-gce` plugin is installed, you can use
+{plugins}/discovery-gce-network-host.html[gce specific host settings].
+
+
[float]
[[tcp-settings]]
=== TCP Settings
diff --git a/plugins/cloud-gce/src/main/java/org/elasticsearch/cloud/gce/GceComputeService.java b/plugins/cloud-gce/src/main/java/org/elasticsearch/cloud/gce/GceComputeService.java
index 6ba857db17a..ccb1eda3cc5 100644
--- a/plugins/cloud-gce/src/main/java/org/elasticsearch/cloud/gce/GceComputeService.java
+++ b/plugins/cloud-gce/src/main/java/org/elasticsearch/cloud/gce/GceComputeService.java
@@ -22,13 +22,14 @@ package org.elasticsearch.cloud.gce;
import com.google.api.services.compute.model.Instance;
import org.elasticsearch.common.component.LifecycleComponent;
+import java.io.IOException;
import java.util.Collection;
/**
*
*/
public interface GceComputeService extends LifecycleComponent {
- static final public class Fields {
+ final class Fields {
public static final String PROJECT = "cloud.gce.project_id";
public static final String ZONE = "cloud.gce.zone";
public static final String REFRESH = "cloud.gce.refresh_interval";
@@ -36,5 +37,24 @@ public interface GceComputeService extends LifecycleComponent
public static final String VERSION = "Elasticsearch/GceCloud/1.0";
}
- public Collection instances();
+ /**
+ * Return a collection of running instances within the same GCE project
+ * @return a collection of running instances within the same GCE project
+ */
+ Collection instances();
+
+ /**
+ * Gets metadata on the current running machine (call to
+ * http://metadata.google.internal/computeMetadata/v1/instance/xxx).
+ * For example, you can retrieve network information by replacing xxx with:
+ *
+ * - `hostname` when we need to resolve the host name
+ * - `network-interfaces/0/ip` when we need to resolve private IP
+ *
+ * @see org.elasticsearch.cloud.gce.network.GceNameResolver for bindings
+ * @param metadataPath path to metadata information
+ * @return extracted information (for example a hostname or an IP address)
+ * @throws IOException in case metadata URL is not accessible
+ */
+ String metadata(String metadataPath) throws IOException;
}
diff --git a/plugins/cloud-gce/src/main/java/org/elasticsearch/cloud/gce/GceComputeServiceImpl.java b/plugins/cloud-gce/src/main/java/org/elasticsearch/cloud/gce/GceComputeServiceImpl.java
index 2a9bf6d62d1..b205322c38c 100644
--- a/plugins/cloud-gce/src/main/java/org/elasticsearch/cloud/gce/GceComputeServiceImpl.java
+++ b/plugins/cloud-gce/src/main/java/org/elasticsearch/cloud/gce/GceComputeServiceImpl.java
@@ -21,6 +21,9 @@ package org.elasticsearch.cloud.gce;
import com.google.api.client.googleapis.compute.ComputeCredential;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
+import com.google.api.client.http.GenericUrl;
+import com.google.api.client.http.HttpHeaders;
+import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
@@ -30,12 +33,15 @@ import com.google.api.services.compute.model.InstanceList;
import org.elasticsearch.SpecialPermission;
import org.elasticsearch.ElasticsearchException;
+import org.elasticsearch.cloud.gce.network.GceNameResolver;
import org.elasticsearch.common.component.AbstractLifecycleComponent;
import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.network.NetworkService;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import java.io.IOException;
+import java.net.URL;
import java.security.AccessController;
import java.security.GeneralSecurityException;
import java.security.PrivilegedActionException;
@@ -54,7 +60,8 @@ public class GceComputeServiceImpl extends AbstractLifecycleComponent instances() {
@@ -95,6 +102,37 @@ public class GceComputeServiceImpl extends AbstractLifecycleComponent() {
+ @Override
+ public HttpHeaders run() throws IOException {
+ return new HttpHeaders();
+ }
+ });
+
+ // This is needed to query meta data: https://cloud.google.com/compute/docs/metadata
+ headers.put("Metadata-Flavor", "Google");
+ HttpResponse response;
+ response = getGceHttpTransport().createRequestFactory()
+ .buildGetRequest(new GenericUrl(url))
+ .setHeaders(headers)
+ .execute();
+ String metadata = response.parseAsString();
+ logger.debug("metadata found [{}]", metadata);
+ return metadata;
+ } catch (Exception e) {
+ throw new IOException("failed to fetch metadata from [" + urlMetadataNetwork + "]", e);
+ }
+ }
+
private Compute client;
private TimeValue refreshInterval = null;
private long lastRefresh;
@@ -106,11 +144,12 @@ public class GceComputeServiceImpl extends AbstractLifecycleComponentResolves certain GCE related 'meta' hostnames into an actual hostname
+ * obtained from gce meta-data.
+ * Valid config values for {@link GceAddressResolverType}s are -
+ *
+ * - _gce_ - maps to privateIp
+ * - _gce:privateIp_
+ * - _gce:hostname_
+ *
+ */
+public class GceNameResolver extends AbstractComponent implements CustomNameResolver {
+
+ private final GceComputeService gceComputeService;
+
+ /**
+ * enum that can be added to over time with more meta-data types
+ */
+ private enum GceAddressResolverType {
+
+ /**
+ * Using the hostname
+ */
+ PRIVATE_DNS("gce:hostname", "hostname"),
+ /**
+ * Can be gce:privateIp, gce:privateIp:X where X is the network interface
+ */
+ PRIVATE_IP("gce:privateIp", "network-interfaces/{{network}}/ip"),
+ /**
+ * same as "gce:privateIp" or "gce:privateIp:0"
+ */
+ GCE("gce", PRIVATE_IP.gceName);
+
+ final String configName;
+ final String gceName;
+
+ GceAddressResolverType(String configName, String gceName) {
+ this.configName = configName;
+ this.gceName = gceName;
+ }
+ }
+
+ /**
+ * Construct a {@link CustomNameResolver}.
+ */
+ public GceNameResolver(Settings settings, GceComputeService gceComputeService) {
+ super(settings);
+ this.gceComputeService = gceComputeService;
+ }
+
+ /**
+ * @param value the gce hostname type to discover.
+ * @return the appropriate host resolved from gce meta-data.
+ * @see CustomNameResolver#resolveIfPossible(String)
+ */
+ private InetAddress[] resolve(String value) throws IOException {
+ String gceMetadataPath;
+ if (value.equals(GceAddressResolverType.GCE.configName)) {
+ // We replace network placeholder with default network interface value: 0
+ gceMetadataPath = Strings.replace(GceAddressResolverType.GCE.gceName, "{{network}}", "0");
+ } else if (value.equals(GceAddressResolverType.PRIVATE_DNS.configName)) {
+ gceMetadataPath = GceAddressResolverType.PRIVATE_DNS.gceName;
+ } else if (value.startsWith(GceAddressResolverType.PRIVATE_IP.configName)) {
+ // We extract the network interface from gce:privateIp:XX
+ String network = "0";
+ String[] privateIpConfig = Strings.splitStringToArray(value, ':');
+ if (privateIpConfig != null && privateIpConfig.length == 3) {
+ network = privateIpConfig[2];
+ }
+
+ // We replace network placeholder with network interface value
+ gceMetadataPath = Strings.replace(GceAddressResolverType.PRIVATE_IP.gceName, "{{network}}", network);
+ } else {
+ throw new IllegalArgumentException("[" + value + "] is not one of the supported GCE network.host setting. " +
+ "Expecting _gce_, _gce:privateIp:X_, _gce:hostname_");
+ }
+
+ try {
+ String metadataResult = gceComputeService.metadata(gceMetadataPath);
+ if (metadataResult == null || metadataResult.length() == 0) {
+ throw new IOException("no gce metadata returned from [" + gceMetadataPath + "] for [" + value + "]");
+ }
+ // only one address: because we explicitly ask for only one via the GceHostnameType
+ return new InetAddress[] { InetAddress.getByName(metadataResult) };
+ } catch (IOException e) {
+ throw new IOException("IOException caught when fetching InetAddress from [" + gceMetadataPath + "]", e);
+ }
+ }
+
+ @Override
+ public InetAddress[] resolveDefault() {
+ return null; // using this, one has to explicitly specify _gce_ in network setting
+ }
+
+ @Override
+ public InetAddress[] resolveIfPossible(String value) throws IOException {
+ // We only try to resolve network.host setting when it starts with _gce
+ if (value.startsWith("gce")) {
+ return resolve(value);
+ }
+ return null;
+ }
+}
diff --git a/plugins/cloud-gce/src/test/java/org/elasticsearch/discovery/gce/GceComputeServiceMock.java b/plugins/cloud-gce/src/test/java/org/elasticsearch/discovery/gce/GceComputeServiceMock.java
index 1892297efe8..dcbd53fa95e 100644
--- a/plugins/cloud-gce/src/test/java/org/elasticsearch/discovery/gce/GceComputeServiceMock.java
+++ b/plugins/cloud-gce/src/test/java/org/elasticsearch/discovery/gce/GceComputeServiceMock.java
@@ -29,6 +29,7 @@ import com.google.api.client.testing.http.MockLowLevelHttpResponse;
import org.elasticsearch.cloud.gce.GceComputeServiceImpl;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.Streams;
+import org.elasticsearch.common.network.NetworkService;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.Callback;
@@ -44,8 +45,8 @@ public class GceComputeServiceMock extends GceComputeServiceImpl {
protected HttpTransport mockHttpTransport;
- public GceComputeServiceMock(Settings settings) {
- super(settings);
+ public GceComputeServiceMock(Settings settings, NetworkService networkService) {
+ super(settings, networkService);
this.mockHttpTransport = configureMock();
}
@@ -55,7 +56,7 @@ public class GceComputeServiceMock extends GceComputeServiceImpl {
}
protected HttpTransport configureMock() {
- HttpTransport transport = new MockHttpTransport() {
+ return new MockHttpTransport() {
@Override
public LowLevelHttpRequest buildRequest(String method, final String url) throws IOException {
return new MockLowLevelHttpRequest() {
@@ -64,8 +65,8 @@ public class GceComputeServiceMock extends GceComputeServiceImpl {
MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
response.setStatusCode(200);
response.setContentType(Json.MEDIA_TYPE);
- if (url.equals(TOKEN_SERVER_ENCODED_URL)) {
- logger.info("--> Simulate GCE Auth response for [{}]", url);
+ if (url.startsWith(GCE_METADATA_URL)) {
+ logger.info("--> Simulate GCE Auth/Metadata response for [{}]", url);
response.setContent(readGoogleInternalJsonResponse(url));
} else {
logger.info("--> Simulate GCE API response for [{}]", url);
@@ -77,8 +78,6 @@ public class GceComputeServiceMock extends GceComputeServiceImpl {
};
}
};
-
- return transport;
}
private String readGoogleInternalJsonResponse(String url) throws IOException {
@@ -91,23 +90,24 @@ public class GceComputeServiceMock extends GceComputeServiceImpl {
private String readJsonResponse(String url, String urlRoot) throws IOException {
// We extract from the url the mock file path we want to use
- String mockFileName = Strings.replace(url, urlRoot, "") + ".json";
+ String mockFileName = Strings.replace(url, urlRoot, "");
logger.debug("--> read mock file from [{}]", mockFileName);
URL resource = GceComputeServiceMock.class.getResource(mockFileName);
+ if (resource == null) {
+ throw new IOException("can't read [" + url + "] in src/test/resources/org/elasticsearch/discovery/gce");
+ }
try (InputStream is = resource.openStream()) {
final StringBuilder sb = new StringBuilder();
Streams.readAllLines(is, new Callback() {
@Override
public void handle(String s) {
- sb.append(s).append("\n");
+ sb.append(s);
}
});
String response = sb.toString();
logger.trace("{}", response);
return response;
- } catch (IOException e) {
- throw e;
}
}
}
diff --git a/plugins/cloud-gce/src/test/java/org/elasticsearch/discovery/gce/GceDiscoveryTests.java b/plugins/cloud-gce/src/test/java/org/elasticsearch/discovery/gce/GceDiscoveryTests.java
index b18cca1d84e..450ff72ca47 100644
--- a/plugins/cloud-gce/src/test/java/org/elasticsearch/discovery/gce/GceDiscoveryTests.java
+++ b/plugins/cloud-gce/src/test/java/org/elasticsearch/discovery/gce/GceDiscoveryTests.java
@@ -63,6 +63,7 @@ public class GceDiscoveryTests extends ESTestCase {
protected static ThreadPool threadPool;
protected MockTransportService transportService;
+ protected NetworkService networkService;
protected GceComputeService mock;
protected String projectName;
@@ -91,6 +92,11 @@ public class GceDiscoveryTests extends ESTestCase {
new LocalTransport(Settings.EMPTY, threadPool, Version.CURRENT, new NamedWriteableRegistry()), threadPool);
}
+ @Before
+ public void createNetworkService() {
+ networkService = new NetworkService(Settings.EMPTY);
+ }
+
@After
public void stopGceComputeService() {
if (mock != null) {
@@ -113,7 +119,7 @@ public class GceDiscoveryTests extends ESTestCase {
.put(GceComputeService.Fields.PROJECT, projectName)
.put(GceComputeService.Fields.ZONE, "europe-west1-b")
.build();
- mock = new GceComputeServiceMock(nodeSettings);
+ mock = new GceComputeServiceMock(nodeSettings, networkService);
List discoveryNodes = buildDynamicNodes(mock, nodeSettings);
assertThat(discoveryNodes, hasSize(2));
}
@@ -125,7 +131,7 @@ public class GceDiscoveryTests extends ESTestCase {
.put(GceComputeService.Fields.ZONE, "europe-west1-b")
.putArray(GceComputeService.Fields.TAGS, "elasticsearch")
.build();
- mock = new GceComputeServiceMock(nodeSettings);
+ mock = new GceComputeServiceMock(nodeSettings, networkService);
List discoveryNodes = buildDynamicNodes(mock, nodeSettings);
assertThat(discoveryNodes, hasSize(1));
assertThat(discoveryNodes.get(0).getId(), is("#cloud-test2-0"));
@@ -138,7 +144,7 @@ public class GceDiscoveryTests extends ESTestCase {
.put(GceComputeService.Fields.ZONE, "europe-west1-b")
.putArray(GceComputeService.Fields.TAGS, "elasticsearch", "dev")
.build();
- mock = new GceComputeServiceMock(nodeSettings);
+ mock = new GceComputeServiceMock(nodeSettings, networkService);
List discoveryNodes = buildDynamicNodes(mock, nodeSettings);
assertThat(discoveryNodes, hasSize(1));
assertThat(discoveryNodes.get(0).getId(), is("#cloud-test2-0"));
@@ -150,7 +156,7 @@ public class GceDiscoveryTests extends ESTestCase {
.put(GceComputeService.Fields.PROJECT, projectName)
.put(GceComputeService.Fields.ZONE, "europe-west1-b")
.build();
- mock = new GceComputeServiceMock(nodeSettings);
+ mock = new GceComputeServiceMock(nodeSettings, networkService);
List discoveryNodes = buildDynamicNodes(mock, nodeSettings);
assertThat(discoveryNodes, hasSize(2));
}
@@ -162,7 +168,7 @@ public class GceDiscoveryTests extends ESTestCase {
.put(GceComputeService.Fields.ZONE, "europe-west1-b")
.putArray(GceComputeService.Fields.TAGS, "elasticsearch")
.build();
- mock = new GceComputeServiceMock(nodeSettings);
+ mock = new GceComputeServiceMock(nodeSettings, networkService);
List discoveryNodes = buildDynamicNodes(mock, nodeSettings);
assertThat(discoveryNodes, hasSize(2));
}
@@ -174,7 +180,7 @@ public class GceDiscoveryTests extends ESTestCase {
.put(GceComputeService.Fields.ZONE, "europe-west1-b")
.putArray(GceComputeService.Fields.TAGS, "elasticsearch", "dev")
.build();
- mock = new GceComputeServiceMock(nodeSettings);
+ mock = new GceComputeServiceMock(nodeSettings, networkService);
List discoveryNodes = buildDynamicNodes(mock, nodeSettings);
assertThat(discoveryNodes, hasSize(2));
}
@@ -185,7 +191,7 @@ public class GceDiscoveryTests extends ESTestCase {
.put(GceComputeService.Fields.PROJECT, projectName)
.putArray(GceComputeService.Fields.ZONE, "us-central1-a", "europe-west1-b")
.build();
- mock = new GceComputeServiceMock(nodeSettings);
+ mock = new GceComputeServiceMock(nodeSettings, networkService);
List discoveryNodes = buildDynamicNodes(mock, nodeSettings);
assertThat(discoveryNodes, hasSize(2));
}
@@ -196,7 +202,7 @@ public class GceDiscoveryTests extends ESTestCase {
.put(GceComputeService.Fields.PROJECT, projectName)
.putArray(GceComputeService.Fields.ZONE, "us-central1-a", "europe-west1-b")
.build();
- mock = new GceComputeServiceMock(nodeSettings);
+ mock = new GceComputeServiceMock(nodeSettings, networkService);
List discoveryNodes = buildDynamicNodes(mock, nodeSettings);
assertThat(discoveryNodes, hasSize(2));
}
@@ -210,7 +216,7 @@ public class GceDiscoveryTests extends ESTestCase {
.put(GceComputeService.Fields.PROJECT, projectName)
.putArray(GceComputeService.Fields.ZONE, "us-central1-a", "us-central1-b")
.build();
- mock = new GceComputeServiceMock(nodeSettings);
+ mock = new GceComputeServiceMock(nodeSettings, networkService);
List discoveryNodes = buildDynamicNodes(mock, nodeSettings);
assertThat(discoveryNodes, hasSize(0));
}
diff --git a/plugins/cloud-gce/src/test/java/org/elasticsearch/discovery/gce/GceNetworkTests.java b/plugins/cloud-gce/src/test/java/org/elasticsearch/discovery/gce/GceNetworkTests.java
new file mode 100644
index 00000000000..7550cdce7e4
--- /dev/null
+++ b/plugins/cloud-gce/src/test/java/org/elasticsearch/discovery/gce/GceNetworkTests.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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.elasticsearch.discovery.gce;
+
+import org.elasticsearch.cloud.gce.network.GceNameResolver;
+import org.elasticsearch.common.network.NetworkService;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.test.ESTestCase;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.net.InetAddress;
+
+import static org.hamcrest.Matchers.arrayContaining;
+import static org.hamcrest.Matchers.containsString;
+
+/**
+ * Test for GCE network.host settings.
+ * Related to https://github.com/elastic/elasticsearch/issues/13605
+ */
+public class GceNetworkTests extends ESTestCase {
+
+ /**
+ * Test for network.host: _gce_
+ */
+ @Test
+ public void networkHostGceDefault() throws IOException {
+ resolveGce("_gce_", InetAddress.getByName("10.240.0.2"));
+ }
+
+ /**
+ * Test for network.host: _gce:privateIp_
+ */
+ @Test
+ public void networkHostPrivateIp() throws IOException {
+ resolveGce("_gce:privateIp_", InetAddress.getByName("10.240.0.2"));
+ }
+
+ /**
+ * Test for network.host: _gce:hostname_
+ */
+ @Test
+ public void networkHostPrivateDns() throws IOException {
+ resolveGce("_gce:hostname_", InetAddress.getByName("localhost"));
+ }
+
+ /**
+ * Test for network.host: _gce:doesnotexist_
+ * This should raise an IllegalArgumentException as this setting does not exist
+ */
+ @Test
+ public void networkHostWrongSetting() throws IOException {
+ resolveGce("_gce:doesnotexist_", (InetAddress) null);
+ }
+
+ /**
+ * Test with multiple network interfaces:
+ * network.host: _gce:privateIp:0_
+ * network.host: _gce:privateIp:1_
+ */
+ @Test
+ public void networkHostPrivateIpInterface() throws IOException {
+ resolveGce("_gce:privateIp:0_", InetAddress.getByName("10.240.0.2"));
+ resolveGce("_gce:privateIp:1_", InetAddress.getByName("10.150.0.1"));
+ }
+
+ /**
+ * Test that we don't have any regression with network host core settings such as
+ * network.host: _local_
+ */
+ @Test
+ public void networkHostCoreLocal() throws IOException {
+ resolveGce("_local_", new NetworkService(Settings.EMPTY).resolveBindHostAddress(NetworkService.DEFAULT_NETWORK_HOST));
+ }
+
+ /**
+ * Utility test method to test different settings
+ * @param gceNetworkSetting tested network.host property
+ * @param expected expected InetAddress, null if we expect an exception
+ * @throws IOException Well... If something goes wrong :)
+ */
+ private void resolveGce(String gceNetworkSetting, InetAddress expected) throws IOException {
+ resolveGce(gceNetworkSetting, expected == null ? null : new InetAddress [] { expected });
+ }
+
+ /**
+ * Utility test method to test different settings
+ * @param gceNetworkSetting tested network.host property
+ * @param expected expected InetAddress, null if we expect an exception
+ * @throws IOException Well... If something goes wrong :)
+ */
+ private void resolveGce(String gceNetworkSetting, InetAddress[] expected) throws IOException {
+ Settings nodeSettings = Settings.builder()
+ .put("network.host", gceNetworkSetting)
+ .build();
+
+ NetworkService networkService = new NetworkService(nodeSettings);
+ GceComputeServiceMock mock = new GceComputeServiceMock(nodeSettings, networkService);
+ networkService.addCustomNameResolver(new GceNameResolver(nodeSettings, mock));
+ try {
+ InetAddress[] addresses = networkService.resolveBindHostAddress(null);
+ if (expected == null) {
+ fail("We should get a IllegalArgumentException when setting network.host: _gce:doesnotexist_");
+ }
+ assertThat(addresses, arrayContaining(expected));
+ } catch (IllegalArgumentException e) {
+ if (expected != null) {
+ // We were expecting something and not an exception
+ throw e;
+ }
+ // We check that we get the expected exception
+ assertThat(e.getMessage(), containsString("is not one of the supported GCE network.host setting"));
+ }
+ }
+}
diff --git a/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/multiplezonesandtwonodesindifferentzones/zones/europe-west1-b/instances.json b/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/multiplezonesandtwonodesindifferentzones/zones/europe-west1-b/instances
similarity index 100%
rename from plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/multiplezonesandtwonodesindifferentzones/zones/europe-west1-b/instances.json
rename to plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/multiplezonesandtwonodesindifferentzones/zones/europe-west1-b/instances
diff --git a/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/multiplezonesandtwonodesindifferentzones/zones/us-central1-a/instances.json b/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/multiplezonesandtwonodesindifferentzones/zones/us-central1-a/instances
similarity index 100%
rename from plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/multiplezonesandtwonodesindifferentzones/zones/us-central1-a/instances.json
rename to plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/multiplezonesandtwonodesindifferentzones/zones/us-central1-a/instances
diff --git a/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/multiplezonesandtwonodesinsamezone/zones/europe-west1-b/instances.json b/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/multiplezonesandtwonodesinsamezone/zones/europe-west1-b/instances
similarity index 100%
rename from plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/multiplezonesandtwonodesinsamezone/zones/europe-west1-b/instances.json
rename to plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/multiplezonesandtwonodesinsamezone/zones/europe-west1-b/instances
diff --git a/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/multiplezonesandtwonodesinsamezone/zones/us-central1-a/instances.json b/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/multiplezonesandtwonodesinsamezone/zones/us-central1-a/instances
similarity index 100%
rename from plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/multiplezonesandtwonodesinsamezone/zones/us-central1-a/instances.json
rename to plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/multiplezonesandtwonodesinsamezone/zones/us-central1-a/instances
diff --git a/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/nodeswithdifferenttagsandnotagset/zones/europe-west1-b/instances.json b/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/nodeswithdifferenttagsandnotagset/zones/europe-west1-b/instances
similarity index 100%
rename from plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/nodeswithdifferenttagsandnotagset/zones/europe-west1-b/instances.json
rename to plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/nodeswithdifferenttagsandnotagset/zones/europe-west1-b/instances
diff --git a/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/nodeswithdifferenttagsandonetagset/zones/europe-west1-b/instances.json b/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/nodeswithdifferenttagsandonetagset/zones/europe-west1-b/instances
similarity index 100%
rename from plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/nodeswithdifferenttagsandonetagset/zones/europe-west1-b/instances.json
rename to plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/nodeswithdifferenttagsandonetagset/zones/europe-west1-b/instances
diff --git a/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/nodeswithdifferenttagsandtwotagset/zones/europe-west1-b/instances.json b/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/nodeswithdifferenttagsandtwotagset/zones/europe-west1-b/instances
similarity index 100%
rename from plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/nodeswithdifferenttagsandtwotagset/zones/europe-west1-b/instances.json
rename to plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/nodeswithdifferenttagsandtwotagset/zones/europe-west1-b/instances
diff --git a/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/nodeswithsametagsandnotagset/zones/europe-west1-b/instances.json b/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/nodeswithsametagsandnotagset/zones/europe-west1-b/instances
similarity index 100%
rename from plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/nodeswithsametagsandnotagset/zones/europe-west1-b/instances.json
rename to plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/nodeswithsametagsandnotagset/zones/europe-west1-b/instances
diff --git a/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/nodeswithsametagsandonetagset/zones/europe-west1-b/instances.json b/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/nodeswithsametagsandonetagset/zones/europe-west1-b/instances
similarity index 100%
rename from plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/nodeswithsametagsandonetagset/zones/europe-west1-b/instances.json
rename to plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/nodeswithsametagsandonetagset/zones/europe-west1-b/instances
diff --git a/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/nodeswithsametagsandtwotagsset/zones/europe-west1-b/instances.json b/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/nodeswithsametagsandtwotagsset/zones/europe-west1-b/instances
similarity index 100%
rename from plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/nodeswithsametagsandtwotagsset/zones/europe-west1-b/instances.json
rename to plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/nodeswithsametagsandtwotagsset/zones/europe-west1-b/instances
diff --git a/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/zeronode43/zones/us-central1-a/instances.json b/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/zeronode43/zones/us-central1-a/instances
similarity index 100%
rename from plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/zeronode43/zones/us-central1-a/instances.json
rename to plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/zeronode43/zones/us-central1-a/instances
diff --git a/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/zeronode43/zones/us-central1-b/instances.json b/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/zeronode43/zones/us-central1-b/instances
similarity index 100%
rename from plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/zeronode43/zones/us-central1-b/instances.json
rename to plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/compute/v1/projects/zeronode43/zones/us-central1-b/instances
diff --git a/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/computeMetadata/v1/instance/hostname b/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/computeMetadata/v1/instance/hostname
new file mode 100644
index 00000000000..2fbb50c4a8d
--- /dev/null
+++ b/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/computeMetadata/v1/instance/hostname
@@ -0,0 +1 @@
+localhost
diff --git a/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/computeMetadata/v1/instance/network-interfaces/0/ip b/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/computeMetadata/v1/instance/network-interfaces/0/ip
new file mode 100644
index 00000000000..1ac79d6027d
--- /dev/null
+++ b/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/computeMetadata/v1/instance/network-interfaces/0/ip
@@ -0,0 +1 @@
+10.240.0.2
diff --git a/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/computeMetadata/v1/instance/network-interfaces/1/ip b/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/computeMetadata/v1/instance/network-interfaces/1/ip
new file mode 100644
index 00000000000..e3bb0f875b6
--- /dev/null
+++ b/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/computeMetadata/v1/instance/network-interfaces/1/ip
@@ -0,0 +1 @@
+10.150.0.1
diff --git a/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/computeMetadata/v1/instance/service-accounts/default/token.json b/plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/computeMetadata/v1/instance/service-accounts/default/token
similarity index 100%
rename from plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/computeMetadata/v1/instance/service-accounts/default/token.json
rename to plugins/cloud-gce/src/test/resources/org/elasticsearch/discovery/gce/computeMetadata/v1/instance/service-accounts/default/token
diff --git a/plugins/discovery-ec2/src/main/java/org/elasticsearch/cloud/aws/network/Ec2NameResolver.java b/plugins/discovery-ec2/src/main/java/org/elasticsearch/cloud/aws/network/Ec2NameResolver.java
index f04d3ecc937..3788f8237dc 100755
--- a/plugins/discovery-ec2/src/main/java/org/elasticsearch/cloud/aws/network/Ec2NameResolver.java
+++ b/plugins/discovery-ec2/src/main/java/org/elasticsearch/cloud/aws/network/Ec2NameResolver.java
@@ -91,31 +91,25 @@ public class Ec2NameResolver extends AbstractComponent implements CustomNameReso
* @return the appropriate host resolved from ec2 meta-data, or null if it cannot be obtained.
* @see CustomNameResolver#resolveIfPossible(String)
*/
- public InetAddress[] resolve(Ec2HostnameType type, boolean warnOnFailure) {
- URLConnection urlConnection = null;
+ public InetAddress[] resolve(Ec2HostnameType type) throws IOException {
InputStream in = null;
+ String metadataUrl = AwsEc2ServiceImpl.EC2_METADATA_URL + type.ec2Name;
try {
- URL url = new URL(AwsEc2ServiceImpl.EC2_METADATA_URL + type.ec2Name);
+ URL url = new URL(metadataUrl);
logger.debug("obtaining ec2 hostname from ec2 meta-data url {}", url);
- urlConnection = url.openConnection();
+ URLConnection urlConnection = url.openConnection();
urlConnection.setConnectTimeout(2000);
in = urlConnection.getInputStream();
BufferedReader urlReader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
String metadataResult = urlReader.readLine();
if (metadataResult == null || metadataResult.length() == 0) {
- logger.error("no ec2 metadata returned from {}", url);
- return null;
+ throw new IOException("no gce metadata returned from [" + url + "] for [" + type.configName + "]");
}
// only one address: because we explicitly ask for only one via the Ec2HostnameType
return new InetAddress[] { InetAddress.getByName(metadataResult) };
} catch (IOException e) {
- if (warnOnFailure) {
- logger.warn("failed to get metadata for [" + type.configName + "]", e);
- } else {
- logger.debug("failed to get metadata for [" + type.configName + "]", e);
- }
- return null;
+ throw new IOException("IOException caught when fetching InetAddress from [" + metadataUrl + "]", e);
} finally {
IOUtils.closeWhileHandlingException(in);
}
@@ -128,10 +122,10 @@ public class Ec2NameResolver extends AbstractComponent implements CustomNameReso
}
@Override
- public InetAddress[] resolveIfPossible(String value) {
+ public InetAddress[] resolveIfPossible(String value) throws IOException {
for (Ec2HostnameType type : Ec2HostnameType.values()) {
if (type.configName.equals(value)) {
- return resolve(type, true);
+ return resolve(type);
}
}
return null;
diff --git a/plugins/discovery-ec2/src/test/java/org/elasticsearch/discovery/ec2/Ec2NetworkTests.java b/plugins/discovery-ec2/src/test/java/org/elasticsearch/discovery/ec2/Ec2NetworkTests.java
new file mode 100644
index 00000000000..8aa9ca56a3f
--- /dev/null
+++ b/plugins/discovery-ec2/src/test/java/org/elasticsearch/discovery/ec2/Ec2NetworkTests.java
@@ -0,0 +1,187 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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.elasticsearch.discovery.ec2;
+
+import org.elasticsearch.cloud.aws.network.Ec2NameResolver;
+import org.elasticsearch.common.network.NetworkService;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.test.ESTestCase;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.net.InetAddress;
+
+import static org.hamcrest.Matchers.arrayContaining;
+import static org.hamcrest.Matchers.containsString;
+
+/**
+ * Test for EC2 network.host settings.
+ */
+public class Ec2NetworkTests extends ESTestCase {
+
+ /**
+ * Test for network.host: _ec2_
+ */
+ @Test
+ public void networkHostEc2() throws IOException {
+ Settings nodeSettings = Settings.builder()
+ .put("network.host", "_ec2_")
+ .build();
+
+ NetworkService networkService = new NetworkService(nodeSettings);
+ networkService.addCustomNameResolver(new Ec2NameResolver(nodeSettings));
+ // TODO we need to replace that with a mock. For now we check the URL we are supposed to reach.
+ try {
+ networkService.resolveBindHostAddress(null);
+ } catch (IOException e) {
+ assertThat(e.getMessage(), containsString("local-ipv4"));
+ }
+ }
+
+ /**
+ * Test for network.host: _ec2:publicIp_
+ */
+ @Test
+ public void networkHostEc2PublicIp() throws IOException {
+ Settings nodeSettings = Settings.builder()
+ .put("network.host", "_ec2:publicIp_")
+ .build();
+
+ NetworkService networkService = new NetworkService(nodeSettings);
+ networkService.addCustomNameResolver(new Ec2NameResolver(nodeSettings));
+ // TODO we need to replace that with a mock. For now we check the URL we are supposed to reach.
+ try {
+ networkService.resolveBindHostAddress(null);
+ } catch (IOException e) {
+ assertThat(e.getMessage(), containsString("public-ipv4"));
+ }
+ }
+
+ /**
+ * Test for network.host: _ec2:privateIp_
+ */
+ @Test
+ public void networkHostEc2PrivateIp() throws IOException {
+ Settings nodeSettings = Settings.builder()
+ .put("network.host", "_ec2:privateIp_")
+ .build();
+
+ NetworkService networkService = new NetworkService(nodeSettings);
+ networkService.addCustomNameResolver(new Ec2NameResolver(nodeSettings));
+ // TODO we need to replace that with a mock. For now we check the URL we are supposed to reach.
+ try {
+ networkService.resolveBindHostAddress(null);
+ } catch (IOException e) {
+ assertThat(e.getMessage(), containsString("local-ipv4"));
+ }
+ }
+
+ /**
+ * Test for network.host: _ec2:privateIpv4_
+ */
+ @Test
+ public void networkHostEc2PrivateIpv4() throws IOException {
+ Settings nodeSettings = Settings.builder()
+ .put("network.host", "_ec2:privateIpv4_")
+ .build();
+
+ NetworkService networkService = new NetworkService(nodeSettings);
+ networkService.addCustomNameResolver(new Ec2NameResolver(nodeSettings));
+ // TODO we need to replace that with a mock. For now we check the URL we are supposed to reach.
+ try {
+ networkService.resolveBindHostAddress(null);
+ } catch (IOException e) {
+ assertThat(e.getMessage(), containsString("local-ipv4"));
+ }
+ }
+
+ /**
+ * Test for network.host: _ec2:privateDns_
+ */
+ @Test
+ public void networkHostEc2PrivateDns() throws IOException {
+ Settings nodeSettings = Settings.builder()
+ .put("network.host", "_ec2:privateDns_")
+ .build();
+
+ NetworkService networkService = new NetworkService(nodeSettings);
+ networkService.addCustomNameResolver(new Ec2NameResolver(nodeSettings));
+ // TODO we need to replace that with a mock. For now we check the URL we are supposed to reach.
+ try {
+ networkService.resolveBindHostAddress(null);
+ } catch (IOException e) {
+ assertThat(e.getMessage(), containsString("local-hostname"));
+ }
+ }
+
+ /**
+ * Test for network.host: _ec2:publicIpv4_
+ */
+ @Test
+ public void networkHostEc2PublicIpv4() throws IOException {
+ Settings nodeSettings = Settings.builder()
+ .put("network.host", "_ec2:publicIpv4_")
+ .build();
+
+ NetworkService networkService = new NetworkService(nodeSettings);
+ networkService.addCustomNameResolver(new Ec2NameResolver(nodeSettings));
+ // TODO we need to replace that with a mock. For now we check the URL we are supposed to reach.
+ try {
+ networkService.resolveBindHostAddress(null);
+ } catch (IOException e) {
+ assertThat(e.getMessage(), containsString("public-ipv4"));
+ }
+ }
+
+ /**
+ * Test for network.host: _ec2:publicDns_
+ */
+ @Test
+ public void networkHostEc2PublicDns() throws IOException {
+ Settings nodeSettings = Settings.builder()
+ .put("network.host", "_ec2:publicDns_")
+ .build();
+
+ NetworkService networkService = new NetworkService(nodeSettings);
+ networkService.addCustomNameResolver(new Ec2NameResolver(nodeSettings));
+ // TODO we need to replace that with a mock. For now we check the URL we are supposed to reach.
+ try {
+ networkService.resolveBindHostAddress(null);
+ } catch (IOException e) {
+ assertThat(e.getMessage(), containsString("public-hostname"));
+ }
+ }
+
+ /**
+ * Test that we don't have any regression with network host core settings such as
+ * network.host: _local_
+ */
+ @Test
+ public void networkHostCoreLocal() throws IOException {
+ Settings nodeSettings = Settings.builder()
+ .put("network.host", "_local_")
+ .build();
+
+ NetworkService networkService = new NetworkService(nodeSettings);
+ networkService.addCustomNameResolver(new Ec2NameResolver(nodeSettings));
+ InetAddress[] addresses = networkService.resolveBindHostAddress(null);
+ assertThat(addresses, arrayContaining(networkService.resolveBindHostAddress("_local_")));
+ }
+}