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:
parent
bec07a7eb5
commit
ff5ad39c7a
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue