-
Notifications
You must be signed in to change notification settings - Fork 25k
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
Prevent downgrades from 8.x to 7.x #78586
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,21 +25,21 @@ | |
import org.elasticsearch.cluster.metadata.IndexMetadata; | ||
import org.elasticsearch.cluster.node.DiscoveryNode; | ||
import org.elasticsearch.cluster.node.DiscoveryNodeRole; | ||
import org.elasticsearch.core.CheckedFunction; | ||
import org.elasticsearch.core.CheckedRunnable; | ||
import org.elasticsearch.common.Randomness; | ||
import org.elasticsearch.core.SuppressForbidden; | ||
import org.elasticsearch.common.UUIDs; | ||
import org.elasticsearch.core.Tuple; | ||
import org.elasticsearch.common.io.FileSystemUtils; | ||
import org.elasticsearch.core.Releasable; | ||
import org.elasticsearch.common.settings.Setting; | ||
import org.elasticsearch.common.settings.Setting.Property; | ||
import org.elasticsearch.common.settings.Settings; | ||
import org.elasticsearch.common.unit.ByteSizeValue; | ||
import org.elasticsearch.core.TimeValue; | ||
import org.elasticsearch.common.util.set.Sets; | ||
import org.elasticsearch.common.xcontent.NamedXContentRegistry; | ||
import org.elasticsearch.core.CheckedFunction; | ||
import org.elasticsearch.core.CheckedRunnable; | ||
import org.elasticsearch.core.Releasable; | ||
import org.elasticsearch.core.SuppressForbidden; | ||
import org.elasticsearch.core.TimeValue; | ||
import org.elasticsearch.core.Tuple; | ||
import org.elasticsearch.core.internal.io.IOUtils; | ||
import org.elasticsearch.gateway.MetadataStateFormat; | ||
import org.elasticsearch.gateway.PersistedClusterStateService; | ||
|
@@ -55,6 +55,7 @@ | |
import java.io.Closeable; | ||
import java.io.IOException; | ||
import java.io.UncheckedIOException; | ||
import java.nio.charset.StandardCharsets; | ||
import java.nio.file.AtomicMoveNotSupportedException; | ||
import java.nio.file.DirectoryStream; | ||
import java.nio.file.FileStore; | ||
|
@@ -269,6 +270,16 @@ public NodeEnvironment(Settings settings, Environment environment) throws IOExce | |
assertCanWrite(); | ||
} | ||
|
||
// versions 7.x and earlier put their data under ${path.data}/nodes/; leave a file at that location to prevent downgrades | ||
final Path legacyNodesPath = environment.dataFile().resolve("nodes"); | ||
if (Files.isRegularFile(legacyNodesPath) == false) { | ||
final String content = "written by Elasticsearch v" + Version.CURRENT + | ||
" to prevent a downgrade to a version prior to v8.0.0 which would result in data loss"; | ||
Files.write(legacyNodesPath, content.getBytes(StandardCharsets.UTF_8)); | ||
IOUtils.fsync(legacyNodesPath, false); | ||
IOUtils.fsync(environment.dataFile(), true); | ||
} | ||
|
||
if (DiscoveryNode.canContainData(settings) == false) { | ||
if (DiscoveryNode.isMasterNode(settings) == false) { | ||
ensureNoIndexMetadata(nodePath); | ||
|
@@ -408,12 +419,11 @@ private static boolean upgradeLegacyNodeFolders(Logger logger, Settings settings | |
IOUtils.fsync(nodePath.path, true); | ||
}); | ||
|
||
// now do the actual upgrade. start by upgrading the node metadata file before moving anything, since a downgrade in an | ||
// intermediate state would be pretty disastrous | ||
loadNodeMetadata(settings, logger, legacyNodeLock.getNodePath()); | ||
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. This doesn't actually do anything any more, the node metadata is in the persisted cluster state which doesn't get written until much later. |
||
// now do the actual upgrade | ||
for (CheckedRunnable<IOException> upgradeAction : upgradeActions) { | ||
upgradeAction.run(); | ||
} | ||
|
||
} finally { | ||
legacyNodeLock.close(); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,6 +26,7 @@ | |
import org.elasticsearch.test.NodeRoles; | ||
|
||
import java.io.IOException; | ||
import java.nio.charset.StandardCharsets; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.util.ArrayList; | ||
|
@@ -41,6 +42,7 @@ | |
import static org.elasticsearch.test.NodeRoles.nonDataNode; | ||
import static org.elasticsearch.test.NodeRoles.nonMasterNode; | ||
import static org.hamcrest.CoreMatchers.equalTo; | ||
import static org.hamcrest.Matchers.allOf; | ||
import static org.hamcrest.Matchers.containsString; | ||
import static org.hamcrest.Matchers.empty; | ||
import static org.hamcrest.Matchers.startsWith; | ||
|
@@ -439,6 +441,23 @@ public void testEnsureNoShardDataOrIndexMetadata() throws IOException { | |
verifyFailsOnShardData(noDataNoMasterSettings, indexPath, shardDataDirName); | ||
} | ||
|
||
public void testBlocksDowngradeToVersionWithMultipleNodesInDataPath() throws IOException { | ||
final Settings settings = buildEnvSettings(Settings.EMPTY); | ||
for (int i = 0; i < 2; i++) { // ensure the file gets created again if missing | ||
try (NodeEnvironment env = newNodeEnvironment(settings)) { | ||
final Path nodesPath = env.nodeDataPath().resolve("nodes"); | ||
assertTrue(Files.isRegularFile(nodesPath)); | ||
assertThat( | ||
Files.readString(nodesPath, StandardCharsets.UTF_8), | ||
allOf( | ||
containsString("written by Elasticsearch"), | ||
containsString("prevent a downgrade"), | ||
containsString("data loss"))); | ||
Files.delete(nodesPath); | ||
} | ||
} | ||
} | ||
|
||
private void verifyFailsOnShardData(Settings settings, Path indexPath, String shardDataDirName) { | ||
IllegalStateException ex = expectThrows(IllegalStateException.class, | ||
"Must fail creating NodeEnvironment on a data path that has shard data if node does not have data role", | ||
|
@@ -459,17 +478,6 @@ private void verifyFailsOnMetadata(Settings settings, Path indexPath) { | |
assertThat(ex.getMessage(), startsWith("node does not have the data and master roles but has index metadata")); | ||
} | ||
|
||
/** | ||
* Converts an array of Strings to an array of Paths, adding an additional child if specified | ||
*/ | ||
private Path[] stringsToPaths(String[] strings, String additional) { | ||
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. Unrelated, but unused. |
||
Path[] locations = new Path[strings.length]; | ||
for (int i = 0; i < strings.length; i++) { | ||
locations[i] = PathUtils.get(strings[i], additional); | ||
} | ||
return locations; | ||
} | ||
|
||
@Override | ||
public NodeEnvironment newNodeEnvironment() throws IOException { | ||
return newNodeEnvironment(Settings.EMPTY); | ||
|
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.
I wonder if we should follow-up with a 7.16 change to log the first 256 bytes of the file if
nodes
is a file and not a directory?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.
heh yeah I suppose we could at least help 7.16 users here :)