Allow `_gce_` network when not using discovery gce

For now we support `_gce_` only if discovery is set to `gce` and all information about GCE is provided (project_id and zone).
But in some cases, people would like to only bind to `_gce_` on a single node (without any elasticsearch cluster).

They could access the machine then from other machines running inside the same project.

This commit adds a new GceMetadataService which is started as soon as the plugin is started so GceNameResolver can use it to resolve `_gce`.

Closes #15724.
This commit is contained in:
David Pilato 2016-04-29 16:56:24 +02:00
parent 2232a7cdf3
commit c16d309c8c
13 changed files with 381 additions and 146 deletions

View File

@ -76,19 +76,4 @@ public interface GceComputeService extends LifecycleComponent<GceComputeService>
* @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(); 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

@ -116,47 +116,6 @@ public class GceComputeServiceImpl extends AbstractLifecycleComponent<GceCompute
return instances; return instances;
} }
@Override
public String metadata(String metadataPath) throws IOException {
String urlMetadataNetwork = this.metaDataUrl + "/" + metadataPath;
logger.debug("get metadata from [{}]", urlMetadataNetwork);
final URL url = new URL(urlMetadataNetwork);
HttpHeaders headers;
try {
// hack around code messiness in GCE code
// TODO: get this fixed
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new SpecialPermission());
}
headers = AccessController.doPrivileged(new PrivilegedExceptionAction<HttpHeaders>() {
@Override
public HttpHeaders run() throws IOException {
return new HttpHeaders();
}
});
GenericUrl genericUrl = AccessController.doPrivileged(new PrivilegedAction<GenericUrl>() {
@Override
public GenericUrl run() {
return new GenericUrl(url);
}
});
// 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(genericUrl)
.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 Compute client;
private TimeValue refreshInterval = null; private TimeValue refreshInterval = null;
private long lastRefresh; private long lastRefresh;
@ -168,17 +127,17 @@ public class GceComputeServiceImpl extends AbstractLifecycleComponent<GceCompute
private JsonFactory gceJsonFactory; private JsonFactory gceJsonFactory;
private final boolean validateCerts; private final boolean validateCerts;
@Inject @Inject
public GceComputeServiceImpl(Settings settings, NetworkService networkService) { public GceComputeServiceImpl(Settings settings) {
super(settings); super(settings);
this.project = PROJECT_SETTING.get(settings); this.project = PROJECT_SETTING.get(settings);
this.zones = ZONE_SETTING.get(settings); this.zones = ZONE_SETTING.get(settings);
this.gceHost = GCE_HOST.get(settings); this.gceHost = GCE_HOST.get(settings);
this.metaDataUrl = gceHost + "/computeMetadata/v1/instance"; this.metaDataUrl = gceHost + "/computeMetadata/v1/instance";
this.gceRootUrl = GCE_ROOT_URL.get(settings); this.gceRootUrl = GCE_ROOT_URL.get(settings);
tokenServerEncodedUrl = metaDataUrl + "/service-accounts/default/token"; this.tokenServerEncodedUrl = metaDataUrl + "/service-accounts/default/token";
this.validateCerts = GCE_VALIDATE_CERTIFICATES.get(settings); this.validateCerts = GCE_VALIDATE_CERTIFICATES.get(settings);
networkService.addCustomNameResolver(new GceNameResolver(settings, this));
} }
protected synchronized HttpTransport getGceHttpTransport() throws GeneralSecurityException, IOException { protected synchronized HttpTransport getGceHttpTransport() throws GeneralSecurityException, IOException {

View File

@ -0,0 +1,44 @@
/*
* 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;
import org.elasticsearch.common.component.LifecycleComponent;
import java.io.IOException;
/**
* Access Google Compute Engine metadata
*/
public interface GceMetadataService extends LifecycleComponent<GceMetadataService> {
/**
* <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

@ -0,0 +1,140 @@
/*
* 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;
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 org.elasticsearch.SpecialPermission;
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.Setting;
import org.elasticsearch.common.settings.Settings;
import java.io.IOException;
import java.net.URL;
import java.security.AccessController;
import java.security.GeneralSecurityException;
import java.security.PrivilegedAction;
import java.security.PrivilegedExceptionAction;
import java.util.List;
import java.util.function.Function;
public class GceMetadataServiceImpl extends AbstractLifecycleComponent<GceMetadataService> implements GceMetadataService {
// 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
// all settings just used for testing - not registered by default
public static final Setting<String> GCE_HOST =
new Setting<>("cloud.gce.host", "http://metadata.google.internal", Function.identity(), Setting.Property.NodeScope);
/** Global instance of the HTTP transport. */
private HttpTransport gceHttpTransport;
// 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
private final String gceHost;
private final String metaDataUrl;
@Inject
public GceMetadataServiceImpl(Settings settings, NetworkService networkService) {
super(settings);
networkService.addCustomNameResolver(new GceNameResolver(settings, this));
this.gceHost = GCE_HOST.get(settings);
this.metaDataUrl = gceHost + "/computeMetadata/v1/instance";
}
protected synchronized HttpTransport getGceHttpTransport() throws GeneralSecurityException, IOException {
if (gceHttpTransport == null) {
gceHttpTransport = GoogleNetHttpTransport.newTrustedTransport();
}
return gceHttpTransport;
}
@Override
public String metadata(String metadataPath) throws IOException {
String urlMetadataNetwork = this.metaDataUrl + "/" + metadataPath;
logger.debug("get metadata from [{}]", urlMetadataNetwork);
final URL url = new URL(urlMetadataNetwork);
HttpHeaders headers;
try {
// hack around code messiness in GCE code
// TODO: get this fixed
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new SpecialPermission());
}
headers = AccessController.doPrivileged(new PrivilegedExceptionAction<HttpHeaders>() {
@Override
public HttpHeaders run() throws IOException {
return new HttpHeaders();
}
});
GenericUrl genericUrl = AccessController.doPrivileged(new PrivilegedAction<GenericUrl>() {
@Override
public GenericUrl run() {
return new GenericUrl(url);
}
});
// 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(genericUrl)
.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);
}
}
@Override
protected void doStart() {
}
@Override
protected void doStop() {
if (gceHttpTransport != null) {
try {
gceHttpTransport.shutdown();
} catch (IOException e) {
logger.warn("unable to shutdown GCE Http Transport", e);
}
gceHttpTransport = null;
}
}
@Override
protected void doClose() {
}
}

