Skip to content

Commit 8a53f2b

Browse files
authored
Implement basic CcrRepository restore (#36287)
This is related to #35975. It implements a basic restore functionality for the CcrRepository. When the restore process is kicked off, it configures the new index as expected for a follower index. This means that the index has a different uuid, the version is not incremented, and the Ccr metadata is installed. When the restore shard method is called, an empty shard is initialized.
1 parent a998f4d commit 8a53f2b

File tree

6 files changed

+314
-70
lines changed

6 files changed

+314
-70
lines changed

server/src/main/java/org/elasticsearch/snapshots/SnapshotInfo.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,10 @@ public SnapshotInfo(SnapshotId snapshotId, List<String> indices, SnapshotState s
235235
this(snapshotId, indices, state, null, null, 0L, 0L, 0, 0, Collections.emptyList(), null);
236236
}
237237

238+
public SnapshotInfo(SnapshotId snapshotId, List<String> indices, SnapshotState state, Version version) {
239+
this(snapshotId, indices, state, null, version, 0L, 0L, 0, 0, Collections.emptyList(), null);
240+
}
241+
238242
public SnapshotInfo(SnapshotId snapshotId, List<String> indices, long startTime, Boolean includeGlobalState) {
239243
this(snapshotId, indices, SnapshotState.IN_PROGRESS, null, Version.CURRENT, startTime, 0L, 0, 0,
240244
Collections.emptyList(), includeGlobalState);

test/framework/src/main/java/org/elasticsearch/test/InternalTestCluster.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1455,6 +1455,10 @@ public synchronized <T> Iterable<T> getDataNodeInstances(Class<T> clazz) {
14551455
return getInstances(clazz, new DataNodePredicate());
14561456
}
14571457

1458+
public synchronized <T> T getCurrentMasterNodeInstance(Class<T> clazz) {
1459+
return getInstance(clazz, new NodeNamePredicate(getMasterName()));
1460+
}
1461+
14581462
/**
14591463
* Returns an Iterable to all instances for the given class &gt;T&lt; across all data and master nodes
14601464
* in the cluster.

x-pack/plugin/ccr/src/main/java/org/elasticsearch/xpack/ccr/Ccr.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ public class Ccr extends Plugin implements ActionPlugin, PersistentTaskPlugin, E
112112
private final Settings settings;
113113
private final CcrLicenseChecker ccrLicenseChecker;
114114
private final SetOnce<CcrRepositoryManager> repositoryManager = new SetOnce<>();
115+
private Client client;
115116

116117
/**
117118
* Construct an instance of the CCR container with the specified settings.
@@ -146,6 +147,7 @@ public Collection<Object> createComponents(
146147
final Environment environment,
147148
final NodeEnvironment nodeEnvironment,
148149
final NamedWriteableRegistry namedWriteableRegistry) {
150+
this.client = client;
149151
if (enabled == false) {
150152
return emptyList();
151153
}
@@ -275,7 +277,7 @@ public List<ExecutorBuilder<?>> getExecutorBuilders(Settings settings) {
275277

276278
@Override
277279
public Map<String, Repository.Factory> getInternalRepositories(Environment env, NamedXContentRegistry namedXContentRegistry) {
278-
Repository.Factory repositoryFactory = (metadata) -> new CcrRepository(metadata, settings);
280+
Repository.Factory repositoryFactory = (metadata) -> new CcrRepository(metadata, client, ccrLicenseChecker, settings);
279281
return Collections.singletonMap(CcrRepository.TYPE, repositoryFactory);
280282
}
281283

x-pack/plugin/ccr/src/main/java/org/elasticsearch/xpack/ccr/repository/CcrRepository.java

Lines changed: 103 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,21 @@
88

99
import org.apache.lucene.index.IndexCommit;
1010
import org.elasticsearch.Version;
11+
import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse;
12+
import org.elasticsearch.action.support.PlainActionFuture;
13+
import org.elasticsearch.client.Client;
1114
import org.elasticsearch.cluster.metadata.IndexMetaData;
1215
import org.elasticsearch.cluster.metadata.MetaData;
1316
import org.elasticsearch.cluster.metadata.RepositoryMetaData;
1417
import org.elasticsearch.cluster.node.DiscoveryNode;
18+
import org.elasticsearch.common.Strings;
19+
import org.elasticsearch.common.collect.ImmutableOpenMap;
1520
import org.elasticsearch.common.component.AbstractLifecycleComponent;
1621
import org.elasticsearch.common.settings.Settings;
22+
import org.elasticsearch.index.Index;
23+
import org.elasticsearch.index.engine.EngineException;
1724
import org.elasticsearch.index.shard.IndexShard;
25+
import org.elasticsearch.index.shard.IndexShardRecoveryException;
1826
import org.elasticsearch.index.shard.ShardId;
1927
import org.elasticsearch.index.snapshots.IndexShardSnapshotStatus;
2028
import org.elasticsearch.index.store.Store;
@@ -25,24 +33,41 @@
2533
import org.elasticsearch.snapshots.SnapshotId;
2634
import org.elasticsearch.snapshots.SnapshotInfo;
2735
import org.elasticsearch.snapshots.SnapshotShardFailure;
36+
import org.elasticsearch.snapshots.SnapshotState;
37+
import org.elasticsearch.xpack.ccr.Ccr;
38+
import org.elasticsearch.xpack.ccr.CcrLicenseChecker;
2839

2940
import java.io.IOException;
41+
import java.util.ArrayList;
42+
import java.util.Collections;
43+
import java.util.HashMap;
3044
import java.util.List;
45+
import java.util.Map;
46+
import java.util.Set;
3147

3248
/**
3349
* This repository relies on a remote cluster for Ccr restores. It is read-only so it can only be used to
3450
* restore shards/indexes that exist on the remote cluster.
3551
*/
3652
public class CcrRepository extends AbstractLifecycleComponent implements Repository {
3753

54+
public static final String LATEST = "_latest_";
3855
public static final String TYPE = "_ccr_";
3956
public static final String NAME_PREFIX = "_ccr_";
57+
private static final SnapshotId SNAPSHOT_ID = new SnapshotId(LATEST, LATEST);
4058

4159
private final RepositoryMetaData metadata;
60+
private final String remoteClusterAlias;
61+
private final Client client;
62+
private final CcrLicenseChecker ccrLicenseChecker;
4263

43-
public CcrRepository(RepositoryMetaData metadata, Settings settings) {
64+
public CcrRepository(RepositoryMetaData metadata, Client client, CcrLicenseChecker ccrLicenseChecker, Settings settings) {
4465
super(settings);
4566
this.metadata = metadata;
67+
assert metadata.name().startsWith(NAME_PREFIX) : "CcrRepository metadata.name() must start with: " + NAME_PREFIX;
68+
this.remoteClusterAlias = Strings.split(metadata.name(), NAME_PREFIX)[1];
69+
this.ccrLicenseChecker = ccrLicenseChecker;
70+
this.client = client;
4671
}
4772

4873
@Override
@@ -67,22 +92,85 @@ public RepositoryMetaData getMetadata() {
6792

6893
@Override
6994
public SnapshotInfo getSnapshotInfo(SnapshotId snapshotId) {
70-
throw new UnsupportedOperationException("Unsupported for repository of type: " + TYPE);
95+
assert SNAPSHOT_ID.equals(snapshotId) : "RemoteClusterRepository only supports " + SNAPSHOT_ID + " as the SnapshotId";
96+
Client remoteClient = client.getRemoteClusterClient(remoteClusterAlias);
97+
ClusterStateResponse response = remoteClient.admin().cluster().prepareState().clear().setMetaData(true).setNodes(true).get();
98+
ImmutableOpenMap<String, IndexMetaData> indicesMap = response.getState().metaData().indices();
99+
ArrayList<String> indices = new ArrayList<>(indicesMap.size());
100+
indicesMap.keysIt().forEachRemaining(indices::add);
101+
102+
return new SnapshotInfo(snapshotId, indices, SnapshotState.SUCCESS, response.getState().getNodes().getMaxNodeVersion());
71103
}
72104

73105
@Override
74106
public MetaData getSnapshotGlobalMetaData(SnapshotId snapshotId) {
75-
throw new UnsupportedOperationException("Unsupported for repository of type: " + TYPE);
107+
assert SNAPSHOT_ID.equals(snapshotId) : "RemoteClusterRepository only supports " + SNAPSHOT_ID + " as the SnapshotId";
108+
Client remoteClient = client.getRemoteClusterClient(remoteClusterAlias);
109+
ClusterStateResponse response = remoteClient
110+
.admin()
111+
.cluster()
112+
.prepareState()
113+
.clear()
114+
.setMetaData(true)
115+
.setIndices("dummy_index_name") // We set a single dummy index name to avoid fetching all the index data
116+
.get();
117+
return response.getState().metaData();
76118
}
77119

78120
@Override
79121
public IndexMetaData getSnapshotIndexMetaData(SnapshotId snapshotId, IndexId index) throws IOException {
80-
throw new UnsupportedOperationException("Unsupported for repository of type: " + TYPE);
122+
assert SNAPSHOT_ID.equals(snapshotId) : "RemoteClusterRepository only supports " + SNAPSHOT_ID + " as the SnapshotId";
123+
String leaderIndex = index.getName();
124+
Client remoteClient = client.getRemoteClusterClient(remoteClusterAlias);
125+
126+
ClusterStateResponse response = remoteClient
127+
.admin()
128+
.cluster()
129+
.prepareState()
130+
.clear()
131+
.setMetaData(true)
132+
.setIndices(leaderIndex)
133+
.get();
134+
135+
// Validates whether the leader cluster has been configured properly:
136+
PlainActionFuture<String[]> future = PlainActionFuture.newFuture();
137+
IndexMetaData leaderIndexMetaData = response.getState().metaData().index(leaderIndex);
138+
ccrLicenseChecker.fetchLeaderHistoryUUIDs(remoteClient, leaderIndexMetaData, future::onFailure, future::onResponse);
139+
String[] leaderHistoryUUIDs = future.actionGet();
140+
141+
IndexMetaData.Builder imdBuilder = IndexMetaData.builder(leaderIndexMetaData);
142+
// Adding the leader index uuid for each shard as custom metadata:
143+
Map<String, String> metadata = new HashMap<>();
144+
metadata.put(Ccr.CCR_CUSTOM_METADATA_LEADER_INDEX_SHARD_HISTORY_UUIDS, String.join(",", leaderHistoryUUIDs));
145+
metadata.put(Ccr.CCR_CUSTOM_METADATA_LEADER_INDEX_UUID_KEY, leaderIndexMetaData.getIndexUUID());
146+
metadata.put(Ccr.CCR_CUSTOM_METADATA_LEADER_INDEX_NAME_KEY, leaderIndexMetaData.getIndex().getName());
147+
metadata.put(Ccr.CCR_CUSTOM_METADATA_REMOTE_CLUSTER_NAME_KEY, remoteClusterAlias);
148+
imdBuilder.putCustom(Ccr.CCR_CUSTOM_METADATA_KEY, metadata);
149+
150+
return imdBuilder.build();
81151
}
82152

83153
@Override
84154
public RepositoryData getRepositoryData() {
85-
throw new UnsupportedOperationException("Unsupported for repository of type: " + TYPE);
155+
Client remoteClient = client.getRemoteClusterClient(remoteClusterAlias);
156+
ClusterStateResponse response = remoteClient.admin().cluster().prepareState().clear().setMetaData(true).get();
157+
MetaData remoteMetaData = response.getState().getMetaData();
158+
159+
Map<String, SnapshotId> copiedSnapshotIds = new HashMap<>();
160+
Map<String, SnapshotState> snapshotStates = new HashMap<>(copiedSnapshotIds.size());
161+
Map<IndexId, Set<SnapshotId>> indexSnapshots = new HashMap<>(copiedSnapshotIds.size());
162+
163+
ImmutableOpenMap<String, IndexMetaData> remoteIndices = remoteMetaData.getIndices();
164+
for (String indexName : remoteMetaData.getConcreteAllIndices()) {
165+
// Both the Snapshot name and UUID are set to _latest_
166+
SnapshotId snapshotId = new SnapshotId(LATEST, LATEST);
167+
copiedSnapshotIds.put(indexName, snapshotId);
168+
snapshotStates.put(indexName, SnapshotState.SUCCESS);
169+
Index index = remoteIndices.get(indexName).getIndex();
170+
indexSnapshots.put(new IndexId(indexName, index.getUUID()), Collections.singleton(snapshotId));
171+
}
172+
173+
return new RepositoryData(1, copiedSnapshotIds, snapshotStates, indexSnapshots, Collections.emptyList());
86174
}
87175

88176
@Override
@@ -137,9 +225,17 @@ public void snapshotShard(IndexShard shard, Store store, SnapshotId snapshotId,
137225
}
138226

139227
@Override
140-
public void restoreShard(IndexShard shard, SnapshotId snapshotId, Version version, IndexId indexId, ShardId snapshotShardId,
228+
public void restoreShard(IndexShard indexShard, SnapshotId snapshotId, Version version, IndexId indexId, ShardId shardId,
141229
RecoveryState recoveryState) {
142-
throw new UnsupportedOperationException("Unsupported for repository of type: " + TYPE);
230+
final Store store = indexShard.store();
231+
store.incRef();
232+
try {
233+
store.createEmpty(indexShard.indexSettings().getIndexMetaData().getCreationVersion().luceneVersion);
234+
} catch (EngineException | IOException e) {
235+
throw new IndexShardRecoveryException(shardId, "failed to recover from gateway", e);
236+
} finally {
237+
store.decRef();
238+
}
143239
}
144240

145241
@Override

0 commit comments

Comments
 (0)