[discovery-gce] add _gce_ network host setting

When running in GCE platform, an instance has access to:

http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/ip

Which gives back the private IP address, for example `10.240.0.2`.

http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/externalIp

Gives back the public Ip address, for example `130.211.108.21`.

As we have for `ec2`, we can support new network host settings:

* `_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).

Closes #13605.
Closes #13590.

BTW resolveIfPossible now throws IOException so code is also updated for ec2 discovery and
some basic tests have been added.
This commit is contained in:
David Pilato 2015-09-16 12:56:28 +02:00
parent c0363dd56b
commit 289cd5dcf4
27 changed files with 595 additions and 41 deletions

View File

@ -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 <tt>null</tt> if can't handle it.
*/
InetAddress[] resolveIfPossible(String value);
InetAddress[] resolveIfPossible(String value) throws IOException;
}
private final List<CustomNameResolver> 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

View File

@ -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 specific host settings>>:
==============================================
[[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)

View File

@ -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

View File

@ -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<GceComputeService> {
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<GceComputeService>
public static final String VERSION = "Elasticsearch/GceCloud/1.0";
}
public Collection<Instance> instances();
/**
* Return a collection of running instances within the same GCE project
* @return a collection of running instances within the same GCE project
*/
Collection<Instance> instances();
/**
* <p>Gets metadata on the current running machine (call to
* http://metadata.google.internal/computeMetadata/v1/instance/xxx).</p>
* <p>For example, you can retrieve network information by replacing xxx with:</p>
* <ul>
* <li>`hostname` when we need to resolve the host name</li>
* <li>`network-interfaces/0/ip` when we need to resolve private IP</li>
* </ul>
* @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;
}

View File

@ -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<GceCompute
// Forcing Google Token API URL as set in GCE SDK to
// http://metadata/computeMetadata/v1/instance/service-accounts/default/token
// See https://developers.google.com/compute/docs/metadata#metadataserver
public static final String TOKEN_SERVER_ENCODED_URL = "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token";
public static final String GCE_METADATA_URL = "http://metadata.google.internal/computeMetadata/v1/instance";
public static final String TOKEN_SERVER_ENCODED_URL = GCE_METADATA_URL + "/service-accounts/default/token";
@Override
public Collection<Instance> instances() {
@ -95,6 +102,37 @@ public class GceComputeServiceImpl extends AbstractLifecycleComponent<GceCompute
return instances;
}
@Override
public String metadata(String metadataPath) throws IOException {
String urlMetadataNetwork = GCE_METADATA_URL + "/" + metadataPath;
logger.debug("get metadata from [{}]", urlMetadataNetwork);
URL url = new URL(urlMetadataNetwork);
HttpHeaders headers;
try {
// hack around code messiness in GCE code
// TODO: get this fixed
headers = AccessController.doPrivileged(new PrivilegedExceptionAction<HttpHeaders>() {
@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 AbstractLifecycleComponent<GceCompute
private JsonFactory gceJsonFactory;
@Inject
public GceComputeServiceImpl(Settings settings) {
public GceComputeServiceImpl(Settings settings, NetworkService networkService) {
super(settings);
this.project = settings.get(Fields.PROJECT);
String[] zoneList = settings.getAsArray(Fields.ZONE);
this.zones = Arrays.asList(zoneList);
networkService.addCustomNameResolver(new GceNameResolver(settings, this));
}
protected synchronized HttpTransport getGceHttpTransport() throws GeneralSecurityException, IOException {

View File

@ -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.cloud.gce.network;
import org.elasticsearch.cloud.gce.GceComputeService;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.network.NetworkService.CustomNameResolver;
import org.elasticsearch.common.settings.Settings;
import java.io.IOException;
import java.net.InetAddress;
/**
* <p>Resolves certain GCE related 'meta' hostnames into an actual hostname
* obtained from gce meta-data.</p>
* Valid config values for {@link GceAddressResolverType}s are -
* <ul>
* <li>_gce_ - maps to privateIp</li>
* <li>_gce:privateIp_</li>
* <li>_gce:hostname_</li>
* </ul>
*/
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;
}
}

View File

@ -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<String>() {
@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;
}
}
}

View File

@ -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<DiscoveryNode> 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<DiscoveryNode> 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<DiscoveryNode> 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<DiscoveryNode> 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<DiscoveryNode> 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<DiscoveryNode> 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<DiscoveryNode> 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<DiscoveryNode> 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<DiscoveryNode> discoveryNodes = buildDynamicNodes(mock, nodeSettings);
assertThat(discoveryNodes, hasSize(0));
}

View File

@ -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"));
}
}
}

View File

@ -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;

View File

@ -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_")));
}
}