View File

@ -20,17 +20,34 @@
package org.elasticsearch.cloud.gce; package org.elasticsearch.cloud.gce;
import org.elasticsearch.common.inject.AbstractModule; import org.elasticsearch.common.inject.AbstractModule;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.Settings;
public class GceModule extends AbstractModule { public class GceModule extends AbstractModule {
// pkg private so tests can override with mock // pkg private so tests can override with mock
static Class<? extends GceComputeService> computeServiceImpl = GceComputeServiceImpl.class; static Class<? extends GceComputeService> computeServiceImpl = GceComputeServiceImpl.class;
static Class<? extends GceMetadataService> metadataServiceImpl = GceMetadataServiceImpl.class;
protected final Settings settings;
protected final ESLogger logger = Loggers.getLogger(GceModule.class);
public GceModule(Settings settings) {
this.settings = settings;
}
public static Class<? extends GceComputeService> getComputeServiceImpl() { public static Class<? extends GceComputeService> getComputeServiceImpl() {
return computeServiceImpl; return computeServiceImpl;
} }
public static Class<? extends GceMetadataService> getMetadataServiceImpl() {
return metadataServiceImpl;
}
@Override @Override
protected void configure() { protected void configure() {
logger.debug("configure GceModule (bind compute and metadata services)");
bind(GceComputeService.class).to(computeServiceImpl).asEagerSingleton(); bind(GceComputeService.class).to(computeServiceImpl).asEagerSingleton();
bind(GceMetadataService.class).to(metadataServiceImpl).asEagerSingleton();
} }
} }

View File

