Add `path.shared_data`

This allows `path.shared_data` to be added to the security manager while
still allowing a custom `data_path` for indices using shadow replicas.

For example, configuring `path.shared_data: /tmp/foo`, then created an
index with:

```
POST /myindex
{
  "index": {
    "number_of_shards": 1,
    "number_of_replicas": 1,
    "data_path": "/tmp/foo/bar/baz",
    "shadow_replicas": true
  }
}
```

The index will then reside in `/tmp/foo/bar/baz`.

`path.shared_data` defaults to `null` if not specified.

Resolves #12714
Relates to #11065
This commit is contained in:
Lee Hinman 2015-08-07 06:27:35 -06:00
parent bec07a7eb5
commit ff5ad39c7a
12 changed files with 143 additions and 62 deletions

View File

@ -126,6 +126,9 @@ final class Security {
// read-write dirs // read-write dirs
addPath(policy, environment.tmpFile(), "read,readlink,write,delete"); addPath(policy, environment.tmpFile(), "read,readlink,write,delete");
addPath(policy, environment.logsFile(), "read,readlink,write,delete"); addPath(policy, environment.logsFile(), "read,readlink,write,delete");
if (environment.sharedDataFile() != null) {
addPath(policy, environment.sharedDataFile(), "read,readlink,write,delete");
}
for (Path path : environment.dataFiles()) { for (Path path : environment.dataFiles()) {
addPath(policy, path, "read,readlink,write,delete"); addPath(policy, path, "read,readlink,write,delete");
} }

View File

@ -50,12 +50,14 @@ import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.compress.CompressedXContent; import org.elasticsearch.common.compress.CompressedXContent;
import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.io.FileSystemUtils; import org.elasticsearch.common.io.FileSystemUtils;
import org.elasticsearch.common.io.PathUtils;
import org.elasticsearch.common.io.Streams; import org.elasticsearch.common.io.Streams;
import org.elasticsearch.common.regex.Regex; import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.env.Environment;
import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.env.NodeEnvironment;
import org.elasticsearch.index.Index; import org.elasticsearch.index.Index;
import org.elasticsearch.index.mapper.DocumentMapper; import org.elasticsearch.index.mapper.DocumentMapper;
@ -99,12 +101,14 @@ public class MetaDataCreateIndexService extends AbstractComponent {
private final AliasValidator aliasValidator; private final AliasValidator aliasValidator;
private final IndexTemplateFilter indexTemplateFilter; private final IndexTemplateFilter indexTemplateFilter;
private final NodeEnvironment nodeEnv; private final NodeEnvironment nodeEnv;
private final Environment env;
@Inject @Inject
public MetaDataCreateIndexService(Settings settings, ThreadPool threadPool, ClusterService clusterService, public MetaDataCreateIndexService(Settings settings, ThreadPool threadPool, ClusterService clusterService,
IndicesService indicesService, AllocationService allocationService, MetaDataService metaDataService, IndicesService indicesService, AllocationService allocationService, MetaDataService metaDataService,
Version version, AliasValidator aliasValidator, Version version, AliasValidator aliasValidator,
Set<IndexTemplateFilter> indexTemplateFilters, NodeEnvironment nodeEnv) { Set<IndexTemplateFilter> indexTemplateFilters, Environment env,
NodeEnvironment nodeEnv) {
super(settings); super(settings);
this.threadPool = threadPool; this.threadPool = threadPool;
this.clusterService = clusterService; this.clusterService = clusterService;
@ -114,6 +118,7 @@ public class MetaDataCreateIndexService extends AbstractComponent {
this.version = version; this.version = version;
this.aliasValidator = aliasValidator; this.aliasValidator = aliasValidator;
this.nodeEnv = nodeEnv; this.nodeEnv = nodeEnv;
this.env = env;
if (indexTemplateFilters.isEmpty()) { if (indexTemplateFilters.isEmpty()) {
this.indexTemplateFilter = DEFAULT_INDEX_TEMPLATE_FILTER; this.indexTemplateFilter = DEFAULT_INDEX_TEMPLATE_FILTER;
@ -511,8 +516,13 @@ public class MetaDataCreateIndexService extends AbstractComponent {
public void validateIndexSettings(String indexName, Settings settings) throws IndexCreationException { public void validateIndexSettings(String indexName, Settings settings) throws IndexCreationException {
String customPath = settings.get(IndexMetaData.SETTING_DATA_PATH, null); String customPath = settings.get(IndexMetaData.SETTING_DATA_PATH, null);
List<String> validationErrors = Lists.newArrayList(); List<String> validationErrors = Lists.newArrayList();
if (customPath != null && nodeEnv.isCustomPathsEnabled() == false) { if (customPath != null && env.sharedDataFile() == null) {
validationErrors.add("custom data_paths for indices is disabled"); validationErrors.add("path.shared_data must be set in order to use custom data paths");
} else if (customPath != null) {
Path resolvedPath = PathUtils.get(new Path[]{env.sharedDataFile()}, customPath);
if (resolvedPath == null) {
validationErrors.add("custom path [" + customPath + "] is not a sub-path of path.shared_data [" + env.sharedDataFile() + "]");
}
} }
Integer number_of_primaries = settings.getAsInt(IndexMetaData.SETTING_NUMBER_OF_SHARDS, null); Integer number_of_primaries = settings.getAsInt(IndexMetaData.SETTING_NUMBER_OF_SHARDS, null);
Integer number_of_replicas = settings.getAsInt(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, null); Integer number_of_replicas = settings.getAsInt(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, null);

View File

@ -57,6 +57,8 @@ public class Environment {
private final Path pluginsFile; private final Path pluginsFile;
private final Path sharedDataFile;
/** location of bin/, used by plugin manager */ /** location of bin/, used by plugin manager */
private final Path binFile; private final Path binFile;
@ -126,6 +128,11 @@ public class Environment {
dataFiles = new Path[]{homeFile.resolve("data")}; dataFiles = new Path[]{homeFile.resolve("data")};
dataWithClusterFiles = new Path[]{homeFile.resolve("data").resolve(ClusterName.clusterNameFromSettings(settings).value())}; dataWithClusterFiles = new Path[]{homeFile.resolve("data").resolve(ClusterName.clusterNameFromSettings(settings).value())};
} }
if (settings.get("path.shared_data") != null) {
sharedDataFile = PathUtils.get(cleanPath(settings.get("path.shared_data")));
} else {
sharedDataFile = null;
}
String[] repoPaths = settings.getAsArray("path.repo"); String[] repoPaths = settings.getAsArray("path.repo");
if (repoPaths.length > 0) { if (repoPaths.length > 0) {
repoFiles = new Path[repoPaths.length]; repoFiles = new Path[repoPaths.length];
@ -165,6 +172,13 @@ public class Environment {
return dataFiles; return dataFiles;
} }
/**
* The shared data location
*/
public Path sharedDataFile() {
return sharedDataFile;
}
/** /**
* The data location with the cluster name as a sub directory. * The data location with the cluster name as a sub directory.
*/ */

View File

@ -105,6 +105,7 @@ public class NodeEnvironment extends AbstractComponent implements Closeable {
} }
private final NodePath[] nodePaths; private final NodePath[] nodePaths;
private final Path sharedDataPath;
private final Lock[] locks; private final Lock[] locks;
private final boolean addNodeId; private final boolean addNodeId;
@ -137,6 +138,7 @@ public class NodeEnvironment extends AbstractComponent implements Closeable {
if (!DiscoveryNode.nodeRequiresLocalStorage(settings)) { if (!DiscoveryNode.nodeRequiresLocalStorage(settings)) {
nodePaths = null; nodePaths = null;
sharedDataPath = null;
locks = null; locks = null;
localNodeId = -1; localNodeId = -1;
return; return;
@ -144,6 +146,7 @@ public class NodeEnvironment extends AbstractComponent implements Closeable {
final NodePath[] nodePaths = new NodePath[environment.dataWithClusterFiles().length]; final NodePath[] nodePaths = new NodePath[environment.dataWithClusterFiles().length];
final Lock[] locks = new Lock[nodePaths.length]; final Lock[] locks = new Lock[nodePaths.length];
sharedDataPath = environment.sharedDataFile();
int localNodeId = -1; int localNodeId = -1;
IOException lastException = null; IOException lastException = null;
@ -792,7 +795,6 @@ public class NodeEnvironment extends AbstractComponent implements Closeable {
* *
* @param indexSettings settings for the index * @param indexSettings settings for the index
*/ */
@SuppressForbidden(reason = "Lee is working on it: https://github.com/elastic/elasticsearch/pull/11065")
private Path resolveCustomLocation(@IndexSettings Settings indexSettings) { private Path resolveCustomLocation(@IndexSettings Settings indexSettings) {
assert indexSettings != Settings.EMPTY; assert indexSettings != Settings.EMPTY;
String customDataDir = indexSettings.get(IndexMetaData.SETTING_DATA_PATH); String customDataDir = indexSettings.get(IndexMetaData.SETTING_DATA_PATH);
@ -800,9 +802,9 @@ public class NodeEnvironment extends AbstractComponent implements Closeable {
// This assert is because this should be caught by MetaDataCreateIndexService // This assert is because this should be caught by MetaDataCreateIndexService
assert customPathsEnabled; assert customPathsEnabled;
if (addNodeId) { if (addNodeId) {
return PathUtils.get(customDataDir).resolve(Integer.toString(this.localNodeId)); return sharedDataPath.resolve(customDataDir).resolve(Integer.toString(this.localNodeId));
} else { } else {
return PathUtils.get(customDataDir); return sharedDataPath.resolve(customDataDir);
} }
} else { } else {
throw new IllegalArgumentException("no custom " + IndexMetaData.SETTING_DATA_PATH + " setting available"); throw new IllegalArgumentException("no custom " + IndexMetaData.SETTING_DATA_PATH + " setting available");

View File

@ -77,6 +77,7 @@ public class SecurityTests extends ESTestCase {
settingsBuilder.put("path.scripts", esHome.resolve("scripts").toString()); settingsBuilder.put("path.scripts", esHome.resolve("scripts").toString());
settingsBuilder.put("path.plugins", esHome.resolve("plugins").toString()); settingsBuilder.put("path.plugins", esHome.resolve("plugins").toString());
settingsBuilder.putArray("path.data", esHome.resolve("data1").toString(), esHome.resolve("data2").toString()); settingsBuilder.putArray("path.data", esHome.resolve("data1").toString(), esHome.resolve("data2").toString());
settingsBuilder.put("path.shared_data", esHome.resolve("custom").toString());
settingsBuilder.put("path.logs", esHome.resolve("logs").toString()); settingsBuilder.put("path.logs", esHome.resolve("logs").toString());
settingsBuilder.put("pidfile", esHome.resolve("test.pid").toString()); settingsBuilder.put("pidfile", esHome.resolve("test.pid").toString());
Settings settings = settingsBuilder.build(); Settings settings = settingsBuilder.build();
@ -122,6 +123,7 @@ public class SecurityTests extends ESTestCase {
for (Path dataPath : environment.dataWithClusterFiles()) { for (Path dataPath : environment.dataWithClusterFiles()) {
assertExactPermissions(new FilePermission(dataPath.toString(), "read,readlink,write,delete"), permissions); assertExactPermissions(new FilePermission(dataPath.toString(), "read,readlink,write,delete"), permissions);
} }
assertExactPermissions(new FilePermission(environment.sharedDataFile().toString(), "read,readlink,write,delete"), permissions);
// logs: r/w // logs: r/w
assertExactPermissions(new FilePermission(environment.logsFile().toString(), "read,readlink,write,delete"), permissions); assertExactPermissions(new FilePermission(environment.logsFile().toString(), "read,readlink,write,delete"), permissions);
// temp dir: r/w // temp dir: r/w

View File

@ -300,7 +300,7 @@ public class NodeEnvironmentTests extends ESTestCase {
@Test @Test
public void testCustomDataPaths() throws Exception { public void testCustomDataPaths() throws Exception {
String[] dataPaths = tmpPaths(); String[] dataPaths = tmpPaths();
NodeEnvironment env = newNodeEnvironment(dataPaths, Settings.EMPTY); NodeEnvironment env = newNodeEnvironment(dataPaths, "/tmp", Settings.EMPTY);
Settings s1 = Settings.builder().put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1).build(); Settings s1 = Settings.builder().put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1).build();
Settings s2 = Settings.builder().put(IndexMetaData.SETTING_DATA_PATH, "/tmp/foo").build(); Settings s2 = Settings.builder().put(IndexMetaData.SETTING_DATA_PATH, "/tmp/foo").build();
@ -323,7 +323,7 @@ public class NodeEnvironmentTests extends ESTestCase {
env.indexPaths(i), equalTo(stringsToPaths(dataPaths, "elasticsearch/nodes/0/indices/myindex"))); env.indexPaths(i), equalTo(stringsToPaths(dataPaths, "elasticsearch/nodes/0/indices/myindex")));
env.close(); env.close();
NodeEnvironment env2 = newNodeEnvironment(dataPaths, NodeEnvironment env2 = newNodeEnvironment(dataPaths, "/tmp",
Settings.builder().put(NodeEnvironment.ADD_NODE_ID_TO_CUSTOM_PATH, false).build()); Settings.builder().put(NodeEnvironment.ADD_NODE_ID_TO_CUSTOM_PATH, false).build());
assertThat(env2.availableShardPaths(sid), equalTo(env2.availableShardPaths(sid))); assertThat(env2.availableShardPaths(sid), equalTo(env2.availableShardPaths(sid)));
@ -381,4 +381,14 @@ public class NodeEnvironmentTests extends ESTestCase {
.putArray("path.data", dataPaths).build(); .putArray("path.data", dataPaths).build();
return new NodeEnvironment(build, new Environment(build)); return new NodeEnvironment(build, new Environment(build));
} }
public NodeEnvironment newNodeEnvironment(String[] dataPaths, String sharedDataPath, Settings settings) throws IOException {
Settings build = Settings.builder()
.put(settings)
.put("path.home", createTempDir().toAbsolutePath().toString())
.put("path.shared_data", sharedDataPath)
.put(NodeEnvironment.SETTING_CUSTOM_DATA_PATH_ENABLED, true)
.putArray("path.data", dataPaths).build();
return new NodeEnvironment(build, new Environment(build));
}
} }

View File

@ -68,23 +68,44 @@ import static org.hamcrest.Matchers.*;
@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST, numDataNodes = 0) @ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST, numDataNodes = 0)
public class IndexWithShadowReplicasIT extends ESIntegTestCase { public class IndexWithShadowReplicasIT extends ESIntegTestCase {
private Settings nodeSettings() { private Settings nodeSettings(Path dataPath) {
return nodeSettings(dataPath.toString());
}
private Settings nodeSettings(String dataPath) {
return Settings.builder() return Settings.builder()
.put("node.add_id_to_custom_path", false) .put("node.add_id_to_custom_path", false)
.put("node.enable_custom_paths", true) .put("node.enable_custom_paths", true)
.put("path.shared_data", dataPath)
.put("index.store.fs.fs_lock", randomFrom("native", "simple")) .put("index.store.fs.fs_lock", randomFrom("native", "simple"))
.build(); .build();
} }
public void testCannotCreateWithBadPath() throws Exception {
Settings nodeSettings = nodeSettings("/badpath");
internalCluster().startNodesAsync(1, nodeSettings).get();
Settings idxSettings = Settings.builder()
.put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1)
.put(IndexMetaData.SETTING_DATA_PATH, "/etc/foo")
.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0).build();
try {
assertAcked(prepareCreate("foo").setSettings(idxSettings));
fail("should have failed");
} catch (IllegalArgumentException e) {
assertTrue(e.getMessage(),
e.getMessage().contains("custom path [/etc/foo] is not a sub-path of path.shared_data"));
}
}
/** /**
* Tests the case where we create an index without shadow replicas, snapshot it and then restore into * Tests the case where we create an index without shadow replicas, snapshot it and then restore into
* an index with shadow replicas enabled. * an index with shadow replicas enabled.
*/ */
public void testRestoreToShadow() throws ExecutionException, InterruptedException { public void testRestoreToShadow() throws ExecutionException, InterruptedException {
Settings nodeSettings = nodeSettings(); final Path dataPath = createTempDir();
Settings nodeSettings = nodeSettings(dataPath);
internalCluster().startNodesAsync(3, nodeSettings).get(); internalCluster().startNodesAsync(3, nodeSettings).get();
final Path dataPath = createTempDir();
Settings idxSettings = Settings.builder() Settings idxSettings = Settings.builder()
.put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1) .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1)
.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0).build(); .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0).build();
@ -137,11 +158,11 @@ public class IndexWithShadowReplicasIT extends ESIntegTestCase {
@Test @Test
public void testIndexWithFewDocuments() throws Exception { public void testIndexWithFewDocuments() throws Exception {
Settings nodeSettings = nodeSettings(); final Path dataPath = createTempDir();
Settings nodeSettings = nodeSettings(dataPath);
internalCluster().startNodesAsync(3, nodeSettings).get(); internalCluster().startNodesAsync(3, nodeSettings).get();
final String IDX = "test"; final String IDX = "test";
final Path dataPath = createTempDir();
Settings idxSettings = Settings.builder() Settings idxSettings = Settings.builder()
.put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1) .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1)
@ -200,10 +221,10 @@ public class IndexWithShadowReplicasIT extends ESIntegTestCase {
@Test @Test
public void testReplicaToPrimaryPromotion() throws Exception { public void testReplicaToPrimaryPromotion() throws Exception {
Settings nodeSettings = nodeSettings(); Path dataPath = createTempDir();
Settings nodeSettings = nodeSettings(dataPath);
String node1 = internalCluster().startNode(nodeSettings); String node1 = internalCluster().startNode(nodeSettings);
Path dataPath = createTempDir();
String IDX = "test"; String IDX = "test";
Settings idxSettings = Settings.builder() Settings idxSettings = Settings.builder()
@ -259,10 +280,10 @@ public class IndexWithShadowReplicasIT extends ESIntegTestCase {
@Test @Test
public void testPrimaryRelocation() throws Exception { public void testPrimaryRelocation() throws Exception {
Settings nodeSettings = nodeSettings(); Path dataPath = createTempDir();
Settings nodeSettings = nodeSettings(dataPath);
String node1 = internalCluster().startNode(nodeSettings); String node1 = internalCluster().startNode(nodeSettings);
Path dataPath = createTempDir();
String IDX = "test"; String IDX = "test";
Settings idxSettings = Settings.builder() Settings idxSettings = Settings.builder()
@ -320,10 +341,10 @@ public class IndexWithShadowReplicasIT extends ESIntegTestCase {
@Test @Test
public void testPrimaryRelocationWithConcurrentIndexing() throws Throwable { public void testPrimaryRelocationWithConcurrentIndexing() throws Throwable {
Settings nodeSettings = nodeSettings(); Path dataPath = createTempDir();
Settings nodeSettings = nodeSettings(dataPath);
String node1 = internalCluster().startNode(nodeSettings); String node1 = internalCluster().startNode(nodeSettings);
Path dataPath = createTempDir();
final String IDX = "test"; final String IDX = "test";
Settings idxSettings = Settings.builder() Settings idxSettings = Settings.builder()
@ -393,14 +414,15 @@ public class IndexWithShadowReplicasIT extends ESIntegTestCase {
@Test @Test
public void testPrimaryRelocationWhereRecoveryFails() throws Exception { public void testPrimaryRelocationWhereRecoveryFails() throws Exception {
Path dataPath = createTempDir();
Settings nodeSettings = Settings.builder() Settings nodeSettings = Settings.builder()
.put("node.add_id_to_custom_path", false) .put("node.add_id_to_custom_path", false)
.put("node.enable_custom_paths", true) .put("node.enable_custom_paths", true)
.put("plugin.types", MockTransportService.Plugin.class.getName()) .put("plugin.types", MockTransportService.Plugin.class.getName())
.put("path.shared_data", dataPath)
.build(); .build();
String node1 = internalCluster().startNode(nodeSettings); String node1 = internalCluster().startNode(nodeSettings);
Path dataPath = createTempDir();
final String IDX = "test"; final String IDX = "test";
Settings idxSettings = Settings.builder() Settings idxSettings = Settings.builder()
@ -490,11 +512,11 @@ public class IndexWithShadowReplicasIT extends ESIntegTestCase {
@Test @Test
public void testIndexWithShadowReplicasCleansUp() throws Exception { public void testIndexWithShadowReplicasCleansUp() throws Exception {
Settings nodeSettings = nodeSettings(); Path dataPath = createTempDir();
Settings nodeSettings = nodeSettings(dataPath);
int nodeCount = randomIntBetween(2, 5); int nodeCount = randomIntBetween(2, 5);
internalCluster().startNodesAsync(nodeCount, nodeSettings).get(); internalCluster().startNodesAsync(nodeCount, nodeSettings).get();
Path dataPath = createTempDir();
String IDX = "test"; String IDX = "test";
Settings idxSettings = Settings.builder() Settings idxSettings = Settings.builder()
@ -532,10 +554,10 @@ public class IndexWithShadowReplicasIT extends ESIntegTestCase {
*/ */
@Test @Test
public void testShadowReplicaNaturalRelocation() throws Exception { public void testShadowReplicaNaturalRelocation() throws Exception {
Settings nodeSettings = nodeSettings(); Path dataPath = createTempDir();
Settings nodeSettings = nodeSettings(dataPath);
internalCluster().startNodesAsync(2, nodeSettings).get(); internalCluster().startNodesAsync(2, nodeSettings).get();
Path dataPath = createTempDir();
String IDX = "test"; String IDX = "test";
Settings idxSettings = Settings.builder() Settings idxSettings = Settings.builder()
@ -586,10 +608,10 @@ public class IndexWithShadowReplicasIT extends ESIntegTestCase {
@Test @Test
public void testShadowReplicasUsingFieldData() throws Exception { public void testShadowReplicasUsingFieldData() throws Exception {
Settings nodeSettings = nodeSettings(); Path dataPath = createTempDir();
Settings nodeSettings = nodeSettings(dataPath);
internalCluster().startNodesAsync(3, nodeSettings).get(); internalCluster().startNodesAsync(3, nodeSettings).get();
Path dataPath = createTempDir();
String IDX = "test"; String IDX = "test";
Settings idxSettings = Settings.builder() Settings idxSettings = Settings.builder()
@ -655,7 +677,8 @@ public class IndexWithShadowReplicasIT extends ESIntegTestCase {
@Test @Test
public void testIndexOnSharedFSRecoversToAnyNode() throws Exception { public void testIndexOnSharedFSRecoversToAnyNode() throws Exception {
Settings nodeSettings = nodeSettings(); Path dataPath = createTempDir();
Settings nodeSettings = nodeSettings(dataPath);
Settings fooSettings = Settings.builder().put(nodeSettings).put("node.affinity", "foo").build(); Settings fooSettings = Settings.builder().put(nodeSettings).put("node.affinity", "foo").build();
Settings barSettings = Settings.builder().put(nodeSettings).put("node.affinity", "bar").build(); Settings barSettings = Settings.builder().put(nodeSettings).put("node.affinity", "bar").build();
@ -663,7 +686,6 @@ public class IndexWithShadowReplicasIT extends ESIntegTestCase {
final Future<List<String>> barNodes = internalCluster().startNodesAsync(2, barSettings); final Future<List<String>> barNodes = internalCluster().startNodesAsync(2, barSettings);
fooNodes.get(); fooNodes.get();
barNodes.get(); barNodes.get();
Path dataPath = createTempDir();
String IDX = "test"; String IDX = "test";
Settings includeFoo = Settings.builder() Settings includeFoo = Settings.builder()

View File

@ -42,10 +42,24 @@ import static org.hamcrest.Matchers.equalTo;
/** /**
* Tests for custom data path locations and templates * Tests for custom data path locations and templates
*/ */
@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST, numDataNodes = 0)
public class IndicesCustomDataPathIT extends ESIntegTestCase { public class IndicesCustomDataPathIT extends ESIntegTestCase {
private String path; private String path;
private Settings nodeSettings(Path dataPath) {
return nodeSettings(dataPath.toString());
}
private Settings nodeSettings(String dataPath) {
return Settings.builder()
.put("node.add_id_to_custom_path", false)
.put("node.enable_custom_paths", true)
.put("path.shared_data", dataPath)
.put("index.store.fs.fs_lock", randomFrom("native", "simple"))
.build();
}
@Before @Before
public void setup() { public void setup() {
path = createTempDir().toAbsolutePath().toString(); path = createTempDir().toAbsolutePath().toString();
@ -61,6 +75,7 @@ public class IndicesCustomDataPathIT extends ESIntegTestCase {
public void testDataPathCanBeChanged() throws Exception { public void testDataPathCanBeChanged() throws Exception {
final String INDEX = "idx"; final String INDEX = "idx";
Path root = createTempDir(); Path root = createTempDir();
internalCluster().startNodesAsync(1, nodeSettings(root));
Path startDir = root.resolve("start"); Path startDir = root.resolve("start");
Path endDir = root.resolve("end"); Path endDir = root.resolve("end");
logger.info("--> start dir: [{}]", startDir.toAbsolutePath().toString()); logger.info("--> start dir: [{}]", startDir.toAbsolutePath().toString());
@ -128,9 +143,12 @@ public class IndicesCustomDataPathIT extends ESIntegTestCase {
@Test @Test
public void testIndexCreatedWithCustomPathAndTemplate() throws Exception { public void testIndexCreatedWithCustomPathAndTemplate() throws Exception {
final String INDEX = "myindex2"; final String INDEX = "myindex2";
internalCluster().startNodesAsync(1, nodeSettings(path));
logger.info("--> creating an index with data_path [{}]", path); logger.info("--> creating an index with data_path [{}]", path);
Settings.Builder sb = Settings.builder().put(IndexMetaData.SETTING_DATA_PATH, path); Settings.Builder sb = Settings.builder()
.put(IndexMetaData.SETTING_DATA_PATH, path)
.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0);
client().admin().indices().prepareCreate(INDEX).setSettings(sb).get(); client().admin().indices().prepareCreate(INDEX).setSettings(sb).get();
ensureGreen(INDEX); ensureGreen(INDEX);

View File

@ -30,8 +30,10 @@ import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.client.HttpClients;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.routing.UnassignedInfo; import org.elasticsearch.cluster.routing.UnassignedInfo;
import org.elasticsearch.cluster.routing.allocation.decider.EnableAllocationDecider; import org.elasticsearch.cluster.routing.allocation.decider.EnableAllocationDecider;
import org.elasticsearch.env.NodeEnvironment;
import org.elasticsearch.index.shard.MergeSchedulerConfig; import org.elasticsearch.index.shard.MergeSchedulerConfig;
import org.apache.lucene.util.IOUtils; import org.apache.lucene.util.IOUtils;
import org.apache.lucene.util.LuceneTestCase; import org.apache.lucene.util.LuceneTestCase;
@ -691,15 +693,12 @@ public abstract class ESIntegTestCase extends ESTestCase {
if (numberOfReplicas >= 0) { if (numberOfReplicas >= 0) {
builder.put(SETTING_NUMBER_OF_REPLICAS, numberOfReplicas).build(); builder.put(SETTING_NUMBER_OF_REPLICAS, numberOfReplicas).build();
} }
// norelease: disabled because custom data paths don't play well against
// an external test cluster: the security manager is not happy that random
// files are touched. See http://build-us-00.elastic.co/job/es_core_master_strong/4357/console
// 30% of the time // 30% of the time
// if (randomInt(9) < 3) { if (randomInt(9) < 3) {
// final Path dataPath = createTempDir(); final String dataPath = randomAsciiOfLength(10);
// logger.info("using custom data_path for index: [{}]", dataPath); logger.info("using custom data_path for index: [{}]", dataPath);
// builder.put(IndexMetaData.SETTING_DATA_PATH, dataPath); builder.put(IndexMetaData.SETTING_DATA_PATH, dataPath);
// } }
return builder.build(); return builder.build();
} }
@ -1616,6 +1615,7 @@ public abstract class ESIntegTestCase extends ESTestCase {
// from failing on nodes without enough disk space // from failing on nodes without enough disk space
.put(DiskThresholdDecider.CLUSTER_ROUTING_ALLOCATION_LOW_DISK_WATERMARK, "1b") .put(DiskThresholdDecider.CLUSTER_ROUTING_ALLOCATION_LOW_DISK_WATERMARK, "1b")
.put(DiskThresholdDecider.CLUSTER_ROUTING_ALLOCATION_HIGH_DISK_WATERMARK, "1b") .put(DiskThresholdDecider.CLUSTER_ROUTING_ALLOCATION_HIGH_DISK_WATERMARK, "1b")
.put(NodeEnvironment.SETTING_CUSTOM_DATA_PATH_ENABLED, true)
.put("script.indexed", "on") .put("script.indexed", "on")
.put("script.inline", "on") .put("script.inline", "on")
// wait short time for other active shards before actually deleting, default 30s not needed in tests // wait short time for other active shards before actually deleting, default 30s not needed in tests

View File

@ -35,6 +35,7 @@ import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.common.util.concurrent.EsExecutors; import org.elasticsearch.common.util.concurrent.EsExecutors;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.env.NodeEnvironment;
import org.elasticsearch.index.IndexService; import org.elasticsearch.index.IndexService;
import org.elasticsearch.indices.IndicesService; import org.elasticsearch.indices.IndicesService;
import org.elasticsearch.node.Node; import org.elasticsearch.node.Node;
@ -118,16 +119,20 @@ public abstract class ESSingleNodeTestCase extends ESTestCase {
private static Node newNode() { private static Node newNode() {
Node build = NodeBuilder.nodeBuilder().local(true).data(true).settings(Settings.builder() Node build = NodeBuilder.nodeBuilder().local(true).data(true).settings(Settings.builder()
.put(ClusterName.SETTING, InternalTestCluster.clusterName("single-node-cluster", randomLong())) .put(ClusterName.SETTING, InternalTestCluster.clusterName("single-node-cluster", randomLong()))
.put("path.home", createTempDir()) .put("path.home", createTempDir())
.put("node.name", nodeName()) // TODO: use a consistent data path for custom paths
.put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1) // This needs to tie into the ESIntegTestCase#indexSettings() method
.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0) .put("path.shared_data", createTempDir().getParent())
.put("script.inline", "on") .put("node.name", nodeName())
.put("script.indexed", "on") .put(NodeEnvironment.SETTING_CUSTOM_DATA_PATH_ENABLED, true)
.put(EsExecutors.PROCESSORS, 1) // limit the number of threads created .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1)
.put("http.enabled", false) .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0)
.put(InternalSettingsPreparer.IGNORE_SYSTEM_PROPERTIES_SETTING, true) // make sure we get what we set :) .put("script.inline", "on")
.put("script.indexed", "on")
.put(EsExecutors.PROCESSORS, 1) // limit the number of threads created
.put("http.enabled", false)
.put(InternalSettingsPreparer.IGNORE_SYSTEM_PROPERTIES_SETTING, true) // make sure we get what we set :)
).build(); ).build();
build.start(); build.start();
assertThat(DiscoveryNode.localNode(build.settings()), is(true)); assertThat(DiscoveryNode.localNode(build.settings()), is(true));

View File

@ -301,6 +301,7 @@ public final class InternalTestCluster extends TestCluster {
builder.put("path.data", dataPath.toString()); builder.put("path.data", dataPath.toString());
} }
} }
builder.put("path.shared_data", baseDir.resolve("custom"));
builder.put("path.home", baseDir); builder.put("path.home", baseDir);
builder.put("path.repo", baseDir.resolve("repos")); builder.put("path.repo", baseDir.resolve("repos"));
builder.put("transport.tcp.port", BASE_PORT + "-" + (BASE_PORT + 100)); builder.put("transport.tcp.port", BASE_PORT + "-" + (BASE_PORT + 100));

View File

@ -14,26 +14,20 @@ settings, you need to enable using it in elasticsearch.yml:
[source,yaml] [source,yaml]
-------------------------------------------------- --------------------------------------------------
node.enable_custom_paths: true node.enable_custom_paths: true
node.add_id_to_custom_path: false
-------------------------------------------------- --------------------------------------------------
You will also need to disable the default security manager that Elasticsearch You will also need to indicate to the security manager where the custom indices
runs with. You can do this by either passing will be, so that the correct permissions can be applied. You can do this by
`-Des.security.manager.enabled=false` with the parameters while starting setting the `path.shared_data` setting in elasticsearch.yml:
Elasticsearch, or you can disable it in elasticsearch.yml:
[source,yaml] [source,yaml]
-------------------------------------------------- --------------------------------------------------
security.manager.enabled: false path.shared_data: /opt/data
-------------------------------------------------- --------------------------------------------------
[WARNING] This means that Elasticsearch can read and write to files in any subdirectory of
======================== the `path.shared_data` setting.
Disabling the security manager means that the Elasticsearch process is not
limited to the directories and files that it can read and write. However,
because the `index.data_path` setting is set when creating the index, the
security manager would prevent writing or reading from the index's location, so
it must be disabled.
========================
You can then create an index with a custom data path, where each node will use You can then create an index with a custom data path, where each node will use
this path for the data: this path for the data:
@ -54,7 +48,7 @@ curl -XPUT 'localhost:9200/my_index' -d '
"index" : { "index" : {
"number_of_shards" : 1, "number_of_shards" : 1,
"number_of_replicas" : 4, "number_of_replicas" : 4,
"data_path": "/var/data/my_index", "data_path": "/opt/data/my_index",
"shadow_replicas": true "shadow_replicas": true
} }
}' }'
@ -62,7 +56,7 @@ curl -XPUT 'localhost:9200/my_index' -d '
[WARNING] [WARNING]
======================== ========================
In the above example, the "/var/data/my_index" path is a shared filesystem that In the above example, the "/opt/data/my_index" path is a shared filesystem that
must be available on every node in the Elasticsearch cluster. You must also must be available on every node in the Elasticsearch cluster. You must also
ensure that the Elasticsearch process has the correct permissions to read from ensure that the Elasticsearch process has the correct permissions to read from
and write to the directory used in the `index.data_path` setting. and write to the directory used in the `index.data_path` setting.