Skip to content

Commit a8c7a29

Browse files
committed
Merge remote-tracking branch 'es/master' into enrich
2 parents 97d3e30 + d9b2d8d commit a8c7a29

File tree

562 files changed

+7656
-4020
lines changed

Some content is hidden

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

562 files changed

+7656
-4020
lines changed

buildSrc/build.gradle

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,22 +66,30 @@ if (JavaVersion.current() < JavaVersion.VERSION_11) {
6666
throw new GradleException('At least Java 11 is required to build elasticsearch gradle tools')
6767
}
6868

69-
// Keep compatibility with Java 8 for external users of build-tools that haven't migrated to Java 11
70-
targetCompatibility = '11'
71-
sourceCompatibility = '11'
69+
allprojects {
70+
targetCompatibility = '11'
71+
sourceCompatibility = '11'
72+
}
7273

7374
sourceSets {
7475
// We have a few classes that need to be compiled for older java versions
7576
minimumRuntime { }
7677
}
7778

79+
configurations {
80+
reaper
81+
}
82+
7883
compileMinimumRuntimeJava {
7984
targetCompatibility = 8
8085
sourceCompatibility = 8
8186
}
8287

8388
jar {
8489
from sourceSets.minimumRuntime.output
90+
into('META-INF') {
91+
from configurations.reaper
92+
}
8593
}
8694

