Skip to content

Commit 82ae674

Browse files
Merge branch 'master' into feature/vector-tiles
2 parents 8ae6cf4 + 1892489 commit 82ae674

File tree

200 files changed

+7144
-2560
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

200 files changed

+7144
-2560
lines changed

build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchTestBasePlugin.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public void apply(Project project) {
4848
File heapdumpDir = new File(project.getBuildDir(), "heapdump");
4949

5050
project.getTasks().withType(Test.class).configureEach(test -> {
51-
File testOutputDir = new File(test.getReports().getJunitXml().getDestination(), "output");
51+
File testOutputDir = new File(test.getReports().getJunitXml().getOutputLocation().getAsFile().get(), "output");
5252

5353
ErrorReportingTestListener listener = new ErrorReportingTestListener(test.getTestLogging(), test.getLogger(), testOutputDir);
5454
test.getExtensions().add("errorReportingTestListener", listener);

build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/precommit/CheckstylePrecommitPlugin.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ public void execute(Task task) {
9494

9595
project.getTasks().withType(Checkstyle.class).configureEach(t -> {
9696
t.dependsOn(copyCheckstyleConf);
97-
t.reports(r -> r.getHtml().setEnabled(false));
97+
t.reports(r -> r.getHtml().getRequired().set(false));
9898
});
9999

100100
return checkstyleTask;

build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/precommit/LoggerUsageTask.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.elasticsearch.gradle.internal.conventions.precommit.PrecommitTask;
1313
import org.gradle.api.file.FileCollection;
1414
import org.gradle.api.plugins.JavaPluginConvention;
15+
import org.gradle.api.plugins.JavaPluginExtension;
1516
import org.gradle.api.tasks.CacheableTask;
1617
import org.gradle.api.tasks.Classpath;
1718
import org.gradle.api.tasks.InputFiles;
@@ -43,7 +44,7 @@ public LoggerUsageTask(ExecOperations execOperations) {
4344
@TaskAction
4445
public void runLoggerUsageTask() {
4546
LoggedExec.javaexec(execOperations, spec -> {
46-
spec.setMain("org.elasticsearch.test.loggerusage.ESLoggerUsageChecker");
47+
spec.getMainClass().set("org.elasticsearch.test.loggerusage.ESLoggerUsageChecker");
4748
spec.classpath(getClasspath());
4849
getClassDirectories().forEach(spec::args);
4950
});
@@ -62,8 +63,7 @@ public void setClasspath(FileCollection classpath) {
6263
@PathSensitive(PathSensitivity.RELATIVE)
6364
@SkipWhenEmpty
6465
public FileCollection getClassDirectories() {
65-
return getProject().getConvention()
66-
.getPlugin(JavaPluginConvention.class)
66+
return getProject().getExtensions().getByType(JavaPluginExtension.class)
6767
.getSourceSets()
6868
.stream()
6969
// Don't pick up all source sets like the java9 ones as logger-check doesn't support the class format

build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/precommit/ThirdPartyAuditTask.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ private String runForbiddenAPIsCli() throws IOException {
334334
getProject().getConfigurations().getByName(CompileOnlyResolvePlugin.RESOLVEABLE_COMPILE_ONLY_CONFIGURATION_NAME)
335335
);
336336
spec.jvmArgs("-Xmx1g");
337-
spec.setMain("de.thetaphi.forbiddenapis.cli.CliMain");
337+
spec.getMainClass().set("de.thetaphi.forbiddenapis.cli.CliMain");
338338
spec.args("-f", getSignatureFile().getAbsolutePath(), "-d", getJarExpandDir(), "--allowmissingclasses");
339339
spec.setErrorOutput(errorOut);
340340
if (getLogger().isInfoEnabled() == false) {
@@ -364,7 +364,7 @@ private Set<String> runJdkJarHellCheck() throws IOException {
364364
getProject().getConfigurations().getByName(CompileOnlyResolvePlugin.RESOLVEABLE_COMPILE_ONLY_CONFIGURATION_NAME)
365365
);
366366

367-
spec.setMain(JDK_JAR_HELL_MAIN_CLASS);
367+
spec.getMainClass().set(JDK_JAR_HELL_MAIN_CLASS);
368368
spec.args(getJarExpandDir());
369369
spec.setIgnoreExitValue(true);
370370
if (javaHome != null) {
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
7.0.2
1+
7.1

build-tools/src/main/java/org/elasticsearch/gradle/LoggedExec.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ public LoggedExec(FileSystemOperations fileSystemOperations) {
5555
doLast(new Action<Task>() {
5656
@Override
5757
public void execute(Task task) {
58-
if (LoggedExec.this.getExecResult().getExitValue() != 0) {
58+
int exitValue = LoggedExec.this.getExecutionResult().get().getExitValue();
59+
if (exitValue != 0) {
5960
try {
6061
LoggedExec.this.getLogger().error("Output for " + LoggedExec.this.getExecutable() + ":");
6162
outputLogger.accept(LoggedExec.this.getLogger());
@@ -67,7 +68,7 @@ public void execute(Task task) {
6768
"Process '%s %s' finished with non-zero exit value %d",
6869
LoggedExec.this.getExecutable(),
6970
LoggedExec.this.getArgs(),
70-
LoggedExec.this.getExecResult().getExitValue()
71+
exitValue
7172
)
7273
);
7374
}

build-tools/src/main/java/org/elasticsearch/gradle/jarhell/JarHellTask.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public File getSuccessMarker() {
5454
public void runJarHellCheck() throws IOException{
5555
LoggedExec.javaexec(execOperations, spec -> {
5656
spec.environment("CLASSPATH", getJarHellRuntimeClasspath().plus(getClasspath()).getAsPath());
57-
spec.setMain("org.elasticsearch.jdk.JarHell");
57+
spec.getMainClass().set("org.elasticsearch.jdk.JarHell");
5858
});
5959
writeMarker();
6060
}

build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ tasks.named("wrapper").configure {
345345
println "Added checksum to wrapper properties"
346346
// Update build-tools to reflect the Gradle upgrade
347347
// TODO: we can remove this once we have tests to make sure older versions work.
348-
project(':build-tools').file('src/main/resources/minimumGradleVersion').text = gradleVersion
348+
project.file('build-tools-internal/src/main/resources/minimumGradleVersion').text = gradleVersion
349349
println "Updated minimum Gradle Version"
350350
}
351351
}

client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java

+216-4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
package org.elasticsearch.client;
1010

1111
import org.apache.http.HttpEntity;
12+
import org.apache.logging.log4j.LogManager;
13+
import org.apache.logging.log4j.Logger;
14+
import org.elasticsearch.Build;
1215
import org.elasticsearch.ElasticsearchException;
1316
import org.elasticsearch.ElasticsearchStatusException;
1417
import org.elasticsearch.action.ActionListener;
@@ -64,9 +67,12 @@
6467
import org.elasticsearch.client.core.TermVectorsRequest;
6568
import org.elasticsearch.client.core.TermVectorsResponse;
6669
import org.elasticsearch.client.tasks.TaskSubmissionResponse;
70+
import org.elasticsearch.common.util.concurrent.FutureUtils;
6771
import org.elasticsearch.core.CheckedConsumer;
6872
import org.elasticsearch.core.CheckedFunction;
6973
import org.elasticsearch.common.xcontent.ParseField;
74+
import org.elasticsearch.common.Strings;
75+
import org.elasticsearch.common.util.concurrent.ListenableFuture;
7076
import org.elasticsearch.common.xcontent.ContextParser;
7177
import org.elasticsearch.common.xcontent.DeprecationHandler;
7278
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
@@ -205,6 +211,8 @@
205211
import java.util.Optional;
206212
import java.util.ServiceLoader;
207213
import java.util.Set;
214+
import java.util.concurrent.CompletableFuture;
215+
import java.util.concurrent.ExecutionException;
208216
import java.util.function.Function;
209217
import java.util.stream.Collectors;
210218
import java.util.stream.Stream;
@@ -244,10 +252,16 @@
244252
*/
245253
public class RestHighLevelClient implements Closeable {
246254

255+
private static final Logger logger = LogManager.getLogger(RestHighLevelClient.class);
256+
257+
// To be called using performClientRequest and performClientRequestAsync to ensure version compatibility check
247258
private final RestClient client;
248259
private final NamedXContentRegistry registry;
249260
private final CheckedConsumer<RestClient, IOException> doClose;
250261

262+
/** Do not access directly but through getVersionValidationFuture() */
263+
private volatile ListenableFuture<Optional<String>> versionValidationFuture;
264+
251265
private final IndicesClient indicesClient = new IndicesClient(this);
252266
private final ClusterClient clusterClient = new ClusterClient(this);
253267
private final IngestClient ingestClient = new IngestClient(this);
@@ -1715,7 +1729,7 @@ private <Req, Resp> Resp internalPerformRequest(Req request,
17151729
req.setOptions(options);
17161730
Response response;
17171731
try {
1718-
response = client.performRequest(req);
1732+
response = performClientRequest(req);
17191733
} catch (ResponseException e) {
17201734
if (ignores.contains(e.getResponse().getStatusLine().getStatusCode())) {
17211735
try {
@@ -1755,7 +1769,7 @@ protected final <Req extends Validatable, Resp> Optional<Resp> performRequestAnd
17551769
req.setOptions(options);
17561770
Response response;
17571771
try {
1758-
response = client.performRequest(req);
1772+
response = performClientRequest(req);
17591773
} catch (ResponseException e) {
17601774
if (RestStatus.NOT_FOUND.getStatus() == e.getResponse().getStatusLine().getStatusCode()) {
17611775
return Optional.empty();
@@ -1854,7 +1868,7 @@ private <Req, Resp> Cancellable internalPerformRequestAsync(Req request,
18541868
req.setOptions(options);
18551869

18561870
ResponseListener responseListener = wrapResponseListener(responseConverter, listener, ignores);
1857-
return client.performRequestAsync(req, responseListener);
1871+
return performClientRequestAsync(req, responseListener);
18581872
}
18591873

18601874

@@ -1920,7 +1934,7 @@ protected final <Req extends Validatable, Resp> Cancellable performRequestAsyncA
19201934
req.setOptions(options);
19211935
ResponseListener responseListener = wrapResponseListener404sOptional(response -> parseEntity(response.getEntity(),
19221936
entityParser), listener);
1923-
return client.performRequestAsync(req, responseListener);
1937+
return performClientRequestAsync(req, responseListener);
19241938
}
19251939

19261940
final <Resp> ResponseListener wrapResponseListener404sOptional(CheckedFunction<Response, Resp, IOException> responseConverter,
@@ -2002,6 +2016,204 @@ protected static boolean convertExistsResponse(Response response) {
20022016
return response.getStatusLine().getStatusCode() == 200;
20032017
}
20042018

2019+
private Cancellable performClientRequestAsync(Request request, ResponseListener listener) {
2020+
2021+
ListenableFuture<Optional<String>> versionCheck = getVersionValidationFuture();
2022+
2023+
// Create a future that tracks cancellation of this method's result and forwards cancellation to the actual LLRC request.
2024+
CompletableFuture<Void> cancellationForwarder = new CompletableFuture<Void>();
2025+
Cancellable result = new Cancellable() {
2026+
@Override
2027+
public void cancel() {
2028+
// Raise the flag by completing the future
2029+
FutureUtils.cancel(cancellationForwarder);
2030+
}
2031+
2032+
@Override
2033+
void runIfNotCancelled(Runnable runnable) {
2034+
if (cancellationForwarder.isCancelled()) {
2035+
throw newCancellationException();
2036+
}
2037+
runnable.run();
2038+
}
2039+
};
2040+
2041+
// Send the request after we have done the version compatibility check. Note that if it has already happened, the listener will
2042+
// be called immediately on the same thread with no asynchronous scheduling overhead.
2043+
versionCheck.addListener(new ActionListener<Optional<String>>() {
2044+
@Override
2045+
public void onResponse(Optional<String> validation) {
2046+
if (validation.isEmpty()) {
2047+
// Send the request and propagate cancellation
2048+
Cancellable call = client.performRequestAsync(request, listener);
2049+
cancellationForwarder.whenComplete((r, t) ->
2050+
// Forward cancellation to the actual request (no need to check parameters as the
2051+
// only way for cancellationForwarder to be completed is by being cancelled).
2052+
call.cancel()
2053+
);
2054+
} else {
2055+
// Version validation wasn't successful, fail the request with the validation result.
2056+
listener.onFailure(new ElasticsearchException(validation.get()));
2057+
}
2058+
}
2059+
2060+
@Override
2061+
public void onFailure(Exception e) {
2062+
// Propagate validation request failure. This will be transient since `getVersionValidationFuture` clears the validation
2063+
// future if the request fails, leading to retries at the next HLRC request (see comments below).
2064+
listener.onFailure(e);
2065+
}
2066+
});
2067+
2068+
return result;
2069+
};
2070+
2071+
private Response performClientRequest(Request request) throws IOException {
2072+
2073+
Optional<String> versionValidation;
2074+
try {
2075+
versionValidation = getVersionValidationFuture().get();
2076+
} catch (InterruptedException | ExecutionException e) {
2077+
// Unlikely to happen
2078+
throw new ElasticsearchException(e);
2079+
}
2080+
2081+
if (versionValidation.isEmpty()) {
2082+
return client.performRequest(request);
2083+
} else {
2084+
throw new ElasticsearchException(versionValidation.get());
2085+
}
2086+
}
2087+
2088+
/**
2089+
* Returns a future that asynchronously validates the Elasticsearch product version. Its result is an optional string: if empty then
2090+
* validation was successful, if present it contains the validation error. API requests should be chained to this future and check
2091+
* the validation result before going further.
2092+
* <p>
2093+
* This future is a memoization of the first successful request to the "/" endpoint and the subsequent compatibility check
2094+
* ({@see #versionValidationFuture}). Further client requests reuse its result.
2095+
* <p>
2096+
* If the version check request fails (e.g. network error), {@link #versionValidationFuture} is cleared so that a new validation
2097+
* request is sent at the next HLRC request. This allows retries to happen while avoiding a busy retry loop (LLRC retries on the node
2098+
* pool still happen).
2099+
*/
2100+
private ListenableFuture<Optional<String>> getVersionValidationFuture() {
2101+
ListenableFuture<Optional<String>> currentFuture = this.versionValidationFuture;
2102+
if (currentFuture != null) {
2103+
return currentFuture;
2104+
} else {
2105+
synchronized (this) {
2106+
// Re-check in synchronized block
2107+
currentFuture = this.versionValidationFuture;
2108+
if (currentFuture != null) {
2109+
return currentFuture;
2110+
}
2111+
ListenableFuture<Optional<String>> future = new ListenableFuture<>();
2112+
this.versionValidationFuture = future;
2113+
2114+
// Asynchronously call the info endpoint and complete the future with the version validation result.
2115+
Request req = new Request("GET", "/");
2116+
// These status codes are nominal in the context of product version verification
2117+
req.addParameter("ignore", "401,403");
2118+
client.performRequestAsync(req, new ResponseListener() {
2119+
@Override
2120+
public void onSuccess(Response response) {
2121+
Optional<String> validation;
2122+
try {
2123+
validation = getVersionValidation(response);
2124+
} catch (Exception e) {
2125+
logger.error("Failed to parse info response", e);
2126+
validation = Optional.of("Failed to parse info response. Check logs for detailed information - " +
2127+
e.getMessage());
2128+
}
2129+
future.onResponse(validation);
2130+
}
2131+
2132+
@Override
2133+
public void onFailure(Exception exception) {
2134+
2135+
// Fail the requests (this one and the ones waiting for it) and clear the future
2136+
// so that we retry the next time the client executes a request.
2137+
versionValidationFuture = null;
2138+
future.onFailure(exception);
2139+
}
2140+
});
2141+
2142+
return future;
2143+
}
2144+
}
2145+
}
2146+
2147+
/**
2148+
* Validates that the response info() is a compatible Elasticsearch version.
2149+
*
2150+
* @return an optional string. If empty, version is compatible. Otherwise, it's the message to return to the application.
2151+
*/
2152+
private Optional<String> getVersionValidation(Response response) throws IOException {
2153+
// Let requests go through if the client doesn't have permissions for the info endpoint.
2154+
int statusCode = response.getStatusLine().getStatusCode();
2155+
if (statusCode == 401 || statusCode == 403) {
2156+
return Optional.empty();
2157+
}
2158+
2159+
MainResponse mainResponse;
2160+
try {
2161+
mainResponse = parseEntity(response.getEntity(), MainResponse::fromXContent);
2162+
} catch (ResponseException e) {
2163+
throw parseResponseException(e);
2164+
}
2165+
2166+
String version = mainResponse.getVersion().getNumber();
2167+
if (Strings.hasLength(version) == false) {
2168+
return Optional.of("Missing version.number in info response");
2169+
}
2170+
2171+
String[] parts = version.split("\\.");
2172+
if (parts.length < 2) {
2173+
return Optional.of("Wrong version.number format in info response");
2174+
}
2175+
2176+
int major = Integer.parseInt(parts[0]);
2177+
int minor = Integer.parseInt(parts[1]);
2178+
2179+
if (major < 6) {
2180+
return Optional.of("Elasticsearch version 6 or more is required");
2181+
}
2182+
2183+
if (major == 6 || (major == 7 && minor < 14)) {
2184+
if ("You Know, for Search".equalsIgnoreCase(mainResponse.getTagline()) == false) {
2185+
return Optional.of("Invalid or missing tagline [" + mainResponse.getTagline() + "]");
2186+
}
2187+
2188+
if (major == 7) {
2189+
// >= 7.0 and < 7.14
2190+
String responseFlavor = mainResponse.getVersion().getBuildFlavor();
2191+
if ("default".equals(responseFlavor) == false) {
2192+
// Flavor is unknown when running tests, and non-mocked responses will return an unknown flavor
2193+
if (Build.CURRENT.flavor() != Build.Flavor.UNKNOWN || "unknown".equals(responseFlavor) == false) {
2194+
return Optional.of("Invalid or missing build flavor [" + responseFlavor + "]");
2195+
}
2196+
}
2197+
}
2198+
2199+
return Optional.empty();
2200+
}
2201+
2202+
String header = response.getHeader("X-Elastic-Product");
2203+
if (header == null) {
2204+
return Optional.of(
2205+
"Missing [X-Elastic-Product] header. Please check that you are connecting to an Elasticsearch " +
2206+
"instance, and that any networking filters are preserving that header."
2207+
);
2208+
}
2209+
2210+
if ("Elasticsearch".equals(header) == false) {
2211+
return Optional.of("Invalid value [" + header + "] for [X-Elastic-Product] header.");
2212+
}
2213+
2214+
return Optional.empty();
2215+
}
2216+
20052217
/**
20062218
* Ignores deprecation warnings. This is appropriate because it is only
20072219
* used to parse responses from Elasticsearch. Any deprecation warnings

0 commit comments

Comments
 (0)