Skip to content

Commit ea35abc

Browse files
authored
Protect shard splitting from illegal target shards (#27468)
While we have an assertion that checks if the number of routing shards is a multiple of the number of shards we need a real hard exception that checks this way earlier. This change adds a check and test that is executed before we create the index. Relates to #26931
1 parent 29450de commit ea35abc

File tree

4 files changed

+62
-13
lines changed

4 files changed

+62
-13
lines changed

core/src/main/java/org/elasticsearch/cluster/metadata/IndexMetaData.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1343,6 +1343,12 @@ public static ShardId selectSplitShard(int shardId, IndexMetaData sourceIndexMet
13431343
+ "] must be less that the number of target shards [" + numTargetShards + "]");
13441344
}
13451345
int routingFactor = getRoutingFactor(numSourceShards, numTargetShards);
1346+
// now we verify that the numRoutingShards is valid in the source index
1347+
int routingNumShards = sourceIndexMetadata.getRoutingNumShards();
1348+
if (routingNumShards % numTargetShards != 0) {
1349+
throw new IllegalStateException("the number of routing shards ["
1350+
+ routingNumShards + "] must be a multiple of the target shards [" + numTargetShards + "]");
1351+
}
13461352
// this is just an additional assertion that ensures we are a factor of the routing num shards.
13471353
assert getRoutingFactor(numTargetShards, sourceIndexMetadata.getRoutingNumShards()) >= 0;
13481354
return new ShardId(sourceIndexMetadata.getIndex(), shardId/routingFactor);

core/src/test/java/org/elasticsearch/cluster/metadata/IndexMetaDataTests.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,16 @@ public void testSelectShrinkShards() {
118118
}
119119

120120
public void testSelectResizeShards() {
121+
int numTargetShards = randomFrom(4, 6, 8, 12);
122+
121123
IndexMetaData split = IndexMetaData.builder("foo")
122124
.settings(Settings.builder()
123125
.put("index.version.created", 1)
124126
.put("index.number_of_shards", 2)
125127
.put("index.number_of_replicas", 0)
126128
.build())
127129
.creationDate(randomLong())
130+
.setRoutingNumShards(numTargetShards * 2)
128131
.build();
129132

130133
IndexMetaData shrink = IndexMetaData.builder("foo")
@@ -135,7 +138,6 @@ public void testSelectResizeShards() {
135138
.build())
136139
.creationDate(randomLong())
137140
.build();
138-
int numTargetShards = randomFrom(4, 6, 8, 12);
139141
int shard = randomIntBetween(0, numTargetShards-1);
140142
assertEquals(Collections.singleton(IndexMetaData.selectSplitShard(shard, split, numTargetShards)),
141143
IndexMetaData.selectRecoverFromShards(shard, split, numTargetShards));
@@ -173,6 +175,9 @@ public void testSelectSplitShard() {
173175

174176
assertEquals("the number of source shards [2] must be a must be a factor of [3]",
175177
expectThrows(IllegalArgumentException.class, () -> IndexMetaData.selectSplitShard(0, metaData, 3)).getMessage());
178+
179+
assertEquals("the number of routing shards [4] must be a multiple of the target shards [8]",
180+
expectThrows(IllegalStateException.class, () -> IndexMetaData.selectSplitShard(0, metaData, 8)).getMessage());
176181
}
177182

178183
public void testIndexFormat() {

core/src/test/java/org/elasticsearch/cluster/metadata/MetaDataCreateIndexServiceTests.java

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,12 @@
5656
public class MetaDataCreateIndexServiceTests extends ESTestCase {
5757

5858
private ClusterState createClusterState(String name, int numShards, int numReplicas, Settings settings) {
59+
int numRoutingShards = settings.getAsInt(IndexMetaData.INDEX_NUMBER_OF_ROUTING_SHARDS_SETTING.getKey(), numShards);
5960
MetaData.Builder metaBuilder = MetaData.builder();
6061
IndexMetaData indexMetaData = IndexMetaData.builder(name).settings(settings(Version.CURRENT)
6162
.put(settings))
62-
.numberOfShards(numShards).numberOfReplicas(numReplicas).build();
63+
.numberOfShards(numShards).numberOfReplicas(numReplicas)
64+
.setRoutingNumShards(numRoutingShards).build();
6365
metaBuilder.put(indexMetaData, false);
6466
MetaData metaData = metaBuilder.build();
6567
RoutingTable.Builder routingTableBuilder = RoutingTable.builder();
@@ -204,10 +206,13 @@ public void testValidateSplitIndex() {
204206
}
205207
).getMessage());
206208

207-
209+
int targetShards;
210+
do {
211+
targetShards = randomIntBetween(numShards+1, 100);
212+
} while (isSplitable(numShards, targetShards) == false);
208213
ClusterState clusterState = ClusterState.builder(createClusterState("source", numShards, 0,
209-
Settings.builder().put("index.blocks.write", true).build())).nodes(DiscoveryNodes.builder().add(newNode("node1")))
210-
.build();
214+
Settings.builder().put("index.blocks.write", true).put("index.number_of_routing_shards", targetShards).build()))
215+
.nodes(DiscoveryNodes.builder().add(newNode("node1"))).build();
211216
AllocationService service = new AllocationService(Settings.builder().build(), new AllocationDeciders(Settings.EMPTY,
212217
Collections.singleton(new MaxRetryAllocationDecider(Settings.EMPTY))),
213218
new TestGatewayAllocator(), new BalancedShardsAllocator(Settings.EMPTY), EmptyClusterInfoService.INSTANCE);
@@ -218,10 +223,7 @@ public void testValidateSplitIndex() {
218223
routingTable = service.applyStartedShards(clusterState,
219224
routingTable.index("source").shardsWithState(ShardRoutingState.INITIALIZING)).routingTable();
220225
clusterState = ClusterState.builder(clusterState).routingTable(routingTable).build();
221-
int targetShards;
222-
do {
223-
targetShards = randomIntBetween(numShards+1, 100);
224-
} while (isSplitable(numShards, targetShards) == false);
226+
225227
MetaDataCreateIndexService.validateSplitIndex(clusterState, "source", Collections.emptySet(), "target",
226228
Settings.builder().put("index.number_of_shards", targetShards).build());
227229
}