@ -20,6 +20,7 @@
package org.elasticsearch.cloud.gce.network; package org.elasticsearch.cloud.gce.network;
import org.elasticsearch.cloud.gce.GceComputeService; import org.elasticsearch.cloud.gce.GceComputeService;
import org.elasticsearch.cloud.gce.GceMetadataService;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.network.NetworkService.CustomNameResolver; import org.elasticsearch.common.network.NetworkService.CustomNameResolver;
@ -40,7 +41,7 @@ import java.net.InetAddress;
*/ */
public class GceNameResolver extends AbstractComponent implements CustomNameResolver { public class GceNameResolver extends AbstractComponent implements CustomNameResolver {
private final GceComputeService gceComputeService; private final GceMetadataService gceMetadataService;
/** /**
* enum that can be added to over time with more meta-data types * enum that can be added to over time with more meta-data types
@ -72,9 +73,9 @@ public class GceNameResolver extends AbstractComponent implements CustomNameReso
/** /**
* Construct a {@link CustomNameResolver}. * Construct a {@link CustomNameResolver}.
*/ */
public GceNameResolver(Settings settings, GceComputeService gceComputeService) { public GceNameResolver(Settings settings, GceMetadataService gceMetadataService) {
super(settings); super(settings);
this.gceComputeService = gceComputeService; this.gceMetadataService = gceMetadataService;
} }
/** /**
@ -105,7 +106,7 @@ public class GceNameResolver extends AbstractComponent implements CustomNameReso
} }
try { try {
String metadataResult = gceComputeService.metadata(gceMetadataPath); String metadataResult = gceMetadataService.metadata(gceMetadataPath);
if (metadataResult == null || metadataResult.length() == 0) { if (metadataResult == null || metadataResult.length() == 0) {
throw new IOException("no gce metadata returned from [" + gceMetadataPath + "] for [" + value + "]"); throw new IOException("no gce metadata returned from [" + gceMetadataPath + "] for [" + value + "]");
} }

View File

@ -37,6 +37,7 @@ import org.elasticsearch.plugins.Plugin;
import java.security.AccessController; import java.security.AccessController;
import java.security.PrivilegedAction; import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -85,22 +86,27 @@ public class GceDiscoveryPlugin extends Plugin {
@Override @Override
public Collection<Module> nodeModules() { public Collection<Module> nodeModules() {
return Collections.singletonList(new GceModule()); return Collections.singletonList(new GceModule(settings));
} }
@Override @Override
@SuppressWarnings("rawtypes") // Supertype uses raw type @SuppressWarnings("rawtypes") // Supertype uses raw type
public Collection<Class<? extends LifecycleComponent>> nodeServices() { public Collection<Class<? extends LifecycleComponent>> nodeServices() {
return Collections.singletonList(GceModule.getComputeServiceImpl()); logger.debug("Register gce compute and metadata services");
Collection<Class<? extends LifecycleComponent>> services = new ArrayList<>();
services.add(GceModule.getComputeServiceImpl());
services.add(GceModule.getMetadataServiceImpl());
return services;
} }
public void onModule(DiscoveryModule discoveryModule) { public void onModule(DiscoveryModule discoveryModule) {
logger.debug("Register gce discovery type and gce unicast provider");
discoveryModule.addDiscoveryType(GCE, ZenDiscovery.class); discoveryModule.addDiscoveryType(GCE, ZenDiscovery.class);
// If discovery.type: gce, we add Gce as a unicast provider
discoveryModule.addUnicastHostProvider(GCE, GceUnicastHostsProvider.class); discoveryModule.addUnicastHostProvider(GCE, GceUnicastHostsProvider.class);
} }
public void onModule(SettingsModule settingsModule) { public void onModule(SettingsModule settingsModule) {
logger.debug("registering GCE Settings");
// Register GCE settings // Register GCE settings
settingsModule.registerSetting(GceComputeService.PROJECT_SETTING); settingsModule.registerSetting(GceComputeService.PROJECT_SETTING);
settingsModule.registerSetting(GceComputeService.ZONE_SETTING); settingsModule.registerSetting(GceComputeService.ZONE_SETTING);

View File

@ -45,69 +45,13 @@ public class GceComputeServiceMock extends GceComputeServiceImpl {
protected HttpTransport mockHttpTransport; protected HttpTransport mockHttpTransport;
public GceComputeServiceMock(Settings settings, NetworkService networkService) { public GceComputeServiceMock(Settings settings) {
super(settings, networkService); super(settings);
this.mockHttpTransport = configureMock(); this.mockHttpTransport = GceMockUtils.configureMock();
} }
@Override @Override
protected HttpTransport getGceHttpTransport() throws GeneralSecurityException, IOException { protected HttpTransport getGceHttpTransport() throws GeneralSecurityException, IOException {
return this.mockHttpTransport; return this.mockHttpTransport;
} }
public static final String GCE_METADATA_URL = "http://metadata.google.internal/computeMetadata/v1/instance";
protected HttpTransport configureMock() {
return new MockHttpTransport() {
@Override
public LowLevelHttpRequest buildRequest(String method, final String url) throws IOException {
return new MockLowLevelHttpRequest() {
@Override
public LowLevelHttpResponse execute() throws IOException {
MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
response.setStatusCode(200);
response.setContentType(Json.MEDIA_TYPE);
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);
response.setContent(readGoogleApiJsonResponse(url));
}
return response;
}
};
}
};
}
public static String readGoogleInternalJsonResponse(String url) throws IOException {
return readJsonResponse(url, "http://metadata.google.internal/");
}
public static String readGoogleApiJsonResponse(String url) throws IOException {
return readJsonResponse(url, "https://www.googleapis.com/");
}
private static 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, "");
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);
}
});
String response = sb.toString();
return response;
}
}
} }

View File

@ -122,7 +122,7 @@ public class GceDiscoverTests extends ESIntegTestCase {
httpServer = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress().getHostAddress(), 0), 0); httpServer = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress().getHostAddress(), 0), 0);
httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext)); httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
httpServer.createContext("/computeMetadata/v1/instance/service-accounts/default/token", (s) -> { httpServer.createContext("/computeMetadata/v1/instance/service-accounts/default/token", (s) -> {
String response = GceComputeServiceMock.readGoogleInternalJsonResponse( String response = GceMockUtils.readGoogleInternalJsonResponse(
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token"); "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token");
byte[] responseAsBytes = response.getBytes(StandardCharsets.UTF_8); byte[] responseAsBytes = response.getBytes(StandardCharsets.UTF_8);
s.sendResponseHeaders(200, responseAsBytes.length); s.sendResponseHeaders(200, responseAsBytes.length);

View File

@ -65,7 +65,6 @@ public class GceDiscoveryTests extends ESTestCase {
protected static ThreadPool threadPool; protected static ThreadPool threadPool;
protected MockTransportService transportService; protected MockTransportService transportService;
protected NetworkService networkService;
protected GceComputeService mock; protected GceComputeService mock;
protected String projectName; protected String projectName;
@ -96,11 +95,6 @@ public class GceDiscoveryTests extends ESTestCase {
transportService = MockTransportService.local(Settings.EMPTY, Version.CURRENT, threadPool); transportService = MockTransportService.local(Settings.EMPTY, Version.CURRENT, threadPool);
} }
@Before
public void createNetworkService() {
networkService = new NetworkService(Settings.EMPTY);
}
@After @After
public void stopGceComputeService() { public void stopGceComputeService() {
if (mock != null) { if (mock != null) {
@ -122,7 +116,7 @@ public class GceDiscoveryTests extends ESTestCase {
.put(GceComputeService.PROJECT_SETTING.getKey(), projectName) .put(GceComputeService.PROJECT_SETTING.getKey(), projectName)
.put(GceComputeService.ZONE_SETTING.getKey(), "europe-west1-b") .put(GceComputeService.ZONE_SETTING.getKey(), "europe-west1-b")
.build(); .build();
mock = new GceComputeServiceMock(nodeSettings, networkService); mock = new GceComputeServiceMock(nodeSettings);
List<DiscoveryNode> discoveryNodes = buildDynamicNodes(mock, nodeSettings); List<DiscoveryNode> discoveryNodes = buildDynamicNodes(mock, nodeSettings);
assertThat(discoveryNodes, hasSize(2)); assertThat(discoveryNodes, hasSize(2));
} }
@ -133,7 +127,7 @@ public class GceDiscoveryTests extends ESTestCase {
.put(GceComputeService.ZONE_SETTING.getKey(), "europe-west1-b") .put(GceComputeService.ZONE_SETTING.getKey(), "europe-west1-b")
.putArray(GceUnicastHostsProvider.TAGS_SETTING.getKey(), "elasticsearch") .putArray(GceUnicastHostsProvider.TAGS_SETTING.getKey(), "elasticsearch")
.build(); .build();
mock = new GceComputeServiceMock(nodeSettings, networkService); mock = new GceComputeServiceMock(nodeSettings);
List<DiscoveryNode> discoveryNodes = buildDynamicNodes(mock, nodeSettings); List<DiscoveryNode> discoveryNodes = buildDynamicNodes(mock, nodeSettings);
assertThat(discoveryNodes, hasSize(1)); assertThat(discoveryNodes, hasSize(1));
assertThat(discoveryNodes.get(0).getId(), is("#cloud-test2-0")); assertThat(discoveryNodes.get(0).getId(), is("#cloud-test2-0"));
@ -145,7 +139,7 @@ public class GceDiscoveryTests extends ESTestCase {
.put(GceComputeService.ZONE_SETTING.getKey(), "europe-west1-b") .put(GceComputeService.ZONE_SETTING.getKey(), "europe-west1-b")
.putArray(GceUnicastHostsProvider.TAGS_SETTING.getKey(), "elasticsearch", "dev") .putArray(GceUnicastHostsProvider.TAGS_SETTING.getKey(), "elasticsearch", "dev")
.build(); .build();
mock = new GceComputeServiceMock(nodeSettings, networkService); mock = new GceComputeServiceMock(nodeSettings);
List<DiscoveryNode> discoveryNodes = buildDynamicNodes(mock, nodeSettings); List<DiscoveryNode> discoveryNodes = buildDynamicNodes(mock, nodeSettings);
assertThat(discoveryNodes, hasSize(1)); assertThat(discoveryNodes, hasSize(1));
assertThat(discoveryNodes.get(0).getId(), is("#cloud-test2-0")); assertThat(discoveryNodes.get(0).getId(), is("#cloud-test2-0"));
@ -156,7 +150,7 @@ public class GceDiscoveryTests extends ESTestCase {
.put(GceComputeService.PROJECT_SETTING.getKey(), projectName) .put(GceComputeService.PROJECT_SETTING.getKey(), projectName)
.put(GceComputeService.ZONE_SETTING.getKey(), "europe-west1-b") .put(GceComputeService.ZONE_SETTING.getKey(), "europe-west1-b")
.build(); .build();
mock = new GceComputeServiceMock(nodeSettings, networkService); mock = new GceComputeServiceMock(nodeSettings);
List<DiscoveryNode> discoveryNodes = buildDynamicNodes(mock, nodeSettings); List<DiscoveryNode> discoveryNodes = buildDynamicNodes(mock, nodeSettings);
assertThat(discoveryNodes, hasSize(2)); assertThat(discoveryNodes, hasSize(2));
} }
@ -167,7 +161,7 @@ public class GceDiscoveryTests extends ESTestCase {
.put(GceComputeService.ZONE_SETTING.getKey(), "europe-west1-b") .put(GceComputeService.ZONE_SETTING.getKey(), "europe-west1-b")
.putArray(GceUnicastHostsProvider.TAGS_SETTING.getKey(), "elasticsearch") .putArray(GceUnicastHostsProvider.TAGS_SETTING.getKey(), "elasticsearch")
.build(); .build();
mock = new GceComputeServiceMock(nodeSettings, networkService); mock = new GceComputeServiceMock(nodeSettings);
List<DiscoveryNode> discoveryNodes = buildDynamicNodes(mock, nodeSettings); List<DiscoveryNode> discoveryNodes = buildDynamicNodes(mock, nodeSettings);
assertThat(discoveryNodes, hasSize(2)); assertThat(discoveryNodes, hasSize(2));
} }
@ -178,7 +172,7 @@ public class GceDiscoveryTests extends ESTestCase {
.put(GceComputeService.ZONE_SETTING.getKey(), "europe-west1-b") .put(GceComputeService.ZONE_SETTING.getKey(), "europe-west1-b")
.putArray(GceUnicastHostsProvider.TAGS_SETTING.getKey(), "elasticsearch", "dev") .putArray(GceUnicastHostsProvider.TAGS_SETTING.getKey(), "elasticsearch", "dev")
.build(); .build();
mock = new GceComputeServiceMock(nodeSettings, networkService); mock = new GceComputeServiceMock(nodeSettings);
List<DiscoveryNode> discoveryNodes = buildDynamicNodes(mock, nodeSettings); List<DiscoveryNode> discoveryNodes = buildDynamicNodes(mock, nodeSettings);
assertThat(discoveryNodes, hasSize(2)); assertThat(discoveryNodes, hasSize(2));
} }
@ -188,7 +182,7 @@ public class GceDiscoveryTests extends ESTestCase {
.put(GceComputeService.PROJECT_SETTING.getKey(), projectName) .put(GceComputeService.PROJECT_SETTING.getKey(), projectName)
.putArray(GceComputeService.ZONE_SETTING.getKey(), "us-central1-a", "europe-west1-b") .putArray(GceComputeService.ZONE_SETTING.getKey(), "us-central1-a", "europe-west1-b")
.build(); .build();
mock = new GceComputeServiceMock(nodeSettings, networkService); mock = new GceComputeServiceMock(nodeSettings);
List<DiscoveryNode> discoveryNodes = buildDynamicNodes(mock, nodeSettings); List<DiscoveryNode> discoveryNodes = buildDynamicNodes(mock, nodeSettings);
assertThat(discoveryNodes, hasSize(2)); assertThat(discoveryNodes, hasSize(2));
} }
@ -198,7 +192,7 @@ public class GceDiscoveryTests extends ESTestCase {
.put(GceComputeService.PROJECT_SETTING.getKey(), projectName) .put(GceComputeService.PROJECT_SETTING.getKey(), projectName)
.putArray(GceComputeService.ZONE_SETTING.getKey(), "us-central1-a", "europe-west1-b") .putArray(GceComputeService.ZONE_SETTING.getKey(), "us-central1-a", "europe-west1-b")
.build(); .build();
mock = new GceComputeServiceMock(nodeSettings, networkService); mock = new GceComputeServiceMock(nodeSettings);
List<DiscoveryNode> discoveryNodes = buildDynamicNodes(mock, nodeSettings); List<DiscoveryNode> discoveryNodes = buildDynamicNodes(mock, nodeSettings);
assertThat(discoveryNodes, hasSize(2)); assertThat(discoveryNodes, hasSize(2));
} }
@ -211,14 +205,14 @@ public class GceDiscoveryTests extends ESTestCase {
.put(GceComputeService.PROJECT_SETTING.getKey(), projectName) .put(GceComputeService.PROJECT_SETTING.getKey(), projectName)
.putArray(GceComputeService.ZONE_SETTING.getKey(), "us-central1-a", "us-central1-b") .putArray(GceComputeService.ZONE_SETTING.getKey(), "us-central1-a", "us-central1-b")
.build(); .build();
mock = new GceComputeServiceMock(nodeSettings, networkService); mock = new GceComputeServiceMock(nodeSettings);
List<DiscoveryNode> discoveryNodes = buildDynamicNodes(mock, nodeSettings); List<DiscoveryNode> discoveryNodes = buildDynamicNodes(mock, nodeSettings);
assertThat(discoveryNodes, hasSize(0)); assertThat(discoveryNodes, hasSize(0));
} }
public void testIllegalSettingsMissingAllRequired() { public void testIllegalSettingsMissingAllRequired() {
Settings nodeSettings = Settings.EMPTY; Settings nodeSettings = Settings.EMPTY;
mock = new GceComputeServiceMock(Settings.EMPTY, networkService); mock = new GceComputeServiceMock(nodeSettings);
try { try {
buildDynamicNodes(mock, nodeSettings); buildDynamicNodes(mock, nodeSettings);
fail("We expect an IllegalArgumentException for incomplete settings"); fail("We expect an IllegalArgumentException for incomplete settings");
@ -231,7 +225,7 @@ public class GceDiscoveryTests extends ESTestCase {
Settings nodeSettings = Settings.builder() Settings nodeSettings = Settings.builder()
.putArray(GceComputeService.ZONE_SETTING.getKey(), "us-central1-a", "us-central1-b") .putArray(GceComputeService.ZONE_SETTING.getKey(), "us-central1-a", "us-central1-b")
.build(); .build();
mock = new GceComputeServiceMock(nodeSettings, networkService); mock = new GceComputeServiceMock(nodeSettings);
try { try {
buildDynamicNodes(mock, nodeSettings); buildDynamicNodes(mock, nodeSettings);
fail("We expect an IllegalArgumentException for incomplete settings"); fail("We expect an IllegalArgumentException for incomplete settings");
@ -244,7 +238,7 @@ public class GceDiscoveryTests extends ESTestCase {
Settings nodeSettings = Settings.builder() Settings nodeSettings = Settings.builder()
.put(GceComputeService.PROJECT_SETTING.getKey(), projectName) .put(GceComputeService.PROJECT_SETTING.getKey(), projectName)
.build(); .build();
mock = new GceComputeServiceMock(nodeSettings, networkService); mock = new GceComputeServiceMock(nodeSettings);
try { try {
buildDynamicNodes(mock, nodeSettings); buildDynamicNodes(mock, nodeSettings);
fail("We expect an IllegalArgumentException for incomplete settings"); fail("We expect an IllegalArgumentException for incomplete settings");

View File

@ -0,0 +1,47 @@
/*
* 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 com.google.api.client.http.HttpTransport;
import org.elasticsearch.cloud.gce.GceComputeServiceImpl;
import org.elasticsearch.cloud.gce.GceMetadataServiceImpl;
import org.elasticsearch.common.network.NetworkService;
import org.elasticsearch.common.settings.Settings;
import java.io.IOException;
import java.security.GeneralSecurityException;
/**
* Mock for GCE Metadata Service
*/
public class GceMetadataServiceMock extends GceMetadataServiceImpl {
protected HttpTransport mockHttpTransport;
public GceMetadataServiceMock(Settings settings, NetworkService networkService) {
super(settings, networkService);
this.mockHttpTransport = GceMockUtils.configureMock();
}
@Override
protected HttpTransport getGceHttpTransport() throws GeneralSecurityException, IOException {
return this.mockHttpTransport;
}
}

View File

@ -0,0 +1,98 @@
/*
* 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 com.google.api.client.http.HttpTransport;
import com.google.api.client.http.LowLevelHttpRequest;
import com.google.api.client.http.LowLevelHttpResponse;
import com.google.api.client.json.Json;
import com.google.api.client.testing.http.MockHttpTransport;
import com.google.api.client.testing.http.MockLowLevelHttpRequest;
import com.google.api.client.testing.http.MockLowLevelHttpResponse;
import org.elasticsearch.cloud.gce.GceMetadataServiceImpl;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.Streams;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.util.Callback;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
public class GceMockUtils {
protected final static ESLogger logger = Loggers.getLogger(GceMockUtils.class);
public static final String GCE_METADATA_URL = "http://metadata.google.internal/computeMetadata/v1/instance";
protected static HttpTransport configureMock() {
return new MockHttpTransport() {
@Override
public LowLevelHttpRequest buildRequest(String method, final String url) throws IOException {
return new MockLowLevelHttpRequest() {
@Override
public LowLevelHttpResponse execute() throws IOException {
MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
response.setStatusCode(200);
response.setContentType(Json.MEDIA_TYPE);
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);
response.setContent(readGoogleApiJsonResponse(url));
}
return response;
}
};
}
};
}
public static String readGoogleInternalJsonResponse(String url) throws IOException {
return readJsonResponse(url, "http://metadata.google.internal/");
}
public static String readGoogleApiJsonResponse(String url) throws IOException {
return readJsonResponse(url, "https://www.googleapis.com/");
}
private static 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, "");
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);
}
});
String response = sb.toString();
return response;
}
}
}

View File

@ -104,7 +104,7 @@ public class GceNetworkTests extends ESTestCase {
.build(); .build();
NetworkService networkService = new NetworkService(nodeSettings); NetworkService networkService = new NetworkService(nodeSettings);
GceComputeServiceMock mock = new GceComputeServiceMock(nodeSettings, networkService); GceMetadataServiceMock mock = new GceMetadataServiceMock(nodeSettings, networkService);
networkService.addCustomNameResolver(new GceNameResolver(nodeSettings, mock)); networkService.addCustomNameResolver(new GceNameResolver(nodeSettings, mock));
try { try {
InetAddress[] addresses = networkService.resolveBindHostAddresses(null); InetAddress[] addresses = networkService.resolveBindHostAddresses(null);