Browse Source

Make data directories work with symlinks again (#85878)

Nikola Grcevski 3 years ago
parent
commit
189fc680e0

+ 6 - 0
docs/changelog/85878.yaml

@@ -0,0 +1,6 @@
+pr: 85878
+summary: Make data directories work with symlinks again
+area: Infra/Core
+type: bug
+issues:
+ - 85701

+ 26 - 0
qa/os/src/test/java/org/elasticsearch/packaging/test/ConfigurationTests.java

@@ -14,6 +14,11 @@ import org.elasticsearch.packaging.util.Platforms;
 import org.elasticsearch.packaging.util.ServerUtils;
 import org.junit.Before;
 
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.stream.Stream;
+
+import static java.nio.file.attribute.PosixFilePermissions.fromString;
 import static org.elasticsearch.packaging.util.FileUtils.append;
 import static org.hamcrest.Matchers.equalTo;
 import static org.junit.Assume.assumeFalse;
@@ -56,4 +61,25 @@ public class ConfigurationTests extends PackagingTestCase {
             Platforms.onWindows(() -> sh.chown(confPath));
         });
     }
+
+    public void test30SymlinkedDataPath() throws Exception {
+        Path data = createTempDir("temp-data");
+        // Make the data directory writeable
+        Platforms.onLinux(() -> Files.setPosixFilePermissions(data, fromString("rwxrwxrwx")));
+        Path symlinkedData = createTempDir("symlink-data");
+        Files.delete(symlinkedData); // delete so we can replace it with a symlink
+        Files.createSymbolicLink(symlinkedData, data);
+
+        withCustomConfig(confPath -> {
+            ServerUtils.addSettingToExistingConfiguration(confPath, "path.data", symlinkedData.toString());
+            // security auto-config requires that the archive owner and the node process user be the same
+            Platforms.onWindows(() -> sh.chown(confPath, installation.getOwner()));
+            assertWhileRunning(() -> {
+                try (Stream<Path> entries = Files.list(data)) {
+                    assertTrue(entries.findFirst().isPresent());
+                }
+            });
+        });
+    }
+
 }

+ 0 - 1
qa/os/src/test/java/org/elasticsearch/packaging/test/PackagingTestCase.java

@@ -702,5 +702,4 @@ public abstract class PackagingTestCase extends Assert {
             assertThat(caCert.toString(), Matchers.not(Matchers.containsString("certs")));
         }
     }
-
 }

+ 6 - 0
server/src/main/java/org/elasticsearch/env/NodeEnvironment.java

@@ -262,6 +262,12 @@ public final class NodeEnvironment implements Closeable {
             sharedDataPath = environment.sharedDataFile();
 
             for (Path path : environment.dataFiles()) {
+                if (Files.exists(path)) {
+                    // Call to toRealPath required to resolve symlinks.
+                    // We let it fall through to create directories to ensure the symlink
+                    // isn't a file instead of a directory.
+                    path = path.toRealPath();
+                }
                 Files.createDirectories(path);
             }
 

+ 1 - 0
server/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy

@@ -114,4 +114,5 @@ grant codeBase "${codebase.netty-transport}" {
 
 grant {
   permission java.net.SocketPermission "127.0.0.1", "connect, resolve";
+  permission java.nio.file.LinkPermission "symbolic";
 };

+ 12 - 0
server/src/test/java/org/elasticsearch/env/NodeEnvironmentTests.java

@@ -597,6 +597,18 @@ public class NodeEnvironmentTests extends ESTestCase {
         }
     }
 
+    public void testSymlinkDataDirectory() throws Exception {
+        Path tempDir = createTempDir().toAbsolutePath();
+        Path dataPath = tempDir.resolve("data");
+        Files.createDirectories(dataPath);
+        Path symLinkPath = tempDir.resolve("data_symlink");
+        Files.createSymbolicLink(symLinkPath, dataPath);
+        NodeEnvironment env = newNodeEnvironment(new String[] { symLinkPath.toString() }, "/tmp", Settings.EMPTY);
+
+        assertTrue(Files.exists(symLinkPath));
+        env.close();
+    }
+
     private void verifyFailsOnShardData(Settings settings, Path indexPath, String shardDataDirName) {
         IllegalStateException ex = expectThrows(
             IllegalStateException.class,