[GCE Discovery] Automatically set project-id and zone (#33721)

Fetch default values for project-id and zone from metadata server

Closes #13618
This commit is contained in:
Vladimir Dolzhenko 2018-10-03 11:37:36 +02:00 committed by GitHub
parent c6fcb60071
commit a7f62ee902
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 635 additions and 19 deletions

View File

@ -26,12 +26,17 @@ The following gce settings (prefixed with `cloud.gce`) are supported:
`project_id`::
Your Google project id (mandatory).
Your Google project id.
By default the project id will be derived from the instance metadata.
Note: Deriving the project id from system properties or environment variables
(`GOOGLE_CLOUD_PROJECT` or `GCLOUD_PROJECT`) is not supported.
`zone`::
helps to retrieve instances running in a given zone (mandatory). It should be one of the
https://developers.google.com/compute/docs/zones#available[GCE supported zones].
helps to retrieve instances running in a given zone.
It should be one of the https://developers.google.com/compute/docs/zones#available[GCE supported zones].
By default the zone will be derived from the instance metadata.
See also <<discovery-gce-usage-zones>>.
`retry`::

View File

@ -26,6 +26,11 @@ dependencyLicenses {
mapping from: /google-.*/, to: 'google'
}
check {
// also execute the QA tests when testing the plugin
dependsOn 'qa:gce:check'
}
test {
// this is needed for insecure plugins, remove if possible!
systemProperty 'tests.artifact', project.name

View File

@ -0,0 +1 @@
group = "${group}.plugins.discovery-gce.qa"

View File

@ -0,0 +1,80 @@
/*
* 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.
*/
import org.elasticsearch.gradle.MavenFilteringHack
import org.elasticsearch.gradle.test.AntFixture
apply plugin: 'elasticsearch.standalone-rest-test'
apply plugin: 'elasticsearch.rest-test'
final int gceNumberOfNodes = 3
File gceDiscoveryFile = new File(project.buildDir, 'generated-resources/nodes.uri')
dependencies {
testCompile project(path: ':plugins:discovery-gce', configuration: 'runtime')
}
/** A task to start the GCEFixture which emulates a GCE service **/
task gceFixture(type: AntFixture) {
dependsOn compileTestJava
env 'CLASSPATH', "${ -> project.sourceSets.test.runtimeClasspath.asPath }"
executable = new File(project.runtimeJavaHome, 'bin/java')
args 'org.elasticsearch.cloud.gce.GCEFixture', baseDir, gceDiscoveryFile.getAbsolutePath()
}
Map<String, Object> expansions = [
'expected_nodes': gceNumberOfNodes
]
processTestResources {
inputs.properties(expansions)
MavenFilteringHack.filter(it, expansions)
}
integTestCluster {
dependsOn gceFixture
numNodes = gceNumberOfNodes
plugin ':plugins:discovery-gce'
setting 'discovery.zen.hosts_provider', 'gce'
// use gce fixture for Auth calls instead of http://metadata.google.internal
integTestCluster.environment 'GCE_METADATA_HOST', "http://${-> gceFixture.addressAndPort}"
// allows to configure hidden settings (`cloud.gce.host` and `cloud.gce.root_url`)
systemProperty 'es.allow_reroute_gce_settings', 'true'
// use gce fixture for metadata server calls instead of http://metadata.google.internal
setting 'cloud.gce.host', "http://${-> gceFixture.addressAndPort}"
// use gce fixture for API calls instead of https://www.googleapis.com
setting 'cloud.gce.root_url', "http://${-> gceFixture.addressAndPort}"
unicastTransportUri = { seedNode, node, ant -> return null }
waitCondition = { node, ant ->
gceDiscoveryFile.parentFile.mkdirs()
gceDiscoveryFile.setText(integTest.nodes.collect { n -> "${n.transportUri()}" }.join('\n'), 'UTF-8')
File tmpFile = new File(node.cwd, 'wait.success')
ant.get(src: "http://${node.httpUri()}/",
dest: tmpFile.toString(),
ignoreerrors: true,
retries: 10)
return tmpFile.exists()
}
}

View File

@ -0,0 +1,37 @@
/*
* 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.carrotsearch.randomizedtesting.annotations.Name;
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate;
import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase;
public class GCEDiscoveryClientYamlTestSuiteIT extends ESClientYamlSuiteTestCase {
public GCEDiscoveryClientYamlTestSuiteIT(@Name("yaml") ClientYamlTestCandidate testCandidate) {
super(testCandidate);
}
@ParametersFactory
public static Iterable<Object[]> parameters() throws Exception {
return ESClientYamlSuiteTestCase.createParameters();
}
}

View File

@ -0,0 +1,214 @@
/*
* 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.apache.http.client.methods.HttpGet;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.path.PathTrie;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.rest.RestUtils;
import org.elasticsearch.test.fixture.AbstractHttpFixture;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
/**
* {@link GCEFixture} is a fixture that emulates a GCE service.
*/
public class GCEFixture extends AbstractHttpFixture {
public static final String PROJECT_ID = "discovery-gce-test";
public static final String ZONE = "test-zone";
public static final String TOKEN = "1/fFAGRNJru1FTz70BzhT3Zg";
public static final String TOKEN_TYPE = "Bearer";
private final PathTrie<RequestHandler> handlers;
private final Path nodes;
private GCEFixture(final String workingDir, final String nodesUriPath) {
super(workingDir);
this.nodes = toPath(Objects.requireNonNull(nodesUriPath));
this.handlers = defaultHandlers();
}
public static void main(String[] args) throws Exception {
if (args == null || args.length != 2) {
throw new IllegalArgumentException("GCEFixture <working directory> <nodes transport uri file>");
}
final GCEFixture fixture = new GCEFixture(args[0], args[1]);
fixture.listen();
}
private static String nonAuthPath(Request request) {
return nonAuthPath(request.getMethod(), request.getPath());
}
private static String nonAuthPath(String method, String path) {
return "NONAUTH " + method + " " + path;
}
private static String authPath(Request request) {
return authPath(request.getMethod(), request.getPath());
}
private static String authPath(String method, String path) {
return "AUTH " + method + " " + path;
}
/** Builds the default request handlers **/
private PathTrie<RequestHandler> defaultHandlers() {
final PathTrie<RequestHandler> handlers = new PathTrie<>(RestUtils.REST_DECODER);
final Consumer<Map<String, String>> commonHeaderConsumer = headers -> headers.put("Metadata-Flavor", "Google");
final Function<String, Response> simpleValue = value -> {
final Map<String, String> headers = new HashMap<>(TEXT_PLAIN_CONTENT_TYPE);
commonHeaderConsumer.accept(headers);
final byte[] responseAsBytes = value.getBytes(StandardCharsets.UTF_8);
return new Response(RestStatus.OK.getStatus(), headers, responseAsBytes);
};
final Function<String, Response> jsonValue = value -> {
final Map<String, String> headers = new HashMap<>(JSON_CONTENT_TYPE);
commonHeaderConsumer.accept(headers);
final byte[] responseAsBytes = value.getBytes(StandardCharsets.UTF_8);
return new Response(RestStatus.OK.getStatus(), headers, responseAsBytes);
};
// https://cloud.google.com/compute/docs/storing-retrieving-metadata
handlers.insert(nonAuthPath(HttpGet.METHOD_NAME, "/computeMetadata/v1/project/project-id"),
request -> simpleValue.apply(PROJECT_ID));
handlers.insert(nonAuthPath(HttpGet.METHOD_NAME, "/computeMetadata/v1/project/attributes/google-compute-default-zone"),
request -> simpleValue.apply(ZONE));
// https://cloud.google.com/compute/docs/access/create-enable-service-accounts-for-instances
handlers.insert(nonAuthPath(HttpGet.METHOD_NAME, "/computeMetadata/v1/instance/service-accounts/default/token"),
request -> jsonValue.apply(Strings.toString(jsonBuilder()
.startObject()
.field("access_token", TOKEN)
.field("expires_in", TimeUnit.HOURS.toSeconds(1))
.field("token_type", TOKEN_TYPE)
.endObject())));
// https://cloud.google.com/compute/docs/reference/rest/v1/instances
handlers.insert(authPath(HttpGet.METHOD_NAME, "/compute/v1/projects/{project}/zones/{zone}/instances"),
request -> {
final List items = new ArrayList();
int count = 0;
for (String address : Files.readAllLines(nodes)) {
count++;
items.add(MapBuilder.<String, Object>newMapBuilder()
.put("id", Long.toString(9309873766405L + count))
.put("description", "ES node" + count)
.put("name", "test" + count)
.put("kind", "compute#instance")
.put("machineType", "n1-standard-1")
.put("networkInterfaces",
Collections.singletonList(MapBuilder.<String, Object>newMapBuilder()
.put("accessConfigs", Collections.emptyList())
.put("name", "nic0")
.put("network", "default")
.put("networkIP", address)
.immutableMap()))
.put("status", "RUNNING")
.put("zone", ZONE)
.immutableMap());
}
final String json = Strings.toString(jsonBuilder()
.startObject()
.field("id", "test-instances")
.field("items", items)
.endObject());
final byte[] responseAsBytes = json.getBytes(StandardCharsets.UTF_8);
final Map<String, String> headers = new HashMap<>(JSON_CONTENT_TYPE);
commonHeaderConsumer.accept(headers);
return new Response(RestStatus.OK.getStatus(), headers, responseAsBytes);
});
return handlers;
}
@Override
protected Response handle(final Request request) throws IOException {
final String nonAuthorizedPath = nonAuthPath(request);
final RequestHandler nonAuthorizedHandler = handlers.retrieve(nonAuthorizedPath, request.getParameters());
if (nonAuthorizedHandler != null) {
return nonAuthorizedHandler.handle(request);
}
final String authorizedPath = authPath(request);
final RequestHandler authorizedHandler = handlers.retrieve(authorizedPath, request.getParameters());
if (authorizedHandler != null) {
final String authorization = request.getHeader("Authorization");
if ((TOKEN_TYPE + " " + TOKEN).equals(authorization) == false) {
return newError(RestStatus.UNAUTHORIZED, "Authorization", "Login Required");
}
return authorizedHandler.handle(request);
}
return null;
}
private static Response newError(final RestStatus status, final String code, final String message) throws IOException {
final String response = Strings.toString(jsonBuilder()
.startObject()
.field("error", MapBuilder.<String, Object>newMapBuilder()
.put("errors", Collections.singletonList(
MapBuilder.<String, Object>newMapBuilder()
.put("domain", "global")
.put("reason", "required")
.put("message", message)
.put("locationType", "header")
.put("location", code)
.immutableMap()
))
.put("code", status.getStatus())
.put("message", message)
.immutableMap())
.endObject());
return new Response(status.getStatus(), JSON_CONTENT_TYPE, response.getBytes(UTF_8));
}
@SuppressForbidden(reason = "Paths#get is fine - we don't have environment here")
private static Path toPath(final String dir) {
return Paths.get(dir);
}
}

View File

@ -0,0 +1,15 @@
# Integration tests for discovery-gce
setup:
- do:
cluster.health:
wait_for_status: green
wait_for_nodes: ${expected_nodes}
---
"All nodes are correctly discovered":
- do:
nodes.info:
metric: [ transport ]
- match: { _nodes.total: ${expected_nodes} }

View File

@ -75,4 +75,8 @@ public interface GceInstancesService extends Closeable {
* @return a collection of running instances within the same GCE project
*/
Collection<Instance> instances();
String projectId();
List<String> zones();
}

View File

@ -29,6 +29,11 @@ import java.util.function.Function;
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.HttpRequest;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
@ -103,9 +108,58 @@ public class GceInstancesServiceImpl extends AbstractComponent implements GceIns
public GceInstancesServiceImpl(Settings settings) {
super(settings);
this.project = PROJECT_SETTING.get(settings);
this.zones = ZONE_SETTING.get(settings);
this.validateCerts = GCE_VALIDATE_CERTIFICATES.get(settings);
this.project = resolveProject();
this.zones = resolveZones();
}
private String resolveProject() {
if (PROJECT_SETTING.exists(settings)) {
return PROJECT_SETTING.get(settings);
}
try {
// this code is based on a private GCE method: {@link com.google.cloud.ServiceOptions#getAppEngineProjectIdFromMetadataServer()}
return getAppEngineValueFromMetadataServer("/computeMetadata/v1/project/project-id");
} catch (Exception e) {
logger.warn("unable to resolve project from metadata server for GCE discovery service", e);
}
return null;
}
private List<String> resolveZones() {
if (ZONE_SETTING.exists(settings)) {
return ZONE_SETTING.get(settings);
}
try {
final String defaultZone =
getAppEngineValueFromMetadataServer("/computeMetadata/v1/project/attributes/google-compute-default-zone");
return Collections.singletonList(defaultZone);
} catch (Exception e) {
logger.warn("unable to resolve default zone from metadata server for GCE discovery service", e);
}
return null;
}
String getAppEngineValueFromMetadataServer(String serviceURL) throws GeneralSecurityException, IOException {
String metadata = GceMetadataService.GCE_HOST.get(settings);
GenericUrl url = Access.doPrivileged(() -> new GenericUrl(metadata + serviceURL));
HttpTransport httpTransport = getGceHttpTransport();
HttpRequestFactory requestFactory = httpTransport.createRequestFactory();
HttpRequest request = requestFactory.buildGetRequest(url)
.setConnectTimeout(500)
.setReadTimeout(500)
.setHeaders(new HttpHeaders().set("Metadata-Flavor", "Google"));
HttpResponse response = Access.doPrivilegedIOException(() -> request.execute());
return headerContainsMetadataFlavor(response) ? response.parseAsString() : null;
}
private static boolean headerContainsMetadataFlavor(HttpResponse response) {
// com.google.cloud.ServiceOptions#headerContainsMetadataFlavor(HttpResponse)}
String metadataFlavorValue = response.getHeaders().getFirstHeaderStringValue("Metadata-Flavor");
return "Google".equals(metadataFlavorValue);
}
protected synchronized HttpTransport getGceHttpTransport() throws GeneralSecurityException, IOException {
@ -180,6 +234,16 @@ public class GceInstancesServiceImpl extends AbstractComponent implements GceIns
return this.client;
}
@Override
public String projectId() {
return project;
}
@Override
public List<String> zones() {
return zones;
}
@Override
public void close() throws IOException {
if (gceHttpTransport != null) {

View File

@ -79,8 +79,8 @@ public class GceUnicastHostsProvider extends AbstractComponent implements Unicas
this.networkService = networkService;
this.refreshInterval = GceInstancesService.REFRESH_SETTING.get(settings);
this.project = GceInstancesService.PROJECT_SETTING.get(settings);
this.zones = GceInstancesService.ZONE_SETTING.get(settings);
this.project = gceInstancesService.projectId();
this.zones = gceInstancesService.zones();
this.tags = TAGS_SETTING.get(settings);
if (logger.isDebugEnabled()) {

View File

@ -22,6 +22,7 @@ package org.elasticsearch.plugin.discovery.gce;
import com.google.api.client.http.HttpHeaders;
import com.google.api.client.util.ClassInfo;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.common.Booleans;
import org.elasticsearch.core.internal.io.IOUtils;
import org.apache.lucene.util.SetOnce;
import org.elasticsearch.cloud.gce.GceInstancesService;
@ -41,6 +42,7 @@ import org.elasticsearch.transport.TransportService;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@ -49,8 +51,12 @@ import java.util.function.Supplier;
public class GceDiscoveryPlugin extends Plugin implements DiscoveryPlugin, Closeable {
/** Determines whether settings those reroutes GCE call should be allowed (for testing purposes only). */
private static final boolean ALLOW_REROUTE_GCE_SETTINGS =
Booleans.parseBoolean(System.getProperty("es.allow_reroute_gce_settings", "false"));
public static final String GCE = "gce";
private final Settings settings;
protected final Settings settings;
private static final Logger logger = Loggers.getLogger(GceDiscoveryPlugin.class);
// stashed when created in order to properly close
private final SetOnce<GceInstancesService> gceInstancesService = new SetOnce<>();
@ -94,14 +100,22 @@ public class GceDiscoveryPlugin extends Plugin implements DiscoveryPlugin, Close
@Override
public List<Setting<?>> getSettings() {
return Arrays.asList(
List<Setting<?>> settings = new ArrayList<>(
Arrays.asList(
// Register GCE settings
GceInstancesService.PROJECT_SETTING,
GceInstancesService.ZONE_SETTING,
GceUnicastHostsProvider.TAGS_SETTING,
GceInstancesService.REFRESH_SETTING,
GceInstancesService.RETRY_SETTING,
GceInstancesService.MAX_WAIT_SETTING);
GceInstancesService.MAX_WAIT_SETTING)
);
if (ALLOW_REROUTE_GCE_SETTINGS) {
settings.add(GceMetadataService.GCE_HOST);
settings.add(GceInstancesServiceImpl.GCE_ROOT_URL);
}
return Collections.unmodifiableList(settings);
}

View File

@ -0,0 +1,72 @@
/*
* 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.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.common.settings.Settings;
import org.elasticsearch.test.ESTestCase;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.core.Is.is;
public class GceInstancesServiceImplTests extends ESTestCase {
public void testHeaderContainsMetadataFlavor() throws Exception {
final AtomicBoolean addMetdataFlavor = new AtomicBoolean();
final MockHttpTransport transport = new MockHttpTransport() {
@Override
public LowLevelHttpRequest buildRequest(String method, final String url) {
return new MockLowLevelHttpRequest() {
@Override
public LowLevelHttpResponse execute() {
MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
response.setStatusCode(200);
response.setContentType(Json.MEDIA_TYPE);
response.setContent("value");
if (addMetdataFlavor.get()) {
response.addHeader("Metadata-Flavor", "Google");
}
return response;
}
};
}
};
final GceInstancesServiceImpl service = new GceInstancesServiceImpl(Settings.EMPTY) {
@Override
protected synchronized HttpTransport getGceHttpTransport() {
return transport;
}
};
final String serviceURL = "/computeMetadata/v1/project/project-id";
assertThat(service.getAppEngineValueFromMetadataServer(serviceURL), is(nullValue()));
addMetdataFlavor.set(true);
assertThat(service.getAppEngineValueFromMetadataServer(serviceURL), is("value"));
}
}

View File

@ -170,6 +170,16 @@ public class GceDiscoverTests extends ESIntegTestCase {
});
}
@Override
public String projectId() {
return PROJECT_SETTING.get(settings);
}
@Override
public List<String> zones() {
return ZONE_SETTING.get(settings);
}
@Override
public void close() throws IOException {
}

View File

@ -21,6 +21,7 @@ package org.elasticsearch.discovery.gce;
import org.elasticsearch.Version;
import org.elasticsearch.cloud.gce.GceInstancesServiceImpl;
import org.elasticsearch.cloud.gce.GceMetadataService;
import org.elasticsearch.common.network.NetworkService;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.TransportAddress;
@ -40,6 +41,7 @@ import java.util.Locale;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.not;
/**
* This test class uses a GCE HTTP Mock system which allows to simulate JSON Responses.
@ -211,7 +213,10 @@ public class GceDiscoveryTests extends ESTestCase {
}
public void testIllegalSettingsMissingAllRequired() {
Settings nodeSettings = Settings.EMPTY;
Settings nodeSettings = Settings.builder()
// to prevent being resolved using default GCE host
.put(GceMetadataService.GCE_HOST.getKey(), "http://internal")
.build();
mock = new GceInstancesServiceMock(nodeSettings);
try {
buildDynamicNodes(mock, nodeSettings);
@ -223,6 +228,8 @@ public class GceDiscoveryTests extends ESTestCase {
public void testIllegalSettingsMissingProject() {
Settings nodeSettings = Settings.builder()
// to prevent being resolved using default GCE host
.put(GceMetadataService.GCE_HOST.getKey(), "http://internal")
.putList(GceInstancesServiceImpl.ZONE_SETTING.getKey(), "us-central1-a", "us-central1-b")
.build();
mock = new GceInstancesServiceMock(nodeSettings);
@ -236,6 +243,8 @@ public class GceDiscoveryTests extends ESTestCase {
public void testIllegalSettingsMissingZone() {
Settings nodeSettings = Settings.builder()
// to prevent being resolved using default GCE host
.put(GceMetadataService.GCE_HOST.getKey(), "http://internal")
.put(GceInstancesServiceImpl.PROJECT_SETTING.getKey(), projectName)
.build();
mock = new GceInstancesServiceMock(nodeSettings);
@ -261,4 +270,13 @@ public class GceDiscoveryTests extends ESTestCase {
List<TransportAddress> dynamicHosts = buildDynamicNodes(mock, nodeSettings);
assertThat(dynamicHosts, hasSize(1));
}
public void testMetadataServerValues() {
Settings nodeSettings = Settings.EMPTY;
mock = new GceInstancesServiceMock(nodeSettings);
assertThat(mock.projectId(), not(projectName));
List<TransportAddress> dynamicHosts = buildDynamicNodes(mock, nodeSettings);
assertThat(dynamicHosts, hasSize(1));
}
}

View File

@ -32,11 +32,13 @@ public class GceInstancesServiceMock extends GceInstancesServiceImpl {
public GceInstancesServiceMock(Settings settings) {
super(settings);
this.mockHttpTransport = GceMockUtils.configureMock();
}
@Override
protected HttpTransport getGceHttpTransport() throws GeneralSecurityException, IOException {
if (this.mockHttpTransport == null) {
this.mockHttpTransport = GceMockUtils.configureMock();
}
return this.mockHttpTransport;
}
}

View File

@ -39,7 +39,7 @@ import java.net.URL;
public class GceMockUtils {
protected static final Logger logger = Loggers.getLogger(GceMockUtils.class);
public static final String GCE_METADATA_URL = "http://metadata.google.internal/computeMetadata/v1/instance";
public static final String GCE_METADATA_URL = "http://metadata.google.internal/computeMetadata/v1/";
protected static HttpTransport configureMock() {
return new MockHttpTransport() {
@ -54,6 +54,7 @@ public class GceMockUtils {
if (url.startsWith(GCE_METADATA_URL)) {
logger.info("--> Simulate GCE Auth/Metadata response for [{}]", url);
response.setContent(readGoogleInternalJsonResponse(url));
response.addHeader("Metadata-Flavor", "Google");
} else {
logger.info("--> Simulate GCE API response for [{}]", url);
response.setContent(readGoogleApiJsonResponse(url));

View File

@ -0,0 +1,36 @@
{
"id": "dummy",
"items":[
{
"description": "ES Node 1",
"id": "9309873766428965105",
"kind": "compute#instance",
"machineType": "n1-standard-1",
"name": "test1",
"networkInterfaces": [
{
"accessConfigs": [
{
"kind": "compute#accessConfig",
"name": "External NAT",
"natIP": "104.155.13.147",
"type": "ONE_TO_ONE_NAT"
}
],
"name": "nic0",
"network": "default",
"networkIP": "10.240.79.59"
}
],
"status": "RUNNING",
"tags": {
"fingerprint": "xA6QJb-rGtg=",
"items": [
"elasticsearch",
"dev"
]
},
"zone": "europe-west1-b"
}
]
}

View File

@ -0,0 +1,36 @@
{
"id": "dummy",
"items":[
{
"description": "ES Node 2",
"id": "9309873766428965105",
"kind": "compute#instance",
"machineType": "n1-standard-1",
"name": "test2",
"networkInterfaces": [
{
"accessConfigs": [
{
"kind": "compute#accessConfig",
"name": "External NAT",
"natIP": "104.155.13.147",
"type": "ONE_TO_ONE_NAT"
}
],
"name": "nic0",
"network": "default",
"networkIP": "10.240.79.59"
}
],
"status": "RUNNING",
"tags": {
"fingerprint": "xA6QJb-rGtg=",
"items": [
"elasticsearch",
"dev"
]
},
"zone": "us-central1-a"
}
]
}