Skip to content

Commit 6876b8c

Browse files
Use logs dir as working directory (#124966) (#130475)
* Use logs dir as working directory (#124966) In the unexpected case that Elasticsearch dies due to a segfault or other similar native issue, a core dump is useful in diagnosing the problem. Yet core dumps are written to the working directory, which is read-only for most installations of Elasticsearch. This commit changes the working directory to the logs dir which should always be writeable. * Ensure logs dir exists before using as working dir (#126566) With the change to using the logs dir as the working dir of the Elasticsearch process we need to ensure the logs dir exists within the CLI instead of later during startup. relates #124966 --------- Co-authored-by: Elastic Machine <[email protected]>
1 parent 54af22b commit 6876b8c

File tree

19 files changed

+200
-93
lines changed

19 files changed

+200
-93
lines changed

build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/test/rest/LegacyYamlRestTestPluginFuncTest.groovy

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,9 @@ echo "Running elasticsearch \$0"
179179

180180
file(distProjectFolder, 'src/config/elasticsearch.properties') << "some propes"
181181
file(distProjectFolder, 'src/config/jvm.options') << """
182-
-Xlog:gc*,gc+age=trace,safepoint:file=logs/gc.log:utctime,level,pid,tags:filecount=32,filesize=64m
183-
-XX:ErrorFile=logs/hs_err_pid%p.log
184-
-XX:HeapDumpPath=data
182+
-Xlog:gc*,gc+age=trace,safepoint:file=gc.log:utctime,level,pid,tags:filecount=32,filesize=64m
183+
-XX:ErrorFile=hs_err_pid%p.log
184+
# -XX:HeapDumpPath=/heap/dump/path
185185
"""
186186
file(distProjectFolder, 'build.gradle') << """
187187
import org.gradle.api.internal.artifacts.ArtifactAttributes;

build-tools/src/main/java/org/elasticsearch/gradle/testclusters/ElasticsearchNode.java

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1437,30 +1437,57 @@ private void tweakJvmOptions(Path configFileRoot) {
14371437
Path jvmOptions = configFileRoot.resolve("jvm.options");
14381438
try {
14391439
String content = new String(Files.readAllBytes(jvmOptions));
1440-
Map<String, String> expansions = jvmOptionExpansions();
1441-
for (String origin : expansions.keySet()) {
1442-
if (content.contains(origin) == false) {
1443-
throw new IOException("template property " + origin + " not found in template.");
1440+
Map<ReplacementKey, String> expansions = jvmOptionExpansions();
1441+
for (var entry : expansions.entrySet()) {
1442+
ReplacementKey replacement = entry.getKey();
1443+
String key = replacement.key();
1444+
if (content.contains(key) == false) {
1445+
key = replacement.fallback();
1446+
if (content.contains(key) == false) {
1447+
throw new IOException("Template property '" + replacement + "' not found in template:\n" + content);
1448+
}
14441449
}
1445-
content = content.replace(origin, expansions.get(origin));
1450+
content = content.replace(key, entry.getValue());
14461451
}
14471452
Files.write(jvmOptions, content.getBytes());
14481453
} catch (IOException ioException) {
14491454
throw new UncheckedIOException(ioException);
14501455
}
14511456
}
14521457

1453-
private Map<String, String> jvmOptionExpansions() {
1454-
Map<String, String> expansions = new HashMap<>();
1458+
private record ReplacementKey(String key, String fallback) {}
1459+
1460+
private Map<ReplacementKey, String> jvmOptionExpansions() {
1461+
Map<ReplacementKey, String> expansions = new HashMap<>();
14551462
Version version = getVersion();
1456-
String heapDumpOrigin = getVersion().onOrAfter("6.3.0") ? "-XX:HeapDumpPath=data" : "-XX:HeapDumpPath=/heap/dump/path";
1457-
expansions.put(heapDumpOrigin, "-XX:HeapDumpPath=" + confPathLogs);
1458-
if (version.onOrAfter("6.2.0")) {
1459-
expansions.put("logs/gc.log", confPathLogs.resolve("gc.log").toString());
1463+
1464+
ReplacementKey heapDumpPathSub;
1465+
if (version.before("8.19.0") && version.onOrAfter("6.3.0")) {
1466+
heapDumpPathSub = new ReplacementKey("-XX:HeapDumpPath=data", null);
1467+
} else {
1468+
// temporarily fall back to the old substitution so both old and new work during backport
1469+
heapDumpPathSub = new ReplacementKey("# -XX:HeapDumpPath=/heap/dump/path", "-XX:HeapDumpPath=data");
14601470
}
1461-
if (getVersion().getMajor() >= 7) {
1462-
expansions.put("-XX:ErrorFile=logs/hs_err_pid%p.log", "-XX:ErrorFile=" + confPathLogs.resolve("hs_err_pid%p.log"));
1471+
expansions.put(heapDumpPathSub, "-XX:HeapDumpPath=" + confPathLogs);
1472+
1473+
ReplacementKey gcLogSub;
1474+
if (version.before("8.19.0") && version.onOrAfter("6.2.0")) {
1475+
gcLogSub = new ReplacementKey("logs/gc.log", null);
1476+
} else {
1477+
// temporarily check the old substitution first so both old and new work during backport
1478+
gcLogSub = new ReplacementKey("logs/gc.log", "gc.log");
14631479
}
1480+
expansions.put(gcLogSub, confPathLogs.resolve("gc.log").toString());
1481+
1482+
ReplacementKey errorFileSub;
1483+
if (version.before("8.19.0") && version.getMajor() >= 7) {
1484+
errorFileSub = new ReplacementKey("-XX:ErrorFile=logs/hs_err_pid%p.log", null);
1485+
} else {
1486+
// temporarily check the old substitution first so both old and new work during backport
1487+
errorFileSub = new ReplacementKey("-XX:ErrorFile=logs/hs_err_pid%p.log", "-XX:ErrorFile=hs_err_pid%p.log");
1488+
}
1489+
expansions.put(errorFileSub, "-XX:ErrorFile=" + confPathLogs.resolve("hs_err_pid%p.log"));
1490+
14641491
return expansions;
14651492
}
14661493

distribution/build.gradle

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,6 @@ subprojects {
534534
final String packagingPathData = "path.data: /var/lib/elasticsearch"
535535
final String pathLogs = "/var/log/elasticsearch"
536536
final String packagingPathLogs = "path.logs: ${pathLogs}"
537-
final String packagingLoggc = "${pathLogs}/gc.log"
538537

539538
String licenseText
540539
if (isTestDistro) {
@@ -579,23 +578,6 @@ subprojects {
579578
'rpm': packagingPathLogs,
580579
'def': '#path.logs: /path/to/logs'
581580
],
582-
'loggc': [
583-
'deb': packagingLoggc,
584-
'rpm': packagingLoggc,
585-
'def': 'logs/gc.log'
586-
],
587-
588-
'heap.dump.path': [
589-
'deb': "-XX:HeapDumpPath=/var/lib/elasticsearch",
590-
'rpm': "-XX:HeapDumpPath=/var/lib/elasticsearch",
591-
'def': "-XX:HeapDumpPath=data"
592-
],
593-
594-
'error.file': [
595-
'deb': "-XX:ErrorFile=/var/log/elasticsearch/hs_err_pid%p.log",
596-
'rpm': "-XX:ErrorFile=/var/log/elasticsearch/hs_err_pid%p.log",
597-
'def': "-XX:ErrorFile=logs/hs_err_pid%p.log"
598-
],
599581

600582
'scripts.footer': [
601583
/* Debian needs exit 0 on these scripts so we add it here and preserve

distribution/src/config/jvm.options

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,10 @@
7474

7575
# specify an alternative path for heap dumps; ensure the directory exists and
7676
# has sufficient space
77-
@heap.dump.path@
77+
# -XX:HeapDumpPath=/heap/dump/path
7878

7979
# specify an alternative path for JVM fatal error logs
80-
@error.file@
80+
-XX:ErrorFile=hs_err_pid%p.log
8181

8282
## GC logging
83-
-Xlog:gc*,gc+age=trace,safepoint:file=@loggc@:utctime,level,pid,tags:filecount=32,filesize=64m
83+
-Xlog:gc*,gc+age=trace,safepoint:file=gc.log:utctime,level,pid,tags:filecount=32,filesize=64m

distribution/tools/cli-launcher/src/main/java/org/elasticsearch/launcher/CliToolLauncher.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public static void main(String[] args) throws Exception {
5858
String toolname = getToolName(pinfo.sysprops());
5959
String libs = pinfo.sysprops().getOrDefault("cli.libs", "");
6060

61-
command = CliToolProvider.load(toolname, libs).create();
61+
command = CliToolProvider.load(pinfo.sysprops(), toolname, libs).create();
6262
Terminal terminal = Terminal.DEFAULT;
6363
Runtime.getRuntime().addShutdownHook(createShutdownHook(terminal, command));
6464

distribution/tools/server-cli/src/main/java/org/elasticsearch/server/cli/JvmOption.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@
1010
package org.elasticsearch.server.cli;
1111

1212
import org.elasticsearch.common.Strings;
13+
import org.elasticsearch.core.SuppressForbidden;
1314

1415
import java.io.BufferedReader;
1516
import java.io.IOException;
1617
import java.io.InputStream;
1718
import java.io.InputStreamReader;
1819
import java.nio.charset.StandardCharsets;
20+
import java.nio.file.Files;
1921
import java.nio.file.Path;
2022
import java.util.List;
2123
import java.util.Locale;
@@ -106,7 +108,9 @@ private static List<String> flagsFinal(final List<String> userDefinedJvmOptions)
106108
userDefinedJvmOptions.stream(),
107109
Stream.of("-XX:+PrintFlagsFinal", "-version")
108110
).flatMap(Function.identity()).toList();
109-
final Process process = new ProcessBuilder().command(command).start();
111+
final ProcessBuilder builder = new ProcessBuilder().command(command);
112+
setWorkingDir(builder);
113+
final Process process = builder.start();
110114
final List<String> output = readLinesFromInputStream(process.getInputStream());
111115
final List<String> error = readLinesFromInputStream(process.getErrorStream());
112116
final int status = process.waitFor();
@@ -124,6 +128,14 @@ private static List<String> flagsFinal(final List<String> userDefinedJvmOptions)
124128
}
125129
}
126130

131+
@SuppressForbidden(reason = "ProcessBuilder takes File")
132+
private static void setWorkingDir(ProcessBuilder builder) throws IOException {
133+
// The real ES process uses the logs dir as the working directory. Since we don't
134+
// have the logs dir yet, here we use a temp directory for calculating jvm options.
135+
final Path tmpDir = Files.createTempDirectory("final-flags");
136+
builder.directory(tmpDir.toFile());
137+
}
138+
127139
private static List<String> readLinesFromInputStream(final InputStream is) throws IOException {
128140
try (InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8); BufferedReader br = new BufferedReader(isr)) {
129141
return br.lines().toList();

distribution/tools/server-cli/src/main/java/org/elasticsearch/server/cli/JvmOptionsParser.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -269,13 +269,13 @@ interface InvalidLineConsumer {
269269
* and the following JVM options will not be accepted:
270270
* <ul>
271271
* <li>
272-
* {@code 18:-Xlog:age*=trace,gc*,safepoint:file=logs/gc.log:utctime,pid,tags:filecount=32,filesize=64m}
272+
* {@code 18:-Xlog:age*=trace,gc*,safepoint:file=gc.log:utctime,pid,tags:filecount=32,filesize=64m}
273273
* </li>
274274
* <li>
275-
* {@code 18-:-Xlog:age*=trace,gc*,safepoint:file=logs/gc.log:utctime,pid,tags:filecount=32,filesize=64m}
275+
* {@code 18-:-Xlog:age*=trace,gc*,safepoint:file=gc.log:utctime,pid,tags:filecount=32,filesize=64m}
276276
* </li>
277277
* <li>
278-
* {@code 18-19:-Xlog:age*=trace,gc*,safepoint:file=logs/gc.log:utctime,pid,tags:filecount=32,filesize=64m}
278+
* {@code 18-19:-Xlog:age*=trace,gc*,safepoint:file=gc.log:utctime,pid,tags:filecount=32,filesize=64m}
279279
* </li>
280280
* </ul>
281281
*

distribution/tools/server-cli/src/main/java/org/elasticsearch/server/cli/ServerCli.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import java.nio.file.Path;
3434
import java.util.Arrays;
3535
import java.util.Locale;
36+
import java.util.Map;
3637
import java.util.concurrent.atomic.AtomicBoolean;
3738

3839
/**
@@ -168,7 +169,7 @@ Environment autoConfigureSecurity(
168169
assert secureSettingsLoader(env) instanceof KeyStoreLoader;
169170

170171
String autoConfigLibs = "modules/x-pack-core,modules/x-pack-security,lib/tools/security-cli";
171-
Command cmd = loadTool("auto-configure-node", autoConfigLibs);
172+
Command cmd = loadTool(processInfo.sysprops(), "auto-configure-node", autoConfigLibs);
172173
assert cmd instanceof EnvironmentAwareCommand;
173174
@SuppressWarnings("raw")
174175
var autoConfigNode = (EnvironmentAwareCommand) cmd;
@@ -210,7 +211,7 @@ Environment autoConfigureSecurity(
210211
// package private for testing
211212
void syncPlugins(Terminal terminal, Environment env, ProcessInfo processInfo) throws Exception {
212213
String pluginCliLibs = "lib/tools/plugin-cli";
213-
Command cmd = loadTool("sync-plugins", pluginCliLibs);
214+
Command cmd = loadTool(processInfo.sysprops(), "sync-plugins", pluginCliLibs);
214215
assert cmd instanceof EnvironmentAwareCommand;
215216
@SuppressWarnings("raw")
216217
var syncPlugins = (EnvironmentAwareCommand) cmd;
@@ -258,8 +259,8 @@ protected ServerProcess getServer() {
258259
}
259260

260261
// protected to allow tests to override
261-
protected Command loadTool(String toolname, String libs) {
262-
return CliToolProvider.load(toolname, libs).create();
262+
protected Command loadTool(Map<String, String> sysprops, String toolname, String libs) {
263+
return CliToolProvider.load(sysprops, toolname, libs).create();
263264
}
264265

265266
// protected to allow tests to override

distribution/tools/server-cli/src/main/java/org/elasticsearch/server/cli/ServerProcessBuilder.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,20 @@
1010
package org.elasticsearch.server.cli;
1111

1212
import org.elasticsearch.bootstrap.ServerArgs;
13+
import org.elasticsearch.cli.ExitCodes;
1314
import org.elasticsearch.cli.ProcessInfo;
1415
import org.elasticsearch.cli.Terminal;
1516
import org.elasticsearch.cli.UserException;
1617
import org.elasticsearch.common.Strings;
1718
import org.elasticsearch.common.io.stream.OutputStreamStreamOutput;
1819
import org.elasticsearch.core.PathUtils;
20+
import org.elasticsearch.core.SuppressForbidden;
1921

2022
import java.io.IOException;
2123
import java.io.OutputStream;
2224
import java.io.UncheckedIOException;
25+
import java.nio.file.FileAlreadyExistsException;
26+
import java.nio.file.Files;
2327
import java.nio.file.Path;
2428
import java.util.HashMap;
2529
import java.util.List;
@@ -134,6 +138,17 @@ public ServerProcess start() throws UserException {
134138
return start(ProcessBuilder::start);
135139
}
136140

141+
private void ensureWorkingDirExists() throws UserException {
142+
Path workingDir = serverArgs.logsDir();
143+
try {
144+
Files.createDirectories(workingDir);
145+
} catch (FileAlreadyExistsException e) {
146+
throw new UserException(ExitCodes.CONFIG, "Logs dir [" + workingDir + "] exists but is not a directory", e);
147+
} catch (IOException e) {
148+
throw new UserException(ExitCodes.CONFIG, "Unable to create logs dir [" + workingDir + "]", e);
149+
}
150+
}
151+
137152
private static void checkRequiredArgument(Object argument, String argumentName) {
138153
if (argument == null) {
139154
throw new IllegalStateException(
@@ -150,12 +165,14 @@ ServerProcess start(ProcessStarter processStarter) throws UserException {
150165
checkRequiredArgument(jvmOptions, "jvmOptions");
151166
checkRequiredArgument(terminal, "terminal");
152167

168+
ensureWorkingDirExists();
169+
153170
Process jvmProcess = null;
154171
ErrorPumpThread errorPump;
155172

156173
boolean success = false;
157174
try {
158-
jvmProcess = createProcess(getCommand(), getJvmArgs(), jvmOptions, getEnvironment(), processStarter);
175+
jvmProcess = createProcess(getCommand(), getJvmArgs(), jvmOptions, getEnvironment(), serverArgs.logsDir(), processStarter);
159176
errorPump = new ErrorPumpThread(terminal, jvmProcess.getErrorStream());
160177
errorPump.start();
161178
sendArgs(serverArgs, jvmProcess.getOutputStream());
@@ -185,16 +202,23 @@ private static Process createProcess(
185202
List<String> jvmArgs,
186203
List<String> jvmOptions,
187204
Map<String, String> environment,
205+
Path workingDir,
188206
ProcessStarter processStarter
189207
) throws InterruptedException, IOException {
190208

191209
var builder = new ProcessBuilder(Stream.concat(Stream.of(command), Stream.concat(jvmOptions.stream(), jvmArgs.stream())).toList());
192210
builder.environment().putAll(environment);
211+
setWorkingDir(builder, workingDir);
193212
builder.redirectOutput(ProcessBuilder.Redirect.INHERIT);
194213

195214
return processStarter.start(builder);
196215
}
197216

217+
@SuppressForbidden(reason = "ProcessBuilder takes File")
218+
private static void setWorkingDir(ProcessBuilder builder, Path path) {
219+
builder.directory(path.toFile());
220+
}
221+
198222
private static void sendArgs(ServerArgs args, OutputStream processStdin) {
199223
// DO NOT close the underlying process stdin, since we need to be able to write to it to signal exit
200224
var out = new OutputStreamStreamOutput(processStdin);

distribution/tools/server-cli/src/main/java/org/elasticsearch/server/cli/SystemJvmOptions.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
final class SystemJvmOptions {
2525

2626
static List<String> systemJvmOptions(Settings nodeSettings, final Map<String, String> sysprops) {
27+
Path esHome = Path.of(sysprops.get("es.path.home"));
2728
String distroType = sysprops.get("es.distribution.type");
2829
String javaType = sysprops.get("es.java.type");
2930
boolean isHotspot = sysprops.getOrDefault("sun.management.compiler", "").contains("HotSpot");
@@ -67,7 +68,8 @@ static List<String> systemJvmOptions(Settings nodeSettings, final Map<String, St
6768
"-Djava.locale.providers=" + getLocaleProviders(),
6869
// Enable vectorization for whatever version we are running. This ensures we use vectorization even when running EA builds.
6970
"-Dorg.apache.lucene.vectorization.upperJavaFeatureVersion=" + Runtime.version().feature(),
70-
// Pass through distribution type and java type
71+
// Pass through some properties
72+
"-Des.path.home=" + esHome,
7173
"-Des.distribution.type=" + distroType,
7274
"-Des.java.type=" + javaType
7375
),
@@ -77,7 +79,7 @@ static List<String> systemJvmOptions(Settings nodeSettings, final Map<String, St
7779
maybeSetReplayFile(distroType, isHotspot),
7880
maybeWorkaroundG1Bug(),
7981
maybeAllowSecurityManager(useEntitlements),
80-
maybeAttachEntitlementAgent(useEntitlements)
82+
maybeAttachEntitlementAgent(esHome, useEntitlements)
8183
).flatMap(s -> s).toList();
8284
}
8385

@@ -167,12 +169,12 @@ private static Stream<String> maybeAllowSecurityManager(boolean useEntitlements)
167169
return Stream.of();
168170
}
169171

170-
private static Stream<String> maybeAttachEntitlementAgent(boolean useEntitlements) {
172+
private static Stream<String> maybeAttachEntitlementAgent(Path esHome, boolean useEntitlements) {
171173
if (useEntitlements == false) {
172174
return Stream.empty();
173175
}
174176

175-
Path dir = Path.of("lib", "entitlement-bridge");
177+
Path dir = esHome.resolve("lib/entitlement-bridge");
176178
if (Files.exists(dir) == false) {
177179
throw new IllegalStateException("Directory for entitlement bridge jar does not exist: " + dir);
178180
}

0 commit comments

Comments
 (0)