8795
javadoc {
@@ -119,6 +127,7 @@ dependencies {
119127
testCompile "junit:junit:${props.getProperty('junit')}"
120128
testCompile "com.carrotsearch.randomizedtesting:randomizedtesting-runner:${props.getProperty('randomizedrunner')}"
121129
testCompile 'com.github.tomakehurst:wiremock-jre8-standalone:2.23.2'
130+
reaper project('reaper')
122131
minimumRuntimeCompile "junit:junit:${props.getProperty('junit')}"
123132
minimumRuntimeCompile localGroovy()
124133
minimumRuntimeCompile gradleApi()

buildSrc/reaper/build.gradle

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
apply plugin: 'java'
2+
3+
jar {
4+
manifest {
5+
attributes 'Main-Class': 'org.elasticsearch.gradle.reaper.Reaper'
6+
}
7+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.gradle.reaper;
21+
22+
import java.io.Closeable;
23+
import java.io.IOException;
24+
import java.io.UncheckedIOException;
25+
import java.nio.file.Files;
26+
import java.nio.file.Path;
27+
import java.nio.file.Paths;
28+
import java.util.Comparator;
29+
import java.util.List;
30+
import java.util.stream.Collectors;
31+
import java.util.stream.Stream;
32+
33+
/**
34+
* A standalone process that will reap external services after a build dies.
35+
*
36+
* <h2>Input</h2>
37+
* Since how to reap a given service is platform and service dependent, this tool
38+
* operates on system commands to execute. It takes a single argument, a directory
39+
* that will contain files with reaping commands. Each line in each file will be
40+
* executed with {@link Runtime#getRuntime()#exec}.
41+
*
42+
* The main method will wait indefinitely on the parent process (Gradle) by
43+
* reading from stdin. When Gradle shuts down, whether normally or abruptly, the
44+
* pipe will be broken and read will return.
45+
*
46+
* The reaper will then iterate over the files in the configured directory,
47+
* and execute the given commands. If any commands fail, a failure message is
48+
* written to stderr. Otherwise, the input file will be deleted. If no inputs
49+
* produced errors, the entire input directory is deleted upon completion of reaping.
50+
*/
51+
public class Reaper implements Closeable {
52+
53+
private Path inputDir;
54+
private boolean failed;
55+
56+
private Reaper(Path inputDir) {
57+
this.inputDir = inputDir;
58+
this.failed = false;
59+
}
60+
61+
public static void main(String[] args) throws Exception {
62+
if (args.length != 1) {
63+
System.err.println("Expected one argument.\nUsage: java -jar reaper.jar <DIR_OF_REAPING_COMMANDS>");
64+
System.exit(1);
65+
}
66+
Path inputDir = Paths.get(args[0]);
67+
68+
try (Reaper reaper = new Reaper(inputDir)){
69+
System.in.read();
70+
reaper.reap();
71+
}
72+
}
73+
74+
private void reap() {
75+
try (Stream<Path> stream = Files.list(inputDir)){
76+
final List<Path> inputFiles = stream.filter(p -> p.getFileName().toString().endsWith(".cmd")).collect(Collectors.toList());
77+
78+
for (Path inputFile : inputFiles) {
79+
System.out.println("Process file: " + inputFile);
80+
String line = Files.readString(inputFile);
81+
System.out.println("Running command: " + line);
82+
String[] command = line.split(" ");
83+
Process process = Runtime.getRuntime().exec(command);
84+
int ret = process.waitFor();
85+
86+
System.out.print("Stdout: ");
87+
process.getInputStream().transferTo(System.out);
88+
System.out.print("\nStderr: ");
89+
process.getErrorStream().transferTo(System.out);
90+
System.out.println(); // end the stream
91+
if (ret != 0) {
92+
logFailure("Command [" + line + "] failed with exit code " + ret, null);
93+
} else {
94+
delete(inputFile);
95+
}
96+
}
97+
} catch (Exception e) {
98+
logFailure("Failed to reap inputs", e);
99+
}
100+
}
101+
102+
private void logFailure(String message, Exception e) {
103+
System.err.println(message);
104+
if (e != null) {
105+
e.printStackTrace(System.err);
106+
}
107+
failed = true;
108+
}
109+
110+
private void delete(Path toDelete) {
111+
try {
112+
Files.delete(toDelete);
113+
} catch (IOException e) {
114+
logFailure("Failed to delete [" + toDelete + "]", e);
115+
}
116+
}
117+
118+
@Override
119+
public void close() {
120+
if (failed == false) {
121+
try (Stream<Path> stream = Files.walk(inputDir)){
122+
stream.sorted(Comparator.reverseOrder()).forEach(this::delete);
123+
} catch (IOException e) {
124+
throw new UncheckedIOException(e);
125+
}
126+
}
127+
}
128+
}

buildSrc/settings.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include 'reaper'
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.gradle;
21+
22+
import org.gradle.api.Plugin;
23+
import org.gradle.api.Project;
24+
25+
import java.nio.file.Path;
26+
27+
/**
28+
* A plugin to handle reaping external services spawned by a build if Gradle dies.
29+
*/
30+
public class ReaperPlugin implements Plugin<Project> {
31+
32+
@Override
33+
public void apply(Project project) {
34+
if (project != project.getRootProject()) {
35+
throw new IllegalArgumentException("ReaperPlugin can only be applied to the root project of a build");
36+
}
37+
38+
Path inputDir = project.getRootDir().toPath().resolve(".gradle")
39+
.resolve("reaper").resolve("build-" + ProcessHandle.current().pid());
40+
ReaperService service = project.getExtensions().create("reaper", ReaperService.class,
41+
project.getLogger(), project.getBuildDir().toPath(), inputDir);
42+
43+
project.getGradle().buildFinished(result -> service.shutdown());
44+
}
45+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.gradle;
21+
22+
import org.gradle.api.GradleException;
23+
import org.gradle.api.logging.Logger;
24+
import org.gradle.internal.jvm.Jvm;
25+
26+
import java.io.IOException;
27+
import java.io.InputStream;
28+
import java.io.OutputStream;
29+
import java.io.UncheckedIOException;
30+
import java.nio.file.Files;
31+
import java.nio.file.Path;
32+
33+
public class ReaperService {
34+
35+
private Logger logger;
36+
private Path buildDir;
37+
private Path inputDir;
38+
private Path logFile;
39+
private volatile Process reaperProcess;
40+
41+
public ReaperService(Logger logger, Path buildDir, Path inputDir) {
42+
this.logger = logger;
43+
this.buildDir = buildDir;
44+
this.inputDir = inputDir;
45+
this.logFile = inputDir.resolve("reaper.log");
46+
}
47+
48+
/**
49+
* Register a pid that will be killed by the reaper.
50+
*/
51+
public void registerPid(String serviceId, long pid) {
52+
String[] killPidCommand = OS.<String[]>conditional()
53+
.onWindows(() -> new String[]{"Taskill", "/F", "/PID", String.valueOf(pid)})
54+
.onUnix(() -> new String[]{"kill", "-9", String.valueOf(pid)})
55+
.supply();
56+
registerCommand(serviceId, killPidCommand);
57+
}
58+
59+
/**
60+
* Register a system command that will be run by the reaper.
61+
*/
62+
public void registerCommand(String serviceId, String... command) {
63+
ensureReaperStarted();
64+
65+
try {
66+
Files.writeString(inputDir.resolve(serviceId + ".cmd"), String.join(" ", command));
67+
} catch (IOException e) {
68+
throw new UncheckedIOException(e);
69+
}
70+
}
71+
72+
public void unregister(String serviceId) {
73+
try {
74+
Files.deleteIfExists(inputDir.resolve(serviceId + ".cmd"));
75+
} catch (IOException e) {
76+
throw new UncheckedIOException(e);
77+
}
78+
}
79+
80+
void shutdown() {
81+
if (reaperProcess != null) {
82+
ensureReaperAlive();
83+
try {
84+
reaperProcess.getOutputStream().close();
85+
logger.info("Waiting for reaper to exit normally");
86+
if (reaperProcess.waitFor() != 0) {
87+
throw new GradleException("Reaper process failed. Check log at "
88+
+ inputDir.resolve("error.log") + " for details");
89+
}
90+
} catch (Exception e) {
91+
throw new RuntimeException(e);
92+
}
93+
94+
}
95+
}
96+
97+
private synchronized void ensureReaperStarted() {
98+
if (reaperProcess == null) {
99+
try {
100+
// copy the reaper jar
101+
Path jarPath = buildDir.resolve("reaper").resolve("reaper.jar");
102+
Files.createDirectories(jarPath.getParent());
103+
InputStream jarInput = ReaperPlugin.class.getResourceAsStream("/META-INF/reaper.jar");
104+
try (OutputStream out = Files.newOutputStream(jarPath)) {
105+
jarInput.transferTo(out);
106+
}
107+
108+
// ensure the input directory exists
109+
Files.createDirectories(inputDir);
110+
111+
// start the reaper
112+
ProcessBuilder builder = new ProcessBuilder(
113+
Jvm.current().getJavaExecutable().toString(), // same jvm as gradle
114+
"-Xms4m", "-Xmx16m", // no need for a big heap, just need to read some files and execute
115+
"-jar", jarPath.toString(),
116+
inputDir.toString());
117+
logger.info("Launching reaper: " + String.join(" ", builder.command()));
118+
// be explicit for stdin, we use closing of the pipe to signal shutdown to the reaper
119+
builder.redirectInput(ProcessBuilder.Redirect.PIPE);
120+
builder.redirectOutput(logFile.toFile());
121+
builder.redirectError(logFile.toFile());
122+
reaperProcess = builder.start();
123+
} catch (Exception e) {
124+
throw new RuntimeException(e);
125+
}
126+
} else {
127+
ensureReaperAlive();
128+
}
129+
}
130+
131+
private void ensureReaperAlive() {
132+
if (reaperProcess.isAlive() == false) {
133+
throw new IllegalStateException("Reaper process died unexpectedly! Check the log at " + logFile.toString());
134+
}
135+
}
136+
}

0 commit comments

Comments
 (0)