From c16d309c8c05f2dd80d2b2dcb36a1bb129c82743 Mon Sep 17 00:00:00 2001 From: David Pilato Date: Fri, 29 Apr 2016 16:56:24 +0200 Subject: [PATCH] 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. --- .../cloud/gce/GceComputeService.java | 15 -- .../cloud/gce/GceComputeServiceImpl.java | 47 +----- .../cloud/gce/GceMetadataService.java | 44 ++++++ .../cloud/gce/GceMetadataServiceImpl.java | 140 ++++++++++++++++++ .../elasticsearch/cloud/gce/GceModule.java | 17 +++ .../cloud/gce/network/GceNameResolver.java | 9 +- .../discovery/gce/GceDiscoveryPlugin.java | 14 +- .../discovery/gce/GceComputeServiceMock.java | 62 +------- .../discovery/gce/GceDiscoverTests.java | 2 +- .../discovery/gce/GceDiscoveryTests.java | 30 ++-- .../discovery/gce/GceMetadataServiceMock.java | 47 ++++++ .../discovery/gce/GceMockUtils.java | 98 ++++++++++++ .../discovery/gce/GceNetworkTests.java | 2 +- 13 files changed, 381 insertions(+), 146 deletions(-) create mode 100644 plugins/discovery-gce/src/main/java/org/elasticsearch/cloud/gce/GceMetadataService.java create mode 100644 plugins/discovery-gce/src/main/java/org/elasticsearch/cloud/gce/GceMetadataServiceImpl.java create mode 100644 plugins/discovery-gce/src/test/java/org/elasticsearch/discovery/gce/GceMetadataServiceMock.java create mode 100644 plugins/discovery-gce/src/test/java/org/elasticsearch/discovery/gce/GceMockUtils.java diff --git a/plugins/discovery-gce/src/main/java/org/elasticsearch/cloud/gce/GceComputeService.java b/plugins/discovery-gce/src/main/java/org/elasticsearch/cloud/gce/GceComputeService.java index a6faa390e5d..a3286afdca3 100644 --- a/plugins/discovery-gce/src/main/java/org/elasticsearch/cloud/gce/GceComputeService.java +++ b/plugins/discovery-gce/src/main/java/org/elasticsearch/cloud/gce/GceComputeService.java @@ -76,19 +76,4 @@ public interface GceComputeService extends LifecycleComponent * @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/discovery-gce/src/main/java/org/elasticsearch/cloud/gce/GceComputeServiceImpl.java b/plugins/discovery-gce/src/main/java/org/elasticsearch/cloud/gce/GceComputeServiceImpl.java index 85e0910736f..14bf56a54c0 100644 --- a/plugins/discovery-gce/src/main/java/org/elasticsearch/cloud/gce/GceComputeServiceImpl.java +++ b/plugins/discovery-gce/src/main/java/org/elasticsearch/cloud/gce/GceComputeServiceImpl.java @@ -116,47 +116,6 @@ public class GceComputeServiceImpl extends AbstractLifecycleComponent() { - @Override - public HttpHeaders run() throws IOException { - return new HttpHeaders(); - } - }); - GenericUrl genericUrl = AccessController.doPrivileged(new PrivilegedAction() { - @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 TimeValue refreshInterval = null; private long lastRefresh; @@ -168,17 +127,17 @@ public class GceComputeServiceImpl extends AbstractLifecycleComponent { + /** + *

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/discovery-gce/src/main/java/org/elasticsearch/cloud/gce/GceMetadataServiceImpl.java b/plugins/discovery-gce/src/main/java/org/elasticsearch/cloud/gce/GceMetadataServiceImpl.java new file mode 100644 index 00000000000..f3d4ea589da --- /dev/null +++ b/plugins/discovery-gce/src/main/java/org/elasticsearch/cloud/gce/GceMetadataServiceImpl.java @@ -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 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 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() { + @Override + public HttpHeaders run() throws IOException { + return new HttpHeaders(); + } + }); + GenericUrl genericUrl = AccessController.doPrivileged(new PrivilegedAction() { + @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() { + + } +} diff --git a/plugins/discovery-gce/src/main/java/org/elasticsearch/cloud/gce/GceModule.java b/plugins/discovery-gce/src/main/java/org/elasticsearch/cloud/gce/GceModule.java index e1b8d6cf02f..dc09d781163 100644 --- a/plugins/discovery-gce/src/main/java/org/elasticsearch/cloud/gce/GceModule.java +++ b/plugins/discovery-gce/src/main/java/org/elasticsearch/cloud/gce/GceModule.java @@ -20,17 +20,34 @@ package org.elasticsearch.cloud.gce; 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 { // pkg private so tests can override with mock static Class computeServiceImpl = GceComputeServiceImpl.class; + static Class 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 getComputeServiceImpl() { return computeServiceImpl; } + public static Class getMetadataServiceImpl() { + return metadataServiceImpl; + } + @Override protected void configure() { + logger.debug("configure GceModule (bind compute and metadata services)"); bind(GceComputeService.class).to(computeServiceImpl).asEagerSingleton(); + bind(GceMetadataService.class).to(metadataServiceImpl).asEagerSingleton(); } } diff --git a/plugins/discovery-gce/src/main/java/org/elasticsearch/cloud/gce/network/GceNameResolver.java b/plugins/discovery-gce/src/main/java/org/elasticsearch/cloud/gce/network/GceNameResolver.java index 22d79fb1614..e8c256c0182 100644 --- a/plugins/discovery-gce/src/main/java/org/elasticsearch/cloud/gce/network/GceNameResolver.java +++ b/plugins/discovery-gce/src/main/java/org/elasticsearch/cloud/gce/network/GceNameResolver.java @@ -20,6 +20,7 @@ package org.elasticsearch.cloud.gce.network; import org.elasticsearch.cloud.gce.GceComputeService; +import org.elasticsearch.cloud.gce.GceMetadataService; import org.elasticsearch.common.Strings; import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.network.NetworkService.CustomNameResolver; @@ -40,7 +41,7 @@ import java.net.InetAddress; */ 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 @@ -72,9 +73,9 @@ public class GceNameResolver extends AbstractComponent implements CustomNameReso /** * Construct a {@link CustomNameResolver}. */ - public GceNameResolver(Settings settings, GceComputeService gceComputeService) { + public GceNameResolver(Settings settings, GceMetadataService gceMetadataService) { super(settings); - this.gceComputeService = gceComputeService; + this.gceMetadataService = gceMetadataService; } /** @@ -105,7 +106,7 @@ public class GceNameResolver extends AbstractComponent implements CustomNameReso } try { - String metadataResult = gceComputeService.metadata(gceMetadataPath); + String metadataResult = gceMetadataService.metadata(gceMetadataPath); if (metadataResult == null || metadataResult.length() == 0) { throw new IOException("no gce metadata returned from [" + gceMetadataPath + "] for [" + value + "]"); } diff --git a/plugins/discovery-gce/src/main/java/org/elasticsearch/plugin/discovery/gce/GceDiscoveryPlugin.java b/plugins/discovery-gce/src/main/java/org/elasticsearch/plugin/discovery/gce/GceDiscoveryPlugin.java index 2b92c4fd8c1..dfa98dbd3f1 100644 --- a/plugins/discovery-gce/src/main/java/org/elasticsearch/plugin/discovery/gce/GceDiscoveryPlugin.java +++ b/plugins/discovery-gce/src/main/java/org/elasticsearch/plugin/discovery/gce/GceDiscoveryPlugin.java @@ -37,6 +37,7 @@ import org.elasticsearch.plugins.Plugin; import java.security.AccessController; import java.security.PrivilegedAction; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -85,22 +86,27 @@ public class GceDiscoveryPlugin extends Plugin { @Override public Collection nodeModules() { - return Collections.singletonList(new GceModule()); + return Collections.singletonList(new GceModule(settings)); } @Override @SuppressWarnings("rawtypes") // Supertype uses raw type public Collection> nodeServices() { - return Collections.singletonList(GceModule.getComputeServiceImpl()); + logger.debug("Register gce compute and metadata services"); + Collection> services = new ArrayList<>(); + services.add(GceModule.getComputeServiceImpl()); + services.add(GceModule.getMetadataServiceImpl()); + return services; } public void onModule(DiscoveryModule discoveryModule) { + logger.debug("Register gce discovery type and gce unicast provider"); 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) { + logger.debug("registering GCE Settings"); // Register GCE settings settingsModule.registerSetting(GceComputeService.PROJECT_SETTING); settingsModule.registerSetting(GceComputeService.ZONE_SETTING); diff --git a/plugins/discovery-gce/src/test/java/org/elasticsearch/discovery/gce/GceComputeServiceMock.java b/plugins/discovery-gce/src/test/java/org/elasticsearch/discovery/gce/GceComputeServiceMock.java index 209657d89d4..acbb10fde1e 100644 --- a/plugins/discovery-gce/src/test/java/org/elasticsearch/discovery/gce/GceComputeServiceMock.java +++ b/plugins/discovery-gce/src/test/java/org/elasticsearch/discovery/gce/GceComputeServiceMock.java @@ -45,69 +45,13 @@ public class GceComputeServiceMock extends GceComputeServiceImpl { protected HttpTransport mockHttpTransport; - public GceComputeServiceMock(Settings settings, NetworkService networkService) { - super(settings, networkService); - this.mockHttpTransport = configureMock(); + public GceComputeServiceMock(Settings settings) { + super(settings); + this.mockHttpTransport = GceMockUtils.configureMock(); } @Override protected HttpTransport getGceHttpTransport() throws GeneralSecurityException, IOException { 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() { - @Override - public void handle(String s) { - sb.append(s); - } - }); - String response = sb.toString(); - return response; - } - } } diff --git a/plugins/discovery-gce/src/test/java/org/elasticsearch/discovery/gce/GceDiscoverTests.java b/plugins/discovery-gce/src/test/java/org/elasticsearch/discovery/gce/GceDiscoverTests.java index dbedbe1a6a9..bc2b025d42a 100644 --- a/plugins/discovery-gce/src/test/java/org/elasticsearch/discovery/gce/GceDiscoverTests.java +++ b/plugins/discovery-gce/src/test/java/org/elasticsearch/discovery/gce/GceDiscoverTests.java @@ -122,7 +122,7 @@ public class GceDiscoverTests extends ESIntegTestCase { httpServer = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress().getHostAddress(), 0), 0); httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext)); 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"); byte[] responseAsBytes = response.getBytes(StandardCharsets.UTF_8); s.sendResponseHeaders(200, responseAsBytes.length); diff --git a/plugins/discovery-gce/src/test/java/org/elasticsearch/discovery/gce/GceDiscoveryTests.java b/plugins/discovery-gce/src/test/java/org/elasticsearch/discovery/gce/GceDiscoveryTests.java index 3b265d6a067..a877ebf0c58 100644 --- a/plugins/discovery-gce/src/test/java/org/elasticsearch/discovery/gce/GceDiscoveryTests.java +++ b/plugins/discovery-gce/src/test/java/org/elasticsearch/discovery/gce/GceDiscoveryTests.java @@ -65,7 +65,6 @@ public class GceDiscoveryTests extends ESTestCase { protected static ThreadPool threadPool; protected MockTransportService transportService; - protected NetworkService networkService; protected GceComputeService mock; protected String projectName; @@ -96,11 +95,6 @@ public class GceDiscoveryTests extends ESTestCase { transportService = MockTransportService.local(Settings.EMPTY, Version.CURRENT, threadPool); } - @Before - public void createNetworkService() { - networkService = new NetworkService(Settings.EMPTY); - } - @After public void stopGceComputeService() { if (mock != null) { @@ -122,7 +116,7 @@ public class GceDiscoveryTests extends ESTestCase { .put(GceComputeService.PROJECT_SETTING.getKey(), projectName) .put(GceComputeService.ZONE_SETTING.getKey(), "europe-west1-b") .build(); - mock = new GceComputeServiceMock(nodeSettings, networkService); + mock = new GceComputeServiceMock(nodeSettings); List discoveryNodes = buildDynamicNodes(mock, nodeSettings); assertThat(discoveryNodes, hasSize(2)); } @@ -133,7 +127,7 @@ public class GceDiscoveryTests extends ESTestCase { .put(GceComputeService.ZONE_SETTING.getKey(), "europe-west1-b") .putArray(GceUnicastHostsProvider.TAGS_SETTING.getKey(), "elasticsearch") .build(); - mock = new GceComputeServiceMock(nodeSettings, networkService); + mock = new GceComputeServiceMock(nodeSettings); List discoveryNodes = buildDynamicNodes(mock, nodeSettings); assertThat(discoveryNodes, hasSize(1)); 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") .putArray(GceUnicastHostsProvider.TAGS_SETTING.getKey(), "elasticsearch", "dev") .build(); - mock = new GceComputeServiceMock(nodeSettings, networkService); + mock = new GceComputeServiceMock(nodeSettings); List discoveryNodes = buildDynamicNodes(mock, nodeSettings); assertThat(discoveryNodes, hasSize(1)); 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.ZONE_SETTING.getKey(), "europe-west1-b") .build(); - mock = new GceComputeServiceMock(nodeSettings, networkService); + mock = new GceComputeServiceMock(nodeSettings); List discoveryNodes = buildDynamicNodes(mock, nodeSettings); assertThat(discoveryNodes, hasSize(2)); } @@ -167,7 +161,7 @@ public class GceDiscoveryTests extends ESTestCase { .put(GceComputeService.ZONE_SETTING.getKey(), "europe-west1-b") .putArray(GceUnicastHostsProvider.TAGS_SETTING.getKey(), "elasticsearch") .build(); - mock = new GceComputeServiceMock(nodeSettings, networkService); + mock = new GceComputeServiceMock(nodeSettings); List discoveryNodes = buildDynamicNodes(mock, nodeSettings); assertThat(discoveryNodes, hasSize(2)); } @@ -178,7 +172,7 @@ public class GceDiscoveryTests extends ESTestCase { .put(GceComputeService.ZONE_SETTING.getKey(), "europe-west1-b") .putArray(GceUnicastHostsProvider.TAGS_SETTING.getKey(), "elasticsearch", "dev") .build(); - mock = new GceComputeServiceMock(nodeSettings, networkService); + mock = new GceComputeServiceMock(nodeSettings); List discoveryNodes = buildDynamicNodes(mock, nodeSettings); assertThat(discoveryNodes, hasSize(2)); } @@ -188,7 +182,7 @@ public class GceDiscoveryTests extends ESTestCase { .put(GceComputeService.PROJECT_SETTING.getKey(), projectName) .putArray(GceComputeService.ZONE_SETTING.getKey(), "us-central1-a", "europe-west1-b") .build(); - mock = new GceComputeServiceMock(nodeSettings, networkService); + mock = new GceComputeServiceMock(nodeSettings); List discoveryNodes = buildDynamicNodes(mock, nodeSettings); assertThat(discoveryNodes, hasSize(2)); } @@ -198,7 +192,7 @@ public class GceDiscoveryTests extends ESTestCase { .put(GceComputeService.PROJECT_SETTING.getKey(), projectName) .putArray(GceComputeService.ZONE_SETTING.getKey(), "us-central1-a", "europe-west1-b") .build(); - mock = new GceComputeServiceMock(nodeSettings, networkService); + mock = new GceComputeServiceMock(nodeSettings); List discoveryNodes = buildDynamicNodes(mock, nodeSettings); assertThat(discoveryNodes, hasSize(2)); } @@ -211,14 +205,14 @@ public class GceDiscoveryTests extends ESTestCase { .put(GceComputeService.PROJECT_SETTING.getKey(), projectName) .putArray(GceComputeService.ZONE_SETTING.getKey(), "us-central1-a", "us-central1-b") .build(); - mock = new GceComputeServiceMock(nodeSettings, networkService); + mock = new GceComputeServiceMock(nodeSettings); List discoveryNodes = buildDynamicNodes(mock, nodeSettings); assertThat(discoveryNodes, hasSize(0)); } public void testIllegalSettingsMissingAllRequired() { Settings nodeSettings = Settings.EMPTY; - mock = new GceComputeServiceMock(Settings.EMPTY, networkService); + mock = new GceComputeServiceMock(nodeSettings); try { buildDynamicNodes(mock, nodeSettings); fail("We expect an IllegalArgumentException for incomplete settings"); @@ -231,7 +225,7 @@ public class GceDiscoveryTests extends ESTestCase { Settings nodeSettings = Settings.builder() .putArray(GceComputeService.ZONE_SETTING.getKey(), "us-central1-a", "us-central1-b") .build(); - mock = new GceComputeServiceMock(nodeSettings, networkService); + mock = new GceComputeServiceMock(nodeSettings); try { buildDynamicNodes(mock, nodeSettings); fail("We expect an IllegalArgumentException for incomplete settings"); @@ -244,7 +238,7 @@ public class GceDiscoveryTests extends ESTestCase { Settings nodeSettings = Settings.builder() .put(GceComputeService.PROJECT_SETTING.getKey(), projectName) .build(); - mock = new GceComputeServiceMock(nodeSettings, networkService); + mock = new GceComputeServiceMock(nodeSettings); try { buildDynamicNodes(mock, nodeSettings); fail("We expect an IllegalArgumentException for incomplete settings"); diff --git a/plugins/discovery-gce/src/test/java/org/elasticsearch/discovery/gce/GceMetadataServiceMock.java b/plugins/discovery-gce/src/test/java/org/elasticsearch/discovery/gce/GceMetadataServiceMock.java new file mode 100644 index 00000000000..37293ef7423 --- /dev/null +++ b/plugins/discovery-gce/src/test/java/org/elasticsearch/discovery/gce/GceMetadataServiceMock.java @@ -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; + } +} diff --git a/plugins/discovery-gce/src/test/java/org/elasticsearch/discovery/gce/GceMockUtils.java b/plugins/discovery-gce/src/test/java/org/elasticsearch/discovery/gce/GceMockUtils.java new file mode 100644 index 00000000000..986859c7592 --- /dev/null +++ b/plugins/discovery-gce/src/test/java/org/elasticsearch/discovery/gce/GceMockUtils.java @@ -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() { + @Override + public void handle(String s) { + sb.append(s); + } + }); + String response = sb.toString(); + return response; + } + } +} diff --git a/plugins/discovery-gce/src/test/java/org/elasticsearch/discovery/gce/GceNetworkTests.java b/plugins/discovery-gce/src/test/java/org/elasticsearch/discovery/gce/GceNetworkTests.java index c09e51fe1ef..d07fab5711d 100644 --- a/plugins/discovery-gce/src/test/java/org/elasticsearch/discovery/gce/GceNetworkTests.java +++ b/plugins/discovery-gce/src/test/java/org/elasticsearch/discovery/gce/GceNetworkTests.java @@ -104,7 +104,7 @@ public class GceNetworkTests extends ESTestCase { .build(); NetworkService networkService = new NetworkService(nodeSettings); - GceComputeServiceMock mock = new GceComputeServiceMock(nodeSettings, networkService); + GceMetadataServiceMock mock = new GceMetadataServiceMock(nodeSettings, networkService); networkService.addCustomNameResolver(new GceNameResolver(nodeSettings, mock)); try { InetAddress[] addresses = networkService.resolveBindHostAddresses(null);