Skip to content

Commit 558e933

Browse files
committed
Don't close indices that are being resharded
1 parent 43841a5 commit 558e933

File tree

4 files changed

+143
-0
lines changed

4 files changed

+143
-0
lines changed

server/src/main/java/org/elasticsearch/cluster/metadata/MetadataIndexStateService.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
import org.elasticsearch.core.Tuple;
6666
import org.elasticsearch.index.Index;
6767
import org.elasticsearch.index.IndexNotFoundException;
68+
import org.elasticsearch.index.IndexReshardService;
6869
import org.elasticsearch.index.IndexVersion;
6970
import org.elasticsearch.index.shard.IndexLongFieldRange;
7071
import org.elasticsearch.index.shard.ShardId;
@@ -352,6 +353,12 @@ static ClusterState addIndexClosedBlocks(
352353
);
353354
}
354355

356+
// Check if index closing conflicts with ongoing resharding
357+
Set<Index> reshardingIndices = IndexReshardService.reshardingIndices(currentProjectState, indicesToClose);
358+
if (reshardingIndices.isEmpty() == false) {
359+
throw new IllegalArgumentException("Cannot close indices that are being resharded: " + reshardingIndices);
360+
}
361+
355362
final ClusterBlocks.Builder blocks = ClusterBlocks.builder(currentState.blocks());
356363

