Skip to content

Commit 671bb92

Browse files
author
Harsh Garg
committed
Implementing pagination for _cat/shards and metadata changes
Signed-off-by: Harsh Garg <[email protected]>
1 parent 270054c commit 671bb92

File tree

9 files changed

+214
-19
lines changed

9 files changed

+214
-19
lines changed

server/src/main/java/org/opensearch/cluster/DiffableUtils.java

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import java.util.Collections;
4444
import java.util.HashMap;
4545
import java.util.HashSet;
46+
import java.util.LinkedHashMap;
4647
import java.util.List;
4748
import java.util.Map;
4849
import java.util.Set;
@@ -88,6 +89,16 @@ public static <K, T extends Diffable<T>> MapDiff<K, T, Map<K, T>> diff(
8889
return new JdkMapDiff<>(before, after, keySerializer, DiffableValueSerializer.getWriteOnlyInstance());
8990
}
9091

92+
public static <K, T extends Diffable<T>> MapDiff<K, T, Map<K, T>> diff(
93+
Map<K, T> before,
94+
Map<K, T> after,
95+
KeySerializer<K> keySerializer,
96+
boolean maintainOrder
97+
) {
98+
assert after != null && before != null;
99+
return new JdkMapDiff<>(before, after, keySerializer, DiffableValueSerializer.getWriteOnlyInstance(), maintainOrder);
100+
}
101+
91102
/**
92103
* Calculates diff between two Maps of non-diffable objects
93104
*/
@@ -138,7 +149,17 @@ protected JdkMapDiff(StreamInput in, KeySerializer<K> keySerializer, ValueSerial
138149
}
139150

