[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:
parent
c6fcb60071
commit
a7f62ee902
|
@ -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`::
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
group = "${group}.plugins.discovery-gce.qa"
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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} }
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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(
|
||||
// Register GCE settings
|
||||
GceInstancesService.PROJECT_SETTING,
|
||||
GceInstancesService.ZONE_SETTING,
|
||||
GceUnicastHostsProvider.TAGS_SETTING,
|
||||
GceInstancesService.REFRESH_SETTING,
|
||||
GceInstancesService.RETRY_SETTING,
|
||||
GceInstancesService.MAX_WAIT_SETTING);
|
||||
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)
|
||||
);
|
||||
|
||||
if (ALLOW_REROUTE_GCE_SETTINGS) {
|
||||
settings.add(GceMetadataService.GCE_HOST);
|
||||
settings.add(GceInstancesServiceImpl.GCE_ROOT_URL);
|
||||
}
|
||||
return Collections.unmodifiableList(settings);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
europe-west1-b
|
|
@ -0,0 +1 @@
|
|||
metadataserver
|
Loading…
Reference in New Issue