357364
for (Index index : indicesToClose) {
@@ -940,6 +947,22 @@ static Tuple<ClusterState, List<IndexResult>> closeRoutingTable(
940947
continue;
941948
}
942949

950+
// Check if index closing conflicts with ongoing resharding
951+
Set<Index> reshardingIndices = IndexReshardService.reshardingIndices(currentProjectState, Set.of(index));
952+
if (reshardingIndices.isEmpty() == false) {
953+
closingResults.put(
954+
result.getKey(),
955+
new IndexResult(
956+
result.getKey(),
957+
new IllegalStateException(
958+
"verification of shards before closing " + index + " succeeded but index is being resharded in the meantime"
959+
)
960+
)
961+
);
962+
logger.debug("verification of shards before closing {} succeeded but index is being resharded in the meantime", index);
963+
continue;
964+
}
965+
943966
blocks.removeIndexBlockWithId(projectId, index.getName(), INDEX_CLOSED_BLOCK_ID);
944967
blocks.addIndexBlock(projectId, index.getName(), INDEX_CLOSED_BLOCK);
945968
final IndexMetadata.Builder updatedMetadata = IndexMetadata.builder(indexMetadata).state(IndexMetadata.State.CLOSE);
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.index;
11+
12+
import org.elasticsearch.cluster.ProjectState;
13+
import org.elasticsearch.cluster.metadata.IndexMetadata;
14+
15+
import java.util.HashSet;
16+
import java.util.Set;
17+
18+
/**
19+
* Resharding is currently only implemented in Serverless. This service only exposes minimal metadata about resharding
20+
* needed by other services.
21+
*/
22+
public class IndexReshardService {
23+
/**
24+
* Returns the indices from the provided set that are currently being resharded.
25+
*/
26+
public static Set<Index> reshardingIndices(final ProjectState projectState, final Set<Index> indicesToCheck) {
27+
final Set<Index> indices = new HashSet<>();
28+
for (Index index : indicesToCheck) {
29+
IndexMetadata indexMetadata = projectState.metadata().index(index);
30+
31+
if (indexMetadata != null && indexMetadata.getReshardingMetadata() != null) {
32+
indices.add(index);
33+
}
34+
}
35+
return indices;
36+
}
37+
}

server/src/test/java/org/elasticsearch/cluster/metadata/MetadataIndexStateServiceTests.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.elasticsearch.cluster.routing.UnassignedInfo;
2828
import org.elasticsearch.common.settings.Settings;
2929
import org.elasticsearch.core.Nullable;
30+
import org.elasticsearch.core.Tuple;
3031
import org.elasticsearch.index.Index;
3132
import org.elasticsearch.index.IndexNotFoundException;
3233
import org.elasticsearch.index.IndexVersion;
@@ -45,6 +46,7 @@
4546
import java.util.Collections;
4647
import java.util.HashMap;
4748
import java.util.HashSet;
49+
import java.util.List;
4850
import java.util.Map;
4951
import java.util.Set;
5052
import java.util.stream.Collectors;
@@ -164,6 +166,38 @@ public void testCloseRoutingTableWithSnapshottedIndex() {
164166
assertThat(updatedState.blocks().hasIndexBlockWithId(projectId, index.getName(), INDEX_CLOSED_BLOCK_ID), is(true));
165167
}
166168

169+
public void testCloseRoutingTableWithReshardingIndex() {
170+
ClusterState state = stateWithProject("testCloseRoutingTableWithReshardingIndex", projectId);
171+
172+
String indexName = "resharding-index";
173+
ClusterBlock block = MetadataIndexStateService.createIndexClosingBlock();
174+
state = addOpenedIndex(projectId, indexName, randomIntBetween(1, 5), randomIntBetween(0, 5), state);
175+
176+
var updatedMetadata = IndexMetadata.builder(state.metadata().getProject(projectId).index(indexName))
177+
.reshardingMetadata(IndexReshardingMetadata.newSplitByMultiple(randomIntBetween(1, 5), 2))
178+
.build();
179+
state = ClusterState.builder(state)
180+
.putProjectMetadata(ProjectMetadata.builder(state.metadata().getProject(projectId)).put(updatedMetadata, true))
181+
.build();
182+
183+
state = ClusterState.builder(state)
184+
.blocks(ClusterBlocks.builder().blocks(state.blocks()).addIndexBlock(projectId, indexName, block))
185+
.build();
186+
187+
final Index index = state.metadata().getProject(projectId).index(indexName).getIndex();
188+
final Tuple<ClusterState, List<IndexResult>> result = MetadataIndexStateService.closeRoutingTable(
189+
state,
190+
projectId,
191+
Map.of(index, block),
192+
Map.of(index, new IndexResult(index)),
193+
TestShardRoutingRoleStrategies.DEFAULT_ROLE_ONLY
194+
);
195+
final ClusterState updatedState = result.v1();
196+
assertIsOpened(index.getName(), updatedState, projectId);
197+
assertThat(updatedState.blocks().hasIndexBlockWithId(projectId, index.getName(), INDEX_CLOSED_BLOCK_ID), is(true));
198+
assertThat(result.v2().get(0).getException(), notNullValue());
199+
}
200+
167201
public void testAddIndexClosedBlocks() {
168202
final ClusterState initialState = stateWithProject("testAddIndexClosedBlocks", projectId);
169203
{
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.index;
11+
12+
import org.elasticsearch.cluster.ClusterName;
13+
import org.elasticsearch.cluster.ClusterState;
14+
import org.elasticsearch.cluster.metadata.IndexMetadata;
15+
import org.elasticsearch.cluster.metadata.IndexReshardingMetadata;
16+
import org.elasticsearch.cluster.metadata.ProjectMetadata;
17+
import org.elasticsearch.test.ESTestCase;
18+
19+
import java.util.HashSet;
20+
import java.util.Set;
21+
22+
public class IndexReshardServiceTests extends ESTestCase {
23+
public void testReshardingIndices() {
24+
var indexMetadata1 = IndexMetadata.builder(randomAlphaOfLength(5)).settings(indexSettings(IndexVersion.current(), 1, 0)).build();
25+
var indexMetadata2 = IndexMetadata.builder(indexMetadata1.getIndex().getName() + "2")
26+
.settings(indexSettings(IndexVersion.current(), 1, 0))
27+
.reshardingMetadata(IndexReshardingMetadata.newSplitByMultiple(1, 2))
28+
.build();
29+
30+
var projectId = randomProjectIdOrDefault();
31+
32+
var projectMetadataBuilder = ProjectMetadata.builder(projectId);
33+
projectMetadataBuilder.put(indexMetadata1, false);
34+
projectMetadataBuilder.put(indexMetadata2, false);
35+
36+
final ClusterState clusterState = ClusterState.builder(new ClusterName("testReshardingIndices"))
37+
.putProjectMetadata(projectMetadataBuilder)
38+
.build();
39+
40+
Set<Index> indicesToCheck = new HashSet<>();
41+
indicesToCheck.add(indexMetadata1.getIndex());
42+
indicesToCheck.add(indexMetadata2.getIndex());
43+
44+
Set<Index> reshardingIndices = IndexReshardService.reshardingIndices(clusterState.projectState(projectId), indicesToCheck);
45+
46+
assertEquals(1, reshardingIndices.size());
47+
assertTrue(reshardingIndices.contains(indexMetadata2.getIndex()));
48+
}
49+
}

0 commit comments

Comments
 (0)