5959import static org .opensearch .cluster .metadata .MetadataIndexStateServiceTests .addClosedIndex ;
6060import static org .opensearch .cluster .metadata .MetadataIndexStateServiceTests .addOpenedIndex ;
6161import static org .opensearch .cluster .shards .ShardCounts .forDataNodeCount ;
62- import static org .opensearch .indices .ShardLimitValidator .SETTING_CLUSTER_IGNORE_DOT_INDEXES ;
63- import static org .opensearch .indices .ShardLimitValidator .SETTING_CLUSTER_MAX_SHARDS_PER_NODE ;
6462import static org .mockito .Mockito .mock ;
6563import static org .mockito .Mockito .when ;
64+ import static org .opensearch .indices .ShardLimitValidator .SETTING_CLUSTER_IGNORE_DOT_INDEXES ;
65+ import static org .opensearch .indices .ShardLimitValidator .SETTING_CLUSTER_MAX_SHARDS_PER_NODE ;
66+ import static org .opensearch .indices .ShardLimitValidator .SETTING_MAX_SHARDS_PER_CLUSTER ;
6667
6768public class ShardLimitValidatorTests extends OpenSearchTestCase {
6869
@@ -75,7 +76,7 @@ public void testOverShardLimit() {
7576 ClusterState state = createClusterForShardLimitTest (nodesInCluster , counts .getFirstIndexShards (), counts .getFirstIndexReplicas ());
7677
7778 int shardsToAdd = counts .getFailingIndexShards () * (1 + counts .getFailingIndexReplicas ());
78- Optional <String > errorMessage = ShardLimitValidator .checkShardLimit (shardsToAdd , state , counts .getShardsPerNode ());
79+ Optional <String > errorMessage = ShardLimitValidator .checkShardLimit (shardsToAdd , state , counts .getShardsPerNode (), - 1 );
7980
8081 int totalShards = counts .getFailingIndexShards () * (1 + counts .getFailingIndexReplicas ());
8182 int currentShards = counts .getFirstIndexShards () * (1 + counts .getFirstIndexReplicas ());
@@ -93,6 +94,30 @@ public void testOverShardLimit() {
9394 );
9495 }
9596
97+ public void testOverShardLimitWithMaxShardCountLimit () {
98+ int nodesInCluster = randomIntBetween (1 , 90 );
99+ ShardCounts counts = forDataNodeCount (nodesInCluster );
100+
101+ ClusterState state = createClusterForShardLimitTest (nodesInCluster , counts .getFirstIndexShards (), counts .getFirstIndexReplicas ());
102+ int shardsToAdd = counts .getFailingIndexShards () * (1 + counts .getFailingIndexReplicas ());
103+ int maxShardLimitOnCluster = shardsToAdd - 1 ;
104+ Optional <String > errorMessage = ShardLimitValidator .checkShardLimit (shardsToAdd , state , counts .getShardsPerNode (), maxShardLimitOnCluster );
105+
106+ int totalShards = counts .getFailingIndexShards () * (1 + counts .getFailingIndexReplicas ());
107+ int currentShards = counts .getFirstIndexShards () * (1 + counts .getFirstIndexReplicas ());
108+ assertTrue (errorMessage .isPresent ());
109+ assertEquals (
110+ "this action would add ["
111+ + totalShards
112+ + "] total shards, but this cluster currently has ["
113+ + currentShards
114+ + "]/["
115+ + maxShardLimitOnCluster
116+ + "] maximum shards open" ,
117+ errorMessage .get ()
118+ );
119+ }
120+
96121 public void testUnderShardLimit () {
97122 int nodesInCluster = randomIntBetween (2 , 90 );
98123 // Calculate the counts for a cluster 1 node smaller than we have to ensure we have headroom
@@ -104,7 +129,7 @@ public void testUnderShardLimit() {
104129
105130 int existingShards = counts .getFirstIndexShards () * (1 + counts .getFirstIndexReplicas ());
106131 int shardsToAdd = randomIntBetween (1 , (counts .getShardsPerNode () * nodesInCluster ) - existingShards );
107- Optional <String > errorMessage = ShardLimitValidator .checkShardLimit (shardsToAdd , state , counts .getShardsPerNode ());
132+ Optional <String > errorMessage = ShardLimitValidator .checkShardLimit (shardsToAdd , state , counts .getShardsPerNode (), - 1 );
108133
109134 assertFalse (errorMessage .isPresent ());
110135 }
@@ -152,6 +177,54 @@ public void testNonSystemIndexCreationFails() {
152177 );
153178 }
154179
180+ public void testNonSystemIndexCreationFailsWithMaxShardLimitOnCluster () {
181+ final int maxShardLimitOnCluster = 1 ;
182+ Settings limitOnlySettings = Settings .builder ()
183+ .put (SETTING_CLUSTER_MAX_SHARDS_PER_NODE .getKey (), 1 )
184+ .put (SETTING_CLUSTER_IGNORE_DOT_INDEXES .getKey (), false )
185+ .put (SETTING_MAX_SHARDS_PER_CLUSTER , maxShardLimitOnCluster )
186+ .build ();
187+ final ShardLimitValidator shardLimitValidator = createTestShardLimitService (limitOnlySettings );
188+ final Settings settings = Settings .builder ()
189+ .put (SETTING_VERSION_CREATED , Version .CURRENT )
190+ .put (SETTING_NUMBER_OF_SHARDS , 1 )
191+ .put (SETTING_NUMBER_OF_REPLICAS , 1 )
192+ .build ();
193+ final ClusterState state = createClusterForShardLimitTest (1 , 1 , 0 );
194+ final ValidationException exception = expectThrows (
195+ ValidationException .class ,
196+ () -> shardLimitValidator .validateShardLimit ("abc" , settings , state )
197+ );
198+ assertEquals (
199+ "Validation Failed: 1: this action would add ["
200+ + 2
201+ + "] total shards, but this cluster currently has ["
202+ + 1
203+ + "]/["
204+ + maxShardLimitOnCluster
205+ + "] maximum shards open;" ,
206+ exception .getMessage ()
207+ );
208+ }
209+
210+ public void testNonSystemIndexCreationPassesWithMaxShardLimitOnCluster () {
211+ final int maxShardLimitOnCluster = 100 ;
212+ Settings limitOnlySettings = Settings .builder ()
213+ .put (SETTING_CLUSTER_MAX_SHARDS_PER_NODE .getKey (), 1 )
214+ .put (SETTING_CLUSTER_IGNORE_DOT_INDEXES .getKey (), false )
215+ .put (SETTING_MAX_SHARDS_PER_CLUSTER , maxShardLimitOnCluster )
216+ .build ();
217+ final ShardLimitValidator shardLimitValidator = createTestShardLimitService (limitOnlySettings );
218+ final Settings settings = Settings .builder ()
219+ .put (SETTING_VERSION_CREATED , Version .CURRENT )
220+ .put (SETTING_NUMBER_OF_SHARDS , 1 )
221+ .put (SETTING_NUMBER_OF_REPLICAS , 1 )
222+ .build ();
223+ final ClusterState state = createClusterForShardLimitTest (1 , 1 , 0 );
224+ shardLimitValidator .validateShardLimit ("abc" , settings , state );
225+ }
226+
227+
155228 /**
156229 * This test validates that index starting with dot creation Succeeds
157230 * when the setting cluster.ignore_dot_indexes is set to true.
@@ -489,6 +562,22 @@ public static ClusterState createClusterForShardLimitTest(
489562 );
490563 }
491564
565+ /**
566+ * Creates a {@link ShardLimitValidator} for testing with the given setting and a mocked cluster service.
567+ *
568+ * @param limitOnlySettings the setting used for creating ShardLimitValidator.
569+ * @return a test instance
570+ */
571+ private static ShardLimitValidator createTestShardLimitService (final Settings limitOnlySettings ) {
572+ // Use a mocked clusterService - for unit tests we won't be updating the setting anyway.
573+ ClusterService clusterService = mock (ClusterService .class );
574+ when (clusterService .getClusterSettings ()).thenReturn (
575+ new ClusterSettings (limitOnlySettings , ClusterSettings .BUILT_IN_CLUSTER_SETTINGS )
576+ );
577+
578+ return new ShardLimitValidator (limitOnlySettings , clusterService , new SystemIndices (emptyMap ()));
579+ }
580+
492581 /**
493582 * Creates a {@link ShardLimitValidator} for testing with the given setting and a mocked cluster service.
494583 *
0 commit comments