diff --git a/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/create/TransportCreateSnapshotAction.java b/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/create/TransportCreateSnapshotAction.java index 2654ac0c269..53b1bf86f66 100644 --- a/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/create/TransportCreateSnapshotAction.java +++ b/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/create/TransportCreateSnapshotAction.java @@ -26,10 +26,10 @@ import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.block.ClusterBlockException; import org.elasticsearch.cluster.block.ClusterBlockLevel; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; -import org.elasticsearch.cluster.metadata.SnapshotId; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.snapshots.Snapshot; import org.elasticsearch.snapshots.SnapshotInfo; import org.elasticsearch.snapshots.SnapshotsService; import org.elasticsearch.threadpool.ThreadPool; @@ -72,7 +72,7 @@ public class TransportCreateSnapshotAction extends TransportMasterNodeAction listener) { SnapshotsService.SnapshotRequest snapshotRequest = - new SnapshotsService.SnapshotRequest("create_snapshot [" + request.snapshot() + "]", request.snapshot(), request.repository()) + new SnapshotsService.SnapshotRequest(request.repository(), request.snapshot(), "create_snapshot [" + request.snapshot() + "]") .indices(request.indices()) .indicesOptions(request.indicesOptions()) .partial(request.partial()) @@ -84,19 +84,19 @@ public class TransportCreateSnapshotAction extends TransportMasterNodeAction listener) { - SnapshotId snapshotIds = new SnapshotId(request.repository(), request.snapshot()); - snapshotsService.deleteSnapshot(snapshotIds, new SnapshotsService.DeleteSnapshotListener() { + snapshotsService.deleteSnapshot(request.repository(), request.snapshot(), new SnapshotsService.DeleteSnapshotListener() { @Override public void onResponse() { listener.onResponse(new DeleteSnapshotResponse(true)); diff --git a/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsResponse.java b/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsResponse.java index ec996e6d366..924f5a90d42 100644 --- a/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsResponse.java +++ b/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/GetSnapshotsResponse.java @@ -42,7 +42,7 @@ public class GetSnapshotsResponse extends ActionResponse implements ToXContent { } GetSnapshotsResponse(List snapshots) { - this.snapshots = snapshots; + this.snapshots = Collections.unmodifiableList(snapshots); } /** diff --git a/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/TransportGetSnapshotsAction.java b/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/TransportGetSnapshotsAction.java index 833b1a62289..f734201e7bd 100644 --- a/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/TransportGetSnapshotsAction.java +++ b/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/TransportGetSnapshotsAction.java @@ -26,20 +26,22 @@ import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.block.ClusterBlockException; import org.elasticsearch.cluster.block.ClusterBlockLevel; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; -import org.elasticsearch.cluster.metadata.SnapshotId; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.regex.Regex; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.snapshots.SnapshotId; import org.elasticsearch.snapshots.SnapshotInfo; +import org.elasticsearch.snapshots.SnapshotMissingException; import org.elasticsearch.snapshots.SnapshotsService; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; import java.util.ArrayList; -import java.util.Collections; +import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -52,7 +54,8 @@ public class TransportGetSnapshotsAction extends TransportMasterNodeAction listener) { + protected void masterOperation(final GetSnapshotsRequest request, ClusterState state, + final ActionListener listener) { try { + final String repository = request.repository(); List snapshotInfoBuilder = new ArrayList<>(); if (isAllSnapshots(request.snapshots())) { - snapshotInfoBuilder.addAll(snapshotsService.snapshots(request.repository(), request.ignoreUnavailable())); + snapshotInfoBuilder.addAll(snapshotsService.currentSnapshots(repository)); + snapshotInfoBuilder.addAll(snapshotsService.snapshots(repository, + snapshotsService.snapshotIds(repository), + request.ignoreUnavailable())); } else if (isCurrentSnapshots(request.snapshots())) { - snapshotInfoBuilder.addAll(snapshotsService.currentSnapshots(request.repository())); + snapshotInfoBuilder.addAll(snapshotsService.currentSnapshots(repository)); } else { - Set snapshotsToGet = new LinkedHashSet<>(); // to keep insertion order - List snapshots = null; + final Map allSnapshotIds = new HashMap<>(); + for (SnapshotInfo snapshotInfo : snapshotsService.currentSnapshots(repository)) { + SnapshotId snapshotId = snapshotInfo.snapshotId(); + allSnapshotIds.put(snapshotId.getName(), snapshotId); + } + for (SnapshotId snapshotId : snapshotsService.snapshotIds(repository)) { + allSnapshotIds.put(snapshotId.getName(), snapshotId); + } + final Set toResolve = new LinkedHashSet<>(); // maintain order for (String snapshotOrPattern : request.snapshots()) { if (Regex.isSimpleMatchPattern(snapshotOrPattern) == false) { - snapshotsToGet.add(snapshotOrPattern); - } else { - if (snapshots == null) { // lazily load snapshots - snapshots = snapshotsService.snapshots(request.repository(), request.ignoreUnavailable()); + if (allSnapshotIds.containsKey(snapshotOrPattern)) { + toResolve.add(allSnapshotIds.get(snapshotOrPattern)); + } else if (request.ignoreUnavailable() == false) { + throw new SnapshotMissingException(repository, snapshotOrPattern); } - for (SnapshotInfo snapshot : snapshots) { - if (Regex.simpleMatch(snapshotOrPattern, snapshot.name())) { - snapshotsToGet.add(snapshot.name()); + } else { + for (Map.Entry entry : allSnapshotIds.entrySet()) { + if (Regex.simpleMatch(snapshotOrPattern, entry.getKey())) { + toResolve.add(entry.getValue()); } } } } - for (String snapshot : snapshotsToGet) { - SnapshotId snapshotId = new SnapshotId(request.repository(), snapshot); - snapshotInfoBuilder.add(snapshotsService.snapshot(snapshotId)); + + if (toResolve.isEmpty() && request.ignoreUnavailable() == false) { + throw new SnapshotMissingException(repository, request.snapshots()[0]); } + + snapshotInfoBuilder.addAll(snapshotsService.snapshots(repository, new ArrayList<>(toResolve), request.ignoreUnavailable())); } - listener.onResponse(new GetSnapshotsResponse(Collections.unmodifiableList(snapshotInfoBuilder))); + listener.onResponse(new GetSnapshotsResponse(snapshotInfoBuilder)); } catch (Throwable t) { listener.onFailure(t); } diff --git a/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/restore/TransportRestoreSnapshotAction.java b/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/restore/TransportRestoreSnapshotAction.java index cbbc195370c..a38fbce46c2 100644 --- a/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/restore/TransportRestoreSnapshotAction.java +++ b/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/restore/TransportRestoreSnapshotAction.java @@ -26,12 +26,12 @@ import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.block.ClusterBlockException; import org.elasticsearch.cluster.block.ClusterBlockLevel; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; -import org.elasticsearch.cluster.metadata.SnapshotId; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.snapshots.RestoreInfo; import org.elasticsearch.snapshots.RestoreService; +import org.elasticsearch.snapshots.Snapshot; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; @@ -72,23 +72,22 @@ public class TransportRestoreSnapshotAction extends TransportMasterNodeAction listener) { - RestoreService.RestoreRequest restoreRequest = new RestoreService.RestoreRequest( - "restore_snapshot[" + request.snapshot() + "]", request.repository(), request.snapshot(), + protected void masterOperation(final RestoreSnapshotRequest request, final ClusterState state, final ActionListener listener) { + RestoreService.RestoreRequest restoreRequest = new RestoreService.RestoreRequest(request.repository(), request.snapshot(), request.indices(), request.indicesOptions(), request.renamePattern(), request.renameReplacement(), request.settings(), request.masterNodeTimeout(), request.includeGlobalState(), request.partial(), request.includeAliases(), - request.indexSettings(), request.ignoreIndexSettings()); + request.indexSettings(), request.ignoreIndexSettings(), "restore_snapshot[" + request.snapshot() + "]"); restoreService.restoreSnapshot(restoreRequest, new ActionListener() { @Override public void onResponse(RestoreInfo restoreInfo) { if (restoreInfo == null && request.waitForCompletion()) { restoreService.addListener(new ActionListener() { - SnapshotId snapshotId = new SnapshotId(request.repository(), request.snapshot()); - @Override public void onResponse(RestoreService.RestoreCompletionResponse restoreCompletionResponse) { - if (this.snapshotId.equals(restoreCompletionResponse.getSnapshotId())) { + final Snapshot snapshot = restoreCompletionResponse.getSnapshot(); + if (snapshot.getRepository().equals(request.repository()) && + snapshot.getSnapshotId().getName().equals(request.snapshot())) { listener.onResponse(new RestoreSnapshotResponse(restoreCompletionResponse.getRestoreInfo())); restoreService.removeListener(this); } diff --git a/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/status/SnapshotStatus.java b/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/status/SnapshotStatus.java index 54f87f3c5fc..1a5ef9ab933 100644 --- a/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/status/SnapshotStatus.java +++ b/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/status/SnapshotStatus.java @@ -20,7 +20,7 @@ package org.elasticsearch.action.admin.cluster.snapshots.status; import org.elasticsearch.cluster.SnapshotsInProgress.State; -import org.elasticsearch.cluster.metadata.SnapshotId; +import org.elasticsearch.snapshots.Snapshot; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Streamable; @@ -35,6 +35,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import static java.util.Collections.unmodifiableMap; @@ -44,7 +45,7 @@ import static java.util.Collections.unmodifiableMap; */ public class SnapshotStatus implements ToXContent, Streamable { - private SnapshotId snapshotId; + private Snapshot snapshot; private State state; @@ -56,11 +57,10 @@ public class SnapshotStatus implements ToXContent, Streamable { private SnapshotStats stats; - - SnapshotStatus(SnapshotId snapshotId, State state, List shards) { - this.snapshotId = snapshotId; - this.state = state; - this.shards = shards; + SnapshotStatus(final Snapshot snapshot, final State state, final List shards) { + this.snapshot = Objects.requireNonNull(snapshot); + this.state = Objects.requireNonNull(state); + this.shards = Objects.requireNonNull(shards); shardsStats = new SnapshotShardsStats(shards); updateShardStats(); } @@ -69,10 +69,10 @@ public class SnapshotStatus implements ToXContent, Streamable { } /** - * Returns snapshot id + * Returns snapshot */ - public SnapshotId getSnapshotId() { - return snapshotId; + public Snapshot getSnapshot() { + return snapshot; } /** @@ -124,7 +124,7 @@ public class SnapshotStatus implements ToXContent, Streamable { @Override public void readFrom(StreamInput in) throws IOException { - snapshotId = SnapshotId.readSnapshotId(in); + snapshot = new Snapshot(in); state = State.fromValue(in.readByte()); int size = in.readVInt(); List builder = new ArrayList<>(); @@ -137,7 +137,7 @@ public class SnapshotStatus implements ToXContent, Streamable { @Override public void writeTo(StreamOutput out) throws IOException { - snapshotId.writeTo(out); + snapshot.writeTo(out); out.writeByte(state.value()); out.writeVInt(shards.size()); for (SnapshotIndexShardStatus shard : shards) { @@ -170,7 +170,6 @@ public class SnapshotStatus implements ToXContent, Streamable { } } - /** * Returns number of files in the snapshot */ @@ -178,22 +177,22 @@ public class SnapshotStatus implements ToXContent, Streamable { return stats; } - static final class Fields { - static final String SNAPSHOT = "snapshot"; - static final String REPOSITORY = "repository"; - static final String STATE = "state"; - static final String INDICES = "indices"; - } + private static final String SNAPSHOT = "snapshot"; + private static final String REPOSITORY = "repository"; + private static final String UUID = "uuid"; + private static final String STATE = "state"; + private static final String INDICES = "indices"; @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(); - builder.field(Fields.SNAPSHOT, snapshotId.getSnapshot()); - builder.field(Fields.REPOSITORY, snapshotId.getRepository()); - builder.field(Fields.STATE, state.name()); + builder.field(SNAPSHOT, snapshot.getSnapshotId().getName()); + builder.field(REPOSITORY, snapshot.getRepository()); + builder.field(UUID, snapshot.getSnapshotId().getUUID()); + builder.field(STATE, state.name()); shardsStats.toXContent(builder, params); stats.toXContent(builder, params); - builder.startObject(Fields.INDICES); + builder.startObject(INDICES); for (SnapshotIndexStatus indexStatus : getIndices().values()) { indexStatus.toXContent(builder, params); } diff --git a/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/status/TransportNodesSnapshotsStatus.java b/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/status/TransportNodesSnapshotsStatus.java index bc139389460..8e7361c1928 100644 --- a/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/status/TransportNodesSnapshotsStatus.java +++ b/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/status/TransportNodesSnapshotsStatus.java @@ -29,7 +29,7 @@ import org.elasticsearch.action.support.nodes.BaseNodesResponse; import org.elasticsearch.action.support.nodes.TransportNodesAction; import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; -import org.elasticsearch.cluster.metadata.SnapshotId; +import org.elasticsearch.snapshots.Snapshot; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.inject.Inject; @@ -94,11 +94,11 @@ public class TransportNodesSnapshotsStatus extends TransportNodesAction> snapshotMapBuilder = new HashMap<>(); + Map> snapshotMapBuilder = new HashMap<>(); try { String nodeId = clusterService.localNode().getId(); - for (SnapshotId snapshotId : request.snapshotIds) { - Map shardsStatus = snapshotShardsService.currentSnapshotShards(snapshotId); + for (Snapshot snapshot : request.snapshots) { + Map shardsStatus = snapshotShardsService.currentSnapshotShards(snapshot); if (shardsStatus == null) { continue; } @@ -114,7 +114,7 @@ public class TransportNodesSnapshotsStatus extends TransportNodesAction { - private SnapshotId[] snapshotIds; + private Snapshot[] snapshots; public Request() { } @@ -138,8 +138,8 @@ public class TransportNodesSnapshotsStatus extends TransportNodesAction snapshotIds; + private List snapshots; public NodeRequest() { } NodeRequest(String nodeId, TransportNodesSnapshotsStatus.Request request) { super(nodeId); - snapshotIds = Arrays.asList(request.snapshotIds); + snapshots = Arrays.asList(request.snapshots); } @Override public void readFrom(StreamInput in) throws IOException { super.readFrom(in); - snapshotIds = in.readList(SnapshotId::readSnapshotId); + snapshots = in.readList(Snapshot::new); } @Override public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); - out.writeStreamableList(snapshotIds); + out.writeList(snapshots); } } public static class NodeSnapshotStatus extends BaseNodeResponse { - private Map> status; + private Map> status; NodeSnapshotStatus() { } - public NodeSnapshotStatus(DiscoveryNode node, Map> status) { + public NodeSnapshotStatus(DiscoveryNode node, Map> status) { super(node); this.status = status; } - public Map> status() { + public Map> status() { return status; } @@ -222,9 +222,9 @@ public class TransportNodesSnapshotsStatus extends TransportNodesAction> snapshotMapBuilder = new HashMap<>(numberOfSnapshots); + Map> snapshotMapBuilder = new HashMap<>(numberOfSnapshots); for (int i = 0; i < numberOfSnapshots; i++) { - SnapshotId snapshotId = SnapshotId.readSnapshotId(in); + Snapshot snapshot = new Snapshot(in); int numberOfShards = in.readVInt(); Map shardMapBuilder = new HashMap<>(numberOfShards); for (int j = 0; j < numberOfShards; j++) { @@ -232,7 +232,7 @@ public class TransportNodesSnapshotsStatus extends TransportNodesAction> entry : status.entrySet()) { + for (Map.Entry> entry : status.entrySet()) { entry.getKey().writeTo(out); out.writeVInt(entry.getValue().size()); for (Map.Entry shardEntry : entry.getValue().entrySet()) { diff --git a/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/status/TransportSnapshotsStatusAction.java b/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/status/TransportSnapshotsStatusAction.java index ee60a919da6..afbb2179c0a 100644 --- a/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/status/TransportSnapshotsStatusAction.java +++ b/core/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/status/TransportSnapshotsStatusAction.java @@ -29,26 +29,32 @@ import org.elasticsearch.cluster.SnapshotsInProgress; import org.elasticsearch.cluster.block.ClusterBlockException; import org.elasticsearch.cluster.block.ClusterBlockLevel; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; -import org.elasticsearch.cluster.metadata.SnapshotId; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.Strings; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.index.snapshots.IndexShardSnapshotStatus; +import org.elasticsearch.snapshots.Snapshot; +import org.elasticsearch.snapshots.SnapshotId; import org.elasticsearch.snapshots.SnapshotInfo; +import org.elasticsearch.snapshots.SnapshotMissingException; import org.elasticsearch.snapshots.SnapshotsService; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; /** */ @@ -87,8 +93,8 @@ public class TransportSnapshotsStatusAction extends TransportMasterNodeAction listener) throws Exception { - List currentSnapshots = snapshotsService.currentSnapshots(request.repository(), request.snapshots()); - + List currentSnapshots = + snapshotsService.currentSnapshots(request.repository(), Arrays.asList(request.snapshots())); if (currentSnapshots.isEmpty()) { listener.onResponse(buildResponse(request, currentSnapshots, null)); return; @@ -105,19 +111,19 @@ public class TransportSnapshotsStatusAction extends TransportMasterNodeAction() { @Override public void onResponse(TransportNodesSnapshotsStatus.NodesSnapshotStatus nodeSnapshotStatuses) { try { List currentSnapshots = - snapshotsService.currentSnapshots(request.repository(), request.snapshots()); + snapshotsService.currentSnapshots(request.repository(), Arrays.asList(request.snapshots())); listener.onResponse(buildResponse(request, currentSnapshots, nodeSnapshotStatuses)); } catch (Throwable e) { listener.onFailure(e); @@ -136,12 +142,12 @@ public class TransportSnapshotsStatusAction extends TransportMasterNodeAction currentSnapshots, + private SnapshotsStatusResponse buildResponse(SnapshotsStatusRequest request, List currentSnapshotEntries, TransportNodesSnapshotsStatus.NodesSnapshotStatus nodeSnapshotStatuses) throws IOException { // First process snapshot that are currently processed List builder = new ArrayList<>(); - Set currentSnapshotIds = new HashSet<>(); - if (!currentSnapshots.isEmpty()) { + Set currentSnapshotNames = new HashSet<>(); + if (!currentSnapshotEntries.isEmpty()) { Map nodeSnapshotStatusMap; if (nodeSnapshotStatuses != null) { nodeSnapshotStatusMap = nodeSnapshotStatuses.getNodesMap(); @@ -149,8 +155,8 @@ public class TransportSnapshotsStatusAction extends TransportMasterNodeAction(); } - for (SnapshotsInProgress.Entry entry : currentSnapshots) { - currentSnapshotIds.add(entry.snapshotId()); + for (SnapshotsInProgress.Entry entry : currentSnapshotEntries) { + currentSnapshotNames.add(entry.snapshot().getSnapshotId().getName()); List shardStatusBuilder = new ArrayList<>(); for (ObjectObjectCursor shardEntry : entry.shards()) { SnapshotsInProgress.ShardSnapshotStatus status = shardEntry.value; @@ -158,7 +164,7 @@ public class TransportSnapshotsStatusAction extends TransportMasterNodeAction shardStatues = nodeStatus.status().get(entry.snapshotId()); + Map shardStatues = nodeStatus.status().get(entry.snapshot()); if (shardStatues != null) { SnapshotIndexShardStatus shardStatus = shardStatues.get(shardEntry.key); if (shardStatus != null) { @@ -190,41 +196,50 @@ public class TransportSnapshotsStatusAction extends TransportMasterNodeAction 0) { - for (String snapshotName : request.snapshots()) { - SnapshotId snapshotId = new SnapshotId(request.repository(), snapshotName); - if (currentSnapshotIds.contains(snapshotId)) { - // This is a snapshot the is currently running - skipping + final String repositoryName = request.repository(); + if (Strings.hasText(repositoryName) && request.snapshots() != null && request.snapshots().length > 0) { + final Set requestedSnapshotNames = Sets.newHashSet(request.snapshots()); + final Map matchedSnapshotIds = snapshotsService.snapshotIds(repositoryName).stream() + .filter(s -> requestedSnapshotNames.contains(s.getName())) + .collect(Collectors.toMap(SnapshotId::getName, Function.identity())); + for (final String snapshotName : request.snapshots()) { + SnapshotId snapshotId = matchedSnapshotIds.get(snapshotName); + if (snapshotId == null) { + if (currentSnapshotNames.contains(snapshotName)) { + // we've already found this snapshot in the current snapshot entries, so skip over continue; + } else { + // neither in the current snapshot entries nor found in the repository + throw new SnapshotMissingException(repositoryName, snapshotName); } - SnapshotInfo snapshot = snapshotsService.snapshot(snapshotId); - List shardStatusBuilder = new ArrayList<>(); - if (snapshot.state().completed()) { - Map shardStatues = snapshotsService.snapshotShards(snapshotId); - for (Map.Entry shardStatus : shardStatues.entrySet()) { - shardStatusBuilder.add(new SnapshotIndexShardStatus(shardStatus.getKey(), shardStatus.getValue())); - } - final SnapshotsInProgress.State state; - switch (snapshot.state()) { - case FAILED: - state = SnapshotsInProgress.State.FAILED; - break; - case SUCCESS: - case PARTIAL: - // Translating both PARTIAL and SUCCESS to SUCCESS for now - // TODO: add the differentiation on the metadata level in the next major release - state = SnapshotsInProgress.State.SUCCESS; - break; - default: - throw new IllegalArgumentException("Unknown snapshot state " + snapshot.state()); - } - builder.add(new SnapshotStatus(snapshotId, state, Collections.unmodifiableList(shardStatusBuilder))); + } + SnapshotInfo snapshotInfo = snapshotsService.snapshot(repositoryName, snapshotId); + List shardStatusBuilder = new ArrayList<>(); + if (snapshotInfo.state().completed()) { + Map shardStatues = + snapshotsService.snapshotShards(request.repository(), snapshotInfo); + for (Map.Entry shardStatus : shardStatues.entrySet()) { + shardStatusBuilder.add(new SnapshotIndexShardStatus(shardStatus.getKey(), shardStatus.getValue())); } + final SnapshotsInProgress.State state; + switch (snapshotInfo.state()) { + case FAILED: + state = SnapshotsInProgress.State.FAILED; + break; + case SUCCESS: + case PARTIAL: + // Translating both PARTIAL and SUCCESS to SUCCESS for now + // TODO: add the differentiation on the metadata level in the next major release + state = SnapshotsInProgress.State.SUCCESS; + break; + default: + throw new IllegalArgumentException("Unknown snapshot state " + snapshotInfo.state()); + } + builder.add(new SnapshotStatus(new Snapshot(repositoryName, snapshotInfo.snapshotId()), state, Collections.unmodifiableList(shardStatusBuilder))); } } } diff --git a/core/src/main/java/org/elasticsearch/cluster/RestoreInProgress.java b/core/src/main/java/org/elasticsearch/cluster/RestoreInProgress.java index a083476ea2f..55a09f87f75 100644 --- a/core/src/main/java/org/elasticsearch/cluster/RestoreInProgress.java +++ b/core/src/main/java/org/elasticsearch/cluster/RestoreInProgress.java @@ -21,7 +21,7 @@ package org.elasticsearch.cluster; import com.carrotsearch.hppc.cursors.ObjectObjectCursor; import org.elasticsearch.cluster.ClusterState.Custom; -import org.elasticsearch.cluster.metadata.SnapshotId; +import org.elasticsearch.snapshots.Snapshot; import org.elasticsearch.common.collect.ImmutableOpenMap; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; @@ -34,6 +34,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Objects; /** * Meta data about restore processes that are currently executing @@ -73,22 +74,6 @@ public class RestoreInProgress extends AbstractDiffable implements Custo return this.entries; } - /** - * Returns currently running restore process with corresponding snapshot id or null if this snapshot is not being - * restored - * - * @param snapshotId snapshot id - * @return restore metadata or null - */ - public Entry snapshot(SnapshotId snapshotId) { - for (Entry entry : entries) { - if (snapshotId.equals(entry.snapshotId())) { - return entry; - } - } - return null; - } - @Override public boolean equals(Object o) { if (this == o) return true; @@ -111,22 +96,22 @@ public class RestoreInProgress extends AbstractDiffable implements Custo */ public static class Entry { private final State state; - private final SnapshotId snapshotId; + private final Snapshot snapshot; private final ImmutableOpenMap shards; private final List indices; /** * Creates new restore metadata * - * @param snapshotId snapshot id + * @param snapshot snapshot * @param state current state of the restore process * @param indices list of indices being restored * @param shards map of shards being restored to their current restore status */ - public Entry(SnapshotId snapshotId, State state, List indices, ImmutableOpenMap shards) { - this.snapshotId = snapshotId; - this.state = state; - this.indices = indices; + public Entry(Snapshot snapshot, State state, List indices, ImmutableOpenMap shards) { + this.snapshot = Objects.requireNonNull(snapshot); + this.state = Objects.requireNonNull(state); + this.indices = Objects.requireNonNull(indices); if (shards == null) { this.shards = ImmutableOpenMap.of(); } else { @@ -135,12 +120,12 @@ public class RestoreInProgress extends AbstractDiffable implements Custo } /** - * Returns snapshot id + * Returns snapshot * - * @return snapshot id + * @return snapshot */ - public SnapshotId snapshotId() { - return this.snapshotId; + public Snapshot snapshot() { + return this.snapshot; } /** @@ -172,26 +157,22 @@ public class RestoreInProgress extends AbstractDiffable implements Custo @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Entry entry = (Entry) o; - - if (!indices.equals(entry.indices)) return false; - if (!snapshotId.equals(entry.snapshotId)) return false; - if (!shards.equals(entry.shards)) return false; - if (state != entry.state) return false; - - return true; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + @SuppressWarnings("unchecked") Entry entry = (Entry) o; + return snapshot.equals(entry.snapshot) && + state == entry.state && + indices.equals(entry.indices) && + shards.equals(entry.shards); } @Override public int hashCode() { - int result = state.hashCode(); - result = 31 * result + snapshotId.hashCode(); - result = 31 * result + shards.hashCode(); - result = 31 * result + indices.hashCode(); - return result; + return Objects.hash(snapshot, state, indices, shards); } } @@ -301,31 +282,29 @@ public class RestoreInProgress extends AbstractDiffable implements Custo @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } - ShardRestoreStatus status = (ShardRestoreStatus) o; - - if (nodeId != null ? !nodeId.equals(status.nodeId) : status.nodeId != null) return false; - if (reason != null ? !reason.equals(status.reason) : status.reason != null) return false; - if (state != status.state) return false; - - return true; + @SuppressWarnings("unchecked") ShardRestoreStatus status = (ShardRestoreStatus) o; + return state == status.state && + Objects.equals(nodeId, status.nodeId) && + Objects.equals(reason, status.reason); } @Override public int hashCode() { - int result = state != null ? state.hashCode() : 0; - result = 31 * result + (nodeId != null ? nodeId.hashCode() : 0); - result = 31 * result + (reason != null ? reason.hashCode() : 0); - return result; + return Objects.hash(state, nodeId, reason); } } /** * Shard restore process state */ - public static enum State { + public enum State { /** * Initializing state */ @@ -409,7 +388,7 @@ public class RestoreInProgress extends AbstractDiffable implements Custo public RestoreInProgress readFrom(StreamInput in) throws IOException { Entry[] entries = new Entry[in.readVInt()]; for (int i = 0; i < entries.length; i++) { - SnapshotId snapshotId = SnapshotId.readSnapshotId(in); + Snapshot snapshot = new Snapshot(in); State state = State.fromValue(in.readByte()); int indices = in.readVInt(); List indexBuilder = new ArrayList<>(); @@ -423,7 +402,7 @@ public class RestoreInProgress extends AbstractDiffable implements Custo ShardRestoreStatus shardState = ShardRestoreStatus.readShardRestoreStatus(in); builder.put(shardId, shardState); } - entries[i] = new Entry(snapshotId, state, Collections.unmodifiableList(indexBuilder), builder.build()); + entries[i] = new Entry(snapshot, state, Collections.unmodifiableList(indexBuilder), builder.build()); } return new RestoreInProgress(entries); } @@ -435,7 +414,7 @@ public class RestoreInProgress extends AbstractDiffable implements Custo public void writeTo(StreamOutput out) throws IOException { out.writeVInt(entries.size()); for (Entry entry : entries) { - entry.snapshotId().writeTo(out); + entry.snapshot().writeTo(out); out.writeByte(entry.state().value()); out.writeVInt(entry.indices().size()); for (String index : entry.indices()) { @@ -471,8 +450,8 @@ public class RestoreInProgress extends AbstractDiffable implements Custo */ public void toXContent(Entry entry, XContentBuilder builder, ToXContent.Params params) throws IOException { builder.startObject(); - builder.field("snapshot", entry.snapshotId().getSnapshot()); - builder.field("repository", entry.snapshotId().getRepository()); + builder.field("snapshot", entry.snapshot().getSnapshotId().getName()); + builder.field("repository", entry.snapshot().getRepository()); builder.field("state", entry.state()); builder.startArray("indices"); { diff --git a/core/src/main/java/org/elasticsearch/cluster/SnapshotsInProgress.java b/core/src/main/java/org/elasticsearch/cluster/SnapshotsInProgress.java index 5432c1f0f19..f0a0fdec665 100644 --- a/core/src/main/java/org/elasticsearch/cluster/SnapshotsInProgress.java +++ b/core/src/main/java/org/elasticsearch/cluster/SnapshotsInProgress.java @@ -23,13 +23,13 @@ import com.carrotsearch.hppc.ObjectContainer; import com.carrotsearch.hppc.cursors.ObjectCursor; import com.carrotsearch.hppc.cursors.ObjectObjectCursor; import org.elasticsearch.cluster.ClusterState.Custom; -import org.elasticsearch.cluster.metadata.SnapshotId; import org.elasticsearch.common.collect.ImmutableOpenMap; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.index.shard.ShardId; +import org.elasticsearch.snapshots.Snapshot; import java.io.IOException; import java.util.ArrayList; @@ -66,7 +66,7 @@ public class SnapshotsInProgress extends AbstractDiffable implements Cus public static class Entry { private final State state; - private final SnapshotId snapshotId; + private final Snapshot snapshot; private final boolean includeGlobalState; private final boolean partial; private final ImmutableOpenMap shards; @@ -74,9 +74,10 @@ public class SnapshotsInProgress extends AbstractDiffable implements Cus private final ImmutableOpenMap> waitingIndices; private final long startTime; - public Entry(SnapshotId snapshotId, boolean includeGlobalState, boolean partial, State state, List indices, long startTime, ImmutableOpenMap shards) { + public Entry(Snapshot snapshot, boolean includeGlobalState, boolean partial, State state, List indices, long startTime, + ImmutableOpenMap shards) { this.state = state; - this.snapshotId = snapshotId; + this.snapshot = snapshot; this.includeGlobalState = includeGlobalState; this.partial = partial; this.indices = indices; @@ -91,15 +92,15 @@ public class SnapshotsInProgress extends AbstractDiffable implements Cus } public Entry(Entry entry, State state, ImmutableOpenMap shards) { - this(entry.snapshotId, entry.includeGlobalState, entry.partial, state, entry.indices, entry.startTime, shards); + this(entry.snapshot, entry.includeGlobalState, entry.partial, state, entry.indices, entry.startTime, shards); } public Entry(Entry entry, ImmutableOpenMap shards) { this(entry, entry.state, shards); } - public SnapshotId snapshotId() { - return this.snapshotId; + public Snapshot snapshot() { + return this.snapshot; } public ImmutableOpenMap shards() { @@ -142,7 +143,7 @@ public class SnapshotsInProgress extends AbstractDiffable implements Cus if (startTime != entry.startTime) return false; if (!indices.equals(entry.indices)) return false; if (!shards.equals(entry.shards)) return false; - if (!snapshotId.equals(entry.snapshotId)) return false; + if (!snapshot.equals(entry.snapshot)) return false; if (state != entry.state) return false; if (!waitingIndices.equals(entry.waitingIndices)) return false; @@ -152,7 +153,7 @@ public class SnapshotsInProgress extends AbstractDiffable implements Cus @Override public int hashCode() { int result = state.hashCode(); - result = 31 * result + snapshotId.hashCode(); + result = 31 * result + snapshot.hashCode(); result = 31 * result + (includeGlobalState ? 1 : 0); result = 31 * result + (partial ? 1 : 0); result = 31 * result + shards.hashCode(); @@ -162,6 +163,11 @@ public class SnapshotsInProgress extends AbstractDiffable implements Cus return result; } + @Override + public String toString() { + return snapshot.toString(); + } + private ImmutableOpenMap> findWaitingIndices(ImmutableOpenMap shards) { Map> waitingIndicesMap = new HashMap<>(); for (ObjectObjectCursor entry : shards) { @@ -277,7 +283,7 @@ public class SnapshotsInProgress extends AbstractDiffable implements Cus } } - public static enum State { + public enum State { INIT((byte) 0, false, false), STARTED((byte) 1, false, false), SUCCESS((byte) 2, true, false), @@ -347,9 +353,10 @@ public class SnapshotsInProgress extends AbstractDiffable implements Cus return this.entries; } - public Entry snapshot(SnapshotId snapshotId) { + public Entry snapshot(final Snapshot snapshot) { for (Entry entry : entries) { - if (snapshotId.equals(entry.snapshotId())) { + final Snapshot curr = entry.snapshot(); + if (curr.equals(snapshot)) { return entry; } } @@ -365,7 +372,7 @@ public class SnapshotsInProgress extends AbstractDiffable implements Cus public SnapshotsInProgress readFrom(StreamInput in) throws IOException { Entry[] entries = new Entry[in.readVInt()]; for (int i = 0; i < entries.length; i++) { - SnapshotId snapshotId = SnapshotId.readSnapshotId(in); + Snapshot snapshot = new Snapshot(in); boolean includeGlobalState = in.readBoolean(); boolean partial = in.readBoolean(); State state = State.fromValue(in.readByte()); @@ -383,7 +390,13 @@ public class SnapshotsInProgress extends AbstractDiffable implements Cus State shardState = State.fromValue(in.readByte()); builder.put(shardId, new ShardSnapshotStatus(nodeId, shardState)); } - entries[i] = new Entry(snapshotId, includeGlobalState, partial, state, Collections.unmodifiableList(indexBuilder), startTime, builder.build()); + entries[i] = new Entry(snapshot, + includeGlobalState, + partial, + state, + Collections.unmodifiableList(indexBuilder), + startTime, + builder.build()); } return new SnapshotsInProgress(entries); } @@ -392,7 +405,7 @@ public class SnapshotsInProgress extends AbstractDiffable implements Cus public void writeTo(StreamOutput out) throws IOException { out.writeVInt(entries.size()); for (Entry entry : entries) { - entry.snapshotId().writeTo(out); + entry.snapshot().writeTo(out); out.writeBoolean(entry.includeGlobalState()); out.writeBoolean(entry.partial()); out.writeByte(entry.state().value()); @@ -410,25 +423,24 @@ public class SnapshotsInProgress extends AbstractDiffable implements Cus } } - static final class Fields { - static final String REPOSITORY = "repository"; - static final String SNAPSHOTS = "snapshots"; - static final String SNAPSHOT = "snapshot"; - static final String INCLUDE_GLOBAL_STATE = "include_global_state"; - static final String PARTIAL = "partial"; - static final String STATE = "state"; - static final String INDICES = "indices"; - static final String START_TIME_MILLIS = "start_time_millis"; - static final String START_TIME = "start_time"; - static final String SHARDS = "shards"; - static final String INDEX = "index"; - static final String SHARD = "shard"; - static final String NODE = "node"; - } + private static final String REPOSITORY = "repository"; + private static final String SNAPSHOTS = "snapshots"; + private static final String SNAPSHOT = "snapshot"; + private static final String UUID = "uuid"; + private static final String INCLUDE_GLOBAL_STATE = "include_global_state"; + private static final String PARTIAL = "partial"; + private static final String STATE = "state"; + private static final String INDICES = "indices"; + private static final String START_TIME_MILLIS = "start_time_millis"; + private static final String START_TIME = "start_time"; + private static final String SHARDS = "shards"; + private static final String INDEX = "index"; + private static final String SHARD = "shard"; + private static final String NODE = "node"; @Override public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException { - builder.startArray(Fields.SNAPSHOTS); + builder.startArray(SNAPSHOTS); for (Entry entry : entries) { toXContent(entry, builder, params); } @@ -438,30 +450,31 @@ public class SnapshotsInProgress extends AbstractDiffable implements Cus public void toXContent(Entry entry, XContentBuilder builder, ToXContent.Params params) throws IOException { builder.startObject(); - builder.field(Fields.REPOSITORY, entry.snapshotId().getRepository()); - builder.field(Fields.SNAPSHOT, entry.snapshotId().getSnapshot()); - builder.field(Fields.INCLUDE_GLOBAL_STATE, entry.includeGlobalState()); - builder.field(Fields.PARTIAL, entry.partial()); - builder.field(Fields.STATE, entry.state()); - builder.startArray(Fields.INDICES); + builder.field(REPOSITORY, entry.snapshot().getRepository()); + builder.field(SNAPSHOT, entry.snapshot().getSnapshotId().getName()); + builder.field(UUID, entry.snapshot().getSnapshotId().getUUID()); + builder.field(INCLUDE_GLOBAL_STATE, entry.includeGlobalState()); + builder.field(PARTIAL, entry.partial()); + builder.field(STATE, entry.state()); + builder.startArray(INDICES); { for (String index : entry.indices()) { builder.value(index); } } builder.endArray(); - builder.timeValueField(Fields.START_TIME_MILLIS, Fields.START_TIME, entry.startTime()); - builder.startArray(Fields.SHARDS); + builder.timeValueField(START_TIME_MILLIS, START_TIME, entry.startTime()); + builder.startArray(SHARDS); { for (ObjectObjectCursor shardEntry : entry.shards) { ShardId shardId = shardEntry.key; ShardSnapshotStatus status = shardEntry.value; builder.startObject(); { - builder.field(Fields.INDEX, shardId.getIndex()); - builder.field(Fields.SHARD, shardId.getId()); - builder.field(Fields.STATE, status.state()); - builder.field(Fields.NODE, status.nodeId()); + builder.field(INDEX, shardId.getIndex()); + builder.field(SHARD, shardId.getId()); + builder.field(STATE, status.state()); + builder.field(NODE, status.nodeId()); } builder.endObject(); } diff --git a/core/src/main/java/org/elasticsearch/cluster/metadata/SnapshotId.java b/core/src/main/java/org/elasticsearch/cluster/metadata/SnapshotId.java deleted file mode 100644 index 88c60f1f07e..00000000000 --- a/core/src/main/java/org/elasticsearch/cluster/metadata/SnapshotId.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * 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.metadata; - -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.common.io.stream.Streamable; - -import java.io.IOException; - -/** - * Snapshot ID - repository name + snapshot name - */ -public class SnapshotId implements Streamable { - - private String repository; - - private String snapshot; - - // Caching hash code - private int hashCode; - - private SnapshotId() { - } - - /** - * Constructs new snapshot id - * - * @param repository repository name - * @param snapshot snapshot name - */ - public SnapshotId(String repository, String snapshot) { - this.repository = repository; - this.snapshot = snapshot; - this.hashCode = computeHashCode(); - } - - /** - * Returns repository name - * - * @return repository name - */ - public String getRepository() { - return repository; - } - - /** - * Returns snapshot name - * - * @return snapshot name - */ - public String getSnapshot() { - return snapshot; - } - - @Override - public String toString() { - return repository + ":" + snapshot; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null) return false; - SnapshotId snapshotId = (SnapshotId) o; - return snapshot.equals(snapshotId.snapshot) && repository.equals(snapshotId.repository); - } - - @Override - public int hashCode() { - return hashCode; - } - - private int computeHashCode() { - int result = repository != null ? repository.hashCode() : 0; - result = 31 * result + snapshot.hashCode(); - return result; - } - - /** - * Reads snapshot id from stream input - * - * @param in stream input - * @return snapshot id - */ - public static SnapshotId readSnapshotId(StreamInput in) throws IOException { - SnapshotId snapshot = new SnapshotId(); - snapshot.readFrom(in); - return snapshot; - } - - /** - * {@inheritDoc} - */ - @Override - public void readFrom(StreamInput in) throws IOException { - repository = in.readString(); - snapshot = in.readString(); - hashCode = computeHashCode(); - } - - /** - * {@inheritDoc} - */ - @Override - public void writeTo(StreamOutput out) throws IOException { - out.writeString(repository); - out.writeString(snapshot); - } -} diff --git a/core/src/main/java/org/elasticsearch/cluster/routing/IndexRoutingTable.java b/core/src/main/java/org/elasticsearch/cluster/routing/IndexRoutingTable.java index 8bb0f51e22e..0fe49369177 100644 --- a/core/src/main/java/org/elasticsearch/cluster/routing/IndexRoutingTable.java +++ b/core/src/main/java/org/elasticsearch/cluster/routing/IndexRoutingTable.java @@ -376,14 +376,20 @@ public class IndexRoutingTable extends AbstractDiffable imple * Initializes a new empty index, to be restored from a snapshot */ public Builder initializeAsNewRestore(IndexMetaData indexMetaData, RestoreSource restoreSource, IntSet ignoreShards) { - return initializeAsRestore(indexMetaData, restoreSource, ignoreShards, true, new UnassignedInfo(UnassignedInfo.Reason.NEW_INDEX_RESTORED, "restore_source[" + restoreSource.snapshotId().getRepository() + "/" + restoreSource.snapshotId().getSnapshot() + "]")); + final UnassignedInfo unassignedInfo = new UnassignedInfo(UnassignedInfo.Reason.NEW_INDEX_RESTORED, + "restore_source[" + restoreSource.snapshot().getRepository() + "/" + + restoreSource.snapshot().getSnapshotId().getName() + "]"); + return initializeAsRestore(indexMetaData, restoreSource, ignoreShards, true, unassignedInfo); } /** * Initializes an existing index, to be restored from a snapshot */ public Builder initializeAsRestore(IndexMetaData indexMetaData, RestoreSource restoreSource) { - return initializeAsRestore(indexMetaData, restoreSource, null, false, new UnassignedInfo(UnassignedInfo.Reason.EXISTING_INDEX_RESTORED, "restore_source[" + restoreSource.snapshotId().getRepository() + "/" + restoreSource.snapshotId().getSnapshot() + "]")); + final UnassignedInfo unassignedInfo = new UnassignedInfo(UnassignedInfo.Reason.EXISTING_INDEX_RESTORED, + "restore_source[" + restoreSource.snapshot().getRepository() + "/" + + restoreSource.snapshot().getSnapshotId().getName() + "]"); + return initializeAsRestore(indexMetaData, restoreSource, null, false, unassignedInfo); } /** diff --git a/core/src/main/java/org/elasticsearch/cluster/routing/RestoreSource.java b/core/src/main/java/org/elasticsearch/cluster/routing/RestoreSource.java index c091f71798b..f80e55ed8b3 100644 --- a/core/src/main/java/org/elasticsearch/cluster/routing/RestoreSource.java +++ b/core/src/main/java/org/elasticsearch/cluster/routing/RestoreSource.java @@ -20,7 +20,7 @@ package org.elasticsearch.cluster.routing; import org.elasticsearch.Version; -import org.elasticsearch.cluster.metadata.SnapshotId; +import org.elasticsearch.snapshots.Snapshot; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Streamable; @@ -28,13 +28,14 @@ import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import java.io.IOException; +import java.util.Objects; /** * Represents snapshot and index from which a recovering index should be restored */ public class RestoreSource implements Streamable, ToXContent { - private SnapshotId snapshotId; + private Snapshot snapshot; private String index; @@ -43,14 +44,14 @@ public class RestoreSource implements Streamable, ToXContent { RestoreSource() { } - public RestoreSource(SnapshotId snapshotId, Version version, String index) { - this.snapshotId = snapshotId; - this.version = version; - this.index = index; + public RestoreSource(Snapshot snapshot, Version version, String index) { + this.snapshot = Objects.requireNonNull(snapshot); + this.version = Objects.requireNonNull(version); + this.index = Objects.requireNonNull(index); } - public SnapshotId snapshotId() { - return snapshotId; + public Snapshot snapshot() { + return snapshot; } public String index() { @@ -61,26 +62,20 @@ public class RestoreSource implements Streamable, ToXContent { return version; } - public static RestoreSource readRestoreSource(StreamInput in) throws IOException { - RestoreSource restoreSource = new RestoreSource(); - restoreSource.readFrom(in); - return restoreSource; - } - public static RestoreSource readOptionalRestoreSource(StreamInput in) throws IOException { return in.readOptionalStreamable(RestoreSource::new); } @Override public void readFrom(StreamInput in) throws IOException { - snapshotId = SnapshotId.readSnapshotId(in); + snapshot = new Snapshot(in); version = Version.readVersion(in); index = in.readString(); } @Override public void writeTo(StreamOutput out) throws IOException { - snapshotId.writeTo(out); + snapshot.writeTo(out); Version.writeVersion(version, out); out.writeString(index); } @@ -88,8 +83,8 @@ public class RestoreSource implements Streamable, ToXContent { @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { return builder.startObject() - .field("repository", snapshotId.getRepository()) - .field("snapshot", snapshotId.getSnapshot()) + .field("repository", snapshot.getRepository()) + .field("snapshot", snapshot.getSnapshotId().getName()) .field("version", version.toString()) .field("index", index) .endObject(); @@ -97,26 +92,24 @@ public class RestoreSource implements Streamable, ToXContent { @Override public String toString() { - return snapshotId.toString(); + return snapshot.toString(); } @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } - RestoreSource that = (RestoreSource) o; - - if (!index.equals(that.index)) return false; - if (!snapshotId.equals(that.snapshotId)) return false; - - return true; + @SuppressWarnings("unchecked") RestoreSource that = (RestoreSource) o; + return snapshot.equals(that.snapshot) && index.equals(that.index); } @Override public int hashCode() { - int result = snapshotId.hashCode(); - result = 31 * result + index.hashCode(); - return result; + return Objects.hash(snapshot, index); } } diff --git a/core/src/main/java/org/elasticsearch/index/shard/IndexShard.java b/core/src/main/java/org/elasticsearch/index/shard/IndexShard.java index adcf36f694f..44445f0b6dd 100644 --- a/core/src/main/java/org/elasticsearch/index/shard/IndexShard.java +++ b/core/src/main/java/org/elasticsearch/index/shard/IndexShard.java @@ -39,7 +39,6 @@ import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.MappingMetaData; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.routing.RestoreSource; -import org.elasticsearch.client.Client; import org.elasticsearch.cluster.routing.ShardRouting; import org.elasticsearch.cluster.routing.ShardRoutingState; import org.elasticsearch.common.Booleans; @@ -1442,7 +1441,7 @@ public class IndexShard extends AbstractIndexShardComponent { markAsRecovering("from snapshot", recoveryState); // mark the shard as recovering on the cluster state thread threadPool.generic().execute(() -> { try { - final IndexShardRepository indexShardRepository = repositoriesService.indexShardRepository(restoreSource.snapshotId().getRepository()); + final IndexShardRepository indexShardRepository = repositoriesService.indexShardRepository(restoreSource.snapshot().getRepository()); if (restoreFromRepository(indexShardRepository)) { recoveryListener.onRecoveryDone(recoveryState); } diff --git a/core/src/main/java/org/elasticsearch/index/shard/StoreRecovery.java b/core/src/main/java/org/elasticsearch/index/shard/StoreRecovery.java index c7d6f35eff2..62173f936c5 100644 --- a/core/src/main/java/org/elasticsearch/index/shard/StoreRecovery.java +++ b/core/src/main/java/org/elasticsearch/index/shard/StoreRecovery.java @@ -395,7 +395,7 @@ final class StoreRecovery { throw new IndexShardRestoreFailedException(shardId, "empty restore source"); } if (logger.isTraceEnabled()) { - logger.trace("[{}] restoring shard [{}]", restoreSource.snapshotId(), shardId); + logger.trace("[{}] restoring shard [{}]", restoreSource.snapshot(), shardId); } try { translogState.totalOperations(0); @@ -405,7 +405,7 @@ final class StoreRecovery { if (!shardId.getIndexName().equals(restoreSource.index())) { snapshotShardId = new ShardId(restoreSource.index(), IndexMetaData.INDEX_UUID_NA_VALUE, shardId.id()); } - indexShardRepository.restore(restoreSource.snapshotId(), restoreSource.version(), shardId, snapshotShardId, indexShard.recoveryState()); + indexShardRepository.restore(restoreSource.snapshot().getSnapshotId(), restoreSource.version(), shardId, snapshotShardId, indexShard.recoveryState()); indexShard.skipTranslogRecovery(); indexShard.finalizeRecovery(); indexShard.postRecovery("restore done"); diff --git a/core/src/main/java/org/elasticsearch/index/snapshots/IndexShardRepository.java b/core/src/main/java/org/elasticsearch/index/snapshots/IndexShardRepository.java index ca481e1430d..5988d82def2 100644 --- a/core/src/main/java/org/elasticsearch/index/snapshots/IndexShardRepository.java +++ b/core/src/main/java/org/elasticsearch/index/snapshots/IndexShardRepository.java @@ -21,7 +21,7 @@ package org.elasticsearch.index.snapshots; import org.apache.lucene.index.IndexCommit; import org.elasticsearch.Version; -import org.elasticsearch.cluster.metadata.SnapshotId; +import org.elasticsearch.snapshots.SnapshotId; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.indices.recovery.RecoveryState; diff --git a/core/src/main/java/org/elasticsearch/index/snapshots/blobstore/BlobStoreIndexShardRepository.java b/core/src/main/java/org/elasticsearch/index/snapshots/blobstore/BlobStoreIndexShardRepository.java index c15d2cfcdbe..e0032fe503b 100644 --- a/core/src/main/java/org/elasticsearch/index/snapshots/blobstore/BlobStoreIndexShardRepository.java +++ b/core/src/main/java/org/elasticsearch/index/snapshots/blobstore/BlobStoreIndexShardRepository.java @@ -32,7 +32,7 @@ import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRefBuilder; import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.Version; -import org.elasticsearch.cluster.metadata.SnapshotId; +import org.elasticsearch.snapshots.SnapshotId; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.ParseFieldMatcher; @@ -204,7 +204,7 @@ public class BlobStoreIndexShardRepository extends AbstractComponent implements try { snapshotContext.restore(); } catch (Throwable e) { - throw new IndexShardRestoreFailedException(shardId, "failed to restore snapshot [" + snapshotId.getSnapshot() + "]", e); + throw new IndexShardRestoreFailedException(shardId, "failed to restore snapshot [" + snapshotId + "]", e); } } @@ -318,7 +318,7 @@ public class BlobStoreIndexShardRepository extends AbstractComponent implements int fileListGeneration = tuple.v2(); try { - indexShardSnapshotFormat(version).delete(blobContainer, snapshotId.getSnapshot()); + indexShardSnapshotFormat(version).delete(blobContainer, snapshotId.getName()); } catch (IOException e) { logger.debug("[{}] [{}] failed to delete shard snapshot file", shardId, snapshotId); } @@ -326,7 +326,7 @@ public class BlobStoreIndexShardRepository extends AbstractComponent implements // Build a list of snapshots that should be preserved List newSnapshotsList = new ArrayList<>(); for (SnapshotFiles point : snapshots) { - if (!point.snapshot().equals(snapshotId.getSnapshot())) { + if (!point.snapshot().equals(snapshotId.getName())) { newSnapshotsList.add(point); } } @@ -339,7 +339,7 @@ public class BlobStoreIndexShardRepository extends AbstractComponent implements */ public BlobStoreIndexShardSnapshot loadSnapshot() { try { - return indexShardSnapshotFormat(version).read(blobContainer, snapshotId.getSnapshot()); + return indexShardSnapshotFormat(version).read(blobContainer, snapshotId.getName()); } catch (IOException ex) { throw new IndexShardRestoreFailedException(shardId, "failed to read shard snapshot file", ex); } @@ -605,14 +605,14 @@ public class BlobStoreIndexShardRepository extends AbstractComponent implements // now create and write the commit point snapshotStatus.updateStage(IndexShardSnapshotStatus.Stage.FINALIZE); - BlobStoreIndexShardSnapshot snapshot = new BlobStoreIndexShardSnapshot(snapshotId.getSnapshot(), + BlobStoreIndexShardSnapshot snapshot = new BlobStoreIndexShardSnapshot(snapshotId.getName(), snapshotIndexCommit.getGeneration(), indexCommitPointFiles, snapshotStatus.startTime(), // snapshotStatus.startTime() is assigned on the same machine, so it's safe to use with VLong System.currentTimeMillis() - snapshotStatus.startTime(), indexNumberOfFiles, indexTotalFilesSize); //TODO: The time stored in snapshot doesn't include cleanup time. logger.trace("[{}] [{}] writing shard snapshot file", shardId, snapshotId); try { - indexShardSnapshotFormat.write(snapshot, blobContainer, snapshotId.getSnapshot()); + indexShardSnapshotFormat.write(snapshot, blobContainer, snapshotId.getName()); } catch (IOException e) { throw new IndexShardSnapshotFailedException(shardId, "Failed to write commit point", e); } diff --git a/core/src/main/java/org/elasticsearch/indices/cluster/IndicesClusterStateService.java b/core/src/main/java/org/elasticsearch/indices/cluster/IndicesClusterStateService.java index dcf055275e5..719fb812c74 100644 --- a/core/src/main/java/org/elasticsearch/indices/cluster/IndicesClusterStateService.java +++ b/core/src/main/java/org/elasticsearch/indices/cluster/IndicesClusterStateService.java @@ -645,7 +645,7 @@ public class IndicesClusterStateService extends AbstractLifecycleComponent * Typical snapshot usage pattern: *
    - *
  • Master calls {@link #initializeSnapshot(org.elasticsearch.cluster.metadata.SnapshotId, List, org.elasticsearch.cluster.metadata.MetaData)} + *
  • Master calls {@link #initializeSnapshot(SnapshotId, List, org.elasticsearch.cluster.metadata.MetaData)} * with list of indices that will be included into the snapshot
  • *
  • Data nodes call {@link org.elasticsearch.index.snapshots.IndexShardRepository#snapshot(SnapshotId, ShardId, IndexCommit, IndexShardSnapshotStatus)} for each shard
  • *
  • When all shard calls return master calls {@link #finalizeSnapshot} @@ -51,7 +52,7 @@ public interface Repository extends LifecycleComponent { /** * Reads snapshot description from repository. * - * @param snapshotId snapshot ID + * @param snapshotId snapshot id * @return information about snapshot */ SnapshotInfo readSnapshot(SnapshotId snapshotId); @@ -65,10 +66,11 @@ public interface Repository extends LifecycleComponent { * @param indices list of indices * @return information about snapshot */ - MetaData readSnapshotMetaData(SnapshotId snapshotId, SnapshotInfo snapshot, List indices) throws IOException; + MetaData readSnapshotMetaData(SnapshotInfo snapshot, List indices) throws IOException; /** - * Returns the list of snapshots currently stored in the repository + * Returns the list of snapshots currently stored in the repository that match the given predicate on the snapshot name. + * To get all snapshots, the predicate filter should return true regardless of the input. * * @return snapshot list */ diff --git a/core/src/main/java/org/elasticsearch/repositories/blobstore/BlobStoreRepository.java b/core/src/main/java/org/elasticsearch/repositories/blobstore/BlobStoreRepository.java index 121df3e5832..506e64cb9a5 100644 --- a/core/src/main/java/org/elasticsearch/repositories/blobstore/BlobStoreRepository.java +++ b/core/src/main/java/org/elasticsearch/repositories/blobstore/BlobStoreRepository.java @@ -24,7 +24,7 @@ import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.Version; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.MetaData; -import org.elasticsearch.cluster.metadata.SnapshotId; +import org.elasticsearch.snapshots.SnapshotId; import org.elasticsearch.common.ParseFieldMatcher; import org.elasticsearch.common.Strings; import org.elasticsearch.common.UUIDs; @@ -34,6 +34,7 @@ import org.elasticsearch.common.blobstore.BlobPath; import org.elasticsearch.common.blobstore.BlobStore; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.component.AbstractLifecycleComponent; import org.elasticsearch.common.compress.NotXContentException; import org.elasticsearch.common.io.Streams; @@ -56,7 +57,6 @@ import org.elasticsearch.repositories.Repository; import org.elasticsearch.repositories.RepositoryException; import org.elasticsearch.repositories.RepositorySettings; import org.elasticsearch.repositories.RepositoryVerificationException; -import org.elasticsearch.snapshots.InvalidSnapshotNameException; import org.elasticsearch.snapshots.SnapshotCreationException; import org.elasticsearch.snapshots.SnapshotException; import org.elasticsearch.snapshots.SnapshotInfo; @@ -71,6 +71,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; /** * BlobStore - based implementation of Snapshot Repository @@ -130,7 +131,7 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent s.getName().equals(snapshotName))) { + throw new SnapshotCreationException(repositoryName, snapshotId, "snapshot with the same name already exists"); + } + if (snapshotFormat.exists(snapshotsBlobContainer, blobId(snapshotId)) || + snapshotLegacyFormat.exists(snapshotsBlobContainer, snapshotName)) { + throw new SnapshotCreationException(repositoryName, snapshotId, "snapshot with such name already exists"); } // Write Global MetaData - globalMetaDataFormat.write(metaData, snapshotsBlobContainer, snapshotId.getSnapshot()); + globalMetaDataFormat.write(metaData, snapshotsBlobContainer, snapshotName); for (String index : indices) { final IndexMetaData indexMetaData = metaData.index(index); final BlobPath indexPath = basePath().add("indices").add(index); final BlobContainer indexMetaDataBlobContainer = blobStore().blobContainer(indexPath); - indexMetaDataFormat.write(indexMetaData, indexMetaDataBlobContainer, snapshotId.getSnapshot()); + indexMetaDataFormat.write(indexMetaData, indexMetaDataBlobContainer, snapshotName); } } catch (IOException ex) { - throw new SnapshotCreationException(snapshotId, ex); + throw new SnapshotCreationException(repositoryName, snapshotId, ex); } } @@ -314,35 +320,27 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent snapshotIds = snapshots(); - if (snapshotIds.contains(snapshotId)) { - List builder = new ArrayList<>(); - for (SnapshotId id : snapshotIds) { - if (!snapshotId.equals(id)) { - builder.add(id); - } - } - snapshotIds = Collections.unmodifiableList(builder); - } + List snapshotIds = snapshots().stream().filter(id -> snapshotId.equals(id) == false).collect(Collectors.toList()); writeSnapshotList(snapshotIds); // Now delete all indices for (String index : indices) { BlobPath indexPath = basePath().add("indices").add(index); BlobContainer indexMetaDataBlobContainer = blobStore().blobContainer(indexPath); try { - indexMetaDataFormat(snapshot.version()).delete(indexMetaDataBlobContainer, snapshotId.getSnapshot()); + indexMetaDataFormat(snapshot.version()).delete(indexMetaDataBlobContainer, snapshotId.getName()); } catch (IOException ex) { logger.warn("[{}] failed to delete metadata for index [{}]", ex, snapshotId, index); } @@ -368,10 +366,21 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent indices, long startTime, String failure, int totalShards, List shardFailures) { + public SnapshotInfo finalizeSnapshot(final SnapshotId snapshotId, + final List indices, + final long startTime, + final String failure, + final int totalShards, + final List shardFailures) { try { - SnapshotInfo blobStoreSnapshot = new SnapshotInfo(snapshotId.getSnapshot(), indices, startTime, failure, System.currentTimeMillis(), totalShards, shardFailures); - snapshotFormat.write(blobStoreSnapshot, snapshotsBlobContainer, snapshotId.getSnapshot()); + SnapshotInfo blobStoreSnapshot = new SnapshotInfo(snapshotId, + indices, + startTime, + failure, + System.currentTimeMillis(), + totalShards, + shardFailures); + snapshotFormat.write(blobStoreSnapshot, snapshotsBlobContainer, blobId(snapshotId)); List snapshotIds = snapshots(); if (!snapshotIds.contains(snapshotId)) { snapshotIds = new ArrayList<>(snapshotIds); @@ -405,15 +414,22 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent legacyPrefixLength) { - name = blobName.substring(prefixLength, blobName.length() - suffixLength); + final String str = blobName.substring(prefixLength, blobName.length() - suffixLength); + // TODO: this will go away once we make the snapshot file writes atomic and + // use it as the source of truth for the snapshots list instead of listing blobs + Tuple pair = parseNameUUIDFromBlobName(str); + name = pair.v1(); + uuid = pair.v2(); } else if (blobName.startsWith(LEGACY_SNAPSHOT_PREFIX) && blobName.length() > suffixLength + prefixLength) { name = blobName.substring(legacyPrefixLength); + uuid = SnapshotId.UNASSIGNED_UUID; } else { // not sure what it was - ignore continue; } - snapshots.add(new SnapshotId(repositoryName, name)); + snapshots.add(new SnapshotId(name, uuid)); } return Collections.unmodifiableList(snapshots); } catch (IOException ex) { @@ -425,28 +441,25 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent indices) throws IOException { - return readSnapshotMetaData(snapshotId, snapshot.version(), indices, false); + public MetaData readSnapshotMetaData(SnapshotInfo snapshot, List indices) throws IOException { + return readSnapshotMetaData(snapshot.snapshotId(), snapshot.version(), indices, false); } - /** - * {@inheritDoc} - */ @Override - public SnapshotInfo readSnapshot(SnapshotId snapshotId) { + public SnapshotInfo readSnapshot(final SnapshotId snapshotId) { try { - return snapshotFormat.read(snapshotsBlobContainer, snapshotId.getSnapshot()); + return snapshotFormat.read(snapshotsBlobContainer, blobId(snapshotId)); } catch (FileNotFoundException | NoSuchFileException ex) { // File is missing - let's try legacy format instead try { - return snapshotLegacyFormat.read(snapshotsBlobContainer, snapshotId.getSnapshot()); + return snapshotLegacyFormat.read(snapshotsBlobContainer, snapshotId.getName()); } catch (FileNotFoundException | NoSuchFileException ex1) { - throw new SnapshotMissingException(snapshotId, ex); + throw new SnapshotMissingException(repositoryName, snapshotId, ex); } catch (IOException | NotXContentException ex1) { - throw new SnapshotException(snapshotId, "failed to get snapshots", ex1); + throw new SnapshotException(repositoryName, snapshotId, "failed to get snapshots", ex1); } } catch (IOException | NotXContentException ex) { - throw new SnapshotException(snapshotId, "failed to get snapshots", ex); + throw new SnapshotException(repositoryName, snapshotId, "failed to get snapshots", ex); } } @@ -456,27 +469,27 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent @@ -561,9 +578,12 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent parseNameUUIDFromBlobName(final String str) { + final String name; + final String uuid; + final int sizeOfUUID = 22; // uuid is 22 chars in length + // unreliable, but highly unlikely to have a snapshot name with a dash followed by 22 characters, + // and this will go away before a release (see #18156). + //norelease + if (str.length() > sizeOfUUID + 1 && str.charAt(str.length() - sizeOfUUID - 1) == '-') { + // new naming convention, snapshot blob id has name and uuid + final int idx = str.length() - sizeOfUUID - 1; + name = str.substring(0, idx); + uuid = str.substring(idx + 1); + } else { + // old naming convention, before snapshots had UUIDs + name = str; + uuid = SnapshotId.UNASSIGNED_UUID; + } + return Tuple.tuple(name, uuid); + } + + // Package private for testing + static String blobId(final SnapshotId snapshotId) { + final String uuid = snapshotId.getUUID(); + if (uuid.equals(SnapshotId.UNASSIGNED_UUID)) { + // the old snapshot blob naming + return snapshotId.getName(); + } + return snapshotId.getName() + "-" + uuid; + } } diff --git a/core/src/main/java/org/elasticsearch/repositories/uri/URLRepository.java b/core/src/main/java/org/elasticsearch/repositories/uri/URLRepository.java index 77d4f1cc816..baea7fe28f9 100644 --- a/core/src/main/java/org/elasticsearch/repositories/uri/URLRepository.java +++ b/core/src/main/java/org/elasticsearch/repositories/uri/URLRepository.java @@ -19,7 +19,7 @@ package org.elasticsearch.repositories.uri; -import org.elasticsearch.cluster.metadata.SnapshotId; +import org.elasticsearch.snapshots.SnapshotId; import org.elasticsearch.common.blobstore.BlobPath; import org.elasticsearch.common.blobstore.BlobStore; import org.elasticsearch.common.blobstore.url.URLBlobStore; @@ -42,6 +42,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.function.Function; +import java.util.function.Predicate; /** * Read-only URL-based implementation of the BlobStoreRepository diff --git a/core/src/main/java/org/elasticsearch/rest/action/cat/RestRecoveryAction.java b/core/src/main/java/org/elasticsearch/rest/action/cat/RestRecoveryAction.java index 4764462d958..07c2611f2ce 100644 --- a/core/src/main/java/org/elasticsearch/rest/action/cat/RestRecoveryAction.java +++ b/core/src/main/java/org/elasticsearch/rest/action/cat/RestRecoveryAction.java @@ -154,8 +154,8 @@ public class RestRecoveryAction extends AbstractCatAction { t.addCell(state.getSourceNode() == null ? "n/a" : state.getSourceNode().getName()); t.addCell(state.getTargetNode().getHostName()); t.addCell(state.getTargetNode().getName()); - t.addCell(state.getRestoreSource() == null ? "n/a" : state.getRestoreSource().snapshotId().getRepository()); - t.addCell(state.getRestoreSource() == null ? "n/a" : state.getRestoreSource().snapshotId().getSnapshot()); + t.addCell(state.getRestoreSource() == null ? "n/a" : state.getRestoreSource().snapshot().getRepository()); + t.addCell(state.getRestoreSource() == null ? "n/a" : state.getRestoreSource().snapshot().getSnapshotId().getName()); t.addCell(state.getIndex().totalRecoverFiles()); t.addCell(state.getIndex().recoveredFileCount()); t.addCell(String.format(Locale.ROOT, "%1.1f%%", state.getIndex().recoveredFilesPercent())); diff --git a/core/src/main/java/org/elasticsearch/rest/action/cat/RestSnapshotAction.java b/core/src/main/java/org/elasticsearch/rest/action/cat/RestSnapshotAction.java index 0d98dd20278..94d178e4db9 100644 --- a/core/src/main/java/org/elasticsearch/rest/action/cat/RestSnapshotAction.java +++ b/core/src/main/java/org/elasticsearch/rest/action/cat/RestSnapshotAction.java @@ -79,7 +79,7 @@ public class RestSnapshotAction extends AbstractCatAction { protected Table getTableWithHeader(RestRequest request) { return new Table() .startHeaders() - .addCell("id", "alias:id,snapshotId;desc:unique snapshot id") + .addCell("id", "alias:id,snapshot;desc:unique snapshot") .addCell("status", "alias:s,status;text-align:right;desc:snapshot name") .addCell("start_epoch", "alias:ste,startEpoch;desc:start time in seconds since 1970-01-01 00:00:00") .addCell("start_time", "alias:sti,startTime;desc:start time in HH:MM:SS") @@ -101,7 +101,7 @@ public class RestSnapshotAction extends AbstractCatAction { for (SnapshotInfo snapshotStatus : getSnapshotsResponse.getSnapshots()) { table.startRow(); - table.addCell(snapshotStatus.name()); + table.addCell(snapshotStatus.snapshotId().getName()); table.addCell(snapshotStatus.state()); table.addCell(TimeUnit.SECONDS.convert(snapshotStatus.startTime(), TimeUnit.MILLISECONDS)); table.addCell(dateFormat.print(snapshotStatus.startTime())); diff --git a/core/src/main/java/org/elasticsearch/snapshots/ConcurrentSnapshotExecutionException.java b/core/src/main/java/org/elasticsearch/snapshots/ConcurrentSnapshotExecutionException.java index 4a844fb4907..73762e2d3a7 100644 --- a/core/src/main/java/org/elasticsearch/snapshots/ConcurrentSnapshotExecutionException.java +++ b/core/src/main/java/org/elasticsearch/snapshots/ConcurrentSnapshotExecutionException.java @@ -19,7 +19,6 @@ package org.elasticsearch.snapshots; -import org.elasticsearch.cluster.metadata.SnapshotId; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.rest.RestStatus; @@ -29,7 +28,12 @@ import java.io.IOException; * Thrown when a user tries to start multiple snapshots at the same time */ public class ConcurrentSnapshotExecutionException extends SnapshotException { - public ConcurrentSnapshotExecutionException(SnapshotId snapshot, String msg) { + + public ConcurrentSnapshotExecutionException(final String repositoryName, final String snapshotName, final String msg) { + super(repositoryName, snapshotName, msg); + } + + public ConcurrentSnapshotExecutionException(final Snapshot snapshot, final String msg) { super(snapshot, msg); } diff --git a/core/src/main/java/org/elasticsearch/snapshots/InvalidSnapshotNameException.java b/core/src/main/java/org/elasticsearch/snapshots/InvalidSnapshotNameException.java index 48949c11d4c..2acc82add36 100644 --- a/core/src/main/java/org/elasticsearch/snapshots/InvalidSnapshotNameException.java +++ b/core/src/main/java/org/elasticsearch/snapshots/InvalidSnapshotNameException.java @@ -19,7 +19,6 @@ package org.elasticsearch.snapshots; -import org.elasticsearch.cluster.metadata.SnapshotId; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.rest.RestStatus; @@ -30,8 +29,8 @@ import java.io.IOException; */ public class InvalidSnapshotNameException extends SnapshotException { - public InvalidSnapshotNameException(SnapshotId snapshot, String desc) { - super(snapshot, "Invalid snapshot name [" + snapshot.getSnapshot() + "], " + desc); + public InvalidSnapshotNameException(final String repositoryName, final String snapshotName, String desc) { + super(repositoryName, snapshotName, "Invalid snapshot name [" + snapshotName + "], " + desc); } public InvalidSnapshotNameException(StreamInput in) throws IOException { diff --git a/core/src/main/java/org/elasticsearch/snapshots/RestoreService.java b/core/src/main/java/org/elasticsearch/snapshots/RestoreService.java index f6e6c4aaed5..44ca6fb972e 100644 --- a/core/src/main/java/org/elasticsearch/snapshots/RestoreService.java +++ b/core/src/main/java/org/elasticsearch/snapshots/RestoreService.java @@ -39,7 +39,6 @@ import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.metadata.MetaDataCreateIndexService; import org.elasticsearch.cluster.metadata.MetaDataIndexUpgradeService; import org.elasticsearch.cluster.metadata.RepositoriesMetaData; -import org.elasticsearch.cluster.metadata.SnapshotId; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.routing.IndexRoutingTable; import org.elasticsearch.cluster.routing.IndexShardRoutingTable; @@ -85,6 +84,8 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CopyOnWriteArrayList; @@ -116,7 +117,7 @@ import static org.elasticsearch.common.util.set.Sets.newHashSet; * method, which detects that shard should be restored from snapshot rather than recovered from gateway by looking * at the {@link org.elasticsearch.cluster.routing.ShardRouting#restoreSource()} property. *

    - * At the end of the successful restore process {@code IndexShardSnapshotAndRestoreService} calls {@link #indexShardRestoreCompleted(SnapshotId, ShardId)}, + * At the end of the successful restore process {@code IndexShardSnapshotAndRestoreService} calls {@link #indexShardRestoreCompleted(Snapshot, ShardId)}, * which updates {@link RestoreInProgress} in cluster state or removes it when all shards are completed. In case of * restore failure a normal recovery fail-over process kicks in. */ @@ -153,8 +154,6 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis private final MetaDataCreateIndexService createIndexService; - private final ClusterSettings dynamicSettings; - private final MetaDataIndexUpgradeService metaDataIndexUpgradeService; private final CopyOnWriteArrayList> listeners = new CopyOnWriteArrayList<>(); @@ -164,7 +163,7 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis @Inject public RestoreService(Settings settings, ClusterService clusterService, RepositoriesService repositoriesService, TransportService transportService, - AllocationService allocationService, MetaDataCreateIndexService createIndexService, ClusterSettings dynamicSettings, + AllocationService allocationService, MetaDataCreateIndexService createIndexService, MetaDataIndexUpgradeService metaDataIndexUpgradeService, ClusterSettings clusterSettings) { super(settings); this.clusterService = clusterService; @@ -172,7 +171,6 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis this.transportService = transportService; this.allocationService = allocationService; this.createIndexService = createIndexService; - this.dynamicSettings = dynamicSettings; this.metaDataIndexUpgradeService = metaDataIndexUpgradeService; transportService.registerRequestHandler(UPDATE_RESTORE_ACTION_NAME, UpdateIndexShardRestoreStatusRequest::new, ThreadPool.Names.SAME, new UpdateRestoreStateRequestHandler()); clusterService.add(this); @@ -188,14 +186,20 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis public void restoreSnapshot(final RestoreRequest request, final ActionListener listener) { try { // Read snapshot info and metadata from the repository - Repository repository = repositoriesService.repository(request.repository()); - final SnapshotId snapshotId = new SnapshotId(request.repository(), request.name()); - final SnapshotInfo snapshot = repository.readSnapshot(snapshotId); - List filteredIndices = SnapshotUtils.filterIndices(snapshot.indices(), request.indices(), request.indicesOptions()); - MetaData metaDataIn = repository.readSnapshotMetaData(snapshotId, snapshot, filteredIndices); + Repository repository = repositoriesService.repository(request.repositoryName); + final Optional matchingSnapshotId = repository.snapshots().stream() + .filter(s -> request.snapshotName.equals(s.getName())).findFirst(); + if (matchingSnapshotId.isPresent() == false) { + throw new SnapshotRestoreException(request.repositoryName, request.snapshotName, "snapshot does not exist"); + } + final SnapshotId snapshotId = matchingSnapshotId.get(); + final SnapshotInfo snapshotInfo = repository.readSnapshot(snapshotId); + final Snapshot snapshot = new Snapshot(request.repositoryName, snapshotId); + List filteredIndices = SnapshotUtils.filterIndices(snapshotInfo.indices(), request.indices(), request.indicesOptions()); + MetaData metaDataIn = repository.readSnapshotMetaData(snapshotInfo, filteredIndices); final MetaData metaData; - if (snapshot.version().before(Version.V_2_0_0_beta1)) { + if (snapshotInfo.version().before(Version.V_2_0_0_beta1)) { // ES 2.0 now requires units for all time and byte-sized settings, so we add the default unit if it's missing in this snapshot: metaData = MetaData.addDefaultUnitsIfNeeded(logger, metaDataIn); } else { @@ -204,7 +208,7 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis } // Make sure that we can restore from this snapshot - validateSnapshotRestorable(snapshotId, snapshot); + validateSnapshotRestorable(request.repositoryName, snapshotInfo); // Find list of indices that we need to restore final Map renamedIndices = renamedIndices(request, filteredIndices); @@ -220,7 +224,7 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis // same time RestoreInProgress restoreInProgress = currentState.custom(RestoreInProgress.TYPE); if (restoreInProgress != null && !restoreInProgress.entries().isEmpty()) { - throw new ConcurrentSnapshotExecutionException(snapshotId, "Restore process is already running in this cluster"); + throw new ConcurrentSnapshotExecutionException(snapshot, "Restore process is already running in this cluster"); } // Updating cluster state @@ -236,14 +240,14 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis for (Map.Entry indexEntry : renamedIndices.entrySet()) { String index = indexEntry.getValue(); boolean partial = checkPartial(index); - RestoreSource restoreSource = new RestoreSource(snapshotId, snapshot.version(), index); + RestoreSource restoreSource = new RestoreSource(snapshot, snapshotInfo.version(), index); String renamedIndexName = indexEntry.getKey(); IndexMetaData snapshotIndexMetaData = metaData.index(index); snapshotIndexMetaData = updateIndexSettings(snapshotIndexMetaData, request.indexSettings, request.ignoreIndexSettings); try { snapshotIndexMetaData = metaDataIndexUpgradeService.upgradeIndexMetaData(snapshotIndexMetaData); } catch (Exception ex) { - throw new SnapshotRestoreException(snapshotId, "cannot restore index [" + index + "] because it cannot be upgraded", ex); + throw new SnapshotRestoreException(snapshot, "cannot restore index [" + index + "] because it cannot be upgraded", ex); } // Check that the index is closed or doesn't exist IndexMetaData currentIndexMetaData = currentState.metaData().index(renamedIndexName); @@ -309,7 +313,7 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis } shards = shardsBuilder.build(); - RestoreInProgress.Entry restoreEntry = new RestoreInProgress.Entry(snapshotId, RestoreInProgress.State.INIT, Collections.unmodifiableList(new ArrayList<>(renamedIndices.keySet())), shards); + RestoreInProgress.Entry restoreEntry = new RestoreInProgress.Entry(snapshot, RestoreInProgress.State.INIT, Collections.unmodifiableList(new ArrayList<>(renamedIndices.keySet())), shards); builder.putCustom(RestoreInProgress.TYPE, new RestoreInProgress(restoreEntry)); } else { shards = ImmutableOpenMap.of(); @@ -322,28 +326,30 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis if (completed(shards)) { // We don't have any indices to restore - we are done - restoreInfo = new RestoreInfo(request.name(), Collections.unmodifiableList(new ArrayList<>(renamedIndices.keySet())), - shards.size(), shards.size() - failedShards(shards)); + restoreInfo = new RestoreInfo(snapshotId.getName(), + Collections.unmodifiableList(new ArrayList<>(renamedIndices.keySet())), + shards.size(), + shards.size() - failedShards(shards)); } RoutingTable rt = rtBuilder.build(); ClusterState updatedState = builder.metaData(mdBuilder).blocks(blocks).routingTable(rt).build(); RoutingAllocation.Result routingResult = allocationService.reroute( ClusterState.builder(updatedState).routingTable(rt).build(), - "restored snapshot [" + snapshotId + "]"); + "restored snapshot [" + snapshot + "]"); return ClusterState.builder(updatedState).routingResult(routingResult).build(); } private void checkAliasNameConflicts(Map renamedIndices, Set aliases) { for (Map.Entry renamedIndex : renamedIndices.entrySet()) { if (aliases.contains(renamedIndex.getKey())) { - throw new SnapshotRestoreException(snapshotId, "cannot rename index [" + renamedIndex.getValue() + "] into [" + renamedIndex.getKey() + "] because of conflict with an alias with the same name"); + throw new SnapshotRestoreException(snapshot, "cannot rename index [" + renamedIndex.getValue() + "] into [" + renamedIndex.getKey() + "] because of conflict with an alias with the same name"); } } } private void populateIgnoredShards(String index, IntSet ignoreShards) { - for (SnapshotShardFailure failure : snapshot.shardFailures()) { + for (SnapshotShardFailure failure : snapshotInfo.shardFailures()) { if (index.equals(failure.index())) { ignoreShards.add(failure.shardId()); } @@ -352,11 +358,11 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis private boolean checkPartial(String index) { // Make sure that index was fully snapshotted - if (failed(snapshot, index)) { + if (failed(snapshotInfo, index)) { if (request.partial()) { return true; } else { - throw new SnapshotRestoreException(snapshotId, "index [" + index + "] wasn't fully snapshotted - cannot restore"); + throw new SnapshotRestoreException(snapshot, "index [" + index + "] wasn't fully snapshotted - cannot restore"); } } else { return false; @@ -367,15 +373,15 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis // Index exist - checking that it's closed if (currentIndexMetaData.getState() != IndexMetaData.State.CLOSE) { // TODO: Enable restore for open indices - throw new SnapshotRestoreException(snapshotId, "cannot restore index [" + renamedIndex + "] because it's open"); + throw new SnapshotRestoreException(snapshot, "cannot restore index [" + renamedIndex + "] because it's open"); } // Index exist - checking if it's partial restore if (partial) { - throw new SnapshotRestoreException(snapshotId, "cannot restore partial index [" + renamedIndex + "] because such index already exists"); + throw new SnapshotRestoreException(snapshot, "cannot restore partial index [" + renamedIndex + "] because such index already exists"); } // Make sure that the number of shards is the same. That's the only thing that we cannot change if (currentIndexMetaData.getNumberOfShards() != snapshotIndexMetaData.getNumberOfShards()) { - throw new SnapshotRestoreException(snapshotId, "cannot restore index [" + renamedIndex + "] with [" + currentIndexMetaData.getNumberOfShards() + + throw new SnapshotRestoreException(snapshot, "cannot restore index [" + renamedIndex + "] with [" + currentIndexMetaData.getNumberOfShards() + "] shard from snapshot with [" + snapshotIndexMetaData.getNumberOfShards() + "] shards"); } } @@ -395,7 +401,7 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis for (String ignoredSetting : ignoreSettings) { if (!Regex.isSimpleMatchPattern(ignoredSetting)) { if (UNREMOVABLE_SETTINGS.contains(ignoredSetting)) { - throw new SnapshotRestoreException(snapshotId, "cannot remove setting [" + ignoredSetting + "] on restore"); + throw new SnapshotRestoreException(snapshot, "cannot remove setting [" + ignoredSetting + "] on restore"); } else { settingsMap.remove(ignoredSetting); } @@ -417,7 +423,7 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis } for(Map.Entry entry : normalizedChangeSettings.getAsMap().entrySet()) { if (UNMODIFIABLE_SETTINGS.contains(entry.getKey())) { - throw new SnapshotRestoreException(snapshotId, "cannot modify setting [" + entry.getKey() + "] on restore"); + throw new SnapshotRestoreException(snapshot, "cannot modify setting [" + entry.getKey() + "] on restore"); } else { settingsMap.put(entry.getKey(), entry.getValue()); } @@ -471,7 +477,7 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis } catch (Throwable e) { - logger.warn("[{}][{}] failed to restore snapshot", e, request.repository(), request.name()); + logger.warn("[{}] failed to restore snapshot", e, request.repositoryName + ":" + request.snapshotName); listener.onFailure(e); } } @@ -480,28 +486,28 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis * This method is used by {@link IndexShard} to notify * {@code RestoreService} about shard restore completion. * - * @param snapshotId snapshot id + * @param snapshot snapshot * @param shardId shard id */ - public void indexShardRestoreCompleted(SnapshotId snapshotId, ShardId shardId) { - logger.trace("[{}] successfully restored shard [{}]", snapshotId, shardId); - UpdateIndexShardRestoreStatusRequest request = new UpdateIndexShardRestoreStatusRequest(snapshotId, shardId, + public void indexShardRestoreCompleted(Snapshot snapshot, ShardId shardId) { + logger.trace("[{}] successfully restored shard [{}]", snapshot, shardId); + UpdateIndexShardRestoreStatusRequest request = new UpdateIndexShardRestoreStatusRequest(snapshot, shardId, new ShardRestoreStatus(clusterService.state().nodes().getLocalNodeId(), RestoreInProgress.State.SUCCESS)); - transportService.sendRequest(clusterService.state().nodes().getMasterNode(), - UPDATE_RESTORE_ACTION_NAME, request, EmptyTransportResponseHandler.INSTANCE_SAME); + transportService.sendRequest(clusterService.state().nodes().getMasterNode(), + UPDATE_RESTORE_ACTION_NAME, request, EmptyTransportResponseHandler.INSTANCE_SAME); } public final static class RestoreCompletionResponse { - private final SnapshotId snapshotId; + private final Snapshot snapshot; private final RestoreInfo restoreInfo; - private RestoreCompletionResponse(SnapshotId snapshotId, RestoreInfo restoreInfo) { - this.snapshotId = snapshotId; + private RestoreCompletionResponse(final Snapshot snapshot, final RestoreInfo restoreInfo) { + this.snapshot = snapshot; this.restoreInfo = restoreInfo; } - public SnapshotId getSnapshotId() { - return snapshotId; + public Snapshot getSnapshot() { + return snapshot; } public RestoreInfo getRestoreInfo() { @@ -520,7 +526,7 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis clusterService.submitStateUpdateTask("update snapshot state", new ClusterStateUpdateTask() { private final List drainedRequests = new ArrayList<>(); - private Map>> batchedRestoreInfo = null; + private Map>> batchedRestoreInfo = null; @Override public ClusterState execute(ClusterState currentState) { @@ -549,8 +555,8 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis final UpdateIndexShardRestoreStatusRequest updateSnapshotState = drainedRequests.get(i); updateSnapshotState.processed = true; - if (entry.snapshotId().equals(updateSnapshotState.snapshotId())) { - logger.trace("[{}] Updating shard [{}] with status [{}]", updateSnapshotState.snapshotId(), updateSnapshotState.shardId(), updateSnapshotState.status().state()); + if (entry.snapshot().equals(updateSnapshotState.snapshot())) { + logger.trace("[{}] Updating shard [{}] with status [{}]", updateSnapshotState.snapshot(), updateSnapshotState.shardId(), updateSnapshotState.status().state()); if (shardsBuilder == null) { shardsBuilder = ImmutableOpenMap.builder(entry.shards()); } @@ -562,16 +568,19 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis if (shardsBuilder != null) { ImmutableOpenMap shards = shardsBuilder.build(); if (!completed(shards)) { - entries.add(new RestoreInProgress.Entry(entry.snapshotId(), RestoreInProgress.State.STARTED, entry.indices(), shards)); + entries.add(new RestoreInProgress.Entry(entry.snapshot(), RestoreInProgress.State.STARTED, entry.indices(), shards)); } else { - logger.info("restore [{}] is done", entry.snapshotId()); + logger.info("restore [{}] is done", entry.snapshot()); if (batchedRestoreInfo == null) { batchedRestoreInfo = new HashMap<>(); } - assert !batchedRestoreInfo.containsKey(entry.snapshotId()); - batchedRestoreInfo.put(entry.snapshotId(), + assert !batchedRestoreInfo.containsKey(entry.snapshot()); + batchedRestoreInfo.put(entry.snapshot(), new Tuple<>( - new RestoreInfo(entry.snapshotId().getSnapshot(), entry.indices(), shards.size(), shards.size() - failedShards(shards)), + new RestoreInfo(entry.snapshot().getSnapshotId().getName(), + entry.indices(), + shards.size(), + shards.size() - failedShards(shards)), shards)); } } else { @@ -592,15 +601,15 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis @Override public void onFailure(String source, @Nullable Throwable t) { for (UpdateIndexShardRestoreStatusRequest request : drainedRequests) { - logger.warn("[{}][{}] failed to update snapshot status to [{}]", t, request.snapshotId(), request.shardId(), request.status()); + logger.warn("[{}][{}] failed to update snapshot status to [{}]", t, request.snapshot(), request.shardId(), request.status()); } } @Override public void clusterStateProcessed(String source, ClusterState oldState, ClusterState newState) { if (batchedRestoreInfo != null) { - for (final Entry>> entry : batchedRestoreInfo.entrySet()) { - final SnapshotId snapshotId = entry.getKey(); + for (final Entry>> entry : batchedRestoreInfo.entrySet()) { + final Snapshot snapshot = entry.getKey(); final RestoreInfo restoreInfo = entry.getValue().v1(); final ImmutableOpenMap shards = entry.getValue().v2(); RoutingTable routingTable = newState.getRoutingTable(); @@ -610,13 +619,13 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis ShardId shardId = shard.key; ShardRouting shardRouting = findPrimaryShard(routingTable, shardId); if (shardRouting != null && !shardRouting.active()) { - logger.trace("[{}][{}] waiting for the shard to start", snapshotId, shardId); + logger.trace("[{}][{}] waiting for the shard to start", snapshot, shardId); waitForStarted.add(shardId); } } } if (waitForStarted.isEmpty()) { - notifyListeners(snapshotId, restoreInfo); + notifyListeners(snapshot, restoreInfo); } else { clusterService.addLast(new ClusterStateListener() { @Override @@ -629,12 +638,12 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis // Shard disappeared (index deleted) or became active if (shardRouting == null || shardRouting.active()) { iterator.remove(); - logger.trace("[{}][{}] shard disappeared or started - removing", snapshotId, shardId); + logger.trace("[{}][{}] shard disappeared or started - removing", snapshot, shardId); } } } if (waitForStarted.isEmpty()) { - notifyListeners(snapshotId, restoreInfo); + notifyListeners(snapshot, restoreInfo); clusterService.remove(this); } } @@ -655,10 +664,10 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis return null; } - private void notifyListeners(SnapshotId snapshotId, RestoreInfo restoreInfo) { + private void notifyListeners(Snapshot snapshot, RestoreInfo restoreInfo) { for (ActionListener listener : listeners) { try { - listener.onResponse(new RestoreCompletionResponse(snapshotId, restoreInfo)); + listener.onResponse(new RestoreCompletionResponse(snapshot, restoreInfo)); } catch (Throwable e) { logger.warn("failed to update snapshot status for [{}]", e, listener); } @@ -695,7 +704,7 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis } String previousIndex = renamedIndices.put(renamedIndex, index); if (previousIndex != null) { - throw new SnapshotRestoreException(new SnapshotId(request.repository(), request.name()), + throw new SnapshotRestoreException(request.repositoryName, request.snapshotName, "indices [" + index + "] and [" + previousIndex + "] are renamed into the same index [" + renamedIndex + "]"); } } @@ -705,16 +714,18 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis /** * Checks that snapshots can be restored and have compatible version * - * @param snapshotId snapshot id - * @param snapshot snapshot metadata + * @param repository repository name + * @param snapshotInfo snapshot metadata */ - private void validateSnapshotRestorable(SnapshotId snapshotId, SnapshotInfo snapshot) { - if (!snapshot.state().restorable()) { - throw new SnapshotRestoreException(snapshotId, "unsupported snapshot state [" + snapshot.state() + "]"); + private void validateSnapshotRestorable(final String repository, final SnapshotInfo snapshotInfo) { + if (!snapshotInfo.state().restorable()) { + throw new SnapshotRestoreException(new Snapshot(repository, snapshotInfo.snapshotId()), + "unsupported snapshot state [" + snapshotInfo.state() + "]"); } - if (Version.CURRENT.before(snapshot.version())) { - throw new SnapshotRestoreException(snapshotId, "the snapshot was created with Elasticsearch version [" + - snapshot.version() + "] which is higher than the version of this node [" + Version.CURRENT + "]"); + if (Version.CURRENT.before(snapshotInfo.version())) { + throw new SnapshotRestoreException(new Snapshot(repository, snapshotInfo.snapshotId()), + "the snapshot was created with Elasticsearch version [" + snapshotInfo.version() + + "] which is higher than the version of this node [" + Version.CURRENT + "]"); } } @@ -746,8 +757,8 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis } if (shardsToFail != null) { for (ShardId shardId : shardsToFail) { - logger.trace("[{}] failing running shard restore [{}]", entry.snapshotId(), shardId); - updateRestoreStateOnMaster(new UpdateIndexShardRestoreStatusRequest(entry.snapshotId(), shardId, new ShardRestoreStatus(null, RestoreInProgress.State.FAILURE, "index was deleted"))); + logger.trace("[{}] failing running shard restore [{}]", entry.snapshot(), shardId); + updateRestoreStateOnMaster(new UpdateIndexShardRestoreStatusRequest(entry.snapshot(), shardId, new ShardRestoreStatus(null, RestoreInProgress.State.FAILURE, "index was deleted"))); } } } @@ -757,12 +768,12 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis /** * Fails the given snapshot restore operation for the given shard */ - public void failRestore(SnapshotId snapshotId, ShardId shardId) { - logger.debug("[{}] failed to restore shard [{}]", snapshotId, shardId); - UpdateIndexShardRestoreStatusRequest request = new UpdateIndexShardRestoreStatusRequest(snapshotId, shardId, + public void failRestore(Snapshot snapshot, ShardId shardId) { + logger.debug("[{}] failed to restore shard [{}]", snapshot, shardId); + UpdateIndexShardRestoreStatusRequest request = new UpdateIndexShardRestoreStatusRequest(snapshot, shardId, new ShardRestoreStatus(clusterService.state().nodes().getLocalNodeId(), RestoreInProgress.State.FAILURE)); - transportService.sendRequest(clusterService.state().nodes().getMasterNode(), - UPDATE_RESTORE_ACTION_NAME, request, EmptyTransportResponseHandler.INSTANCE_SAME); + transportService.sendRequest(clusterService.state().nodes().getMasterNode(), + UPDATE_RESTORE_ACTION_NAME, request, EmptyTransportResponseHandler.INSTANCE_SAME); } private boolean failed(SnapshotInfo snapshot, String index) { @@ -846,7 +857,7 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis RestoreInProgress snapshots = clusterState.custom(RestoreInProgress.TYPE); if (snapshots != null) { for (RestoreInProgress.Entry snapshot : snapshots.entries()) { - if (repository.equals(snapshot.snapshotId().getRepository())) { + if (repository.equals(snapshot.snapshot().getRepository())) { return true; } } @@ -861,9 +872,9 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis final private String cause; - final private String name; + final private String repositoryName; - final private String repository; + final private String snapshotName; final private String[] indices; @@ -890,9 +901,8 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis /** * Constructs new restore request * - * @param cause cause for restoring the snapshot - * @param repository repository name - * @param name snapshot name + * @param repositoryName repositoryName + * @param snapshotName snapshotName * @param indices list of indices to restore * @param indicesOptions indices options * @param renamePattern pattern to rename indices @@ -903,14 +913,14 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis * @param partial allow partial restore * @param indexSettings index settings that should be changed on restore * @param ignoreIndexSettings index settings that shouldn't be restored + * @param cause cause for restoring the snapshot */ - public RestoreRequest(String cause, String repository, String name, String[] indices, IndicesOptions indicesOptions, + public RestoreRequest(String repositoryName, String snapshotName, String[] indices, IndicesOptions indicesOptions, String renamePattern, String renameReplacement, Settings settings, TimeValue masterNodeTimeout, boolean includeGlobalState, boolean partial, boolean includeAliases, - Settings indexSettings, String[] ignoreIndexSettings ) { - this.cause = cause; - this.name = name; - this.repository = repository; + Settings indexSettings, String[] ignoreIndexSettings, String cause) { + this.repositoryName = Objects.requireNonNull(repositoryName); + this.snapshotName = Objects.requireNonNull(snapshotName); this.indices = indices; this.renamePattern = renamePattern; this.renameReplacement = renameReplacement; @@ -922,7 +932,7 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis this.includeAliases = includeAliases; this.indexSettings = indexSettings; this.ignoreIndexSettings = ignoreIndexSettings; - + this.cause = cause; } /** @@ -934,22 +944,22 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis return cause; } - /** - * Returns snapshot name - * - * @return snapshot name - */ - public String name() { - return name; - } - /** * Returns repository name * * @return repository name */ - public String repository() { - return repository; + public String repositoryName() { + return repositoryName; + } + + /** + * Returns snapshot name + * + * @return snapshot name + */ + public String snapshotName() { + return snapshotName; } /** @@ -1058,7 +1068,7 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis * Internal class that is used to send notifications about finished shard restore operations to master node */ public static class UpdateIndexShardRestoreStatusRequest extends TransportRequest { - private SnapshotId snapshotId; + private Snapshot snapshot; private ShardId shardId; private ShardRestoreStatus status; @@ -1068,8 +1078,8 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis } - private UpdateIndexShardRestoreStatusRequest(SnapshotId snapshotId, ShardId shardId, ShardRestoreStatus status) { - this.snapshotId = snapshotId; + private UpdateIndexShardRestoreStatusRequest(Snapshot snapshot, ShardId shardId, ShardRestoreStatus status) { + this.snapshot = snapshot; this.shardId = shardId; this.status = status; } @@ -1077,7 +1087,7 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis @Override public void readFrom(StreamInput in) throws IOException { super.readFrom(in); - snapshotId = SnapshotId.readSnapshotId(in); + snapshot = new Snapshot(in); shardId = ShardId.readShardId(in); status = ShardRestoreStatus.readShardRestoreStatus(in); } @@ -1085,13 +1095,13 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis @Override public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); - snapshotId.writeTo(out); + snapshot.writeTo(out); shardId.writeTo(out); status.writeTo(out); } - public SnapshotId snapshotId() { - return snapshotId; + public Snapshot snapshot() { + return snapshot; } public ShardId shardId() { @@ -1104,7 +1114,7 @@ public class RestoreService extends AbstractComponent implements ClusterStateLis @Override public String toString() { - return "" + snapshotId + ", shardId [" + shardId + "], status [" + status.state() + "]"; + return "" + snapshot + ", shardId [" + shardId + "], status [" + status.state() + "]"; } } diff --git a/core/src/main/java/org/elasticsearch/snapshots/Snapshot.java b/core/src/main/java/org/elasticsearch/snapshots/Snapshot.java new file mode 100644 index 00000000000..314cd4053dd --- /dev/null +++ b/core/src/main/java/org/elasticsearch/snapshots/Snapshot.java @@ -0,0 +1,102 @@ +/* + * 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.snapshots; + +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Writeable; + +import java.io.IOException; +import java.util.Objects; + +/** + * Basic information about a snapshot - a SnapshotId and the repository that the snapshot belongs to. + */ +public final class Snapshot implements Writeable { + + private final String repository; + private final SnapshotId snapshotId; + private final int hashCode; + + /** + * Constructs a snapshot. + */ + public Snapshot(final String repository, final SnapshotId snapshotId) { + this.repository = Objects.requireNonNull(repository); + this.snapshotId = Objects.requireNonNull(snapshotId); + this.hashCode = computeHashCode(); + } + + /** + * Constructs a snapshot from the stream input. + */ + public Snapshot(final StreamInput in) throws IOException { + repository = in.readString(); + snapshotId = new SnapshotId(in); + hashCode = computeHashCode(); + } + + /** + * Gets the repository name for the snapshot. + */ + public String getRepository() { + return repository; + } + + /** + * Gets the snapshot id for the snapshot. + */ + public SnapshotId getSnapshotId() { + return snapshotId; + } + + @Override + public String toString() { + return repository + ":" + snapshotId.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + @SuppressWarnings("unchecked") Snapshot that = (Snapshot) o; + return repository.equals(that.repository) && snapshotId.equals(that.snapshotId); + } + + @Override + public int hashCode() { + return hashCode; + } + + private int computeHashCode() { + return Objects.hash(repository, snapshotId); + } + + @Override + public void writeTo(final StreamOutput out) throws IOException { + out.writeString(repository); + snapshotId.writeTo(out); + } + +} diff --git a/core/src/main/java/org/elasticsearch/snapshots/SnapshotCreationException.java b/core/src/main/java/org/elasticsearch/snapshots/SnapshotCreationException.java index 58faecda4a4..32d3992f243 100644 --- a/core/src/main/java/org/elasticsearch/snapshots/SnapshotCreationException.java +++ b/core/src/main/java/org/elasticsearch/snapshots/SnapshotCreationException.java @@ -19,7 +19,6 @@ package org.elasticsearch.snapshots; -import org.elasticsearch.cluster.metadata.SnapshotId; import org.elasticsearch.common.io.stream.StreamInput; import java.io.IOException; @@ -29,11 +28,19 @@ import java.io.IOException; */ public class SnapshotCreationException extends SnapshotException { + public SnapshotCreationException(final String repositoryName, final String snapshotName, final Throwable cause) { + super(repositoryName, snapshotName, "failed to create snapshot", cause); + } + + public SnapshotCreationException(final String repositoryName, final SnapshotId snapshotId, final Throwable cause) { + super(repositoryName, snapshotId, "failed to create snapshot", cause); + } + + public SnapshotCreationException(final String repositoryName, final SnapshotId snapshotId, final String msg) { + super(repositoryName, snapshotId, msg); + } + public SnapshotCreationException(StreamInput in) throws IOException { super(in); } - - public SnapshotCreationException(SnapshotId snapshot, Throwable cause) { - super(snapshot, "failed to create snapshot", cause); - } } diff --git a/core/src/main/java/org/elasticsearch/snapshots/SnapshotException.java b/core/src/main/java/org/elasticsearch/snapshots/SnapshotException.java index b109c46f46b..0acd73d62ea 100644 --- a/core/src/main/java/org/elasticsearch/snapshots/SnapshotException.java +++ b/core/src/main/java/org/elasticsearch/snapshots/SnapshotException.java @@ -20,7 +20,7 @@ package org.elasticsearch.snapshots; import org.elasticsearch.ElasticsearchException; -import org.elasticsearch.cluster.metadata.SnapshotId; +import org.elasticsearch.common.Nullable; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; @@ -30,33 +30,68 @@ import java.io.IOException; * Generic snapshot exception */ public class SnapshotException extends ElasticsearchException { - private final SnapshotId snapshot; - public SnapshotException(SnapshotId snapshot, String msg) { + @Nullable + private final String repositoryName; + @Nullable + private final String snapshotName; + + public SnapshotException(final Snapshot snapshot, final String msg) { this(snapshot, msg, null); } - public SnapshotException(SnapshotId snapshot, String msg, Throwable cause) { + public SnapshotException(final Snapshot snapshot, final String msg, final Throwable cause) { super("[" + (snapshot == null ? "_na" : snapshot) + "] " + msg, cause); - this.snapshot = snapshot; + if (snapshot != null) { + this.repositoryName = snapshot.getRepository(); + this.snapshotName = snapshot.getSnapshotId().getName(); + } else { + this.repositoryName = null; + this.snapshotName = null; + } } - public SnapshotException(StreamInput in) throws IOException { + public SnapshotException(final String repositoryName, final SnapshotId snapshotId, final String msg) { + this(repositoryName, snapshotId, msg, null); + } + + public SnapshotException(final String repositoryName, final SnapshotId snapshotId, final String msg, final Throwable cause) { + super("[" + repositoryName + ":" + snapshotId + "] " + msg, cause); + this.repositoryName = repositoryName; + this.snapshotName = snapshotId.getName(); + } + + public SnapshotException(final String repositoryName, final String snapshotName, final String msg) { + this(repositoryName, snapshotName, msg, null); + } + + public SnapshotException(final String repositoryName, final String snapshotName, final String msg, final Throwable cause) { + super("[" + repositoryName + ":" + snapshotName + "]" + msg, cause); + this.repositoryName = repositoryName; + this.snapshotName = snapshotName; + } + + public SnapshotException(final StreamInput in) throws IOException { super(in); - if (in.readBoolean()) { - snapshot = SnapshotId.readSnapshotId(in); - } else { - snapshot = null; - } + repositoryName = in.readOptionalString(); + snapshotName = in.readOptionalString(); } @Override public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); - out.writeOptionalStreamable(snapshot); + out.writeOptionalString(repositoryName); + out.writeOptionalString(snapshotName); } - public SnapshotId snapshot() { - return snapshot; + @Nullable + public String getRepositoryName() { + return repositoryName; } + + @Nullable + public String getSnapshotName() { + return snapshotName; + } + } diff --git a/core/src/main/java/org/elasticsearch/snapshots/SnapshotId.java b/core/src/main/java/org/elasticsearch/snapshots/SnapshotId.java new file mode 100644 index 00000000000..16f371b28f7 --- /dev/null +++ b/core/src/main/java/org/elasticsearch/snapshots/SnapshotId.java @@ -0,0 +1,118 @@ +/* + * 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.snapshots; + +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Writeable; + +import java.io.IOException; +import java.util.Objects; + +/** + * SnapshotId - snapshot name + snapshot UUID + */ +public final class SnapshotId implements Writeable { + + /** + * This value is for older snapshots that don't have a UUID. + */ + public static final String UNASSIGNED_UUID = "_na_"; + + private final String name; + private final String uuid; + + // Caching hash code + private final int hashCode; + + /** + * Constructs a new snapshot + * + * @param name snapshot name + * @param uuid snapshot uuid + */ + public SnapshotId(final String name, final String uuid) { + this.name = Objects.requireNonNull(name); + this.uuid = Objects.requireNonNull(uuid); + this.hashCode = computeHashCode(); + } + + /** + * Constructs a new snapshot from a input stream + * + * @param in input stream + */ + public SnapshotId(final StreamInput in) throws IOException { + name = in.readString(); + uuid = in.readString(); + hashCode = computeHashCode(); + } + + /** + * Returns snapshot name + * + * @return snapshot name + */ + public String getName() { + return name; + } + + /** + * Returns the snapshot UUID + * + * @return snapshot uuid + */ + public String getUUID() { + return uuid; + } + + @Override + public String toString() { + return name + "/" + uuid; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + @SuppressWarnings("unchecked") final SnapshotId that = (SnapshotId) o; + return name.equals(that.name) && uuid.equals(that.uuid); + } + + @Override + public int hashCode() { + return hashCode; + } + + private int computeHashCode() { + return Objects.hash(name, uuid); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(name); + out.writeString(uuid); + } + +} diff --git a/core/src/main/java/org/elasticsearch/snapshots/SnapshotInfo.java b/core/src/main/java/org/elasticsearch/snapshots/SnapshotInfo.java index 2b8ea8ace31..2159fda2237 100644 --- a/core/src/main/java/org/elasticsearch/snapshots/SnapshotInfo.java +++ b/core/src/main/java/org/elasticsearch/snapshots/SnapshotInfo.java @@ -37,17 +37,19 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; /** * Information about a snapshot */ public final class SnapshotInfo implements Comparable, ToXContent, FromXContentBuilder, Writeable { - public static final SnapshotInfo PROTO = new SnapshotInfo("", Collections.emptyList(), 0); + public static final SnapshotInfo PROTO = new SnapshotInfo(new SnapshotId("", ""), Collections.emptyList(), 0); public static final String CONTEXT_MODE_PARAM = "context_mode"; public static final String CONTEXT_MODE_SNAPSHOT = "SNAPSHOT"; private static final FormatDateTimeFormatter DATE_TIME_FORMATTER = Joda.forPattern("strictDateOptionalTime"); private static final String SNAPSHOT = "snapshot"; + private static final String UUID = "uuid"; private static final String INDICES = "indices"; private static final String STATE = "state"; private static final String REASON = "reason"; @@ -68,7 +70,7 @@ public final class SnapshotInfo implements Comparable, ToXContent, private static final String TOTAL_SHARDS = "total_shards"; private static final String SUCCESSFUL_SHARDS = "successful_shards"; - private final String name; + private final SnapshotId snapshotId; private final SnapshotState state; @@ -88,39 +90,35 @@ public final class SnapshotInfo implements Comparable, ToXContent, private final List shardFailures; - public SnapshotInfo(String name, List indices, long startTime) { - this(name, indices, SnapshotState.IN_PROGRESS, null, Version.CURRENT, startTime, 0L, 0, 0, Collections.emptyList()); + public SnapshotInfo(SnapshotId snapshotId, List indices, long startTime) { + this(snapshotId, indices, SnapshotState.IN_PROGRESS, null, Version.CURRENT, startTime, 0L, 0, 0, Collections.emptyList()); } - public SnapshotInfo(String name, List indices, long startTime, String reason, long endTime, + public SnapshotInfo(SnapshotId snapshotId, List indices, long startTime, String reason, long endTime, int totalShards, List shardFailures) { - this(name, indices, snapshotState(reason, shardFailures), reason, Version.CURRENT, + this(snapshotId, indices, snapshotState(reason, shardFailures), reason, Version.CURRENT, startTime, endTime, totalShards, totalShards - shardFailures.size(), shardFailures); } - private SnapshotInfo(String name, List indices, SnapshotState state, String reason, Version version, long startTime, - long endTime, int totalShards, int successfulShards, List shardFailures) { - assert name != null; - assert indices != null; - assert state != null; - assert shardFailures != null; - this.name = name; - this.indices = indices; - this.state = state; + private SnapshotInfo(SnapshotId snapshotId, List indices, SnapshotState state, String reason, Version version, + long startTime, long endTime, int totalShards, int successfulShards, List shardFailures) { + this.snapshotId = Objects.requireNonNull(snapshotId); + this.indices = Objects.requireNonNull(indices); + this.state = Objects.requireNonNull(state); this.reason = reason; this.version = version; this.startTime = startTime; this.endTime = endTime; this.totalShards = totalShards; this.successfulShards = successfulShards; - this.shardFailures = shardFailures; + this.shardFailures = Objects.requireNonNull(shardFailures); } /** * Constructs snapshot information from stream input */ public SnapshotInfo(final StreamInput in) throws IOException { - name = in.readString(); + snapshotId = new SnapshotId(in); int size = in.readVInt(); List indicesListBuilder = new ArrayList<>(); for (int i = 0; i < size; i++) { @@ -147,12 +145,12 @@ public final class SnapshotInfo implements Comparable, ToXContent, } /** - * Returns snapshot name + * Returns snapshot id * - * @return snapshot name + * @return snapshot id */ - public String name() { - return name; + public SnapshotId snapshotId() { + return snapshotId; } /** @@ -270,16 +268,21 @@ public final class SnapshotInfo implements Comparable, ToXContent, } final SnapshotInfo that = (SnapshotInfo) o; - return startTime == that.startTime && name.equals(that.name); + return startTime == that.startTime && snapshotId.equals(that.snapshotId); } @Override public int hashCode() { - int result = name.hashCode(); + int result = snapshotId.hashCode(); result = 31 * result + Long.hashCode(startTime); return result; } + @Override + public String toString() { + return "SnapshotInfo[snapshotId=" + snapshotId + ", state=" + state + ", indices=" + indices + "]"; + } + /** * Returns snapshot REST status */ @@ -303,7 +306,8 @@ public final class SnapshotInfo implements Comparable, ToXContent, // write snapshot info for the API and any other situations builder.startObject(); - builder.field(SNAPSHOT, name); + builder.field(SNAPSHOT, snapshotId.getName()); + builder.field(UUID, snapshotId.getUUID()); builder.field(VERSION_ID, version.id); builder.field(VERSION, version.toString()); builder.startArray(INDICES); @@ -342,7 +346,8 @@ public final class SnapshotInfo implements Comparable, ToXContent, private XContentBuilder toXContentSnapshot(final XContentBuilder builder, final ToXContent.Params params) throws IOException { builder.startObject(SNAPSHOT); - builder.field(NAME, name); + builder.field(NAME, snapshotId.getName()); + builder.field(UUID, snapshotId.getUUID()); builder.field(VERSION_ID, version.id); builder.startArray(INDICES); for (String index : indices) { @@ -380,13 +385,14 @@ public final class SnapshotInfo implements Comparable, ToXContent, */ public static SnapshotInfo fromXContent(final XContentParser parser) throws IOException { String name = null; + String uuid = null; Version version = Version.CURRENT; SnapshotState state = SnapshotState.IN_PROGRESS; String reason = null; List indices = Collections.emptyList(); long startTime = 0; long endTime = 0; - int totalShard = 0; + int totalShards = 0; int successfulShards = 0; List shardFailures = Collections.emptyList(); if (parser.currentToken() == null) { // fresh parser? move to the first token @@ -406,6 +412,8 @@ public final class SnapshotInfo implements Comparable, ToXContent, if (token.isValue()) { if (NAME.equals(currentFieldName)) { name = parser.text(); + } else if (UUID.equals(currentFieldName)) { + uuid = parser.text(); } else if (STATE.equals(currentFieldName)) { state = SnapshotState.valueOf(parser.text()); } else if (REASON.equals(currentFieldName)) { @@ -415,7 +423,7 @@ public final class SnapshotInfo implements Comparable, ToXContent, } else if (END_TIME.equals(currentFieldName)) { endTime = parser.longValue(); } else if (TOTAL_SHARDS.equals(currentFieldName)) { - totalShard = parser.intValue(); + totalShards = parser.intValue(); } else if (SUCCESSFUL_SHARDS.equals(currentFieldName)) { successfulShards = parser.intValue(); } else if (VERSION_ID.equals(currentFieldName)) { @@ -448,12 +456,25 @@ public final class SnapshotInfo implements Comparable, ToXContent, } else { throw new ElasticsearchParseException("unexpected token [" + token + "]"); } - return new SnapshotInfo(name, indices, state, reason, version, startTime, endTime, totalShard, successfulShards, shardFailures); + if (uuid == null) { + // the old format where there wasn't a UUID + uuid = SnapshotId.UNASSIGNED_UUID; + } + return new SnapshotInfo(new SnapshotId(name, uuid), + indices, + state, + reason, + version, + startTime, + endTime, + totalShards, + successfulShards, + shardFailures); } @Override public void writeTo(final StreamOutput out) throws IOException { - out.writeString(name); + snapshotId.writeTo(out); out.writeVInt(indices.size()); for (String index : indices) { out.writeString(index); diff --git a/core/src/main/java/org/elasticsearch/snapshots/SnapshotMissingException.java b/core/src/main/java/org/elasticsearch/snapshots/SnapshotMissingException.java index 27fe3de51e0..5f0979e38d8 100644 --- a/core/src/main/java/org/elasticsearch/snapshots/SnapshotMissingException.java +++ b/core/src/main/java/org/elasticsearch/snapshots/SnapshotMissingException.java @@ -19,7 +19,6 @@ package org.elasticsearch.snapshots; -import org.elasticsearch.cluster.metadata.SnapshotId; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.rest.RestStatus; @@ -30,12 +29,16 @@ import java.io.IOException; */ public class SnapshotMissingException extends SnapshotException { - public SnapshotMissingException(SnapshotId snapshot, Throwable cause) { - super(snapshot, "is missing", cause); + public SnapshotMissingException(final String repositoryName, final SnapshotId snapshotId, final Throwable cause) { + super(repositoryName, snapshotId, "is missing", cause); } - public SnapshotMissingException(SnapshotId snapshot) { - super(snapshot, "is missing"); + public SnapshotMissingException(final String repositoryName, final SnapshotId snapshotId) { + super(repositoryName, snapshotId, "is missing"); + } + + public SnapshotMissingException(final String repositoryName, final String snapshotName) { + super(repositoryName, snapshotName, "is missing"); } public SnapshotMissingException(StreamInput in) throws IOException { diff --git a/core/src/main/java/org/elasticsearch/snapshots/SnapshotRestoreException.java b/core/src/main/java/org/elasticsearch/snapshots/SnapshotRestoreException.java index 940f8162e6b..9003a08a54a 100644 --- a/core/src/main/java/org/elasticsearch/snapshots/SnapshotRestoreException.java +++ b/core/src/main/java/org/elasticsearch/snapshots/SnapshotRestoreException.java @@ -19,7 +19,6 @@ package org.elasticsearch.snapshots; -import org.elasticsearch.cluster.metadata.SnapshotId; import org.elasticsearch.common.io.stream.StreamInput; import java.io.IOException; @@ -28,11 +27,19 @@ import java.io.IOException; * Snapshot restore exception */ public class SnapshotRestoreException extends SnapshotException { - public SnapshotRestoreException(SnapshotId snapshot, String message) { + public SnapshotRestoreException(final String repositoryName, final String snapshotName, final String message) { + super(repositoryName, snapshotName, message); + } + + public SnapshotRestoreException(final String repositoryName, final String snapshotName, final String message, final Throwable cause) { + super(repositoryName, snapshotName, message, cause); + } + + public SnapshotRestoreException(final Snapshot snapshot, final String message) { super(snapshot, message); } - public SnapshotRestoreException(SnapshotId snapshot, String message, Throwable cause) { + public SnapshotRestoreException(final Snapshot snapshot, final String message, final Throwable cause) { super(snapshot, message, cause); } diff --git a/core/src/main/java/org/elasticsearch/snapshots/SnapshotShardsService.java b/core/src/main/java/org/elasticsearch/snapshots/SnapshotShardsService.java index c2a33680efa..b9d8c729223 100644 --- a/core/src/main/java/org/elasticsearch/snapshots/SnapshotShardsService.java +++ b/core/src/main/java/org/elasticsearch/snapshots/SnapshotShardsService.java @@ -27,7 +27,6 @@ import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterStateListener; import org.elasticsearch.cluster.ClusterStateUpdateTask; import org.elasticsearch.cluster.SnapshotsInProgress; -import org.elasticsearch.cluster.metadata.SnapshotId; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.collect.ImmutableOpenMap; @@ -94,7 +93,7 @@ public class SnapshotShardsService extends AbstractLifecycleComponent shardSnapshots = emptyMap(); + private volatile Map shardSnapshots = emptyMap(); private final BlockingQueue updatedSnapshotStateQueue = ConcurrentCollections.newBlockingQueue(); @@ -176,11 +175,11 @@ public class SnapshotShardsService extends AbstractLifecycleComponent * - * @param snapshotId snapshot id + * @param snapshot snapshot * @return map of shard id to snapshot status */ - public Map currentSnapshotShards(SnapshotId snapshotId) { - SnapshotShards snapshotShards = shardSnapshots.get(snapshotId); + public Map currentSnapshotShards(Snapshot snapshot) { + SnapshotShards snapshotShards = shardSnapshots.get(snapshot); if (snapshotShards == null) { return null; } else { @@ -195,24 +194,25 @@ public class SnapshotShardsService extends AbstractLifecycleComponent survivors = new HashMap<>(); + Map survivors = new HashMap<>(); // First, remove snapshots that are no longer there - for (Map.Entry entry : shardSnapshots.entrySet()) { - if (snapshotsInProgress != null && snapshotsInProgress.snapshot(entry.getKey()) != null) { + for (Map.Entry entry : shardSnapshots.entrySet()) { + final Snapshot snapshot = entry.getKey(); + if (snapshotsInProgress != null && snapshotsInProgress.snapshot(snapshot) != null) { survivors.put(entry.getKey(), entry.getValue()); } } // For now we will be mostly dealing with a single snapshot at a time but might have multiple simultaneously running // snapshots in the future - Map> newSnapshots = new HashMap<>(); + Map> newSnapshots = new HashMap<>(); // Now go through all snapshots and update existing or create missing final String localNodeId = clusterService.localNode().getId(); if (snapshotsInProgress != null) { for (SnapshotsInProgress.Entry entry : snapshotsInProgress.entries()) { if (entry.state() == SnapshotsInProgress.State.STARTED) { Map startedShards = new HashMap<>(); - SnapshotShards snapshotShards = shardSnapshots.get(entry.snapshotId()); + SnapshotShards snapshotShards = shardSnapshots.get(entry.snapshot()); for (ObjectObjectCursor shard : entry.shards()) { // Add all new shards to start processing on if (localNodeId.equals(shard.value.nodeId())) { @@ -223,7 +223,7 @@ public class SnapshotShardsService extends AbstractLifecycleComponent shards = new HashMap<>(); @@ -231,15 +231,15 @@ public class SnapshotShardsService extends AbstractLifecycleComponent shard : entry.shards()) { IndexShardSnapshotStatus snapshotStatus = snapshotShards.shards.get(shard.key); @@ -250,16 +250,16 @@ public class SnapshotShardsService extends AbstractLifecycleComponent> entry : newSnapshots.entrySet()) { + for (final Map.Entry> entry : newSnapshots.entrySet()) { for (final Map.Entry shardEntry : entry.getValue().entrySet()) { final ShardId shardId = shardEntry.getKey(); try { @@ -318,11 +318,11 @@ public class SnapshotShardsService extends AbstractLifecycleComponent localShards = currentSnapshotShards(snapshot.snapshotId()); + Map localShards = currentSnapshotShards(snapshot.snapshot()); if (localShards != null) { ImmutableOpenMap masterShards = snapshot.shards(); for(Map.Entry localShard : localShards.entrySet()) { @@ -380,13 +380,13 @@ public class SnapshotShardsService extends AbstractLifecycleComponentOnce shard snapshot is created data node updates state of the shard in the cluster state using the {@link SnapshotShardsService#updateIndexShardSnapshotStatus} method

  • *
  • When last shard is completed master node in {@link SnapshotShardsService#innerUpdateSnapshotState} method marks the snapshot as completed
  • *
  • After cluster state is updated, the {@link #endSnapshot(SnapshotsInProgress.Entry)} finalizes snapshot in the repository, - * notifies all {@link #snapshotCompletionListeners} that snapshot is completed, and finally calls {@link #removeSnapshotFromClusterState(SnapshotId, SnapshotInfo, Throwable)} to remove snapshot from cluster state
  • + * notifies all {@link #snapshotCompletionListeners} that snapshot is completed, and finally calls {@link #removeSnapshotFromClusterState(Snapshot, SnapshotInfo, Throwable)} to remove snapshot from cluster state *
*/ public class SnapshotsService extends AbstractLifecycleComponent implements ClusterStateListener { @@ -121,49 +124,67 @@ public class SnapshotsService extends AbstractLifecycleComponent snapshotIds(final String repositoryName) { + Repository repository = repositoriesService.repository(repositoryName); + assert repository != null; // should only be called once we've validated the repository exists + return repository.snapshots(); + } + /** * Retrieves snapshot from repository * - * @param snapshotId snapshot id + * @param repositoryName repository name + * @param snapshotId snapshot id * @return snapshot * @throws SnapshotMissingException if snapshot is not found */ - public SnapshotInfo snapshot(SnapshotId snapshotId) { - validate(snapshotId); - List entries = currentSnapshots(snapshotId.getRepository(), new String[]{snapshotId.getSnapshot()}); + public SnapshotInfo snapshot(final String repositoryName, final SnapshotId snapshotId) { + List entries = currentSnapshots(repositoryName, Arrays.asList(snapshotId.getName())); if (!entries.isEmpty()) { return inProgressSnapshot(entries.iterator().next()); } - return repositoriesService.repository(snapshotId.getRepository()).readSnapshot(snapshotId); + return repositoriesService.repository(repositoryName).readSnapshot(snapshotId); } /** * Returns a list of snapshots from repository sorted by snapshot creation date * * @param repositoryName repository name + * @param snapshotIds snapshots for which to fetch snapshot information + * @param ignoreUnavailable if true, snapshots that could not be read will only be logged with a warning, + * if false, they will throw an error * @return list of snapshots */ - public List snapshots(String repositoryName, boolean ignoreUnavailable) { - Set snapshotSet = new HashSet<>(); - List entries = currentSnapshots(repositoryName, null); + public List snapshots(final String repositoryName, List snapshotIds, final boolean ignoreUnavailable) { + final Set snapshotSet = new HashSet<>(); + final Set snapshotIdsToIterate = new HashSet<>(snapshotIds); + // first, look at the snapshots in progress + final List entries = + currentSnapshots(repositoryName, snapshotIdsToIterate.stream().map(SnapshotId::getName).collect(Collectors.toList())); for (SnapshotsInProgress.Entry entry : entries) { snapshotSet.add(inProgressSnapshot(entry)); + snapshotIdsToIterate.remove(entry.snapshot().getSnapshotId()); } - Repository repository = repositoriesService.repository(repositoryName); - List snapshotIds = repository.snapshots(); - for (SnapshotId snapshotId : snapshotIds) { + // then, look in the repository + final Repository repository = repositoriesService.repository(repositoryName); + for (SnapshotId snapshotId : snapshotIdsToIterate) { try { snapshotSet.add(repository.readSnapshot(snapshotId)); } catch (Exception ex) { if (ignoreUnavailable) { logger.warn("failed to get snapshot [{}]", ex, snapshotId); } else { - throw new SnapshotException(snapshotId, "Snapshot could not be read", ex); + throw new SnapshotException(repositoryName, snapshotId, "Snapshot could not be read", ex); } } } - - ArrayList snapshotList = new ArrayList<>(snapshotSet); + final ArrayList snapshotList = new ArrayList<>(snapshotSet); CollectionUtil.timSort(snapshotList); return Collections.unmodifiableList(snapshotList); } @@ -174,9 +195,9 @@ public class SnapshotsService extends AbstractLifecycleComponent currentSnapshots(String repositoryName) { + public List currentSnapshots(final String repositoryName) { List snapshotList = new ArrayList<>(); - List entries = currentSnapshots(repositoryName, null); + List entries = currentSnapshots(repositoryName, Collections.emptyList()); for (SnapshotsInProgress.Entry entry : entries) { snapshotList.add(inProgressSnapshot(entry)); } @@ -194,8 +215,11 @@ public class SnapshotsService extends AbstractLifecycleComponent indices = Arrays.asList(indexNameExpressionResolver.concreteIndexNames(currentState, request.indicesOptions(), request.indices())); - logger.trace("[{}][{}] creating snapshot for indices [{}]", request.repository(), request.name(), indices); - newSnapshot = new SnapshotsInProgress.Entry(snapshotId, request.includeGlobalState(), request.partial(), State.INIT, indices, System.currentTimeMillis(), null); + logger.trace("[{}][{}] creating snapshot for indices [{}]", repositoryName, snapshotName, indices); + newSnapshot = new SnapshotsInProgress.Entry(new Snapshot(repositoryName, snapshotId), + request.includeGlobalState(), + request.partial(), + State.INIT, + indices, + System.currentTimeMillis(), + null); snapshots = new SnapshotsInProgress(newSnapshot); } else { // TODO: What should we do if a snapshot is already running? - throw new ConcurrentSnapshotExecutionException(snapshotId, "a snapshot is already running"); + throw new ConcurrentSnapshotExecutionException(repositoryName, snapshotName, "a snapshot is already running"); } return ClusterState.builder(currentState).putCustom(SnapshotsInProgress.TYPE, snapshots).build(); } @Override public void onFailure(String source, Throwable t) { - logger.warn("[{}][{}] failed to create snapshot", t, request.repository(), request.name()); + logger.warn("[{}][{}] failed to create snapshot", t, repositoryName, snapshotName); newSnapshot = null; listener.onFailure(t); } @@ -228,12 +258,9 @@ public class SnapshotsService extends AbstractLifecycleComponent + beginSnapshot(newState, newSnapshot, request.partial(), listener) + ); } } @@ -253,34 +280,36 @@ public class SnapshotsService extends AbstractLifecycleComponent entries = new ArrayList<>(); for (SnapshotsInProgress.Entry entry : snapshots.entries()) { - if (entry.snapshotId().equals(snapshot.snapshotId())) { + if (entry.snapshot().equals(snapshot.snapshot())) { // Replace the snapshot that was just created ImmutableOpenMap shards = shards(currentState, entry.indices()); if (!partial) { @@ -362,13 +394,15 @@ public class SnapshotsService extends AbstractLifecycleComponentemptyList()); + repositoriesService.repository(snapshot.snapshot().getRepository()) + .finalizeSnapshot(snapshot.snapshot().getSnapshotId(), + snapshot.indices(), + snapshot.startTime(), + ExceptionsHelper.detailedMessage(t), + 0, + Collections.emptyList()); } catch (Throwable t2) { - logger.warn("[{}] failed to close snapshot in repository", snapshot.snapshotId()); + logger.warn("[{}] failed to close snapshot in repository", snapshot.snapshot()); } } userCreateSnapshotListener.onFailure(t); @@ -433,7 +472,7 @@ public class SnapshotsService extends AbstractLifecycleComponent * * @param repository repository id - * @param snapshots optional list of snapshots that will be used as a filter + * @param snapshots list of snapshots that will be used as a filter, empty list means no snapshots are filtered * @return list of metadata for currently running snapshots */ - public List currentSnapshots(String repository, String[] snapshots) { + public List currentSnapshots(final String repository, final List snapshots) { SnapshotsInProgress snapshotsInProgress = clusterService.state().custom(SnapshotsInProgress.TYPE); if (snapshotsInProgress == null || snapshotsInProgress.entries().isEmpty()) { return Collections.emptyList(); @@ -458,12 +497,12 @@ public class SnapshotsService extends AbstractLifecycleComponent 0) { + if (snapshots.isEmpty() == false) { for (String snapshot : snapshots) { - if (entry.snapshotId().getSnapshot().equals(snapshot)) { + if (entry.snapshot().getSnapshotId().getName().equals(snapshot)) { return snapshotsInProgress.entries(); } } @@ -474,12 +513,12 @@ public class SnapshotsService extends AbstractLifecycleComponent builder = new ArrayList<>(); for (SnapshotsInProgress.Entry entry : snapshotsInProgress.entries()) { - if (!entry.snapshotId().getRepository().equals(repository)) { + if (entry.snapshot().getRepository().equals(repository) == false) { continue; } - if (snapshots != null && snapshots.length > 0) { + if (snapshots.isEmpty() == false) { for (String snapshot : snapshots) { - if (entry.snapshotId().getSnapshot().equals(snapshot)) { + if (entry.snapshot().getSnapshotId().getName().equals(snapshot)) { builder.add(entry); break; } @@ -494,34 +533,35 @@ public class SnapshotsService extends AbstractLifecycleComponent - * This method is executed on master node and it's complimentary to the {@link SnapshotShardsService#currentSnapshotShards(SnapshotId)} because it + * This method is executed on master node and it's complimentary to the {@link SnapshotShardsService#currentSnapshotShards(Snapshot)} because it * returns similar information but for already finished snapshots. *

* - * @param snapshotId snapshot id + * @param repositoryName repository name + * @param snapshotInfo snapshot info * @return map of shard id to snapshot status */ - public Map snapshotShards(SnapshotId snapshotId) throws IOException { - validate(snapshotId); + public Map snapshotShards(final String repositoryName, + final SnapshotInfo snapshotInfo) throws IOException { Map shardStatus = new HashMap<>(); - Repository repository = repositoriesService.repository(snapshotId.getRepository()); - IndexShardRepository indexShardRepository = repositoriesService.indexShardRepository(snapshotId.getRepository()); - SnapshotInfo snapshot = repository.readSnapshot(snapshotId); - MetaData metaData = repository.readSnapshotMetaData(snapshotId, snapshot, snapshot.indices()); - for (String index : snapshot.indices()) { + Repository repository = repositoriesService.repository(repositoryName); + IndexShardRepository indexShardRepository = repositoriesService.indexShardRepository(repositoryName); + MetaData metaData = repository.readSnapshotMetaData(snapshotInfo, snapshotInfo.indices()); + for (String index : snapshotInfo.indices()) { IndexMetaData indexMetaData = metaData.indices().get(index); if (indexMetaData != null) { int numberOfShards = indexMetaData.getNumberOfShards(); for (int i = 0; i < numberOfShards; i++) { ShardId shardId = new ShardId(indexMetaData.getIndex(), i); - SnapshotShardFailure shardFailure = findShardFailure(snapshot.shardFailures(), shardId); + SnapshotShardFailure shardFailure = findShardFailure(snapshotInfo.shardFailures(), shardId); if (shardFailure != null) { IndexShardSnapshotStatus shardSnapshotStatus = new IndexShardSnapshotStatus(); shardSnapshotStatus.updateStage(IndexShardSnapshotStatus.Stage.FAILURE); shardSnapshotStatus.failure(shardFailure.reason()); shardStatus.put(shardId, shardSnapshotStatus); } else { - IndexShardSnapshotStatus shardSnapshotStatus = indexShardRepository.snapshotStatus(snapshotId, snapshot.version(), shardId); + IndexShardSnapshotStatus shardSnapshotStatus = + indexShardRepository.snapshotStatus(snapshotInfo.snapshotId(), snapshotInfo.version(), shardId); shardStatus.put(shardId, shardSnapshotStatus); } } @@ -606,15 +646,15 @@ public class SnapshotsService extends AbstractLifecycleComponent failures = new ArrayList<>(); ArrayList shardFailures = new ArrayList<>(); for (ObjectObjectCursor shardStatus : entry.shards()) { @@ -824,11 +864,11 @@ public class SnapshotsService extends AbstractLifecycleComponent listener) { clusterService.submitStateUpdateTask("remove snapshot metadata", new ClusterStateUpdateTask() { @Override @@ -863,7 +903,7 @@ public class SnapshotsService extends AbstractLifecycleComponent entries = new ArrayList<>(); for (SnapshotsInProgress.Entry entry : snapshots.entries()) { - if (entry.snapshotId().equals(snapshotId)) { + if (entry.snapshot().equals(snapshot)) { changed = true; } else { entries.add(entry); @@ -879,7 +919,7 @@ public class SnapshotsService extends AbstractLifecycleComponent matchedEntry = repository.snapshots().stream().filter(s -> s.getName().equals(snapshotName)).findFirst(); + // if nothing found by the same name, then look in the cluster state for current in progress snapshots + if (matchedEntry.isPresent() == false) { + matchedEntry = currentSnapshots(repositoryName, Collections.emptyList()).stream() + .map(e -> e.snapshot().getSnapshotId()).filter(s -> s.getName().equals(snapshotName)).findFirst(); + } + if (matchedEntry.isPresent() == false) { + throw new SnapshotMissingException(repositoryName, snapshotName); + } + deleteSnapshot(new Snapshot(repositoryName, matchedEntry.get()), listener); + } + /** * Deletes snapshot from repository. *

* If the snapshot is still running cancels the snapshot first and then deletes it from the repository. * - * @param snapshotId snapshot id - * @param listener listener + * @param snapshot snapshot + * @param listener listener */ - public void deleteSnapshot(final SnapshotId snapshotId, final DeleteSnapshotListener listener) { - validate(snapshotId); + public void deleteSnapshot(final Snapshot snapshot, final DeleteSnapshotListener listener) { clusterService.submitStateUpdateTask("delete snapshot", new ClusterStateUpdateTask() { boolean waitForSnapshot = false; @@ -926,22 +988,22 @@ public class SnapshotsService extends AbstractLifecycleComponent shards; - if (snapshot.state() == State.STARTED && snapshot.shards() != null) { + if (snapshotEntry.state() == State.STARTED && snapshotEntry.shards() != null) { // snapshot is currently running - stop started shards ImmutableOpenMap.Builder shardsBuilder = ImmutableOpenMap.builder(); - for (ObjectObjectCursor shardEntry : snapshot.shards()) { + for (ObjectObjectCursor shardEntry : snapshotEntry.shards()) { ShardSnapshotStatus status = shardEntry.value; if (!status.state().completed()) { shardsBuilder.put(shardEntry.key, new ShardSnapshotStatus(status.nodeId(), State.ABORTED)); @@ -950,14 +1012,14 @@ public class SnapshotsService extends AbstractLifecycleComponent shardStatus : snapshot.shards().values()) { + for (ObjectCursor shardStatus : snapshotEntry.shards().values()) { // Check if we still have shard running on existing nodes if (shardStatus.value.state().completed() == false && shardStatus.value.nodeId() != null && currentState.nodes().get(shardStatus.value.nodeId()) != null) { @@ -972,11 +1034,11 @@ public class SnapshotsService extends AbstractLifecycleComponent { + try { + Repository repository = repositoriesService.repository(snapshot.getRepository()); + repository.deleteSnapshot(snapshot.getSnapshotId()); + listener.onResponse(); + } catch (Throwable t) { + listener.onFailure(t); } }); } @@ -1203,7 +1262,7 @@ public class SnapshotsService extends AbstractLifecycleComponent repoVersions = listRepoVersions(); + // run the test for each supported version + for (final String version : repoVersions) { + final String repoName = "test-repo-" + version; + logger.info("--> creating repository [{}] for version [{}]", repoName, version); + createRepository(version, repoName); + + logger.info("--> get the snapshots"); + final String originalIndex = "index-" + version; + final Set indices = Sets.newHashSet(originalIndex); + final Set snapshotInfos = Sets.newHashSet(getSnapshots(repoName)); + assertThat(snapshotInfos.size(), equalTo(1)); + SnapshotInfo originalSnapshot = snapshotInfos.iterator().next(); + assertThat(originalSnapshot.snapshotId(), equalTo(new SnapshotId("test_1", SnapshotId.UNASSIGNED_UUID))); + assertThat(Sets.newHashSet(originalSnapshot.indices()), equalTo(indices)); + + logger.info("--> restore the original snapshot"); + final Set restoredIndices = Sets.newHashSet( + restoreSnapshot(repoName, originalSnapshot.snapshotId().getName()) + ); + assertThat(restoredIndices, equalTo(indices)); + // make sure it has documents + for (final String searchIdx : restoredIndices) { + assertThat(client().prepareSearch(searchIdx).setSize(0).get().getHits().totalHits(), greaterThan(0L)); + } + deleteIndices(restoredIndices); // delete so we can restore again later + + final String snapshotName2 = "test_2"; + logger.info("--> take a new snapshot of the old index"); + final int addedDocSize = 10; + for (int i = 0; i < addedDocSize; i++) { + index(originalIndex, "doc", Integer.toString(i), "foo", "new-bar-" + i); + } + refresh(); + snapshotInfos.add(createSnapshot(repoName, snapshotName2)); + + logger.info("--> get the snapshots with the newly created snapshot [{}]", snapshotName2); + Set snapshotInfosFromRepo = Sets.newHashSet(getSnapshots(repoName)); + assertThat(snapshotInfosFromRepo, equalTo(snapshotInfos)); + snapshotInfosFromRepo.forEach(snapshotInfo -> { + assertThat(Sets.newHashSet(snapshotInfo.indices()), equalTo(indices)); + }); + + final String snapshotName3 = "test_3"; + final String indexName2 = "index2"; + logger.info("--> take a new snapshot with a new index"); + createIndex(indexName2); + indices.add(indexName2); + for (int i = 0; i < addedDocSize; i++) { + index(indexName2, "doc", Integer.toString(i), "foo", "new-bar-" + i); + } + refresh(); + snapshotInfos.add(createSnapshot(repoName, snapshotName3)); + + logger.info("--> get the snapshots with the newly created snapshot [{}]", snapshotName3); + snapshotInfosFromRepo = Sets.newHashSet(getSnapshots(repoName)); + assertThat(snapshotInfosFromRepo, equalTo(snapshotInfos)); + snapshotInfosFromRepo.forEach(snapshotInfo -> { + if (snapshotInfo.snapshotId().getName().equals(snapshotName3)) { + // only the last snapshot has all the indices + assertThat(Sets.newHashSet(snapshotInfo.indices()), equalTo(indices)); + } else { + assertThat(Sets.newHashSet(snapshotInfo.indices()), equalTo(Sets.newHashSet(originalIndex))); + } + }); + deleteIndices(indices); // clean up indices + + logger.info("--> restore the old snapshot again"); + Set oldRestoredIndices = Sets.newHashSet(restoreSnapshot(repoName, originalSnapshot.snapshotId().getName())); + assertThat(oldRestoredIndices, equalTo(Sets.newHashSet(originalIndex))); + for (final String searchIdx : oldRestoredIndices) { + assertThat(client().prepareSearch(searchIdx).setSize(0).get().getHits().totalHits(), + greaterThanOrEqualTo((long)addedDocSize)); + } + deleteIndices(oldRestoredIndices); + + logger.info("--> restore the new snapshot"); + Set newSnapshotIndices = Sets.newHashSet(restoreSnapshot(repoName, snapshotName3)); + assertThat(newSnapshotIndices, equalTo(Sets.newHashSet(originalIndex, indexName2))); + for (final String searchIdx : newSnapshotIndices) { + assertThat(client().prepareSearch(searchIdx).setSize(0).get().getHits().totalHits(), + greaterThanOrEqualTo((long)addedDocSize)); + } + deleteIndices(newSnapshotIndices); // clean up indices before starting again + } + } + + private List listRepoVersions() throws Exception { + final String prefix = "repo"; + final List repoVersions = new ArrayList<>(); + final Path repoFiles = getBwcIndicesPath(); + try (final DirectoryStream dirStream = Files.newDirectoryStream(repoFiles, prefix + "-*.zip")) { + for (final Path entry : dirStream) { + final String fileName = entry.getFileName().toString(); + String version = fileName.substring(prefix.length() + 1); + version = version.substring(0, version.length() - ".zip".length()); + repoVersions.add(version); + } + } + return Collections.unmodifiableList(repoVersions); + } + + private void createRepository(final String version, final String repoName) throws Exception { + final String prefix = "repo"; + final Path repoFile = getBwcIndicesPath().resolve(prefix + "-" + version + ".zip"); + final Path repoPath = randomRepoPath(); + FileTestUtils.unzip(repoFile, repoPath, "repo/"); + assertAcked(client().admin().cluster().preparePutRepository(repoName) + .setType("fs") + .setSettings(Settings.builder().put("location", repoPath))); + } + + private List getSnapshots(final String repoName) throws Exception { + return client().admin().cluster().prepareGetSnapshots(repoName) + .addSnapshots("_all") + .get() + .getSnapshots(); + } + + private SnapshotInfo createSnapshot(final String repoName, final String snapshotName) throws Exception { + return client().admin().cluster().prepareCreateSnapshot(repoName, snapshotName) + .setWaitForCompletion(true) + .get() + .getSnapshotInfo(); + } + + private List restoreSnapshot(final String repoName, final String snapshotName) throws Exception { + return client().admin().cluster().prepareRestoreSnapshot(repoName, snapshotName) + .setWaitForCompletion(true) + .get() + .getRestoreInfo() + .indices(); + } + + private void deleteIndices(final Set indices) throws Exception { + client().admin().indices().prepareDelete(indices.toArray(new String[indices.size()])).get(); + } + +} diff --git a/core/src/test/java/org/elasticsearch/bwcompat/RestoreBackwardsCompatIT.java b/core/src/test/java/org/elasticsearch/bwcompat/RestoreBackwardsCompatIT.java index ad16267cde9..419104bfe34 100644 --- a/core/src/test/java/org/elasticsearch/bwcompat/RestoreBackwardsCompatIT.java +++ b/core/src/test/java/org/elasticsearch/bwcompat/RestoreBackwardsCompatIT.java @@ -40,7 +40,6 @@ import org.elasticsearch.test.ESIntegTestCase.Scope; import org.elasticsearch.test.VersionUtils; import java.io.IOException; -import java.lang.reflect.Modifier; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.DirectoryStream; diff --git a/core/src/test/java/org/elasticsearch/cluster/ClusterStateDiffIT.java b/core/src/test/java/org/elasticsearch/cluster/ClusterStateDiffIT.java index 1d013737a30..b82b5e0ba60 100644 --- a/core/src/test/java/org/elasticsearch/cluster/ClusterStateDiffIT.java +++ b/core/src/test/java/org/elasticsearch/cluster/ClusterStateDiffIT.java @@ -30,7 +30,7 @@ import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.IndexTemplateMetaData; import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.metadata.RepositoriesMetaData; -import org.elasticsearch.cluster.metadata.SnapshotId; +import org.elasticsearch.snapshots.SnapshotId; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.node.DiscoveryNodes; import org.elasticsearch.cluster.routing.IndexRoutingTable; @@ -51,6 +51,7 @@ import org.elasticsearch.gateway.GatewayService; import org.elasticsearch.index.Index; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.shard.ShardId; +import org.elasticsearch.snapshots.Snapshot; import org.elasticsearch.test.ESIntegTestCase; import java.util.Collections; @@ -653,7 +654,7 @@ public class ClusterStateDiffIT extends ESIntegTestCase { switch (randomIntBetween(0, 1)) { case 0: return new SnapshotsInProgress(new SnapshotsInProgress.Entry( - new SnapshotId(randomName("repo"), randomName("snap")), + new Snapshot(randomName("repo"), new SnapshotId(randomName("snap"), UUIDs.randomBase64UUID())), randomBoolean(), randomBoolean(), SnapshotsInProgress.State.fromValue((byte) randomIntBetween(0, 6)), @@ -662,7 +663,7 @@ public class ClusterStateDiffIT extends ESIntegTestCase { ImmutableOpenMap.of())); case 1: return new RestoreInProgress(new RestoreInProgress.Entry( - new SnapshotId(randomName("repo"), randomName("snap")), + new Snapshot(randomName("repo"), new SnapshotId(randomName("snap"), UUIDs.randomBase64UUID())), RestoreInProgress.State.fromValue((byte) randomIntBetween(0, 3)), emptyList(), ImmutableOpenMap.of())); diff --git a/core/src/test/java/org/elasticsearch/cluster/routing/ShardRoutingTests.java b/core/src/test/java/org/elasticsearch/cluster/routing/ShardRoutingTests.java index 31f48a68654..7267252b19f 100644 --- a/core/src/test/java/org/elasticsearch/cluster/routing/ShardRoutingTests.java +++ b/core/src/test/java/org/elasticsearch/cluster/routing/ShardRoutingTests.java @@ -20,18 +20,15 @@ package org.elasticsearch.cluster.routing; import org.elasticsearch.Version; -import org.elasticsearch.cluster.ClusterState; -import org.elasticsearch.cluster.metadata.IndexMetaData; -import org.elasticsearch.cluster.metadata.MetaData; -import org.elasticsearch.cluster.metadata.SnapshotId; +import org.elasticsearch.common.UUIDs; +import org.elasticsearch.snapshots.SnapshotId; import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.snapshots.Snapshot; import org.elasticsearch.test.ESTestCase; import java.io.IOException; -import static org.hamcrest.Matchers.equalTo; - public class ShardRoutingTests extends ESTestCase { public void testIsSameAllocation() { @@ -155,8 +152,8 @@ public class ShardRoutingTests extends ESTestCase { case 4: // change restore source otherRouting = TestShardRouting.newShardRouting(otherRouting.getIndexName(), otherRouting.id(), otherRouting.currentNodeId(), otherRouting.relocatingNodeId(), - otherRouting.restoreSource() == null ? new RestoreSource(new SnapshotId("test", "s1"), Version.CURRENT, "test") : - new RestoreSource(otherRouting.restoreSource().snapshotId(), Version.CURRENT, otherRouting.index() + "_1"), + otherRouting.restoreSource() == null ? new RestoreSource(new Snapshot("test", new SnapshotId("s1", UUIDs.randomBase64UUID())), Version.CURRENT, "test") : + new RestoreSource(otherRouting.restoreSource().snapshot(), Version.CURRENT, otherRouting.index() + "_1"), otherRouting.primary(), otherRouting.state(), otherRouting.unassignedInfo()); break; case 5: diff --git a/core/src/test/java/org/elasticsearch/cluster/routing/UnassignedInfoTests.java b/core/src/test/java/org/elasticsearch/cluster/routing/UnassignedInfoTests.java index a0ef14ee98d..82703c6f29e 100644 --- a/core/src/test/java/org/elasticsearch/cluster/routing/UnassignedInfoTests.java +++ b/core/src/test/java/org/elasticsearch/cluster/routing/UnassignedInfoTests.java @@ -26,7 +26,8 @@ import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.MetaData; -import org.elasticsearch.cluster.metadata.SnapshotId; +import org.elasticsearch.common.UUIDs; +import org.elasticsearch.snapshots.SnapshotId; import org.elasticsearch.cluster.node.DiscoveryNodes; import org.elasticsearch.cluster.routing.allocation.AllocationService; import org.elasticsearch.cluster.routing.allocation.FailedRerouteAllocation; @@ -35,6 +36,7 @@ import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.index.Index; +import org.elasticsearch.snapshots.Snapshot; import org.elasticsearch.test.ESAllocationTestCase; import java.util.Collections; @@ -130,7 +132,7 @@ public class UnassignedInfoTests extends ESAllocationTestCase { .build(); ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT) .metaData(metaData) - .routingTable(RoutingTable.builder().addAsNewRestore(metaData.index("test"), new RestoreSource(new SnapshotId("rep1", "snp1"), Version.CURRENT, "test"), new IntHashSet()).build()).build(); + .routingTable(RoutingTable.builder().addAsNewRestore(metaData.index("test"), new RestoreSource(new Snapshot("rep1", new SnapshotId("snp1", UUIDs.randomBase64UUID())), Version.CURRENT, "test"), new IntHashSet()).build()).build(); for (ShardRouting shard : clusterState.getRoutingNodes().shardsWithState(UNASSIGNED)) { assertThat(shard.unassignedInfo().getReason(), equalTo(UnassignedInfo.Reason.NEW_INDEX_RESTORED)); } @@ -142,7 +144,7 @@ public class UnassignedInfoTests extends ESAllocationTestCase { .build(); ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT) .metaData(metaData) - .routingTable(RoutingTable.builder().addAsRestore(metaData.index("test"), new RestoreSource(new SnapshotId("rep1", "snp1"), Version.CURRENT, "test")).build()).build(); + .routingTable(RoutingTable.builder().addAsRestore(metaData.index("test"), new RestoreSource(new Snapshot("rep1", new SnapshotId("snp1", UUIDs.randomBase64UUID())), Version.CURRENT, "test")).build()).build(); for (ShardRouting shard : clusterState.getRoutingNodes().shardsWithState(UNASSIGNED)) { assertThat(shard.unassignedInfo().getReason(), equalTo(UnassignedInfo.Reason.EXISTING_INDEX_RESTORED)); } diff --git a/core/src/test/java/org/elasticsearch/cluster/routing/allocation/NodeVersionAllocationDeciderTests.java b/core/src/test/java/org/elasticsearch/cluster/routing/allocation/NodeVersionAllocationDeciderTests.java index 8eb3e2f8a8b..54df74db1f5 100644 --- a/core/src/test/java/org/elasticsearch/cluster/routing/allocation/NodeVersionAllocationDeciderTests.java +++ b/core/src/test/java/org/elasticsearch/cluster/routing/allocation/NodeVersionAllocationDeciderTests.java @@ -25,7 +25,8 @@ import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.EmptyClusterInfoService; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.MetaData; -import org.elasticsearch.cluster.metadata.SnapshotId; +import org.elasticsearch.common.UUIDs; +import org.elasticsearch.snapshots.SnapshotId; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.node.DiscoveryNodes; import org.elasticsearch.cluster.routing.IndexRoutingTable; @@ -48,6 +49,7 @@ import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.DummyTransportAddress; import org.elasticsearch.index.shard.ShardId; +import org.elasticsearch.snapshots.Snapshot; import org.elasticsearch.test.ESAllocationTestCase; import org.elasticsearch.test.VersionUtils; import org.elasticsearch.test.gateway.NoopGatewayAllocator; @@ -360,7 +362,8 @@ public class NodeVersionAllocationDeciderTests extends ESAllocationTestCase { ClusterState state = ClusterState.builder(ClusterName.DEFAULT) .metaData(metaData) - .routingTable(RoutingTable.builder().addAsRestore(metaData.index("test"), new RestoreSource(new SnapshotId("rep1", "snp1"), + .routingTable(RoutingTable.builder().addAsRestore(metaData.index("test"), + new RestoreSource(new Snapshot("rep1", new SnapshotId("snp1", UUIDs.randomBase64UUID())), Version.CURRENT, "test")).build()) .nodes(DiscoveryNodes.builder().put(newNode).put(oldNode1).put(oldNode2)).build(); AllocationDeciders allocationDeciders = new AllocationDeciders(Settings.EMPTY, new AllocationDecider[]{ diff --git a/core/src/test/java/org/elasticsearch/gateway/PrimaryShardAllocatorTests.java b/core/src/test/java/org/elasticsearch/gateway/PrimaryShardAllocatorTests.java index ee1cf7280e7..3e088f4f82e 100644 --- a/core/src/test/java/org/elasticsearch/gateway/PrimaryShardAllocatorTests.java +++ b/core/src/test/java/org/elasticsearch/gateway/PrimaryShardAllocatorTests.java @@ -24,7 +24,7 @@ import org.elasticsearch.Version; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.MetaData; -import org.elasticsearch.cluster.metadata.SnapshotId; +import org.elasticsearch.snapshots.SnapshotId; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.node.DiscoveryNodes; import org.elasticsearch.cluster.routing.RestoreSource; @@ -40,6 +40,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.index.shard.ShardStateMetaData; +import org.elasticsearch.snapshots.Snapshot; import org.elasticsearch.test.ESAllocationTestCase; import org.junit.Before; @@ -339,8 +340,9 @@ public class PrimaryShardAllocatorTests extends ESAllocationTestCase { .putActiveAllocationIds(0, hasActiveAllocation ? Sets.newHashSet("allocId") : Collections.emptySet())) .build(); + final Snapshot snapshot = new Snapshot("test", new SnapshotId("test", UUIDs.randomBase64UUID())); RoutingTable routingTable = RoutingTable.builder() - .addAsRestore(metaData.index(shardId.getIndex()), new RestoreSource(new SnapshotId("test", "test"), version, shardId.getIndexName())) + .addAsRestore(metaData.index(shardId.getIndex()), new RestoreSource(snapshot, version, shardId.getIndexName())) .build(); ClusterState state = ClusterState.builder(org.elasticsearch.cluster.ClusterName.DEFAULT) .metaData(metaData) @@ -419,7 +421,7 @@ public class PrimaryShardAllocatorTests extends ESAllocationTestCase { .build(); RoutingTable routingTable = RoutingTable.builder() - .addAsRestore(metaData.index(shardId.getIndex()), new RestoreSource(new SnapshotId("test", "test"), Version.CURRENT, shardId.getIndexName())) + .addAsRestore(metaData.index(shardId.getIndex()), new RestoreSource(new Snapshot("test", new SnapshotId("test", UUIDs.randomBase64UUID())), Version.CURRENT, shardId.getIndexName())) .build(); ClusterState state = ClusterState.builder(org.elasticsearch.cluster.ClusterName.DEFAULT) .metaData(metaData) diff --git a/core/src/test/java/org/elasticsearch/index/shard/IndexShardTests.java b/core/src/test/java/org/elasticsearch/index/shard/IndexShardTests.java index 52aa07b3eb3..7774537c734 100644 --- a/core/src/test/java/org/elasticsearch/index/shard/IndexShardTests.java +++ b/core/src/test/java/org/elasticsearch/index/shard/IndexShardTests.java @@ -49,7 +49,8 @@ import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.InternalClusterInfoService; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.MappingMetaData; -import org.elasticsearch.cluster.metadata.SnapshotId; +import org.elasticsearch.common.UUIDs; +import org.elasticsearch.snapshots.SnapshotId; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.routing.AllocationId; import org.elasticsearch.cluster.routing.RestoreSource; @@ -97,6 +98,7 @@ import org.elasticsearch.index.translog.Translog; import org.elasticsearch.indices.IndicesService; import org.elasticsearch.indices.recovery.RecoveryState; import org.elasticsearch.plugins.Plugin; +import org.elasticsearch.snapshots.Snapshot; import org.elasticsearch.test.DummyShardLock; import org.elasticsearch.test.ESSingleNodeTestCase; import org.elasticsearch.test.FieldMaskingReader; @@ -1110,7 +1112,8 @@ public class IndexShardTests extends ESSingleNodeTestCase { client().admin().indices().prepareFlush("test").get(); // only flush test final ShardRouting origRouting = test_target.getShardOrNull(0).routingEntry(); ShardRouting routing = ShardRoutingHelper.reinit(origRouting); - routing = ShardRoutingHelper.newWithRestoreSource(routing, new RestoreSource(new SnapshotId("foo", "bar"), Version.CURRENT, "test")); + final Snapshot snapshot = new Snapshot("foo", new SnapshotId("bar", UUIDs.randomBase64UUID())); + routing = ShardRoutingHelper.newWithRestoreSource(routing, new RestoreSource(snapshot, Version.CURRENT, "test")); test_target.removeShard(0, "just do it man!"); final IndexShard test_target_shard = test_target.createShard(routing); Store sourceStore = test_shard.store(); diff --git a/core/src/test/java/org/elasticsearch/repositories/blobstore/BlobStoreRepositoryTests.java b/core/src/test/java/org/elasticsearch/repositories/blobstore/BlobStoreRepositoryTests.java new file mode 100644 index 00000000000..3d46c0bbacf --- /dev/null +++ b/core/src/test/java/org/elasticsearch/repositories/blobstore/BlobStoreRepositoryTests.java @@ -0,0 +1,230 @@ +/* + * 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.repositories.blobstore; + +import org.elasticsearch.action.admin.cluster.repositories.put.PutRepositoryResponse; +import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.UUIDs; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.collect.Tuple; +import org.elasticsearch.common.io.stream.BytesStreamOutput; +import org.elasticsearch.common.io.stream.OutputStreamStreamOutput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.set.Sets; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.repositories.RepositoriesService; +import org.elasticsearch.snapshots.SnapshotId; +import org.elasticsearch.test.ESIntegTestCase; +import org.elasticsearch.test.ESSingleNodeTestCase; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import static org.elasticsearch.repositories.blobstore.BlobStoreRepository.blobId; +import static org.elasticsearch.repositories.blobstore.BlobStoreRepository.parseNameUUIDFromBlobName; +import static org.hamcrest.Matchers.equalTo; + +/** + * Tests for the {@link BlobStoreRepository} and its subclasses. + */ +public class BlobStoreRepositoryTests extends ESSingleNodeTestCase { + + public void testRetrieveSnapshots() throws Exception { + final Client client = client(); + final Path location = ESIntegTestCase.randomRepoPath(node().settings()); + final String repositoryName = "test-repo"; + + logger.info("--> creating repository"); + PutRepositoryResponse putRepositoryResponse = + client.admin().cluster().preparePutRepository(repositoryName) + .setType("fs") + .setSettings(Settings.builder().put(node().settings()).put("location", location)) + .get(); + assertThat(putRepositoryResponse.isAcknowledged(), equalTo(true)); + + logger.info("--> creating an index and indexing documents"); + final String indexName = "test-idx"; + createIndex(indexName); + ensureGreen(); + int numDocs = randomIntBetween(10, 20); + for (int i = 0; i < numDocs; i++) { + String id = Integer.toString(i); + client().prepareIndex(indexName, "type1", id).setSource("text", "sometext").get(); + } + client().admin().indices().prepareFlush(indexName).setWaitIfOngoing(true).get(); + + logger.info("--> create first snapshot"); + CreateSnapshotResponse createSnapshotResponse = client.admin() + .cluster() + .prepareCreateSnapshot(repositoryName, "test-snap-1") + .setWaitForCompletion(true) + .setIndices(indexName) + .get(); + final SnapshotId snapshotId1 = createSnapshotResponse.getSnapshotInfo().snapshotId(); + + logger.info("--> create second snapshot"); + createSnapshotResponse = client.admin() + .cluster() + .prepareCreateSnapshot(repositoryName, "test-snap-2") + .setWaitForCompletion(true) + .setIndices(indexName) + .get(); + final SnapshotId snapshotId2 = createSnapshotResponse.getSnapshotInfo().snapshotId(); + + logger.info("--> make sure the node's repository can resolve the snapshots"); + final RepositoriesService repositoriesService = getInstanceFromNode(RepositoriesService.class); + @SuppressWarnings("unchecked") final BlobStoreRepository repository = + (BlobStoreRepository) repositoriesService.repository(repositoryName); + final List originalSnapshots = Arrays.asList(snapshotId1, snapshotId2); + + List snapshotIds = repository.snapshots().stream() + .sorted((s1, s2) -> s1.getName().compareTo(s2.getName())) + .collect(Collectors.toList()); + assertThat(snapshotIds, equalTo(originalSnapshots)); + } + + public void testSnapshotIndexFile() throws Exception { + final Client client = client(); + final Path location = ESIntegTestCase.randomRepoPath(node().settings()); + final String repositoryName = "test-repo"; + + PutRepositoryResponse putRepositoryResponse = + client.admin().cluster().preparePutRepository(repositoryName) + .setType("fs") + .setSettings(Settings.builder().put(node().settings()).put("location", location)) + .get(); + assertThat(putRepositoryResponse.isAcknowledged(), equalTo(true)); + + final RepositoriesService repositoriesService = getInstanceFromNode(RepositoriesService.class); + @SuppressWarnings("unchecked") final BlobStoreRepository repository = + (BlobStoreRepository) repositoriesService.repository(repositoryName); + + // write to and read from a snapshot file with no entries + repository.writeSnapshotList(Collections.emptyList()); + List readSnapshotIds = repository.readSnapshotList(); + assertThat(readSnapshotIds.size(), equalTo(0)); + + // write to and read from a snapshot file with a random number of entries + final int numSnapshots = randomIntBetween(1, 1000); + final List snapshotIds = new ArrayList<>(numSnapshots); + for (int i = 0; i < numSnapshots; i++) { + snapshotIds.add(new SnapshotId(randomAsciiOfLength(8), UUIDs.randomBase64UUID())); + } + repository.writeSnapshotList(snapshotIds); + readSnapshotIds = repository.readSnapshotList(); + assertThat(readSnapshotIds, equalTo(snapshotIds)); + } + + public void testOldIndexFileFormat() throws Exception { + final Client client = client(); + final Path location = ESIntegTestCase.randomRepoPath(node().settings()); + final String repositoryName = "test-repo"; + + PutRepositoryResponse putRepositoryResponse = + client.admin().cluster().preparePutRepository(repositoryName) + .setType("fs") + .setSettings(Settings.builder().put(node().settings()).put("location", location)) + .get(); + assertThat(putRepositoryResponse.isAcknowledged(), equalTo(true)); + + final RepositoriesService repositoriesService = getInstanceFromNode(RepositoriesService.class); + @SuppressWarnings("unchecked") final BlobStoreRepository repository = + (BlobStoreRepository) repositoriesService.repository(repositoryName); + + // write old index file format + final int numOldSnapshots = randomIntBetween(1, 50); + final List snapshotIds = new ArrayList<>(); + for (int i = 0; i < numOldSnapshots; i++) { + snapshotIds.add(new SnapshotId(randomAsciiOfLength(8), SnapshotId.UNASSIGNED_UUID)); + } + writeOldFormat(repository, snapshotIds.stream().map(SnapshotId::getName).collect(Collectors.toList())); + List readSnapshotIds = repository.readSnapshotList(); + assertThat(Sets.newHashSet(readSnapshotIds), equalTo(Sets.newHashSet(snapshotIds))); + + // write to and read from a snapshot file with a random number of new entries added + final int numSnapshots = randomIntBetween(1, 1000); + for (int i = 0; i < numSnapshots; i++) { + snapshotIds.add(new SnapshotId(randomAsciiOfLength(8), UUIDs.randomBase64UUID())); + } + repository.writeSnapshotList(snapshotIds); + readSnapshotIds = repository.readSnapshotList(); + assertThat(Sets.newHashSet(readSnapshotIds), equalTo(Sets.newHashSet(snapshotIds))); + } + + public void testParseUUIDFromBlobName() { + String blobStr = "abc123"; + Tuple pair = parseNameUUIDFromBlobName(blobStr); + assertThat(pair.v1(), equalTo(blobStr)); // snapshot name + assertThat(pair.v2(), equalTo(SnapshotId.UNASSIGNED_UUID)); // snapshot uuid + blobStr = "abcefghijklmnopqrstuvwxyz"; + pair = parseNameUUIDFromBlobName(blobStr); + assertThat(pair.v1(), equalTo(blobStr)); + assertThat(pair.v2(), equalTo(SnapshotId.UNASSIGNED_UUID)); + blobStr = "abc123-xyz"; // not enough characters after '-' to have a uuid + pair = parseNameUUIDFromBlobName(blobStr); + assertThat(pair.v1(), equalTo(blobStr)); + assertThat(pair.v2(), equalTo(SnapshotId.UNASSIGNED_UUID)); + blobStr = "abc123-a1b2c3d4e5f6g7h8i9j0k1"; + pair = parseNameUUIDFromBlobName(blobStr); + assertThat(pair.v1(), equalTo("abc123")); + assertThat(pair.v2(), equalTo("a1b2c3d4e5f6g7h8i9j0k1")); + } + + public void testBlobId() { + SnapshotId snapshotId = new SnapshotId("abc123", SnapshotId.UNASSIGNED_UUID); + assertThat(blobId(snapshotId), equalTo("abc123")); // just the snapshot name + snapshotId = new SnapshotId("abc-123", SnapshotId.UNASSIGNED_UUID); + assertThat(blobId(snapshotId), equalTo("abc-123")); // just the snapshot name + String uuid = UUIDs.randomBase64UUID(); + snapshotId = new SnapshotId("abc123", uuid); + assertThat(blobId(snapshotId), equalTo("abc123-" + uuid)); // snapshot name + '-' + uuid + uuid = UUIDs.randomBase64UUID(); + snapshotId = new SnapshotId("abc-123", uuid); + assertThat(blobId(snapshotId), equalTo("abc-123-" + uuid)); // snapshot name + '-' + uuid + } + + private void writeOldFormat(final BlobStoreRepository repository, final List snapshotNames) throws Exception { + final BytesReference bRef; + try (BytesStreamOutput bStream = new BytesStreamOutput()) { + try (StreamOutput stream = new OutputStreamStreamOutput(bStream)) { + XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON, stream); + builder.startObject(); + builder.startArray("snapshots"); + for (final String snapshotName : snapshotNames) { + builder.value(snapshotName); + } + builder.endArray(); + builder.endObject(); + builder.close(); + } + bRef = bStream.bytes(); + } + repository.blobContainer().writeBlob(BlobStoreRepository.SNAPSHOTS_FILE, bRef); // write to index file + } + +} diff --git a/core/src/test/java/org/elasticsearch/rest/action/cat/RestRecoveryActionTests.java b/core/src/test/java/org/elasticsearch/rest/action/cat/RestRecoveryActionTests.java index 978d226da40..b603ded8697 100644 --- a/core/src/test/java/org/elasticsearch/rest/action/cat/RestRecoveryActionTests.java +++ b/core/src/test/java/org/elasticsearch/rest/action/cat/RestRecoveryActionTests.java @@ -21,7 +21,8 @@ package org.elasticsearch.rest.action.cat; import org.elasticsearch.action.ShardOperationFailedException; import org.elasticsearch.action.admin.indices.recovery.RecoveryResponse; -import org.elasticsearch.cluster.metadata.SnapshotId; +import org.elasticsearch.common.UUIDs; +import org.elasticsearch.snapshots.SnapshotId; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.routing.RestoreSource; import org.elasticsearch.common.Randomness; @@ -32,6 +33,7 @@ import org.elasticsearch.index.Index; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.indices.recovery.RecoveryState; import org.elasticsearch.rest.RestController; +import org.elasticsearch.snapshots.Snapshot; import org.elasticsearch.test.ESTestCase; import java.util.ArrayList; @@ -76,10 +78,9 @@ public class RestRecoveryActionTests extends ESTestCase { final RestoreSource restoreSource = randomBoolean() ? mock(RestoreSource.class) : null; if (restoreSource != null) { - final SnapshotId snapshotId = mock(SnapshotId.class); - when(snapshotId.getRepository()).thenReturn(randomAsciiOfLength(8)); - when(snapshotId.getSnapshot()).thenReturn(randomAsciiOfLength(8)); - when(restoreSource.snapshotId()).thenReturn(snapshotId); + final Snapshot snapshot = new Snapshot(randomAsciiOfLength(8), + new SnapshotId(randomAsciiOfLength(8), UUIDs.randomBase64UUID())); + when(restoreSource.snapshot()).thenReturn(snapshot); } RecoveryState.Index index = mock(RecoveryState.Index.class); @@ -166,10 +167,10 @@ public class RestRecoveryActionTests extends ESTestCase { assertThat(cells.get(8).value, equalTo(state.getTargetNode().getName())); assertThat( cells.get(9).value, - equalTo(state.getRestoreSource() == null ? "n/a" : state.getRestoreSource().snapshotId().getRepository())); + equalTo(state.getRestoreSource() == null ? "n/a" : state.getRestoreSource().snapshot().getRepository())); assertThat( cells.get(10).value, - equalTo(state.getRestoreSource() == null ? "n/a" : state.getRestoreSource().snapshotId().getSnapshot())); + equalTo(state.getRestoreSource() == null ? "n/a" : state.getRestoreSource().snapshot().getSnapshotId().getName())); assertThat(cells.get(11).value, equalTo(state.getIndex().totalRecoverFiles())); assertThat(cells.get(12).value, equalTo(state.getIndex().recoveredFileCount())); assertThat(cells.get(13).value, equalTo(percent(state.getIndex().recoveredFilesPercent()))); diff --git a/core/src/test/java/org/elasticsearch/snapshots/AbstractSnapshotIntegTestCase.java b/core/src/test/java/org/elasticsearch/snapshots/AbstractSnapshotIntegTestCase.java index 102173626f3..50fb3f9074b 100644 --- a/core/src/test/java/org/elasticsearch/snapshots/AbstractSnapshotIntegTestCase.java +++ b/core/src/test/java/org/elasticsearch/snapshots/AbstractSnapshotIntegTestCase.java @@ -25,7 +25,6 @@ import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterStateListener; import org.elasticsearch.cluster.ClusterStateUpdateTask; import org.elasticsearch.cluster.SnapshotsInProgress; -import org.elasticsearch.cluster.metadata.SnapshotId; import org.elasticsearch.cluster.routing.allocation.decider.EnableAllocationDecider; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.cluster.service.PendingClusterTask; @@ -108,18 +107,29 @@ public abstract class AbstractSnapshotIntegTestCase extends ESIntegTestCase { fail("Timeout!!!"); } - public SnapshotInfo waitForCompletion(String repository, String snapshot, TimeValue timeout) throws InterruptedException { + public SnapshotInfo waitForCompletion(String repository, String snapshotName, TimeValue timeout) throws InterruptedException { long start = System.currentTimeMillis(); - SnapshotId snapshotId = new SnapshotId(repository, snapshot); while (System.currentTimeMillis() - start < timeout.millis()) { - List snapshotInfos = client().admin().cluster().prepareGetSnapshots(repository).setSnapshots(snapshot).get().getSnapshots(); + List snapshotInfos = client().admin().cluster().prepareGetSnapshots(repository).setSnapshots(snapshotName).get().getSnapshots(); assertThat(snapshotInfos.size(), equalTo(1)); if (snapshotInfos.get(0).state().completed()) { // Make sure that snapshot clean up operations are finished ClusterStateResponse stateResponse = client().admin().cluster().prepareState().get(); SnapshotsInProgress snapshotsInProgress = stateResponse.getState().custom(SnapshotsInProgress.TYPE); - if (snapshotsInProgress == null || snapshotsInProgress.snapshot(snapshotId) == null) { + if (snapshotsInProgress == null) { return snapshotInfos.get(0); + } else { + boolean found = false; + for (SnapshotsInProgress.Entry entry : snapshotsInProgress.entries()) { + final Snapshot curr = entry.snapshot(); + if (curr.getRepository().equals(repository) && curr.getSnapshotId().getName().equals(snapshotName)) { + found = true; + break; + } + } + if (found == false) { + return snapshotInfos.get(0); + } } } Thread.sleep(100); @@ -128,12 +138,13 @@ public abstract class AbstractSnapshotIntegTestCase extends ESIntegTestCase { return null; } - public static String blockNodeWithIndex(String index) { - for(String node : internalCluster().nodesInclude("test-idx")) { - ((MockRepository)internalCluster().getInstance(RepositoriesService.class, node).repository("test-repo")).blockOnDataFiles(true); + public static String blockNodeWithIndex(final String repositoryName, final String indexName) { + for(String node : internalCluster().nodesInclude(indexName)) { + ((MockRepository)internalCluster().getInstance(RepositoriesService.class, node).repository(repositoryName)) + .blockOnDataFiles(true); return node; } - fail("No nodes for the index " + index + " found"); + fail("No nodes for the index " + indexName + " found"); return null; } @@ -163,8 +174,8 @@ public abstract class AbstractSnapshotIntegTestCase extends ESIntegTestCase { } } - public static void unblockNode(String node) { - ((MockRepository)internalCluster().getInstance(RepositoriesService.class, node).repository("test-repo")).unblock(); + public static void unblockNode(final String repository, final String node) { + ((MockRepository)internalCluster().getInstance(RepositoriesService.class, node).repository(repository)).unblock(); } protected void assertBusyPendingTasks(final String taskPrefix, final int expectedCount) throws Exception { diff --git a/core/src/test/java/org/elasticsearch/snapshots/DedicatedClusterSnapshotRestoreIT.java b/core/src/test/java/org/elasticsearch/snapshots/DedicatedClusterSnapshotRestoreIT.java index 34333a583af..3ba6c875b68 100644 --- a/core/src/test/java/org/elasticsearch/snapshots/DedicatedClusterSnapshotRestoreIT.java +++ b/core/src/test/java/org/elasticsearch/snapshots/DedicatedClusterSnapshotRestoreIT.java @@ -313,7 +313,7 @@ public class DedicatedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTest assertThat(putRepositoryResponse.isAcknowledged(), equalTo(true)); // Pick one node and block it - String blockedNode = blockNodeWithIndex("test-idx"); + String blockedNode = blockNodeWithIndex("test-repo", "test-idx"); logger.info("--> snapshot"); client.admin().cluster().prepareCreateSnapshot("test-repo", "test-snap").setWaitForCompletion(false).setIndices("test-idx").get(); @@ -322,7 +322,7 @@ public class DedicatedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTest waitForBlock(blockedNode, "test-repo", TimeValue.timeValueSeconds(60)); logger.info("--> execution was blocked on node [{}], shutting it down", blockedNode); - unblockNode(blockedNode); + unblockNode("test-repo", blockedNode); logger.info("--> stopping node [{}]", blockedNode); stopNode(blockedNode); @@ -361,7 +361,7 @@ public class DedicatedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTest assertThat(putRepositoryResponse.isAcknowledged(), equalTo(true)); // Pick one node and block it - String blockedNode = blockNodeWithIndex("test-idx"); + String blockedNode = blockNodeWithIndex("test-repo", "test-idx"); // Remove it from the list of available nodes nodes.remove(blockedNode); @@ -377,7 +377,7 @@ public class DedicatedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTest ListenableActionFuture deleteSnapshotResponseFuture = internalCluster().client(nodes.get(0)).admin().cluster().prepareDeleteSnapshot("test-repo", "test-snap").execute(); // Make sure that abort makes some progress Thread.sleep(100); - unblockNode(blockedNode); + unblockNode("test-repo", blockedNode); logger.info("--> stopping node [{}]", blockedNode); stopNode(blockedNode); try { diff --git a/core/src/test/java/org/elasticsearch/snapshots/SharedClusterSnapshotRestoreIT.java b/core/src/test/java/org/elasticsearch/snapshots/SharedClusterSnapshotRestoreIT.java index 3996aad25e3..19b46710fea 100644 --- a/core/src/test/java/org/elasticsearch/snapshots/SharedClusterSnapshotRestoreIT.java +++ b/core/src/test/java/org/elasticsearch/snapshots/SharedClusterSnapshotRestoreIT.java @@ -50,7 +50,6 @@ import org.elasticsearch.cluster.block.ClusterBlocks; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.MappingMetaData; import org.elasticsearch.cluster.metadata.MetaDataIndexStateService; -import org.elasticsearch.cluster.metadata.SnapshotId; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.Priority; import org.elasticsearch.common.collect.ImmutableOpenMap; @@ -73,9 +72,11 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_REPLICAS; import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_SHARDS; @@ -917,7 +918,7 @@ public class SharedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTestCas assertThat(createSnapshotResponse.getSnapshotInfo().successfulShards(), equalTo(createSnapshotResponse.getSnapshotInfo().totalShards())); logger.info("--> truncate snapshot file to make it unreadable"); - Path snapshotPath = repo.resolve("snap-test-snap-1.dat"); + Path snapshotPath = repo.resolve("snap-test-snap-1-" + createSnapshotResponse.getSnapshotInfo().snapshotId().getUUID() + ".dat"); try(SeekableByteChannel outChan = Files.newByteChannel(snapshotPath, StandardOpenOption.WRITE)) { outChan.truncate(randomInt(10)); } @@ -1120,7 +1121,7 @@ public class SharedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTestCas assertThat(client.prepareSearch("test-idx").setSize(0).get().getHits().totalHits(), equalTo(100L)); // Pick one node and block it - String blockedNode = blockNodeWithIndex("test-idx"); + String blockedNode = blockNodeWithIndex("test-repo", "test-idx"); logger.info("--> snapshot"); client.admin().cluster().prepareCreateSnapshot("test-repo", "test-snap").setWaitForCompletion(false).setIndices("test-idx").get(); @@ -1133,7 +1134,7 @@ public class SharedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTestCas client().admin().indices().prepareUpdateSettings("test-idx").setSettings(excludeSettings).get(); logger.info("--> unblocking blocked node"); - unblockNode(blockedNode); + unblockNode("test-repo", blockedNode); logger.info("--> waiting for completion"); SnapshotInfo snapshotInfo = waitForCompletion("test-repo", "test-snap", TimeValue.timeValueSeconds(600)); logger.info("Number of failed shards [{}]", snapshotInfo.shardFailures().size()); @@ -1183,7 +1184,7 @@ public class SharedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTestCas assertThat(client.prepareSearch("test-idx").setSize(0).get().getHits().totalHits(), equalTo(100L)); // Pick one node and block it - String blockedNode = blockNodeWithIndex("test-idx"); + String blockedNode = blockNodeWithIndex("test-repo", "test-idx"); logger.info("--> snapshot"); client.admin().cluster().prepareCreateSnapshot("test-repo", "test-snap").setWaitForCompletion(false).setIndices("test-idx").get(); @@ -1215,7 +1216,7 @@ public class SharedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTestCas .setType("fs").setSettings(Settings.builder().put("location", repositoryLocation.resolve("test")))); logger.info("--> unblocking blocked node"); - unblockNode(blockedNode); + unblockNode("test-repo", blockedNode); logger.info("--> waiting for completion"); SnapshotInfo snapshotInfo = waitForCompletion("test-repo", "test-snap", TimeValue.timeValueSeconds(600)); logger.info("Number of failed shards [{}]", snapshotInfo.shardFailures().size()); @@ -1436,7 +1437,7 @@ public class SharedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTestCas assertThat(client.prepareSearch("test-idx").setSize(0).get().getHits().totalHits(), equalTo(100L)); // Pick one node and block it - String blockedNode = blockNodeWithIndex("test-idx"); + String blockedNode = blockNodeWithIndex("test-repo", "test-idx"); logger.info("--> snapshot"); client.admin().cluster().prepareCreateSnapshot("test-repo", "test-snap").setWaitForCompletion(false).setIndices("test-idx").get(); @@ -1477,7 +1478,7 @@ public class SharedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTestCas assertThat(snapshotInfo.state(), equalTo(SnapshotState.IN_PROGRESS)); logger.info("--> unblocking blocked node"); - unblockNode(blockedNode); + unblockNode("test-repo", blockedNode); snapshotInfo = waitForCompletion("test-repo", "test-snap", TimeValue.timeValueSeconds(600)); logger.info("Number of failed shards [{}]", snapshotInfo.shardFailures().size()); @@ -1904,7 +1905,7 @@ public class SharedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTestCas } finally { if (initBlocking) { logger.info("--> unblock running master node"); - unblockNode(internalCluster().getMasterName()); + unblockNode("test-repo", internalCluster().getMasterName()); } else { logger.info("--> unblock all data nodes"); unblockAllDataNodes("test-repo"); @@ -1993,14 +1994,17 @@ public class SharedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTestCas Client client = client(); logger.info("--> creating repository"); - assertAcked(client.admin().cluster().preparePutRepository("test-repo") + final String repositoryName = "test-repo"; + assertAcked(client.admin().cluster().preparePutRepository(repositoryName) .setType("mock").setSettings(Settings.builder() .put("location", randomRepoPath()) .put("compress", randomBoolean()) .put("chunk_size", randomIntBetween(100, 1000), ByteSizeUnit.BYTES) )); - createIndex("test-idx"); + logger.info("--> create the index"); + final String idxName = "test-idx"; + createIndex(idxName); ensureGreen(); ClusterService clusterService = internalCluster().getInstance(ClusterService.class, internalCluster().getMasterName()); @@ -2008,7 +2012,8 @@ public class SharedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTestCas final CountDownLatch countDownLatch = new CountDownLatch(1); logger.info("--> snapshot"); - CreateSnapshotResponse createSnapshotResponse = client.admin().cluster().prepareCreateSnapshot("test-repo", "test-snap").setWaitForCompletion(true).setIndices("test-idx").get(); + final String snapshotName = "test-snap"; + CreateSnapshotResponse createSnapshotResponse = client.admin().cluster().prepareCreateSnapshot(repositoryName, snapshotName).setWaitForCompletion(true).setIndices(idxName).get(); assertThat(createSnapshotResponse.getSnapshotInfo().successfulShards(), greaterThan(0)); assertThat(createSnapshotResponse.getSnapshotInfo().successfulShards(), equalTo(createSnapshotResponse.getSnapshotInfo().totalShards())); @@ -2020,11 +2025,18 @@ public class SharedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTestCas public ClusterState execute(ClusterState currentState) { // Simulate orphan snapshot ImmutableOpenMap.Builder shards = ImmutableOpenMap.builder(); - shards.put(new ShardId("test-idx", "_na_", 0), new ShardSnapshotStatus("unknown-node", State.ABORTED)); - shards.put(new ShardId("test-idx", "_na_", 1), new ShardSnapshotStatus("unknown-node", State.ABORTED)); - shards.put(new ShardId("test-idx", "_na_", 2), new ShardSnapshotStatus("unknown-node", State.ABORTED)); + shards.put(new ShardId(idxName, "_na_", 0), new ShardSnapshotStatus("unknown-node", State.ABORTED)); + shards.put(new ShardId(idxName, "_na_", 1), new ShardSnapshotStatus("unknown-node", State.ABORTED)); + shards.put(new ShardId(idxName, "_na_", 2), new ShardSnapshotStatus("unknown-node", State.ABORTED)); List entries = new ArrayList<>(); - entries.add(new Entry(new SnapshotId("test-repo", "test-snap"), true, false, State.ABORTED, Collections.singletonList("test-idx"), System.currentTimeMillis(), shards.build())); + entries.add(new Entry(new Snapshot(repositoryName, + createSnapshotResponse.getSnapshotInfo().snapshotId()), + true, + false, + State.ABORTED, + Collections.singletonList(idxName), + System.currentTimeMillis(), + shards.build())); return ClusterState.builder(currentState).putCustom(SnapshotsInProgress.TYPE, new SnapshotsInProgress(Collections.unmodifiableList(entries))).build(); } @@ -2042,8 +2054,7 @@ public class SharedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTestCas countDownLatch.await(); logger.info("--> try deleting the orphan snapshot"); - assertAcked(client.admin().cluster().prepareDeleteSnapshot("test-repo", "test-snap").get("10s")); - + assertAcked(client.admin().cluster().prepareDeleteSnapshot(repositoryName, snapshotName).get("10s")); } private boolean waitForIndex(final String index, TimeValue timeout) throws InterruptedException { @@ -2143,33 +2154,14 @@ public class SharedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTestCas .put("compress", randomBoolean()) .put("chunk_size", randomIntBetween(100, 1000), ByteSizeUnit.BYTES))); - try { - client.admin().cluster().prepareGetSnapshots("test-repo").setSnapshots("_foo").get(); - fail("shouldn't be here"); - } catch (InvalidSnapshotNameException ex) { - assertThat(ex.getMessage(), containsString("Invalid snapshot name")); - } - - try { - client.admin().cluster().prepareCreateSnapshot("test-repo", "_foo").get(); - fail("shouldn't be here"); - } catch (InvalidSnapshotNameException ex) { - assertThat(ex.getMessage(), containsString("Invalid snapshot name")); - } - - try { - client.admin().cluster().prepareDeleteSnapshot("test-repo", "_foo").get(); - fail("shouldn't be here"); - } catch (InvalidSnapshotNameException ex) { - assertThat(ex.getMessage(), containsString("Invalid snapshot name")); - } - - try { - client.admin().cluster().prepareSnapshotStatus("test-repo").setSnapshots("_foo").get(); - fail("shouldn't be here"); - } catch (InvalidSnapshotNameException ex) { - assertThat(ex.getMessage(), containsString("Invalid snapshot name")); - } + expectThrows(InvalidSnapshotNameException.class, + () -> client.admin().cluster().prepareCreateSnapshot("test-repo", "_foo").get()); + expectThrows(SnapshotMissingException.class, + () -> client.admin().cluster().prepareGetSnapshots("test-repo").setSnapshots("_foo").get()); + expectThrows(SnapshotMissingException.class, + () -> client.admin().cluster().prepareDeleteSnapshot("test-repo", "_foo").get()); + expectThrows(SnapshotMissingException.class, + () -> client.admin().cluster().prepareSnapshotStatus("test-repo").setSnapshots("_foo").get()); } public void testListCorruptedSnapshot() throws Exception { @@ -2199,7 +2191,7 @@ public class SharedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTestCas assertThat(createSnapshotResponse.getSnapshotInfo().successfulShards(), equalTo(createSnapshotResponse.getSnapshotInfo().totalShards())); logger.info("--> truncate snapshot file to make it unreadable"); - Path snapshotPath = repo.resolve("snap-test-snap-2.dat"); + Path snapshotPath = repo.resolve("snap-test-snap-2-" + createSnapshotResponse.getSnapshotInfo().snapshotId().getUUID() + ".dat"); try(SeekableByteChannel outChan = Files.newByteChannel(snapshotPath, StandardOpenOption.WRITE)) { outChan.truncate(randomInt(10)); } @@ -2211,13 +2203,194 @@ public class SharedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTestCas assertThat(snapshotInfos.size(), equalTo(1)); assertThat(snapshotInfos.get(0).state(), equalTo(SnapshotState.SUCCESS)); - assertThat(snapshotInfos.get(0).name(), equalTo("test-snap-1")); + assertThat(snapshotInfos.get(0).snapshotId().getName(), equalTo("test-snap-1")); try { client.admin().cluster().prepareGetSnapshots("test-repo").setIgnoreUnavailable(false).get().getSnapshots(); } catch (SnapshotException ex) { - assertThat(ex.snapshot().getRepository(), equalTo("test-repo")); - assertThat(ex.snapshot().getSnapshot(), equalTo("test-snap-2")); + assertThat(ex.getRepositoryName(), equalTo("test-repo")); + assertThat(ex.getSnapshotName(), equalTo("test-snap-2")); } } + + public void testCannotCreateSnapshotsWithSameName() throws Exception { + final String repositoryName = "test-repo"; + final String snapshotName = "test-snap"; + final String indexName = "test-idx"; + final Client client = client(); + final Path repo = randomRepoPath(); + + logger.info("--> creating repository at {}", repo.toAbsolutePath()); + assertAcked(client.admin().cluster().preparePutRepository(repositoryName) + .setType("fs").setSettings(Settings.builder() + .put("location", repo) + .put("compress", false) + .put("chunk_size", randomIntBetween(100, 1000), ByteSizeUnit.BYTES))); + logger.info("--> creating an index and indexing documents"); + createIndex(indexName); + ensureGreen(); + for (int i = 0; i < 10; i++) { + index(indexName, "doc", Integer.toString(i), "foo", "bar" + i); + } + refresh(); + + logger.info("--> take first snapshot"); + CreateSnapshotResponse createSnapshotResponse = client.admin() + .cluster() + .prepareCreateSnapshot(repositoryName, snapshotName) + .setWaitForCompletion(true) + .setIndices(indexName) + .get(); + assertThat(createSnapshotResponse.getSnapshotInfo().successfulShards(), greaterThan(0)); + assertThat(createSnapshotResponse.getSnapshotInfo().successfulShards(), + equalTo(createSnapshotResponse.getSnapshotInfo().totalShards())); + + logger.info("--> index more documents"); + for (int i = 10; i < 20; i++) { + index(indexName, "doc", Integer.toString(i), "foo", "bar" + i); + } + refresh(); + + logger.info("--> second snapshot of the same name should fail"); + try { + createSnapshotResponse = client.admin() + .cluster() + .prepareCreateSnapshot(repositoryName, snapshotName) + .setWaitForCompletion(true) + .setIndices(indexName) + .get(); + fail("should not be allowed to create a snapshot with the same name as an already existing snapshot: " + + createSnapshotResponse.getSnapshotInfo().snapshotId()); + } catch (SnapshotCreationException e) { + assertThat(e.getMessage(), containsString("snapshot with the same name already exists")); + } + + logger.info("--> delete the first snapshot"); + client.admin().cluster().prepareDeleteSnapshot(repositoryName, snapshotName).get(); + + logger.info("--> try creating a snapshot with the same name, now it should work because the first one was deleted"); + createSnapshotResponse = client.admin() + .cluster() + .prepareCreateSnapshot(repositoryName, snapshotName) + .setWaitForCompletion(true) + .setIndices(indexName) + .get(); + assertThat(createSnapshotResponse.getSnapshotInfo().snapshotId().getName(), equalTo(snapshotName)); + } + + public void testGetSnapshotsRequest() throws Exception { + final String repositoryName = "test-repo"; + final String indexName = "test-idx"; + final Client client = client(); + final Path repo = randomRepoPath(); + + logger.info("--> creating repository at {}", repo.toAbsolutePath()); + assertAcked(client.admin().cluster().preparePutRepository(repositoryName) + .setType("mock").setSettings(Settings.builder() + .put("location", repo) + .put("compress", false) + .put("chunk_size", randomIntBetween(100, 1000), ByteSizeUnit.BYTES) + .put("wait_after_unblock", 200))); + + logger.info("--> get snapshots on an empty repository"); + expectThrows(SnapshotMissingException.class, () -> client.admin() + .cluster() + .prepareGetSnapshots(repositoryName) + .addSnapshots("non-existent-snapshot") + .get()); + // with ignore unavailable set to true, should not throw an exception + GetSnapshotsResponse getSnapshotsResponse = client.admin() + .cluster() + .prepareGetSnapshots(repositoryName) + .setIgnoreUnavailable(true) + .addSnapshots("non-existent-snapshot") + .get(); + assertThat(getSnapshotsResponse.getSnapshots().size(), equalTo(0)); + + logger.info("--> creating an index and indexing documents"); + // Create index on 2 nodes and make sure each node has a primary by setting no replicas + assertAcked(prepareCreate(indexName, 1, Settings.builder().put("number_of_replicas", 0))); + ensureGreen(); + for (int i = 0; i < 10; i++) { + index(indexName, "doc", Integer.toString(i), "foo", "bar" + i); + } + refresh(); + + final int numSnapshots = randomIntBetween(1, 3) + 1; + logger.info("--> take {} snapshot(s)", numSnapshots); + final String[] snapshotNames = new String[numSnapshots]; + for (int i = 0; i < numSnapshots - 1; i++) { + final String snapshotName = randomAsciiOfLength(8).toLowerCase(Locale.ROOT); + CreateSnapshotResponse createSnapshotResponse = client.admin() + .cluster() + .prepareCreateSnapshot(repositoryName, snapshotName) + .setWaitForCompletion(true) + .setIndices(indexName) + .get(); + assertThat(createSnapshotResponse.getSnapshotInfo().successfulShards(), greaterThan(0)); + snapshotNames[i] = snapshotName; + } + logger.info("--> take another snapshot to be in-progress"); + // add documents so there are data files to block on + for (int i = 10; i < 20; i++) { + index(indexName, "doc", Integer.toString(i), "foo", "bar" + i); + } + refresh(); + + final String inProgressSnapshot = randomAsciiOfLength(8).toLowerCase(Locale.ROOT); + snapshotNames[numSnapshots - 1] = inProgressSnapshot; + // block a node so the create snapshot operation can remain in progress + final String blockedNode = blockNodeWithIndex(repositoryName, indexName); + client.admin().cluster().prepareCreateSnapshot(repositoryName, inProgressSnapshot) + .setWaitForCompletion(false) + .setIndices(indexName) + .get(); + waitForBlock(blockedNode, repositoryName, TimeValue.timeValueSeconds(60)); // wait for block to kick in + + logger.info("--> get all snapshots with a current in-progress"); + // with ignore unavailable set to true, should not throw an exception + getSnapshotsResponse = client.admin().cluster() + .prepareGetSnapshots(repositoryName) + .addSnapshots("_all") + .get(); + List sortedNames = Arrays.asList(snapshotNames); + Collections.sort(sortedNames); + assertThat(getSnapshotsResponse.getSnapshots().size(), equalTo(numSnapshots)); + assertThat(getSnapshotsResponse.getSnapshots().stream() + .map(s -> s.snapshotId().getName()) + .sorted() + .collect(Collectors.toList()), equalTo(sortedNames)); + + getSnapshotsResponse = client.admin().cluster() + .prepareGetSnapshots(repositoryName) + .addSnapshots(snapshotNames) + .get(); + sortedNames = Arrays.asList(snapshotNames); + Collections.sort(sortedNames); + assertThat(getSnapshotsResponse.getSnapshots().size(), equalTo(numSnapshots)); + assertThat(getSnapshotsResponse.getSnapshots().stream() + .map(s -> s.snapshotId().getName()) + .sorted() + .collect(Collectors.toList()), equalTo(sortedNames)); + + logger.info("--> make sure duplicates are not returned in the response"); + String regexName = snapshotNames[randomIntBetween(0, numSnapshots - 1)]; + final int splitPos = regexName.length() / 2; + final String firstRegex = regexName.substring(0, splitPos) + "*"; + final String secondRegex = "*" + regexName.substring(splitPos); + getSnapshotsResponse = client.admin().cluster() + .prepareGetSnapshots(repositoryName) + .addSnapshots(snapshotNames) + .addSnapshots(firstRegex, secondRegex) + .get(); + assertThat(getSnapshotsResponse.getSnapshots().size(), equalTo(numSnapshots)); + assertThat(getSnapshotsResponse.getSnapshots().stream() + .map(s -> s.snapshotId().getName()) + .sorted() + .collect(Collectors.toList()), equalTo(sortedNames)); + + unblockNode(repositoryName, blockedNode); // unblock node + waitForCompletion(repositoryName, inProgressSnapshot, TimeValue.timeValueSeconds(60)); + } + } diff --git a/core/src/test/java/org/elasticsearch/snapshots/SnapshotBackwardsCompatibilityIT.java b/core/src/test/java/org/elasticsearch/snapshots/SnapshotBackwardsCompatibilityIT.java index e6379468382..31c0a193f07 100644 --- a/core/src/test/java/org/elasticsearch/snapshots/SnapshotBackwardsCompatibilityIT.java +++ b/core/src/test/java/org/elasticsearch/snapshots/SnapshotBackwardsCompatibilityIT.java @@ -86,7 +86,7 @@ public class SnapshotBackwardsCompatibilityIT extends ESBackcompatTestCase { counts[i] = client().prepareSearch(indices[i]).setSize(0).get().getHits().totalHits(); } - logger.info("--> snapshot subset of indices before upgrage"); + logger.info("--> snapshot subset of indices before upgrade"); CreateSnapshotResponse createSnapshotResponse = client().admin().cluster().prepareCreateSnapshot("test-repo", "test-snap-1").setWaitForCompletion(true).setIndices("index_before_*").get(); assertThat(createSnapshotResponse.getSnapshotInfo().successfulShards(), greaterThan(0)); assertThat(createSnapshotResponse.getSnapshotInfo().successfulShards(), equalTo(createSnapshotResponse.getSnapshotInfo().totalShards())); diff --git a/core/src/test/java/org/elasticsearch/snapshots/SnapshotTests.java b/core/src/test/java/org/elasticsearch/snapshots/SnapshotTests.java new file mode 100644 index 00000000000..cb297785e4b --- /dev/null +++ b/core/src/test/java/org/elasticsearch/snapshots/SnapshotTests.java @@ -0,0 +1,57 @@ +/* + * 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.snapshots; + +import org.elasticsearch.common.UUIDs; +import org.elasticsearch.common.io.stream.ByteBufferStreamInput; +import org.elasticsearch.common.io.stream.BytesStreamOutput; +import org.elasticsearch.test.ESTestCase; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import static org.hamcrest.CoreMatchers.equalTo; + +/** + * Tests for the {@link Snapshot} class. + */ +public class SnapshotTests extends ESTestCase { + + public void testSnapshotEquals() { + final SnapshotId snapshotId = new SnapshotId("snap", UUIDs.randomBase64UUID()); + final Snapshot original = new Snapshot("repo", snapshotId); + final Snapshot expected = new Snapshot(original.getRepository(), original.getSnapshotId()); + assertThat(expected, equalTo(original)); + assertThat(expected.getRepository(), equalTo(original.getRepository())); + assertThat(expected.getSnapshotId(), equalTo(original.getSnapshotId())); + assertThat(expected.getSnapshotId().getName(), equalTo(original.getSnapshotId().getName())); + assertThat(expected.getSnapshotId().getUUID(), equalTo(original.getSnapshotId().getUUID())); + } + + public void testSerialization() throws IOException { + final SnapshotId snapshotId = new SnapshotId(randomAsciiOfLength(randomIntBetween(2, 8)), UUIDs.randomBase64UUID()); + final Snapshot original = new Snapshot(randomAsciiOfLength(randomIntBetween(2, 8)), snapshotId); + final BytesStreamOutput out = new BytesStreamOutput(); + original.writeTo(out); + final ByteBufferStreamInput in = new ByteBufferStreamInput(ByteBuffer.wrap(out.bytes().toBytes())); + assertThat(new Snapshot(in), equalTo(original)); + } + +} diff --git a/core/src/test/java/org/elasticsearch/snapshots/mockstore/MockRepository.java b/core/src/test/java/org/elasticsearch/snapshots/mockstore/MockRepository.java index 7df44738076..8b02f90f51c 100644 --- a/core/src/test/java/org/elasticsearch/snapshots/mockstore/MockRepository.java +++ b/core/src/test/java/org/elasticsearch/snapshots/mockstore/MockRepository.java @@ -21,7 +21,7 @@ package org.elasticsearch.snapshots.mockstore; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.cluster.metadata.MetaData; -import org.elasticsearch.cluster.metadata.SnapshotId; +import org.elasticsearch.snapshots.SnapshotId; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.blobstore.BlobContainer; import org.elasticsearch.common.blobstore.BlobMetaData; @@ -174,6 +174,10 @@ public class MockRepository extends FsRepository { blockOnControlFiles = blocked; } + public boolean blockOnDataFiles() { + return blockOnDataFiles; + } + public synchronized void unblockExecution() { blocked = false; // Clean blocking flags, so we wouldn't try to block again diff --git a/plugins/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureRepository.java b/plugins/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureRepository.java index 8af614df605..4d3459cdcd4 100644 --- a/plugins/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureRepository.java +++ b/plugins/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureRepository.java @@ -24,7 +24,7 @@ import com.microsoft.azure.storage.StorageException; import org.elasticsearch.cloud.azure.blobstore.AzureBlobStore; import org.elasticsearch.cloud.azure.storage.AzureStorageService.Storage; import org.elasticsearch.cluster.metadata.MetaData; -import org.elasticsearch.cluster.metadata.SnapshotId; +import org.elasticsearch.snapshots.SnapshotId; import org.elasticsearch.common.Strings; import org.elasticsearch.common.blobstore.BlobPath; import org.elasticsearch.common.blobstore.BlobStore; @@ -166,7 +166,7 @@ public class AzureRepository extends BlobStoreRepository { super.initializeSnapshot(snapshotId, indices, metaData); } catch (StorageException | URISyntaxException e) { logger.warn("can not initialize container [{}]: [{}]", blobStore.container(), e.getMessage()); - throw new SnapshotCreationException(snapshotId, e); + throw new SnapshotCreationException(repositoryName, snapshotId, e); } } diff --git a/plugins/repository-azure/src/test/java/org/elasticsearch/cloud/azure/storage/AzureStorageServiceMock.java b/plugins/repository-azure/src/test/java/org/elasticsearch/cloud/azure/storage/AzureStorageServiceMock.java index 5a1c76df413..8160c560325 100644 --- a/plugins/repository-azure/src/test/java/org/elasticsearch/cloud/azure/storage/AzureStorageServiceMock.java +++ b/plugins/repository-azure/src/test/java/org/elasticsearch/cloud/azure/storage/AzureStorageServiceMock.java @@ -94,8 +94,15 @@ public class AzureStorageServiceMock extends AbstractLifecycleComponent listBlobsByPrefix(String account, LocationMode mode, String container, String keyPath, String prefix) { MapBuilder blobsBuilder = MapBuilder.newMapBuilder(); for (String blobName : blobs.keySet()) { - if (startsWithIgnoreCase(blobName, prefix)) { - blobsBuilder.put(blobName, new PlainBlobMetaData(blobName, blobs.get(blobName).size())); + final String checkBlob; + if (keyPath != null) { + // strip off key path from the beginning of the blob name + checkBlob = blobName.replace(keyPath, ""); + } else { + checkBlob = blobName; + } + if (startsWithIgnoreCase(checkBlob, prefix)) { + blobsBuilder.put(blobName, new PlainBlobMetaData(checkBlob, blobs.get(blobName).size())); } } return blobsBuilder.immutableMap(); diff --git a/test/framework/src/main/java/org/elasticsearch/common/io/FileTestUtils.java b/test/framework/src/main/java/org/elasticsearch/common/io/FileTestUtils.java index 50d677c600c..98155967514 100644 --- a/test/framework/src/main/java/org/elasticsearch/common/io/FileTestUtils.java +++ b/test/framework/src/main/java/org/elasticsearch/common/io/FileTestUtils.java @@ -19,11 +19,15 @@ package org.elasticsearch.common.io; +import org.elasticsearch.common.Nullable; import org.junit.Assert; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertFileExists; import static org.hamcrest.CoreMatchers.equalTo; @@ -50,4 +54,44 @@ public class FileTestUtils { Assert.assertThat(fileContent.trim(), equalTo(expected.trim())); } } + + /** + * Unzip a zip file to a destination directory. If the zip file does not exist, an IOException is thrown. + * If the destination directory does not exist, it will be created. + * + * @param zip zip file to unzip + * @param destDir directory to unzip the file to + * @param prefixToRemove the (optional) prefix in the zip file path to remove when writing to the destination directory + * @throws IOException if zip file does not exist, or there was an error reading from the zip file or + * writing to the destination directory + */ + public static void unzip(final Path zip, final Path destDir, @Nullable final String prefixToRemove) throws IOException { + if (Files.notExists(zip)) { + throw new IOException("[" + zip + "] zip file must exist"); + } + Files.createDirectories(destDir); + + try (final ZipInputStream zipInput = new ZipInputStream(Files.newInputStream(zip))) { + ZipEntry entry; + while ((entry = zipInput.getNextEntry()) != null) { + final String entryPath; + if (prefixToRemove != null) { + if (entry.getName().startsWith(prefixToRemove)) { + entryPath = entry.getName().substring(prefixToRemove.length()); + } else { + throw new IOException("prefix not found: " + prefixToRemove); + } + } else { + entryPath = entry.getName(); + } + final Path path = Paths.get(destDir.toString(), entryPath); + if (entry.isDirectory()) { + Files.createDirectories(path); + } else { + Files.copy(zipInput, path); + } + zipInput.closeEntry(); + } + } + } } diff --git a/test/framework/src/main/java/org/elasticsearch/test/ESSingleNodeTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/ESSingleNodeTestCase.java index c86c111f645..f4d5b4dd482 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/ESSingleNodeTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/ESSingleNodeTestCase.java @@ -54,6 +54,7 @@ import org.junit.Before; import org.junit.BeforeClass; import java.io.IOException; +import java.nio.file.Path; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -171,14 +172,15 @@ public abstract class ESSingleNodeTestCase extends ESTestCase { } private Node newNode() { + final Path tempDir = createTempDir(); Settings settings = Settings.builder() .put(ClusterName.CLUSTER_NAME_SETTING.getKey(), InternalTestCluster.clusterName("single-node-cluster", randomLong())) - .put(Environment.PATH_HOME_SETTING.getKey(), createTempDir()) + .put(Environment.PATH_HOME_SETTING.getKey(), tempDir) + .put(Environment.PATH_REPO_SETTING.getKey(), tempDir.resolve("repo")) // TODO: use a consistent data path for custom paths // This needs to tie into the ESIntegTestCase#indexSettings() method .put(Environment.PATH_SHARED_DATA_SETTING.getKey(), createTempDir().getParent()) .put("node.name", nodeName()) - .put("script.inline", "true") .put("script.stored", "true") .put(EsExecutors.PROCESSORS_SETTING.getKey(), 1) // limit the number of threads created