3939import org .opensearch .action .admin .indices .stats .IndicesStatsResponse ;
4040import org .opensearch .action .admin .indices .stats .ShardStats ;
4141import org .opensearch .client .node .NodeClient ;
42+ import org .opensearch .cluster .routing .IndexShardRoutingTable ;
4243import org .opensearch .cluster .routing .ShardRouting ;
4344import org .opensearch .cluster .routing .UnassignedInfo ;
4445import org .opensearch .common .Table ;
6667import org .opensearch .search .suggest .completion .CompletionStats ;
6768
6869import java .time .Instant ;
70+ import java .util .ArrayList ;
71+ import java .util .Base64 ;
6972import java .util .List ;
7073import java .util .Locale ;
74+ import java .util .Map ;
75+ import java .util .Objects ;
7176import java .util .function .Function ;
7277
7378import static java .util .Arrays .asList ;
@@ -106,24 +111,126 @@ protected void documentation(StringBuilder sb) {
106111
107112 @ Override
108113 public RestChannelConsumer doCatRequest (final RestRequest request , final NodeClient client ) {
109- final String [] indices = Strings .splitStringByCommaToArray (request .param ("index" ));
114+
115+ String [] indices = new String [0 ];
110116 final ClusterStateRequest clusterStateRequest = new ClusterStateRequest ();
111117 clusterStateRequest .local (request .paramAsBoolean ("local" , clusterStateRequest .local ()));
112118 clusterStateRequest .clusterManagerNodeTimeout (
113119 request .paramAsTime ("cluster_manager_timeout" , clusterStateRequest .clusterManagerNodeTimeout ())
114120 );
115121 parseDeprecatedMasterTimeoutParameter (clusterStateRequest , request , deprecationLogger , getName ());
116- clusterStateRequest .clear ().nodes (true ).routingTable (true ).indices (indices );
122+ if (request .hasParam ("nextToken" )) {
123+ // ToDo: Add validation on the nextToken passed in the request
124+ // Need to get the metadata as well
125+ request .param ("nextToken" );
126+ clusterStateRequest .clear ().nodes (true ).routingTable (true ).metadata (true );
127+ } else {
128+ // Only parse the "index" param if the request is not-paginated.
129+ indices = Strings .splitStringByCommaToArray (request .param ("index" ));
130+ clusterStateRequest .clear ().nodes (true ).routingTable (true ).indices (indices );
131+ }
132+
133+ String [] finalIndices = indices ;
117134 return channel -> client .admin ().cluster ().state (clusterStateRequest , new RestActionListener <ClusterStateResponse >(channel ) {
118135 @ Override
119136 public void processResponse (final ClusterStateResponse clusterStateResponse ) {
137+ String nextToken = null ;
120138 IndicesStatsRequest indicesStatsRequest = new IndicesStatsRequest ();
121139 indicesStatsRequest .all ();
122- indicesStatsRequest .indices (indices );
140+ final List <ShardRouting > shardRoutingResponseList = new ArrayList <>();
141+ if (request .hasParam ("nextToken" )) {
142+ final long defaultPageSize = (long ) clusterStateResponse .getState ().nodes ().getDataNodes ().size () + 1 ;
143+ // Get the nextToken
144+ final String nextTokenInRequest = Objects .equals (request .param ("nextToken" ), "null" )
145+ ? "null"
146+ : new String (Base64 .getDecoder ().decode (request .param ("nextToken" )));
147+ List <String > sortedIndicesList = new ArrayList <String >(clusterStateResponse .getState ().metadata ().indices ().keySet ());
148+
149+ // Get the number of shards upto the maxPageSize
150+ long shardCountSoFar = 0L ;
151+ List <String > indicesToBeQueried = new ArrayList <String >();
152+
153+ // Since all the shards for last ID would have already been sent in the last response,
154+ // start iterating from the next shard for current page
155+ int newPageStartShardID = "null" .equals (nextTokenInRequest )
156+ ? 0
157+ : Integer .parseInt (nextTokenInRequest .split ("\\ $" )[0 ]) + 1 ;
158+ // Since all the shards corresponding to the last processed index might not have been included in the last page,
159+ // start iterating from the last index number itself
160+ int newPageStartIndexNumber = "null" .equals (nextTokenInRequest )
161+ ? 0
162+ : Integer .parseInt (nextTokenInRequest .split ("\\ $" )[1 ]);
163+
164+ int lastProcessedShardNumber = -1 ;
165+ int lastProcessedIndexNumber = -1 ;
166+ // ToDo: Handle case when index gets deleted. Select the first index with creationTime just greater than the last
167+ // index's creationTime
168+ int indexNumberInSortedList = newPageStartIndexNumber ;
169+ for (; indexNumberInSortedList < sortedIndicesList .size (); indexNumberInSortedList ++) {
170+ String index = sortedIndicesList .get (indexNumberInSortedList );
171+ Map <Integer , IndexShardRoutingTable > indexShards = clusterStateResponse .getState ()
172+ .getRoutingTable ()
173+ .getIndicesRouting ()
174+ .get (index )
175+ .getShards ();
176+ // If all the shards corresponding to the index were already processed, move to the next Index
177+ if (indexNumberInSortedList == newPageStartIndexNumber && (newPageStartShardID > indexShards .size () - 1 )) {
178+ // ToDo: Add validation that the newPageStartShardID should not be greater than the
179+ // newPageStartIndexShards.size()
180+ newPageStartShardID = 0 ;
181+ continue ;
182+ }
183+ int lastProcessedShardNumberForCurrentIndex = -1 ;
184+ int shardID = (indexNumberInSortedList == newPageStartIndexNumber ) ? newPageStartShardID : 0 ;
185+ for (; shardID < indexShards .size (); shardID ++) {
186+ shardCountSoFar += indexShards .get (shardID ).shards ().size ();
187+ if (shardCountSoFar > defaultPageSize ) {
188+ break ;
189+ }
190+ shardRoutingResponseList .addAll (indexShards .get (shardID ).shards ());
191+ lastProcessedShardNumberForCurrentIndex = shardID ;
192+ }
193+
194+ if (shardCountSoFar > defaultPageSize ) {
195+ if (lastProcessedShardNumberForCurrentIndex != -1 ) {
196+ indicesToBeQueried .add (index );
197+ lastProcessedIndexNumber = indexNumberInSortedList ;
198+ lastProcessedShardNumber = lastProcessedShardNumberForCurrentIndex ;
199+ }
200+ break ;
201+ }
202+ indicesToBeQueried .add (index );
203+ lastProcessedShardNumber = lastProcessedShardNumberForCurrentIndex ;
204+ lastProcessedIndexNumber = indexNumberInSortedList ;
205+ }
206+ nextToken = indexNumberInSortedList >= sortedIndicesList .size ()
207+ ? "null"
208+ : Base64 .getEncoder ()
209+ .encodeToString (
210+ (lastProcessedShardNumber
211+ + "$"
212+ + (lastProcessedIndexNumber )
213+ + "$"
214+ + clusterStateResponse .getState ()
215+ .metadata ()
216+ .indices ()
217+ .get (sortedIndicesList .get (lastProcessedIndexNumber ))
218+ .getCreationDate ()).getBytes ()
219+ );
220+ indicesStatsRequest .indices (indicesToBeQueried .toArray (new String [0 ]));
221+ } else {
222+ shardRoutingResponseList .addAll (clusterStateResponse .getState ().routingTable ().allShards ());
223+ indicesStatsRequest .indices (finalIndices );
224+ }
225+
226+ final String finalNextToken = nextToken ;
123227 client .admin ().indices ().stats (indicesStatsRequest , new RestResponseListener <IndicesStatsResponse >(channel ) {
124228 @ Override
125229 public RestResponse buildResponse (IndicesStatsResponse indicesStatsResponse ) throws Exception {
126- return RestTable .buildResponse (buildTable (request , clusterStateResponse , indicesStatsResponse ), channel );
230+ return RestTable .buildResponse (
231+ buildTable (request , clusterStateResponse , indicesStatsResponse , shardRoutingResponseList , finalNextToken ),
232+ channel
233+ );
127234 }
128235 });
129236 }
@@ -132,7 +239,11 @@ public RestResponse buildResponse(IndicesStatsResponse indicesStatsResponse) thr
132239
133240 @ Override
134241 protected Table getTableWithHeader (final RestRequest request ) {
135- Table table = new Table ();
242+ return getTableWithHeader (request , null );
243+ }
244+
245+ protected Table getTableWithHeader (final RestRequest request , String nextToken ) {
246+ Table table = new Table (nextToken , "Shards" );
136247 table .startHeaders ()
137248 .addCell ("index" , "default:true;alias:i,idx;desc:index name" )
138249 .addCell ("shard" , "default:true;alias:s,sh;desc:shard name" )
@@ -301,10 +412,15 @@ private static <S, T> Object getOrNull(S stats, Function<S, T> accessor, Functio
301412 }
302413
303414 // package private for testing
304- Table buildTable (RestRequest request , ClusterStateResponse state , IndicesStatsResponse stats ) {
305- Table table = getTableWithHeader (request );
306-
307- for (ShardRouting shard : state .getState ().routingTable ().allShards ()) {
415+ Table buildTable (
416+ RestRequest request ,
417+ ClusterStateResponse state ,
418+ IndicesStatsResponse stats ,
419+ List <ShardRouting > shardRoutingList ,
420+ String nextToken
421+ ) {
422+ Table table = getTableWithHeader (request , nextToken );
423+ for (ShardRouting shard : shardRoutingList ) {
308424 ShardStats shardStats = stats .asMap ().get (shard );
309425 CommonStats commonStats = null ;
310426 CommitStats commitStats = null ;
@@ -453,7 +569,6 @@ Table buildTable(RestRequest request, ClusterStateResponse state, IndicesStatsRe
453569
454570 table .endRow ();
455571 }
456-
457572 return table ;
458573 }
459574}
0 commit comments