140151
JdkMapDiff(Map<K, T> before, Map<K, T> after, KeySerializer<K> keySerializer, ValueSerializer<K, T> valueSerializer) {
141-
super(keySerializer, valueSerializer);
152+
this(before, after, keySerializer, valueSerializer, false);
153+
}
154+
155+
JdkMapDiff(
156+
Map<K, T> before,
157+
Map<K, T> after,
158+
KeySerializer<K> keySerializer,
159+
ValueSerializer<K, T> valueSerializer,
160+
boolean maintainOrder
161+
) {
162+
super(keySerializer, valueSerializer, maintainOrder);
142163
assert after != null && before != null;
143164

144165
for (K key : before.keySet()) {
@@ -163,7 +184,7 @@ protected JdkMapDiff(StreamInput in, KeySerializer<K> keySerializer, ValueSerial
163184

164185
@Override
165186
public Map<K, T> apply(Map<K, T> map) {
166-
Map<K, T> builder = new HashMap<>(map);
187+
Map<K, T> builder = maintainOrder ? new LinkedHashMap<>(map) : new HashMap<>(map);
167188

168189
for (K part : deletes) {
169190
builder.remove(part);
@@ -198,13 +219,24 @@ public abstract static class MapDiff<K, T, M> implements Diff<M> {
198219
protected final Map<K, T> upserts; // additions or full updates
199220
protected final KeySerializer<K> keySerializer;
200221
protected final ValueSerializer<K, T> valueSerializer;
222+
protected final boolean maintainOrder;
201223

202224
protected MapDiff(KeySerializer<K> keySerializer, ValueSerializer<K, T> valueSerializer) {
203225
this.keySerializer = keySerializer;
204226
this.valueSerializer = valueSerializer;
205227
deletes = new ArrayList<>();
206228
diffs = new HashMap<>();
207229
upserts = new HashMap<>();
230+
this.maintainOrder = false;
231+
}
232+
233+
protected MapDiff(KeySerializer<K> keySerializer, ValueSerializer<K, T> valueSerializer, boolean maintainOrder) {
234+
this.keySerializer = keySerializer;
235+
this.valueSerializer = valueSerializer;
236+
deletes = new ArrayList<>();
237+
diffs = new HashMap<>();
238+
upserts = maintainOrder ? new LinkedHashMap<>() : new HashMap<>();
239+
this.maintainOrder = maintainOrder;
208240
}
209241

210242
protected MapDiff(
@@ -219,11 +251,13 @@ protected MapDiff(
219251
this.deletes = deletes;
220252
this.diffs = diffs;
221253
this.upserts = upserts;
254+
this.maintainOrder = false;
222255
}
223256

224257
protected MapDiff(StreamInput in, KeySerializer<K> keySerializer, ValueSerializer<K, T> valueSerializer) throws IOException {
225258
this.keySerializer = keySerializer;
226259
this.valueSerializer = valueSerializer;
260+
this.maintainOrder = false;
227261
deletes = in.readList(keySerializer::readKey);
228262
int diffsCount = in.readVInt();
229263
diffs = diffsCount == 0 ? Collections.emptyMap() : new HashMap<>(diffsCount);

server/src/main/java/org/opensearch/cluster/metadata/Metadata.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
import java.util.HashMap;
8080
import java.util.HashSet;
8181
import java.util.Iterator;
82+
import java.util.LinkedHashMap;
8283
import java.util.List;
8384
import java.util.Map;
8485
import java.util.Objects;
@@ -307,7 +308,8 @@ static Custom fromXContent(XContentParser parser, String name) throws IOExceptio
307308
this.persistentSettings = persistentSettings;
308309
this.settings = Settings.builder().put(persistentSettings).put(transientSettings).build();
309310
this.hashesOfConsistentSettings = hashesOfConsistentSettings;
310-
this.indices = Collections.unmodifiableMap(indices);
311+
LinkedHashMap<String, IndexMetadata> linkedMap = new LinkedHashMap<>(indices);
312+
this.indices = Collections.unmodifiableMap(linkedMap);
311313
this.customs = Collections.unmodifiableMap(customs);
312314
this.templates = new TemplatesMetadata(templates);
313315
int totalNumberOfShards = 0;
@@ -1039,7 +1041,7 @@ private static class MetadataDiff implements Diff<Metadata> {
10391041
transientSettings = after.transientSettings;
10401042
persistentSettings = after.persistentSettings;
10411043
hashesOfConsistentSettings = after.hashesOfConsistentSettings.diff(before.hashesOfConsistentSettings);
1042-
indices = DiffableUtils.diff(before.indices, after.indices, DiffableUtils.getStringKeySerializer());
1044+
indices = DiffableUtils.diff(before.indices, after.indices, DiffableUtils.getStringKeySerializer(), true);
10431045
templates = DiffableUtils.diff(
10441046
before.templates.getTemplates(),
10451047
after.templates.getTemplates(),
@@ -1183,7 +1185,7 @@ public static class Builder {
11831185

11841186
public Builder() {
11851187
clusterUUID = UNKNOWN_CLUSTER_UUID;
1186-
indices = new HashMap<>();
1188+
indices = new LinkedHashMap<>();
11871189
templates = new HashMap<>();
11881190
customs = new HashMap<>();
11891191
previousMetadata = null;
@@ -1198,7 +1200,7 @@ public Builder(Metadata metadata) {
11981200
this.persistentSettings = metadata.persistentSettings;
11991201
this.hashesOfConsistentSettings = metadata.hashesOfConsistentSettings;
12001202
this.version = metadata.version;
1201-
this.indices = new HashMap<>(metadata.indices);
1203+
this.indices = new LinkedHashMap<>(metadata.indices);
12021204
this.templates = new HashMap<>(metadata.templates.getTemplates());
12031205
this.customs = new HashMap<>(metadata.customs);
12041206
this.previousMetadata = metadata;

server/src/main/java/org/opensearch/cluster/service/ClusterApplierService.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,7 @@ private void callClusterStateListener(
652652
clusterManagerMetrics.clusterStateListenersHistogram,
653653
(double) Math.max(0, TimeValue.nsecToMSec(System.nanoTime() - listenerStartTimeNS)),
654654
Optional.of(Tags.create().addTag("Operation", listener.getClass().getSimpleName()))
655+
655656
);
656657
}
657658
} catch (Exception ex) {

server/src/main/java/org/opensearch/common/Table.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,21 @@ public class Table {
5959
private List<Cell> currentCells;
6060
private boolean inHeaders = false;
6161
private boolean withTime = false;
62+
public String nextToken = null;
63+
public String paginatedElement = null;
6264
public static final String EPOCH = "epoch";
6365
public static final String TIMESTAMP = "timestamp";
6466

67+
public Table() {}
68+
69+
public Table(@Nullable String nextToken, @Nullable String paginatedElement) {
70+
if (nextToken != null) {
71+
assert paginatedElement != null : "Element which is getting paginated such as indices, shards or segments should be specified";
72+
this.nextToken = nextToken;
73+
this.paginatedElement = paginatedElement;
74+
}
75+
}
76+
6577
public Table startHeaders() {
6678
inHeaders = true;
6779
currentCells = new ArrayList<>();

server/src/main/java/org/opensearch/indices/cluster/IndicesClusterStateService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,7 @@ private void createIndices(final ClusterState state) {
538538
for (Map.Entry<Index, List<ShardRouting>> entry : indicesToCreate.entrySet()) {
539539
final Index index = entry.getKey();
540540
final IndexMetadata indexMetadata = state.metadata().index(index);
541-
logger.debug("[{}] creating index", index);
541+
logger.info("[{}] creating index", index);
542542

543543
AllocatedIndex<? extends Shard> indexService = null;
544544
try {

server/src/main/java/org/opensearch/rest/action/cat/RestIndicesAction.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,6 @@ public RestChannelConsumer doCatRequest(final RestRequest request, final NodeCli
133133
}
134134
final TimeValue clusterManagerNodeTimeout = clusterManagerTimeout;
135135
final boolean includeUnloadedSegments = request.paramAsBoolean("include_unloaded_segments", false);
136-
137136
return channel -> {
138137
final ActionListener<Table> listener = ActionListener.notifyOnce(new RestResponseListener<Table>(channel) {
139138
@Override

server/src/main/java/org/opensearch/rest/action/cat/RestShardsAction.java

Lines changed: 125 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.opensearch.action.admin.indices.stats.IndicesStatsResponse;
4040
import org.opensearch.action.admin.indices.stats.ShardStats;
4141
import org.opensearch.client.node.NodeClient;
42+
import org.opensearch.cluster.routing.IndexShardRoutingTable;
4243
import org.opensearch.cluster.routing.ShardRouting;
4344
import org.opensearch.cluster.routing.UnassignedInfo;
4445
import org.opensearch.common.Table;
@@ -66,8 +67,12 @@
6667
import org.opensearch.search.suggest.completion.CompletionStats;
6768

6869
import java.time.Instant;
70+
import java.util.ArrayList;
71+
import java.util.Base64;
6972
import java.util.List;
7073
import java.util.Locale;
74+
import java.util.Map;
75+
import java.util.Objects;
7176
import java.util.function.Function;
7277

7378
import 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

Comments
 (0)