-
Notifications
You must be signed in to change notification settings - Fork 25.6k
Fix APM configuration file delete #91058
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
fa396cb
Fix APM configuration file delete
237fe0f
Update docs/changelog/91058.yaml
grcevski 6c95c78
Update docs/changelog/91058.yaml
grcevski b3f054c
Fix test
733ddf5
Merge branch 'fix/apm_file_delete' of github.com:grcevski/elasticsear…
25c962b
Adjust test code per feedback
a8a5561
Merge branch 'main' into fix/apm_file_delete
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -166,13 +166,18 @@ static List<String> apmJvmOptions(Settings settings, @Nullable KeyStoreWrapper k | |
| final List<String> options = new ArrayList<>(); | ||
| // Use an agent argument to specify the config file instead of e.g. `-Delastic.apm.config_file=...` | ||
| // because then the agent won't try to reload the file, and we can remove it after startup. | ||
| options.add("-javaagent:" + agentJar + "=c=" + tmpProperties); | ||
| options.add(agentCommandLineOption(agentJar, tmpProperties)); | ||
|
|
||
| dynamicSettings.forEach((key, value) -> options.add("-Delastic.apm." + key + "=" + value)); | ||
|
|
||
| return options; | ||
| } | ||
|
|
||
| // package private for testing | ||
| static String agentCommandLineOption(Path agentJar, Path tmpPropertiesFile) { | ||
| return "-javaagent:" + agentJar + "=c=" + tmpPropertiesFile; | ||
| } | ||
|
|
||
| private static void extractSecureSettings(KeyStoreWrapper keystore, Map<String, String> propertiesMap) { | ||
| final Set<String> settingNames = keystore.getSettingNames(); | ||
| for (String key : List.of("api_key", "secret_token")) { | ||
|
|
@@ -225,9 +230,18 @@ private static Map<String, String> extractApmSettings(Settings settings) throws | |
| return propertiesMap; | ||
| } | ||
|
|
||
| // package private for testing | ||
| static Path createTemporaryPropertiesFile(Path tmpdir) throws IOException { | ||
| return Files.createTempFile(tmpdir, ".elstcapm.", ".tmp"); | ||
| } | ||
|
|
||
| /** | ||
| * Writes a Java properties file with data from supplied map to a temporary config, and returns | ||
| * the file that was created. | ||
| * <p> | ||
| * We expect that the deleteTemporaryApmConfig function in Node will delete this temporary | ||
| * configuration file, however if we fail to launch the node (because of an error) we might leave the | ||
| * file behind. Therefore, we register a CLI shutdown hook that will also attempt to delete the file. | ||
| * | ||
| * @param tmpdir the directory for the file | ||
| * @param propertiesMap the data to write | ||
|
|
@@ -238,10 +252,18 @@ private static Path writeApmProperties(Path tmpdir, Map<String, String> properti | |
| final Properties p = new Properties(); | ||
| p.putAll(propertiesMap); | ||
|
|
||
| final Path tmpFile = Files.createTempFile(tmpdir, ".elstcapm.", ".tmp"); | ||
| final Path tmpFile = createTemporaryPropertiesFile(tmpdir); | ||
| try (OutputStream os = Files.newOutputStream(tmpFile)) { | ||
| p.store(os, " Automatically generated by Elasticsearch, do not edit!"); | ||
| } | ||
| Runtime.getRuntime().addShutdownHook(new Thread(() -> { | ||
| try { | ||
| Files.deleteIfExists(tmpFile); | ||
| } catch (IOException e) { | ||
| // ignore | ||
| } | ||
| }, "elasticsearch[apmagent-cleanup]")); | ||
|
|
||
| return tmpFile; | ||
| } | ||
|
|
||
|
|
@@ -253,7 +275,12 @@ private static Path writeApmProperties(Path tmpdir, Map<String, String> properti | |
| */ | ||
| @Nullable | ||
| private static Path findAgentJar() throws IOException, UserException { | ||
| final Path apmModule = Path.of(System.getProperty("user.dir")).resolve("modules/apm"); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did resolve("modules").resolve("apm") to be Windows friendly. |
||
| return findAgentJar(System.getProperty("user.dir")); | ||
| } | ||
|
|
||
| // package private for testing | ||
| static Path findAgentJar(String installDir) throws IOException, UserException { | ||
| final Path apmModule = Path.of(installDir).resolve("modules").resolve("apm"); | ||
|
|
||
| if (Files.notExists(apmModule)) { | ||
| if (Build.CURRENT.isProductionRelease()) { | ||
|
|
||
74 changes: 74 additions & 0 deletions
74
...ution/tools/server-cli/src/test/java/org/elasticsearch/server/cli/APMJvmOptionsTests.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| /* | ||
| * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
| * or more contributor license agreements. Licensed under the Elastic License | ||
| * 2.0 and the Server Side Public License, v 1; you may not use this file except | ||
| * in compliance with, at your election, the Elastic License 2.0 or the Server | ||
| * Side Public License, v 1. | ||
| */ | ||
|
|
||
| package org.elasticsearch.server.cli; | ||
|
|
||
| import org.elasticsearch.cli.UserException; | ||
| import org.elasticsearch.monitor.jvm.JvmInfo; | ||
| import org.elasticsearch.node.Node; | ||
| import org.elasticsearch.test.ESTestCase; | ||
| import org.junit.After; | ||
| import org.junit.Before; | ||
|
|
||
| import java.io.IOException; | ||
| import java.nio.file.Files; | ||
| import java.nio.file.Path; | ||
|
|
||
| import static org.mockito.Mockito.doReturn; | ||
| import static org.mockito.Mockito.mock; | ||
|
|
||
| @ESTestCase.WithoutSecurityManager | ||
| public class APMJvmOptionsTests extends ESTestCase { | ||
|
|
||
| private Path installDir; | ||
| private Path agentPath; | ||
|
|
||
| @Before | ||
| public void setup() throws IOException, UserException { | ||
| installDir = makeFakeAgentJar(); | ||
| agentPath = APMJvmOptions.findAgentJar(installDir.toAbsolutePath().toString()); | ||
| } | ||
|
|
||
| @After | ||
| public void cleanup() throws IOException { | ||
| Files.delete(agentPath); | ||
| } | ||
|
|
||
| public void testFindJar() throws IOException { | ||
| assertNotNull(agentPath); | ||
|
|
||
| Path anotherPath = Files.createDirectories(installDir.resolve("another")); | ||
| Path apmPathDir = anotherPath.resolve("modules").resolve("apm"); | ||
| Files.createDirectories(apmPathDir); | ||
|
|
||
| assertTrue( | ||
| expectThrows(UserException.class, () -> APMJvmOptions.findAgentJar(anotherPath.toAbsolutePath().toString())).getMessage() | ||
| .contains("Installation is corrupt") | ||
| ); | ||
| } | ||
|
|
||
| public void testFileDeleteWorks() throws IOException { | ||
| var tempFile = APMJvmOptions.createTemporaryPropertiesFile(agentPath.getParent()); | ||
| var commandLineOption = APMJvmOptions.agentCommandLineOption(agentPath, tempFile); | ||
| var jvmInfo = mock(JvmInfo.class); | ||
| doReturn(new String[] { commandLineOption }).when(jvmInfo).getInputArguments(); | ||
| assertTrue(Files.exists(tempFile)); | ||
| Node.deleteTemporaryApmConfig(jvmInfo, (e, p) -> fail("Shouldn't hit an exception")); | ||
| assertFalse(Files.exists(tempFile)); | ||
| } | ||
|
|
||
| private Path makeFakeAgentJar() throws IOException { | ||
| Path tempFile = createTempFile(); | ||
| Path apmPathDir = tempFile.getParent().resolve("modules").resolve("apm"); | ||
| Files.createDirectories(apmPathDir); | ||
| Path apmAgentFile = apmPathDir.resolve("elastic-apm-agent-0.0.0.jar"); | ||
| Files.move(tempFile, apmAgentFile); | ||
|
|
||
| return tempFile.getParent(); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| pr: 91058 | ||
| summary: Fix APM configuration file delete | ||
| area: Infra/Core | ||
| type: bug | ||
| issues: | ||
| - 89439 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -229,6 +229,7 @@ | |
| import java.util.Set; | ||
| import java.util.concurrent.CountDownLatch; | ||
| import java.util.concurrent.TimeUnit; | ||
| import java.util.function.BiConsumer; | ||
| import java.util.function.Function; | ||
| import java.util.function.LongSupplier; | ||
| import java.util.function.UnaryOperator; | ||
|
|
@@ -404,7 +405,10 @@ protected Node( | |
| ); | ||
| } | ||
|
|
||
| deleteTemporaryApmConfig(jvmInfo); | ||
| deleteTemporaryApmConfig( | ||
| jvmInfo, | ||
| (e, apmConfig) -> logger.error("failed to delete temporary APM config file [{}], reason: [{}]", apmConfig, e.getMessage()) | ||
| ); | ||
|
|
||
| this.pluginsService = pluginServiceCtor.apply(tmpSettings); | ||
| final Settings settings = mergePluginSettings(pluginsService.pluginMap(), tmpSettings); | ||
|
|
@@ -1134,24 +1138,23 @@ protected Node( | |
| * If the JVM was started with the Elastic APM agent and a config file argument was specified, then | ||
| * delete the config file. The agent only reads it once, when supplied in this fashion, and it | ||
| * may contain a secret token. | ||
| * <p> | ||
| * Public for testing only | ||
| */ | ||
| @SuppressForbidden(reason = "Cannot guarantee that the temp config path is relative to the environment") | ||
| private void deleteTemporaryApmConfig(JvmInfo jvmInfo) { | ||
| public static void deleteTemporaryApmConfig(JvmInfo jvmInfo, BiConsumer<Exception, Path> errorHandler) { | ||
| for (String inputArgument : jvmInfo.getInputArguments()) { | ||
| if (inputArgument.startsWith("-javaagent:")) { | ||
| final String agentArg = inputArgument.substring(11); | ||
| final String[] parts = agentArg.split("=", 2); | ||
| if (parts[0].matches("modules/x-pack-apm-integration/elastic-apm-agent-\\d+\\.\\d+\\.\\d+\\.jar")) { | ||
| if (parts[0].matches(".*modules/apm/elastic-apm-agent-\\d+\\.\\d+\\.\\d+\\.jar")) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had to add |
||
| if (parts.length == 2 && parts[1].startsWith("c=")) { | ||
| final Path apmConfig = PathUtils.get(parts[1].substring(2)); | ||
| if (apmConfig.getFileName().toString().matches("^\\.elstcapm\\..*\\.tmp")) { | ||
| try { | ||
| Files.deleteIfExists(apmConfig); | ||
| } catch (IOException e) { | ||
| logger.error( | ||
| "Failed to delete temporary APM config file [" + apmConfig + "], reason: [" + e.getMessage() + "]", | ||
| e | ||
| ); | ||
| errorHandler.accept(e, apmConfig); | ||
| } | ||
| } | ||
| } | ||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These small methods are extracted so that I can prove the tests use whatever is used in the code (given we have the cross file dependency with Node).