rest-api-spec/src/main/resources/rest-api-spec/test/indices.split/10_basic.yml

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
---
2-
"Split index via API":
3-
- skip:
4-
version: " - 6.0.99"
5-
reason: Added in 6.1.0
2+
setup:
63
- do:
74
indices.create:
85
index: source
@@ -33,6 +30,12 @@
3330
id: "3"
3431
body: { "foo": "hello world 3" }
3532

33+
---
34+
"Split index via API":
35+
- skip:
36+
version: " - 6.0.99"
37+
reason: Added in 6.1.0
38+
3639
# make it read-only
3740
- do:
3841
indices.put_settings:
@@ -97,5 +100,38 @@
97100
- match: { _id: "3" }
98101
- match: { _source: { foo: "hello world 3" } }
99102

103+
---
104+
"Create illegal split indices":
105+
- skip:
106+
version: " - 6.99.99"
107+
reason: fixed in 7.0.0
108+
109+
# try to do an illegal split with number_of_routing_shards set
110+
- do:
111+
catch: /illegal_argument_exception/
112+
indices.split:
113+
index: "source"
114+
target: "target"
115+
wait_for_active_shards: 1
116+
master_timeout: 10s
117+
body:
118+
settings:
119+
index.number_of_replicas: 0
120+
index.number_of_shards: 2
121+
index.number_of_routing_shards: 4
122+
123+
# try to do an illegal split with illegal number_of_shards
124+
- do:
125+
catch: /illegal_state_exception/
126+
indices.split:
127+
index: "source"
128+
target: "target"
129+
wait_for_active_shards: 1
130+
master_timeout: 10s
131+
body:
132+
settings:
133+
index.number_of_replicas: 0
134+
index.number_of_shards: 3
135+
100136

101137

0 commit comments

Comments
 (0)