Move metadata storage to Lucene (#50928)

* Move metadata storage to Lucene (#50907)

Today we split the on-disk cluster metadata across many files: one file for the metadata of each
index, plus one file for the global metadata and another for the manifest. Most metadata updates
only touch a few of these files, but some must write them all. If a node holds a large number of
indices then it's possible its disks are not fast enough to process a complete metadata update before timing out. In severe cases affecting master-eligible nodes this can prevent an election
from succeeding.

This commit uses Lucene as a metadata storage for the cluster state, and is a squashed version
of the following PRs that were targeting a feature branch:

* Introduce Lucene-based metadata persistence (#48733)

This commit introduces `LucenePersistedState` which master-eligible nodes
can use to persist the cluster metadata in a Lucene index rather than in
many separate files.

Relates #48701

* Remove per-index metadata without assigned shards (#49234)

Today on master-eligible nodes we maintain per-index metadata files for every
index. However, we also keep this metadata in the `LucenePersistedState`, and
only use the per-index metadata files for importing dangling indices. However
there is no point in importing a dangling index without any shard data, so we
do not need to maintain these extra files any more.

This commit removes per-index metadata files from nodes which do not hold any
shards of those indices.

Relates #48701

* Use Lucene exclusively for metadata storage (#50144)

This moves metadata persistence to Lucene for all node types. It also reenables BWC and adds
an interoperability layer for upgrades from prior versions.

This commit disables a number of tests related to dangling indices and command-line tools.
Those will be addressed in follow-ups.

Relates #48701

* Add command-line tool support for Lucene-based metadata storage (#50179)

Adds command-line tool support (unsafe-bootstrap, detach-cluster, repurpose, & shard
commands) for the Lucene-based metadata storage.

Relates #48701

* Use single directory for metadata (#50639)

Earlier PRs for #48701 introduced a separate directory for the cluster state. This is not needed
though, and introduces an additional unnecessary cognitive burden to the users.

Co-Authored-By: David Turner <david.turner@elastic.co>

* Add async dangling indices support (#50642)

Adds support for writing out dangling indices in an asynchronous way. Also provides an option to
avoid writing out dangling indices at all.

Relates #48701

* Fold node metadata into new node storage (#50741)

Moves node metadata to uses the new storage mechanism (see #48701) as the authoritative source.

* Write CS asynchronously on data-only nodes (#50782)

Writes cluster states out asynchronously on data-only nodes. The main reason for writing out
the cluster state at all is so that the data-only nodes can snap into a cluster, that they can do a
bit of bootstrap validation and so that the shard recovery tools work.
Cluster states that are written asynchronously have their voting configuration adapted to a non
existing configuration so that these nodes cannot mistakenly become master even if their node
role is changed back and forth.

Relates #48701

* Remove persistent cluster settings tool (#50694)

Adds the elasticsearch-node remove-settings tool to remove persistent settings from the on
disk cluster state in case where it contains incompatible settings that prevent the cluster from
forming.

Relates #48701

* Make cluster state writer resilient to disk issues (#50805)

Adds handling to make the cluster state writer resilient to disk issues. Relates to #48701

* Omit writing global metadata if no change (#50901)

Uses the same optimization for the new cluster state storage layer as the old one, writing global
metadata only when changed. Avoids writing out the global metadata if none of the persistent
fields changed. Speeds up server:integTest by ~10%.

Relates #48701

* DanglingIndicesIT should ensure node removed first (#50896)

These tests occasionally failed because the deletion was submitted before the
restarting node was removed from the cluster, causing the deletion not to be
fully acked. This commit fixes this by checking the restarting node has been
removed from the cluster.

Co-authored-by: David Turner <david.turner@elastic.co>

* fix tests

Co-authored-by: David Turner <david.turner@elastic.co>
This commit is contained in:
Yannick Welsch 2020-01-14 09:35:43 +01:00 committed by GitHub
parent b02b073a57
commit 22ba759e1f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 3444 additions and 980 deletions

View File

@ -3,9 +3,9 @@
The `elasticsearch-node` command enables you to perform certain unsafe The `elasticsearch-node` command enables you to perform certain unsafe
operations on a node that are only possible while it is shut down. This command operations on a node that are only possible while it is shut down. This command
allows you to adjust the <<modules-node,role>> of a node and may be able to allows you to adjust the <<modules-node,role>> of a node, unsafely edit cluster
recover some data after a disaster or start a node even if it is incompatible settings and may be able to recover some data after a disaster or start a node
with the data on disk. even if it is incompatible with the data on disk.
[float] [float]
=== Synopsis === Synopsis
@ -20,13 +20,17 @@ bin/elasticsearch-node repurpose|unsafe-bootstrap|detach-cluster|override-versio
[float] [float]
=== Description === Description
This tool has four modes: This tool has five modes:
* `elasticsearch-node repurpose` can be used to delete unwanted data from a * `elasticsearch-node repurpose` can be used to delete unwanted data from a
node if it used to be a <<data-node,data node>> or a node if it used to be a <<data-node,data node>> or a
<<master-node,master-eligible node>> but has been repurposed not to have one <<master-node,master-eligible node>> but has been repurposed not to have one
or other of these roles. or other of these roles.
* `elasticsearch-node remove-settings` can be used to remove persistent settings
from the cluster state in case where it contains incompatible settings that
prevent the cluster from forming.
* `elasticsearch-node unsafe-bootstrap` can be used to perform _unsafe cluster * `elasticsearch-node unsafe-bootstrap` can be used to perform _unsafe cluster
bootstrapping_. It forces one of the nodes to form a brand-new cluster on bootstrapping_. It forces one of the nodes to form a brand-new cluster on
its own, using its local copy of the cluster metadata. its own, using its local copy of the cluster metadata.
@ -76,6 +80,26 @@ The tool provides a summary of the data to be deleted and asks for confirmation
before making any changes. You can get detailed information about the affected before making any changes. You can get detailed information about the affected
indices and shards by passing the verbose (`-v`) option. indices and shards by passing the verbose (`-v`) option.
[float]
==== Removing persistent cluster settings
There may be situations where a node contains persistent cluster
settings that prevent the cluster from forming. Since the cluster cannot form,
it is not possible to remove these settings using the
<<cluster-update-settings>> API.
The `elasticsearch-node remove-settings` tool allows you to forcefully remove
those persistent settings from the on-disk cluster state. The tool takes a
list of settings as parameters that should be removed, and also supports
wildcard patterns.
The intended use is:
* Stop the node
* Run `elasticsearch-node remove-settings name-of-setting-to-remove` on the node
* Repeat for all other master-eligible nodes
* Start the nodes
[float] [float]
==== Recovering data after a disaster ==== Recovering data after a disaster
@ -290,6 +314,9 @@ it can join a different cluster.
`override-version`:: Overwrites the version number stored in the data path so `override-version`:: Overwrites the version number stored in the data path so
that a node can start despite being incompatible with the on-disk data. that a node can start despite being incompatible with the on-disk data.
`remove-settings`:: Forcefully removes the provided persistent cluster settings
from the on-disk cluster state.
`--ordinal <Integer>`:: If there is <<max-local-storage-nodes,more than one `--ordinal <Integer>`:: If there is <<max-local-storage-nodes,more than one
node sharing a data path>> then this specifies which node to target. Defaults node sharing a data path>> then this specifies which node to target. Defaults
to `0`, meaning to use the first node in the data path. to `0`, meaning to use the first node in the data path.
@ -350,6 +377,40 @@ Confirm [y/N] y
Node successfully repurposed to no-master and no-data. Node successfully repurposed to no-master and no-data.
---- ----
[float]
==== Removing persistent cluster settings
If your nodes contain persistent cluster settings that prevent the cluster
from forming, i.e., can't be removed using the <<cluster-update-settings>> API,
you can run the following commands to remove one or more cluster settings.
[source,txt]
----
node$ ./bin/elasticsearch-node remove-settings xpack.monitoring.exporters.my_exporter.host
WARNING: Elasticsearch MUST be stopped before running this tool.
The following settings will be removed:
xpack.monitoring.exporters.my_exporter.host: "10.1.2.3"
You should only run this tool if you have incompatible settings in the
cluster state that prevent the cluster from forming.
This tool can cause data loss and its use should be your last resort.
Do you want to proceed?
Confirm [y/N] y
Settings were successfully removed from the cluster state
----
You can also use wildcards to remove multiple settings, for example using
[source,txt]
----
node$ ./bin/elasticsearch-node remove-settings xpack.monitoring.*
----
[float] [float]
==== Unsafe cluster bootstrapping ==== Unsafe cluster bootstrapping

View File

@ -25,6 +25,8 @@ import org.elasticsearch.cluster.coordination.CoordinationMetaData.VotingConfigu
import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.node.DiscoveryNode;
import java.io.Closeable;
import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -444,15 +446,14 @@ public class CoordinationState {
assert publishVotes.isEmpty() || electionWon(); assert publishVotes.isEmpty() || electionWon();
} }
public void close() { public void close() throws IOException {
persistedState.close(); persistedState.close();
} }
/** /**
* Pluggable persistence layer for {@link CoordinationState}. * Pluggable persistence layer for {@link CoordinationState}.
*
*/ */
public interface PersistedState { public interface PersistedState extends Closeable {
/** /**
* Returns the current term * Returns the current term
@ -511,7 +512,8 @@ public class CoordinationState {
} }
} }
default void close() {} default void close() throws IOException {
}
} }
/** /**

View File

@ -75,6 +75,7 @@ import org.elasticsearch.threadpool.ThreadPool.Names;
import org.elasticsearch.transport.TransportResponse.Empty; import org.elasticsearch.transport.TransportResponse.Empty;
import org.elasticsearch.transport.TransportService; import org.elasticsearch.transport.TransportService;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -732,7 +733,7 @@ public class Coordinator extends AbstractLifecycleComponent implements Discovery
} }
@Override @Override
protected void doClose() { protected void doClose() throws IOException {
final CoordinationState coordinationState = this.coordinationState.get(); final CoordinationState coordinationState = this.coordinationState.get();
if (coordinationState != null) { if (coordinationState != null) {
// This looks like a race that might leak an unclosed CoordinationState if it's created while execution is here, but this method // This looks like a race that might leak an unclosed CoordinationState if it's created while execution is here, but this method

View File

@ -18,11 +18,12 @@
*/ */
package org.elasticsearch.cluster.coordination; package org.elasticsearch.cluster.coordination;
import joptsimple.OptionSet;
import org.elasticsearch.cli.Terminal; import org.elasticsearch.cli.Terminal;
import org.elasticsearch.cluster.metadata.Manifest; import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.env.Environment; import org.elasticsearch.env.Environment;
import org.elasticsearch.gateway.PersistedClusterStateService;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
@ -48,14 +49,22 @@ public class DetachClusterCommand extends ElasticsearchNodeCommand {
@Override @Override
protected void processNodePaths(Terminal terminal, Path[] dataPaths, Environment env) throws IOException { protected void processNodePaths(Terminal terminal, Path[] dataPaths, int nodeLockId, OptionSet options, Environment env)
final Tuple<Manifest, MetaData> manifestMetaDataTuple = loadMetaData(terminal, dataPaths); throws IOException {
final Manifest manifest = manifestMetaDataTuple.v1(); final PersistedClusterStateService persistedClusterStateService = createPersistedClusterStateService(dataPaths);
final MetaData metaData = manifestMetaDataTuple.v2();
terminal.println(Terminal.Verbosity.VERBOSE, "Loading cluster state");
final ClusterState oldClusterState = loadTermAndClusterState(persistedClusterStateService, env).v2();
final ClusterState newClusterState = ClusterState.builder(oldClusterState)
.metaData(updateMetaData(oldClusterState.metaData())).build();
terminal.println(Terminal.Verbosity.VERBOSE,
"[old cluster state = " + oldClusterState + ", new cluster state = " + newClusterState + "]");
confirm(terminal, CONFIRMATION_MSG); confirm(terminal, CONFIRMATION_MSG);
writeNewMetaData(terminal, manifest, updateCurrentTerm(), metaData, updateMetaData(metaData), dataPaths); try (PersistedClusterStateService.Writer writer = persistedClusterStateService.createWriter()) {
writer.writeFullStateAndCommit(updateCurrentTerm(), newClusterState);
}
terminal.println(NODE_DETACHED_MSG); terminal.println(NODE_DETACHED_MSG);
} }

View File

@ -27,37 +27,48 @@ import org.apache.lucene.store.LockObtainFailedException;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.cli.EnvironmentAwareCommand; import org.elasticsearch.cli.EnvironmentAwareCommand;
import org.elasticsearch.cli.Terminal; import org.elasticsearch.cli.Terminal;
import org.elasticsearch.cluster.metadata.Manifest; import org.elasticsearch.cli.UserException;
import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.ClusterModule;
import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.env.Environment; import org.elasticsearch.env.Environment;
import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.env.NodeEnvironment;
import org.elasticsearch.env.NodeMetaData;
import org.elasticsearch.gateway.PersistedClusterStateService;
import org.elasticsearch.indices.IndicesModule;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Arrays; import java.util.Arrays;
import java.util.Objects; import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public abstract class ElasticsearchNodeCommand extends EnvironmentAwareCommand { public abstract class ElasticsearchNodeCommand extends EnvironmentAwareCommand {
private static final Logger logger = LogManager.getLogger(ElasticsearchNodeCommand.class); private static final Logger logger = LogManager.getLogger(ElasticsearchNodeCommand.class);
protected static final String DELIMITER = "------------------------------------------------------------------------\n"; protected static final String DELIMITER = "------------------------------------------------------------------------\n";
static final String STOP_WARNING_MSG = static final String STOP_WARNING_MSG =
DELIMITER + DELIMITER +
"\n" + "\n" +
" WARNING: Elasticsearch MUST be stopped before running this tool." + " WARNING: Elasticsearch MUST be stopped before running this tool." +
"\n"; "\n";
protected static final String FAILED_TO_OBTAIN_NODE_LOCK_MSG = "failed to lock node's directory, is Elasticsearch still running?"; protected static final String FAILED_TO_OBTAIN_NODE_LOCK_MSG = "failed to lock node's directory, is Elasticsearch still running?";
static final String NO_NODE_FOLDER_FOUND_MSG = "no node folder is found in data folder(s), node has not been started yet?";
static final String NO_MANIFEST_FILE_FOUND_MSG = "no manifest file is found, do you run pre 7.0 Elasticsearch?";
protected static final String GLOBAL_GENERATION_MISSING_MSG =
"no metadata is referenced from the manifest file, cluster has never been bootstrapped?";
static final String NO_GLOBAL_METADATA_MSG = "failed to find global metadata, metadata corrupted?";
static final String WRITE_METADATA_EXCEPTION_MSG = "exception occurred when writing new metadata to disk";
protected static final String ABORTED_BY_USER_MSG = "aborted by user"; protected static final String ABORTED_BY_USER_MSG = "aborted by user";
final OptionSpec<Integer> nodeOrdinalOption; final OptionSpec<Integer> nodeOrdinalOption;
static final String NO_NODE_FOLDER_FOUND_MSG = "no node folder is found in data folder(s), node has not been started yet?";
static final String NO_NODE_METADATA_FOUND_MSG = "no node meta data is found, node has not been started yet?";
protected static final String CS_MISSING_MSG =
"cluster state is empty, cluster has never been bootstrapped?";
protected static final NamedXContentRegistry namedXContentRegistry = new NamedXContentRegistry(
Stream.of(ClusterModule.getNamedXWriteables().stream(), IndicesModule.getNamedXContents().stream())
.flatMap(Function.identity())
.collect(Collectors.toList()));
public ElasticsearchNodeCommand(String description) { public ElasticsearchNodeCommand(String description) {
super(description); super(description);
@ -65,7 +76,33 @@ public abstract class ElasticsearchNodeCommand extends EnvironmentAwareCommand {
.withRequiredArg().ofType(Integer.class); .withRequiredArg().ofType(Integer.class);
} }
protected void processNodePathsWithLock(Terminal terminal, OptionSet options, Environment env) throws IOException { public static PersistedClusterStateService createPersistedClusterStateService(Path[] dataPaths) throws IOException {
final NodeMetaData nodeMetaData = PersistedClusterStateService.nodeMetaData(dataPaths);
if (nodeMetaData == null) {
throw new ElasticsearchException(NO_NODE_METADATA_FOUND_MSG);
}
String nodeId = nodeMetaData.nodeId();
return new PersistedClusterStateService(dataPaths, nodeId, namedXContentRegistry, BigArrays.NON_RECYCLING_INSTANCE, true);
}
public static ClusterState clusterState(Environment environment, PersistedClusterStateService.OnDiskState onDiskState) {
return ClusterState.builder(ClusterName.CLUSTER_NAME_SETTING.get(environment.settings()))
.version(onDiskState.lastAcceptedVersion)
.metaData(onDiskState.metaData)
.build();
}
public static Tuple<Long, ClusterState> loadTermAndClusterState(PersistedClusterStateService psf,
Environment env) throws IOException {
final PersistedClusterStateService.OnDiskState bestOnDiskState = psf.loadBestOnDiskState();
if (bestOnDiskState.empty()) {
throw new ElasticsearchException(CS_MISSING_MSG);
}
return Tuple.tuple(bestOnDiskState.currentTerm, clusterState(env, bestOnDiskState));
}
protected void processNodePaths(Terminal terminal, OptionSet options, Environment env) throws IOException, UserException {
terminal.println(Terminal.Verbosity.VERBOSE, "Obtaining lock for node"); terminal.println(Terminal.Verbosity.VERBOSE, "Obtaining lock for node");
Integer nodeOrdinal = nodeOrdinalOption.value(options); Integer nodeOrdinal = nodeOrdinalOption.value(options);
if (nodeOrdinal == null) { if (nodeOrdinal == null) {
@ -77,32 +114,12 @@ public abstract class ElasticsearchNodeCommand extends EnvironmentAwareCommand {
if (dataPaths.length == 0) { if (dataPaths.length == 0) {
throw new ElasticsearchException(NO_NODE_FOLDER_FOUND_MSG); throw new ElasticsearchException(NO_NODE_FOLDER_FOUND_MSG);
} }
processNodePaths(terminal, dataPaths, env); processNodePaths(terminal, dataPaths, nodeOrdinal, options, env);
} catch (LockObtainFailedException e) { } catch (LockObtainFailedException e) {
throw new ElasticsearchException(FAILED_TO_OBTAIN_NODE_LOCK_MSG, e); throw new ElasticsearchException(FAILED_TO_OBTAIN_NODE_LOCK_MSG, e);
} }
} }
protected Tuple<Manifest, MetaData> loadMetaData(Terminal terminal, Path[] dataPaths) throws IOException {
terminal.println(Terminal.Verbosity.VERBOSE, "Loading manifest file");
final Manifest manifest = Manifest.FORMAT.loadLatestState(logger, NamedXContentRegistry.EMPTY, dataPaths);
if (manifest == null) {
throw new ElasticsearchException(NO_MANIFEST_FILE_FOUND_MSG);
}
if (manifest.isGlobalGenerationMissing()) {
throw new ElasticsearchException(GLOBAL_GENERATION_MISSING_MSG);
}
terminal.println(Terminal.Verbosity.VERBOSE, "Loading global metadata file");
final MetaData metaData = MetaData.FORMAT_PRESERVE_CUSTOMS.loadGeneration(
logger, NamedXContentRegistry.EMPTY, manifest.getGlobalGeneration(), dataPaths);
if (metaData == null) {
throw new ElasticsearchException(NO_GLOBAL_METADATA_MSG + " [generation = " + manifest.getGlobalGeneration() + "]");
}
return Tuple.tuple(manifest, metaData);
}
protected void confirm(Terminal terminal, String msg) { protected void confirm(Terminal terminal, String msg) {
terminal.println(msg); terminal.println(msg);
String text = terminal.readText("Confirm [y/N] "); String text = terminal.readText("Confirm [y/N] ");
@ -112,10 +129,10 @@ public abstract class ElasticsearchNodeCommand extends EnvironmentAwareCommand {
} }
@Override @Override
protected final void execute(Terminal terminal, OptionSet options, Environment env) throws Exception { public final void execute(Terminal terminal, OptionSet options, Environment env) throws Exception {
terminal.println(STOP_WARNING_MSG); terminal.println(STOP_WARNING_MSG);
if (validateBeforeLock(terminal, env)) { if (validateBeforeLock(terminal, env)) {
processNodePathsWithLock(terminal, options, env); processNodePaths(terminal, options, env);
} }
} }
@ -134,33 +151,11 @@ public abstract class ElasticsearchNodeCommand extends EnvironmentAwareCommand {
* Process the paths. Locks for the paths is held during this method invocation. * Process the paths. Locks for the paths is held during this method invocation.
* @param terminal the terminal to use for messages * @param terminal the terminal to use for messages
* @param dataPaths the paths of the node to process * @param dataPaths the paths of the node to process
* @param options the command line options
* @param env the env of the node to process * @param env the env of the node to process
*/ */
protected abstract void processNodePaths(Terminal terminal, Path[] dataPaths, Environment env) throws IOException; protected abstract void processNodePaths(Terminal terminal, Path[] dataPaths, int nodeLockId, OptionSet options, Environment env)
throws IOException, UserException;
protected void writeNewMetaData(Terminal terminal, Manifest oldManifest, long newCurrentTerm,
MetaData oldMetaData, MetaData newMetaData, Path[] dataPaths) {
try {
terminal.println(Terminal.Verbosity.VERBOSE,
"[clusterUUID = " + oldMetaData.clusterUUID() + ", committed = " + oldMetaData.clusterUUIDCommitted() + "] => " +
"[clusterUUID = " + newMetaData.clusterUUID() + ", committed = " + newMetaData.clusterUUIDCommitted() + "]");
terminal.println(Terminal.Verbosity.VERBOSE, "New coordination metadata is " + newMetaData.coordinationMetaData());
terminal.println(Terminal.Verbosity.VERBOSE, "Writing new global metadata to disk");
long newGeneration = MetaData.FORMAT.write(newMetaData, dataPaths);
Manifest newManifest = new Manifest(newCurrentTerm, oldManifest.getClusterStateVersion(), newGeneration,
oldManifest.getIndexGenerations());
terminal.println(Terminal.Verbosity.VERBOSE, "New manifest is " + newManifest);
terminal.println(Terminal.Verbosity.VERBOSE, "Writing new manifest file to disk");
Manifest.FORMAT.writeAndCleanup(newManifest, dataPaths);
terminal.println(Terminal.Verbosity.VERBOSE, "Cleaning up old metadata");
MetaData.FORMAT.cleanupOldFiles(newGeneration, dataPaths);
} catch (Exception e) {
terminal.println(Terminal.Verbosity.VERBOSE, "Cleaning up new metadata");
MetaData.FORMAT.cleanupOldFiles(oldManifest.getGlobalGeneration(), dataPaths);
throw new ElasticsearchException(WRITE_METADATA_EXCEPTION_MSG, e);
}
}
protected NodeEnvironment.NodePath[] toNodePaths(Path[] dataPaths) { protected NodeEnvironment.NodePath[] toNodePaths(Path[] dataPaths) {
return Arrays.stream(dataPaths).map(ElasticsearchNodeCommand::createNodePath).toArray(NodeEnvironment.NodePath[]::new); return Arrays.stream(dataPaths).map(ElasticsearchNodeCommand::createNodePath).toArray(NodeEnvironment.NodePath[]::new);

View File

@ -41,6 +41,7 @@ public class NodeToolCli extends MultiCommand {
subcommands.put("unsafe-bootstrap", new UnsafeBootstrapMasterCommand()); subcommands.put("unsafe-bootstrap", new UnsafeBootstrapMasterCommand());
subcommands.put("detach-cluster", new DetachClusterCommand()); subcommands.put("detach-cluster", new DetachClusterCommand());
subcommands.put("override-version", new OverrideNodeVersionCommand()); subcommands.put("override-version", new OverrideNodeVersionCommand());
subcommands.put("remove-settings", new RemoveSettingsCommand());
} }
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {

View File

@ -0,0 +1,104 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.cluster.coordination;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import org.elasticsearch.cli.ExitCodes;
import org.elasticsearch.cli.Terminal;
import org.elasticsearch.cli.UserException;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.gateway.PersistedClusterStateService;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
public class RemoveSettingsCommand extends ElasticsearchNodeCommand {
static final String SETTINGS_REMOVED_MSG = "Settings were successfully removed from the cluster state";
static final String CONFIRMATION_MSG =
DELIMITER +
"\n" +
"You should only run this tool if you have incompatible settings in the\n" +
"cluster state that prevent the cluster from forming.\n" +
"This tool can cause data loss and its use should be your last resort.\n" +
"\n" +
"Do you want to proceed?\n";
private final OptionSpec<String> arguments;
public RemoveSettingsCommand() {
super("Removes persistent settings from the cluster state");
arguments = parser.nonOptions("setting names");
}
@Override
protected void processNodePaths(Terminal terminal, Path[] dataPaths, int nodeLockId, OptionSet options, Environment env)
throws IOException, UserException {
final List<String> settingsToRemove = arguments.values(options);
if (settingsToRemove.isEmpty()) {
throw new UserException(ExitCodes.USAGE, "Must supply at least one setting to remove");
}
final PersistedClusterStateService persistedClusterStateService = createPersistedClusterStateService(dataPaths);
terminal.println(Terminal.Verbosity.VERBOSE, "Loading cluster state");
final Tuple<Long, ClusterState> termAndClusterState = loadTermAndClusterState(persistedClusterStateService, env);
final ClusterState oldClusterState = termAndClusterState.v2();
final Settings oldPersistentSettings = oldClusterState.metaData().persistentSettings();
terminal.println(Terminal.Verbosity.VERBOSE, "persistent settings: " + oldPersistentSettings);
final Settings.Builder newPersistentSettingsBuilder = Settings.builder().put(oldPersistentSettings);
for (String settingToRemove : settingsToRemove) {
boolean matched = false;
for (String settingKey : oldPersistentSettings.keySet()) {
if (Regex.simpleMatch(settingToRemove, settingKey)) {
newPersistentSettingsBuilder.remove(settingKey);
if (matched == false) {
terminal.println("The following settings will be removed:");
}
matched = true;
terminal.println(settingKey + ": " + oldPersistentSettings.get(settingKey));
}
}
if (matched == false) {
throw new UserException(ExitCodes.USAGE,
"No persistent cluster settings matching [" + settingToRemove + "] were found on this node");
}
}
final ClusterState newClusterState = ClusterState.builder(oldClusterState)
.metaData(MetaData.builder(oldClusterState.metaData()).persistentSettings(newPersistentSettingsBuilder.build()).build())
.build();
terminal.println(Terminal.Verbosity.VERBOSE,
"[old cluster state = " + oldClusterState + ", new cluster state = " + newClusterState + "]");
confirm(terminal, CONFIRMATION_MSG);
try (PersistedClusterStateService.Writer writer = persistedClusterStateService.createWriter()) {
writer.writeFullStateAndCommit(termAndClusterState.v1(), newClusterState);
}
terminal.println(SETTINGS_REMOVED_MSG);
}
}

View File

@ -18,19 +18,17 @@
*/ */
package org.elasticsearch.cluster.coordination; package org.elasticsearch.cluster.coordination;
import org.apache.logging.log4j.LogManager; import joptsimple.OptionSet;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.cli.Terminal; import org.elasticsearch.cli.Terminal;
import org.elasticsearch.cluster.metadata.Manifest; import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.env.Environment; import org.elasticsearch.env.Environment;
import org.elasticsearch.env.NodeMetaData; import org.elasticsearch.gateway.PersistedClusterStateService;
import org.elasticsearch.node.Node; import org.elasticsearch.node.Node;
import java.io.IOException; import java.io.IOException;
@ -40,8 +38,6 @@ import java.util.Locale;
public class UnsafeBootstrapMasterCommand extends ElasticsearchNodeCommand { public class UnsafeBootstrapMasterCommand extends ElasticsearchNodeCommand {
private static final Logger logger = LogManager.getLogger(UnsafeBootstrapMasterCommand.class);
static final String CLUSTER_STATE_TERM_VERSION_MSG_FORMAT = static final String CLUSTER_STATE_TERM_VERSION_MSG_FORMAT =
"Current node cluster state (term, version) pair is (%s, %s)"; "Current node cluster state (term, version) pair is (%s, %s)";
static final String CONFIRMATION_MSG = static final String CONFIRMATION_MSG =
@ -58,8 +54,6 @@ public class UnsafeBootstrapMasterCommand extends ElasticsearchNodeCommand {
static final String NOT_MASTER_NODE_MSG = "unsafe-bootstrap tool can only be run on master eligible node"; static final String NOT_MASTER_NODE_MSG = "unsafe-bootstrap tool can only be run on master eligible node";
static final String NO_NODE_METADATA_FOUND_MSG = "no node meta data is found, node has not been started yet?";
static final String EMPTY_LAST_COMMITTED_VOTING_CONFIG_MSG = static final String EMPTY_LAST_COMMITTED_VOTING_CONFIG_MSG =
"last committed voting voting configuration is empty, cluster has never been bootstrapped?"; "last committed voting voting configuration is empty, cluster has never been bootstrapped?";
@ -83,19 +77,15 @@ public class UnsafeBootstrapMasterCommand extends ElasticsearchNodeCommand {
return true; return true;
} }
protected void processNodePaths(Terminal terminal, Path[] dataPaths, Environment env) throws IOException { protected void processNodePaths(Terminal terminal, Path[] dataPaths, int nodeLockId, OptionSet options, Environment env)
terminal.println(Terminal.Verbosity.VERBOSE, "Loading node metadata"); throws IOException {
final NodeMetaData nodeMetaData = NodeMetaData.FORMAT.loadLatestState(logger, NamedXContentRegistry.EMPTY, dataPaths); final PersistedClusterStateService persistedClusterStateService = createPersistedClusterStateService(dataPaths);
if (nodeMetaData == null) {
throw new ElasticsearchException(NO_NODE_METADATA_FOUND_MSG);
}
String nodeId = nodeMetaData.nodeId(); final Tuple<Long, ClusterState> state = loadTermAndClusterState(persistedClusterStateService, env);
terminal.println(Terminal.Verbosity.VERBOSE, "Current nodeId is " + nodeId); final ClusterState oldClusterState = state.v2();
final MetaData metaData = oldClusterState.metaData();
final Tuple<Manifest, MetaData> manifestMetaDataTuple = loadMetaData(terminal, dataPaths);
final Manifest manifest = manifestMetaDataTuple.v1();
final MetaData metaData = manifestMetaDataTuple.v2();
final CoordinationMetaData coordinationMetaData = metaData.coordinationMetaData(); final CoordinationMetaData coordinationMetaData = metaData.coordinationMetaData();
if (coordinationMetaData == null || if (coordinationMetaData == null ||
coordinationMetaData.getLastCommittedConfiguration() == null || coordinationMetaData.getLastCommittedConfiguration() == null ||
@ -105,12 +95,12 @@ public class UnsafeBootstrapMasterCommand extends ElasticsearchNodeCommand {
terminal.println(String.format(Locale.ROOT, CLUSTER_STATE_TERM_VERSION_MSG_FORMAT, coordinationMetaData.term(), terminal.println(String.format(Locale.ROOT, CLUSTER_STATE_TERM_VERSION_MSG_FORMAT, coordinationMetaData.term(),
metaData.version())); metaData.version()));
confirm(terminal, CONFIRMATION_MSG);
CoordinationMetaData newCoordinationMetaData = CoordinationMetaData.builder(coordinationMetaData) CoordinationMetaData newCoordinationMetaData = CoordinationMetaData.builder(coordinationMetaData)
.clearVotingConfigExclusions() .clearVotingConfigExclusions()
.lastAcceptedConfiguration(new CoordinationMetaData.VotingConfiguration(Collections.singleton(nodeId))) .lastAcceptedConfiguration(new CoordinationMetaData.VotingConfiguration(
.lastCommittedConfiguration(new CoordinationMetaData.VotingConfiguration(Collections.singleton(nodeId))) Collections.singleton(persistedClusterStateService.getNodeId())))
.lastCommittedConfiguration(new CoordinationMetaData.VotingConfiguration(
Collections.singleton(persistedClusterStateService.getNodeId())))
.build(); .build();
Settings persistentSettings = Settings.builder() Settings persistentSettings = Settings.builder()
@ -125,7 +115,17 @@ public class UnsafeBootstrapMasterCommand extends ElasticsearchNodeCommand {
.coordinationMetaData(newCoordinationMetaData) .coordinationMetaData(newCoordinationMetaData)
.build(); .build();
writeNewMetaData(terminal, manifest, manifest.getCurrentTerm(), metaData, newMetaData, dataPaths); final ClusterState newClusterState = ClusterState.builder(oldClusterState)
.metaData(newMetaData).build();
terminal.println(Terminal.Verbosity.VERBOSE,
"[old cluster state = " + oldClusterState + ", new cluster state = " + newClusterState + "]");
confirm(terminal, CONFIRMATION_MSG);
try (PersistedClusterStateService.Writer writer = persistedClusterStateService.createWriter()) {
writer.writeFullStateAndCommit(state.v1(), newClusterState);
}
terminal.println(MASTER_NODE_BOOTSTRAPPED_MSG); terminal.println(MASTER_NODE_BOOTSTRAPPED_MSG);
} }

View File

@ -687,7 +687,7 @@ public class MetaDataCreateIndexService {
"]: cannot be greater than number of shard copies [" + "]: cannot be greater than number of shard copies [" +
(tmpImd.getNumberOfReplicas() + 1) + "]"); (tmpImd.getNumberOfReplicas() + 1) + "]");
} }
return indicesService.createIndex(tmpImd, Collections.emptyList()); return indicesService.createIndex(tmpImd, Collections.emptyList(), false);
} }
private void validate(CreateIndexClusterStateUpdateRequest request, ClusterState state) { private void validate(CreateIndexClusterStateUpdateRequest request, ClusterState state) {

View File

@ -140,7 +140,7 @@ public class MetaDataIndexAliasesService {
if (indexService == null) { if (indexService == null) {
// temporarily create the index and add mappings so we can parse the filter // temporarily create the index and add mappings so we can parse the filter
try { try {
indexService = indicesService.createIndex(index, emptyList()); indexService = indicesService.createIndex(index, emptyList(), false);
indicesToClose.add(index.getIndex()); indicesToClose.add(index.getIndex());
} catch (IOException e) { } catch (IOException e) {
throw new ElasticsearchException("Failed to create temporary index for parsing the alias", e); throw new ElasticsearchException("Failed to create temporary index for parsing the alias", e);

View File

@ -232,7 +232,7 @@ public class MetaDataIndexTemplateService {
.build(); .build();
final IndexMetaData tmpIndexMetadata = IndexMetaData.builder(temporaryIndexName).settings(dummySettings).build(); final IndexMetaData tmpIndexMetadata = IndexMetaData.builder(temporaryIndexName).settings(dummySettings).build();
IndexService dummyIndexService = indicesService.createIndex(tmpIndexMetadata, Collections.emptyList()); IndexService dummyIndexService = indicesService.createIndex(tmpIndexMetadata, Collections.emptyList(), false);
createdIndex = dummyIndexService.index(); createdIndex = dummyIndexService.index();
templateBuilder.order(request.order); templateBuilder.order(request.order);

View File

@ -146,7 +146,7 @@ public class MetaDataMappingService {
IndexService indexService = indicesService.indexService(indexMetaData.getIndex()); IndexService indexService = indicesService.indexService(indexMetaData.getIndex());
if (indexService == null) { if (indexService == null) {
// we need to create the index here, and add the current mapping to it, so we can merge // we need to create the index here, and add the current mapping to it, so we can merge
indexService = indicesService.createIndex(indexMetaData, Collections.emptyList()); indexService = indicesService.createIndex(indexMetaData, Collections.emptyList(), false);
removeIndex = true; removeIndex = true;
indexService.mapperService().merge(indexMetaData, MergeReason.MAPPING_RECOVERY); indexService.mapperService().merge(indexMetaData, MergeReason.MAPPING_RECOVERY);
} }

View File

@ -213,6 +213,7 @@ public final class ClusterSettings extends AbstractScopedSettings {
IndicesQueryCache.INDICES_CACHE_QUERY_COUNT_SETTING, IndicesQueryCache.INDICES_CACHE_QUERY_COUNT_SETTING,
IndicesQueryCache.INDICES_QUERIES_CACHE_ALL_SEGMENTS_SETTING, IndicesQueryCache.INDICES_QUERIES_CACHE_ALL_SEGMENTS_SETTING,
IndicesService.INDICES_ID_FIELD_DATA_ENABLED_SETTING, IndicesService.INDICES_ID_FIELD_DATA_ENABLED_SETTING,
IndicesService.WRITE_DANGLING_INDICES_INFO_SETTING,
MappingUpdatedAction.INDICES_MAPPING_DYNAMIC_TIMEOUT_SETTING, MappingUpdatedAction.INDICES_MAPPING_DYNAMIC_TIMEOUT_SETTING,
MetaData.SETTING_READ_ONLY_SETTING, MetaData.SETTING_READ_ONLY_SETTING,
MetaData.SETTING_READ_ONLY_ALLOW_DELETE_SETTING, MetaData.SETTING_READ_ONLY_ALLOW_DELETE_SETTING,

View File

@ -49,6 +49,7 @@ import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.core.internal.io.IOUtils; import org.elasticsearch.core.internal.io.IOUtils;
import org.elasticsearch.gateway.MetaDataStateFormat; import org.elasticsearch.gateway.MetaDataStateFormat;
import org.elasticsearch.gateway.PersistedClusterStateService;
import org.elasticsearch.index.Index; import org.elasticsearch.index.Index;
import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.IndexSettings;
import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.index.shard.ShardId;
@ -302,7 +303,6 @@ public final class NodeEnvironment implements Closeable {
this.locks = nodeLock.locks; this.locks = nodeLock.locks;
this.nodePaths = nodeLock.nodePaths; this.nodePaths = nodeLock.nodePaths;
this.nodeLockId = nodeLock.nodeId; this.nodeLockId = nodeLock.nodeId;
this.nodeMetaData = loadOrCreateNodeMetaData(settings, logger, nodePaths);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("using node location [{}], local_lock_id [{}]", nodePaths, nodeLockId); logger.debug("using node location [{}], local_lock_id [{}]", nodePaths, nodeLockId);
@ -326,6 +326,7 @@ public final class NodeEnvironment implements Closeable {
ensureNoShardData(nodePaths); ensureNoShardData(nodePaths);
} }
this.nodeMetaData = loadNodeMetaData(settings, logger, nodePaths);
success = true; success = true;
} finally { } finally {
if (success == false) { if (success == false) {
@ -400,36 +401,36 @@ public final class NodeEnvironment implements Closeable {
/** /**
* scans the node paths and loads existing metaData file. If not found a new meta data will be generated * scans the node paths and loads existing metaData file. If not found a new meta data will be generated
* and persisted into the nodePaths
*/ */
private static NodeMetaData loadOrCreateNodeMetaData(Settings settings, Logger logger, private static NodeMetaData loadNodeMetaData(Settings settings, Logger logger,
NodePath... nodePaths) throws IOException { NodePath... nodePaths) throws IOException {
final Path[] paths = Arrays.stream(nodePaths).map(np -> np.path).toArray(Path[]::new); final Path[] paths = Arrays.stream(nodePaths).map(np -> np.path).toArray(Path[]::new);
NodeMetaData metaData = PersistedClusterStateService.nodeMetaData(paths);
if (metaData == null) {
// load legacy metadata
final Set<String> nodeIds = new HashSet<>(); final Set<String> nodeIds = new HashSet<>();
for (final Path path : paths) { for (final Path path : paths) {
final NodeMetaData metaData = NodeMetaData.FORMAT.loadLatestState(logger, NamedXContentRegistry.EMPTY, path); final NodeMetaData oldStyleMetaData = NodeMetaData.FORMAT.loadLatestState(logger, NamedXContentRegistry.EMPTY, path);
if (metaData != null) { if (oldStyleMetaData != null) {
nodeIds.add(metaData.nodeId()); nodeIds.add(oldStyleMetaData.nodeId());
} }
} }
if (nodeIds.size() > 1) { if (nodeIds.size() > 1) {
throw new IllegalStateException( throw new IllegalStateException(
"data paths " + Arrays.toString(paths) + " belong to multiple nodes with IDs " + nodeIds); "data paths " + Arrays.toString(paths) + " belong to multiple nodes with IDs " + nodeIds);
} }
// load legacy metadata
NodeMetaData metaData = NodeMetaData.FORMAT.loadLatestState(logger, NamedXContentRegistry.EMPTY, paths); final NodeMetaData legacyMetaData = NodeMetaData.FORMAT.loadLatestState(logger, NamedXContentRegistry.EMPTY, paths);
if (metaData == null) { if (legacyMetaData == null) {
assert nodeIds.isEmpty() : nodeIds; assert nodeIds.isEmpty() : nodeIds;
metaData = new NodeMetaData(generateNodeId(settings), Version.CURRENT); metaData = new NodeMetaData(generateNodeId(settings), Version.CURRENT);
} else { } else {
assert nodeIds.equals(Collections.singleton(metaData.nodeId())) : nodeIds + " doesn't match " + metaData; assert nodeIds.equals(Collections.singleton(legacyMetaData.nodeId())) : nodeIds + " doesn't match " + legacyMetaData;
metaData = metaData.upgradeToCurrentVersion(); metaData = legacyMetaData;
} }
}
// we write again to make sure all paths have the latest state file metaData = metaData.upgradeToCurrentVersion();
assert metaData.nodeVersion().equals(Version.CURRENT) : metaData.nodeVersion() + " != " + Version.CURRENT; assert metaData.nodeVersion().equals(Version.CURRENT) : metaData.nodeVersion() + " != " + Version.CURRENT;
NodeMetaData.FORMAT.writeAndCleanup(metaData, paths);
return metaData; return metaData;
} }

View File

@ -18,42 +18,41 @@
*/ */
package org.elasticsearch.env; package org.elasticsearch.env;
import com.carrotsearch.hppc.cursors.ObjectObjectCursor;
import joptsimple.OptionParser; import joptsimple.OptionParser;
import joptsimple.OptionSet; import joptsimple.OptionSet;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.cli.Terminal; import org.elasticsearch.cli.Terminal;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.coordination.ElasticsearchNodeCommand; import org.elasticsearch.cluster.coordination.ElasticsearchNodeCommand;
import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.metadata.Manifest; import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.core.internal.io.IOUtils; import org.elasticsearch.core.internal.io.IOUtils;
import org.elasticsearch.gateway.WriteStateException; import org.elasticsearch.gateway.MetaDataStateFormat;
import org.elasticsearch.gateway.PersistedClusterStateService;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static org.elasticsearch.env.NodeEnvironment.INDICES_FOLDER;
public class NodeRepurposeCommand extends ElasticsearchNodeCommand { public class NodeRepurposeCommand extends ElasticsearchNodeCommand {
private static final Logger logger = LogManager.getLogger(NodeRepurposeCommand.class);
static final String ABORTED_BY_USER_MSG = ElasticsearchNodeCommand.ABORTED_BY_USER_MSG; static final String ABORTED_BY_USER_MSG = ElasticsearchNodeCommand.ABORTED_BY_USER_MSG;
static final String FAILED_TO_OBTAIN_NODE_LOCK_MSG = ElasticsearchNodeCommand.FAILED_TO_OBTAIN_NODE_LOCK_MSG; static final String FAILED_TO_OBTAIN_NODE_LOCK_MSG = ElasticsearchNodeCommand.FAILED_TO_OBTAIN_NODE_LOCK_MSG;
static final String NO_CLEANUP = "Node has node.data=true -> no clean up necessary"; static final String NO_CLEANUP = "Node has node.data=true -> no clean up necessary";
static final String NO_DATA_TO_CLEAN_UP_FOUND = "No data to clean-up found"; static final String NO_DATA_TO_CLEAN_UP_FOUND = "No data to clean-up found";
static final String NO_SHARD_DATA_TO_CLEAN_UP_FOUND = "No shard data to clean-up found"; static final String NO_SHARD_DATA_TO_CLEAN_UP_FOUND = "No shard data to clean-up found";
static final String PRE_V7_MESSAGE =
"No manifest file found. If you were previously running this node on Elasticsearch version 6, please proceed.\n" +
"If this node was ever started on Elasticsearch version 7 or higher, it might mean metadata corruption, please abort.";
public NodeRepurposeCommand() { public NodeRepurposeCommand() {
super("Repurpose this node to another master/data role, cleaning up any excess persisted data"); super("Repurpose this node to another master/data role, cleaning up any excess persisted data");
@ -75,17 +74,18 @@ public class NodeRepurposeCommand extends ElasticsearchNodeCommand {
} }
@Override @Override
protected void processNodePaths(Terminal terminal, Path[] dataPaths, Environment env) throws IOException { protected void processNodePaths(Terminal terminal, Path[] dataPaths, int nodeLockId, OptionSet options, Environment env)
throws IOException {
assert DiscoveryNode.isDataNode(env.settings()) == false; assert DiscoveryNode.isDataNode(env.settings()) == false;
if (DiscoveryNode.isMasterNode(env.settings()) == false) { if (DiscoveryNode.isMasterNode(env.settings()) == false) {
processNoMasterNoDataNode(terminal, dataPaths); processNoMasterNoDataNode(terminal, dataPaths, env);
} else { } else {
processMasterNoDataNode(terminal, dataPaths); processMasterNoDataNode(terminal, dataPaths, env);
} }
} }
private void processNoMasterNoDataNode(Terminal terminal, Path[] dataPaths) throws IOException { private void processNoMasterNoDataNode(Terminal terminal, Path[] dataPaths, Environment env) throws IOException {
NodeEnvironment.NodePath[] nodePaths = toNodePaths(dataPaths); NodeEnvironment.NodePath[] nodePaths = toNodePaths(dataPaths);
terminal.println(Terminal.Verbosity.VERBOSE, "Collecting shard data paths"); terminal.println(Terminal.Verbosity.VERBOSE, "Collecting shard data paths");
@ -95,32 +95,36 @@ public class NodeRepurposeCommand extends ElasticsearchNodeCommand {
List<Path> indexMetaDataPaths = NodeEnvironment.collectIndexMetaDataPaths(nodePaths); List<Path> indexMetaDataPaths = NodeEnvironment.collectIndexMetaDataPaths(nodePaths);
Set<Path> indexPaths = uniqueParentPaths(shardDataPaths, indexMetaDataPaths); Set<Path> indexPaths = uniqueParentPaths(shardDataPaths, indexMetaDataPaths);
if (indexPaths.isEmpty()) {
final PersistedClusterStateService persistedClusterStateService = createPersistedClusterStateService(dataPaths);
final MetaData metaData = loadClusterState(terminal, env, persistedClusterStateService).metaData();
if (indexPaths.isEmpty() && metaData.indices().isEmpty()) {
terminal.println(Terminal.Verbosity.NORMAL, NO_DATA_TO_CLEAN_UP_FOUND); terminal.println(Terminal.Verbosity.NORMAL, NO_DATA_TO_CLEAN_UP_FOUND);
return; return;
} }
Set<String> indexUUIDs = indexUUIDsFor(indexPaths); final Set<String> indexUUIDs = Sets.union(indexUUIDsFor(indexPaths),
outputVerboseInformation(terminal, nodePaths, indexPaths, indexUUIDs); StreamSupport.stream(metaData.indices().values().spliterator(), false)
.map(imd -> imd.value.getIndexUUID()).collect(Collectors.toSet()));
outputVerboseInformation(terminal, indexPaths, indexUUIDs, metaData);
terminal.println(noMasterMessage(indexUUIDs.size(), shardDataPaths.size(), indexMetaDataPaths.size())); terminal.println(noMasterMessage(indexUUIDs.size(), shardDataPaths.size(), indexMetaDataPaths.size()));
outputHowToSeeVerboseInformation(terminal); outputHowToSeeVerboseInformation(terminal);
final Manifest manifest = loadManifest(terminal, dataPaths);
terminal.println("Node is being re-purposed as no-master and no-data. Clean-up of index data will be performed."); terminal.println("Node is being re-purposed as no-master and no-data. Clean-up of index data will be performed.");
confirm(terminal, "Do you want to proceed?"); confirm(terminal, "Do you want to proceed?");
if (manifest != null) { removePaths(terminal, indexPaths); // clean-up shard dirs
rewriteManifest(terminal, manifest, dataPaths); // clean-up all metadata dirs
} MetaDataStateFormat.deleteMetaState(dataPaths);
IOUtils.rm(Stream.of(dataPaths).map(path -> path.resolve(INDICES_FOLDER)).toArray(Path[]::new));
removePaths(terminal, indexPaths);
terminal.println("Node successfully repurposed to no-master and no-data."); terminal.println("Node successfully repurposed to no-master and no-data.");
} }
private void processMasterNoDataNode(Terminal terminal, Path[] dataPaths) throws IOException { private void processMasterNoDataNode(Terminal terminal, Path[] dataPaths, Environment env) throws IOException {
NodeEnvironment.NodePath[] nodePaths = toNodePaths(dataPaths); NodeEnvironment.NodePath[] nodePaths = toNodePaths(dataPaths);
terminal.println(Terminal.Verbosity.VERBOSE, "Collecting shard data paths"); terminal.println(Terminal.Verbosity.VERBOSE, "Collecting shard data paths");
@ -130,9 +134,14 @@ public class NodeRepurposeCommand extends ElasticsearchNodeCommand {
return; return;
} }
Set<Path> indexPaths = uniqueParentPaths(shardDataPaths); final PersistedClusterStateService persistedClusterStateService = createPersistedClusterStateService(dataPaths);
Set<String> indexUUIDs = indexUUIDsFor(indexPaths);
outputVerboseInformation(terminal, nodePaths, shardDataPaths, indexUUIDs); final MetaData metaData = loadClusterState(terminal, env, persistedClusterStateService).metaData();
final Set<Path> indexPaths = uniqueParentPaths(shardDataPaths);
final Set<String> indexUUIDs = indexUUIDsFor(indexPaths);
outputVerboseInformation(terminal, shardDataPaths, indexUUIDs, metaData);
terminal.println(shardMessage(shardDataPaths.size(), indexUUIDs.size())); terminal.println(shardMessage(shardDataPaths.size(), indexUUIDs.size()));
outputHowToSeeVerboseInformation(terminal); outputHowToSeeVerboseInformation(terminal);
@ -140,18 +149,22 @@ public class NodeRepurposeCommand extends ElasticsearchNodeCommand {
terminal.println("Node is being re-purposed as master and no-data. Clean-up of shard data will be performed."); terminal.println("Node is being re-purposed as master and no-data. Clean-up of shard data will be performed.");
confirm(terminal, "Do you want to proceed?"); confirm(terminal, "Do you want to proceed?");
removePaths(terminal, shardDataPaths); removePaths(terminal, shardDataPaths); // clean-up shard dirs
terminal.println("Node successfully repurposed to master and no-data."); terminal.println("Node successfully repurposed to master and no-data.");
} }
private void outputVerboseInformation(Terminal terminal, NodeEnvironment.NodePath[] nodePaths, private ClusterState loadClusterState(Terminal terminal, Environment env, PersistedClusterStateService psf) throws IOException {
Collection<Path> pathsToCleanup, Set<String> indexUUIDs) { terminal.println(Terminal.Verbosity.VERBOSE, "Loading cluster state");
return clusterState(env, psf.loadBestOnDiskState());
}
private void outputVerboseInformation(Terminal terminal, Collection<Path> pathsToCleanup, Set<String> indexUUIDs, MetaData metaData) {
if (terminal.isPrintable(Terminal.Verbosity.VERBOSE)) { if (terminal.isPrintable(Terminal.Verbosity.VERBOSE)) {
terminal.println(Terminal.Verbosity.VERBOSE, "Paths to clean up:"); terminal.println(Terminal.Verbosity.VERBOSE, "Paths to clean up:");
pathsToCleanup.forEach(p -> terminal.println(Terminal.Verbosity.VERBOSE, " " + p.toString())); pathsToCleanup.forEach(p -> terminal.println(Terminal.Verbosity.VERBOSE, " " + p.toString()));
terminal.println(Terminal.Verbosity.VERBOSE, "Indices affected:"); terminal.println(Terminal.Verbosity.VERBOSE, "Indices affected:");
indexUUIDs.forEach(uuid -> terminal.println(Terminal.Verbosity.VERBOSE, " " + toIndexName(nodePaths, uuid))); indexUUIDs.forEach(uuid -> terminal.println(Terminal.Verbosity.VERBOSE, " " + toIndexName(uuid, metaData)));
} }
} }
@ -160,18 +173,16 @@ public class NodeRepurposeCommand extends ElasticsearchNodeCommand {
terminal.println("Use -v to see list of paths and indices affected"); terminal.println("Use -v to see list of paths and indices affected");
} }
} }
private String toIndexName(NodeEnvironment.NodePath[] nodePaths, String uuid) { private String toIndexName(String uuid, MetaData metaData) {
Path[] indexPaths = new Path[nodePaths.length]; if (metaData != null) {
for (int i = 0; i < nodePaths.length; i++) { for (ObjectObjectCursor<String, IndexMetaData> indexMetaData : metaData.indices()) {
indexPaths[i] = nodePaths[i].resolve(uuid); if (indexMetaData.value.getIndexUUID().equals(uuid)) {
return indexMetaData.value.getIndex().getName();
} }
try {
IndexMetaData metaData = IndexMetaData.FORMAT.loadLatestState(logger, NamedXContentRegistry.EMPTY, indexPaths);
return metaData.getIndex().getName();
} catch (Exception e) {
return "no name for uuid: " + uuid + ": " + e;
} }
} }
return "no name for uuid: " + uuid;
}
private Set<String> indexUUIDsFor(Set<Path> indexPaths) { private Set<String> indexUUIDsFor(Set<Path> indexPaths) {
return indexPaths.stream().map(Path::getFileName).map(Path::toString).collect(Collectors.toSet()); return indexPaths.stream().map(Path::getFileName).map(Path::toString).collect(Collectors.toSet());
@ -186,23 +197,6 @@ public class NodeRepurposeCommand extends ElasticsearchNodeCommand {
return "Found " + shards + " shards in " + indices + " indices to clean up"; return "Found " + shards + " shards in " + indices + " indices to clean up";
} }
private void rewriteManifest(Terminal terminal, Manifest manifest, Path[] dataPaths) throws WriteStateException {
terminal.println(Terminal.Verbosity.VERBOSE, "Re-writing manifest");
Manifest newManifest = new Manifest(manifest.getCurrentTerm(), manifest.getClusterStateVersion(), manifest.getGlobalGeneration(),
new HashMap<>());
Manifest.FORMAT.writeAndCleanup(newManifest, dataPaths);
}
private Manifest loadManifest(Terminal terminal, Path[] dataPaths) throws IOException {
terminal.println(Terminal.Verbosity.VERBOSE, "Loading manifest");
final Manifest manifest = Manifest.FORMAT.loadLatestState(logger, NamedXContentRegistry.EMPTY, dataPaths);
if (manifest == null) {
terminal.println(Terminal.Verbosity.SILENT, PRE_V7_MESSAGE);
}
return manifest;
}
private void removePaths(Terminal terminal, Collection<Path> paths) { private void removePaths(Terminal terminal, Collection<Path> paths) {
terminal.println(Terminal.Verbosity.VERBOSE, "Removing data"); terminal.println(Terminal.Verbosity.VERBOSE, "Removing data");
paths.forEach(this::removePath); paths.forEach(this::removePath);

View File

@ -19,21 +19,18 @@
package org.elasticsearch.env; package org.elasticsearch.env;
import joptsimple.OptionParser; import joptsimple.OptionParser;
import org.apache.logging.log4j.LogManager; import joptsimple.OptionSet;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.Version; import org.elasticsearch.Version;
import org.elasticsearch.cli.Terminal; import org.elasticsearch.cli.Terminal;
import org.elasticsearch.cluster.coordination.ElasticsearchNodeCommand; import org.elasticsearch.cluster.coordination.ElasticsearchNodeCommand;
import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.gateway.PersistedClusterStateService;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Arrays; import java.util.Arrays;
public class OverrideNodeVersionCommand extends ElasticsearchNodeCommand { public class OverrideNodeVersionCommand extends ElasticsearchNodeCommand {
private static final Logger logger = LogManager.getLogger(OverrideNodeVersionCommand.class);
private static final String TOO_NEW_MESSAGE = private static final String TOO_NEW_MESSAGE =
DELIMITER + DELIMITER +
"\n" + "\n" +
@ -72,10 +69,10 @@ public class OverrideNodeVersionCommand extends ElasticsearchNodeCommand {
} }
@Override @Override
protected void processNodePaths(Terminal terminal, Path[] dataPaths, Environment env) throws IOException { protected void processNodePaths(Terminal terminal, Path[] dataPaths, int nodeLockId, OptionSet options, Environment env)
throws IOException {
final Path[] nodePaths = Arrays.stream(toNodePaths(dataPaths)).map(p -> p.path).toArray(Path[]::new); final Path[] nodePaths = Arrays.stream(toNodePaths(dataPaths)).map(p -> p.path).toArray(Path[]::new);
final NodeMetaData nodeMetaData final NodeMetaData nodeMetaData = PersistedClusterStateService.nodeMetaData(nodePaths);
= new NodeMetaData.NodeMetaDataStateFormat(true).loadLatestState(logger, NamedXContentRegistry.EMPTY, nodePaths);
if (nodeMetaData == null) { if (nodeMetaData == null) {
throw new ElasticsearchException(NO_METADATA_MESSAGE); throw new ElasticsearchException(NO_METADATA_MESSAGE);
} }
@ -93,7 +90,7 @@ public class OverrideNodeVersionCommand extends ElasticsearchNodeCommand {
.replace("V_NEW", nodeMetaData.nodeVersion().toString()) .replace("V_NEW", nodeMetaData.nodeVersion().toString())
.replace("V_CUR", Version.CURRENT.toString())); .replace("V_CUR", Version.CURRENT.toString()));
NodeMetaData.FORMAT.writeAndCleanup(new NodeMetaData(nodeMetaData.nodeId(), Version.CURRENT), nodePaths); PersistedClusterStateService.overrideVersion(Version.CURRENT, dataPaths);
terminal.println(SUCCESS_MESSAGE); terminal.println(SUCCESS_MESSAGE);
} }

View File

@ -22,14 +22,16 @@ package org.elasticsearch.gateway;
import com.carrotsearch.hppc.cursors.ObjectObjectCursor; import com.carrotsearch.hppc.cursors.ObjectObjectCursor;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.ParameterizedMessage; import org.apache.lucene.store.AlreadyClosedException;
import org.apache.lucene.util.SetOnce; import org.apache.lucene.util.SetOnce;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.Version; import org.elasticsearch.Version;
import org.elasticsearch.cluster.ClusterChangedEvent; import org.elasticsearch.cluster.ClusterChangedEvent;
import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.ClusterStateApplier; import org.elasticsearch.cluster.ClusterStateApplier;
import org.elasticsearch.cluster.coordination.CoordinationMetaData;
import org.elasticsearch.cluster.coordination.CoordinationState.PersistedState; import org.elasticsearch.cluster.coordination.CoordinationState.PersistedState;
import org.elasticsearch.cluster.coordination.InMemoryPersistedState; import org.elasticsearch.cluster.coordination.InMemoryPersistedState;
import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.IndexMetaData;
@ -42,20 +44,34 @@ import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.collect.ImmutableOpenMap; import org.elasticsearch.common.collect.ImmutableOpenMap;
import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.util.concurrent.AbstractRunnable;
import org.elasticsearch.common.util.concurrent.EsExecutors;
import org.elasticsearch.common.util.concurrent.EsRejectedExecutionException;
import org.elasticsearch.common.util.concurrent.EsThreadPoolExecutor;
import org.elasticsearch.core.internal.io.IOUtils;
import org.elasticsearch.discovery.DiscoveryModule; import org.elasticsearch.discovery.DiscoveryModule;
import org.elasticsearch.index.Index; import org.elasticsearch.env.NodeMetaData;
import org.elasticsearch.node.Node;
import org.elasticsearch.plugins.MetaDataUpgrader; import org.elasticsearch.plugins.MetaDataUpgrader;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService; import org.elasticsearch.transport.TransportService;
import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.UnaryOperator; import java.util.function.UnaryOperator;
import static org.elasticsearch.common.util.concurrent.EsExecutors.daemonThreadFactory;
/** /**
* Loads (and maybe upgrades) cluster metadata at startup, and persistently stores cluster metadata for future restarts. * Loads (and maybe upgrades) cluster metadata at startup, and persistently stores cluster metadata for future restarts.
* *
@ -64,8 +80,7 @@ import java.util.function.UnaryOperator;
* ClusterState#metaData()} because it might be stale or incomplete. Master-eligible nodes must perform an election to find a complete and * ClusterState#metaData()} because it might be stale or incomplete. Master-eligible nodes must perform an election to find a complete and
* non-stale state, and master-ineligible nodes receive the real cluster state from the elected master after joining the cluster. * non-stale state, and master-ineligible nodes receive the real cluster state from the elected master after joining the cluster.
*/ */
public class GatewayMetaState { public class GatewayMetaState implements Closeable {
private static final Logger logger = LogManager.getLogger(GatewayMetaState.class);
// Set by calling start() // Set by calling start()
private final SetOnce<PersistedState> persistedState = new SetOnce<>(); private final SetOnce<PersistedState> persistedState = new SetOnce<>();
@ -82,55 +97,111 @@ public class GatewayMetaState {
public void start(Settings settings, TransportService transportService, ClusterService clusterService, public void start(Settings settings, TransportService transportService, ClusterService clusterService,
MetaStateService metaStateService, MetaDataIndexUpgradeService metaDataIndexUpgradeService, MetaStateService metaStateService, MetaDataIndexUpgradeService metaDataIndexUpgradeService,
MetaDataUpgrader metaDataUpgrader) { MetaDataUpgrader metaDataUpgrader, PersistedClusterStateService persistedClusterStateService) {
assert persistedState.get() == null : "should only start once, but already have " + persistedState.get(); assert persistedState.get() == null : "should only start once, but already have " + persistedState.get();
final Tuple<Manifest, ClusterState> manifestClusterStateTuple;
try {
upgradeMetaData(settings, metaStateService, metaDataIndexUpgradeService, metaDataUpgrader);
manifestClusterStateTuple = loadStateAndManifest(ClusterName.CLUSTER_NAME_SETTING.get(settings), metaStateService);
} catch (IOException e) {
throw new ElasticsearchException("failed to load metadata", e);
}
final IncrementalClusterStateWriter incrementalClusterStateWriter
= new IncrementalClusterStateWriter(settings, clusterService.getClusterSettings(), metaStateService,
manifestClusterStateTuple.v1(),
prepareInitialClusterState(transportService, clusterService, manifestClusterStateTuple.v2()),
transportService.getThreadPool()::relativeTimeInMillis);
if (DiscoveryModule.DISCOVERY_TYPE_SETTING.get(settings).equals(DiscoveryModule.ZEN_DISCOVERY_TYPE)) { if (DiscoveryModule.DISCOVERY_TYPE_SETTING.get(settings).equals(DiscoveryModule.ZEN_DISCOVERY_TYPE)) {
// only for tests that simulate mixed Zen1/Zen2 clusters, see Zen1IT // only for tests that simulate mixed Zen1/Zen2 clusters, see Zen1IT
if (isMasterOrDataNode(settings)) { final Tuple<Manifest, MetaData> manifestClusterStateTuple;
try {
NodeMetaData.FORMAT.writeAndCleanup(new NodeMetaData(persistedClusterStateService.getNodeId(), Version.CURRENT),
persistedClusterStateService.getDataPaths());
manifestClusterStateTuple = metaStateService.loadFullState();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
final ClusterState clusterState = ClusterState.builder(ClusterName.CLUSTER_NAME_SETTING.get(settings))
.version(manifestClusterStateTuple.v1().getClusterStateVersion())
.metaData(manifestClusterStateTuple.v2()).build();
final IncrementalClusterStateWriter incrementalClusterStateWriter
= new IncrementalClusterStateWriter(settings, clusterService.getClusterSettings(), metaStateService,
manifestClusterStateTuple.v1(),
prepareInitialClusterState(transportService, clusterService, clusterState),
transportService.getThreadPool()::relativeTimeInMillis);
if (DiscoveryNode.isMasterNode(settings) || DiscoveryNode.isDataNode(settings)) {
clusterService.addLowPriorityApplier(new GatewayClusterApplier(incrementalClusterStateWriter)); clusterService.addLowPriorityApplier(new GatewayClusterApplier(incrementalClusterStateWriter));
} }
persistedState.set(new InMemoryPersistedState(manifestClusterStateTuple.v1().getCurrentTerm(), manifestClusterStateTuple.v2())); persistedState.set(new InMemoryPersistedState(manifestClusterStateTuple.v1().getCurrentTerm(), clusterState));
} else if (DiscoveryNode.isMasterNode(settings) == false) { return;
if (DiscoveryNode.isDataNode(settings)) {
// Master-eligible nodes persist index metadata for all indices regardless of whether they hold any shards or not. It's
// vitally important to the safety of the cluster coordination system that master-eligible nodes persist this metadata when
// _accepting_ the cluster state (i.e. before it is committed). This persistence happens on the generic threadpool.
//
// In contrast, master-ineligible data nodes only persist the index metadata for shards that they hold. When all shards of
// an index are moved off such a node the IndicesStore is responsible for removing the corresponding index directory,
// including the metadata, and does so on the cluster applier thread.
//
// This presents a problem: if a shard is unassigned from a node and then reassigned back to it again then there is a race
// between the IndicesStore deleting the index folder and the CoordinationState concurrently trying to write the updated
// metadata into it. We could probably solve this with careful synchronization, but in fact there is no need. The persisted
// state on master-ineligible data nodes is mostly ignored - it's only there to support dangling index imports, which is
// inherently unsafe anyway. Thus we can safely delay metadata writes on master-ineligible data nodes until applying the
// cluster state, which is what this does:
clusterService.addLowPriorityApplier(new GatewayClusterApplier(incrementalClusterStateWriter));
} }
// Master-ineligible nodes do not need to persist the cluster state when accepting it because they are not in the voting if (DiscoveryNode.isMasterNode(settings) || DiscoveryNode.isDataNode(settings)) {
// configuration, so it's ok if they have a stale or incomplete cluster state when restarted. We track the latest cluster state try {
// in memory instead. final PersistedClusterStateService.OnDiskState onDiskState = persistedClusterStateService.loadBestOnDiskState();
persistedState.set(new InMemoryPersistedState(manifestClusterStateTuple.v1().getCurrentTerm(), manifestClusterStateTuple.v2()));
MetaData metaData = onDiskState.metaData;
long lastAcceptedVersion = onDiskState.lastAcceptedVersion;
long currentTerm = onDiskState.currentTerm;
if (onDiskState.empty()) {
assert Version.CURRENT.major <= Version.V_7_0_0.major + 1 :
"legacy metadata loader is not needed anymore from v9 onwards";
final Tuple<Manifest, MetaData> legacyState = metaStateService.loadFullState();
if (legacyState.v1().isEmpty() == false) {
metaData = legacyState.v2();
lastAcceptedVersion = legacyState.v1().getClusterStateVersion();
currentTerm = legacyState.v1().getCurrentTerm();
}
}
PersistedState persistedState = null;
boolean success = false;
try {
final ClusterState clusterState = prepareInitialClusterState(transportService, clusterService,
ClusterState.builder(ClusterName.CLUSTER_NAME_SETTING.get(settings))
.version(lastAcceptedVersion)
.metaData(upgradeMetaDataForNode(metaData, metaDataIndexUpgradeService, metaDataUpgrader))
.build());
if (DiscoveryNode.isMasterNode(settings)) {
persistedState = new LucenePersistedState(persistedClusterStateService, currentTerm, clusterState);
} else { } else {
// Master-ineligible nodes must persist the cluster state when accepting it because they must reload the (complete, fresh) persistedState = new AsyncLucenePersistedState(settings, transportService.getThreadPool(),
// last-accepted cluster state when restarted. new LucenePersistedState(persistedClusterStateService, currentTerm, clusterState));
persistedState.set(new GatewayPersistedState(incrementalClusterStateWriter)); }
if (DiscoveryNode.isDataNode(settings)) {
metaStateService.unreferenceAll(); // unreference legacy files (only keep them for dangling indices functionality)
} else {
metaStateService.deleteAll(); // delete legacy files
}
// write legacy node metadata to prevent accidental downgrades from spawning empty cluster state
NodeMetaData.FORMAT.writeAndCleanup(new NodeMetaData(persistedClusterStateService.getNodeId(), Version.CURRENT),
persistedClusterStateService.getDataPaths());
success = true;
} finally {
if (success == false) {
IOUtils.closeWhileHandlingException(persistedState);
}
}
this.persistedState.set(persistedState);
} catch (IOException e) {
throw new ElasticsearchException("failed to load metadata", e);
}
} else {
final long currentTerm = 0L;
final ClusterState clusterState = ClusterState.builder(ClusterName.CLUSTER_NAME_SETTING.get(settings)).build();
if (persistedClusterStateService.getDataPaths().length > 0) {
// write empty cluster state just so that we have a persistent node id. There is no need to write out global metadata with
// cluster uuid as coordinating-only nodes do not snap into a cluster as they carry no state
try (PersistedClusterStateService.Writer persistenceWriter = persistedClusterStateService.createWriter()) {
persistenceWriter.writeFullStateAndCommit(currentTerm, clusterState);
} catch (IOException e) {
throw new ElasticsearchException("failed to load metadata", e);
}
try {
// delete legacy cluster state files
metaStateService.deleteAll();
// write legacy node metadata to prevent downgrades from spawning empty cluster state
NodeMetaData.FORMAT.writeAndCleanup(new NodeMetaData(persistedClusterStateService.getNodeId(), Version.CURRENT),
persistedClusterStateService.getDataPaths());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
persistedState.set(new InMemoryPersistedState(currentTerm, clusterState));
} }
} }
@ -147,67 +218,10 @@ public class GatewayMetaState {
} }
// exposed so it can be overridden by tests // exposed so it can be overridden by tests
void upgradeMetaData(Settings settings, MetaStateService metaStateService, MetaDataIndexUpgradeService metaDataIndexUpgradeService, MetaData upgradeMetaDataForNode(MetaData metaData,
MetaDataUpgrader metaDataUpgrader) throws IOException { MetaDataIndexUpgradeService metaDataIndexUpgradeService,
if (isMasterOrDataNode(settings)) { MetaDataUpgrader metaDataUpgrader) {
try { return upgradeMetaData(metaData, metaDataIndexUpgradeService, metaDataUpgrader);
final Tuple<Manifest, MetaData> metaStateAndData = metaStateService.loadFullState();
final Manifest manifest = metaStateAndData.v1();
final MetaData metaData = metaStateAndData.v2();
// We finished global state validation and successfully checked all indices for backward compatibility
// and found no non-upgradable indices, which means the upgrade can continue.
// Now it's safe to overwrite global and index metadata.
// We don't re-write metadata if it's not upgraded by upgrade plugins, because
// if there is manifest file, it means metadata is properly persisted to all data paths
// if there is no manifest file (upgrade from 6.x to 7.x) metadata might be missing on some data paths,
// but anyway we will re-write it as soon as we receive first ClusterState
final IncrementalClusterStateWriter.AtomicClusterStateWriter writer
= new IncrementalClusterStateWriter.AtomicClusterStateWriter(metaStateService, manifest);
final MetaData upgradedMetaData = upgradeMetaData(metaData, metaDataIndexUpgradeService, metaDataUpgrader);
final long globalStateGeneration;
if (MetaData.isGlobalStateEquals(metaData, upgradedMetaData) == false) {
globalStateGeneration = writer.writeGlobalState("upgrade", upgradedMetaData);
} else {
globalStateGeneration = manifest.getGlobalGeneration();
}
Map<Index, Long> indices = new HashMap<>(manifest.getIndexGenerations());
for (IndexMetaData indexMetaData : upgradedMetaData) {
if (metaData.hasIndexMetaData(indexMetaData) == false) {
final long generation = writer.writeIndex("upgrade", indexMetaData);
indices.put(indexMetaData.getIndex(), generation);
}
}
final Manifest newManifest = new Manifest(manifest.getCurrentTerm(), manifest.getClusterStateVersion(),
globalStateGeneration, indices);
writer.writeManifestAndCleanup("startup", newManifest);
} catch (Exception e) {
logger.error("failed to read or upgrade local state, exiting...", e);
throw e;
}
}
}
private static Tuple<Manifest,ClusterState> loadStateAndManifest(ClusterName clusterName,
MetaStateService metaStateService) throws IOException {
final long startNS = System.nanoTime();
final Tuple<Manifest, MetaData> manifestAndMetaData = metaStateService.loadFullState();
final Manifest manifest = manifestAndMetaData.v1();
final ClusterState clusterState = ClusterState.builder(clusterName)
.version(manifest.getClusterStateVersion())
.metaData(manifestAndMetaData.v2()).build();
logger.debug("took {} to load state", TimeValue.timeValueMillis(TimeValue.nsecToMSec(System.nanoTime() - startNS)));
return Tuple.tuple(manifest, clusterState);
}
private static boolean isMasterOrDataNode(Settings settings) {
return DiscoveryNode.isMasterNode(settings) || DiscoveryNode.isDataNode(settings);
} }
/** /**
@ -259,9 +273,10 @@ public class GatewayMetaState {
return false; return false;
} }
private static class GatewayClusterApplier implements ClusterStateApplier { private static class GatewayClusterApplier implements ClusterStateApplier {
private static final Logger logger = LogManager.getLogger(GatewayClusterApplier.class);
private final IncrementalClusterStateWriter incrementalClusterStateWriter; private final IncrementalClusterStateWriter incrementalClusterStateWriter;
private GatewayClusterApplier(IncrementalClusterStateWriter incrementalClusterStateWriter) { private GatewayClusterApplier(IncrementalClusterStateWriter incrementalClusterStateWriter) {
@ -292,48 +307,270 @@ public class GatewayMetaState {
} }
private static class GatewayPersistedState implements PersistedState { @Override
public void close() throws IOException {
private final IncrementalClusterStateWriter incrementalClusterStateWriter; IOUtils.close(persistedState.get());
GatewayPersistedState(IncrementalClusterStateWriter incrementalClusterStateWriter) {
this.incrementalClusterStateWriter = incrementalClusterStateWriter;
} }
@Override // visible for testing
public long getCurrentTerm() { public boolean allPendingAsyncStatesWritten() {
return incrementalClusterStateWriter.getPreviousManifest().getCurrentTerm(); final PersistedState ps = persistedState.get();
if (ps instanceof AsyncLucenePersistedState) {
return ((AsyncLucenePersistedState) ps).allPendingAsyncStatesWritten();
} else {
return true;
}
} }
@Override static class AsyncLucenePersistedState extends InMemoryPersistedState {
public ClusterState getLastAcceptedState() {
final ClusterState previousClusterState = incrementalClusterStateWriter.getPreviousClusterState(); private static final Logger logger = LogManager.getLogger(AsyncLucenePersistedState.class);
assert previousClusterState.nodes().getLocalNode() != null : "Cluster state is not fully built yet";
return previousClusterState; static final String THREAD_NAME = "AsyncLucenePersistedState#updateTask";
private final EsThreadPoolExecutor threadPoolExecutor;
private final PersistedState persistedState;
boolean newCurrentTermQueued = false;
boolean newStateQueued = false;
private final Object mutex = new Object();
AsyncLucenePersistedState(Settings settings, ThreadPool threadPool, PersistedState persistedState) {
super(persistedState.getCurrentTerm(), persistedState.getLastAcceptedState());
final String nodeName = Objects.requireNonNull(Node.NODE_NAME_SETTING.get(settings));
threadPoolExecutor = EsExecutors.newFixed(
nodeName + "/" + THREAD_NAME,
1, 1,
daemonThreadFactory(nodeName, THREAD_NAME),
threadPool.getThreadContext());
this.persistedState = persistedState;
} }
@Override @Override
public void setCurrentTerm(long currentTerm) { public void setCurrentTerm(long currentTerm) {
try { synchronized (mutex) {
incrementalClusterStateWriter.setCurrentTerm(currentTerm); super.setCurrentTerm(currentTerm);
} catch (WriteStateException e) { if (newCurrentTermQueued) {
logger.error(new ParameterizedMessage("Failed to set current term to {}", currentTerm), e); logger.trace("term update already queued (setting term to {})", currentTerm);
e.rethrowAsErrorOrUncheckedException(); } else {
logger.trace("queuing term update (setting term to {})", currentTerm);
newCurrentTermQueued = true;
scheduleUpdate();
}
} }
} }
@Override @Override
public void setLastAcceptedState(ClusterState clusterState) { public void setLastAcceptedState(ClusterState clusterState) {
synchronized (mutex) {
super.setLastAcceptedState(clusterState);
if (newStateQueued) {
logger.trace("cluster state update already queued (setting cluster state to {})", clusterState.version());
} else {
logger.trace("queuing cluster state update (setting cluster state to {})", clusterState.version());
newStateQueued = true;
scheduleUpdate();
}
}
}
private void scheduleUpdate() {
assert Thread.holdsLock(mutex);
try { try {
incrementalClusterStateWriter.setIncrementalWrite( threadPoolExecutor.execute(new AbstractRunnable() {
incrementalClusterStateWriter.getPreviousClusterState().term() == clusterState.term());
incrementalClusterStateWriter.updateClusterState(clusterState); @Override
} catch (WriteStateException e) { public void onFailure(Exception e) {
logger.error(new ParameterizedMessage("Failed to set last accepted state with version {}", clusterState.version()), e); logger.error("Exception occurred when storing new meta data", e);
e.rethrowAsErrorOrUncheckedException(); }
@Override
protected void doRun() {
final Long term;
final ClusterState clusterState;
synchronized (mutex) {
if (newCurrentTermQueued) {
term = getCurrentTerm();
newCurrentTermQueued = false;
} else {
term = null;
}
if (newStateQueued) {
clusterState = getLastAcceptedState();
newStateQueued = false;
} else {
clusterState = null;
}
}
// write current term before last accepted state so that it is never below term in last accepted state
if (term != null) {
persistedState.setCurrentTerm(term);
}
if (clusterState != null) {
persistedState.setLastAcceptedState(resetVotingConfiguration(clusterState));
}
}
});
} catch (EsRejectedExecutionException e) {
// ignore cases where we are shutting down..., there is really nothing interesting to be done here...
if (threadPoolExecutor.isShutdown() == false) {
assert false : "only expect rejections when shutting down";
throw e;
}
} }
} }
static final CoordinationMetaData.VotingConfiguration staleStateConfiguration =
new CoordinationMetaData.VotingConfiguration(Collections.singleton("STALE_STATE_CONFIG"));
static ClusterState resetVotingConfiguration(ClusterState clusterState) {
CoordinationMetaData newCoordinationMetaData = CoordinationMetaData.builder(clusterState.coordinationMetaData())
.lastAcceptedConfiguration(staleStateConfiguration)
.lastCommittedConfiguration(staleStateConfiguration)
.build();
return ClusterState.builder(clusterState).metaData(MetaData.builder(clusterState.metaData())
.coordinationMetaData(newCoordinationMetaData).build()).build();
} }
@Override
public void close() throws IOException {
try {
ThreadPool.terminate(threadPoolExecutor, 10, TimeUnit.SECONDS);
} finally {
persistedState.close();
}
}
boolean allPendingAsyncStatesWritten() {
synchronized (mutex) {
if (newCurrentTermQueued || newStateQueued) {
return false;
}
return threadPoolExecutor.getActiveCount() == 0;
}
}
}
/**
* Encapsulates the incremental writing of metadata to a {@link PersistedClusterStateService.Writer}.
*/
static class LucenePersistedState implements PersistedState {
private long currentTerm;
private ClusterState lastAcceptedState;
private final PersistedClusterStateService persistedClusterStateService;
// As the close method can be concurrently called to the other PersistedState methods, this class has extra protection in place.
private final AtomicReference<PersistedClusterStateService.Writer> persistenceWriter = new AtomicReference<>();
boolean writeNextStateFully;
LucenePersistedState(PersistedClusterStateService persistedClusterStateService, long currentTerm, ClusterState lastAcceptedState)
throws IOException {
this.persistedClusterStateService = persistedClusterStateService;
this.currentTerm = currentTerm;
this.lastAcceptedState = lastAcceptedState;
// Write the whole state out to be sure it's fresh and using the latest format. Called during initialisation, so that
// (1) throwing an IOException is enough to halt the node, and
// (2) the index is currently empty since it was opened with IndexWriterConfig.OpenMode.CREATE
// In the common case it's actually sufficient to commit() the existing state and not do any indexing. For instance,
// this is true if there's only one data path on this master node, and the commit we just loaded was already written out
// by this version of Elasticsearch. TODO TBD should we avoid indexing when possible?
final PersistedClusterStateService.Writer writer = persistedClusterStateService.createWriter();
try {
writer.writeFullStateAndCommit(currentTerm, lastAcceptedState);
} catch (Exception e) {
try {
writer.close();
} catch (Exception e2) {
e.addSuppressed(e2);
}
throw e;
}
persistenceWriter.set(writer);
}
@Override
public long getCurrentTerm() {
return currentTerm;
}
@Override
public ClusterState getLastAcceptedState() {
return lastAcceptedState;
}
@Override
public void setCurrentTerm(long currentTerm) {
try {
if (writeNextStateFully) {
getWriterSafe().writeFullStateAndCommit(currentTerm, lastAcceptedState);
writeNextStateFully = false;
} else {
getWriterSafe().commit(currentTerm, lastAcceptedState.version());
}
} catch (Exception e) {
handleExceptionOnWrite(e);
}
this.currentTerm = currentTerm;
}
@Override
public void setLastAcceptedState(ClusterState clusterState) {
try {
if (writeNextStateFully) {
getWriterSafe().writeFullStateAndCommit(currentTerm, clusterState);
writeNextStateFully = false;
} else {
if (clusterState.term() != lastAcceptedState.term()) {
assert clusterState.term() > lastAcceptedState.term() : clusterState.term() + " vs " + lastAcceptedState.term();
// In a new currentTerm, we cannot compare the persisted metadata's lastAcceptedVersion to those in the new state,
// so it's simplest to write everything again.
getWriterSafe().writeFullStateAndCommit(currentTerm, clusterState);
} else {
// Within the same currentTerm, we _can_ use metadata versions to skip unnecessary writing.
getWriterSafe().writeIncrementalStateAndCommit(currentTerm, lastAcceptedState, clusterState);
}
}
} catch (Exception e) {
handleExceptionOnWrite(e);
}
lastAcceptedState = clusterState;
}
private PersistedClusterStateService.Writer getWriterSafe() {
final PersistedClusterStateService.Writer writer = persistenceWriter.get();
if (writer == null) {
throw new AlreadyClosedException("persisted state has been closed");
}
if (writer.isOpen()) {
return writer;
} else {
try {
final PersistedClusterStateService.Writer newWriter = persistedClusterStateService.createWriter();
if (persistenceWriter.compareAndSet(writer, newWriter)) {
return newWriter;
} else {
assert persistenceWriter.get() == null : "expected no concurrent calls to getWriterSafe";
newWriter.close();
throw new AlreadyClosedException("persisted state has been closed");
}
} catch (Exception e) {
throw ExceptionsHelper.convertToRuntime(e);
}
}
}
private void handleExceptionOnWrite(Exception e) {
writeNextStateFully = true;
throw ExceptionsHelper.convertToRuntime(e);
}
@Override
public void close() throws IOException {
IOUtils.close(persistenceWriter.getAndSet(null));
}
}
} }

View File

@ -33,7 +33,6 @@ import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.Index; import org.elasticsearch.index.Index;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -53,9 +52,7 @@ public class IncrementalClusterStateWriter {
private final MetaStateService metaStateService; private final MetaStateService metaStateService;
// On master-eligible nodes we call updateClusterState under the Coordinator's mutex; on master-ineligible data nodes we call // We call updateClusterState on the (unique) cluster applier thread so there's no need to synchronize access to these fields.
// updateClusterState on the (unique) cluster applier thread; on other nodes we never call updateClusterState. In all cases there's
// no need to synchronize access to these fields.
private Manifest previousManifest; private Manifest previousManifest;
private ClusterState previousClusterState; private ClusterState previousClusterState;
private final LongSupplier relativeTimeMillisSupplier; private final LongSupplier relativeTimeMillisSupplier;
@ -89,10 +86,6 @@ public class IncrementalClusterStateWriter {
return previousManifest; return previousManifest;
} }
ClusterState getPreviousClusterState() {
return previousClusterState;
}
void setIncrementalWrite(boolean incrementalWrite) { void setIncrementalWrite(boolean incrementalWrite) {
this.incrementalWrite = incrementalWrite; this.incrementalWrite = incrementalWrite;
} }
@ -206,38 +199,20 @@ public class IncrementalClusterStateWriter {
return actions; return actions;
} }
private static Set<Index> getRelevantIndicesOnDataOnlyNode(ClusterState state) { // exposed for tests
RoutingNode newRoutingNode = state.getRoutingNodes().node(state.nodes().getLocalNodeId()); static Set<Index> getRelevantIndices(ClusterState state) {
assert state.nodes().getLocalNode().isDataNode();
final RoutingNode newRoutingNode = state.getRoutingNodes().node(state.nodes().getLocalNodeId());
if (newRoutingNode == null) { if (newRoutingNode == null) {
throw new IllegalStateException("cluster state does not contain this node - cannot write index meta state"); throw new IllegalStateException("cluster state does not contain this node - cannot write index meta state");
} }
Set<Index> indices = new HashSet<>(); final Set<Index> indices = new HashSet<>();
for (ShardRouting routing : newRoutingNode) { for (final ShardRouting routing : newRoutingNode) {
indices.add(routing.index()); indices.add(routing.index());
} }
return indices; return indices;
} }
private static Set<Index> getRelevantIndicesForMasterEligibleNode(ClusterState state) {
Set<Index> relevantIndices = new HashSet<>();
// we have to iterate over the metadata to make sure we also capture closed indices
for (IndexMetaData indexMetaData : state.metaData()) {
relevantIndices.add(indexMetaData.getIndex());
}
return relevantIndices;
}
// exposed for tests
static Set<Index> getRelevantIndices(ClusterState state) {
if (state.nodes().getLocalNode().isMasterNode()) {
return getRelevantIndicesForMasterEligibleNode(state);
} else if (state.nodes().getLocalNode().isDataNode()) {
return getRelevantIndicesOnDataOnlyNode(state);
} else {
return Collections.emptySet();
}
}
/** /**
* Action to perform with index metadata. * Action to perform with index metadata.
*/ */

View File

@ -69,7 +69,7 @@ public class MetaStateService {
* meta state with globalGeneration -1 and empty meta data is returned. * meta state with globalGeneration -1 and empty meta data is returned.
* @throws IOException if some IOException when loading files occurs or there is no metadata referenced by manifest file. * @throws IOException if some IOException when loading files occurs or there is no metadata referenced by manifest file.
*/ */
Tuple<Manifest, MetaData> loadFullState() throws IOException { public Tuple<Manifest, MetaData> loadFullState() throws IOException {
final Manifest manifest = MANIFEST_FORMAT.loadLatestState(logger, namedXContentRegistry, nodeEnv.nodeDataPaths()); final Manifest manifest = MANIFEST_FORMAT.loadLatestState(logger, namedXContentRegistry, nodeEnv.nodeDataPaths());
if (manifest == null) { if (manifest == null) {
return loadFullStateBWC(); return loadFullStateBWC();
@ -275,28 +275,26 @@ public class MetaStateService {
} }
/** /**
* Writes index metadata and updates manifest file accordingly. * Creates empty cluster state file on disk, deleting global metadata and unreferencing all index metadata
* Used by tests. * (only used for dangling indices at that point).
*/ */
public void writeIndexAndUpdateManifest(String reason, IndexMetaData metaData) throws IOException { public void unreferenceAll() throws IOException {
long generation = writeIndex(reason, metaData); MANIFEST_FORMAT.writeAndCleanup(Manifest.empty(), nodeEnv.nodeDataPaths()); // write empty file so that indices become unreferenced
Manifest manifest = loadManifestOrEmpty(); META_DATA_FORMAT.cleanupOldFiles(Long.MAX_VALUE, nodeEnv.nodeDataPaths());
Map<Index, Long> indices = new HashMap<>(manifest.getIndexGenerations());
indices.put(metaData.getIndex(), generation);
manifest = new Manifest(manifest.getCurrentTerm(), manifest.getClusterStateVersion(), manifest.getGlobalGeneration(), indices);
writeManifestAndCleanup(reason, manifest);
cleanupIndex(metaData.getIndex(), generation);
} }
/** /**
* Writes global metadata and updates manifest file accordingly. * Removes manifest file, global metadata and all index metadata
* Used by tests.
*/ */
public void writeGlobalStateAndUpdateManifest(String reason, MetaData metaData) throws IOException { public void deleteAll() throws IOException {
long generation = writeGlobalState(reason, metaData); // To ensure that the metadata is never reimported by loadFullStateBWC in case where the deletions here fail mid-way through,
Manifest manifest = loadManifestOrEmpty(); // we first write an empty manifest file so that the indices become unreferenced, then clean up the indices, and only then delete
manifest = new Manifest(manifest.getCurrentTerm(), manifest.getClusterStateVersion(), generation, manifest.getIndexGenerations()); // the manifest file.
writeManifestAndCleanup(reason, manifest); unreferenceAll();
cleanupGlobalState(generation); for (String indexFolderName : nodeEnv.availableIndexFolders()) {
// delete meta state directories of indices
MetaDataStateFormat.deleteMetaState(nodeEnv.resolveIndexFolder(indexFolderName));
}
MANIFEST_FORMAT.cleanupOldFiles(Long.MAX_VALUE, nodeEnv.nodeDataPaths()); // finally delete manifest
} }
} }

View File

@ -0,0 +1,770 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.gateway;
import com.carrotsearch.hppc.cursors.ObjectCursor;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.apache.lucene.analysis.core.KeywordAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexNotFoundException;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.SerialMergeScheduler;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreMode;
import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.Weight;
import org.apache.lucene.store.AlreadyClosedException;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.SimpleFSDirectory;
import org.apache.lucene.util.Bits;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.SetOnce;
import org.elasticsearch.Version;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.common.CheckedConsumer;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.io.stream.ReleasableBytesStreamOutput;
import org.elasticsearch.common.lease.Releasable;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.lucene.Lucene;
import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.core.internal.io.IOUtils;
import org.elasticsearch.env.NodeEnvironment;
import org.elasticsearch.env.NodeMetaData;
import org.elasticsearch.index.Index;
import java.io.Closeable;
import java.io.FilterOutputStream;
import java.io.IOError;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.IntPredicate;
/**
* Stores cluster metadata in a bare Lucene index (per data path) split across a number of documents. This is used by master-eligible nodes
* to record the last-accepted cluster state during publication. The metadata is written incrementally where possible, leaving alone any
* documents that have not changed. The index has the following fields:
*
* +------------------------------+-----------------------------+----------------------------------------------+
* | "type" (string field) | "index_uuid" (string field) | "data" (stored binary field in SMILE format) |
* +------------------------------+-----------------------------+----------------------------------------------+
* | GLOBAL_TYPE_NAME == "global" | (omitted) | Global metadata |
* | INDEX_TYPE_NAME == "index" | Index UUID | Index metadata |
* +------------------------------+-----------------------------+----------------------------------------------+
*
* Additionally each commit has the following user data:
*
* +---------------------------+-------------------------+-------------------------------------------------------------------------------+
* | Key symbol | Key literal | Value |
* +---------------------------+-------------------------+-------------------------------------------------------------------------------+
* | CURRENT_TERM_KEY | "current_term" | Node's "current" term ( last-accepted term and the terms of all sent joins) |
* | LAST_ACCEPTED_VERSION_KEY | "last_accepted_version" | The cluster state version corresponding with the persisted metadata |
* | NODE_ID_KEY | "node_id" | The (persistent) ID of the node that wrote this metadata |
* | NODE_VERSION_KEY | "node_version" | The (ID of the) version of the node that wrote this metadata |
* +---------------------------+-------------------------+-------------------------------------------------------------------------------+
*
* (the last-accepted term is recorded in MetaData CoordinationMetaData so does not need repeating here)
*/
public class PersistedClusterStateService {
private static final Logger logger = LogManager.getLogger(PersistedClusterStateService.class);
private static final String CURRENT_TERM_KEY = "current_term";
private static final String LAST_ACCEPTED_VERSION_KEY = "last_accepted_version";
private static final String NODE_ID_KEY = "node_id";
private static final String NODE_VERSION_KEY = "node_version";
private static final String TYPE_FIELD_NAME = "type";
private static final String DATA_FIELD_NAME = "data";
private static final String GLOBAL_TYPE_NAME = "global";
private static final String INDEX_TYPE_NAME = "index";
private static final String INDEX_UUID_FIELD_NAME = "index_uuid";
private static final int COMMIT_DATA_SIZE = 4;
public static final String METADATA_DIRECTORY_NAME = MetaDataStateFormat.STATE_DIR_NAME;
private final Path[] dataPaths;
private final String nodeId;
private final NamedXContentRegistry namedXContentRegistry;
private final BigArrays bigArrays;
private final boolean preserveUnknownCustoms;
public PersistedClusterStateService(NodeEnvironment nodeEnvironment, NamedXContentRegistry namedXContentRegistry, BigArrays bigArrays) {
this(nodeEnvironment.nodeDataPaths(), nodeEnvironment.nodeId(), namedXContentRegistry, bigArrays, false);
}
public PersistedClusterStateService(Path[] dataPaths, String nodeId, NamedXContentRegistry namedXContentRegistry,
BigArrays bigArrays, boolean preserveUnknownCustoms) {
this.dataPaths = dataPaths;
this.nodeId = nodeId;
this.namedXContentRegistry = namedXContentRegistry;
this.bigArrays = bigArrays;
this.preserveUnknownCustoms = preserveUnknownCustoms;
}
public String getNodeId() {
return nodeId;
}
/**
* Creates a new disk-based writer for cluster states
*/
public Writer createWriter() throws IOException {
final List<MetaDataIndexWriter> metaDataIndexWriters = new ArrayList<>();
final List<Closeable> closeables = new ArrayList<>();
boolean success = false;
try {
for (final Path path : dataPaths) {
final Directory directory = createDirectory(path.resolve(METADATA_DIRECTORY_NAME));
closeables.add(directory);
final IndexWriter indexWriter = createIndexWriter(directory, false);
closeables.add(indexWriter);
metaDataIndexWriters.add(new MetaDataIndexWriter(directory, indexWriter));
}
success = true;
} finally {
if (success == false) {
IOUtils.closeWhileHandlingException(closeables);
}
}
return new Writer(metaDataIndexWriters, nodeId, bigArrays);
}
private static IndexWriter createIndexWriter(Directory directory, boolean openExisting) throws IOException {
final IndexWriterConfig indexWriterConfig = new IndexWriterConfig(new KeywordAnalyzer());
// start empty since we re-write the whole cluster state to ensure it is all using the same format version
indexWriterConfig.setOpenMode(openExisting ? IndexWriterConfig.OpenMode.APPEND : IndexWriterConfig.OpenMode.CREATE);
// only commit when specifically instructed, we must not write any intermediate states
indexWriterConfig.setCommitOnClose(false);
// most of the data goes into stored fields which are not buffered, so we only really need a tiny buffer
indexWriterConfig.setRAMBufferSizeMB(1.0);
// merge on the write thread (e.g. while flushing)
indexWriterConfig.setMergeScheduler(new SerialMergeScheduler());
return new IndexWriter(directory, indexWriterConfig);
}
/**
* Remove all persisted cluster states from the given data paths, for use in tests. Should only be called when there is no open
* {@link Writer} on these paths.
*/
public static void deleteAll(Path[] dataPaths) throws IOException {
for (Path dataPath : dataPaths) {
Lucene.cleanLuceneIndex(new SimpleFSDirectory(dataPath.resolve(METADATA_DIRECTORY_NAME)));
}
}
// exposed for tests
Directory createDirectory(Path path) throws IOException {
// it is possible to disable the use of MMapDirectory for indices, and it may be surprising to users that have done so if we still
// use a MMapDirectory here, which might happen with FSDirectory.open(path). Concurrency is of no concern here so a
// SimpleFSDirectory is fine:
return new SimpleFSDirectory(path);
}
public Path[] getDataPaths() {
return dataPaths;
}
public static class OnDiskState {
private static final OnDiskState NO_ON_DISK_STATE = new OnDiskState(null, null, 0L, 0L, MetaData.EMPTY_META_DATA);
private final String nodeId;
private final Path dataPath;
public final long currentTerm;
public final long lastAcceptedVersion;
public final MetaData metaData;
private OnDiskState(String nodeId, Path dataPath, long currentTerm, long lastAcceptedVersion, MetaData metaData) {
this.nodeId = nodeId;
this.dataPath = dataPath;
this.currentTerm = currentTerm;
this.lastAcceptedVersion = lastAcceptedVersion;
this.metaData = metaData;
}
public boolean empty() {
return this == NO_ON_DISK_STATE;
}
}
/**
* Returns the node metadata for the given data paths, and checks if the node ids are unique
* @param dataPaths the data paths to scan
*/
@Nullable
public static NodeMetaData nodeMetaData(Path... dataPaths) throws IOException {
String nodeId = null;
Version version = null;
for (final Path dataPath : dataPaths) {
final Path indexPath = dataPath.resolve(METADATA_DIRECTORY_NAME);
if (Files.exists(indexPath)) {
try (DirectoryReader reader = DirectoryReader.open(new SimpleFSDirectory(dataPath.resolve(METADATA_DIRECTORY_NAME)))) {
final Map<String, String> userData = reader.getIndexCommit().getUserData();
assert userData.get(NODE_VERSION_KEY) != null;
final String thisNodeId = userData.get(NODE_ID_KEY);
assert thisNodeId != null;
if (nodeId != null && nodeId.equals(thisNodeId) == false) {
throw new IllegalStateException("unexpected node ID in metadata, found [" + thisNodeId +
"] in [" + dataPath + "] but expected [" + nodeId + "]");
} else if (nodeId == null) {
nodeId = thisNodeId;
version = Version.fromId(Integer.parseInt(userData.get(NODE_VERSION_KEY)));
}
} catch (IndexNotFoundException e) {
logger.debug(new ParameterizedMessage("no on-disk state at {}", indexPath), e);
}
}
}
if (nodeId == null) {
return null;
}
return new NodeMetaData(nodeId, version);
}
/**
* Overrides the version field for the metadata in the given data path
*/
public static void overrideVersion(Version newVersion, Path... dataPaths) throws IOException {
for (final Path dataPath : dataPaths) {
final Path indexPath = dataPath.resolve(METADATA_DIRECTORY_NAME);
if (Files.exists(indexPath)) {
try (DirectoryReader reader = DirectoryReader.open(new SimpleFSDirectory(dataPath.resolve(METADATA_DIRECTORY_NAME)))) {
final Map<String, String> userData = reader.getIndexCommit().getUserData();
assert userData.get(NODE_VERSION_KEY) != null;
try (IndexWriter indexWriter =
createIndexWriter(new SimpleFSDirectory(dataPath.resolve(METADATA_DIRECTORY_NAME)), true)) {
final Map<String, String> commitData = new HashMap<>(userData);
commitData.put(NODE_VERSION_KEY, Integer.toString(newVersion.id));
indexWriter.setLiveCommitData(commitData.entrySet());
indexWriter.commit();
}
} catch (IndexNotFoundException e) {
logger.debug(new ParameterizedMessage("no on-disk state at {}", indexPath), e);
}
}
}
}
/**
* Loads the best available on-disk cluster state. Returns {@link OnDiskState#NO_ON_DISK_STATE} if no such state was found.
*/
public OnDiskState loadBestOnDiskState() throws IOException {
String committedClusterUuid = null;
Path committedClusterUuidPath = null;
OnDiskState bestOnDiskState = OnDiskState.NO_ON_DISK_STATE;
OnDiskState maxCurrentTermOnDiskState = bestOnDiskState;
// We use a write-all-read-one strategy: metadata is written to every data path when accepting it, which means it is mostly
// sufficient to read _any_ copy. "Mostly" sufficient because the user can change the set of data paths when restarting, and may
// add a data path containing a stale copy of the metadata. We deal with this by using the freshest copy we can find.
for (final Path dataPath : dataPaths) {
final Path indexPath = dataPath.resolve(METADATA_DIRECTORY_NAME);
if (Files.exists(indexPath)) {
try (Directory directory = createDirectory(indexPath);
DirectoryReader directoryReader = DirectoryReader.open(directory)) {
final OnDiskState onDiskState = loadOnDiskState(dataPath, directoryReader);
if (nodeId.equals(onDiskState.nodeId) == false) {
throw new IllegalStateException("unexpected node ID in metadata, found [" + onDiskState.nodeId +
"] in [" + dataPath + "] but expected [" + nodeId + "]");
}
if (onDiskState.metaData.clusterUUIDCommitted()) {
if (committedClusterUuid == null) {
committedClusterUuid = onDiskState.metaData.clusterUUID();
committedClusterUuidPath = dataPath;
} else if (committedClusterUuid.equals(onDiskState.metaData.clusterUUID()) == false) {
throw new IllegalStateException("mismatched cluster UUIDs in metadata, found [" + committedClusterUuid +
"] in [" + committedClusterUuidPath + "] and [" + onDiskState.metaData.clusterUUID() + "] in ["
+ dataPath + "]");
}
}
if (maxCurrentTermOnDiskState.empty() || maxCurrentTermOnDiskState.currentTerm < onDiskState.currentTerm) {
maxCurrentTermOnDiskState = onDiskState;
}
long acceptedTerm = onDiskState.metaData.coordinationMetaData().term();
long maxAcceptedTerm = bestOnDiskState.metaData.coordinationMetaData().term();
if (bestOnDiskState.empty()
|| acceptedTerm > maxAcceptedTerm
|| (acceptedTerm == maxAcceptedTerm
&& (onDiskState.lastAcceptedVersion > bestOnDiskState.lastAcceptedVersion
|| (onDiskState.lastAcceptedVersion == bestOnDiskState.lastAcceptedVersion)
&& onDiskState.currentTerm > bestOnDiskState.currentTerm))) {
bestOnDiskState = onDiskState;
}
} catch (IndexNotFoundException e) {
logger.debug(new ParameterizedMessage("no on-disk state at {}", indexPath), e);
}
}
}
if (bestOnDiskState.currentTerm != maxCurrentTermOnDiskState.currentTerm) {
throw new IllegalStateException("inconsistent terms found: best state is from [" + bestOnDiskState.dataPath +
"] in term [" + bestOnDiskState.currentTerm + "] but there is a stale state in [" + maxCurrentTermOnDiskState.dataPath +
"] with greater term [" + maxCurrentTermOnDiskState.currentTerm + "]");
}
return bestOnDiskState;
}
private OnDiskState loadOnDiskState(Path dataPath, DirectoryReader reader) throws IOException {
final IndexSearcher searcher = new IndexSearcher(reader);
searcher.setQueryCache(null);
final SetOnce<MetaData.Builder> builderReference = new SetOnce<>();
consumeFromType(searcher, GLOBAL_TYPE_NAME, bytes ->
{
final MetaData metaData = MetaData.Builder.fromXContent(XContentFactory.xContent(XContentType.SMILE)
.createParser(namedXContentRegistry, LoggingDeprecationHandler.INSTANCE, bytes.bytes, bytes.offset, bytes.length),
preserveUnknownCustoms);
logger.trace("found global metadata with last-accepted term [{}]", metaData.coordinationMetaData().term());
if (builderReference.get() != null) {
throw new IllegalStateException("duplicate global metadata found in [" + dataPath + "]");
}
builderReference.set(MetaData.builder(metaData));
});
final MetaData.Builder builder = builderReference.get();
if (builder == null) {
throw new IllegalStateException("no global metadata found in [" + dataPath + "]");
}
logger.trace("got global metadata, now reading index metadata");
final Set<String> indexUUIDs = new HashSet<>();
consumeFromType(searcher, INDEX_TYPE_NAME, bytes ->
{
final IndexMetaData indexMetaData = IndexMetaData.fromXContent(XContentFactory.xContent(XContentType.SMILE)
.createParser(namedXContentRegistry, LoggingDeprecationHandler.INSTANCE, bytes.bytes, bytes.offset, bytes.length));
logger.trace("found index metadata for {}", indexMetaData.getIndex());
if (indexUUIDs.add(indexMetaData.getIndexUUID()) == false) {
throw new IllegalStateException("duplicate metadata found for " + indexMetaData.getIndex() + " in [" + dataPath + "]");
}
builder.put(indexMetaData, false);
});
final Map<String, String> userData = reader.getIndexCommit().getUserData();
logger.trace("loaded metadata [{}] from [{}]", userData, reader.directory());
assert userData.size() == COMMIT_DATA_SIZE : userData;
assert userData.get(CURRENT_TERM_KEY) != null;
assert userData.get(LAST_ACCEPTED_VERSION_KEY) != null;
assert userData.get(NODE_ID_KEY) != null;
assert userData.get(NODE_VERSION_KEY) != null;
return new OnDiskState(userData.get(NODE_ID_KEY), dataPath, Long.parseLong(userData.get(CURRENT_TERM_KEY)),
Long.parseLong(userData.get(LAST_ACCEPTED_VERSION_KEY)), builder.build());
}
private static void consumeFromType(IndexSearcher indexSearcher, String type,
CheckedConsumer<BytesRef, IOException> bytesRefConsumer) throws IOException {
final Query query = new TermQuery(new Term(TYPE_FIELD_NAME, type));
final Weight weight = indexSearcher.createWeight(query, ScoreMode.COMPLETE_NO_SCORES, 0.0f);
logger.trace("running query [{}]", query);
for (LeafReaderContext leafReaderContext : indexSearcher.getIndexReader().leaves()) {
logger.trace("new leafReaderContext: {}", leafReaderContext);
final Scorer scorer = weight.scorer(leafReaderContext);
if (scorer != null) {
final Bits liveDocs = leafReaderContext.reader().getLiveDocs();
final IntPredicate isLiveDoc = liveDocs == null ? i -> true : liveDocs::get;
final DocIdSetIterator docIdSetIterator = scorer.iterator();
while (docIdSetIterator.nextDoc() != DocIdSetIterator.NO_MORE_DOCS) {
if (isLiveDoc.test(docIdSetIterator.docID())) {
logger.trace("processing doc {}", docIdSetIterator.docID());
bytesRefConsumer.accept(
leafReaderContext.reader().document(docIdSetIterator.docID()).getBinaryValue(DATA_FIELD_NAME));
}
}
}
}
}
private static final ToXContent.Params FORMAT_PARAMS;
static {
Map<String, String> params = new HashMap<>(2);
params.put("binary", "true");
params.put(MetaData.CONTEXT_MODE_PARAM, MetaData.CONTEXT_MODE_GATEWAY);
FORMAT_PARAMS = new ToXContent.MapParams(params);
}
/**
* A {@link Document} with a stored field containing serialized metadata written to a {@link ReleasableBytesStreamOutput} which must be
* released when no longer needed.
*/
private static class ReleasableDocument implements Releasable {
private final Document document;
private final Releasable releasable;
ReleasableDocument(Document document, Releasable releasable) {
this.document = document;
this.releasable = releasable;
}
Document getDocument() {
return document;
}
@Override
public void close() {
releasable.close();
}
}
/**
* Encapsulates a single {@link IndexWriter} with its {@link Directory} for ease of closing, and a {@link Logger}. There is one of these
* for each data path.
*/
private static class MetaDataIndexWriter implements Closeable {
private final Logger logger;
private final Directory directory;
private final IndexWriter indexWriter;
MetaDataIndexWriter(Directory directory, IndexWriter indexWriter) {
this.directory = directory;
this.indexWriter = indexWriter;
this.logger = Loggers.getLogger(MetaDataIndexWriter.class, directory.toString());
}
void deleteAll() throws IOException {
this.logger.trace("clearing existing metadata");
this.indexWriter.deleteAll();
}
void updateIndexMetaDataDocument(Document indexMetaDataDocument, Index index) throws IOException {
this.logger.trace("updating metadata for [{}]", index);
indexWriter.updateDocument(new Term(INDEX_UUID_FIELD_NAME, index.getUUID()), indexMetaDataDocument);
}
void updateGlobalMetaData(Document globalMetaDataDocument) throws IOException {
this.logger.trace("updating global metadata doc");
indexWriter.updateDocument(new Term(TYPE_FIELD_NAME, GLOBAL_TYPE_NAME), globalMetaDataDocument);
}
void deleteIndexMetaData(String indexUUID) throws IOException {
this.logger.trace("removing metadata for [{}]", indexUUID);
indexWriter.deleteDocuments(new Term(INDEX_UUID_FIELD_NAME, indexUUID));
}
void flush() throws IOException {
this.logger.trace("flushing");
this.indexWriter.flush();
}
void prepareCommit(String nodeId, long currentTerm, long lastAcceptedVersion) throws IOException {
final Map<String, String> commitData = new HashMap<>(COMMIT_DATA_SIZE);
commitData.put(CURRENT_TERM_KEY, Long.toString(currentTerm));
commitData.put(LAST_ACCEPTED_VERSION_KEY, Long.toString(lastAcceptedVersion));
commitData.put(NODE_VERSION_KEY, Integer.toString(Version.CURRENT.id));
commitData.put(NODE_ID_KEY, nodeId);
indexWriter.setLiveCommitData(commitData.entrySet());
indexWriter.prepareCommit();
}
void commit() throws IOException {
indexWriter.commit();
}
@Override
public void close() throws IOException {
IOUtils.close(indexWriter, directory);
}
}
public static class Writer implements Closeable {
private final List<MetaDataIndexWriter> metaDataIndexWriters;
private final String nodeId;
private final BigArrays bigArrays;
boolean fullStateWritten = false;
private final AtomicBoolean closed = new AtomicBoolean();
private Writer(List<MetaDataIndexWriter> metaDataIndexWriters, String nodeId, BigArrays bigArrays) {
this.metaDataIndexWriters = metaDataIndexWriters;
this.nodeId = nodeId;
this.bigArrays = bigArrays;
}
private void ensureOpen() {
if (closed.get()) {
throw new AlreadyClosedException("cluster state writer is closed already");
}
}
public boolean isOpen() {
return closed.get() == false;
}
private void closeIfAnyIndexWriterHasTragedyOrIsClosed() {
if (metaDataIndexWriters.stream().map(writer -> writer.indexWriter)
.anyMatch(iw -> iw.getTragicException() != null || iw.isOpen() == false)) {
try {
close();
} catch (Exception e) {
logger.warn("failed on closing cluster state writer", e);
}
}
}
/**
* Overrides and commits the given current term and cluster state
*/
public void writeFullStateAndCommit(long currentTerm, ClusterState clusterState) throws IOException {
ensureOpen();
try {
overwriteMetaData(clusterState.metaData());
commit(currentTerm, clusterState.version());
fullStateWritten = true;
} finally {
closeIfAnyIndexWriterHasTragedyOrIsClosed();
}
}
/**
* Updates and commits the given cluster state update
*/
void writeIncrementalStateAndCommit(long currentTerm, ClusterState previousClusterState,
ClusterState clusterState) throws IOException {
ensureOpen();
assert fullStateWritten : "Need to write full state first before doing incremental writes";
try {
updateMetaData(previousClusterState.metaData(), clusterState.metaData());
commit(currentTerm, clusterState.version());
} finally {
closeIfAnyIndexWriterHasTragedyOrIsClosed();
}
}
/**
* Update the persisted metadata to match the given cluster state by removing any stale or unnecessary documents and adding any
* updated documents.
*/
private void updateMetaData(MetaData previouslyWrittenMetaData, MetaData metaData) throws IOException {
assert previouslyWrittenMetaData.coordinationMetaData().term() == metaData.coordinationMetaData().term();
logger.trace("currentTerm [{}] matches previous currentTerm, writing changes only",
metaData.coordinationMetaData().term());
if (MetaData.isGlobalStateEquals(previouslyWrittenMetaData, metaData) == false) {
try (ReleasableDocument globalMetaDataDocument = makeGlobalMetaDataDocument(metaData)) {
for (MetaDataIndexWriter metaDataIndexWriter : metaDataIndexWriters) {
metaDataIndexWriter.updateGlobalMetaData(globalMetaDataDocument.getDocument());
}
}
}
final Map<String, Long> indexMetaDataVersionByUUID = new HashMap<>(previouslyWrittenMetaData.indices().size());
for (ObjectCursor<IndexMetaData> cursor : previouslyWrittenMetaData.indices().values()) {
final IndexMetaData indexMetaData = cursor.value;
final Long previousValue = indexMetaDataVersionByUUID.putIfAbsent(indexMetaData.getIndexUUID(), indexMetaData.getVersion());
assert previousValue == null : indexMetaData.getIndexUUID() + " already mapped to " + previousValue;
}
for (ObjectCursor<IndexMetaData> cursor : metaData.indices().values()) {
final IndexMetaData indexMetaData = cursor.value;
final Long previousVersion = indexMetaDataVersionByUUID.get(indexMetaData.getIndexUUID());
if (previousVersion == null || indexMetaData.getVersion() != previousVersion) {
logger.trace("updating metadata for [{}], changing version from [{}] to [{}]",
indexMetaData.getIndex(), previousVersion, indexMetaData.getVersion());
try (ReleasableDocument indexMetaDataDocument = makeIndexMetaDataDocument(indexMetaData)) {
for (MetaDataIndexWriter metaDataIndexWriter : metaDataIndexWriters) {
metaDataIndexWriter.updateIndexMetaDataDocument(indexMetaDataDocument.getDocument(), indexMetaData.getIndex());
}
}
} else {
logger.trace("no action required for [{}]", indexMetaData.getIndex());
}
indexMetaDataVersionByUUID.remove(indexMetaData.getIndexUUID());
}
for (String removedIndexUUID : indexMetaDataVersionByUUID.keySet()) {
for (MetaDataIndexWriter metaDataIndexWriter : metaDataIndexWriters) {
metaDataIndexWriter.deleteIndexMetaData(removedIndexUUID);
}
}
// Flush, to try and expose a failure (e.g. out of disk space) before committing, because we can handle a failure here more
// gracefully than one that occurs during the commit process.
for (MetaDataIndexWriter metaDataIndexWriter : metaDataIndexWriters) {
metaDataIndexWriter.flush();
}
}
/**
* Update the persisted metadata to match the given cluster state by removing all existing documents and then adding new documents.
*/
private void overwriteMetaData(MetaData metaData) throws IOException {
for (MetaDataIndexWriter metaDataIndexWriter : metaDataIndexWriters) {
metaDataIndexWriter.deleteAll();
}
addMetaData(metaData);
}
/**
* Add documents for the metadata of the given cluster state, assuming that there are currently no documents.
*/
private void addMetaData(MetaData metaData) throws IOException {
try (ReleasableDocument globalMetaDataDocument = makeGlobalMetaDataDocument(metaData)) {
for (MetaDataIndexWriter metaDataIndexWriter : metaDataIndexWriters) {
metaDataIndexWriter.updateGlobalMetaData(globalMetaDataDocument.getDocument());
}
}
for (ObjectCursor<IndexMetaData> cursor : metaData.indices().values()) {
final IndexMetaData indexMetaData = cursor.value;
try (ReleasableDocument indexMetaDataDocument = makeIndexMetaDataDocument(indexMetaData)) {
for (MetaDataIndexWriter metaDataIndexWriter : metaDataIndexWriters) {
metaDataIndexWriter.updateIndexMetaDataDocument(indexMetaDataDocument.getDocument(), indexMetaData.getIndex());
}
}
}
// Flush, to try and expose a failure (e.g. out of disk space) before committing, because we can handle a failure here more
// gracefully than one that occurs during the commit process.
for (MetaDataIndexWriter metaDataIndexWriter : metaDataIndexWriters) {
metaDataIndexWriter.flush();
}
}
public void commit(long currentTerm, long lastAcceptedVersion) throws IOException {
ensureOpen();
try {
for (MetaDataIndexWriter metaDataIndexWriter : metaDataIndexWriters) {
metaDataIndexWriter.prepareCommit(nodeId, currentTerm, lastAcceptedVersion);
}
} catch (Exception e) {
try {
close();
} catch (Exception e2) {
logger.warn("failed on closing cluster state writer", e2);
e.addSuppressed(e2);
}
throw e;
} finally {
closeIfAnyIndexWriterHasTragedyOrIsClosed();
}
try {
for (MetaDataIndexWriter metaDataIndexWriter : metaDataIndexWriters) {
metaDataIndexWriter.commit();
}
} catch (IOException e) {
// The commit() call has similar semantics to a fsync(): although it's atomic, if it fails then we've no idea whether the
// data on disk is now the old version or the new version, and this is a disaster. It's safest to fail the whole node and
// retry from the beginning.
try {
close();
} catch (Exception e2) {
e.addSuppressed(e2);
}
throw new IOError(e);
} finally {
closeIfAnyIndexWriterHasTragedyOrIsClosed();
}
}
@Override
public void close() throws IOException {
logger.trace("closing PersistedClusterStateService.Writer");
if (closed.compareAndSet(false, true)) {
IOUtils.close(metaDataIndexWriters);
}
}
private ReleasableDocument makeIndexMetaDataDocument(IndexMetaData indexMetaData) throws IOException {
final ReleasableDocument indexMetaDataDocument = makeDocument(INDEX_TYPE_NAME, indexMetaData);
boolean success = false;
try {
final String indexUUID = indexMetaData.getIndexUUID();
assert indexUUID.equals(IndexMetaData.INDEX_UUID_NA_VALUE) == false;
indexMetaDataDocument.getDocument().add(new StringField(INDEX_UUID_FIELD_NAME, indexUUID, Field.Store.NO));
success = true;
return indexMetaDataDocument;
} finally {
if (success == false) {
IOUtils.closeWhileHandlingException(indexMetaDataDocument);
}
}
}
private ReleasableDocument makeGlobalMetaDataDocument(MetaData metaData) throws IOException {
return makeDocument(GLOBAL_TYPE_NAME, metaData);
}
private ReleasableDocument makeDocument(String typeName, ToXContent metaData) throws IOException {
final Document document = new Document();
document.add(new StringField(TYPE_FIELD_NAME, typeName, Field.Store.NO));
boolean success = false;
final ReleasableBytesStreamOutput releasableBytesStreamOutput = new ReleasableBytesStreamOutput(bigArrays);
try {
final FilterOutputStream outputStream = new FilterOutputStream(releasableBytesStreamOutput) {
@Override
public void close() {
// closing the XContentBuilder should not release the bytes yet
}
};
try (XContentBuilder xContentBuilder = XContentFactory.contentBuilder(XContentType.SMILE, outputStream)) {
xContentBuilder.startObject();
metaData.toXContent(xContentBuilder, FORMAT_PARAMS);
xContentBuilder.endObject();
}
document.add(new StoredField(DATA_FIELD_NAME, releasableBytesStreamOutput.bytes().toBytesRef()));
final ReleasableDocument releasableDocument = new ReleasableDocument(document, releasableBytesStreamOutput);
success = true;
return releasableDocument;
} finally {
if (success == false) {
IOUtils.closeWhileHandlingException(releasableBytesStreamOutput);
}
}
}
}
}

View File

@ -48,6 +48,8 @@ import org.elasticsearch.core.internal.io.IOUtils;
import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.env.NodeEnvironment;
import org.elasticsearch.env.ShardLock; import org.elasticsearch.env.ShardLock;
import org.elasticsearch.env.ShardLockObtainFailedException; import org.elasticsearch.env.ShardLockObtainFailedException;
import org.elasticsearch.gateway.MetaDataStateFormat;
import org.elasticsearch.gateway.WriteStateException;
import org.elasticsearch.index.analysis.IndexAnalyzers; import org.elasticsearch.index.analysis.IndexAnalyzers;
import org.elasticsearch.index.cache.IndexCache; import org.elasticsearch.index.cache.IndexCache;
import org.elasticsearch.index.cache.bitset.BitsetFilterCache; import org.elasticsearch.index.cache.bitset.BitsetFilterCache;
@ -90,6 +92,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BooleanSupplier; import java.util.function.BooleanSupplier;
@ -325,6 +328,29 @@ public class IndexService extends AbstractIndexComponent implements IndicesClust
} }
} }
// method is synchronized so that IndexService can't be closed while we're writing out dangling indices information
public synchronized void writeDanglingIndicesInfo() {
if (closed.get()) {
return;
}
try {
IndexMetaData.FORMAT.writeAndCleanup(getMetaData(), nodeEnv.indexPaths(index()));
} catch (WriteStateException e) {
logger.warn(() -> new ParameterizedMessage("failed to write dangling indices state for index {}", index()), e);
}
}
// method is synchronized so that IndexService can't be closed while we're deleting dangling indices information
public synchronized void deleteDanglingIndicesInfo() {
if (closed.get()) {
return;
}
try {
MetaDataStateFormat.deleteMetaState(nodeEnv.indexPaths(index()));
} catch (IOException e) {
logger.warn(() -> new ParameterizedMessage("failed to delete dangling indices state for index {}", index()), e);
}
}
public String indexUUID() { public String indexUUID() {
return indexSettings.getUUID(); return indexSettings.getUUID();
@ -671,9 +697,15 @@ public class IndexService extends AbstractIndexComponent implements IndicesClust
return indexSettings.getIndexMetaData(); return indexSettings.getIndexMetaData();
} }
private final CopyOnWriteArrayList<Consumer<IndexMetaData>> metaDataListeners = new CopyOnWriteArrayList<>();
public void addMetaDataListener(Consumer<IndexMetaData> listener) {
metaDataListeners.add(listener);
}
@Override @Override
public synchronized void updateMetaData(final IndexMetaData currentIndexMetaData, final IndexMetaData newIndexMetaData) { public synchronized void updateMetaData(final IndexMetaData currentIndexMetaData, final IndexMetaData newIndexMetaData) {
final boolean updateIndexMetaData = indexSettings.updateIndexMetaData(newIndexMetaData); final boolean updateIndexSettings = indexSettings.updateIndexMetaData(newIndexMetaData);
if (Assertions.ENABLED if (Assertions.ENABLED
&& currentIndexMetaData != null && currentIndexMetaData != null
@ -681,16 +713,16 @@ public class IndexService extends AbstractIndexComponent implements IndicesClust
final long currentSettingsVersion = currentIndexMetaData.getSettingsVersion(); final long currentSettingsVersion = currentIndexMetaData.getSettingsVersion();
final long newSettingsVersion = newIndexMetaData.getSettingsVersion(); final long newSettingsVersion = newIndexMetaData.getSettingsVersion();
if (currentSettingsVersion == newSettingsVersion) { if (currentSettingsVersion == newSettingsVersion) {
assert updateIndexMetaData == false; assert updateIndexSettings == false;
} else { } else {
assert updateIndexMetaData; assert updateIndexSettings;
assert currentSettingsVersion < newSettingsVersion : assert currentSettingsVersion < newSettingsVersion :
"expected current settings version [" + currentSettingsVersion + "] " "expected current settings version [" + currentSettingsVersion + "] "
+ "to be less than new settings version [" + newSettingsVersion + "]"; + "to be less than new settings version [" + newSettingsVersion + "]";
} }
} }
if (updateIndexMetaData) { if (updateIndexSettings) {
for (final IndexShard shard : this.shards.values()) { for (final IndexShard shard : this.shards.values()) {
try { try {
shard.onSettingsChanged(); shard.onSettingsChanged();
@ -726,6 +758,8 @@ public class IndexService extends AbstractIndexComponent implements IndicesClust
} }
updateFsyncTaskIfNecessary(); updateFsyncTaskIfNecessary();
} }
metaDataListeners.forEach(c -> c.accept(newIndexMetaData));
} }
private void updateFsyncTaskIfNecessary() { private void updateFsyncTaskIfNecessary() {

View File

@ -21,7 +21,6 @@ package org.elasticsearch.index.shard;
import joptsimple.OptionParser; import joptsimple.OptionParser;
import joptsimple.OptionSet; import joptsimple.OptionSet;
import joptsimple.OptionSpec; import joptsimple.OptionSpec;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriter;
@ -33,9 +32,9 @@ import org.apache.lucene.store.Lock;
import org.apache.lucene.store.LockObtainFailedException; import org.apache.lucene.store.LockObtainFailedException;
import org.apache.lucene.store.NativeFSLockFactory; import org.apache.lucene.store.NativeFSLockFactory;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.cli.EnvironmentAwareCommand;
import org.elasticsearch.cli.Terminal; import org.elasticsearch.cli.Terminal;
import org.elasticsearch.cluster.ClusterModule; import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.coordination.ElasticsearchNodeCommand;
import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.routing.AllocationId; import org.elasticsearch.cluster.routing.AllocationId;
import org.elasticsearch.cluster.routing.allocation.command.AllocateEmptyPrimaryAllocationCommand; import org.elasticsearch.cluster.routing.allocation.command.AllocateEmptyPrimaryAllocationCommand;
@ -49,11 +48,10 @@ import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.io.PathUtils; import org.elasticsearch.common.io.PathUtils;
import org.elasticsearch.common.lucene.Lucene; import org.elasticsearch.common.lucene.Lucene;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.env.Environment; import org.elasticsearch.env.Environment;
import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.env.NodeEnvironment;
import org.elasticsearch.env.NodeMetaData; import org.elasticsearch.env.NodeMetaData;
import org.elasticsearch.gateway.MetaDataStateFormat; import org.elasticsearch.gateway.PersistedClusterStateService;
import org.elasticsearch.index.Index; import org.elasticsearch.index.Index;
import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.IndexSettings;
import org.elasticsearch.index.engine.Engine; import org.elasticsearch.index.engine.Engine;
@ -65,15 +63,15 @@ import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.PrintStream; import java.io.PrintStream;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.nio.file.DirectoryStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.stream.StreamSupport;
public class RemoveCorruptedShardDataCommand extends EnvironmentAwareCommand { public class RemoveCorruptedShardDataCommand extends ElasticsearchNodeCommand {
private static final Logger logger = LogManager.getLogger(RemoveCorruptedShardDataCommand.class); private static final Logger logger = LogManager.getLogger(RemoveCorruptedShardDataCommand.class);
@ -84,7 +82,6 @@ public class RemoveCorruptedShardDataCommand extends EnvironmentAwareCommand {
private final RemoveCorruptedLuceneSegmentsAction removeCorruptedLuceneSegmentsAction; private final RemoveCorruptedLuceneSegmentsAction removeCorruptedLuceneSegmentsAction;
private final TruncateTranslogAction truncateTranslogAction; private final TruncateTranslogAction truncateTranslogAction;
private final NamedXContentRegistry namedXContentRegistry;
public RemoveCorruptedShardDataCommand() { public RemoveCorruptedShardDataCommand() {
super("Removes corrupted shard files"); super("Removes corrupted shard files");
@ -102,8 +99,6 @@ public class RemoveCorruptedShardDataCommand extends EnvironmentAwareCommand {
parser.accepts(TRUNCATE_CLEAN_TRANSLOG_FLAG, "Truncate the translog even if it is not corrupt"); parser.accepts(TRUNCATE_CLEAN_TRANSLOG_FLAG, "Truncate the translog even if it is not corrupt");
namedXContentRegistry = new NamedXContentRegistry(ClusterModule.getNamedXWriteables());
removeCorruptedLuceneSegmentsAction = new RemoveCorruptedLuceneSegmentsAction(); removeCorruptedLuceneSegmentsAction = new RemoveCorruptedLuceneSegmentsAction();
truncateTranslogAction = new TruncateTranslogAction(namedXContentRegistry); truncateTranslogAction = new TruncateTranslogAction(namedXContentRegistry);
} }
@ -123,11 +118,12 @@ public class RemoveCorruptedShardDataCommand extends EnvironmentAwareCommand {
return PathUtils.get(dirValue, "", ""); return PathUtils.get(dirValue, "", "");
} }
protected void findAndProcessShardPath(OptionSet options, Environment environment, CheckedConsumer<ShardPath, IOException> consumer) protected void findAndProcessShardPath(OptionSet options, Environment environment, Path[] dataPaths, int nodeLockId,
ClusterState clusterState, CheckedConsumer<ShardPath, IOException> consumer)
throws IOException { throws IOException {
final Settings settings = environment.settings(); final Settings settings = environment.settings();
final String indexName; final IndexMetaData indexMetaData;
final int shardId; final int shardId;
final int fromNodeId; final int fromNodeId;
final int toNodeId; final int toNodeId;
@ -141,81 +137,56 @@ public class RemoveCorruptedShardDataCommand extends EnvironmentAwareCommand {
throw new ElasticsearchException("index directory [" + indexPath + "], must exist and be a directory"); throw new ElasticsearchException("index directory [" + indexPath + "], must exist and be a directory");
} }
final IndexMetaData indexMetaData =
IndexMetaData.FORMAT.loadLatestState(logger, namedXContentRegistry, shardParent);
final String shardIdFileName = path.getFileName().toString(); final String shardIdFileName = path.getFileName().toString();
final String nodeIdFileName = shardParentParent.getParent().getFileName().toString(); final String nodeIdFileName = shardParentParent.getParent().getFileName().toString();
final String indexUUIDFolderName = shardParent.getFileName().toString();
if (Files.isDirectory(path) && shardIdFileName.chars().allMatch(Character::isDigit) // SHARD-ID path element check if (Files.isDirectory(path) && shardIdFileName.chars().allMatch(Character::isDigit) // SHARD-ID path element check
&& NodeEnvironment.INDICES_FOLDER.equals(shardParentParent.getFileName().toString()) // `indices` check && NodeEnvironment.INDICES_FOLDER.equals(shardParentParent.getFileName().toString()) // `indices` check
&& nodeIdFileName.chars().allMatch(Character::isDigit) // NODE-ID check && nodeIdFileName.chars().allMatch(Character::isDigit) // NODE-ID check
&& NodeEnvironment.NODES_FOLDER.equals(shardParentParent.getParent().getParent().getFileName().toString()) // `nodes` check && NodeEnvironment.NODES_FOLDER.equals(shardParentParent.getParent().getParent().getFileName().toString()) // `nodes` check
) { ) {
shardId = Integer.parseInt(shardIdFileName); shardId = Integer.parseInt(shardIdFileName);
indexName = indexMetaData.getIndex().getName();
fromNodeId = Integer.parseInt(nodeIdFileName); fromNodeId = Integer.parseInt(nodeIdFileName);
toNodeId = fromNodeId + 1; toNodeId = fromNodeId + 1;
indexMetaData = StreamSupport.stream(clusterState.metaData().indices().values().spliterator(), false)
.map(imd -> imd.value)
.filter(imd -> imd.getIndexUUID().equals(indexUUIDFolderName)).findFirst()
.orElse(null);
} else { } else {
throw new ElasticsearchException("Unable to resolve shard id. Wrong folder structure at [ " + path.toString() throw new ElasticsearchException("Unable to resolve shard id. Wrong folder structure at [ " + path.toString()
+ " ], expected .../nodes/[NODE-ID]/indices/[INDEX-UUID]/[SHARD-ID]"); + " ], expected .../nodes/[NODE-ID]/indices/[INDEX-UUID]/[SHARD-ID]");
} }
} else { } else {
// otherwise resolve shardPath based on the index name and shard id // otherwise resolve shardPath based on the index name and shard id
indexName = Objects.requireNonNull(indexNameOption.value(options), "Index name is required"); String indexName = Objects.requireNonNull(indexNameOption.value(options), "Index name is required");
shardId = Objects.requireNonNull(shardIdOption.value(options), "Shard ID is required"); shardId = Objects.requireNonNull(shardIdOption.value(options), "Shard ID is required");
indexMetaData = clusterState.metaData().index(indexName);
// resolve shard path in case of multi-node layout per environment
fromNodeId = 0;
toNodeId = NodeEnvironment.MAX_LOCAL_STORAGE_NODES_SETTING.get(settings);
} }
// have to iterate over possibleLockId as NodeEnvironment; on a contrast to it - we have to fail if node is busy
for (int possibleLockId = fromNodeId; possibleLockId < toNodeId; possibleLockId++) {
try {
try (NodeEnvironment.NodeLock nodeLock = new NodeEnvironment.NodeLock(possibleLockId, logger, environment, Files::exists)) {
final NodeEnvironment.NodePath[] nodePaths = nodeLock.getNodePaths();
for (NodeEnvironment.NodePath nodePath : nodePaths) {
if (Files.exists(nodePath.indicesPath)) {
// have to scan all index uuid folders to resolve from index name
try (DirectoryStream<Path> stream = Files.newDirectoryStream(nodePath.indicesPath)) {
for (Path file : stream) {
if (Files.exists(file.resolve(MetaDataStateFormat.STATE_DIR_NAME)) == false) {
continue;
}
final IndexMetaData indexMetaData =
IndexMetaData.FORMAT.loadLatestState(logger, namedXContentRegistry, file);
if (indexMetaData == null) { if (indexMetaData == null) {
continue; throw new ElasticsearchException("Unable to find index in cluster state");
} }
final IndexSettings indexSettings = new IndexSettings(indexMetaData, settings); final IndexSettings indexSettings = new IndexSettings(indexMetaData, settings);
final Index index = indexMetaData.getIndex(); final Index index = indexMetaData.getIndex();
if (indexName.equals(index.getName()) == false) {
continue;
}
final ShardId shId = new ShardId(index, shardId); final ShardId shId = new ShardId(index, shardId);
final Path shardPathLocation = nodePath.resolve(shId); for (Path dataPath : dataPaths) {
if (Files.exists(shardPathLocation) == false) { final Path shardPathLocation = dataPath
continue; .resolve(NodeEnvironment.INDICES_FOLDER)
} .resolve(index.getUUID())
.resolve(Integer.toString(shId.id()));
if (Files.exists(shardPathLocation)) {
final ShardPath shardPath = ShardPath.loadShardPath(logger, shId, indexSettings.customDataPath(), final ShardPath shardPath = ShardPath.loadShardPath(logger, shId, indexSettings.customDataPath(),
new Path[]{shardPathLocation}, possibleLockId, nodePath.path); new Path[]{shardPathLocation}, nodeLockId, dataPath);
if (shardPath != null) { if (shardPath != null) {
consumer.accept(shardPath); consumer.accept(shardPath);
return; return;
} }
} }
} }
} throw new ElasticsearchException("Unable to resolve shard path for index [" + indexMetaData.getIndex().getName() +
} "] and shard id [" + shardId + "]");
}
} catch (LockObtainFailedException lofe) {
throw new ElasticsearchException("Failed to lock node's directory [" + lofe.getMessage()
+ "], is Elasticsearch still running ?");
}
}
throw new ElasticsearchException("Unable to resolve shard path for index [" + indexName + "] and shard id [" + shardId + "]");
} }
public static boolean isCorruptMarkerFileIsPresent(final Directory directory) throws IOException { public static boolean isCorruptMarkerFileIsPresent(final Directory directory) throws IOException {
@ -267,11 +238,9 @@ public class RemoveCorruptedShardDataCommand extends EnvironmentAwareCommand {
} }
} }
private void warnAboutESShouldBeStopped(Terminal terminal) { private void warnAboutIndexBackup(Terminal terminal) {
terminal.println("-----------------------------------------------------------------------"); terminal.println("-----------------------------------------------------------------------");
terminal.println(""); terminal.println("");
terminal.println(" WARNING: Elasticsearch MUST be stopped before running this tool.");
terminal.println("");
terminal.println(" Please make a complete backup of your index before using this tool."); terminal.println(" Please make a complete backup of your index before using this tool.");
terminal.println(""); terminal.println("");
terminal.println("-----------------------------------------------------------------------"); terminal.println("-----------------------------------------------------------------------");
@ -279,10 +248,13 @@ public class RemoveCorruptedShardDataCommand extends EnvironmentAwareCommand {
// Visible for testing // Visible for testing
@Override @Override
public void execute(Terminal terminal, OptionSet options, Environment environment) throws Exception { public void processNodePaths(Terminal terminal, Path[] dataPaths, int nodeLockId, OptionSet options, Environment environment)
warnAboutESShouldBeStopped(terminal); throws IOException {
warnAboutIndexBackup(terminal);
findAndProcessShardPath(options, environment, shardPath -> { final ClusterState clusterState = loadTermAndClusterState(createPersistedClusterStateService(dataPaths), environment).v2();
findAndProcessShardPath(options, environment, dataPaths, nodeLockId, clusterState, shardPath -> {
final Path indexPath = shardPath.resolveIndex(); final Path indexPath = shardPath.resolveIndex();
final Path translogPath = shardPath.resolveTranslog(); final Path translogPath = shardPath.resolveTranslog();
final Path nodePath = getNodePath(shardPath); final Path nodePath = getNodePath(shardPath);
@ -332,7 +304,7 @@ public class RemoveCorruptedShardDataCommand extends EnvironmentAwareCommand {
terminal.println("Opening translog at " + translogPath); terminal.println("Opening translog at " + translogPath);
terminal.println(""); terminal.println("");
try { try {
translogCleanStatus = truncateTranslogAction.getCleanStatus(shardPath, indexDir); translogCleanStatus = truncateTranslogAction.getCleanStatus(shardPath, clusterState, indexDir);
} catch (Exception e) { } catch (Exception e) {
terminal.println(e.getMessage()); terminal.println(e.getMessage());
throw e; throw e;
@ -476,21 +448,17 @@ public class RemoveCorruptedShardDataCommand extends EnvironmentAwareCommand {
printRerouteCommand(shardPath, terminal, true); printRerouteCommand(shardPath, terminal, true);
} }
private void printRerouteCommand(ShardPath shardPath, Terminal terminal, boolean allocateStale) throws IOException { private void printRerouteCommand(ShardPath shardPath, Terminal terminal, boolean allocateStale)
final IndexMetaData indexMetaData = throws IOException {
IndexMetaData.FORMAT.loadLatestState(logger, namedXContentRegistry,
shardPath.getDataPath().getParent());
final Path nodePath = getNodePath(shardPath); final Path nodePath = getNodePath(shardPath);
final NodeMetaData nodeMetaData = final NodeMetaData nodeMetaData = PersistedClusterStateService.nodeMetaData(nodePath);
NodeMetaData.FORMAT.loadLatestState(logger, namedXContentRegistry, nodePath);
if (nodeMetaData == null) { if (nodeMetaData == null) {
throw new ElasticsearchException("No node meta data at " + nodePath); throw new ElasticsearchException("No node meta data at " + nodePath);
} }
final String nodeId = nodeMetaData.nodeId(); final String nodeId = nodeMetaData.nodeId();
final String index = indexMetaData.getIndex().getName(); final String index = shardPath.getShardId().getIndexName();
final int id = shardPath.getShardId().id(); final int id = shardPath.getShardId().id();
final AllocationCommands commands = new AllocationCommands( final AllocationCommands commands = new AllocationCommands(
allocateStale allocateStale
@ -506,7 +474,8 @@ public class RemoveCorruptedShardDataCommand extends EnvironmentAwareCommand {
private Path getNodePath(ShardPath shardPath) { private Path getNodePath(ShardPath shardPath) {
final Path nodePath = shardPath.getDataPath().getParent().getParent().getParent(); final Path nodePath = shardPath.getDataPath().getParent().getParent().getParent();
if (Files.exists(nodePath) == false || Files.exists(nodePath.resolve(MetaDataStateFormat.STATE_DIR_NAME)) == false) { if (Files.exists(nodePath) == false ||
Files.exists(nodePath.resolve(PersistedClusterStateService.METADATA_DIRECTORY_NAME)) == false) {
throw new ElasticsearchException("Unable to resolve node path for " + shardPath); throw new ElasticsearchException("Unable to resolve node path for " + shardPath);
} }
return nodePath; return nodePath;

View File

@ -26,6 +26,7 @@ import org.apache.lucene.index.IndexCommit;
import org.apache.lucene.store.Directory; import org.apache.lucene.store.Directory;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.cli.Terminal; import org.elasticsearch.cli.Terminal;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.UUIDs; import org.elasticsearch.common.UUIDs;
import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.collect.Tuple;
@ -63,6 +64,7 @@ public class TruncateTranslogAction {
} }
public Tuple<RemoveCorruptedShardDataCommand.CleanStatus, String> getCleanStatus(ShardPath shardPath, public Tuple<RemoveCorruptedShardDataCommand.CleanStatus, String> getCleanStatus(ShardPath shardPath,
ClusterState clusterState,
Directory indexDirectory) throws IOException { Directory indexDirectory) throws IOException {
final Path indexPath = shardPath.resolveIndex(); final Path indexPath = shardPath.resolveIndex();
final Path translogPath = shardPath.resolveTranslog(); final Path translogPath = shardPath.resolveTranslog();
@ -83,7 +85,7 @@ public class TruncateTranslogAction {
throw new ElasticsearchException("shard must have a valid translog UUID but got: [null]"); throw new ElasticsearchException("shard must have a valid translog UUID but got: [null]");
} }
final boolean clean = isTranslogClean(shardPath, translogUUID); final boolean clean = isTranslogClean(shardPath, clusterState, translogUUID);
if (clean) { if (clean) {
return Tuple.tuple(RemoveCorruptedShardDataCommand.CleanStatus.CLEAN, null); return Tuple.tuple(RemoveCorruptedShardDataCommand.CleanStatus.CLEAN, null);
@ -166,13 +168,12 @@ public class TruncateTranslogAction {
IOUtils.fsync(translogPath, true); IOUtils.fsync(translogPath, true);
} }
private boolean isTranslogClean(ShardPath shardPath, String translogUUID) throws IOException { private boolean isTranslogClean(ShardPath shardPath, ClusterState clusterState, String translogUUID) throws IOException {
// perform clean check of translog instead of corrupted marker file // perform clean check of translog instead of corrupted marker file
try { try {
final Path translogPath = shardPath.resolveTranslog(); final Path translogPath = shardPath.resolveTranslog();
final long translogGlobalCheckpoint = Translog.readGlobalCheckpoint(translogPath, translogUUID); final long translogGlobalCheckpoint = Translog.readGlobalCheckpoint(translogPath, translogUUID);
final IndexMetaData indexMetaData = final IndexMetaData indexMetaData = clusterState.metaData().getIndexSafe(shardPath.getShardId().getIndex());
IndexMetaData.FORMAT.loadLatestState(logger, namedXContentRegistry, shardPath.getDataPath().getParent());
final IndexSettings indexSettings = new IndexSettings(indexMetaData, Settings.EMPTY); final IndexSettings indexSettings = new IndexSettings(indexMetaData, Settings.EMPTY);
final TranslogConfig translogConfig = new TranslogConfig(shardPath.getShardId(), translogPath, final TranslogConfig translogConfig = new TranslogConfig(shardPath.getShardId(), translogPath,
indexSettings, BigArrays.NON_RECYCLING_INSTANCE); indexSettings, BigArrays.NON_RECYCLING_INSTANCE);

View File

@ -104,7 +104,7 @@ public class IndicesModule extends AbstractModule {
return namedWritables; return namedWritables;
} }
public List<NamedXContentRegistry.Entry> getNamedXContents() { public static List<NamedXContentRegistry.Entry> getNamedXContents() {
return Arrays.asList( return Arrays.asList(
new NamedXContentRegistry.Entry(Condition.class, new ParseField(MaxAgeCondition.NAME), (p, c) -> new NamedXContentRegistry.Entry(Condition.class, new ParseField(MaxAgeCondition.NAME), (p, c) ->
MaxAgeCondition.fromXContent(p)), MaxAgeCondition.fromXContent(p)),

View File

@ -41,6 +41,7 @@ import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.routing.RecoverySource; import org.elasticsearch.cluster.routing.RecoverySource;
import org.elasticsearch.cluster.routing.ShardRouting; import org.elasticsearch.cluster.routing.ShardRouting;
import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.cluster.service.ClusterService;
@ -64,8 +65,12 @@ import org.elasticsearch.common.unit.ByteSizeValue;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.common.util.concurrent.AbstractRefCounted; import org.elasticsearch.common.util.concurrent.AbstractRefCounted;
import org.elasticsearch.common.util.concurrent.AbstractRunnable;
import org.elasticsearch.common.util.concurrent.EsExecutors; import org.elasticsearch.common.util.concurrent.EsExecutors;
import org.elasticsearch.common.util.concurrent.EsRejectedExecutionException;
import org.elasticsearch.common.util.concurrent.EsThreadPoolExecutor;
import org.elasticsearch.common.util.iterable.Iterables; import org.elasticsearch.common.util.iterable.Iterables;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler; import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentFactory;
@ -116,6 +121,7 @@ import org.elasticsearch.indices.fielddata.cache.IndicesFieldDataCache;
import org.elasticsearch.indices.mapper.MapperRegistry; import org.elasticsearch.indices.mapper.MapperRegistry;
import org.elasticsearch.indices.recovery.PeerRecoveryTargetService; import org.elasticsearch.indices.recovery.PeerRecoveryTargetService;
import org.elasticsearch.indices.recovery.RecoveryState; import org.elasticsearch.indices.recovery.RecoveryState;
import org.elasticsearch.node.Node;
import org.elasticsearch.plugins.IndexStorePlugin; import org.elasticsearch.plugins.IndexStorePlugin;
import org.elasticsearch.plugins.PluginsService; import org.elasticsearch.plugins.PluginsService;
import org.elasticsearch.repositories.RepositoriesService; import org.elasticsearch.repositories.RepositoriesService;
@ -159,6 +165,7 @@ import static java.util.Collections.emptyMap;
import static java.util.Collections.unmodifiableMap; import static java.util.Collections.unmodifiableMap;
import static org.elasticsearch.common.collect.MapBuilder.newMapBuilder; import static org.elasticsearch.common.collect.MapBuilder.newMapBuilder;
import static org.elasticsearch.common.util.CollectionUtils.arrayAsArrayList; import static org.elasticsearch.common.util.CollectionUtils.arrayAsArrayList;
import static org.elasticsearch.common.util.concurrent.EsExecutors.daemonThreadFactory;
import static org.elasticsearch.index.IndexService.IndexCreationContext.CREATE_INDEX; import static org.elasticsearch.index.IndexService.IndexCreationContext.CREATE_INDEX;
import static org.elasticsearch.index.IndexService.IndexCreationContext.META_DATA_VERIFICATION; import static org.elasticsearch.index.IndexService.IndexCreationContext.META_DATA_VERIFICATION;
import static org.elasticsearch.index.query.AbstractQueryBuilder.parseInnerQueryBuilder; import static org.elasticsearch.index.query.AbstractQueryBuilder.parseInnerQueryBuilder;
@ -173,6 +180,11 @@ public class IndicesService extends AbstractLifecycleComponent
public static final Setting<Boolean> INDICES_ID_FIELD_DATA_ENABLED_SETTING = public static final Setting<Boolean> INDICES_ID_FIELD_DATA_ENABLED_SETTING =
Setting.boolSetting("indices.id_field_data.enabled", true, Property.Dynamic, Property.NodeScope); Setting.boolSetting("indices.id_field_data.enabled", true, Property.Dynamic, Property.NodeScope);
public static final Setting<Boolean> WRITE_DANGLING_INDICES_INFO_SETTING = Setting.boolSetting(
"gateway.write_dangling_indices_info",
true,
Setting.Property.NodeScope
);
/** /**
* The node's settings. * The node's settings.
@ -210,6 +222,12 @@ public class IndicesService extends AbstractLifecycleComponent
private final CountDownLatch closeLatch = new CountDownLatch(1); private final CountDownLatch closeLatch = new CountDownLatch(1);
private volatile boolean idFieldDataEnabled; private volatile boolean idFieldDataEnabled;
@Nullable
private final EsThreadPoolExecutor danglingIndicesThreadPoolExecutor;
private final Set<Index> danglingIndicesToWrite = Sets.newConcurrentHashSet();
private final boolean nodeWriteDanglingIndicesInfo;
@Override @Override
protected void doStart() { protected void doStart() {
// Start thread that will manage cleaning the field data cache periodically // Start thread that will manage cleaning the field data cache periodically
@ -290,12 +308,25 @@ public class IndicesService extends AbstractLifecycleComponent
} }
} }
}; };
final String nodeName = Objects.requireNonNull(Node.NODE_NAME_SETTING.get(settings));
nodeWriteDanglingIndicesInfo = WRITE_DANGLING_INDICES_INFO_SETTING.get(settings);
danglingIndicesThreadPoolExecutor = nodeWriteDanglingIndicesInfo ? EsExecutors.newScaling(
nodeName + "/" + DANGLING_INDICES_UPDATE_THREAD_NAME,
1, 1,
0, TimeUnit.MILLISECONDS,
daemonThreadFactory(nodeName, DANGLING_INDICES_UPDATE_THREAD_NAME),
threadPool.getThreadContext()) : null;
} }
private static final String DANGLING_INDICES_UPDATE_THREAD_NAME = "DanglingIndices#updateTask";
@Override @Override
protected void doStop() { protected void doStop() {
ThreadPool.terminate(danglingIndicesThreadPoolExecutor, 10, TimeUnit.SECONDS);
ExecutorService indicesStopExecutor = ExecutorService indicesStopExecutor =
Executors.newFixedThreadPool(5, EsExecutors.daemonThreadFactory(settings, "indices_shutdown")); Executors.newFixedThreadPool(5, daemonThreadFactory(settings, "indices_shutdown"));
// Copy indices because we modify it asynchronously in the body of the loop // Copy indices because we modify it asynchronously in the body of the loop
final Set<Index> indices = this.indices.values().stream().map(s -> s.index()).collect(Collectors.toSet()); final Set<Index> indices = this.indices.values().stream().map(s -> s.index()).collect(Collectors.toSet());
@ -456,6 +487,7 @@ public class IndicesService extends AbstractLifecycleComponent
public IndexService indexService(Index index) { public IndexService indexService(Index index) {
return indices.get(index.getUUID()); return indices.get(index.getUUID());
} }
/** /**
* Returns an IndexService for the specified index if exists otherwise a {@link IndexNotFoundException} is thrown. * Returns an IndexService for the specified index if exists otherwise a {@link IndexNotFoundException} is thrown.
*/ */
@ -479,7 +511,8 @@ public class IndicesService extends AbstractLifecycleComponent
*/ */
@Override @Override
public synchronized IndexService createIndex( public synchronized IndexService createIndex(
final IndexMetaData indexMetaData, final List<IndexEventListener> builtInListeners) throws IOException { final IndexMetaData indexMetaData, final List<IndexEventListener> builtInListeners,
final boolean writeDanglingIndices) throws IOException {
ensureChangesAllowed(); ensureChangesAllowed();
if (indexMetaData.getIndexUUID().equals(IndexMetaData.INDEX_UUID_NA_VALUE)) { if (indexMetaData.getIndexUUID().equals(IndexMetaData.INDEX_UUID_NA_VALUE)) {
throw new IllegalArgumentException("index must have a real UUID found value: [" + indexMetaData.getIndexUUID() + "]"); throw new IllegalArgumentException("index must have a real UUID found value: [" + indexMetaData.getIndexUUID() + "]");
@ -515,8 +548,18 @@ public class IndicesService extends AbstractLifecycleComponent
indexingMemoryController); indexingMemoryController);
boolean success = false; boolean success = false;
try { try {
if (writeDanglingIndices && nodeWriteDanglingIndicesInfo) {
indexService.addMetaDataListener(imd -> updateDanglingIndicesInfo(index));
}
indexService.getIndexEventListener().afterIndexCreated(indexService); indexService.getIndexEventListener().afterIndexCreated(indexService);
indices = newMapBuilder(indices).put(index.getUUID(), indexService).immutableMap(); indices = newMapBuilder(indices).put(index.getUUID(), indexService).immutableMap();
if (writeDanglingIndices) {
if (nodeWriteDanglingIndicesInfo) {
updateDanglingIndicesInfo(index);
} else {
indexService.deleteDanglingIndicesInfo();
}
}
success = true; success = true;
return indexService; return indexService;
} finally { } finally {
@ -763,7 +806,7 @@ public class IndicesService extends AbstractLifecycleComponent
throw new IllegalStateException("Can't delete unassigned index store for [" + indexName + "] - it's still part of " + throw new IllegalStateException("Can't delete unassigned index store for [" + indexName + "] - it's still part of " +
"the cluster state [" + index.getIndexUUID() + "] [" + metaData.getIndexUUID() + "]"); "the cluster state [" + index.getIndexUUID() + "] [" + metaData.getIndexUUID() + "]");
} }
deleteIndexStore(reason, metaData, clusterState); deleteIndexStore(reason, metaData);
} catch (Exception e) { } catch (Exception e) {
logger.warn(() -> new ParameterizedMessage("[{}] failed to delete unassigned index (reason [{}])", logger.warn(() -> new ParameterizedMessage("[{}] failed to delete unassigned index (reason [{}])",
metaData.getIndex(), reason), e); metaData.getIndex(), reason), e);
@ -777,7 +820,7 @@ public class IndicesService extends AbstractLifecycleComponent
* *
* Package private for testing * Package private for testing
*/ */
void deleteIndexStore(String reason, IndexMetaData metaData, ClusterState clusterState) throws IOException { void deleteIndexStore(String reason, IndexMetaData metaData) throws IOException {
if (nodeEnv.hasNodeFile()) { if (nodeEnv.hasNodeFile()) {
synchronized (this) { synchronized (this) {
Index index = metaData.getIndex(); Index index = metaData.getIndex();
@ -786,15 +829,6 @@ public class IndicesService extends AbstractLifecycleComponent
throw new IllegalStateException("Can't delete index store for [" + index.getName() + throw new IllegalStateException("Can't delete index store for [" + index.getName() +
"] - it's still part of the indices service [" + localUUid + "] [" + metaData.getIndexUUID() + "]"); "] - it's still part of the indices service [" + localUUid + "] [" + metaData.getIndexUUID() + "]");
} }
if (clusterState.metaData().hasIndex(index.getName()) && (clusterState.nodes().getLocalNode().isMasterNode() == true)) {
// we do not delete the store if it is a master eligible node and the index is still in the cluster state
// because we want to keep the meta data for indices around even if no shards are left here
final IndexMetaData idxMeta = clusterState.metaData().index(index.getName());
throw new IllegalStateException("Can't delete index store for [" + index.getName() + "] - it's still part of the " +
"cluster state [" + idxMeta.getIndexUUID() + "] [" + metaData.getIndexUUID() + "], " +
"we are master eligible, so will keep the index metadata even if no shards are left.");
}
} }
final IndexSettings indexSettings = buildIndexSettings(metaData); final IndexSettings indexSettings = buildIndexSettings(metaData);
deleteIndexStore(reason, indexSettings.getIndex(), indexSettings); deleteIndexStore(reason, indexSettings.getIndex(), indexSettings);
@ -872,13 +906,11 @@ public class IndicesService extends AbstractLifecycleComponent
nodeEnv.deleteShardDirectorySafe(shardId, indexSettings); nodeEnv.deleteShardDirectorySafe(shardId, indexSettings);
logger.debug("{} deleted shard reason [{}]", shardId, reason); logger.debug("{} deleted shard reason [{}]", shardId, reason);
// master nodes keep the index meta data, even if having no shards.. if (canDeleteIndexContents(shardId.getIndex(), indexSettings)) {
if (clusterState.nodes().getLocalNode().isMasterNode() == false &&
canDeleteIndexContents(shardId.getIndex(), indexSettings)) {
if (nodeEnv.findAllShardIds(shardId.getIndex()).isEmpty()) { if (nodeEnv.findAllShardIds(shardId.getIndex()).isEmpty()) {
try { try {
// note that deleteIndexStore have more safety checks and may throw an exception if index was concurrently created. // note that deleteIndexStore have more safety checks and may throw an exception if index was concurrently created.
deleteIndexStore("no longer used", metaData, clusterState); deleteIndexStore("no longer used", metaData);
} catch (Exception e) { } catch (Exception e) {
// wrap the exception to indicate we already deleted the shard // wrap the exception to indicate we already deleted the shard
throw new ElasticsearchException("failed to delete unused index after deleting its last shard (" + shardId + ")", e); throw new ElasticsearchException("failed to delete unused index after deleting its last shard (" + shardId + ")", e);
@ -1500,4 +1532,51 @@ public class IndicesService extends AbstractLifecycleComponent
} }
return Optional.empty(); return Optional.empty();
} }
private void updateDanglingIndicesInfo(Index index) {
assert DiscoveryNode.isDataNode(settings) : "dangling indices information should only be persisted on data nodes";
assert nodeWriteDanglingIndicesInfo : "writing dangling indices info is not enabled";
assert danglingIndicesThreadPoolExecutor != null : "executor for dangling indices info is not available";
if (danglingIndicesToWrite.add(index)) {
logger.trace("triggered dangling indices update for {}", index);
final long triggeredTimeMillis = threadPool.relativeTimeInMillis();
try {
danglingIndicesThreadPoolExecutor.execute(new AbstractRunnable() {
@Override
public void onFailure(Exception e) {
logger.warn(() -> new ParameterizedMessage("failed to write dangling indices state for index {}", index), e);
}
@Override
protected void doRun() {
final boolean exists = danglingIndicesToWrite.remove(index);
assert exists : "removed non-existing item for " + index;
final IndexService indexService = indices.get(index.getUUID());
if (indexService != null) {
final long executedTimeMillis = threadPool.relativeTimeInMillis();
logger.trace("writing out dangling indices state for index {}, triggered {} ago", index,
TimeValue.timeValueMillis(Math.min(0L, executedTimeMillis - triggeredTimeMillis)));
indexService.writeDanglingIndicesInfo();
final long completedTimeMillis = threadPool.relativeTimeInMillis();
logger.trace("writing out of dangling indices state for index {} completed after {}", index,
TimeValue.timeValueMillis(Math.min(0L, completedTimeMillis - executedTimeMillis)));
} else {
logger.trace("omit writing dangling indices state for index {} as index is deallocated on this node", index);
}
}
});
} catch (EsRejectedExecutionException e) {
// ignore cases where we are shutting down..., there is really nothing interesting to be done here...
assert danglingIndicesThreadPoolExecutor.isShutdown();
}
} else {
logger.trace("dangling indices update already pending for {}", index);
}
}
// visible for testing
public boolean allPendingDanglingIndicesWritten() {
return nodeWriteDanglingIndicesInfo == false ||
(danglingIndicesToWrite.isEmpty() && danglingIndicesThreadPoolExecutor.getActiveCount() == 0);
}
} }

View File

@ -481,7 +481,7 @@ public class IndicesClusterStateService extends AbstractLifecycleComponent imple
AllocatedIndex<? extends Shard> indexService = null; AllocatedIndex<? extends Shard> indexService = null;
try { try {
indexService = indicesService.createIndex(indexMetaData, buildInIndexListener); indexService = indicesService.createIndex(indexMetaData, buildInIndexListener, true);
if (indexService.updateMapping(null, indexMetaData) && sendRefreshMapping) { if (indexService.updateMapping(null, indexMetaData) && sendRefreshMapping) {
nodeMappingRefreshAction.nodeMappingRefresh(state.nodes().getMasterNode(), nodeMappingRefreshAction.nodeMappingRefresh(state.nodes().getMasterNode(),
new NodeMappingRefreshAction.NodeMappingRefreshRequest(indexMetaData.getIndex().getName(), new NodeMappingRefreshAction.NodeMappingRefreshRequest(indexMetaData.getIndex().getName(),
@ -845,10 +845,12 @@ public class IndicesClusterStateService extends AbstractLifecycleComponent imple
* @param indexMetaData the index metadata to create the index for * @param indexMetaData the index metadata to create the index for
* @param builtInIndexListener a list of built-in lifecycle {@link IndexEventListener} that should should be used along side with * @param builtInIndexListener a list of built-in lifecycle {@link IndexEventListener} that should should be used along side with
* the per-index listeners * the per-index listeners
* @param writeDanglingIndices whether dangling indices information should be written
* @throws ResourceAlreadyExistsException if the index already exists. * @throws ResourceAlreadyExistsException if the index already exists.
*/ */
U createIndex(IndexMetaData indexMetaData, U createIndex(IndexMetaData indexMetaData,
List<IndexEventListener> builtInIndexListener) throws IOException; List<IndexEventListener> builtInIndexListener,
boolean writeDanglingIndices) throws IOException;
/** /**
* Verify that the contents on disk for the given index is deleted; if not, delete the contents. * Verify that the contents on disk for the given index is deleted; if not, delete the contents.

View File

@ -23,9 +23,11 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.apache.lucene.util.Constants; import org.apache.lucene.util.Constants;
import org.apache.lucene.util.SetOnce; import org.apache.lucene.util.SetOnce;
import org.elasticsearch.Assertions;
import org.elasticsearch.Build; import org.elasticsearch.Build;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ElasticsearchTimeoutException; import org.elasticsearch.ElasticsearchTimeoutException;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionModule; import org.elasticsearch.action.ActionModule;
import org.elasticsearch.action.ActionType; import org.elasticsearch.action.ActionType;
import org.elasticsearch.action.admin.cluster.snapshots.status.TransportNodesSnapshotsStatus; import org.elasticsearch.action.admin.cluster.snapshots.status.TransportNodesSnapshotsStatus;
@ -92,10 +94,12 @@ import org.elasticsearch.discovery.DiscoveryModule;
import org.elasticsearch.discovery.DiscoverySettings; import org.elasticsearch.discovery.DiscoverySettings;
import org.elasticsearch.env.Environment; import org.elasticsearch.env.Environment;
import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.env.NodeEnvironment;
import org.elasticsearch.env.NodeMetaData;
import org.elasticsearch.gateway.GatewayAllocator; import org.elasticsearch.gateway.GatewayAllocator;
import org.elasticsearch.gateway.GatewayMetaState; import org.elasticsearch.gateway.GatewayMetaState;
import org.elasticsearch.gateway.GatewayModule; import org.elasticsearch.gateway.GatewayModule;
import org.elasticsearch.gateway.GatewayService; import org.elasticsearch.gateway.GatewayService;
import org.elasticsearch.gateway.PersistedClusterStateService;
import org.elasticsearch.gateway.MetaStateService; import org.elasticsearch.gateway.MetaStateService;
import org.elasticsearch.http.HttpServerTransport; import org.elasticsearch.http.HttpServerTransport;
import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.IndexSettings;
@ -401,13 +405,15 @@ public class Node implements Closeable {
final NamedWriteableRegistry namedWriteableRegistry = new NamedWriteableRegistry(namedWriteables); final NamedWriteableRegistry namedWriteableRegistry = new NamedWriteableRegistry(namedWriteables);
NamedXContentRegistry xContentRegistry = new NamedXContentRegistry(Stream.of( NamedXContentRegistry xContentRegistry = new NamedXContentRegistry(Stream.of(
NetworkModule.getNamedXContents().stream(), NetworkModule.getNamedXContents().stream(),
indicesModule.getNamedXContents().stream(), IndicesModule.getNamedXContents().stream(),
searchModule.getNamedXContents().stream(), searchModule.getNamedXContents().stream(),
pluginsService.filterPlugins(Plugin.class).stream() pluginsService.filterPlugins(Plugin.class).stream()
.flatMap(p -> p.getNamedXContent().stream()), .flatMap(p -> p.getNamedXContent().stream()),
ClusterModule.getNamedXWriteables().stream()) ClusterModule.getNamedXWriteables().stream())
.flatMap(Function.identity()).collect(toList())); .flatMap(Function.identity()).collect(toList()));
final MetaStateService metaStateService = new MetaStateService(nodeEnvironment, xContentRegistry); final MetaStateService metaStateService = new MetaStateService(nodeEnvironment, xContentRegistry);
final PersistedClusterStateService lucenePersistedStateFactory
= new PersistedClusterStateService(nodeEnvironment, xContentRegistry, bigArrays);
// collect engine factory providers from server and from plugins // collect engine factory providers from server and from plugins
final Collection<EnginePlugin> enginePlugins = pluginsService.filterPlugins(EnginePlugin.class); final Collection<EnginePlugin> enginePlugins = pluginsService.filterPlugins(EnginePlugin.class);
@ -547,6 +553,7 @@ public class Node implements Closeable {
b.bind(NamedWriteableRegistry.class).toInstance(namedWriteableRegistry); b.bind(NamedWriteableRegistry.class).toInstance(namedWriteableRegistry);
b.bind(MetaDataUpgrader.class).toInstance(metaDataUpgrader); b.bind(MetaDataUpgrader.class).toInstance(metaDataUpgrader);
b.bind(MetaStateService.class).toInstance(metaStateService); b.bind(MetaStateService.class).toInstance(metaStateService);
b.bind(PersistedClusterStateService.class).toInstance(lucenePersistedStateFactory);
b.bind(IndicesService.class).toInstance(indicesService); b.bind(IndicesService.class).toInstance(indicesService);
b.bind(AliasValidator.class).toInstance(aliasValidator); b.bind(AliasValidator.class).toInstance(aliasValidator);
b.bind(MetaDataCreateIndexService.class).toInstance(metaDataCreateIndexService); b.bind(MetaDataCreateIndexService.class).toInstance(metaDataCreateIndexService);
@ -695,7 +702,23 @@ public class Node implements Closeable {
// Load (and maybe upgrade) the metadata stored on disk // Load (and maybe upgrade) the metadata stored on disk
final GatewayMetaState gatewayMetaState = injector.getInstance(GatewayMetaState.class); final GatewayMetaState gatewayMetaState = injector.getInstance(GatewayMetaState.class);
gatewayMetaState.start(settings(), transportService, clusterService, injector.getInstance(MetaStateService.class), gatewayMetaState.start(settings(), transportService, clusterService, injector.getInstance(MetaStateService.class),
injector.getInstance(MetaDataIndexUpgradeService.class), injector.getInstance(MetaDataUpgrader.class)); injector.getInstance(MetaDataIndexUpgradeService.class), injector.getInstance(MetaDataUpgrader.class),
injector.getInstance(PersistedClusterStateService.class));
if (Assertions.ENABLED) {
try {
if (DiscoveryModule.DISCOVERY_TYPE_SETTING.get(environment.settings()).equals(
DiscoveryModule.ZEN_DISCOVERY_TYPE) == false) {
assert injector.getInstance(MetaStateService.class).loadFullState().v1().isEmpty();
}
final NodeMetaData nodeMetaData = NodeMetaData.FORMAT.loadLatestState(logger, NamedXContentRegistry.EMPTY,
nodeEnvironment.nodeDataPaths());
assert nodeMetaData != null;
assert nodeMetaData.nodeVersion().equals(Version.CURRENT);
assert nodeMetaData.nodeId().equals(localNodeFactory.getNode().getId());
} catch (IOException e) {
assert false : e;
}
}
// we load the global state here (the persistent part of the cluster state stored on disk) to // we load the global state here (the persistent part of the cluster state stored on disk) to
// pass it to the bootstrap checks to allow plugins to enforce certain preconditions based on the recovered state. // pass it to the bootstrap checks to allow plugins to enforce certain preconditions based on the recovered state.
final MetaData onDiskMetadata = gatewayMetaState.getPersistedState().getLastAcceptedState().metaData(); final MetaData onDiskMetadata = gatewayMetaState.getPersistedState().getLastAcceptedState().metaData();
@ -867,8 +890,11 @@ public class Node implements Closeable {
// Don't call shutdownNow here, it might break ongoing operations on Lucene indices. // Don't call shutdownNow here, it might break ongoing operations on Lucene indices.
// See https://issues.apache.org/jira/browse/LUCENE-7248. We call shutdownNow in // See https://issues.apache.org/jira/browse/LUCENE-7248. We call shutdownNow in
// awaitClose if the node doesn't finish closing within the specified time. // awaitClose if the node doesn't finish closing within the specified time.
toClose.add(() -> stopWatch.stop().start("node_environment"));
toClose.add(() -> stopWatch.stop().start("gateway_meta_state"));
toClose.add(injector.getInstance(GatewayMetaState.class));
toClose.add(() -> stopWatch.stop().start("node_environment"));
toClose.add(injector.getInstance(NodeEnvironment.class)); toClose.add(injector.getInstance(NodeEnvironment.class));
toClose.add(stopWatch::stop); toClose.add(stopWatch::stop);

View File

@ -992,16 +992,17 @@ public class CoordinatorTests extends AbstractCoordinatorTestCase {
cluster1.runRandomly(); cluster1.runRandomly();
cluster1.stabilise(); cluster1.stabilise();
final ClusterNode newNode; final ClusterNode nodeInOtherCluster;
try (Cluster cluster2 = new Cluster(3)) { try (Cluster cluster2 = new Cluster(3)) {
cluster2.runRandomly(); cluster2.runRandomly();
cluster2.stabilise(); cluster2.stabilise();
final ClusterNode nodeInOtherCluster = randomFrom(cluster2.clusterNodes); nodeInOtherCluster = randomFrom(cluster2.clusterNodes);
newNode = cluster1.new ClusterNode(nextNodeIndex.getAndIncrement(), }
final ClusterNode newNode = cluster1.new ClusterNode(nextNodeIndex.getAndIncrement(),
nodeInOtherCluster.getLocalNode(), n -> cluster1.new MockPersistedState(n, nodeInOtherCluster.persistedState, nodeInOtherCluster.getLocalNode(), n -> cluster1.new MockPersistedState(n, nodeInOtherCluster.persistedState,
Function.identity(), Function.identity()), nodeInOtherCluster.nodeSettings); Function.identity(), Function.identity()), nodeInOtherCluster.nodeSettings);
}
cluster1.clusterNodes.add(newNode); cluster1.clusterNodes.add(newNode);

View File

@ -0,0 +1,135 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.cluster.coordination;
import joptsimple.OptionSet;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.cli.MockTerminal;
import org.elasticsearch.cli.UserException;
import org.elasticsearch.cluster.routing.allocation.DiskThresholdSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.env.TestEnvironment;
import org.elasticsearch.test.ESIntegTestCase;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST, numDataNodes = 0, autoManageMasterNodes = false)
public class RemoveSettingsCommandIT extends ESIntegTestCase {
public void testRemoveSettingsAbortedByUser() throws Exception {
internalCluster().setBootstrapMasterNodeIndex(0);
String node = internalCluster().startNode();
client().admin().cluster().prepareUpdateSettings().setPersistentSettings(Settings.builder()
.put(DiskThresholdSettings.CLUSTER_ROUTING_ALLOCATION_DISK_THRESHOLD_ENABLED_SETTING.getKey(), false).build()).get();
Settings dataPathSettings = internalCluster().dataPathSettings(node);
ensureStableCluster(1);
internalCluster().stopRandomDataNode();
Environment environment = TestEnvironment.newEnvironment(
Settings.builder().put(internalCluster().getDefaultSettings()).put(dataPathSettings).build());
expectThrows(() -> removeSettings(environment, true,
new String[]{ DiskThresholdSettings.CLUSTER_ROUTING_ALLOCATION_DISK_THRESHOLD_ENABLED_SETTING.getKey() }),
ElasticsearchNodeCommand.ABORTED_BY_USER_MSG);
}
public void testRemoveSettingsSuccessful() throws Exception {
internalCluster().setBootstrapMasterNodeIndex(0);
String node = internalCluster().startNode();
client().admin().cluster().prepareUpdateSettings().setPersistentSettings(Settings.builder()
.put(DiskThresholdSettings.CLUSTER_ROUTING_ALLOCATION_DISK_THRESHOLD_ENABLED_SETTING.getKey(), false).build()).get();
assertThat(client().admin().cluster().prepareState().get().getState().metaData().persistentSettings().keySet(),
contains(DiskThresholdSettings.CLUSTER_ROUTING_ALLOCATION_DISK_THRESHOLD_ENABLED_SETTING.getKey()));
Settings dataPathSettings = internalCluster().dataPathSettings(node);
ensureStableCluster(1);
internalCluster().stopRandomDataNode();
Environment environment = TestEnvironment.newEnvironment(
Settings.builder().put(internalCluster().getDefaultSettings()).put(dataPathSettings).build());
MockTerminal terminal = removeSettings(environment, false,
randomBoolean() ?
new String[]{ DiskThresholdSettings.CLUSTER_ROUTING_ALLOCATION_DISK_THRESHOLD_ENABLED_SETTING.getKey() } :
new String[]{ "cluster.routing.allocation.disk.*" }
);
assertThat(terminal.getOutput(), containsString(RemoveSettingsCommand.SETTINGS_REMOVED_MSG));
assertThat(terminal.getOutput(), containsString("The following settings will be removed:"));
assertThat(terminal.getOutput(), containsString(
DiskThresholdSettings.CLUSTER_ROUTING_ALLOCATION_DISK_THRESHOLD_ENABLED_SETTING.getKey() + ": " + false));
internalCluster().startNode(dataPathSettings);
assertThat(client().admin().cluster().prepareState().get().getState().metaData().persistentSettings().keySet(),
not(contains(DiskThresholdSettings.CLUSTER_ROUTING_ALLOCATION_DISK_THRESHOLD_ENABLED_SETTING.getKey())));
}
public void testSettingDoesNotMatch() throws Exception {
internalCluster().setBootstrapMasterNodeIndex(0);
String node = internalCluster().startNode();
client().admin().cluster().prepareUpdateSettings().setPersistentSettings(Settings.builder()
.put(DiskThresholdSettings.CLUSTER_ROUTING_ALLOCATION_DISK_THRESHOLD_ENABLED_SETTING.getKey(), false).build()).get();
assertThat(client().admin().cluster().prepareState().get().getState().metaData().persistentSettings().keySet(),
contains(DiskThresholdSettings.CLUSTER_ROUTING_ALLOCATION_DISK_THRESHOLD_ENABLED_SETTING.getKey()));
Settings dataPathSettings = internalCluster().dataPathSettings(node);
ensureStableCluster(1);
internalCluster().stopRandomDataNode();
Environment environment = TestEnvironment.newEnvironment(
Settings.builder().put(internalCluster().getDefaultSettings()).put(dataPathSettings).build());
UserException ex = expectThrows(UserException.class, () -> removeSettings(environment, false,
new String[]{ "cluster.routing.allocation.disk.bla.*" }));
assertThat(ex.getMessage(), containsString("No persistent cluster settings matching [cluster.routing.allocation.disk.bla.*] were " +
"found on this node"));
}
private MockTerminal executeCommand(ElasticsearchNodeCommand command, Environment environment, boolean abort, String... args)
throws Exception {
final MockTerminal terminal = new MockTerminal();
final OptionSet options = command.getParser().parse(args);
final String input;
if (abort) {
input = randomValueOtherThanMany(c -> c.equalsIgnoreCase("y"), () -> randomAlphaOfLength(1));
} else {
input = randomBoolean() ? "y" : "Y";
}
terminal.addTextInput(input);
try {
command.execute(terminal, options, environment);
} finally {
assertThat(terminal.getOutput(), containsString(ElasticsearchNodeCommand.STOP_WARNING_MSG));
}
return terminal;
}
private MockTerminal removeSettings(Environment environment, boolean abort, String... args) throws Exception {
final MockTerminal terminal = executeCommand(new RemoveSettingsCommand(), environment, abort, args);
assertThat(terminal.getOutput(), containsString(RemoveSettingsCommand.CONFIRMATION_MSG));
assertThat(terminal.getOutput(), containsString(RemoveSettingsCommand.SETTINGS_REMOVED_MSG));
return terminal;
}
private void expectThrows(ThrowingRunnable runnable, String message) {
ElasticsearchException ex = expectThrows(ElasticsearchException.class, runnable);
assertThat(ex.getMessage(), containsString(message));
}
}

View File

@ -23,14 +23,15 @@ import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest; import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest;
import org.elasticsearch.cli.MockTerminal; import org.elasticsearch.cli.MockTerminal;
import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.Manifest;
import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.discovery.DiscoverySettings; import org.elasticsearch.discovery.DiscoverySettings;
import org.elasticsearch.env.Environment; import org.elasticsearch.env.Environment;
import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.env.NodeEnvironment;
import org.elasticsearch.env.NodeMetaData;
import org.elasticsearch.env.TestEnvironment; import org.elasticsearch.env.TestEnvironment;
import org.elasticsearch.gateway.GatewayMetaState;
import org.elasticsearch.gateway.PersistedClusterStateService;
import org.elasticsearch.indices.IndicesService;
import org.elasticsearch.node.Node; import org.elasticsearch.node.Node;
import org.elasticsearch.test.ESIntegTestCase; import org.elasticsearch.test.ESIntegTestCase;
import org.elasticsearch.test.InternalTestCluster; import org.elasticsearch.test.InternalTestCluster;
@ -134,14 +135,10 @@ public class UnsafeBootstrapAndDetachCommandIT extends ESIntegTestCase {
} }
} }
public void testBootstrapNoNodeMetaData() throws IOException { public void testBootstrapNoNodeMetaData() {
Settings envSettings = buildEnvSettings(Settings.EMPTY); Settings envSettings = buildEnvSettings(Settings.EMPTY);
Environment environment = TestEnvironment.newEnvironment(envSettings); Environment environment = TestEnvironment.newEnvironment(envSettings);
try (NodeEnvironment nodeEnvironment = new NodeEnvironment(envSettings, environment)) { expectThrows(() -> unsafeBootstrap(environment), ElasticsearchNodeCommand.NO_NODE_FOLDER_FOUND_MSG);
NodeMetaData.FORMAT.cleanupOldFiles(-1, nodeEnvironment.nodeDataPaths());
}
expectThrows(() -> unsafeBootstrap(environment), UnsafeBootstrapMasterCommand.NO_NODE_METADATA_FOUND_MSG);
} }
public void testBootstrapNotBootstrappedCluster() throws Exception { public void testBootstrapNotBootstrappedCluster() throws Exception {
@ -161,30 +158,10 @@ public class UnsafeBootstrapAndDetachCommandIT extends ESIntegTestCase {
Environment environment = TestEnvironment.newEnvironment( Environment environment = TestEnvironment.newEnvironment(
Settings.builder().put(internalCluster().getDefaultSettings()).put(dataPathSettings).build()); Settings.builder().put(internalCluster().getDefaultSettings()).put(dataPathSettings).build());
expectThrows(() -> unsafeBootstrap(environment), ElasticsearchNodeCommand.GLOBAL_GENERATION_MISSING_MSG); expectThrows(() -> unsafeBootstrap(environment), UnsafeBootstrapMasterCommand.EMPTY_LAST_COMMITTED_VOTING_CONFIG_MSG);
} }
public void testDetachNotBootstrappedCluster() throws Exception { public void testBootstrapNoClusterState() throws IOException {
String node = internalCluster().startNode(
Settings.builder()
.put(DiscoverySettings.INITIAL_STATE_TIMEOUT_SETTING.getKey(), "0s") // to ensure quick node startup
.build());
assertBusy(() -> {
ClusterState state = client().admin().cluster().prepareState().setLocal(true)
.execute().actionGet().getState();
assertTrue(state.blocks().hasGlobalBlockWithId(NoMasterBlockService.NO_MASTER_BLOCK_ID));
});
Settings dataPathSettings = internalCluster().dataPathSettings(node);
internalCluster().stopRandomDataNode();
Environment environment = TestEnvironment.newEnvironment(
Settings.builder().put(internalCluster().getDefaultSettings()).put(dataPathSettings).build());
expectThrows(() -> detachCluster(environment), ElasticsearchNodeCommand.GLOBAL_GENERATION_MISSING_MSG);
}
public void testBootstrapNoManifestFile() throws IOException {
internalCluster().setBootstrapMasterNodeIndex(0); internalCluster().setBootstrapMasterNodeIndex(0);
String node = internalCluster().startNode(); String node = internalCluster().startNode();
Settings dataPathSettings = internalCluster().dataPathSettings(node); Settings dataPathSettings = internalCluster().dataPathSettings(node);
@ -193,12 +170,12 @@ public class UnsafeBootstrapAndDetachCommandIT extends ESIntegTestCase {
internalCluster().stopRandomDataNode(); internalCluster().stopRandomDataNode();
Environment environment = TestEnvironment.newEnvironment( Environment environment = TestEnvironment.newEnvironment(
Settings.builder().put(internalCluster().getDefaultSettings()).put(dataPathSettings).build()); Settings.builder().put(internalCluster().getDefaultSettings()).put(dataPathSettings).build());
Manifest.FORMAT.cleanupOldFiles(-1, nodeEnvironment.nodeDataPaths()); PersistedClusterStateService.deleteAll(nodeEnvironment.nodeDataPaths());
expectThrows(() -> unsafeBootstrap(environment), ElasticsearchNodeCommand.NO_MANIFEST_FILE_FOUND_MSG); expectThrows(() -> unsafeBootstrap(environment), ElasticsearchNodeCommand.NO_NODE_METADATA_FOUND_MSG);
} }
public void testDetachNoManifestFile() throws IOException { public void testDetachNoClusterState() throws IOException {
internalCluster().setBootstrapMasterNodeIndex(0); internalCluster().setBootstrapMasterNodeIndex(0);
String node = internalCluster().startNode(); String node = internalCluster().startNode();
Settings dataPathSettings = internalCluster().dataPathSettings(node); Settings dataPathSettings = internalCluster().dataPathSettings(node);
@ -207,39 +184,9 @@ public class UnsafeBootstrapAndDetachCommandIT extends ESIntegTestCase {
internalCluster().stopRandomDataNode(); internalCluster().stopRandomDataNode();
Environment environment = TestEnvironment.newEnvironment( Environment environment = TestEnvironment.newEnvironment(
Settings.builder().put(internalCluster().getDefaultSettings()).put(dataPathSettings).build()); Settings.builder().put(internalCluster().getDefaultSettings()).put(dataPathSettings).build());
Manifest.FORMAT.cleanupOldFiles(-1, nodeEnvironment.nodeDataPaths()); PersistedClusterStateService.deleteAll(nodeEnvironment.nodeDataPaths());
expectThrows(() -> detachCluster(environment), ElasticsearchNodeCommand.NO_MANIFEST_FILE_FOUND_MSG); expectThrows(() -> detachCluster(environment), ElasticsearchNodeCommand.NO_NODE_METADATA_FOUND_MSG);
}
public void testBootstrapNoMetaData() throws IOException {
internalCluster().setBootstrapMasterNodeIndex(0);
String node = internalCluster().startNode();
Settings dataPathSettings = internalCluster().dataPathSettings(node);
ensureStableCluster(1);
NodeEnvironment nodeEnvironment = internalCluster().getMasterNodeInstance(NodeEnvironment.class);
internalCluster().stopRandomDataNode();
Environment environment = TestEnvironment.newEnvironment(
Settings.builder().put(internalCluster().getDefaultSettings()).put(dataPathSettings).build());
MetaData.FORMAT.cleanupOldFiles(-1, nodeEnvironment.nodeDataPaths());
expectThrows(() -> unsafeBootstrap(environment), ElasticsearchNodeCommand.NO_GLOBAL_METADATA_MSG);
}
public void testDetachNoMetaData() throws IOException {
internalCluster().setBootstrapMasterNodeIndex(0);
String node = internalCluster().startNode();
Settings dataPathSettings = internalCluster().dataPathSettings(node);
ensureStableCluster(1);
NodeEnvironment nodeEnvironment = internalCluster().getMasterNodeInstance(NodeEnvironment.class);
internalCluster().stopRandomDataNode();
Environment environment = TestEnvironment.newEnvironment(
Settings.builder().put(internalCluster().getDefaultSettings()).put(dataPathSettings).build());
MetaData.FORMAT.cleanupOldFiles(-1, nodeEnvironment.nodeDataPaths());
expectThrows(() -> detachCluster(environment), ElasticsearchNodeCommand.NO_GLOBAL_METADATA_MSG);
} }
public void testBootstrapAbortedByUser() throws IOException { public void testBootstrapAbortedByUser() throws IOException {
@ -314,11 +261,13 @@ public class UnsafeBootstrapAndDetachCommandIT extends ESIntegTestCase {
logger.info("--> stop 1st master-eligible node and data-only node"); logger.info("--> stop 1st master-eligible node and data-only node");
NodeEnvironment nodeEnvironment = internalCluster().getMasterNodeInstance(NodeEnvironment.class); NodeEnvironment nodeEnvironment = internalCluster().getMasterNodeInstance(NodeEnvironment.class);
internalCluster().stopRandomNode(InternalTestCluster.nameFilter(masterNodes.get(0))); internalCluster().stopRandomNode(InternalTestCluster.nameFilter(masterNodes.get(0)));
assertBusy(() -> internalCluster().getInstance(GatewayMetaState.class, dataNode).allPendingAsyncStatesWritten());
internalCluster().stopRandomDataNode(); internalCluster().stopRandomDataNode();
logger.info("--> unsafely-bootstrap 1st master-eligible node"); logger.info("--> unsafely-bootstrap 1st master-eligible node");
MockTerminal terminal = unsafeBootstrap(environmentMaster1); MockTerminal terminal = unsafeBootstrap(environmentMaster1);
MetaData metaData = MetaData.FORMAT.loadLatestState(logger, xContentRegistry(), nodeEnvironment.nodeDataPaths()); MetaData metaData = ElasticsearchNodeCommand.createPersistedClusterStateService(nodeEnvironment.nodeDataPaths())
.loadBestOnDiskState().metaData;
assertThat(terminal.getOutput(), containsString( assertThat(terminal.getOutput(), containsString(
String.format(Locale.ROOT, UnsafeBootstrapMasterCommand.CLUSTER_STATE_TERM_VERSION_MSG_FORMAT, String.format(Locale.ROOT, UnsafeBootstrapMasterCommand.CLUSTER_STATE_TERM_VERSION_MSG_FORMAT,
metaData.coordinationMetaData().term(), metaData.version()))); metaData.coordinationMetaData().term(), metaData.version())));
@ -372,6 +321,8 @@ public class UnsafeBootstrapAndDetachCommandIT extends ESIntegTestCase {
logger.info("--> index 1 doc and ensure index is green"); logger.info("--> index 1 doc and ensure index is green");
client().prepareIndex("test", "type1", "1").setSource("field1", "value1").setRefreshPolicy(IMMEDIATE).get(); client().prepareIndex("test", "type1", "1").setSource("field1", "value1").setRefreshPolicy(IMMEDIATE).get();
ensureGreen("test"); ensureGreen("test");
assertBusy(() -> internalCluster().getInstances(IndicesService.class).forEach(
indicesService -> assertTrue(indicesService.allPendingDanglingIndicesWritten())));
logger.info("--> verify 1 doc in the index"); logger.info("--> verify 1 doc in the index");
assertHitCount(client().prepareSearch().setQuery(matchAllQuery()).get(), 1L); assertHitCount(client().prepareSearch().setQuery(matchAllQuery()).get(), 1L);
@ -379,6 +330,7 @@ public class UnsafeBootstrapAndDetachCommandIT extends ESIntegTestCase {
logger.info("--> stop data-only node and detach it from the old cluster"); logger.info("--> stop data-only node and detach it from the old cluster");
Settings dataNodeDataPathSettings = internalCluster().dataPathSettings(dataNode); Settings dataNodeDataPathSettings = internalCluster().dataPathSettings(dataNode);
assertBusy(() -> internalCluster().getInstance(GatewayMetaState.class, dataNode).allPendingAsyncStatesWritten());
internalCluster().stopRandomNode(InternalTestCluster.nameFilter(dataNode)); internalCluster().stopRandomNode(InternalTestCluster.nameFilter(dataNode));
final Environment environment = TestEnvironment.newEnvironment( final Environment environment = TestEnvironment.newEnvironment(
Settings.builder().put(internalCluster().getDefaultSettings()).put(dataNodeDataPathSettings).build()); Settings.builder().put(internalCluster().getDefaultSettings()).put(dataNodeDataPathSettings).build());

View File

@ -41,6 +41,7 @@ import org.elasticsearch.discovery.DiscoveryModule;
import org.elasticsearch.discovery.zen.ElectMasterService; import org.elasticsearch.discovery.zen.ElectMasterService;
import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.env.NodeEnvironment;
import org.elasticsearch.gateway.MetaStateService; import org.elasticsearch.gateway.MetaStateService;
import org.elasticsearch.indices.IndicesService;
import org.elasticsearch.plugins.Plugin; import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.test.ESIntegTestCase; import org.elasticsearch.test.ESIntegTestCase;
import org.elasticsearch.test.InternalTestCluster.RestartCallback; import org.elasticsearch.test.InternalTestCluster.RestartCallback;
@ -73,7 +74,9 @@ import static org.hamcrest.Matchers.is;
public class Zen1IT extends ESIntegTestCase { public class Zen1IT extends ESIntegTestCase {
private static Settings ZEN1_SETTINGS = Coordinator.addZen1Attribute(true, Settings.builder() private static Settings ZEN1_SETTINGS = Coordinator.addZen1Attribute(true, Settings.builder()
.put(DiscoveryModule.DISCOVERY_TYPE_SETTING.getKey(), DiscoveryModule.ZEN_DISCOVERY_TYPE)).build(); .put(DiscoveryModule.DISCOVERY_TYPE_SETTING.getKey(), DiscoveryModule.ZEN_DISCOVERY_TYPE)
.put(IndicesService.WRITE_DANGLING_INDICES_INFO_SETTING.getKey(), false)
).build();
private static Settings ZEN2_SETTINGS = Settings.builder() private static Settings ZEN2_SETTINGS = Settings.builder()
.put(DiscoveryModule.DISCOVERY_TYPE_SETTING.getKey(), DiscoveryModule.ZEN2_DISCOVERY_TYPE) .put(DiscoveryModule.DISCOVERY_TYPE_SETTING.getKey(), DiscoveryModule.ZEN2_DISCOVERY_TYPE)

View File

@ -71,7 +71,7 @@ public class IndexMetaDataTests extends ESTestCase {
@Override @Override
protected NamedXContentRegistry xContentRegistry() { protected NamedXContentRegistry xContentRegistry() {
return new NamedXContentRegistry(INDICES_MODULE.getNamedXContents()); return new NamedXContentRegistry(IndicesModule.getNamedXContents());
} }
public void testIndexMetaDataSerialization() throws IOException { public void testIndexMetaDataSerialization() throws IOException {

View File

@ -21,7 +21,10 @@ package org.elasticsearch.env;
import org.elasticsearch.Version; import org.elasticsearch.Version;
import org.elasticsearch.common.CheckedConsumer; import org.elasticsearch.common.CheckedConsumer;
import org.elasticsearch.common.io.PathUtils;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.gateway.PersistedClusterStateService;
import org.elasticsearch.indices.IndicesService;
import org.elasticsearch.node.Node; import org.elasticsearch.node.Node;
import org.elasticsearch.test.ESIntegTestCase; import org.elasticsearch.test.ESIntegTestCase;
import org.elasticsearch.test.InternalTestCluster; import org.elasticsearch.test.InternalTestCluster;
@ -38,11 +41,13 @@ import static org.hamcrest.Matchers.startsWith;
@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST, numDataNodes = 0) @ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST, numDataNodes = 0)
public class NodeEnvironmentIT extends ESIntegTestCase { public class NodeEnvironmentIT extends ESIntegTestCase {
public void testStartFailureOnDataForNonDataNode() { public void testStartFailureOnDataForNonDataNode() throws Exception {
final String indexName = "test-fail-on-data"; final String indexName = "test-fail-on-data";
logger.info("--> starting one node"); logger.info("--> starting one node");
String node = internalCluster().startNode(); final boolean writeDanglingIndices = randomBoolean();
String node = internalCluster().startNode(Settings.builder()
.put(IndicesService.WRITE_DANGLING_INDICES_INFO_SETTING.getKey(), writeDanglingIndices).build());
Settings dataPathSettings = internalCluster().dataPathSettings(node); Settings dataPathSettings = internalCluster().dataPathSettings(node);
logger.info("--> creating index"); logger.info("--> creating index");
@ -51,6 +56,10 @@ public class NodeEnvironmentIT extends ESIntegTestCase {
.put("index.number_of_replicas", 0) .put("index.number_of_replicas", 0)
).get(); ).get();
final String indexUUID = resolveIndex(indexName).getUUID(); final String indexUUID = resolveIndex(indexName).getUUID();
if (writeDanglingIndices) {
assertBusy(() -> internalCluster().getInstances(IndicesService.class).forEach(
indicesService -> assertTrue(indicesService.allPendingDanglingIndicesWritten())));
}
logger.info("--> restarting the node with node.data=false and node.master=false"); logger.info("--> restarting the node with node.data=false and node.master=false");
IllegalStateException ex = expectThrows(IllegalStateException.class, IllegalStateException ex = expectThrows(IllegalStateException.class,
@ -65,13 +74,19 @@ public class NodeEnvironmentIT extends ESIntegTestCase {
.build(); .build();
} }
})); }));
assertThat(ex.getMessage(), containsString(indexUUID)); if (writeDanglingIndices) {
assertThat(ex.getMessage(), assertThat(ex.getMessage(),
startsWith("Node is started with " startsWith("Node is started with "
+ Node.NODE_DATA_SETTING.getKey() + Node.NODE_DATA_SETTING.getKey()
+ "=false and " + "=false and "
+ Node.NODE_MASTER_SETTING.getKey() + Node.NODE_MASTER_SETTING.getKey()
+ "=false, but has index metadata")); + "=false, but has index metadata"));
} else {
assertThat(ex.getMessage(),
startsWith("Node is started with "
+ Node.NODE_DATA_SETTING.getKey()
+ "=false, but has shard data"));
}
logger.info("--> start the node again with node.data=true and node.master=true"); logger.info("--> start the node again with node.data=true and node.master=true");
internalCluster().startNode(dataPathSettings); internalCluster().startNode(dataPathSettings);
@ -115,14 +130,14 @@ public class NodeEnvironmentIT extends ESIntegTestCase {
public void testFailsToStartIfDowngraded() { public void testFailsToStartIfDowngraded() {
final IllegalStateException illegalStateException = expectThrowsOnRestart(dataPaths -> final IllegalStateException illegalStateException = expectThrowsOnRestart(dataPaths ->
NodeMetaData.FORMAT.writeAndCleanup(new NodeMetaData(randomAlphaOfLength(10), NodeMetaDataTests.tooNewVersion()), dataPaths)); PersistedClusterStateService.overrideVersion(NodeMetaDataTests.tooNewVersion(), dataPaths));
assertThat(illegalStateException.getMessage(), assertThat(illegalStateException.getMessage(),
allOf(startsWith("cannot downgrade a node from version ["), endsWith("] to version [" + Version.CURRENT + "]"))); allOf(startsWith("cannot downgrade a node from version ["), endsWith("] to version [" + Version.CURRENT + "]")));
} }
public void testFailsToStartIfUpgradedTooFar() { public void testFailsToStartIfUpgradedTooFar() {
final IllegalStateException illegalStateException = expectThrowsOnRestart(dataPaths -> final IllegalStateException illegalStateException = expectThrowsOnRestart(dataPaths ->
NodeMetaData.FORMAT.writeAndCleanup(new NodeMetaData(randomAlphaOfLength(10), NodeMetaDataTests.tooOldVersion()), dataPaths)); PersistedClusterStateService.overrideVersion(NodeMetaDataTests.tooOldVersion(), dataPaths));
assertThat(illegalStateException.getMessage(), assertThat(illegalStateException.getMessage(),
allOf(startsWith("cannot upgrade a node from version ["), endsWith("] directly to version [" + Version.CURRENT + "]"))); allOf(startsWith("cannot upgrade a node from version ["), endsWith("] directly to version [" + Version.CURRENT + "]")));
} }
@ -140,10 +155,16 @@ public class NodeEnvironmentIT extends ESIntegTestCase {
internalCluster().stopRandomNode(InternalTestCluster.nameFilter(nodes.get(1))); internalCluster().stopRandomNode(InternalTestCluster.nameFilter(nodes.get(1)));
internalCluster().stopRandomNode(InternalTestCluster.nameFilter(nodes.get(0))); internalCluster().stopRandomNode(InternalTestCluster.nameFilter(nodes.get(0)));
final IllegalStateException illegalStateException = expectThrows(IllegalStateException.class, IllegalStateException illegalStateException = expectThrows(IllegalStateException.class,
() -> PersistedClusterStateService.nodeMetaData(allDataPaths.stream().map(PathUtils::get)
.map(path -> NodeEnvironment.resolveNodePath(path, 0)).toArray(Path[]::new)));
assertThat(illegalStateException.getMessage(), containsString("unexpected node ID in metadata"));
illegalStateException = expectThrows(IllegalStateException.class,
() -> internalCluster().startNode(Settings.builder().putList(Environment.PATH_DATA_SETTING.getKey(), allDataPaths))); () -> internalCluster().startNode(Settings.builder().putList(Environment.PATH_DATA_SETTING.getKey(), allDataPaths)));
assertThat(illegalStateException.getMessage(), containsString("belong to multiple nodes with IDs")); assertThat(illegalStateException.getMessage(), containsString("unexpected node ID in metadata"));
final List<String> node0DataPathsPlusOne = new ArrayList<>(node0DataPaths); final List<String> node0DataPathsPlusOne = new ArrayList<>(node0DataPaths);
node0DataPathsPlusOne.add(createTempDir().toString()); node0DataPathsPlusOne.add(createTempDir().toString());

View File

@ -419,7 +419,7 @@ public class NodeEnvironmentTests extends ESTestCase {
env.close(); env.close();
} }
public void testPersistentNodeId() throws IOException { public void testNodeIdNotPersistedAtInitialization() throws IOException {
NodeEnvironment env = newNodeEnvironment(new String[0], Settings.builder() NodeEnvironment env = newNodeEnvironment(new String[0], Settings.builder()
.put("node.local_storage", false) .put("node.local_storage", false)
.put("node.master", false) .put("node.master", false)
@ -433,7 +433,7 @@ public class NodeEnvironmentTests extends ESTestCase {
nodeID = env.nodeId(); nodeID = env.nodeId();
env.close(); env.close();
env = newNodeEnvironment(paths, Settings.EMPTY); env = newNodeEnvironment(paths, Settings.EMPTY);
assertThat(env.nodeId(), equalTo(nodeID)); assertThat(env.nodeId(), not(equalTo(nodeID)));
env.close(); env.close();
env = newNodeEnvironment(Settings.EMPTY); env = newNodeEnvironment(Settings.EMPTY);
assertThat(env.nodeId(), not(equalTo(nodeID))); assertThat(env.nodeId(), not(equalTo(nodeID)));

View File

@ -21,14 +21,13 @@ package org.elasticsearch.env;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.NoShardAvailableActionException; import org.elasticsearch.action.NoShardAvailableActionException;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.indices.IndicesService;
import org.elasticsearch.node.Node; import org.elasticsearch.node.Node;
import org.elasticsearch.test.ESIntegTestCase; import org.elasticsearch.test.ESIntegTestCase;
import org.hamcrest.Matcher; import org.hamcrest.Matcher;
import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.mockito.Matchers.contains;
@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST, numDataNodes = 0) @ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST, numDataNodes = 0)
public class NodeRepurposeCommandIT extends ESIntegTestCase { public class NodeRepurposeCommandIT extends ESIntegTestCase {
@ -38,14 +37,14 @@ public class NodeRepurposeCommandIT extends ESIntegTestCase {
logger.info("--> starting two nodes"); logger.info("--> starting two nodes");
final String masterNode = internalCluster().startMasterOnlyNode(); final String masterNode = internalCluster().startMasterOnlyNode();
final String dataNode = internalCluster().startDataOnlyNode(); final String dataNode = internalCluster().startDataOnlyNode(
Settings.builder().put(IndicesService.WRITE_DANGLING_INDICES_INFO_SETTING.getKey(), false).build());
logger.info("--> creating index"); logger.info("--> creating index");
prepareCreate(indexName, Settings.builder() prepareCreate(indexName, Settings.builder()
.put("index.number_of_shards", 1) .put("index.number_of_shards", 1)
.put("index.number_of_replicas", 0) .put("index.number_of_replicas", 0)
).get(); ).get();
final String indexUUID = resolveIndex(indexName).getUUID();
logger.info("--> indexing a simple document"); logger.info("--> indexing a simple document");
client().prepareIndex(indexName, "type1", "1").setSource("field1", "value1").get(); client().prepareIndex(indexName, "type1", "1").setSource("field1", "value1").get();
@ -82,10 +81,10 @@ public class NodeRepurposeCommandIT extends ESIntegTestCase {
); );
logger.info("--> Repurposing node 1"); logger.info("--> Repurposing node 1");
executeRepurposeCommand(noMasterNoDataSettingsForDataNode, indexUUID, 1); executeRepurposeCommand(noMasterNoDataSettingsForDataNode, 1, 1);
ElasticsearchException lockedException = expectThrows(ElasticsearchException.class, ElasticsearchException lockedException = expectThrows(ElasticsearchException.class,
() -> executeRepurposeCommand(noMasterNoDataSettingsForMasterNode, indexUUID, 1) () -> executeRepurposeCommand(noMasterNoDataSettingsForMasterNode, 1, 1)
); );
assertThat(lockedException.getMessage(), containsString(NodeRepurposeCommand.FAILED_TO_OBTAIN_NODE_LOCK_MSG)); assertThat(lockedException.getMessage(), containsString(NodeRepurposeCommand.FAILED_TO_OBTAIN_NODE_LOCK_MSG));
@ -101,7 +100,7 @@ public class NodeRepurposeCommandIT extends ESIntegTestCase {
internalCluster().stopRandomNode(s -> true); internalCluster().stopRandomNode(s -> true);
internalCluster().stopRandomNode(s -> true); internalCluster().stopRandomNode(s -> true);
executeRepurposeCommand(noMasterNoDataSettingsForMasterNode, indexUUID, 0); executeRepurposeCommand(noMasterNoDataSettingsForMasterNode, 1, 0);
// by restarting as master and data node, we can check that the index definition was really deleted and also that the tool // by restarting as master and data node, we can check that the index definition was really deleted and also that the tool
// does not mess things up so much that the nodes cannot boot as master or data node any longer. // does not mess things up so much that the nodes cannot boot as master or data node any longer.
@ -114,14 +113,13 @@ public class NodeRepurposeCommandIT extends ESIntegTestCase {
assertFalse(indexExists(indexName)); assertFalse(indexExists(indexName));
} }
private void executeRepurposeCommand(Settings settings, String indexUUID, int expectedShardCount) throws Exception { private void executeRepurposeCommand(Settings settings, int expectedIndexCount,
int expectedShardCount) throws Exception {
boolean verbose = randomBoolean(); boolean verbose = randomBoolean();
Settings settingsWithPath = Settings.builder().put(internalCluster().getDefaultSettings()).put(settings).build(); Settings settingsWithPath = Settings.builder().put(internalCluster().getDefaultSettings()).put(settings).build();
int expectedIndexCount = TestEnvironment.newEnvironment(settingsWithPath).dataFiles().length;
Matcher<String> matcher = allOf( Matcher<String> matcher = allOf(
containsString(NodeRepurposeCommand.noMasterMessage(1, expectedShardCount, expectedIndexCount)), containsString(NodeRepurposeCommand.noMasterMessage(expectedIndexCount, expectedShardCount, 0)),
not(contains(NodeRepurposeCommand.PRE_V7_MESSAGE)), NodeRepurposeCommandTests.conditionalNot(containsString("test-repurpose"), verbose == false));
NodeRepurposeCommandTests.conditionalNot(containsString(indexUUID), verbose == false));
NodeRepurposeCommandTests.verifySuccess(settingsWithPath, matcher, NodeRepurposeCommandTests.verifySuccess(settingsWithPath, matcher,
verbose); verbose);
} }

View File

@ -23,13 +23,16 @@ import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.Version; import org.elasticsearch.Version;
import org.elasticsearch.cli.MockTerminal; import org.elasticsearch.cli.MockTerminal;
import org.elasticsearch.cli.Terminal; import org.elasticsearch.cli.Terminal;
import org.elasticsearch.cluster.ClusterModule; import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.coordination.ElasticsearchNodeCommand;
import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.metadata.Manifest; import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.common.CheckedConsumer; import org.elasticsearch.common.CheckedConsumer;
import org.elasticsearch.common.CheckedRunnable; import org.elasticsearch.common.CheckedRunnable;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.gateway.PersistedClusterStateService;
import org.elasticsearch.index.Index; import org.elasticsearch.index.Index;
import org.elasticsearch.node.Node; import org.elasticsearch.node.Node;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
@ -40,16 +43,13 @@ import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.stream.Stream; import java.util.stream.Stream;
import static org.elasticsearch.env.NodeRepurposeCommand.NO_CLEANUP; import static org.elasticsearch.env.NodeRepurposeCommand.NO_CLEANUP;
import static org.elasticsearch.env.NodeRepurposeCommand.NO_DATA_TO_CLEAN_UP_FOUND; import static org.elasticsearch.env.NodeRepurposeCommand.NO_DATA_TO_CLEAN_UP_FOUND;
import static org.elasticsearch.env.NodeRepurposeCommand.NO_SHARD_DATA_TO_CLEAN_UP_FOUND; import static org.elasticsearch.env.NodeRepurposeCommand.NO_SHARD_DATA_TO_CLEAN_UP_FOUND;
import static org.elasticsearch.env.NodeRepurposeCommand.PRE_V7_MESSAGE;
import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
public class NodeRepurposeCommandTests extends ESTestCase { public class NodeRepurposeCommandTests extends ESTestCase {
@ -68,6 +68,11 @@ public class NodeRepurposeCommandTests extends ESTestCase {
environment = TestEnvironment.newEnvironment(dataMasterSettings); environment = TestEnvironment.newEnvironment(dataMasterSettings);
try (NodeEnvironment nodeEnvironment = new NodeEnvironment(dataMasterSettings, environment)) { try (NodeEnvironment nodeEnvironment = new NodeEnvironment(dataMasterSettings, environment)) {
nodePaths = nodeEnvironment.nodeDataPaths(); nodePaths = nodeEnvironment.nodeDataPaths();
final String nodeId = randomAlphaOfLength(10);
try (PersistedClusterStateService.Writer writer = new PersistedClusterStateService(nodePaths, nodeId,
xContentRegistry(), BigArrays.NON_RECYCLING_INSTANCE, true).createWriter()) {
writer.writeFullStateAndCommit(1L, ClusterState.EMPTY_STATE);
}
} }
dataNoMasterSettings = Settings.builder() dataNoMasterSettings = Settings.builder()
.put(dataMasterSettings) .put(dataMasterSettings)
@ -86,27 +91,32 @@ public class NodeRepurposeCommandTests extends ESTestCase {
} }
public void testEarlyExitNoCleanup() throws Exception { public void testEarlyExitNoCleanup() throws Exception {
createIndexDataFiles(dataMasterSettings, randomInt(10)); createIndexDataFiles(dataMasterSettings, randomInt(10), randomBoolean());
verifyNoQuestions(dataMasterSettings, containsString(NO_CLEANUP)); verifyNoQuestions(dataMasterSettings, containsString(NO_CLEANUP));
verifyNoQuestions(dataNoMasterSettings, containsString(NO_CLEANUP)); verifyNoQuestions(dataNoMasterSettings, containsString(NO_CLEANUP));
} }
public void testNothingToCleanup() throws Exception { public void testNothingToCleanup() throws Exception {
verifyNoQuestions(noDataNoMasterSettings, allOf(containsString(NO_DATA_TO_CLEAN_UP_FOUND), not(containsString(PRE_V7_MESSAGE)))); verifyNoQuestions(noDataNoMasterSettings, containsString(NO_DATA_TO_CLEAN_UP_FOUND));
verifyNoQuestions(noDataMasterSettings, verifyNoQuestions(noDataMasterSettings, containsString(NO_SHARD_DATA_TO_CLEAN_UP_FOUND));
allOf(containsString(NO_SHARD_DATA_TO_CLEAN_UP_FOUND), not(containsString(PRE_V7_MESSAGE))));
createManifest(null); Environment environment = TestEnvironment.newEnvironment(noDataMasterSettings);
if (randomBoolean()) {
try (NodeEnvironment env = new NodeEnvironment(noDataMasterSettings, environment)) {
try (PersistedClusterStateService.Writer writer =
ElasticsearchNodeCommand.createPersistedClusterStateService(env.nodeDataPaths()).createWriter()) {
writer.writeFullStateAndCommit(1L, ClusterState.EMPTY_STATE);
}
}
}
verifyNoQuestions(noDataNoMasterSettings, allOf(containsString(NO_DATA_TO_CLEAN_UP_FOUND), not(containsString(PRE_V7_MESSAGE)))); verifyNoQuestions(noDataNoMasterSettings, containsString(NO_DATA_TO_CLEAN_UP_FOUND));
verifyNoQuestions(noDataMasterSettings, verifyNoQuestions(noDataMasterSettings, containsString(NO_SHARD_DATA_TO_CLEAN_UP_FOUND));
allOf(containsString(NO_SHARD_DATA_TO_CLEAN_UP_FOUND), not(containsString(PRE_V7_MESSAGE))));
createIndexDataFiles(dataMasterSettings, 0); createIndexDataFiles(dataMasterSettings, 0, randomBoolean());
verifyNoQuestions(noDataMasterSettings, verifyNoQuestions(noDataMasterSettings, containsString(NO_SHARD_DATA_TO_CLEAN_UP_FOUND));
allOf(containsString(NO_SHARD_DATA_TO_CLEAN_UP_FOUND), not(containsString(PRE_V7_MESSAGE))));
} }
@ -119,33 +129,20 @@ public class NodeRepurposeCommandTests extends ESTestCase {
} }
public void testCleanupAll() throws Exception { public void testCleanupAll() throws Exception {
Manifest oldManifest = createManifest(INDEX); int shardCount = randomIntBetween(1, 10);
checkCleanupAll(not(containsString(PRE_V7_MESSAGE)));
Manifest newManifest = loadManifest();
assertThat(newManifest.getIndexGenerations().entrySet(), hasSize(0));
assertManifestIdenticalExceptIndices(oldManifest, newManifest);
}
public void testCleanupAllPreV7() throws Exception {
checkCleanupAll(containsString(PRE_V7_MESSAGE));
}
private void checkCleanupAll(Matcher<String> additionalOutputMatcher) throws Exception {
int shardCount = randomInt(10);
boolean verbose = randomBoolean(); boolean verbose = randomBoolean();
createIndexDataFiles(dataMasterSettings, shardCount); boolean hasClusterState = randomBoolean();
createIndexDataFiles(dataMasterSettings, shardCount, hasClusterState);
String messageText = NodeRepurposeCommand.noMasterMessage( String messageText = NodeRepurposeCommand.noMasterMessage(
1, 1,
environment.dataFiles().length*shardCount, environment.dataFiles().length*shardCount,
environment.dataFiles().length); 0);
Matcher<String> outputMatcher = allOf( Matcher<String> outputMatcher = allOf(
containsString(messageText), containsString(messageText),
additionalOutputMatcher, conditionalNot(containsString("testIndex"), verbose == false || hasClusterState == false),
conditionalNot(containsString("testUUID"), verbose == false), conditionalNot(containsString("no name for uuid: testUUID"), verbose == false || hasClusterState)
conditionalNot(containsString("testIndex"), verbose == false)
); );
verifyUnchangedOnAbort(noDataNoMasterSettings, outputMatcher, verbose); verifyUnchangedOnAbort(noDataNoMasterSettings, outputMatcher, verbose);
@ -162,18 +159,17 @@ public class NodeRepurposeCommandTests extends ESTestCase {
public void testCleanupShardData() throws Exception { public void testCleanupShardData() throws Exception {
int shardCount = randomIntBetween(1, 10); int shardCount = randomIntBetween(1, 10);
boolean verbose = randomBoolean(); boolean verbose = randomBoolean();
Manifest manifest = randomBoolean() ? createManifest(INDEX) : null; boolean hasClusterState = randomBoolean();
createIndexDataFiles(dataMasterSettings, shardCount, hasClusterState);
createIndexDataFiles(dataMasterSettings, shardCount);
Matcher<String> matcher = allOf( Matcher<String> matcher = allOf(
containsString(NodeRepurposeCommand.shardMessage(environment.dataFiles().length * shardCount, 1)), containsString(NodeRepurposeCommand.shardMessage(environment.dataFiles().length * shardCount, 1)),
conditionalNot(containsString("testUUID"), verbose == false), conditionalNot(containsString("testUUID"), verbose == false),
conditionalNot(containsString("testIndex"), verbose == false) conditionalNot(containsString("testIndex"), verbose == false || hasClusterState == false),
conditionalNot(containsString("no name for uuid: testUUID"), verbose == false || hasClusterState)
); );
verifyUnchangedOnAbort(noDataMasterSettings, verifyUnchangedOnAbort(noDataMasterSettings, matcher, verbose);
matcher, verbose);
// verify test setup // verify test setup
expectThrows(IllegalStateException.class, () -> new NodeEnvironment(noDataMasterSettings, environment).close()); expectThrows(IllegalStateException.class, () -> new NodeEnvironment(noDataMasterSettings, environment).close());
@ -182,12 +178,6 @@ public class NodeRepurposeCommandTests extends ESTestCase {
//verify clean. //verify clean.
new NodeEnvironment(noDataMasterSettings, environment).close(); new NodeEnvironment(noDataMasterSettings, environment).close();
if (manifest != null) {
Manifest newManifest = loadManifest();
assertThat(newManifest.getIndexGenerations().entrySet(), hasSize(1));
assertManifestIdenticalExceptIndices(manifest, newManifest);
}
} }
static void verifySuccess(Settings settings, Matcher<String> outputMatcher, boolean verbose) throws Exception { static void verifySuccess(Settings settings, Matcher<String> outputMatcher, boolean verbose) throws Exception {
@ -237,31 +227,22 @@ public class NodeRepurposeCommandTests extends ESTestCase {
nodeRepurposeCommand.testExecute(terminal, options, env); nodeRepurposeCommand.testExecute(terminal, options, env);
} }
private Manifest createManifest(Index index) throws org.elasticsearch.gateway.WriteStateException { private void createIndexDataFiles(Settings settings, int shardCount, boolean writeClusterState) throws IOException {
Manifest manifest = new Manifest(randomIntBetween(1,100), randomIntBetween(1,100), randomIntBetween(1,100),
index != null ? Collections.singletonMap(index, randomLongBetween(1,100)) : Collections.emptyMap());
Manifest.FORMAT.writeAndCleanup(manifest, nodePaths);
return manifest;
}
private Manifest loadManifest() throws IOException {
return Manifest.FORMAT.loadLatestState(logger, new NamedXContentRegistry(ClusterModule.getNamedXWriteables()), nodePaths);
}
private void assertManifestIdenticalExceptIndices(Manifest oldManifest, Manifest newManifest) {
assertEquals(oldManifest.getGlobalGeneration(), newManifest.getGlobalGeneration());
assertEquals(oldManifest.getClusterStateVersion(), newManifest.getClusterStateVersion());
assertEquals(oldManifest.getCurrentTerm(), newManifest.getCurrentTerm());
}
private void createIndexDataFiles(Settings settings, int shardCount) throws IOException {
int shardDataDirNumber = randomInt(10); int shardDataDirNumber = randomInt(10);
try (NodeEnvironment env = new NodeEnvironment(settings, TestEnvironment.newEnvironment(settings))) { Environment environment = TestEnvironment.newEnvironment(settings);
IndexMetaData.FORMAT.write(IndexMetaData.builder(INDEX.getName()) try (NodeEnvironment env = new NodeEnvironment(settings, environment)) {
.settings(Settings.builder().put("index.version.created", Version.CURRENT)) if (writeClusterState) {
try (PersistedClusterStateService.Writer writer =
ElasticsearchNodeCommand.createPersistedClusterStateService(env.nodeDataPaths()).createWriter()) {
writer.writeFullStateAndCommit(1L, ClusterState.builder(ClusterName.DEFAULT)
.metaData(MetaData.builder().put(IndexMetaData.builder(INDEX.getName())
.settings(Settings.builder().put("index.version.created", Version.CURRENT)
.put(IndexMetaData.SETTING_INDEX_UUID, INDEX.getUUID()))
.numberOfShards(1) .numberOfShards(1)
.numberOfReplicas(1) .numberOfReplicas(1)).build())
.build(), env.indexPaths(INDEX)); .build());
}
}
for (Path path : env.indexPaths(INDEX)) { for (Path path : env.indexPaths(INDEX)) {
for (int i = 0; i < shardCount; ++i) { for (int i = 0; i < shardCount; ++i) {
Files.createDirectories(path.resolve(Integer.toString(shardDataDirNumber))); Files.createDirectories(path.resolve(Integer.toString(shardDataDirNumber)));

View File

@ -18,31 +18,34 @@
*/ */
package org.elasticsearch.env; package org.elasticsearch.env;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.Version; import org.elasticsearch.Version;
import org.elasticsearch.cli.MockTerminal; import org.elasticsearch.cli.MockTerminal;
import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.gateway.PersistedClusterStateService;
import org.elasticsearch.gateway.MetaDataStateFormat;
import org.elasticsearch.gateway.WriteStateException;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import org.junit.After;
import org.junit.Before; import org.junit.Before;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import static org.elasticsearch.env.NodeMetaData.NODE_ID_KEY;
import static org.elasticsearch.env.NodeMetaData.NODE_VERSION_KEY;
import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasToString;
public class OverrideNodeVersionCommandTests extends ESTestCase { public class OverrideNodeVersionCommandTests extends ESTestCase {
private Environment environment; private Environment environment;
private Path[] nodePaths; private Path[] nodePaths;
private String nodeId;
private final OptionSet noOptions = new OptionParser().parse();
@Before @Before
public void createNodePaths() throws IOException { public void createNodePaths() throws IOException {
@ -50,24 +53,38 @@ public class OverrideNodeVersionCommandTests extends ESTestCase {
environment = TestEnvironment.newEnvironment(settings); environment = TestEnvironment.newEnvironment(settings);
try (NodeEnvironment nodeEnvironment = new NodeEnvironment(settings, environment)) { try (NodeEnvironment nodeEnvironment = new NodeEnvironment(settings, environment)) {
nodePaths = nodeEnvironment.nodeDataPaths(); nodePaths = nodeEnvironment.nodeDataPaths();
nodeId = nodeEnvironment.nodeId();
try (PersistedClusterStateService.Writer writer = new PersistedClusterStateService(nodePaths, nodeId,
xContentRegistry(), BigArrays.NON_RECYCLING_INSTANCE, true).createWriter()) {
writer.writeFullStateAndCommit(1L, ClusterState.builder(ClusterName.DEFAULT).metaData(MetaData.builder()
.persistentSettings(Settings.builder().put(MetaData.SETTING_READ_ONLY_SETTING.getKey(), true).build()).build())
.build());
} }
} }
}
@After
public void checkClusterStateIntact() throws IOException {
assertTrue(MetaData.SETTING_READ_ONLY_SETTING.get(new PersistedClusterStateService(nodePaths, nodeId,
xContentRegistry(), BigArrays.NON_RECYCLING_INSTANCE, true).loadBestOnDiskState().metaData.persistentSettings()));
}
public void testFailsOnEmptyPath() { public void testFailsOnEmptyPath() {
final Path emptyPath = createTempDir(); final Path emptyPath = createTempDir();
final MockTerminal mockTerminal = new MockTerminal(); final MockTerminal mockTerminal = new MockTerminal();
final ElasticsearchException elasticsearchException = expectThrows(ElasticsearchException.class, () -> final ElasticsearchException elasticsearchException = expectThrows(ElasticsearchException.class, () ->
new OverrideNodeVersionCommand().processNodePaths(mockTerminal, new Path[]{emptyPath}, environment)); new OverrideNodeVersionCommand().processNodePaths(mockTerminal, new Path[]{emptyPath}, 0, noOptions, environment));
assertThat(elasticsearchException.getMessage(), equalTo(OverrideNodeVersionCommand.NO_METADATA_MESSAGE)); assertThat(elasticsearchException.getMessage(), equalTo(OverrideNodeVersionCommand.NO_METADATA_MESSAGE));
expectThrows(IllegalStateException.class, () -> mockTerminal.readText("")); expectThrows(IllegalStateException.class, () -> mockTerminal.readText(""));
} }
public void testFailsIfUnnecessary() throws WriteStateException { public void testFailsIfUnnecessary() throws IOException {
final Version nodeVersion = Version.fromId(between(Version.CURRENT.minimumIndexCompatibilityVersion().id, Version.CURRENT.id)); final Version nodeVersion = Version.fromId(between(Version.CURRENT.minimumIndexCompatibilityVersion().id, Version.CURRENT.id));
NodeMetaData.FORMAT.writeAndCleanup(new NodeMetaData(randomAlphaOfLength(10), nodeVersion), nodePaths); PersistedClusterStateService.overrideVersion(nodeVersion, nodePaths);
final MockTerminal mockTerminal = new MockTerminal(); final MockTerminal mockTerminal = new MockTerminal();
final ElasticsearchException elasticsearchException = expectThrows(ElasticsearchException.class, () -> final ElasticsearchException elasticsearchException = expectThrows(ElasticsearchException.class, () ->
new OverrideNodeVersionCommand().processNodePaths(mockTerminal, nodePaths, environment)); new OverrideNodeVersionCommand().processNodePaths(mockTerminal, nodePaths, 0, noOptions, environment));
assertThat(elasticsearchException.getMessage(), allOf( assertThat(elasticsearchException.getMessage(), allOf(
containsString("compatible with current version"), containsString("compatible with current version"),
containsString(Version.CURRENT.toString()), containsString(Version.CURRENT.toString()),
@ -76,13 +93,12 @@ public class OverrideNodeVersionCommandTests extends ESTestCase {
} }
public void testWarnsIfTooOld() throws Exception { public void testWarnsIfTooOld() throws Exception {
final String nodeId = randomAlphaOfLength(10);
final Version nodeVersion = NodeMetaDataTests.tooOldVersion(); final Version nodeVersion = NodeMetaDataTests.tooOldVersion();
NodeMetaData.FORMAT.writeAndCleanup(new NodeMetaData(nodeId, nodeVersion), nodePaths); PersistedClusterStateService.overrideVersion(nodeVersion, nodePaths);
final MockTerminal mockTerminal = new MockTerminal(); final MockTerminal mockTerminal = new MockTerminal();
mockTerminal.addTextInput("n\n"); mockTerminal.addTextInput("n\n");
final ElasticsearchException elasticsearchException = expectThrows(ElasticsearchException.class, () -> final ElasticsearchException elasticsearchException = expectThrows(ElasticsearchException.class, () ->
new OverrideNodeVersionCommand().processNodePaths(mockTerminal, nodePaths, environment)); new OverrideNodeVersionCommand().processNodePaths(mockTerminal, nodePaths, 0, noOptions, environment));
assertThat(elasticsearchException.getMessage(), equalTo("aborted by user")); assertThat(elasticsearchException.getMessage(), equalTo("aborted by user"));
assertThat(mockTerminal.getOutput(), allOf( assertThat(mockTerminal.getOutput(), allOf(
containsString("too old"), containsString("too old"),
@ -92,19 +108,17 @@ public class OverrideNodeVersionCommandTests extends ESTestCase {
containsString(nodeVersion.toString()))); containsString(nodeVersion.toString())));
expectThrows(IllegalStateException.class, () -> mockTerminal.readText("")); expectThrows(IllegalStateException.class, () -> mockTerminal.readText(""));
final NodeMetaData nodeMetaData = NodeMetaData.FORMAT.loadLatestState(logger, xContentRegistry(), nodePaths); final NodeMetaData nodeMetaData = PersistedClusterStateService.nodeMetaData(nodePaths);
assertThat(nodeMetaData.nodeId(), equalTo(nodeId));
assertThat(nodeMetaData.nodeVersion(), equalTo(nodeVersion)); assertThat(nodeMetaData.nodeVersion(), equalTo(nodeVersion));
} }
public void testWarnsIfTooNew() throws Exception { public void testWarnsIfTooNew() throws Exception {
final String nodeId = randomAlphaOfLength(10);
final Version nodeVersion = NodeMetaDataTests.tooNewVersion(); final Version nodeVersion = NodeMetaDataTests.tooNewVersion();
NodeMetaData.FORMAT.writeAndCleanup(new NodeMetaData(nodeId, nodeVersion), nodePaths); PersistedClusterStateService.overrideVersion(nodeVersion, nodePaths);
final MockTerminal mockTerminal = new MockTerminal(); final MockTerminal mockTerminal = new MockTerminal();
mockTerminal.addTextInput(randomFrom("yy", "Yy", "n", "yes", "true", "N", "no")); mockTerminal.addTextInput(randomFrom("yy", "Yy", "n", "yes", "true", "N", "no"));
final ElasticsearchException elasticsearchException = expectThrows(ElasticsearchException.class, () -> final ElasticsearchException elasticsearchException = expectThrows(ElasticsearchException.class, () ->
new OverrideNodeVersionCommand().processNodePaths(mockTerminal, nodePaths, environment)); new OverrideNodeVersionCommand().processNodePaths(mockTerminal, nodePaths, 0, noOptions, environment));
assertThat(elasticsearchException.getMessage(), equalTo("aborted by user")); assertThat(elasticsearchException.getMessage(), equalTo("aborted by user"));
assertThat(mockTerminal.getOutput(), allOf( assertThat(mockTerminal.getOutput(), allOf(
containsString("data loss"), containsString("data loss"),
@ -113,18 +127,16 @@ public class OverrideNodeVersionCommandTests extends ESTestCase {
containsString(nodeVersion.toString()))); containsString(nodeVersion.toString())));
expectThrows(IllegalStateException.class, () -> mockTerminal.readText("")); expectThrows(IllegalStateException.class, () -> mockTerminal.readText(""));
final NodeMetaData nodeMetaData = NodeMetaData.FORMAT.loadLatestState(logger, xContentRegistry(), nodePaths); final NodeMetaData nodeMetaData = PersistedClusterStateService.nodeMetaData(nodePaths);
assertThat(nodeMetaData.nodeId(), equalTo(nodeId));
assertThat(nodeMetaData.nodeVersion(), equalTo(nodeVersion)); assertThat(nodeMetaData.nodeVersion(), equalTo(nodeVersion));
} }
public void testOverwritesIfTooOld() throws Exception { public void testOverwritesIfTooOld() throws Exception {
final String nodeId = randomAlphaOfLength(10);
final Version nodeVersion = NodeMetaDataTests.tooOldVersion(); final Version nodeVersion = NodeMetaDataTests.tooOldVersion();
NodeMetaData.FORMAT.writeAndCleanup(new NodeMetaData(nodeId, nodeVersion), nodePaths); PersistedClusterStateService.overrideVersion(nodeVersion, nodePaths);
final MockTerminal mockTerminal = new MockTerminal(); final MockTerminal mockTerminal = new MockTerminal();
mockTerminal.addTextInput(randomFrom("y", "Y")); mockTerminal.addTextInput(randomFrom("y", "Y"));
new OverrideNodeVersionCommand().processNodePaths(mockTerminal, nodePaths, environment); new OverrideNodeVersionCommand().processNodePaths(mockTerminal, nodePaths, 0, noOptions, environment);
assertThat(mockTerminal.getOutput(), allOf( assertThat(mockTerminal.getOutput(), allOf(
containsString("too old"), containsString("too old"),
containsString("data loss"), containsString("data loss"),
@ -134,18 +146,16 @@ public class OverrideNodeVersionCommandTests extends ESTestCase {
containsString(OverrideNodeVersionCommand.SUCCESS_MESSAGE))); containsString(OverrideNodeVersionCommand.SUCCESS_MESSAGE)));
expectThrows(IllegalStateException.class, () -> mockTerminal.readText("")); expectThrows(IllegalStateException.class, () -> mockTerminal.readText(""));
final NodeMetaData nodeMetaData = NodeMetaData.FORMAT.loadLatestState(logger, xContentRegistry(), nodePaths); final NodeMetaData nodeMetaData = PersistedClusterStateService.nodeMetaData(nodePaths);
assertThat(nodeMetaData.nodeId(), equalTo(nodeId));
assertThat(nodeMetaData.nodeVersion(), equalTo(Version.CURRENT)); assertThat(nodeMetaData.nodeVersion(), equalTo(Version.CURRENT));
} }
public void testOverwritesIfTooNew() throws Exception { public void testOverwritesIfTooNew() throws Exception {
final String nodeId = randomAlphaOfLength(10);
final Version nodeVersion = NodeMetaDataTests.tooNewVersion(); final Version nodeVersion = NodeMetaDataTests.tooNewVersion();
NodeMetaData.FORMAT.writeAndCleanup(new NodeMetaData(nodeId, nodeVersion), nodePaths); PersistedClusterStateService.overrideVersion(nodeVersion, nodePaths);
final MockTerminal mockTerminal = new MockTerminal(); final MockTerminal mockTerminal = new MockTerminal();
mockTerminal.addTextInput(randomFrom("y", "Y")); mockTerminal.addTextInput(randomFrom("y", "Y"));
new OverrideNodeVersionCommand().processNodePaths(mockTerminal, nodePaths, environment); new OverrideNodeVersionCommand().processNodePaths(mockTerminal, nodePaths, 0, noOptions, environment);
assertThat(mockTerminal.getOutput(), allOf( assertThat(mockTerminal.getOutput(), allOf(
containsString("data loss"), containsString("data loss"),
containsString("You should not use this tool"), containsString("You should not use this tool"),
@ -154,59 +164,7 @@ public class OverrideNodeVersionCommandTests extends ESTestCase {
containsString(OverrideNodeVersionCommand.SUCCESS_MESSAGE))); containsString(OverrideNodeVersionCommand.SUCCESS_MESSAGE)));
expectThrows(IllegalStateException.class, () -> mockTerminal.readText("")); expectThrows(IllegalStateException.class, () -> mockTerminal.readText(""));
final NodeMetaData nodeMetaData = NodeMetaData.FORMAT.loadLatestState(logger, xContentRegistry(), nodePaths); final NodeMetaData nodeMetaData = PersistedClusterStateService.nodeMetaData(nodePaths);
assertThat(nodeMetaData.nodeId(), equalTo(nodeId));
assertThat(nodeMetaData.nodeVersion(), equalTo(Version.CURRENT)); assertThat(nodeMetaData.nodeVersion(), equalTo(Version.CURRENT));
} }
public void testLenientlyIgnoresExtraFields() throws Exception {
final String nodeId = randomAlphaOfLength(10);
final Version nodeVersion = NodeMetaDataTests.tooNewVersion();
FutureNodeMetaData.FORMAT.writeAndCleanup(new FutureNodeMetaData(nodeId, nodeVersion, randomLong()), nodePaths);
assertThat(expectThrows(ElasticsearchException.class,
() -> NodeMetaData.FORMAT.loadLatestState(logger, xContentRegistry(), nodePaths)),
hasToString(containsString("unknown field [future_field]")));
final MockTerminal mockTerminal = new MockTerminal();
mockTerminal.addTextInput(randomFrom("y", "Y"));
new OverrideNodeVersionCommand().processNodePaths(mockTerminal, nodePaths, environment);
assertThat(mockTerminal.getOutput(), allOf(
containsString("data loss"),
containsString("You should not use this tool"),
containsString(Version.CURRENT.toString()),
containsString(nodeVersion.toString()),
containsString(OverrideNodeVersionCommand.SUCCESS_MESSAGE)));
expectThrows(IllegalStateException.class, () -> mockTerminal.readText(""));
final NodeMetaData nodeMetaData = NodeMetaData.FORMAT.loadLatestState(logger, xContentRegistry(), nodePaths);
assertThat(nodeMetaData.nodeId(), equalTo(nodeId));
assertThat(nodeMetaData.nodeVersion(), equalTo(Version.CURRENT));
}
private static class FutureNodeMetaData {
private final String nodeId;
private final Version nodeVersion;
private final long futureValue;
FutureNodeMetaData(String nodeId, Version nodeVersion, long futureValue) {
this.nodeId = nodeId;
this.nodeVersion = nodeVersion;
this.futureValue = futureValue;
}
static final MetaDataStateFormat<FutureNodeMetaData> FORMAT
= new MetaDataStateFormat<FutureNodeMetaData>(NodeMetaData.FORMAT.getPrefix()) {
@Override
public void toXContent(XContentBuilder builder, FutureNodeMetaData state) throws IOException {
builder.field(NODE_ID_KEY, state.nodeId);
builder.field(NODE_VERSION_KEY, state.nodeVersion.id);
builder.field("future_field", state.futureValue);
}
@Override
public FutureNodeMetaData fromXContent(XContentParser parser) {
throw new AssertionError("shouldn't be loading a FutureNodeMetaData");
}
};
}
} }

View File

@ -30,10 +30,8 @@ import org.elasticsearch.action.support.ActiveShardCount;
import org.elasticsearch.client.Client; import org.elasticsearch.client.Client;
import org.elasticsearch.client.Requests; import org.elasticsearch.client.Requests;
import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.coordination.CoordinationMetaData;
import org.elasticsearch.cluster.metadata.IndexGraveyard; import org.elasticsearch.cluster.metadata.IndexGraveyard;
import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.metadata.Manifest;
import org.elasticsearch.cluster.metadata.MappingMetaData; import org.elasticsearch.cluster.metadata.MappingMetaData;
import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.cluster.routing.IndexRoutingTable; import org.elasticsearch.cluster.routing.IndexRoutingTable;
@ -42,12 +40,10 @@ import org.elasticsearch.cluster.routing.RoutingTable;
import org.elasticsearch.cluster.routing.ShardRoutingState; import org.elasticsearch.cluster.routing.ShardRoutingState;
import org.elasticsearch.cluster.routing.UnassignedInfo; import org.elasticsearch.cluster.routing.UnassignedInfo;
import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.CheckedConsumer;
import org.elasticsearch.common.Priority; import org.elasticsearch.common.Priority;
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.XContentType; import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.core.internal.io.IOUtils;
import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.env.NodeEnvironment;
import org.elasticsearch.index.mapper.MapperParsingException; import org.elasticsearch.index.mapper.MapperParsingException;
import org.elasticsearch.indices.IndexClosedException; import org.elasticsearch.indices.IndexClosedException;
@ -58,7 +54,6 @@ import org.elasticsearch.test.ESIntegTestCase.Scope;
import org.elasticsearch.test.InternalTestCluster.RestartCallback; import org.elasticsearch.test.InternalTestCluster.RestartCallback;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -379,14 +374,13 @@ public class GatewayIndexStateIT extends ESIntegTestCase {
ClusterState state = client().admin().cluster().prepareState().get().getState(); ClusterState state = client().admin().cluster().prepareState().get().getState();
final IndexMetaData metaData = state.getMetaData().index("test"); final IndexMetaData metaData = state.getMetaData().index("test");
final IndexMetaData brokenMeta = IndexMetaData.builder(metaData).settings(Settings.builder().put(metaData.getSettings()) final IndexMetaData.Builder brokenMeta = IndexMetaData.builder(metaData).settings(Settings.builder().put(metaData.getSettings())
.put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT.minimumIndexCompatibilityVersion().id) .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT.minimumIndexCompatibilityVersion().id)
// this is invalid but should be archived // this is invalid but should be archived
.put("index.similarity.BM25.type", "classic") .put("index.similarity.BM25.type", "classic")
// this one is not validated ahead of time and breaks allocation // this one is not validated ahead of time and breaks allocation
.put("index.analysis.filter.myCollator.type", "icu_collation") .put("index.analysis.filter.myCollator.type", "icu_collation"));
).build(); restartNodesOnBrokenClusterState(ClusterState.builder(state).metaData(MetaData.builder(state.getMetaData()).put(brokenMeta)));
writeBrokenMeta(metaStateService -> metaStateService.writeIndexAndUpdateManifest("broken metadata", brokenMeta));
// check that the cluster does not keep reallocating shards // check that the cluster does not keep reallocating shards
assertBusy(() -> { assertBusy(() -> {
@ -451,9 +445,9 @@ public class GatewayIndexStateIT extends ESIntegTestCase {
ClusterState state = client().admin().cluster().prepareState().get().getState(); ClusterState state = client().admin().cluster().prepareState().get().getState();
final IndexMetaData metaData = state.getMetaData().index("test"); final IndexMetaData metaData = state.getMetaData().index("test");
final IndexMetaData brokenMeta = IndexMetaData.builder(metaData).settings(metaData.getSettings() final IndexMetaData.Builder brokenMeta = IndexMetaData.builder(metaData).settings(metaData.getSettings()
.filter((s) -> "index.analysis.analyzer.test.tokenizer".equals(s) == false)).build(); .filter((s) -> "index.analysis.analyzer.test.tokenizer".equals(s) == false));
writeBrokenMeta(metaStateService -> metaStateService.writeIndexAndUpdateManifest("broken metadata", brokenMeta)); restartNodesOnBrokenClusterState(ClusterState.builder(state).metaData(MetaData.builder(state.getMetaData()).put(brokenMeta)));
// check that the cluster does not keep reallocating shards // check that the cluster does not keep reallocating shards
assertBusy(() -> { assertBusy(() -> {
@ -498,7 +492,7 @@ public class GatewayIndexStateIT extends ESIntegTestCase {
final MetaData brokenMeta = MetaData.builder(metaData).persistentSettings(Settings.builder() final MetaData brokenMeta = MetaData.builder(metaData).persistentSettings(Settings.builder()
.put(metaData.persistentSettings()).put("this.is.unknown", true) .put(metaData.persistentSettings()).put("this.is.unknown", true)
.put(MetaData.SETTING_CLUSTER_MAX_SHARDS_PER_NODE.getKey(), "broken").build()).build(); .put(MetaData.SETTING_CLUSTER_MAX_SHARDS_PER_NODE.getKey(), "broken").build()).build();
writeBrokenMeta(metaStateService -> metaStateService.writeGlobalStateAndUpdateManifest("broken metadata", brokenMeta)); restartNodesOnBrokenClusterState(ClusterState.builder(state).metaData(brokenMeta));
ensureYellow("test"); // wait for state recovery ensureYellow("test"); // wait for state recovery
state = client().admin().cluster().prepareState().get().getState(); state = client().admin().cluster().prepareState().get().getState();
@ -516,6 +510,8 @@ public class GatewayIndexStateIT extends ESIntegTestCase {
assertHitCount(client().prepareSearch().setQuery(matchAllQuery()).get(), 1L); assertHitCount(client().prepareSearch().setQuery(matchAllQuery()).get(), 1L);
} }
@AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/48701")
// This test relates to loading a broken state that was written by a 6.x node, but for now we do not load state from old nodes.
public void testHalfDeletedIndexImport() throws Exception { public void testHalfDeletedIndexImport() throws Exception {
// It's possible for a 6.x node to add a tombstone for an index but not actually delete the index metadata from disk since that // It's possible for a 6.x node to add a tombstone for an index but not actually delete the index metadata from disk since that
// deletion is slightly deferred and may race against the node being shut down; if you upgrade to 7.x when in this state then the // deletion is slightly deferred and may race against the node being shut down; if you upgrade to 7.x when in this state then the
@ -530,36 +526,40 @@ public class GatewayIndexStateIT extends ESIntegTestCase {
final MetaData metaData = internalCluster().getInstance(ClusterService.class).state().metaData(); final MetaData metaData = internalCluster().getInstance(ClusterService.class).state().metaData();
final Path[] paths = internalCluster().getInstance(NodeEnvironment.class).nodeDataPaths(); final Path[] paths = internalCluster().getInstance(NodeEnvironment.class).nodeDataPaths();
writeBrokenMeta(metaStateService -> { // writeBrokenMeta(metaStateService -> {
metaStateService.writeGlobalState("test", MetaData.builder(metaData) // metaStateService.writeGlobalState("test", MetaData.builder(metaData)
// we remove the manifest file, resetting the term and making this look like an upgrade from 6.x, so must also reset the // // we remove the manifest file, resetting the term and making this look like an upgrade from 6.x, so must also reset the
// term in the coordination metadata // // term in the coordination metadata
.coordinationMetaData(CoordinationMetaData.builder(metaData.coordinationMetaData()).term(0L).build()) // .coordinationMetaData(CoordinationMetaData.builder(metaData.coordinationMetaData()).term(0L).build())
// add a tombstone but do not delete the index metadata from disk // // add a tombstone but do not delete the index metadata from disk
.putCustom(IndexGraveyard.TYPE, IndexGraveyard.builder().addTombstone(metaData.index("test").getIndex()).build()).build()); // .putCustom(IndexGraveyard.TYPE, IndexGraveyard.builder().addTombstone(metaData.index("test").getIndex()).build()).build());
for (final Path path : paths) { // for (final Path path : paths) {
try (Stream<Path> stateFiles = Files.list(path.resolve(MetaDataStateFormat.STATE_DIR_NAME))) { // try (Stream<Path> stateFiles = Files.list(path.resolve(MetaDataStateFormat.STATE_DIR_NAME))) {
for (final Path manifestPath : stateFiles // for (final Path manifestPath : stateFiles
.filter(p -> p.getFileName().toString().startsWith(Manifest.FORMAT.getPrefix())).collect(Collectors.toList())) { // .filter(p -> p.getFileName().toString().startsWith(Manifest.FORMAT.getPrefix())).collect(Collectors.toList())) {
IOUtils.rm(manifestPath); // IOUtils.rm(manifestPath);
} // }
} // }
} // }
}); // });
ensureGreen(); ensureGreen();
assertBusy(() -> assertThat(internalCluster().getInstance(NodeEnvironment.class).availableIndexFolders(), empty())); assertBusy(() -> assertThat(internalCluster().getInstance(NodeEnvironment.class).availableIndexFolders(), empty()));
} }
private void writeBrokenMeta(CheckedConsumer<MetaStateService, IOException> writer) throws Exception { private void restartNodesOnBrokenClusterState(ClusterState.Builder clusterStateBuilder) throws Exception {
Map<String, MetaStateService> metaStateServices = Stream.of(internalCluster().getNodeNames()) Map<String, PersistedClusterStateService> lucenePersistedStateFactories = Stream.of(internalCluster().getNodeNames())
.collect(Collectors.toMap(Function.identity(), nodeName -> internalCluster().getInstance(MetaStateService.class, nodeName))); .collect(Collectors.toMap(Function.identity(),
nodeName -> internalCluster().getInstance(PersistedClusterStateService.class, nodeName)));
final ClusterState clusterState = clusterStateBuilder.build();
internalCluster().fullRestart(new RestartCallback(){ internalCluster().fullRestart(new RestartCallback(){
@Override @Override
public Settings onNodeStopped(String nodeName) throws Exception { public Settings onNodeStopped(String nodeName) throws Exception {
final MetaStateService metaStateService = metaStateServices.get(nodeName); final PersistedClusterStateService lucenePersistedStateFactory = lucenePersistedStateFactories.get(nodeName);
writer.accept(metaStateService); try (PersistedClusterStateService.Writer writer = lucenePersistedStateFactory.createWriter()) {
writer.writeFullStateAndCommit(clusterState.term(), clusterState);
}
return super.onNodeStopped(nodeName); return super.onNodeStopped(nodeName);
} }
}); });

View File

@ -19,29 +19,50 @@
package org.elasticsearch.gateway; package org.elasticsearch.gateway;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.MockDirectoryWrapper;
import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.Version; import org.elasticsearch.Version;
import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.coordination.CoordinationMetaData; import org.elasticsearch.cluster.coordination.CoordinationMetaData;
import org.elasticsearch.cluster.coordination.CoordinationMetaData.VotingConfigExclusion; import org.elasticsearch.cluster.coordination.CoordinationMetaData.VotingConfigExclusion;
import org.elasticsearch.cluster.coordination.CoordinationState; import org.elasticsearch.cluster.coordination.CoordinationState;
import org.elasticsearch.cluster.coordination.InMemoryPersistedState;
import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.metadata.Manifest; import org.elasticsearch.cluster.metadata.Manifest;
import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.node.DiscoveryNodeRole; import org.elasticsearch.cluster.node.DiscoveryNodeRole;
import org.elasticsearch.cluster.node.DiscoveryNodes; import org.elasticsearch.cluster.node.DiscoveryNodes;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.core.internal.io.IOUtils;
import org.elasticsearch.env.Environment;
import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.env.NodeEnvironment;
import org.elasticsearch.env.TestEnvironment;
import org.elasticsearch.node.Node;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.threadpool.TestThreadPool;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import java.io.IOError;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class GatewayMetaStatePersistedStateTests extends ESTestCase { public class GatewayMetaStatePersistedStateTests extends ESTestCase {
private NodeEnvironment nodeEnvironment; private NodeEnvironment nodeEnvironment;
@ -69,19 +90,22 @@ public class GatewayMetaStatePersistedStateTests extends ESTestCase {
final MockGatewayMetaState gateway = new MockGatewayMetaState(localNode); final MockGatewayMetaState gateway = new MockGatewayMetaState(localNode);
gateway.start(settings, nodeEnvironment, xContentRegistry()); gateway.start(settings, nodeEnvironment, xContentRegistry());
final CoordinationState.PersistedState persistedState = gateway.getPersistedState(); final CoordinationState.PersistedState persistedState = gateway.getPersistedState();
assertThat(persistedState, not(instanceOf(InMemoryPersistedState.class))); assertThat(persistedState, instanceOf(GatewayMetaState.LucenePersistedState.class));
return persistedState; return persistedState;
} }
private CoordinationState.PersistedState maybeNew(CoordinationState.PersistedState persistedState) { private CoordinationState.PersistedState maybeNew(CoordinationState.PersistedState persistedState) throws IOException {
if (randomBoolean()) { if (randomBoolean()) {
persistedState.close();
return newGatewayPersistedState(); return newGatewayPersistedState();
} }
return persistedState; return persistedState;
} }
public void testInitialState() { public void testInitialState() throws IOException {
CoordinationState.PersistedState gateway = newGatewayPersistedState(); CoordinationState.PersistedState gateway = null;
try {
gateway = newGatewayPersistedState();
ClusterState state = gateway.getLastAcceptedState(); ClusterState state = gateway.getLastAcceptedState();
assertThat(state.getClusterName(), equalTo(clusterName)); assertThat(state.getClusterName(), equalTo(clusterName));
assertTrue(MetaData.isGlobalStateEquals(state.metaData(), MetaData.EMPTY_META_DATA)); assertTrue(MetaData.isGlobalStateEquals(state.metaData(), MetaData.EMPTY_META_DATA));
@ -90,10 +114,15 @@ public class GatewayMetaStatePersistedStateTests extends ESTestCase {
long currentTerm = gateway.getCurrentTerm(); long currentTerm = gateway.getCurrentTerm();
assertThat(currentTerm, equalTo(Manifest.empty().getCurrentTerm())); assertThat(currentTerm, equalTo(Manifest.empty().getCurrentTerm()));
} finally {
IOUtils.close(gateway);
}
} }
public void testSetCurrentTerm() { public void testSetCurrentTerm() throws IOException {
CoordinationState.PersistedState gateway = newGatewayPersistedState(); CoordinationState.PersistedState gateway = null;
try {
gateway = newGatewayPersistedState();
for (int i = 0; i < randomIntBetween(1, 5); i++) { for (int i = 0; i < randomIntBetween(1, 5); i++) {
final long currentTerm = randomNonNegativeLong(); final long currentTerm = randomNonNegativeLong();
@ -101,6 +130,9 @@ public class GatewayMetaStatePersistedStateTests extends ESTestCase {
gateway = maybeNew(gateway); gateway = maybeNew(gateway);
assertThat(gateway.getCurrentTerm(), equalTo(currentTerm)); assertThat(gateway.getCurrentTerm(), equalTo(currentTerm));
} }
} finally {
IOUtils.close(gateway);
}
} }
private ClusterState createClusterState(long version, MetaData metaData) { private ClusterState createClusterState(long version, MetaData metaData) {
@ -146,14 +178,16 @@ public class GatewayMetaStatePersistedStateTests extends ESTestCase {
} }
} }
public void testSetLastAcceptedState() { public void testSetLastAcceptedState() throws IOException {
CoordinationState.PersistedState gateway = newGatewayPersistedState(); CoordinationState.PersistedState gateway = null;
try {
gateway = newGatewayPersistedState();
final long term = randomNonNegativeLong(); final long term = randomNonNegativeLong();
for (int i = 0; i < randomIntBetween(1, 5); i++) { for (int i = 0; i < randomIntBetween(1, 5); i++) {
final long version = randomNonNegativeLong(); final long version = randomNonNegativeLong();
final String indexName = randomAlphaOfLength(10); final String indexName = randomAlphaOfLength(10);
final IndexMetaData indexMetaData = createIndexMetaData(indexName, randomIntBetween(1,5), randomNonNegativeLong()); final IndexMetaData indexMetaData = createIndexMetaData(indexName, randomIntBetween(1, 5), randomNonNegativeLong());
final MetaData metaData = MetaData.builder(). final MetaData metaData = MetaData.builder().
persistentSettings(Settings.builder().put(randomAlphaOfLength(10), randomAlphaOfLength(10)).build()). persistentSettings(Settings.builder().put(randomAlphaOfLength(10), randomAlphaOfLength(10)).build()).
coordinationMetaData(createCoordinationMetaData(term)). coordinationMetaData(createCoordinationMetaData(term)).
@ -167,23 +201,28 @@ public class GatewayMetaStatePersistedStateTests extends ESTestCase {
ClusterState lastAcceptedState = gateway.getLastAcceptedState(); ClusterState lastAcceptedState = gateway.getLastAcceptedState();
assertClusterStateEqual(state, lastAcceptedState); assertClusterStateEqual(state, lastAcceptedState);
} }
} finally {
IOUtils.close(gateway);
}
} }
public void testSetLastAcceptedStateTermChanged() { public void testSetLastAcceptedStateTermChanged() throws IOException {
CoordinationState.PersistedState gateway = newGatewayPersistedState(); CoordinationState.PersistedState gateway = null;
try {
gateway = newGatewayPersistedState();
final String indexName = randomAlphaOfLength(10); final String indexName = randomAlphaOfLength(10);
final int numberOfShards = randomIntBetween(1, 5); final int numberOfShards = randomIntBetween(1, 5);
final long version = randomNonNegativeLong(); final long version = randomNonNegativeLong();
final long term = randomNonNegativeLong(); final long term = randomValueOtherThan(Long.MAX_VALUE, ESTestCase::randomNonNegativeLong);
final IndexMetaData indexMetaData = createIndexMetaData(indexName, numberOfShards, version); final IndexMetaData indexMetaData = createIndexMetaData(indexName, numberOfShards, version);
final ClusterState state = createClusterState(randomNonNegativeLong(), final ClusterState state = createClusterState(randomNonNegativeLong(),
MetaData.builder().coordinationMetaData(createCoordinationMetaData(term)).put(indexMetaData, false).build()); MetaData.builder().coordinationMetaData(createCoordinationMetaData(term)).put(indexMetaData, false).build());
gateway.setLastAcceptedState(state); gateway.setLastAcceptedState(state);
gateway = maybeNew(gateway); gateway = maybeNew(gateway);
final long newTerm = randomValueOtherThan(term, ESTestCase::randomNonNegativeLong); final long newTerm = randomLongBetween(term + 1, Long.MAX_VALUE);
final int newNumberOfShards = randomValueOtherThan(numberOfShards, () -> randomIntBetween(1,5)); final int newNumberOfShards = randomValueOtherThan(numberOfShards, () -> randomIntBetween(1, 5));
final IndexMetaData newIndexMetaData = createIndexMetaData(indexName, newNumberOfShards, version); final IndexMetaData newIndexMetaData = createIndexMetaData(indexName, newNumberOfShards, version);
final ClusterState newClusterState = createClusterState(randomNonNegativeLong(), final ClusterState newClusterState = createClusterState(randomNonNegativeLong(),
MetaData.builder().coordinationMetaData(createCoordinationMetaData(newTerm)).put(newIndexMetaData, false).build()); MetaData.builder().coordinationMetaData(createCoordinationMetaData(newTerm)).put(newIndexMetaData, false).build());
@ -191,10 +230,15 @@ public class GatewayMetaStatePersistedStateTests extends ESTestCase {
gateway = maybeNew(gateway); gateway = maybeNew(gateway);
assertThat(gateway.getLastAcceptedState().metaData().index(indexName), equalTo(newIndexMetaData)); assertThat(gateway.getLastAcceptedState().metaData().index(indexName), equalTo(newIndexMetaData));
} finally {
IOUtils.close(gateway);
}
} }
public void testCurrentTermAndTermAreDifferent() { public void testCurrentTermAndTermAreDifferent() throws IOException {
CoordinationState.PersistedState gateway = newGatewayPersistedState(); CoordinationState.PersistedState gateway = null;
try {
gateway = newGatewayPersistedState();
long currentTerm = randomNonNegativeLong(); long currentTerm = randomNonNegativeLong();
long term = randomValueOtherThan(currentTerm, ESTestCase::randomNonNegativeLong); long term = randomValueOtherThan(currentTerm, ESTestCase::randomNonNegativeLong);
@ -206,12 +250,17 @@ public class GatewayMetaStatePersistedStateTests extends ESTestCase {
gateway = maybeNew(gateway); gateway = maybeNew(gateway);
assertThat(gateway.getCurrentTerm(), equalTo(currentTerm)); assertThat(gateway.getCurrentTerm(), equalTo(currentTerm));
assertThat(gateway.getLastAcceptedState().coordinationMetaData().term(), equalTo(term)); assertThat(gateway.getLastAcceptedState().coordinationMetaData().term(), equalTo(term));
} finally {
IOUtils.close(gateway);
}
} }
public void testMarkAcceptedConfigAsCommitted() { public void testMarkAcceptedConfigAsCommitted() throws IOException {
CoordinationState.PersistedState gateway = newGatewayPersistedState(); CoordinationState.PersistedState gateway = null;
try {
gateway = newGatewayPersistedState();
//generate random coordinationMetaData with different lastAcceptedConfiguration and lastCommittedConfiguration // generate random coordinationMetaData with different lastAcceptedConfiguration and lastCommittedConfiguration
CoordinationMetaData coordinationMetaData; CoordinationMetaData coordinationMetaData;
do { do {
coordinationMetaData = createCoordinationMetaData(randomNonNegativeLong()); coordinationMetaData = createCoordinationMetaData(randomNonNegativeLong());
@ -239,5 +288,224 @@ public class GatewayMetaStatePersistedStateTests extends ESTestCase {
gateway = maybeNew(gateway); gateway = maybeNew(gateway);
assertClusterStateEqual(expectedClusterState, gateway.getLastAcceptedState()); assertClusterStateEqual(expectedClusterState, gateway.getLastAcceptedState());
} finally {
IOUtils.close(gateway);
} }
}
public void testStatePersistedOnLoad() throws IOException {
// open LucenePersistedState to make sure that cluster state is written out to each data path
final PersistedClusterStateService persistedClusterStateService =
new PersistedClusterStateService(nodeEnvironment, xContentRegistry(), BigArrays.NON_RECYCLING_INSTANCE);
final ClusterState state = createClusterState(randomNonNegativeLong(),
MetaData.builder().clusterUUID(randomAlphaOfLength(10)).build());
try (GatewayMetaState.LucenePersistedState ignored = new GatewayMetaState.LucenePersistedState(
persistedClusterStateService, 42L, state)) {
}
nodeEnvironment.close();
// verify that the freshest state was rewritten to each data path
for (Path path : nodeEnvironment.nodeDataPaths()) {
Settings settings = Settings.builder()
.put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toAbsolutePath())
.put(Environment.PATH_DATA_SETTING.getKey(), path.getParent().getParent().toString()).build();
try (NodeEnvironment nodeEnvironment = new NodeEnvironment(settings, TestEnvironment.newEnvironment(settings))) {
final PersistedClusterStateService newPersistedClusterStateService =
new PersistedClusterStateService(nodeEnvironment, xContentRegistry(), BigArrays.NON_RECYCLING_INSTANCE);
final PersistedClusterStateService.OnDiskState onDiskState = newPersistedClusterStateService.loadBestOnDiskState();
assertFalse(onDiskState.empty());
assertThat(onDiskState.currentTerm, equalTo(42L));
assertClusterStateEqual(state,
ClusterState.builder(ClusterName.DEFAULT)
.version(onDiskState.lastAcceptedVersion)
.metaData(onDiskState.metaData).build());
}
}
}
public void testDataOnlyNodePersistence() throws Exception {
DiscoveryNode localNode = new DiscoveryNode("node1", buildNewFakeTransportAddress(), Collections.emptyMap(),
Sets.newHashSet(DiscoveryNodeRole.DATA_ROLE), Version.CURRENT);
Settings settings = Settings.builder().put(ClusterName.CLUSTER_NAME_SETTING.getKey(), clusterName.value()).put(
Node.NODE_MASTER_SETTING.getKey(), false).put(Node.NODE_NAME_SETTING.getKey(), "test").build();
final MockGatewayMetaState gateway = new MockGatewayMetaState(localNode);
final TransportService transportService = mock(TransportService.class);
TestThreadPool threadPool = new TestThreadPool("testMarkAcceptedConfigAsCommittedOnDataOnlyNode");
when(transportService.getThreadPool()).thenReturn(threadPool);
ClusterService clusterService = mock(ClusterService.class);
when(clusterService.getClusterSettings()).thenReturn(
new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS));
final PersistedClusterStateService persistedClusterStateService =
new PersistedClusterStateService(nodeEnvironment, xContentRegistry(), BigArrays.NON_RECYCLING_INSTANCE);
gateway.start(settings, transportService, clusterService,
new MetaStateService(nodeEnvironment, xContentRegistry()), null, null, persistedClusterStateService);
final CoordinationState.PersistedState persistedState = gateway.getPersistedState();
assertThat(persistedState, instanceOf(GatewayMetaState.AsyncLucenePersistedState.class));
//generate random coordinationMetaData with different lastAcceptedConfiguration and lastCommittedConfiguration
CoordinationMetaData coordinationMetaData;
do {
coordinationMetaData = createCoordinationMetaData(randomNonNegativeLong());
} while (coordinationMetaData.getLastAcceptedConfiguration().equals(coordinationMetaData.getLastCommittedConfiguration()));
ClusterState state = createClusterState(randomNonNegativeLong(),
MetaData.builder().coordinationMetaData(coordinationMetaData)
.clusterUUID(randomAlphaOfLength(10)).build());
persistedState.setLastAcceptedState(state);
assertBusy(() -> assertTrue(gateway.allPendingAsyncStatesWritten()));
assertThat(persistedState.getLastAcceptedState().getLastAcceptedConfiguration(),
not(equalTo(persistedState.getLastAcceptedState().getLastCommittedConfiguration())));
CoordinationMetaData persistedCoordinationMetaData =
persistedClusterStateService.loadBestOnDiskState().metaData.coordinationMetaData();
assertThat(persistedCoordinationMetaData.getLastAcceptedConfiguration(),
equalTo(GatewayMetaState.AsyncLucenePersistedState.staleStateConfiguration));
assertThat(persistedCoordinationMetaData.getLastCommittedConfiguration(),
equalTo(GatewayMetaState.AsyncLucenePersistedState.staleStateConfiguration));
persistedState.markLastAcceptedStateAsCommitted();
assertBusy(() -> assertTrue(gateway.allPendingAsyncStatesWritten()));
CoordinationMetaData expectedCoordinationMetaData = CoordinationMetaData.builder(coordinationMetaData)
.lastCommittedConfiguration(coordinationMetaData.getLastAcceptedConfiguration()).build();
ClusterState expectedClusterState =
ClusterState.builder(state).metaData(MetaData.builder().coordinationMetaData(expectedCoordinationMetaData)
.clusterUUID(state.metaData().clusterUUID()).clusterUUIDCommitted(true).build()).build();
assertClusterStateEqual(expectedClusterState, persistedState.getLastAcceptedState());
persistedCoordinationMetaData = persistedClusterStateService.loadBestOnDiskState().metaData.coordinationMetaData();
assertThat(persistedCoordinationMetaData.getLastAcceptedConfiguration(),
equalTo(GatewayMetaState.AsyncLucenePersistedState.staleStateConfiguration));
assertThat(persistedCoordinationMetaData.getLastCommittedConfiguration(),
equalTo(GatewayMetaState.AsyncLucenePersistedState.staleStateConfiguration));
assertTrue(persistedClusterStateService.loadBestOnDiskState().metaData.clusterUUIDCommitted());
// generate a series of updates and check if batching works
final String indexName = randomAlphaOfLength(10);
long currentTerm = state.term();
for (int i = 0; i < 1000; i++) {
if (rarely()) {
// bump term
currentTerm = currentTerm + (rarely() ? randomIntBetween(1, 5) : 0L);
persistedState.setCurrentTerm(currentTerm);
} else {
// update cluster state
final int numberOfShards = randomIntBetween(1, 5);
final long term = Math.min(state.term() + (rarely() ? randomIntBetween(1, 5) : 0L), currentTerm);
final IndexMetaData indexMetaData = createIndexMetaData(indexName, numberOfShards, i);
state = createClusterState(state.version() + 1,
MetaData.builder().coordinationMetaData(createCoordinationMetaData(term)).put(indexMetaData, false).build());
persistedState.setLastAcceptedState(state);
}
}
assertEquals(currentTerm, persistedState.getCurrentTerm());
assertClusterStateEqual(state, persistedState.getLastAcceptedState());
assertBusy(() -> assertTrue(gateway.allPendingAsyncStatesWritten()));
gateway.close();
try (CoordinationState.PersistedState reloadedPersistedState = newGatewayPersistedState()) {
assertEquals(currentTerm, reloadedPersistedState.getCurrentTerm());
assertClusterStateEqual(GatewayMetaState.AsyncLucenePersistedState.resetVotingConfiguration(state),
reloadedPersistedState.getLastAcceptedState());
assertNotNull(reloadedPersistedState.getLastAcceptedState().metaData().index(indexName));
}
ThreadPool.terminate(threadPool, 10, TimeUnit.SECONDS);
}
public void testStatePersistenceWithIOIssues() throws IOException {
final AtomicReference<Double> ioExceptionRate = new AtomicReference<>(0.01d);
final List<MockDirectoryWrapper> list = new ArrayList<>();
final PersistedClusterStateService persistedClusterStateService =
new PersistedClusterStateService(nodeEnvironment, xContentRegistry(), BigArrays.NON_RECYCLING_INSTANCE) {
@Override
Directory createDirectory(Path path) {
final MockDirectoryWrapper wrapper = newMockFSDirectory(path);
wrapper.setAllowRandomFileNotFoundException(randomBoolean());
wrapper.setRandomIOExceptionRate(ioExceptionRate.get());
wrapper.setRandomIOExceptionRateOnOpen(ioExceptionRate.get());
list.add(wrapper);
return wrapper;
}
};
ClusterState state = createClusterState(randomNonNegativeLong(),
MetaData.builder().clusterUUID(randomAlphaOfLength(10)).build());
long currentTerm = 42L;
try (GatewayMetaState.LucenePersistedState persistedState = new GatewayMetaState.LucenePersistedState(
persistedClusterStateService, currentTerm, state)) {
try {
if (randomBoolean()) {
final ClusterState newState = createClusterState(randomNonNegativeLong(),
MetaData.builder().clusterUUID(randomAlphaOfLength(10)).build());
persistedState.setLastAcceptedState(newState);
state = newState;
} else {
final long newTerm = currentTerm + 1;
persistedState.setCurrentTerm(newTerm);
currentTerm = newTerm;
}
} catch (IOError | Exception e) {
assertNotNull(ExceptionsHelper.unwrap(e, IOException.class));
}
ioExceptionRate.set(0.0d);
for (MockDirectoryWrapper wrapper : list) {
wrapper.setRandomIOExceptionRate(ioExceptionRate.get());
wrapper.setRandomIOExceptionRateOnOpen(ioExceptionRate.get());
}
for (int i = 0; i < randomIntBetween(1, 5); i++) {
if (randomBoolean()) {
final long version = randomNonNegativeLong();
final String indexName = randomAlphaOfLength(10);
final IndexMetaData indexMetaData = createIndexMetaData(indexName, randomIntBetween(1, 5), randomNonNegativeLong());
final MetaData metaData = MetaData.builder().
persistentSettings(Settings.builder().put(randomAlphaOfLength(10), randomAlphaOfLength(10)).build()).
coordinationMetaData(createCoordinationMetaData(1L)).
put(indexMetaData, false).
build();
state = createClusterState(version, metaData);
persistedState.setLastAcceptedState(state);
} else {
currentTerm += 1;
persistedState.setCurrentTerm(currentTerm);
}
}
assertEquals(state, persistedState.getLastAcceptedState());
assertEquals(currentTerm, persistedState.getCurrentTerm());
} catch (IOError | Exception e) {
if (ioExceptionRate.get() == 0.0d) {
throw e;
}
assertNotNull(ExceptionsHelper.unwrap(e, IOException.class));
return;
}
nodeEnvironment.close();
// verify that the freshest state was rewritten to each data path
for (Path path : nodeEnvironment.nodeDataPaths()) {
Settings settings = Settings.builder()
.put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toAbsolutePath())
.put(Environment.PATH_DATA_SETTING.getKey(), path.getParent().getParent().toString()).build();
try (NodeEnvironment nodeEnvironment = new NodeEnvironment(settings, TestEnvironment.newEnvironment(settings))) {
final PersistedClusterStateService newPersistedClusterStateService =
new PersistedClusterStateService(nodeEnvironment, xContentRegistry(), BigArrays.NON_RECYCLING_INSTANCE);
final PersistedClusterStateService.OnDiskState onDiskState = newPersistedClusterStateService.loadBestOnDiskState();
assertFalse(onDiskState.empty());
assertThat(onDiskState.currentTerm, equalTo(currentTerm));
assertClusterStateEqual(state,
ClusterState.builder(ClusterName.DEFAULT)
.version(onDiskState.lastAcceptedVersion)
.metaData(onDiskState.metaData).build());
}
}
}
} }

View File

@ -173,7 +173,7 @@ public class IncrementalClusterStateWriterTests extends ESAllocationTestCase {
public void testGetRelevantIndicesWithUnassignedShardsOnMasterEligibleNode() { public void testGetRelevantIndicesWithUnassignedShardsOnMasterEligibleNode() {
IndexMetaData indexMetaData = createIndexMetaData("test"); IndexMetaData indexMetaData = createIndexMetaData("test");
Set<Index> indices = IncrementalClusterStateWriter.getRelevantIndices(clusterStateWithUnassignedIndex(indexMetaData, true)); Set<Index> indices = IncrementalClusterStateWriter.getRelevantIndices(clusterStateWithUnassignedIndex(indexMetaData, true));
assertThat(indices.size(), equalTo(1)); assertThat(indices.size(), equalTo(0));
} }
public void testGetRelevantIndicesWithUnassignedShardsOnDataOnlyNode() { public void testGetRelevantIndicesWithUnassignedShardsOnDataOnlyNode() {

View File

@ -21,10 +21,11 @@ package org.elasticsearch.gateway;
import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse; import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse;
import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse; import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse;
import org.elasticsearch.cluster.coordination.Coordinator;
import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.common.collect.ImmutableOpenMap; import org.elasticsearch.common.collect.ImmutableOpenMap;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.discovery.Discovery;
import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.env.NodeEnvironment;
import org.elasticsearch.index.Index; import org.elasticsearch.index.Index;
import org.elasticsearch.test.ESIntegTestCase; import org.elasticsearch.test.ESIntegTestCase;
@ -55,8 +56,8 @@ public class MetaDataWriteDataNodesIT extends ESIntegTestCase {
assertIndexInMetaState(masterNode, "test"); assertIndexInMetaState(masterNode, "test");
} }
public void testMetaIsRemovedIfAllShardsFromIndexRemoved() throws Exception { public void testIndexFilesAreRemovedIfAllShardsFromIndexRemoved() throws Exception {
// this test checks that the index state is removed from a data only node once all shards have been allocated away from it // this test checks that the index data is removed from a data only node once all shards have been allocated away from it
String masterNode = internalCluster().startMasterOnlyNode(Settings.EMPTY); String masterNode = internalCluster().startMasterOnlyNode(Settings.EMPTY);
List<String> nodeNames= internalCluster().startDataOnlyNodes(2); List<String> nodeNames= internalCluster().startDataOnlyNodes(2);
String node1 = nodeNames.get(0); String node1 = nodeNames.get(0);
@ -69,8 +70,10 @@ public class MetaDataWriteDataNodesIT extends ESIntegTestCase {
ensureGreen(); ensureGreen();
assertIndexInMetaState(node1, index); assertIndexInMetaState(node1, index);
Index resolveIndex = resolveIndex(index); Index resolveIndex = resolveIndex(index);
assertIndexDirectoryExists(node1, resolveIndex);
assertIndexDirectoryDeleted(node2, resolveIndex); assertIndexDirectoryDeleted(node2, resolveIndex);
assertIndexInMetaState(masterNode, index); assertIndexInMetaState(masterNode, index);
assertIndexDirectoryDeleted(masterNode, resolveIndex);
logger.debug("relocating index..."); logger.debug("relocating index...");
client().admin().indices().prepareUpdateSettings(index).setSettings(Settings.builder() client().admin().indices().prepareUpdateSettings(index).setSettings(Settings.builder()
@ -79,7 +82,13 @@ public class MetaDataWriteDataNodesIT extends ESIntegTestCase {
ensureGreen(); ensureGreen();
assertIndexDirectoryDeleted(node1, resolveIndex); assertIndexDirectoryDeleted(node1, resolveIndex);
assertIndexInMetaState(node2, index); assertIndexInMetaState(node2, index);
assertIndexDirectoryExists(node2, resolveIndex);
assertIndexInMetaState(masterNode, index); assertIndexInMetaState(masterNode, index);
assertIndexDirectoryDeleted(masterNode, resolveIndex);
client().admin().indices().prepareDelete(index).get();
assertIndexDirectoryDeleted(node1, resolveIndex);
assertIndexDirectoryDeleted(node2, resolveIndex);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -156,17 +165,19 @@ public class MetaDataWriteDataNodesIT extends ESIntegTestCase {
} }
protected void assertIndexDirectoryDeleted(final String nodeName, final Index index) throws Exception { protected void assertIndexDirectoryDeleted(final String nodeName, final Index index) throws Exception {
assertBusy(() -> { assertBusy(() -> assertFalse("Expecting index directory of " + index + " to be deleted from node " + nodeName,
logger.info("checking if index directory exists..."); indexDirectoryExists(nodeName, index))
assertFalse("Expecting index directory of " + index + " to be deleted from node " + nodeName, );
indexDirectoryExists(nodeName, index));
} }
protected void assertIndexDirectoryExists(final String nodeName, final Index index) throws Exception {
assertBusy(() -> assertTrue("Expecting index directory of " + index + " to exist on node " + nodeName,
indexDirectoryExists(nodeName, index))
); );
} }
protected void assertIndexInMetaState(final String nodeName, final String indexName) throws Exception { protected void assertIndexInMetaState(final String nodeName, final String indexName) throws Exception {
assertBusy(() -> { assertBusy(() -> {
logger.info("checking if meta state exists...");
try { try {
assertTrue("Expecting meta state of index " + indexName + " to be on node " + nodeName, assertTrue("Expecting meta state of index " + indexName + " to be on node " + nodeName,
getIndicesMetaDataOnNode(nodeName).containsKey(indexName)); getIndicesMetaDataOnNode(nodeName).containsKey(indexName));
@ -190,8 +201,7 @@ public class MetaDataWriteDataNodesIT extends ESIntegTestCase {
} }
private ImmutableOpenMap<String, IndexMetaData> getIndicesMetaDataOnNode(String nodeName) { private ImmutableOpenMap<String, IndexMetaData> getIndicesMetaDataOnNode(String nodeName) {
GatewayMetaState nodeMetaState = ((InternalTestCluster) cluster()).getInstance(GatewayMetaState.class, nodeName); final Coordinator coordinator = (Coordinator) internalCluster().getInstance(Discovery.class, nodeName);
MetaData nodeMetaData = nodeMetaState.getMetaData(); return coordinator.getApplierState().getMetaData().getIndices();
return nodeMetaData.getIndices();
} }
} }

View File

@ -155,7 +155,7 @@ public class MetaStateServiceTests extends ESTestCase {
assertThat(loadedMetaData.index("test1"), equalTo(index)); assertThat(loadedMetaData.index("test1"), equalTo(index));
} }
public void testLoadFullStateAndUpdate() throws IOException { public void testLoadFullStateAndUpdateAndClean() throws IOException {
IndexMetaData index = indexMetaData("test1"); IndexMetaData index = indexMetaData("test1");
MetaData metaData = MetaData.builder() MetaData metaData = MetaData.builder()
.persistentSettings(Settings.builder().put("test1", "value1").build()) .persistentSettings(Settings.builder().put("test1", "value1").build())
@ -201,5 +201,15 @@ public class MetaStateServiceTests extends ESTestCase {
assertThat(loadedMetaData.persistentSettings(), equalTo(newMetaData.persistentSettings())); assertThat(loadedMetaData.persistentSettings(), equalTo(newMetaData.persistentSettings()));
assertThat(loadedMetaData.hasIndex("test1"), equalTo(true)); assertThat(loadedMetaData.hasIndex("test1"), equalTo(true));
assertThat(loadedMetaData.index("test1"), equalTo(index)); assertThat(loadedMetaData.index("test1"), equalTo(index));
if (randomBoolean()) {
metaStateService.unreferenceAll();
} else {
metaStateService.deleteAll();
}
manifestAndMetaData = metaStateService.loadFullState();
assertTrue(manifestAndMetaData.v1().isEmpty());
metaData = manifestAndMetaData.v2();
assertTrue(MetaData.isGlobalStateEquals(metaData, MetaData.EMPTY_META_DATA));
} }
} }

View File

@ -0,0 +1,793 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.gateway;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FilterDirectory;
import org.apache.lucene.store.IOContext;
import org.apache.lucene.store.IndexOutput;
import org.apache.lucene.store.SimpleFSDirectory;
import org.elasticsearch.Version;
import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.coordination.CoordinationMetaData;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.common.UUIDs;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.common.util.MockBigArrays;
import org.elasticsearch.common.util.MockPageCacheRecycler;
import org.elasticsearch.core.internal.io.IOUtils;
import org.elasticsearch.env.Environment;
import org.elasticsearch.env.NodeEnvironment;
import org.elasticsearch.env.NodeMetaData;
import org.elasticsearch.gateway.PersistedClusterStateService.Writer;
import org.elasticsearch.index.Index;
import org.elasticsearch.indices.breaker.NoneCircuitBreakerService;
import org.elasticsearch.test.ESTestCase;
import java.io.IOError;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.nullValue;
public class PersistedClusterStateServiceTests extends ESTestCase {
private PersistedClusterStateService newPersistedClusterStateService(NodeEnvironment nodeEnvironment) {
return new PersistedClusterStateService(nodeEnvironment, xContentRegistry(),
usually()
? BigArrays.NON_RECYCLING_INSTANCE
: new MockBigArrays(new MockPageCacheRecycler(Settings.EMPTY), new NoneCircuitBreakerService()));
}
public void testPersistsAndReloadsTerm() throws IOException {
try (NodeEnvironment nodeEnvironment = newNodeEnvironment(createDataPaths())) {
final PersistedClusterStateService persistedClusterStateService = newPersistedClusterStateService(nodeEnvironment);
final long newTerm = randomNonNegativeLong();
assertThat(persistedClusterStateService.loadBestOnDiskState().currentTerm, equalTo(0L));
try (Writer writer = persistedClusterStateService.createWriter()) {
writer.writeFullStateAndCommit(newTerm, ClusterState.EMPTY_STATE);
assertThat(persistedClusterStateService.loadBestOnDiskState().currentTerm, equalTo(newTerm));
}
assertThat(persistedClusterStateService.loadBestOnDiskState().currentTerm, equalTo(newTerm));
}
}
public void testPersistsAndReloadsGlobalMetadata() throws IOException {
try (NodeEnvironment nodeEnvironment = newNodeEnvironment(createDataPaths())) {
final PersistedClusterStateService persistedClusterStateService = newPersistedClusterStateService(nodeEnvironment);
final String clusterUUID = UUIDs.randomBase64UUID(random());
final long version = randomLongBetween(1L, Long.MAX_VALUE);
ClusterState clusterState = loadPersistedClusterState(persistedClusterStateService);
try (Writer writer = persistedClusterStateService.createWriter()) {
writer.writeFullStateAndCommit(0L, ClusterState.builder(clusterState)
.metaData(MetaData.builder(clusterState.metaData())
.clusterUUID(clusterUUID)
.clusterUUIDCommitted(true)
.version(version))
.incrementVersion().build());
clusterState = loadPersistedClusterState(persistedClusterStateService);
assertThat(clusterState.metaData().clusterUUID(), equalTo(clusterUUID));
assertTrue(clusterState.metaData().clusterUUIDCommitted());
assertThat(clusterState.metaData().version(), equalTo(version));
}
try (Writer writer = persistedClusterStateService.createWriter()) {
writer.writeFullStateAndCommit(0L, ClusterState.builder(clusterState)
.metaData(MetaData.builder(clusterState.metaData())
.clusterUUID(clusterUUID)
.clusterUUIDCommitted(true)
.version(version + 1))
.incrementVersion().build());
}
clusterState = loadPersistedClusterState(persistedClusterStateService);
assertThat(clusterState.metaData().clusterUUID(), equalTo(clusterUUID));
assertTrue(clusterState.metaData().clusterUUIDCommitted());
assertThat(clusterState.metaData().version(), equalTo(version + 1));
}
}
private static void writeState(Writer writer, long currentTerm, ClusterState clusterState,
ClusterState previousState) throws IOException {
if (randomBoolean() || clusterState.term() != previousState.term() || writer.fullStateWritten == false) {
writer.writeFullStateAndCommit(currentTerm, clusterState);
} else {
writer.writeIncrementalStateAndCommit(currentTerm, previousState, clusterState);
}
}
public void testLoadsFreshestState() throws IOException {
final Path[] dataPaths = createDataPaths();
final long freshTerm = randomLongBetween(1L, Long.MAX_VALUE);
final long staleTerm = randomBoolean() ? freshTerm : randomLongBetween(1L, freshTerm);
final long freshVersion = randomLongBetween(2L, Long.MAX_VALUE);
final long staleVersion = staleTerm == freshTerm ? randomLongBetween(1L, freshVersion - 1) : randomLongBetween(1L, Long.MAX_VALUE);
final HashSet<Path> unimportantPaths = Arrays.stream(dataPaths).collect(Collectors.toCollection(HashSet::new));
try (NodeEnvironment nodeEnvironment = newNodeEnvironment(dataPaths)) {
final ClusterState clusterState = loadPersistedClusterState(newPersistedClusterStateService(nodeEnvironment));
try (Writer writer = newPersistedClusterStateService(nodeEnvironment).createWriter()) {
writeState(writer, staleTerm,
ClusterState.builder(clusterState).version(staleVersion)
.metaData(MetaData.builder(clusterState.metaData()).coordinationMetaData(
CoordinationMetaData.builder(clusterState.coordinationMetaData()).term(staleTerm).build())).build(),
clusterState);
}
}
final Path freshPath = randomFrom(dataPaths);
try (NodeEnvironment nodeEnvironment = newNodeEnvironment(new Path[]{freshPath})) {
unimportantPaths.remove(freshPath);
try (Writer writer = newPersistedClusterStateService(nodeEnvironment).createWriter()) {
final ClusterState clusterState = loadPersistedClusterState(newPersistedClusterStateService(nodeEnvironment));
writeState(writer, freshTerm,
ClusterState.builder(clusterState).version(freshVersion)
.metaData(MetaData.builder(clusterState.metaData()).coordinationMetaData(
CoordinationMetaData.builder(clusterState.coordinationMetaData()).term(freshTerm).build())).build(),
clusterState);
}
}
if (randomBoolean() && unimportantPaths.isEmpty() == false) {
IOUtils.rm(randomFrom(unimportantPaths));
}
// verify that the freshest state is chosen
try (NodeEnvironment nodeEnvironment = newNodeEnvironment(dataPaths)) {
final PersistedClusterStateService.OnDiskState onDiskState = newPersistedClusterStateService(nodeEnvironment)
.loadBestOnDiskState();
final ClusterState clusterState = clusterStateFromMetadata(onDiskState.lastAcceptedVersion, onDiskState.metaData);
assertThat(clusterState.term(), equalTo(freshTerm));
assertThat(clusterState.version(), equalTo(freshVersion));
}
}
public void testFailsOnMismatchedNodeIds() throws IOException {
final Path[] dataPaths1 = createDataPaths();
final Path[] dataPaths2 = createDataPaths();
final String[] nodeIds = new String[2];
try (NodeEnvironment nodeEnvironment = newNodeEnvironment(dataPaths1)) {
nodeIds[0] = nodeEnvironment.nodeId();
try (Writer writer = newPersistedClusterStateService(nodeEnvironment).createWriter()) {
final ClusterState clusterState = loadPersistedClusterState(newPersistedClusterStateService(nodeEnvironment));
writer.writeFullStateAndCommit(0L,
ClusterState.builder(clusterState).version(randomLongBetween(1L, Long.MAX_VALUE)).build());
}
}
try (NodeEnvironment nodeEnvironment = newNodeEnvironment(dataPaths2)) {
nodeIds[1] = nodeEnvironment.nodeId();
try (Writer writer = newPersistedClusterStateService(nodeEnvironment).createWriter()) {
final ClusterState clusterState = loadPersistedClusterState(newPersistedClusterStateService(nodeEnvironment));
writer.writeFullStateAndCommit(0L,
ClusterState.builder(clusterState).version(randomLongBetween(1L, Long.MAX_VALUE)).build());
}
}
NodeMetaData.FORMAT.cleanupOldFiles(Long.MAX_VALUE, dataPaths2);
final Path[] combinedPaths = Stream.concat(Arrays.stream(dataPaths1), Arrays.stream(dataPaths2)).toArray(Path[]::new);
final String failure = expectThrows(IllegalStateException.class, () -> newNodeEnvironment(combinedPaths)).getMessage();
assertThat(failure,
allOf(containsString("unexpected node ID in metadata"), containsString(nodeIds[0]), containsString(nodeIds[1])));
assertTrue("[" + failure + "] should match " + Arrays.toString(dataPaths2),
Arrays.stream(dataPaths2).anyMatch(p -> failure.contains(p.toString())));
// verify that loadBestOnDiskState has same check
final String message = expectThrows(IllegalStateException.class,
() -> new PersistedClusterStateService(Stream.of(combinedPaths).map(path -> NodeEnvironment.resolveNodePath(path, 0))
.toArray(Path[]::new), nodeIds[0], xContentRegistry(), BigArrays.NON_RECYCLING_INSTANCE,
randomBoolean()).loadBestOnDiskState()).getMessage();
assertThat(message,
allOf(containsString("unexpected node ID in metadata"), containsString(nodeIds[0]), containsString(nodeIds[1])));
assertTrue("[" + message + "] should match " + Arrays.toString(dataPaths2),
Arrays.stream(dataPaths2).anyMatch(p -> message.contains(p.toString())));
}
public void testFailsOnMismatchedCommittedClusterUUIDs() throws IOException {
final Path[] dataPaths1 = createDataPaths();
final Path[] dataPaths2 = createDataPaths();
final Path[] combinedPaths = Stream.concat(Arrays.stream(dataPaths1), Arrays.stream(dataPaths2)).toArray(Path[]::new);
final String clusterUUID1 = UUIDs.randomBase64UUID(random());
final String clusterUUID2 = UUIDs.randomBase64UUID(random());
// first establish consistent node IDs and write initial metadata
try (NodeEnvironment nodeEnvironment = newNodeEnvironment(combinedPaths)) {
try (Writer writer = newPersistedClusterStateService(nodeEnvironment).createWriter()) {
final ClusterState clusterState = loadPersistedClusterState(newPersistedClusterStateService(nodeEnvironment));
assertFalse(clusterState.metaData().clusterUUIDCommitted());
writer.writeFullStateAndCommit(0L, clusterState);
}
}
try (NodeEnvironment nodeEnvironment = newNodeEnvironment(dataPaths1)) {
try (Writer writer = newPersistedClusterStateService(nodeEnvironment).createWriter()) {
final ClusterState clusterState = loadPersistedClusterState(newPersistedClusterStateService(nodeEnvironment));
assertFalse(clusterState.metaData().clusterUUIDCommitted());
writer.writeFullStateAndCommit(0L, ClusterState.builder(clusterState)
.metaData(MetaData.builder(clusterState.metaData())
.clusterUUID(clusterUUID1)
.clusterUUIDCommitted(true)
.version(1))
.incrementVersion().build());
}
}
try (NodeEnvironment nodeEnvironment = newNodeEnvironment(dataPaths2)) {
try (Writer writer = newPersistedClusterStateService(nodeEnvironment).createWriter()) {
final ClusterState clusterState = loadPersistedClusterState(newPersistedClusterStateService(nodeEnvironment));
assertFalse(clusterState.metaData().clusterUUIDCommitted());
writer.writeFullStateAndCommit(0L, ClusterState.builder(clusterState)
.metaData(MetaData.builder(clusterState.metaData())
.clusterUUID(clusterUUID2)
.clusterUUIDCommitted(true)
.version(1))
.incrementVersion().build());
}
}
try (NodeEnvironment nodeEnvironment = newNodeEnvironment(combinedPaths)) {
final String message = expectThrows(IllegalStateException.class,
() -> newPersistedClusterStateService(nodeEnvironment).loadBestOnDiskState()).getMessage();
assertThat(message,
allOf(containsString("mismatched cluster UUIDs in metadata"), containsString(clusterUUID1), containsString(clusterUUID2)));
assertTrue("[" + message + "] should match " + Arrays.toString(dataPaths1),
Arrays.stream(dataPaths1).anyMatch(p -> message.contains(p.toString())));
assertTrue("[" + message + "] should match " + Arrays.toString(dataPaths2),
Arrays.stream(dataPaths2).anyMatch(p -> message.contains(p.toString())));
}
}
public void testFailsIfFreshestStateIsInStaleTerm() throws IOException {
final Path[] dataPaths1 = createDataPaths();
final Path[] dataPaths2 = createDataPaths();
final Path[] combinedPaths = Stream.concat(Arrays.stream(dataPaths1), Arrays.stream(dataPaths2)).toArray(Path[]::new);
final long staleCurrentTerm = randomLongBetween(1L, Long.MAX_VALUE - 1);
final long freshCurrentTerm = randomLongBetween(staleCurrentTerm + 1, Long.MAX_VALUE);
final long freshTerm = randomLongBetween(1L, Long.MAX_VALUE);
final long staleTerm = randomBoolean() ? freshTerm : randomLongBetween(1L, freshTerm);
final long freshVersion = randomLongBetween(2L, Long.MAX_VALUE);
final long staleVersion = staleTerm == freshTerm ? randomLongBetween(1L, freshVersion - 1) : randomLongBetween(1L, Long.MAX_VALUE);
try (NodeEnvironment nodeEnvironment = newNodeEnvironment(combinedPaths)) {
try (Writer writer = newPersistedClusterStateService(nodeEnvironment).createWriter()) {
final ClusterState clusterState = loadPersistedClusterState(newPersistedClusterStateService(nodeEnvironment));
assertFalse(clusterState.metaData().clusterUUIDCommitted());
writeState(writer, staleCurrentTerm, ClusterState.builder(clusterState)
.metaData(MetaData.builder(clusterState.metaData()).version(1)
.coordinationMetaData(CoordinationMetaData.builder(clusterState.coordinationMetaData()).term(staleTerm).build()))
.version(staleVersion)
.build(),
clusterState);
}
}
try (NodeEnvironment nodeEnvironment = newNodeEnvironment(dataPaths1)) {
try (Writer writer = newPersistedClusterStateService(nodeEnvironment).createWriter()) {
final ClusterState clusterState = loadPersistedClusterState(newPersistedClusterStateService(nodeEnvironment));
writeState(writer, freshCurrentTerm, clusterState, clusterState);
}
}
try (NodeEnvironment nodeEnvironment = newNodeEnvironment(dataPaths2)) {
try (Writer writer = newPersistedClusterStateService(nodeEnvironment).createWriter()) {
final PersistedClusterStateService.OnDiskState onDiskState = newPersistedClusterStateService(nodeEnvironment)
.loadBestOnDiskState();
final ClusterState clusterState = clusterStateFromMetadata(onDiskState.lastAcceptedVersion, onDiskState.metaData);
writeState(writer, onDiskState.currentTerm, ClusterState.builder(clusterState)
.metaData(MetaData.builder(clusterState.metaData()).version(2)
.coordinationMetaData(CoordinationMetaData.builder(clusterState.coordinationMetaData()).term(freshTerm).build()))
.version(freshVersion)
.build(), clusterState);
}
}
try (NodeEnvironment nodeEnvironment = newNodeEnvironment(combinedPaths)) {
final String message = expectThrows(IllegalStateException.class,
() -> newPersistedClusterStateService(nodeEnvironment).loadBestOnDiskState()).getMessage();
assertThat(message, allOf(
containsString("inconsistent terms found"),
containsString(Long.toString(staleCurrentTerm)),
containsString(Long.toString(freshCurrentTerm))));
assertTrue("[" + message + "] should match " + Arrays.toString(dataPaths1),
Arrays.stream(dataPaths1).anyMatch(p -> message.contains(p.toString())));
assertTrue("[" + message + "] should match " + Arrays.toString(dataPaths2),
Arrays.stream(dataPaths2).anyMatch(p -> message.contains(p.toString())));
}
}
public void testFailsGracefullyOnExceptionDuringFlush() throws IOException {
final AtomicBoolean throwException = new AtomicBoolean();
try (NodeEnvironment nodeEnvironment = newNodeEnvironment(createDataPaths())) {
final PersistedClusterStateService persistedClusterStateService
= new PersistedClusterStateService(nodeEnvironment, xContentRegistry(), BigArrays.NON_RECYCLING_INSTANCE) {
@Override
Directory createDirectory(Path path) throws IOException {
return new FilterDirectory(super.createDirectory(path)) {
@Override
public IndexOutput createOutput(String name, IOContext context) throws IOException {
if (throwException.get()) {
throw new IOException("simulated");
}
return super.createOutput(name, context);
}
};
}
};
try (Writer writer = persistedClusterStateService.createWriter()) {
final ClusterState clusterState = loadPersistedClusterState(persistedClusterStateService);
final long newTerm = randomNonNegativeLong();
final ClusterState newState = ClusterState.builder(clusterState)
.metaData(MetaData.builder(clusterState.metaData())
.clusterUUID(UUIDs.randomBase64UUID(random()))
.clusterUUIDCommitted(true)
.version(randomLongBetween(1L, Long.MAX_VALUE)))
.incrementVersion().build();
throwException.set(true);
assertThat(expectThrows(IOException.class, () ->
writeState(writer, newTerm, newState, clusterState)).getMessage(),
containsString("simulated"));
}
}
}
public void testClosesWriterOnFatalError() throws IOException {
final AtomicBoolean throwException = new AtomicBoolean();
try (NodeEnvironment nodeEnvironment = newNodeEnvironment(createDataPaths())) {
final PersistedClusterStateService persistedClusterStateService
= new PersistedClusterStateService(nodeEnvironment, xContentRegistry(), BigArrays.NON_RECYCLING_INSTANCE) {
@Override
Directory createDirectory(Path path) throws IOException {
return new FilterDirectory(super.createDirectory(path)) {
@Override
public void sync(Collection<String> names) {
throw new OutOfMemoryError("simulated");
}
};
}
};
try (Writer writer = persistedClusterStateService.createWriter()) {
final ClusterState clusterState = loadPersistedClusterState(persistedClusterStateService);
final long newTerm = randomNonNegativeLong();
final ClusterState newState = ClusterState.builder(clusterState)
.metaData(MetaData.builder(clusterState.metaData())
.clusterUUID(UUIDs.randomBase64UUID(random()))
.clusterUUIDCommitted(true)
.version(randomLongBetween(1L, Long.MAX_VALUE)))
.incrementVersion().build();
throwException.set(true);
assertThat(expectThrows(OutOfMemoryError.class, () -> {
if (randomBoolean()) {
writeState(writer, newTerm, newState, clusterState);
} else {
writer.commit(newTerm, newState.version());
}
}).getMessage(),
containsString("simulated"));
assertFalse(writer.isOpen());
}
// check if we can open writer again
try (Writer ignored = persistedClusterStateService.createWriter()) {
}
}
}
public void testCrashesWithIOErrorOnCommitFailure() throws IOException {
final AtomicBoolean throwException = new AtomicBoolean();
try (NodeEnvironment nodeEnvironment = newNodeEnvironment(createDataPaths())) {
final PersistedClusterStateService persistedClusterStateService
= new PersistedClusterStateService(nodeEnvironment, xContentRegistry(), BigArrays.NON_RECYCLING_INSTANCE) {
@Override
Directory createDirectory(Path path) throws IOException {
return new FilterDirectory(super.createDirectory(path)) {
@Override
public void rename(String source, String dest) throws IOException {
if (throwException.get() && dest.startsWith("segments")) {
throw new IOException("simulated");
}
}
};
}
};
try (Writer writer = persistedClusterStateService.createWriter()) {
final ClusterState clusterState = loadPersistedClusterState(persistedClusterStateService);
final long newTerm = randomNonNegativeLong();
final ClusterState newState = ClusterState.builder(clusterState)
.metaData(MetaData.builder(clusterState.metaData())
.clusterUUID(UUIDs.randomBase64UUID(random()))
.clusterUUIDCommitted(true)
.version(randomLongBetween(1L, Long.MAX_VALUE)))
.incrementVersion().build();
throwException.set(true);
assertThat(expectThrows(IOError.class, () -> {
if (randomBoolean()) {
writeState(writer, newTerm, newState, clusterState);
} else {
writer.commit(newTerm, newState.version());
}
}).getMessage(),
containsString("simulated"));
assertFalse(writer.isOpen());
}
// check if we can open writer again
try (Writer ignored = persistedClusterStateService.createWriter()) {
}
}
}
public void testFailsIfGlobalMetadataIsMissing() throws IOException {
// if someone attempted surgery on the metadata index by hand, e.g. deleting broken segments, then maybe the global metadata
// isn't there any more
try (NodeEnvironment nodeEnvironment = newNodeEnvironment(createDataPaths())) {
try (Writer writer = newPersistedClusterStateService(nodeEnvironment).createWriter()) {
final ClusterState clusterState = loadPersistedClusterState(newPersistedClusterStateService(nodeEnvironment));
writeState(writer, 0L, ClusterState.builder(clusterState).version(randomLongBetween(1L, Long.MAX_VALUE)).build(),
clusterState);
}
final Path brokenPath = randomFrom(nodeEnvironment.nodeDataPaths());
try (Directory directory = new SimpleFSDirectory(brokenPath.resolve(PersistedClusterStateService.METADATA_DIRECTORY_NAME))) {
final IndexWriterConfig indexWriterConfig = new IndexWriterConfig();
indexWriterConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE);
try (IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig)) {
indexWriter.commit();
}
}
final String message = expectThrows(IllegalStateException.class,
() -> newPersistedClusterStateService(nodeEnvironment).loadBestOnDiskState()).getMessage();
assertThat(message, allOf(containsString("no global metadata found"), containsString(brokenPath.toString())));
}
}
public void testFailsIfGlobalMetadataIsDuplicated() throws IOException {
// if someone attempted surgery on the metadata index by hand, e.g. deleting broken segments, then maybe the global metadata
// is duplicated
final Path[] dataPaths1 = createDataPaths();
final Path[] dataPaths2 = createDataPaths();
final Path[] combinedPaths = Stream.concat(Arrays.stream(dataPaths1), Arrays.stream(dataPaths2)).toArray(Path[]::new);
try (NodeEnvironment nodeEnvironment = newNodeEnvironment(combinedPaths)) {
try (Writer writer = newPersistedClusterStateService(nodeEnvironment).createWriter()) {
final ClusterState clusterState = loadPersistedClusterState(newPersistedClusterStateService(nodeEnvironment));
writeState(writer, 0L, ClusterState.builder(clusterState).version(randomLongBetween(1L, Long.MAX_VALUE)).build(),
clusterState);
}
final Path brokenPath = randomFrom(nodeEnvironment.nodeDataPaths());
final Path dupPath = randomValueOtherThan(brokenPath, () -> randomFrom(nodeEnvironment.nodeDataPaths()));
try (Directory directory = new SimpleFSDirectory(brokenPath.resolve(PersistedClusterStateService.METADATA_DIRECTORY_NAME));
Directory dupDirectory = new SimpleFSDirectory(dupPath.resolve(PersistedClusterStateService.METADATA_DIRECTORY_NAME))) {
try (IndexWriter indexWriter = new IndexWriter(directory, new IndexWriterConfig())) {
indexWriter.addIndexes(dupDirectory);
indexWriter.commit();
}
}
final String message = expectThrows(IllegalStateException.class,
() -> newPersistedClusterStateService(nodeEnvironment).loadBestOnDiskState()).getMessage();
assertThat(message, allOf(containsString("duplicate global metadata found"), containsString(brokenPath.toString())));
}
}
public void testFailsIfIndexMetadataIsDuplicated() throws IOException {
// if someone attempted surgery on the metadata index by hand, e.g. deleting broken segments, then maybe some index metadata
// is duplicated
final Path[] dataPaths1 = createDataPaths();
final Path[] dataPaths2 = createDataPaths();
final Path[] combinedPaths = Stream.concat(Arrays.stream(dataPaths1), Arrays.stream(dataPaths2)).toArray(Path[]::new);
try (NodeEnvironment nodeEnvironment = newNodeEnvironment(combinedPaths)) {
final String indexUUID = UUIDs.randomBase64UUID(random());
final String indexName = randomAlphaOfLength(10);
try (Writer writer = newPersistedClusterStateService(nodeEnvironment).createWriter()) {
final ClusterState clusterState = loadPersistedClusterState(newPersistedClusterStateService(nodeEnvironment));
writeState(writer, 0L, ClusterState.builder(clusterState)
.metaData(MetaData.builder(clusterState.metaData())
.version(1L)
.coordinationMetaData(CoordinationMetaData.builder(clusterState.coordinationMetaData()).term(1L).build())
.put(IndexMetaData.builder(indexName)
.version(1L)
.settings(Settings.builder()
.put(IndexMetaData.INDEX_NUMBER_OF_SHARDS_SETTING.getKey(), 1)
.put(IndexMetaData.INDEX_NUMBER_OF_REPLICAS_SETTING.getKey(), 0)
.put(IndexMetaData.SETTING_INDEX_VERSION_CREATED.getKey(), Version.CURRENT)
.put(IndexMetaData.SETTING_INDEX_UUID, indexUUID))))
.incrementVersion().build(),
clusterState);
}
final Path brokenPath = randomFrom(nodeEnvironment.nodeDataPaths());
final Path dupPath = randomValueOtherThan(brokenPath, () -> randomFrom(nodeEnvironment.nodeDataPaths()));
try (Directory directory = new SimpleFSDirectory(brokenPath.resolve(PersistedClusterStateService.METADATA_DIRECTORY_NAME));
Directory dupDirectory = new SimpleFSDirectory(dupPath.resolve(PersistedClusterStateService.METADATA_DIRECTORY_NAME))) {
try (IndexWriter indexWriter = new IndexWriter(directory, new IndexWriterConfig())) {
indexWriter.deleteDocuments(new Term("type", "global")); // do not duplicate global metadata
indexWriter.addIndexes(dupDirectory);
indexWriter.commit();
}
}
final String message = expectThrows(IllegalStateException.class,
() -> newPersistedClusterStateService(nodeEnvironment).loadBestOnDiskState()).getMessage();
assertThat(message, allOf(
containsString("duplicate metadata found"),
containsString(brokenPath.toString()),
containsString(indexName),
containsString(indexUUID)));
}
}
public void testPersistsAndReloadsIndexMetadataIffVersionOrTermChanges() throws IOException {
try (NodeEnvironment nodeEnvironment = newNodeEnvironment(createDataPaths())) {
final PersistedClusterStateService persistedClusterStateService = newPersistedClusterStateService(nodeEnvironment);
final long globalVersion = randomLongBetween(1L, Long.MAX_VALUE);
final String indexUUID = UUIDs.randomBase64UUID(random());
final long indexMetaDataVersion = randomLongBetween(1L, Long.MAX_VALUE);
final long oldTerm = randomLongBetween(1L, Long.MAX_VALUE - 1);
final long newTerm = randomLongBetween(oldTerm + 1, Long.MAX_VALUE);
try (Writer writer = persistedClusterStateService.createWriter()) {
ClusterState clusterState = loadPersistedClusterState(persistedClusterStateService);
writeState(writer, 0L, ClusterState.builder(clusterState)
.metaData(MetaData.builder(clusterState.metaData())
.version(globalVersion)
.coordinationMetaData(CoordinationMetaData.builder(clusterState.coordinationMetaData()).term(oldTerm).build())
.put(IndexMetaData.builder("test")
.version(indexMetaDataVersion - 1) // -1 because it's incremented in .put()
.settings(Settings.builder()
.put(IndexMetaData.INDEX_NUMBER_OF_SHARDS_SETTING.getKey(), 1)
.put(IndexMetaData.INDEX_NUMBER_OF_REPLICAS_SETTING.getKey(), 0)
.put(IndexMetaData.SETTING_INDEX_VERSION_CREATED.getKey(), Version.CURRENT)
.put(IndexMetaData.SETTING_INDEX_UUID, indexUUID))))
.incrementVersion().build(),
clusterState);
clusterState = loadPersistedClusterState(persistedClusterStateService);
IndexMetaData indexMetaData = clusterState.metaData().index("test");
assertThat(indexMetaData.getIndexUUID(), equalTo(indexUUID));
assertThat(indexMetaData.getVersion(), equalTo(indexMetaDataVersion));
assertThat(IndexMetaData.INDEX_NUMBER_OF_REPLICAS_SETTING.get(indexMetaData.getSettings()), equalTo(0));
// ensure we do not wastefully persist the same index metadata version by making a bad update with the same version
writer.writeIncrementalStateAndCommit(0L, clusterState, ClusterState.builder(clusterState)
.metaData(MetaData.builder(clusterState.metaData())
.put(IndexMetaData.builder(indexMetaData).settings(Settings.builder()
.put(indexMetaData.getSettings())
.put(IndexMetaData.INDEX_NUMBER_OF_REPLICAS_SETTING.getKey(), 1)).build(), false))
.incrementVersion().build());
clusterState = loadPersistedClusterState(persistedClusterStateService);
indexMetaData = clusterState.metaData().index("test");
assertThat(indexMetaData.getIndexUUID(), equalTo(indexUUID));
assertThat(indexMetaData.getVersion(), equalTo(indexMetaDataVersion));
assertThat(IndexMetaData.INDEX_NUMBER_OF_REPLICAS_SETTING.get(indexMetaData.getSettings()), equalTo(0));
// ensure that we do persist the same index metadata version by making an update with a higher version
writeState(writer, 0L, ClusterState.builder(clusterState)
.metaData(MetaData.builder(clusterState.metaData())
.put(IndexMetaData.builder(indexMetaData).settings(Settings.builder()
.put(indexMetaData.getSettings())
.put(IndexMetaData.INDEX_NUMBER_OF_REPLICAS_SETTING.getKey(), 2)).build(), true))
.incrementVersion().build(),
clusterState);
clusterState = loadPersistedClusterState(persistedClusterStateService);
indexMetaData = clusterState.metaData().index("test");
assertThat(indexMetaData.getVersion(), equalTo(indexMetaDataVersion + 1));
assertThat(IndexMetaData.INDEX_NUMBER_OF_REPLICAS_SETTING.get(indexMetaData.getSettings()), equalTo(2));
// ensure that we also persist the index metadata when the term changes
writeState(writer, 0L, ClusterState.builder(clusterState)
.metaData(MetaData.builder(clusterState.metaData())
.coordinationMetaData(CoordinationMetaData.builder(clusterState.coordinationMetaData()).term(newTerm).build())
.put(IndexMetaData.builder(indexMetaData).settings(Settings.builder()
.put(indexMetaData.getSettings())
.put(IndexMetaData.INDEX_NUMBER_OF_REPLICAS_SETTING.getKey(), 3)).build(), false))
.incrementVersion().build(),
clusterState);
}
final ClusterState clusterState = loadPersistedClusterState(persistedClusterStateService);
final IndexMetaData indexMetaData = clusterState.metaData().index("test");
assertThat(indexMetaData.getIndexUUID(), equalTo(indexUUID));
assertThat(indexMetaData.getVersion(), equalTo(indexMetaDataVersion + 1));
assertThat(IndexMetaData.INDEX_NUMBER_OF_REPLICAS_SETTING.get(indexMetaData.getSettings()), equalTo(3));
}
}
public void testPersistsAndReloadsIndexMetadataForMultipleIndices() throws IOException {
try (NodeEnvironment nodeEnvironment = newNodeEnvironment(createDataPaths())) {
final PersistedClusterStateService persistedClusterStateService = newPersistedClusterStateService(nodeEnvironment);
final long term = randomLongBetween(1L, Long.MAX_VALUE);
final String addedIndexUuid = UUIDs.randomBase64UUID(random());
final String updatedIndexUuid = UUIDs.randomBase64UUID(random());
final String deletedIndexUuid = UUIDs.randomBase64UUID(random());
try (Writer writer = persistedClusterStateService.createWriter()) {
final ClusterState clusterState = loadPersistedClusterState(persistedClusterStateService);
writeState(writer, 0L, ClusterState.builder(clusterState)
.metaData(MetaData.builder(clusterState.metaData())
.version(clusterState.metaData().version() + 1)
.coordinationMetaData(CoordinationMetaData.builder(clusterState.coordinationMetaData()).term(term).build())
.put(IndexMetaData.builder("updated")
.version(randomLongBetween(0L, Long.MAX_VALUE - 1) - 1) // -1 because it's incremented in .put()
.settings(Settings.builder()
.put(IndexMetaData.INDEX_NUMBER_OF_REPLICAS_SETTING.getKey(), 1)
.put(IndexMetaData.INDEX_NUMBER_OF_SHARDS_SETTING.getKey(), 1)
.put(IndexMetaData.SETTING_INDEX_VERSION_CREATED.getKey(), Version.CURRENT)
.put(IndexMetaData.SETTING_INDEX_UUID, updatedIndexUuid)))
.put(IndexMetaData.builder("deleted")
.version(randomLongBetween(0L, Long.MAX_VALUE - 1) - 1) // -1 because it's incremented in .put()
.settings(Settings.builder()
.put(IndexMetaData.INDEX_NUMBER_OF_REPLICAS_SETTING.getKey(), 1)
.put(IndexMetaData.INDEX_NUMBER_OF_SHARDS_SETTING.getKey(), 1)
.put(IndexMetaData.SETTING_INDEX_VERSION_CREATED.getKey(), Version.CURRENT)
.put(IndexMetaData.SETTING_INDEX_UUID, deletedIndexUuid))))
.incrementVersion().build(),
clusterState);
}
try (Writer writer = persistedClusterStateService.createWriter()) {
final ClusterState clusterState = loadPersistedClusterState(persistedClusterStateService);
assertThat(clusterState.metaData().indices().size(), equalTo(2));
assertThat(clusterState.metaData().index("updated").getIndexUUID(), equalTo(updatedIndexUuid));
assertThat(IndexMetaData.INDEX_NUMBER_OF_REPLICAS_SETTING.get(clusterState.metaData().index("updated").getSettings()),
equalTo(1));
assertThat(clusterState.metaData().index("deleted").getIndexUUID(), equalTo(deletedIndexUuid));
writeState(writer, 0L, ClusterState.builder(clusterState)
.metaData(MetaData.builder(clusterState.metaData())
.version(clusterState.metaData().version() + 1)
.remove("deleted")
.put(IndexMetaData.builder("updated")
.settings(Settings.builder()
.put(clusterState.metaData().index("updated").getSettings())
.put(IndexMetaData.INDEX_NUMBER_OF_REPLICAS_SETTING.getKey(), 2)))
.put(IndexMetaData.builder("added")
.version(randomLongBetween(0L, Long.MAX_VALUE - 1) - 1) // -1 because it's incremented in .put()
.settings(Settings.builder()
.put(IndexMetaData.INDEX_NUMBER_OF_REPLICAS_SETTING.getKey(), 1)
.put(IndexMetaData.INDEX_NUMBER_OF_SHARDS_SETTING.getKey(), 1)
.put(IndexMetaData.SETTING_INDEX_VERSION_CREATED.getKey(), Version.CURRENT)
.put(IndexMetaData.SETTING_INDEX_UUID, addedIndexUuid))))
.incrementVersion().build(),
clusterState);
}
final ClusterState clusterState = loadPersistedClusterState(persistedClusterStateService);
assertThat(clusterState.metaData().indices().size(), equalTo(2));
assertThat(clusterState.metaData().index("updated").getIndexUUID(), equalTo(updatedIndexUuid));
assertThat(IndexMetaData.INDEX_NUMBER_OF_REPLICAS_SETTING.get(clusterState.metaData().index("updated").getSettings()),
equalTo(2));
assertThat(clusterState.metaData().index("added").getIndexUUID(), equalTo(addedIndexUuid));
assertThat(clusterState.metaData().index("deleted"), nullValue());
}
}
public void testReloadsMetadataAcrossMultipleSegments() throws IOException {
try (NodeEnvironment nodeEnvironment = newNodeEnvironment(createDataPaths())) {
final PersistedClusterStateService persistedClusterStateService = newPersistedClusterStateService(nodeEnvironment);
final int writes = between(5, 20);
final List<Index> indices = new ArrayList<>(writes);
try (Writer writer = persistedClusterStateService.createWriter()) {
for (int i = 0; i < writes; i++) {
final Index index = new Index("test-" + i, UUIDs.randomBase64UUID(random()));
indices.add(index);
final ClusterState clusterState = loadPersistedClusterState(persistedClusterStateService);
writeState(writer, 0L, ClusterState.builder(clusterState)
.metaData(MetaData.builder(clusterState.metaData())
.version(i + 2)
.put(IndexMetaData.builder(index.getName())
.settings(Settings.builder()
.put(IndexMetaData.INDEX_NUMBER_OF_SHARDS_SETTING.getKey(), 1)
.put(IndexMetaData.INDEX_NUMBER_OF_REPLICAS_SETTING.getKey(), 0)
.put(IndexMetaData.SETTING_INDEX_VERSION_CREATED.getKey(), Version.CURRENT)
.put(IndexMetaData.SETTING_INDEX_UUID, index.getUUID()))))
.incrementVersion().build(),
clusterState);
}
}
final ClusterState clusterState = loadPersistedClusterState(persistedClusterStateService);
for (Index index : indices) {
final IndexMetaData indexMetaData = clusterState.metaData().index(index.getName());
assertThat(indexMetaData.getIndexUUID(), equalTo(index.getUUID()));
}
}
}
@Override
public Settings buildEnvSettings(Settings settings) {
assertTrue(settings.hasValue(Environment.PATH_DATA_SETTING.getKey()));
return Settings.builder()
.put(settings)
.put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toAbsolutePath()).build();
}
public static Path[] createDataPaths() {
final Path[] dataPaths = new Path[randomIntBetween(1, 4)];
for (int i = 0; i < dataPaths.length; i++) {
dataPaths[i] = createTempDir();
}
return dataPaths;
}
private NodeEnvironment newNodeEnvironment(Path[] dataPaths) throws IOException {
return newNodeEnvironment(Settings.builder()
.putList(Environment.PATH_DATA_SETTING.getKey(), Arrays.stream(dataPaths).map(Path::toString).collect(Collectors.toList()))
.build());
}
private static ClusterState loadPersistedClusterState(PersistedClusterStateService persistedClusterStateService) throws IOException {
final PersistedClusterStateService.OnDiskState onDiskState = persistedClusterStateService.loadBestOnDiskState();
return clusterStateFromMetadata(onDiskState.lastAcceptedVersion, onDiskState.metaData);
}
private static ClusterState clusterStateFromMetadata(long version, MetaData metaData) {
return ClusterState.builder(ClusterName.DEFAULT).version(version).metaData(metaData).build();
}
}

View File

@ -42,6 +42,7 @@ public class PeerRecoveryRetentionLeaseCreationIT extends ESIntegTestCase {
return false; return false;
} }
@AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/48701")
public void testCanRecoverFromStoreWithoutPeerRecoveryRetentionLease() throws Exception { public void testCanRecoverFromStoreWithoutPeerRecoveryRetentionLease() throws Exception {
/* /*
* In a full cluster restart from a version without peer-recovery retention leases, the leases on disk will not include a lease for * In a full cluster restart from a version without peer-recovery retention leases, the leases on disk will not include a lease for

View File

@ -56,6 +56,7 @@ import org.elasticsearch.common.unit.ByteSizeValue;
import org.elasticsearch.env.Environment; import org.elasticsearch.env.Environment;
import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.env.NodeEnvironment;
import org.elasticsearch.env.TestEnvironment; import org.elasticsearch.env.TestEnvironment;
import org.elasticsearch.gateway.GatewayMetaState;
import org.elasticsearch.index.Index; import org.elasticsearch.index.Index;
import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.IndexSettings;
import org.elasticsearch.index.MergePolicyConfig; import org.elasticsearch.index.MergePolicyConfig;
@ -85,6 +86,7 @@ import java.util.Set;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
import static org.elasticsearch.common.util.CollectionUtils.iterableAsArrayList; import static org.elasticsearch.common.util.CollectionUtils.iterableAsArrayList;
@ -155,8 +157,8 @@ public class RemoveCorruptedShardDataCommandIT extends ESIntegTestCase {
fail("expected the command to fail as node is locked"); fail("expected the command to fail as node is locked");
} catch (Exception e) { } catch (Exception e) {
assertThat(e.getMessage(), assertThat(e.getMessage(),
allOf(containsString("Failed to lock node's directory"), allOf(containsString("failed to lock node's directory"),
containsString("is Elasticsearch still running ?"))); containsString("is Elasticsearch still running?")));
} }
final Path indexDir = getPathToShardData(indexName, ShardPath.INDEX_FOLDER_NAME); final Path indexDir = getPathToShardData(indexName, ShardPath.INDEX_FOLDER_NAME);
@ -478,6 +480,9 @@ public class RemoveCorruptedShardDataCommandIT extends ESIntegTestCase {
final Settings node1PathSettings = internalCluster().dataPathSettings(node1); final Settings node1PathSettings = internalCluster().dataPathSettings(node1);
final Settings node2PathSettings = internalCluster().dataPathSettings(node2); final Settings node2PathSettings = internalCluster().dataPathSettings(node2);
assertBusy(() -> internalCluster().getInstances(GatewayMetaState.class)
.forEach(gw -> assertTrue(gw.allPendingAsyncStatesWritten())));
// stop data nodes // stop data nodes
internalCluster().stopRandomDataNode(); internalCluster().stopRandomDataNode();
internalCluster().stopRandomDataNode(); internalCluster().stopRandomDataNode();
@ -574,7 +579,8 @@ public class RemoveCorruptedShardDataCommandIT extends ESIntegTestCase {
final Path indexPath = indexPathByNodeName.get(nodeName); final Path indexPath = indexPathByNodeName.get(nodeName);
final OptionSet options = parser.parse("--dir", indexPath.toAbsolutePath().toString()); final OptionSet options = parser.parse("--dir", indexPath.toAbsolutePath().toString());
command.findAndProcessShardPath(options, environmentByNodeName.get(nodeName), command.findAndProcessShardPath(options, environmentByNodeName.get(nodeName),
shardPath -> assertThat(shardPath.resolveIndex(), equalTo(indexPath))); Stream.of(environmentByNodeName.get(nodeName).dataFiles()).map(path -> NodeEnvironment.resolveNodePath(path, 0))
.toArray(Path[]::new), 0, state, shardPath -> assertThat(shardPath.resolveIndex(), equalTo(indexPath)));
} }
} }

View File

@ -25,17 +25,23 @@ import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.Version; import org.elasticsearch.Version;
import org.elasticsearch.cli.MockTerminal; import org.elasticsearch.cli.MockTerminal;
import org.elasticsearch.cli.Terminal; import org.elasticsearch.cli.Terminal;
import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.cluster.routing.RecoverySource; import org.elasticsearch.cluster.routing.RecoverySource;
import org.elasticsearch.cluster.routing.ShardRouting; import org.elasticsearch.cluster.routing.ShardRouting;
import org.elasticsearch.cluster.routing.ShardRoutingHelper; import org.elasticsearch.cluster.routing.ShardRoutingHelper;
import org.elasticsearch.cluster.routing.ShardRoutingState; import org.elasticsearch.cluster.routing.ShardRoutingState;
import org.elasticsearch.cluster.routing.TestShardRouting; import org.elasticsearch.cluster.routing.TestShardRouting;
import org.elasticsearch.common.CheckedFunction; import org.elasticsearch.common.CheckedFunction;
import org.elasticsearch.common.UUIDs;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.env.Environment; import org.elasticsearch.env.Environment;
import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.env.NodeEnvironment;
import org.elasticsearch.env.TestEnvironment; import org.elasticsearch.env.TestEnvironment;
import org.elasticsearch.gateway.PersistedClusterStateService;
import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.IndexSettings;
import org.elasticsearch.index.MergePolicyConfig; import org.elasticsearch.index.MergePolicyConfig;
import org.elasticsearch.index.engine.EngineException; import org.elasticsearch.index.engine.EngineException;
@ -52,6 +58,7 @@ import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Arrays; import java.util.Arrays;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -72,7 +79,9 @@ public class RemoveCorruptedShardDataCommandTests extends IndexShardTestCase {
private Environment environment; private Environment environment;
private ShardPath shardPath; private ShardPath shardPath;
private IndexMetaData indexMetaData; private IndexMetaData indexMetaData;
private ClusterState clusterState;
private IndexShard indexShard; private IndexShard indexShard;
private Path[] dataPaths;
private Path translogPath; private Path translogPath;
private Path indexPath; private Path indexPath;
@ -81,7 +90,7 @@ public class RemoveCorruptedShardDataCommandTests extends IndexShardTestCase {
@Before @Before
public void setup() throws IOException { public void setup() throws IOException {
shardId = new ShardId("index0", "_na_", 0); shardId = new ShardId("index0", UUIDs.randomBase64UUID(), 0);
final String nodeId = randomAlphaOfLength(10); final String nodeId = randomAlphaOfLength(10);
routing = TestShardRouting.newShardRouting(shardId, nodeId, true, ShardRoutingState.INITIALIZING, routing = TestShardRouting.newShardRouting(shardId, nodeId, true, ShardRoutingState.INITIALIZING,
RecoverySource.EmptyStoreRecoverySource.INSTANCE); RecoverySource.EmptyStoreRecoverySource.INSTANCE);
@ -96,11 +105,13 @@ public class RemoveCorruptedShardDataCommandTests extends IndexShardTestCase {
// create same directory structure as prod does // create same directory structure as prod does
final Path path = NodeEnvironment.resolveNodePath(dataDir, 0); final Path path = NodeEnvironment.resolveNodePath(dataDir, 0);
Files.createDirectories(path); Files.createDirectories(path);
dataPaths = new Path[] {path};
final Settings settings = Settings.builder() final Settings settings = Settings.builder()
.put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT) .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT)
.put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1) .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1)
.put(MergePolicyConfig.INDEX_MERGE_ENABLED, false) .put(MergePolicyConfig.INDEX_MERGE_ENABLED, false)
.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0) .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0)
.put(IndexMetaData.SETTING_INDEX_UUID, shardId.getIndex().getUUID())
.build(); .build();
final NodeEnvironment.NodePath nodePath = new NodeEnvironment.NodePath(path); final NodeEnvironment.NodePath nodePath = new NodeEnvironment.NodePath(path);
@ -111,6 +122,16 @@ public class RemoveCorruptedShardDataCommandTests extends IndexShardTestCase {
.putMapping("_doc", "{ \"properties\": {} }"); .putMapping("_doc", "{ \"properties\": {} }");
indexMetaData = metaData.build(); indexMetaData = metaData.build();
clusterState = ClusterState.builder(ClusterName.DEFAULT).metaData(MetaData.builder().put(indexMetaData, false).build()).build();
try (NodeEnvironment.NodeLock lock = new NodeEnvironment.NodeLock(0, logger, environment, Files::exists)) {
final Path[] dataPaths = Arrays.stream(lock.getNodePaths()).filter(Objects::nonNull).map(p -> p.path).toArray(Path[]::new);
try (PersistedClusterStateService.Writer writer = new PersistedClusterStateService(dataPaths, nodeId,
xContentRegistry(), BigArrays.NON_RECYCLING_INSTANCE, true).createWriter()) {
writer.writeFullStateAndCommit(1L, clusterState);
}
}
indexShard = newStartedShard(p -> newShard(routing, shardPath, indexMetaData, null, null, indexShard = newStartedShard(p -> newShard(routing, shardPath, indexMetaData, null, null,
new InternalEngineFactory(), () -> { }, RetentionLeaseSyncer.EMPTY, EMPTY_EVENT_LISTENER), true); new InternalEngineFactory(), () -> { }, RetentionLeaseSyncer.EMPTY, EMPTY_EVENT_LISTENER), true);
@ -331,7 +352,6 @@ public class RemoveCorruptedShardDataCommandTests extends IndexShardTestCase {
// index a single doc to have files on a disk // index a single doc to have files on a disk
indexDoc(indexShard, "_doc", "0", "{}"); indexDoc(indexShard, "_doc", "0", "{}");
flushShard(indexShard, true); flushShard(indexShard, true);
writeIndexState();
// close shard // close shard
closeShards(indexShard); closeShards(indexShard);
@ -343,11 +363,11 @@ public class RemoveCorruptedShardDataCommandTests extends IndexShardTestCase {
final OptionSet options = parser.parse("--index", shardId.getIndex().getName(), final OptionSet options = parser.parse("--index", shardId.getIndex().getName(),
"--shard-id", Integer.toString(shardId.id())); "--shard-id", Integer.toString(shardId.id()));
command.findAndProcessShardPath(options, environment, command.findAndProcessShardPath(options, environment, dataPaths, 0, clusterState,
shardPath -> assertThat(shardPath.resolveIndex(), equalTo(indexPath))); shardPath -> assertThat(shardPath.resolveIndex(), equalTo(indexPath)));
final OptionSet options2 = parser.parse("--dir", indexPath.toAbsolutePath().toString()); final OptionSet options2 = parser.parse("--dir", indexPath.toAbsolutePath().toString());
command.findAndProcessShardPath(options2, environment, command.findAndProcessShardPath(options2, environment, dataPaths, 0, clusterState,
shardPath -> assertThat(shardPath.resolveIndex(), equalTo(indexPath))); shardPath -> assertThat(shardPath.resolveIndex(), equalTo(indexPath)));
} }
@ -485,17 +505,7 @@ public class RemoveCorruptedShardDataCommandTests extends IndexShardTestCase {
logger.info("--> indexed {} docs, {} to keep", numDocs, numDocsToKeep); logger.info("--> indexed {} docs, {} to keep", numDocs, numDocsToKeep);
writeIndexState();
return numDocsToKeep; return numDocsToKeep;
} }
private void writeIndexState() throws IOException {
// create _state of IndexMetaData
try(NodeEnvironment nodeEnvironment = new NodeEnvironment(environment.settings(), environment)) {
final Path[] paths = nodeEnvironment.indexPaths(indexMetaData.getIndex());
IndexMetaData.FORMAT.writeAndCleanup(indexMetaData, paths);
logger.info("--> index metadata persisted to {} ", Arrays.toString(paths));
}
}
} }

View File

@ -122,7 +122,7 @@ public class IndicesLifecycleListenerSingleNodeTests extends ESSingleNodeTestCas
}; };
indicesService.removeIndex(idx, DELETED, "simon says"); indicesService.removeIndex(idx, DELETED, "simon says");
try { try {
IndexService index = indicesService.createIndex(metaData, Arrays.asList(countingListener)); IndexService index = indicesService.createIndex(metaData, Arrays.asList(countingListener), false);
assertEquals(3, counter.get()); assertEquals(3, counter.get());
idx = index.index(); idx = index.index();
ShardRouting newRouting = shardRouting; ShardRouting newRouting = shardRouting;

View File

@ -223,13 +223,11 @@ public class IndicesServiceTests extends ESSingleNodeTestCase {
ClusterService clusterService = getInstanceFromNode(ClusterService.class); ClusterService clusterService = getInstanceFromNode(ClusterService.class);
IndexMetaData firstMetaData = clusterService.state().metaData().index("test"); IndexMetaData firstMetaData = clusterService.state().metaData().index("test");
assertTrue(test.hasShard(0)); assertTrue(test.hasShard(0));
ShardPath firstPath = ShardPath.loadShardPath(logger, getNodeEnvironment(), new ShardId(test.index(), 0),
test.getIndexSettings().customDataPath());
try { expectThrows(IllegalStateException.class, () -> indicesService.deleteIndexStore("boom", firstMetaData));
indicesService.deleteIndexStore("boom", firstMetaData, clusterService.state()); assertTrue(firstPath.exists());
fail();
} catch (IllegalStateException ex) {
// all good
}
GatewayMetaState gwMetaState = getInstanceFromNode(GatewayMetaState.class); GatewayMetaState gwMetaState = getInstanceFromNode(GatewayMetaState.class);
MetaData meta = gwMetaState.getMetaData(); MetaData meta = gwMetaState.getMetaData();
@ -237,37 +235,25 @@ public class IndicesServiceTests extends ESSingleNodeTestCase {
assertNotNull(meta.index("test")); assertNotNull(meta.index("test"));
assertAcked(client().admin().indices().prepareDelete("test")); assertAcked(client().admin().indices().prepareDelete("test"));
assertFalse(firstPath.exists());
meta = gwMetaState.getMetaData(); meta = gwMetaState.getMetaData();
assertNotNull(meta); assertNotNull(meta);
assertNull(meta.index("test")); assertNull(meta.index("test"));
test = createIndex("test"); test = createIndex("test");
client().prepareIndex("test", "type", "1").setSource("field", "value").setRefreshPolicy(IMMEDIATE).get(); client().prepareIndex("test", "type", "1").setSource("field", "value").setRefreshPolicy(IMMEDIATE).get();
client().admin().indices().prepareFlush("test").get(); client().admin().indices().prepareFlush("test").get();
assertHitCount(client().prepareSearch("test").get(), 1); assertHitCount(client().prepareSearch("test").get(), 1);
IndexMetaData secondMetaData = clusterService.state().metaData().index("test"); IndexMetaData secondMetaData = clusterService.state().metaData().index("test");
assertAcked(client().admin().indices().prepareClose("test")); assertAcked(client().admin().indices().prepareClose("test").setWaitForActiveShards(1));
ShardPath path = ShardPath.loadShardPath(logger, getNodeEnvironment(), new ShardId(test.index(), 0), ShardPath secondPath = ShardPath.loadShardPath(logger, getNodeEnvironment(), new ShardId(test.index(), 0),
test.getIndexSettings().customDataPath()); test.getIndexSettings().customDataPath());
assertTrue(path.exists()); assertTrue(secondPath.exists());
try { expectThrows(IllegalStateException.class, () -> indicesService.deleteIndexStore("boom", secondMetaData));
indicesService.deleteIndexStore("boom", secondMetaData, clusterService.state()); assertTrue(secondPath.exists());
fail();
} catch (IllegalStateException ex) {
// all good
}
assertTrue(path.exists());
// now delete the old one and make sure we resolve against the name
try {
indicesService.deleteIndexStore("boom", firstMetaData, clusterService.state());
fail();
} catch (IllegalStateException ex) {
// all good
}
assertAcked(client().admin().indices().prepareOpen("test")); assertAcked(client().admin().indices().prepareOpen("test"));
ensureGreen("test"); ensureGreen("test");
} }
@ -563,7 +549,7 @@ public class IndicesServiceTests extends ESSingleNodeTestCase {
.numberOfShards(1) .numberOfShards(1)
.numberOfReplicas(0) .numberOfReplicas(0)
.build(); .build();
final IndexService indexService = indicesService.createIndex(indexMetaData, Collections.emptyList()); final IndexService indexService = indicesService.createIndex(indexMetaData, Collections.emptyList(), false);
if (value != null && value) { if (value != null && value) {
assertThat(indexService.getEngineFactory(), instanceOf(FooEnginePlugin.FooEngineFactory.class)); assertThat(indexService.getEngineFactory(), instanceOf(FooEnginePlugin.FooEngineFactory.class));
} else { } else {
@ -589,7 +575,7 @@ public class IndicesServiceTests extends ESSingleNodeTestCase {
final IndicesService indicesService = getIndicesService(); final IndicesService indicesService = getIndicesService();
final IllegalStateException e = final IllegalStateException e =
expectThrows(IllegalStateException.class, () -> indicesService.createIndex(indexMetaData, Collections.emptyList())); expectThrows(IllegalStateException.class, () -> indicesService.createIndex(indexMetaData, Collections.emptyList(), false));
final String pattern = final String pattern =
".*multiple engine factories provided for \\[foobar/.*\\]: \\[.*FooEngineFactory\\],\\[.*BarEngineFactory\\].*"; ".*multiple engine factories provided for \\[foobar/.*\\]: \\[.*FooEngineFactory\\],\\[.*BarEngineFactory\\].*";
assertThat(e, hasToString(new RegexMatcher(pattern))); assertThat(e, hasToString(new RegexMatcher(pattern)));
@ -675,7 +661,7 @@ public class IndicesServiceTests extends ESSingleNodeTestCase {
.numberOfShards(1) .numberOfShards(1)
.numberOfReplicas(0) .numberOfReplicas(0)
.build(); .build();
IndexService indexService = indicesService.createIndex(indexMetaData, Collections.emptyList()); IndexService indexService = indicesService.createIndex(indexMetaData, Collections.emptyList(), false);
assertNotNull(indexService); assertNotNull(indexService);
final Index index2 = new Index("bar-index", UUIDs.randomBase64UUID()); final Index index2 = new Index("bar-index", UUIDs.randomBase64UUID());
@ -689,7 +675,7 @@ public class IndicesServiceTests extends ESSingleNodeTestCase {
.numberOfReplicas(0) .numberOfReplicas(0)
.build(); .build();
IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, IllegalArgumentException ex = expectThrows(IllegalArgumentException.class,
() -> indicesService.createIndex(indexMetaData2, Collections.emptyList())); () -> indicesService.createIndex(indexMetaData2, Collections.emptyList(), false));
assertEquals("Setting [" + EngineConfig.INDEX_OPTIMIZE_AUTO_GENERATED_IDS.getKey() + "] was removed in version 7.0.0", assertEquals("Setting [" + EngineConfig.INDEX_OPTIMIZE_AUTO_GENERATED_IDS.getKey() + "] was removed in version 7.0.0",
ex.getMessage()); ex.getMessage());
@ -703,7 +689,7 @@ public class IndicesServiceTests extends ESSingleNodeTestCase {
.numberOfShards(1) .numberOfShards(1)
.numberOfReplicas(0) .numberOfReplicas(0)
.build(); .build();
IndexService indexService2 = indicesService.createIndex(indexMetaData3, Collections.emptyList()); IndexService indexService2 = indicesService.createIndex(indexMetaData3, Collections.emptyList(), false);
assertNotNull(indexService2); assertNotNull(indexService2);
} }

View File

@ -195,7 +195,8 @@ public abstract class AbstractIndicesClusterStateServiceTestCase extends ESTestC
@Override @Override
public synchronized MockIndexService createIndex( public synchronized MockIndexService createIndex(
IndexMetaData indexMetaData, IndexMetaData indexMetaData,
List<IndexEventListener> buildInIndexListener) throws IOException { List<IndexEventListener> buildInIndexListener,
boolean writeDanglingIndices) throws IOException {
MockIndexService indexService = new MockIndexService(new IndexSettings(indexMetaData, Settings.EMPTY)); MockIndexService indexService = new MockIndexService(new IndexSettings(indexMetaData, Settings.EMPTY));
indices = newMapBuilder(indices).put(indexMetaData.getIndexUUID(), indexService).immutableMap(); indices = newMapBuilder(indices).put(indexMetaData.getIndexUUID(), indexService).immutableMap();
return indexService; return indexService;

View File

@ -108,6 +108,7 @@ import static org.elasticsearch.env.Environment.PATH_HOME_SETTING;
import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyList; import static org.mockito.Matchers.anyList;
import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer;
@ -161,7 +162,7 @@ public class ClusterStateChanges {
// MetaDataCreateIndexService creates indices using its IndicesService instance to check mappings -> fake it here // MetaDataCreateIndexService creates indices using its IndicesService instance to check mappings -> fake it here
try { try {
@SuppressWarnings("unchecked") final List<IndexEventListener> listeners = anyList(); @SuppressWarnings("unchecked") final List<IndexEventListener> listeners = anyList();
when(indicesService.createIndex(any(IndexMetaData.class), listeners)) when(indicesService.createIndex(any(IndexMetaData.class), listeners, anyBoolean()))
.then(invocationOnMock -> { .then(invocationOnMock -> {
IndexService indexService = mock(IndexService.class); IndexService indexService = mock(IndexService.class);
IndexMetaData indexMetaData = (IndexMetaData)invocationOnMock.getArguments()[0]; IndexMetaData indexMetaData = (IndexMetaData)invocationOnMock.getArguments()[0];

View File

@ -20,6 +20,7 @@
package org.elasticsearch.indices.recovery; package org.elasticsearch.indices.recovery;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.indices.IndicesService;
import org.elasticsearch.test.ESIntegTestCase; import org.elasticsearch.test.ESIntegTestCase;
import org.elasticsearch.test.ESIntegTestCase.ClusterScope; import org.elasticsearch.test.ESIntegTestCase.ClusterScope;
import org.elasticsearch.test.InternalTestCluster; import org.elasticsearch.test.InternalTestCluster;
@ -29,16 +30,18 @@ import java.util.concurrent.TimeUnit;
import static org.elasticsearch.cluster.metadata.IndexGraveyard.SETTING_MAX_TOMBSTONES; import static org.elasticsearch.cluster.metadata.IndexGraveyard.SETTING_MAX_TOMBSTONES;
import static org.elasticsearch.gateway.DanglingIndicesState.AUTO_IMPORT_DANGLING_INDICES_SETTING; import static org.elasticsearch.gateway.DanglingIndicesState.AUTO_IMPORT_DANGLING_INDICES_SETTING;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.hamcrest.Matchers.equalTo;
@ClusterScope(numDataNodes = 0, scope = ESIntegTestCase.Scope.TEST) @ClusterScope(numDataNodes = 0, scope = ESIntegTestCase.Scope.TEST)
public class DanglingIndicesIT extends ESIntegTestCase { public class DanglingIndicesIT extends ESIntegTestCase {
private static final String INDEX_NAME = "test-idx-1"; private static final String INDEX_NAME = "test-idx-1";
private Settings buildSettings(boolean importDanglingIndices) { private Settings buildSettings(boolean writeDanglingIndices, boolean importDanglingIndices) {
return Settings.builder() return Settings.builder()
// Don't keep any indices in the graveyard, so that when we delete an index, // Don't keep any indices in the graveyard, so that when we delete an index,
// it's definitely considered to be dangling. // it's definitely considered to be dangling.
.put(SETTING_MAX_TOMBSTONES.getKey(), 0) .put(SETTING_MAX_TOMBSTONES.getKey(), 0)
.put(IndicesService.WRITE_DANGLING_INDICES_INFO_SETTING.getKey(), writeDanglingIndices)
.put(AUTO_IMPORT_DANGLING_INDICES_SETTING.getKey(), importDanglingIndices) .put(AUTO_IMPORT_DANGLING_INDICES_SETTING.getKey(), importDanglingIndices)
.build(); .build();
} }
@ -48,10 +51,21 @@ public class DanglingIndicesIT extends ESIntegTestCase {
* the cluster, so long as the recovery setting is enabled. * the cluster, so long as the recovery setting is enabled.
*/ */
public void testDanglingIndicesAreRecoveredWhenSettingIsEnabled() throws Exception { public void testDanglingIndicesAreRecoveredWhenSettingIsEnabled() throws Exception {
final Settings settings = buildSettings(true); final Settings settings = buildSettings(true, true);
internalCluster().startNodes(3, settings); internalCluster().startNodes(3, settings);
createIndex(INDEX_NAME, Settings.builder().put("number_of_replicas", 2).build()); createIndex(INDEX_NAME, Settings.builder().put("number_of_replicas", 2).build());
ensureGreen(INDEX_NAME);
assertBusy(() -> internalCluster().getInstances(IndicesService.class).forEach(
indicesService -> assertTrue(indicesService.allPendingDanglingIndicesWritten())));
boolean refreshIntervalChanged = randomBoolean();
if (refreshIntervalChanged) {
client().admin().indices().prepareUpdateSettings(INDEX_NAME).setSettings(
Settings.builder().put("index.refresh_interval", "42s").build()).get();
assertBusy(() -> internalCluster().getInstances(IndicesService.class).forEach(
indicesService -> assertTrue(indicesService.allPendingDanglingIndicesWritten())));
}
if (randomBoolean()) { if (randomBoolean()) {
client().admin().indices().prepareClose(INDEX_NAME).get(); client().admin().indices().prepareClose(INDEX_NAME).get();
@ -63,12 +77,17 @@ public class DanglingIndicesIT extends ESIntegTestCase {
@Override @Override
public Settings onNodeStopped(String nodeName) throws Exception { public Settings onNodeStopped(String nodeName) throws Exception {
ensureClusterSizeConsistency();
assertAcked(client().admin().indices().prepareDelete(INDEX_NAME)); assertAcked(client().admin().indices().prepareDelete(INDEX_NAME));
return super.onNodeStopped(nodeName); return super.onNodeStopped(nodeName);
} }
}); });
assertBusy(() -> assertTrue("Expected dangling index " + INDEX_NAME + " to be recovered", indexExists(INDEX_NAME))); assertBusy(() -> assertTrue("Expected dangling index " + INDEX_NAME + " to be recovered", indexExists(INDEX_NAME)));
if (refreshIntervalChanged) {
assertThat(client().admin().indices().prepareGetSettings(INDEX_NAME).get().getSetting(INDEX_NAME, "index.refresh_interval"),
equalTo("42s"));
}
ensureGreen(INDEX_NAME); ensureGreen(INDEX_NAME);
} }
@ -77,15 +96,49 @@ public class DanglingIndicesIT extends ESIntegTestCase {
* the cluster when the recovery setting is disabled. * the cluster when the recovery setting is disabled.
*/ */
public void testDanglingIndicesAreNotRecoveredWhenSettingIsDisabled() throws Exception { public void testDanglingIndicesAreNotRecoveredWhenSettingIsDisabled() throws Exception {
internalCluster().startNodes(3, buildSettings(false)); internalCluster().startNodes(3, buildSettings(true, false));
createIndex(INDEX_NAME, Settings.builder().put("number_of_replicas", 2).build()); createIndex(INDEX_NAME, Settings.builder().put("number_of_replicas", 2).build());
ensureGreen(INDEX_NAME);
assertBusy(() -> internalCluster().getInstances(IndicesService.class).forEach(
indicesService -> assertTrue(indicesService.allPendingDanglingIndicesWritten())));
// Restart node, deleting the index in its absence, so that there is a dangling index to recover // Restart node, deleting the index in its absence, so that there is a dangling index to recover
internalCluster().restartRandomDataNode(new InternalTestCluster.RestartCallback() { internalCluster().restartRandomDataNode(new InternalTestCluster.RestartCallback() {
@Override @Override
public Settings onNodeStopped(String nodeName) throws Exception { public Settings onNodeStopped(String nodeName) throws Exception {
ensureClusterSizeConsistency();
assertAcked(client().admin().indices().prepareDelete(INDEX_NAME));
return super.onNodeStopped(nodeName);
}
});
// Since index recovery is async, we can't prove index recovery will never occur, just that it doesn't occur within some reasonable
// amount of time
assertFalse(
"Did not expect dangling index " + INDEX_NAME + " to be recovered",
waitUntil(() -> indexExists(INDEX_NAME), 1, TimeUnit.SECONDS)
);
}
/**
* Check that when dangling indices are not written, then they cannot be recovered into the cluster.
*/
public void testDanglingIndicesAreNotRecoveredWhenNotWritten() throws Exception {
internalCluster().startNodes(3, buildSettings(false, true));
createIndex(INDEX_NAME, Settings.builder().put("number_of_replicas", 2).build());
ensureGreen(INDEX_NAME);
internalCluster().getInstances(IndicesService.class).forEach(
indicesService -> assertTrue(indicesService.allPendingDanglingIndicesWritten()));
// Restart node, deleting the index in its absence, so that there is a dangling index to recover
internalCluster().restartRandomDataNode(new InternalTestCluster.RestartCallback() {
@Override
public Settings onNodeStopped(String nodeName) throws Exception {
ensureClusterSizeConsistency();
assertAcked(client().admin().indices().prepareDelete(INDEX_NAME)); assertAcked(client().admin().indices().prepareDelete(INDEX_NAME));
return super.onNodeStopped(nodeName); return super.onNodeStopped(nodeName);
} }

View File

@ -35,7 +35,6 @@ import org.elasticsearch.cluster.coordination.AbstractCoordinatorTestCase.Cluste
import org.elasticsearch.cluster.coordination.CoordinationMetaData.VotingConfiguration; import org.elasticsearch.cluster.coordination.CoordinationMetaData.VotingConfiguration;
import org.elasticsearch.cluster.coordination.LinearizabilityChecker.History; import org.elasticsearch.cluster.coordination.LinearizabilityChecker.History;
import org.elasticsearch.cluster.coordination.LinearizabilityChecker.SequentialSpec; import org.elasticsearch.cluster.coordination.LinearizabilityChecker.SequentialSpec;
import org.elasticsearch.cluster.metadata.Manifest;
import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.node.DiscoveryNodeRole; import org.elasticsearch.cluster.node.DiscoveryNodeRole;
@ -57,14 +56,15 @@ import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.common.util.concurrent.PrioritizedEsThreadPoolExecutor; import org.elasticsearch.common.util.concurrent.PrioritizedEsThreadPoolExecutor;
import org.elasticsearch.discovery.DiscoveryModule; import org.elasticsearch.discovery.DiscoveryModule;
import org.elasticsearch.discovery.SeedHostsProvider; import org.elasticsearch.discovery.SeedHostsProvider;
import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.env.NodeEnvironment;
import org.elasticsearch.gateway.ClusterStateUpdaters; import org.elasticsearch.gateway.ClusterStateUpdaters;
import org.elasticsearch.gateway.GatewayService; import org.elasticsearch.gateway.GatewayService;
import org.elasticsearch.gateway.MetaStateService;
import org.elasticsearch.gateway.MockGatewayMetaState; import org.elasticsearch.gateway.MockGatewayMetaState;
import org.elasticsearch.gateway.PersistedClusterStateService;
import org.elasticsearch.node.Node; import org.elasticsearch.node.Node;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.disruption.DisruptableMockTransport; import org.elasticsearch.test.disruption.DisruptableMockTransport;
@ -741,17 +741,15 @@ public class AbstractCoordinatorTestCase extends ESTestCase {
try { try {
if (oldState.nodeEnvironment != null) { if (oldState.nodeEnvironment != null) {
nodeEnvironment = oldState.nodeEnvironment; nodeEnvironment = oldState.nodeEnvironment;
final MetaStateService metaStateService = new MetaStateService(nodeEnvironment, xContentRegistry());
final MetaData updatedMetaData = adaptGlobalMetaData.apply(oldState.getLastAcceptedState().metaData()); final MetaData updatedMetaData = adaptGlobalMetaData.apply(oldState.getLastAcceptedState().metaData());
if (updatedMetaData != oldState.getLastAcceptedState().metaData()) {
metaStateService.writeGlobalStateAndUpdateManifest("update global state", updatedMetaData);
}
final long updatedTerm = adaptCurrentTerm.apply(oldState.getCurrentTerm()); final long updatedTerm = adaptCurrentTerm.apply(oldState.getCurrentTerm());
if (updatedTerm != oldState.getCurrentTerm()) { if (updatedMetaData != oldState.getLastAcceptedState().metaData() || updatedTerm != oldState.getCurrentTerm()) {
final Manifest manifest = metaStateService.loadManifestOrEmpty(); try (PersistedClusterStateService.Writer writer =
metaStateService.writeManifestAndCleanup("update term", new PersistedClusterStateService(nodeEnvironment, xContentRegistry(), BigArrays.NON_RECYCLING_INSTANCE)
new Manifest(updatedTerm, manifest.getClusterStateVersion(), manifest.getGlobalGeneration(), .createWriter()) {
manifest.getIndexGenerations())); writer.writeFullStateAndCommit(updatedTerm,
ClusterState.builder(oldState.getLastAcceptedState()).metaData(updatedMetaData).build());
}
} }
final MockGatewayMetaState gatewayMetaState = new MockGatewayMetaState(newLocalNode); final MockGatewayMetaState gatewayMetaState = new MockGatewayMetaState(newLocalNode);
gatewayMetaState.start(Settings.EMPTY, nodeEnvironment, xContentRegistry()); gatewayMetaState.start(Settings.EMPTY, nodeEnvironment, xContentRegistry());
@ -854,6 +852,11 @@ public class AbstractCoordinatorTestCase extends ESTestCase {
@Override @Override
public void close() { public void close() {
assertTrue(openPersistedStates.remove(this)); assertTrue(openPersistedStates.remove(this));
try {
delegate.close();
} catch (IOException e) {
throw new AssertionError("unexpected", e);
}
} }
} }

View File

@ -20,17 +20,23 @@
package org.elasticsearch.gateway; package org.elasticsearch.gateway;
import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.Manifest;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.cluster.metadata.MetaDataIndexUpgradeService; import org.elasticsearch.cluster.metadata.MetaDataIndexUpgradeService;
import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.settings.ClusterSettings; import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.env.NodeEnvironment;
import org.elasticsearch.plugins.MetaDataUpgrader; import org.elasticsearch.plugins.MetaDataUpgrader;
import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService; import org.elasticsearch.transport.TransportService;
import java.io.IOException;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -48,9 +54,10 @@ public class MockGatewayMetaState extends GatewayMetaState {
} }
@Override @Override
void upgradeMetaData(Settings settings, MetaStateService metaStateService, MetaDataIndexUpgradeService metaDataIndexUpgradeService, MetaData upgradeMetaDataForNode(MetaData metaData, MetaDataIndexUpgradeService metaDataIndexUpgradeService,
MetaDataUpgrader metaDataUpgrader) { MetaDataUpgrader metaDataUpgrader) {
// MetaData upgrade is tested in GatewayMetaStateTests, we override this method to NOP to make mocking easier // MetaData upgrade is tested in GatewayMetaStateTests, we override this method to NOP to make mocking easier
return metaData;
} }
@Override @Override
@ -65,7 +72,13 @@ public class MockGatewayMetaState extends GatewayMetaState {
final ClusterService clusterService = mock(ClusterService.class); final ClusterService clusterService = mock(ClusterService.class);
when(clusterService.getClusterSettings()) when(clusterService.getClusterSettings())
.thenReturn(new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS)); .thenReturn(new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS));
start(settings, transportService, clusterService, new MetaStateService(nodeEnvironment, xContentRegistry), final MetaStateService metaStateService = mock(MetaStateService.class);
null, null); try {
when(metaStateService.loadFullState()).thenReturn(new Tuple<>(Manifest.empty(), MetaData.builder().build()));
} catch (IOException e) {
throw new AssertionError(e);
}
start(settings, transportService, clusterService, metaStateService,
null, null, new PersistedClusterStateService(nodeEnvironment, xContentRegistry, BigArrays.NON_RECYCLING_INSTANCE));
} }
} }

View File

@ -73,6 +73,7 @@ import org.elasticsearch.common.unit.ByteSizeValue;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.PageCacheRecycler; import org.elasticsearch.common.util.PageCacheRecycler;
import org.elasticsearch.common.util.concurrent.EsExecutors; import org.elasticsearch.common.util.concurrent.EsExecutors;
import org.elasticsearch.common.util.concurrent.FutureUtils;
import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.core.internal.io.IOUtils; import org.elasticsearch.core.internal.io.IOUtils;
@ -1701,7 +1702,9 @@ public final class InternalTestCluster extends TestCluster {
} catch (InterruptedException e) { } catch (InterruptedException e) {
throw new AssertionError("interrupted while starting nodes", e); throw new AssertionError("interrupted while starting nodes", e);
} catch (ExecutionException e) { } catch (ExecutionException e) {
throw new RuntimeException("failed to start nodes", e); RuntimeException re = FutureUtils.rethrowExecutionException(e);
re.addSuppressed(new RuntimeException("failed to start nodes"));
throw re;
} }
nodeAndClients.forEach(this::publishNode); nodeAndClients.forEach(this::publishNode);