Skip to content

Commit b9e72de

Browse files
todvoramoesterheld
andauthored
Datanode: skip datadir compatibility check if already performed (#25688)
* code cleanup * Store the checked version locally in the data dir * Skip datadir check for same major versions of opensearch * Added changelog * Update data-node/src/main/java/org/graylog/datanode/bootstrap/preflight/OpensearchDataDirCompatibilityCheck.java Co-authored-by: Matthias Oesterheld <33032967+moesterheld@users.noreply.github.com> --------- Co-authored-by: Matthias Oesterheld <33032967+moesterheld@users.noreply.github.com>
1 parent 86e1390 commit b9e72de

3 files changed

Lines changed: 191 additions & 1 deletion

File tree

changelog/unreleased/pr-25688.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
type = "c"
2+
message = "Reduced the frequency of the OpenSearch data directory compatibility preflight check to trigger only on major version upgrades, skipping it for minor and patch releases. "
3+
4+
pulls = ["25688"]

data-node/src/main/java/org/graylog/datanode/bootstrap/preflight/OpensearchDataDirCompatibilityCheck.java

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,26 @@
3030
import org.slf4j.Logger;
3131
import org.slf4j.LoggerFactory;
3232

33+
import java.io.IOException;
34+
import java.nio.charset.StandardCharsets;
35+
import java.nio.file.Files;
3336
import java.nio.file.Path;
3437
import java.util.Locale;
3538

3639
public class OpensearchDataDirCompatibilityCheck implements PreflightCheck {
3740

3841
private static final Logger LOG = LoggerFactory.getLogger(OpensearchDataDirCompatibilityCheck.class);
3942

43+
/**
44+
* Name of the marker file written into the opensearch data directory after a successful compatibility check.
45+
* The file contains the opensearch version string; the path context is implicit from the file's location.
46+
*/
47+
static final String COMPATIBILITY_CHECK_FILENAME = ".dn-compat-check";
48+
4049
private final DatanodeConfiguration datanodeConfiguration;
4150
private final IndicesDirectoryParser indicesDirectoryParser;
4251
private final DirectoryReadableValidator directoryReadableValidator = new DirectoryReadableValidator();
4352

44-
4553
@Inject
4654
public OpensearchDataDirCompatibilityCheck(DatanodeConfiguration datanodeConfiguration, IndicesDirectoryParser indicesDirectoryParser) {
4755
this.datanodeConfiguration = datanodeConfiguration;
@@ -54,19 +62,54 @@ public void runCheck() throws PreflightCheckException {
5462
final Path opensearchDataDir = datanodeConfiguration.datanodeDirectories().getDataTargetDir();
5563
final String opensearchVersion = datanodeConfiguration.opensearchDistributionProvider().get().version();
5664

65+
// We want to run the compatibility check only once per major opensearch version for this data dir. Let's memorize
66+
// the run and skip every time we are starting with the same major version in the same data dir.
67+
// A change in the major version will re-run the full check; minor/patch upgrades are skipped.
68+
if (isCompatibilityAlreadyVerified(opensearchDataDir, opensearchVersion)) {
69+
LOG.info("Opensearch data directory compatibility already successfully verified for data directory {} and opensearch major version {}, skipping check", opensearchDataDir, Version.fromString(opensearchVersion).major);
70+
return;
71+
}
72+
5773
try {
5874
directoryReadableValidator.validate(opensearchDataDir.toUri().toString(), opensearchDataDir);
5975
final IndexerDirectoryInformation info = indicesDirectoryParser.parse(opensearchDataDir);
6076
checkCompatibility(opensearchVersion, info);
6177
final int indicesCount = info.nodes().stream().mapToInt(n -> n.indices().size()).sum();
6278
LOG.info("Found {} indices and all of them are valid with current opensearch version {}", indicesCount, opensearchVersion);
79+
// The check succeeded, let's remember this configuration and skip next time
80+
writeCompatibilityCheckResult(opensearchDataDir, opensearchVersion);
6381
} catch (IncompatibleIndexVersionException e) {
6482
throw new PreflightCheckException("Index directory is not compatible with current version " + opensearchVersion + " of Opensearch, terminating.", e);
6583
} catch (ValidationException e) {
6684
throw new PreflightCheckException(e);
6785
}
6886
}
6987

88+
private boolean isCompatibilityAlreadyVerified(Path opensearchDataDir, String opensearchVersion) {
89+
final Path checkFile = opensearchDataDir.resolve(COMPATIBILITY_CHECK_FILENAME);
90+
if (!Files.exists(checkFile)) {
91+
return false;
92+
}
93+
try {
94+
final String storedVersion = Files.readString(checkFile, StandardCharsets.UTF_8).trim();
95+
final int storedMajor = Version.fromString(storedVersion).major;
96+
final int currentMajor = Version.fromString(opensearchVersion).major;
97+
return storedMajor == currentMajor;
98+
} catch (Exception e) {
99+
LOG.warn("Failed to read compatibility check file, re-running check", e);
100+
return false;
101+
}
102+
}
103+
104+
private void writeCompatibilityCheckResult(Path opensearchDataDir, String opensearchVersion) {
105+
final Path checkFile = opensearchDataDir.resolve(COMPATIBILITY_CHECK_FILENAME);
106+
try {
107+
Files.writeString(checkFile, opensearchVersion, StandardCharsets.UTF_8);
108+
} catch (IOException e) {
109+
LOG.warn("Failed to write compatibility check result, check will re-run on next startup", e);
110+
}
111+
}
112+
70113
private void checkCompatibility(String opensearchVersion, IndexerDirectoryInformation info) {
71114
final Version currentVersion = Version.fromString(opensearchVersion);
72115
for (NodeInformation node : info.nodes()) {
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*
2+
* Copyright (C) 2020 Graylog, Inc.
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the Server Side Public License, version 1,
6+
* as published by MongoDB, Inc.
7+
*
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* Server Side Public License for more details.
12+
*
13+
* You should have received a copy of the Server Side Public License
14+
* along with this program. If not, see
15+
* <http://www.mongodb.com/licensing/server-side-public-license>.
16+
*/
17+
package org.graylog.datanode.bootstrap.preflight;
18+
19+
import org.assertj.core.api.Assertions;
20+
import org.graylog.datanode.DatanodeTestUtils;
21+
import org.graylog.datanode.OpensearchDistribution;
22+
import org.graylog.datanode.configuration.DatanodeConfiguration;
23+
import org.graylog.datanode.configuration.DatanodeDirectories;
24+
import org.graylog.datanode.filesystem.index.IncompatibleIndexVersionException;
25+
import org.graylog.datanode.filesystem.index.IndicesDirectoryParser;
26+
import org.graylog.datanode.filesystem.index.dto.IndexerDirectoryInformation;
27+
import org.graylog.datanode.filesystem.index.indexreader.ShardStatsParserImpl;
28+
import org.graylog.datanode.filesystem.index.statefile.StateFileParserImpl;
29+
import org.graylog2.bootstrap.preflight.PreflightCheckException;
30+
import org.graylog2.security.jwt.IndexerJwtAuthToken;
31+
import org.junit.jupiter.api.Test;
32+
import org.junit.jupiter.api.io.TempDir;
33+
34+
import java.io.IOException;
35+
import java.nio.charset.StandardCharsets;
36+
import java.nio.file.Files;
37+
import java.nio.file.Path;
38+
39+
class OpensearchDataDirCompatibilityCheckTest {
40+
41+
private static final String OPENSEARCH_VERSION = "2.19.0";
42+
43+
@Test
44+
void testCompatibilityCheckSkipping(@TempDir Path tempDir) throws IOException {
45+
writeCheckFile(tempDir, OPENSEARCH_VERSION);
46+
47+
final IndicesDirectoryParser parser = new IndicesDirectoryParser(null, null) {
48+
@Override
49+
public IndexerDirectoryInformation parse(Path path) {
50+
throw new AssertionError("Should not be called");
51+
}
52+
};
53+
54+
final OpensearchDataDirCompatibilityCheck check =
55+
new OpensearchDataDirCompatibilityCheck(configFor(tempDir, OPENSEARCH_VERSION), parser);
56+
57+
Assertions.assertThatCode(check::runCheck).doesNotThrowAnyException();
58+
}
59+
60+
@Test
61+
void testCompatibilityCheckInitialRun(@TempDir Path tempDir) throws IOException {
62+
final OpensearchDataDirCompatibilityCheck check =
63+
new OpensearchDataDirCompatibilityCheck(configFor(tempDir, OPENSEARCH_VERSION), realParser());
64+
65+
Assertions.assertThatCode(check::runCheck).doesNotThrowAnyException();
66+
Assertions.assertThat(readCheckFile(tempDir)).isEqualTo(OPENSEARCH_VERSION);
67+
}
68+
69+
@Test
70+
void testCompatibilityCheckSkipsOnMinorVersionChange(@TempDir Path dataDir) throws IOException {
71+
writeCheckFile(dataDir, "2.18.0");
72+
73+
final IndicesDirectoryParser parser = new IndicesDirectoryParser(null, null) {
74+
@Override
75+
public IndexerDirectoryInformation parse(Path path) {
76+
throw new AssertionError("Should not be called on minor version change");
77+
}
78+
};
79+
80+
final OpensearchDataDirCompatibilityCheck check =
81+
new OpensearchDataDirCompatibilityCheck(configFor(dataDir, OPENSEARCH_VERSION), parser);
82+
83+
Assertions.assertThatCode(check::runCheck).doesNotThrowAnyException();
84+
}
85+
86+
@Test
87+
void testCompatibilityCheckRerunsForMajorVersionChange(@TempDir Path dataDir) throws IOException {
88+
writeCheckFile(dataDir, "2.19.0");
89+
final String nextMajorVersion = "3.0.0";
90+
91+
final OpensearchDataDirCompatibilityCheck check =
92+
new OpensearchDataDirCompatibilityCheck(configFor(dataDir, nextMajorVersion), realParser());
93+
94+
Assertions.assertThatCode(check::runCheck).doesNotThrowAnyException();
95+
Assertions.assertThat(readCheckFile(dataDir)).isEqualTo(nextMajorVersion);
96+
}
97+
98+
@Test
99+
void testCompatibilityCheckFailsForNonExistentDirectory() {
100+
final Path nonExistentDir = Path.of("/nonexistent/opensearch/data");
101+
102+
final OpensearchDataDirCompatibilityCheck check =
103+
new OpensearchDataDirCompatibilityCheck(configFor(nonExistentDir, OPENSEARCH_VERSION), realParser());
104+
105+
Assertions.assertThatThrownBy(check::runCheck)
106+
.isInstanceOf(PreflightCheckException.class)
107+
.hasMessageContaining("nonexistent");
108+
}
109+
110+
@Test
111+
void testCompatibilityCheckFailsForIncompatibleIndexVersion(@TempDir Path dataDir) {
112+
final IndicesDirectoryParser parser = new IndicesDirectoryParser(null, null) {
113+
@Override
114+
public IndexerDirectoryInformation parse(Path path) {
115+
throw new IncompatibleIndexVersionException("Index data version is not compatible");
116+
}
117+
};
118+
119+
final OpensearchDataDirCompatibilityCheck check =
120+
new OpensearchDataDirCompatibilityCheck(configFor(dataDir, OPENSEARCH_VERSION), parser);
121+
122+
Assertions.assertThatThrownBy(check::runCheck)
123+
.isInstanceOf(PreflightCheckException.class)
124+
.hasMessageContaining("is not compatible with current version " + OPENSEARCH_VERSION);
125+
}
126+
127+
private DatanodeConfiguration configFor(Path dataDir, String opensearchVersion) {
128+
final DatanodeDirectories directories = DatanodeTestUtils.tempDirectories(dataDir);
129+
return new DatanodeConfiguration(() -> new OpensearchDistribution(Path.of("/opensearch"), opensearchVersion), directories, 0, IndexerJwtAuthToken.disabled());
130+
}
131+
132+
private IndicesDirectoryParser realParser() {
133+
return new IndicesDirectoryParser(new StateFileParserImpl(), new ShardStatsParserImpl());
134+
}
135+
136+
private void writeCheckFile(Path dataDir, String version) throws IOException {
137+
Files.writeString(dataDir.resolve(OpensearchDataDirCompatibilityCheck.COMPATIBILITY_CHECK_FILENAME), version, StandardCharsets.UTF_8);
138+
}
139+
140+
private String readCheckFile(Path dataDir) throws IOException {
141+
return Files.readString(dataDir.resolve(OpensearchDataDirCompatibilityCheck.COMPATIBILITY_CHECK_FILENAME), StandardCharsets.UTF_8);
142+
}
143+
}

0 commit comments

Comments
 (0)