Skip to content

Commit 0e80384

Browse files
Done
1 parent 0cc8b12 commit 0e80384

File tree

3 files changed

+310
-57
lines changed

3 files changed

+310
-57
lines changed

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

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@
3535
import org.elasticsearch.cluster.health.ClusterIndexHealth;
3636
import org.elasticsearch.cluster.metadata.IndexMetaData;
3737
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
38-
import org.elasticsearch.cluster.metadata.MetaData;
3938
import org.elasticsearch.common.Strings;
4039
import org.elasticsearch.common.Table;
40+
import org.elasticsearch.common.collect.ImmutableOpenMap;
4141
import org.elasticsearch.common.settings.Settings;
4242
import org.elasticsearch.common.time.DateFormatter;
4343
import org.elasticsearch.index.Index;
@@ -95,13 +95,8 @@ public RestChannelConsumer doCatRequest(final RestRequest request, final NodeCli
9595
return channel -> client.admin().cluster().state(clusterStateRequest, new RestActionListener<ClusterStateResponse>(channel) {
9696
@Override
9797
public void processResponse(final ClusterStateResponse clusterStateResponse) {
98-
final ClusterState state = clusterStateResponse.getState();
99-
final Index[] concreteIndices = indexNameExpressionResolver.concreteIndices(state, strictExpandIndicesOptions, indices);
100-
// concreteIndices should contain exactly the indices in state.metaData() that were selected by clusterStateRequest using
101-
// IndicesOptions.strictExpand(). We select the indices again here so that they can be displayed in the resulting table
102-
// in the requesting order.
103-
assert concreteIndices.length == state.metaData().getIndices().size();
104-
98+
final ClusterState clusterState = clusterStateResponse.getState();
99+
final IndexMetaData[] indicesMetaData = getOrderedIndexMetaData(indices, clusterState, strictExpandIndicesOptions);
105100
// Indices that were successfully resolved during the cluster state request might be deleted when the subsequent cluster
106101
// health and indices stats requests execute. We have to distinguish two cases:
107102
// 1) the deleted index was explicitly passed as parameter to the /_cat/indices request. In this case we want the subsequent
@@ -111,24 +106,24 @@ public void processResponse(final ClusterStateResponse clusterStateResponse) {
111106
// This behavior can be ensured by letting the cluster health and indices stats requests re-resolve the index names with the
112107
// same indices options that we used for the initial cluster state request (strictExpand). Unfortunately cluster health
113108
// requests hard-code their indices options and the best we can do is apply strictExpand to the indices stats request.
114-
ClusterHealthRequest clusterHealthRequest = Requests.clusterHealthRequest(indices);
109+
final ClusterHealthRequest clusterHealthRequest = Requests.clusterHealthRequest(indices);
115110
clusterHealthRequest.local(request.paramAsBoolean("local", clusterHealthRequest.local()));
111+
116112
client.admin().cluster().health(clusterHealthRequest, new RestActionListener<ClusterHealthResponse>(channel) {
117113
@Override
118114
public void processResponse(final ClusterHealthResponse clusterHealthResponse) {
119-
IndicesStatsRequest indicesStatsRequest = new IndicesStatsRequest();
115+
final IndicesStatsRequest indicesStatsRequest = new IndicesStatsRequest();
120116
indicesStatsRequest.indices(indices);
121117
indicesStatsRequest.indicesOptions(strictExpandIndicesOptions);
122118
indicesStatsRequest.all();
119+
123120
client.admin().indices().stats(indicesStatsRequest, new RestResponseListener<IndicesStatsResponse>(channel) {
124121
@Override
125122
public RestResponse buildResponse(IndicesStatsResponse indicesStatsResponse) throws Exception {
126-
Table tab = buildTable(request, concreteIndices, clusterHealthResponse,
127-
indicesStatsResponse, state.metaData());
123+
final Table tab = buildTable(request, indicesMetaData, clusterHealthResponse, indicesStatsResponse);
128124
return RestTable.buildResponse(tab, channel);
129125
}
130126
});
131-
132127
}
133128
});
134129
}
@@ -388,8 +383,7 @@ protected Table getTableWithHeader(final RestRequest request) {
388383
}
389384

390385
// package private for testing
391-
Table buildTable(RestRequest request, Index[] indices, ClusterHealthResponse response,
392-
IndicesStatsResponse stats, MetaData indexMetaDatas) {
386+
Table buildTable(RestRequest request, IndexMetaData[] indicesMetaData, ClusterHealthResponse response, IndicesStatsResponse stats) {
393387
final String healthParam = request.param("health");
394388
final ClusterHealthStatus status;
395389
if (healthParam != null) {
@@ -400,31 +394,40 @@ Table buildTable(RestRequest request, Index[] indices, ClusterHealthResponse res
400394

401395
Table table = getTableWithHeader(request);
402396

403-
for (final Index index : indices) {
404-
final String indexName = index.getName();
397+
for (IndexMetaData indexMetaData : indicesMetaData) {
398+
final String indexName = indexMetaData.getIndex().getName();
405399
ClusterIndexHealth indexHealth = response.getIndices().get(indexName);
406400
IndexStats indexStats = stats.getIndices().get(indexName);
407-
IndexMetaData indexMetaData = indexMetaDatas.getIndices().get(indexName);
408401
IndexMetaData.State state = indexMetaData.getState();
409402
boolean searchThrottled = IndexSettings.INDEX_SEARCH_THROTTLED.get(indexMetaData.getSettings());
410403

411404
if (status != null) {
412405
if (state == IndexMetaData.State.CLOSE ||
413-
(indexHealth == null && !ClusterHealthStatus.RED.equals(status)) ||
414-
!indexHealth.getStatus().equals(status)) {
406+
(indexHealth == null && false == ClusterHealthStatus.RED.equals(status)) ||
407+
false == indexHealth.getStatus().equals(status)) {
415408
continue;
416409
}
417410
}
418411

419-
final CommonStats primaryStats = indexStats == null ? new CommonStats() : indexStats.getPrimaries();
420-
final CommonStats totalStats = indexStats == null ? new CommonStats() : indexStats.getTotal();
412+
// the index is present in the cluster state but is not returned in the indices stats API
413+
if (indexStats == null) {
414+
// the index stats API is called last, after cluster state and cluster health. If the index stats
415+
// has not resolved the same indices as the initial cluster state call, then the indices might
416+
// have been removed in the meantime or, more likely, are unauthorized. This is because the cluster
417+
// state exposes everything, even unauthorized indices, which are not exposed in APIs.
418+
// We ignore such an index instead of displaying it with an empty stats.
419+
continue;
420+
}
421+
422+
final CommonStats primaryStats = indexStats.getPrimaries();
423+
final CommonStats totalStats = indexStats.getTotal();
421424

422425
table.startRow();
423426
table.addCell(state == IndexMetaData.State.OPEN ?
424427
(indexHealth == null ? "red*" : indexHealth.getStatus().toString().toLowerCase(Locale.ROOT)) : null);
425428
table.addCell(state.toString().toLowerCase(Locale.ROOT));
426429
table.addCell(indexName);
427-
table.addCell(index.getUUID());
430+
table.addCell(indexMetaData.getIndexUUID());
428431
table.addCell(indexHealth == null ? null : indexHealth.getNumberOfShards());
429432
table.addCell(indexHealth == null ? null : indexHealth.getNumberOfReplicas());
430433

@@ -606,8 +609,8 @@ Table buildTable(RestRequest request, Index[] indices, ClusterHealthResponse res
606609
table.addCell(totalStats.getSearch() == null ? null : totalStats.getSearch().getTotal().getSuggestCount());
607610
table.addCell(primaryStats.getSearch() == null ? null : primaryStats.getSearch().getTotal().getSuggestCount());
608611

609-
table.addCell(indexStats == null ? null : indexStats.getTotal().getTotalMemory());
610-
table.addCell(indexStats == null ? null : indexStats.getPrimaries().getTotalMemory());
612+
table.addCell(indexStats.getTotal().getTotalMemory());
613+
table.addCell(indexStats.getPrimaries().getTotalMemory());
611614

612615
table.addCell(searchThrottled);
613616

@@ -616,4 +619,21 @@ Table buildTable(RestRequest request, Index[] indices, ClusterHealthResponse res
616619

617620
return table;
618621
}
622+
623+
// package private for testing
624+
IndexMetaData[] getOrderedIndexMetaData(String[] indicesExpression, ClusterState clusterState, IndicesOptions indicesOptions) {
625+
final Index[] concreteIndices = indexNameExpressionResolver.concreteIndices(clusterState, indicesOptions, indicesExpression);
626+
// concreteIndices should contain exactly the indices in state.metaData() that were selected by clusterStateRequest using the
627+
// same indices option (IndicesOptions.strictExpand()). We select the indices again here so that they can be displayed in the
628+
// resulting table in the requesting order.
629+
assert concreteIndices.length == clusterState.metaData().getIndices().size();
630+
final ImmutableOpenMap<String, IndexMetaData> indexMetaDataMap = clusterState.metaData().getIndices();
631+
final IndexMetaData[] indicesMetaData = new IndexMetaData[concreteIndices.length];
632+
// select the index metadata in the requested order, so that the response can display the indices in the resulting table
633+
// in the requesting order.
634+
for (int i = 0; i < concreteIndices.length; i++) {
635+
indicesMetaData[i] = indexMetaDataMap.get(concreteIndices[i].getName());
636+
}
637+
return indicesMetaData;
638+
}
619639
}

server/src/test/java/org/elasticsearch/rest/action/cat/RestIndicesActionTests.java

Lines changed: 65 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse;
2626
import org.elasticsearch.action.admin.indices.stats.IndicesStatsTests;
2727
import org.elasticsearch.action.admin.indices.stats.ShardStats;
28+
import org.elasticsearch.action.support.IndicesOptions;
2829
import org.elasticsearch.cluster.ClusterName;
2930
import org.elasticsearch.cluster.ClusterState;
3031
import org.elasticsearch.cluster.metadata.IndexMetaData;
@@ -38,7 +39,6 @@
3839
import org.elasticsearch.common.UUIDs;
3940
import org.elasticsearch.common.settings.Settings;
4041
import org.elasticsearch.common.unit.TimeValue;
41-
import org.elasticsearch.index.Index;
4242
import org.elasticsearch.index.cache.query.QueryCacheStats;
4343
import org.elasticsearch.index.cache.request.RequestCacheStats;
4444
import org.elasticsearch.index.engine.SegmentsStats;
@@ -60,6 +60,7 @@
6060
import org.elasticsearch.test.rest.FakeRestRequest;
6161
import org.elasticsearch.usage.UsageService;
6262

63+
import java.lang.reflect.Array;
6364
import java.nio.file.Path;
6465
import java.util.ArrayList;
6566
import java.util.Collections;
@@ -73,44 +74,61 @@
7374
*/
7475
public class RestIndicesActionTests extends ESTestCase {
7576

76-
public void testBuildTable() {
77-
final Settings settings = Settings.EMPTY;
78-
UsageService usageService = new UsageService();
79-
final RestController restController = new RestController(Collections.emptySet(), null, null, null, usageService);
80-
final RestIndicesAction action = new RestIndicesAction(settings, restController, new IndexNameExpressionResolver());
81-
77+
private IndexMetaData[] buildRandomIndicesMetaData(int numIndices) {
8278
// build a (semi-)random table
83-
final int numIndices = randomIntBetween(0, 5);
84-
Index[] indices = new Index[numIndices];
79+
final IndexMetaData[] indicesMetaData = new IndexMetaData[numIndices];
8580
for (int i = 0; i < numIndices; i++) {
86-
indices[i] = new Index(randomAlphaOfLength(5), UUIDs.randomBase64UUID());
87-
}
88-
89-
final MetaData.Builder metaDataBuilder = MetaData.builder();
90-
for (final Index index : indices) {
91-
metaDataBuilder.put(IndexMetaData.builder(index.getName())
81+
indicesMetaData[i] = IndexMetaData.builder(randomAlphaOfLength(5) + i)
9282
.settings(Settings.builder()
93-
.put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT)
94-
.put(IndexMetaData.SETTING_INDEX_UUID, index.getUUID()))
83+
.put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT)
84+
.put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID()))
9585
.creationDate(System.currentTimeMillis())
9686
.numberOfShards(1)
9787
.numberOfReplicas(1)
98-
.state(IndexMetaData.State.OPEN));
88+
.state(IndexMetaData.State.OPEN)
89+
.build();
9990
}
100-
final MetaData metaData = metaDataBuilder.build();
91+
return indicesMetaData;
92+
}
10193

94+
private ClusterState buildClusterState(IndexMetaData[] indicesMetaData) {
95+
final MetaData.Builder metaDataBuilder = MetaData.builder();
96+
for (IndexMetaData indexMetaData : indicesMetaData) {
97+
metaDataBuilder.put(indexMetaData, false);
98+
}
99+
final MetaData metaData = metaDataBuilder.build();
102100
final ClusterState clusterState = ClusterState.builder(ClusterName.CLUSTER_NAME_SETTING.getDefault(Settings.EMPTY))
103101
.metaData(metaData)
104102
.build();
105-
final String[] indicesStr = new String[indices.length];
106-
for (int i = 0; i < indices.length; i++) {
107-
indicesStr[i] = indices[i].getName();
103+
return clusterState;
104+
}
105+
106+
private ClusterHealthResponse buildClusterHealthResponse(ClusterState clusterState, IndexMetaData[] indicesMetaData) {
107+
final String[] indicesStr = new String[indicesMetaData.length];
108+
for (int i = 0; i < indicesMetaData.length; i++) {
109+
indicesStr[i] = indicesMetaData[i].getIndex().getName();
108110
}
109-
final ClusterHealthResponse clusterHealth = new ClusterHealthResponse(
111+
final ClusterHealthResponse clusterHealthResponse = new ClusterHealthResponse(
110112
clusterState.getClusterName().value(), indicesStr, clusterState, 0, 0, 0, TimeValue.timeValueMillis(1000L)
111113
);
114+
return clusterHealthResponse;
115+
}
116+
117+
public void testBuildTable() {
118+
final Settings settings = Settings.EMPTY;
119+
UsageService usageService = new UsageService();
120+
final RestController restController = new RestController(Collections.emptySet(), null, null, null, usageService);
121+
final RestIndicesAction action = new RestIndicesAction(settings, restController, new IndexNameExpressionResolver());
112122

113-
final Table table = action.buildTable(new FakeRestRequest(), indices, clusterHealth, randomIndicesStatsResponse(indices), metaData);
123+
final IndexMetaData[] generatedIndicesMetaData = buildRandomIndicesMetaData(randomIntBetween(1, 5));
124+
final ClusterState clusterState = buildClusterState(generatedIndicesMetaData);
125+
final ClusterHealthResponse clusterHealthResponse = buildClusterHealthResponse(clusterState, generatedIndicesMetaData);
126+
127+
final IndexMetaData[] sortedIndicesMetaData = action.getOrderedIndexMetaData(new String[0], clusterState,
128+
IndicesOptions.strictExpand());
129+
final IndexMetaData[] smallerSortedIndicesMetaData = removeRandomElement(sortedIndicesMetaData);
130+
final Table table = action.buildTable(new FakeRestRequest(), sortedIndicesMetaData, clusterHealthResponse,
131+
randomIndicesStatsResponse(smallerSortedIndicesMetaData));
114132

115133
// now, verify the table is correct
116134
int count = 0;
@@ -121,27 +139,27 @@ public void testBuildTable() {
121139
assertThat(headers.get(count++).value, equalTo("uuid"));
122140

123141
List<List<Table.Cell>> rows = table.getRows();
124-
assertThat(rows.size(), equalTo(indices.length));
142+
assertThat(rows.size(), equalTo(smallerSortedIndicesMetaData.length));
125143
// TODO: more to verify (e.g. randomize cluster health, num primaries, num replicas, etc)
126144
for (int i = 0; i < rows.size(); i++) {
127145
count = 0;
128146
final List<Table.Cell> row = rows.get(i);
129147
assertThat(row.get(count++).value, equalTo("red*")); // all are red because cluster state doesn't have routing entries
130148
assertThat(row.get(count++).value, equalTo("open")); // all are OPEN for now
131-
assertThat(row.get(count++).value, equalTo(indices[i].getName()));
132-
assertThat(row.get(count++).value, equalTo(indices[i].getUUID()));
149+
assertThat(row.get(count++).value, equalTo(smallerSortedIndicesMetaData[i].getIndex().getName()));
150+
assertThat(row.get(count++).value, equalTo(smallerSortedIndicesMetaData[i].getIndexUUID()));
133151
}
134152
}
135153

136-
private IndicesStatsResponse randomIndicesStatsResponse(final Index[] indices) {
154+
private IndicesStatsResponse randomIndicesStatsResponse(final IndexMetaData[] indices) {
137155
List<ShardStats> shardStats = new ArrayList<>();
138-
for (final Index index : indices) {
139-
int numShards = randomInt(5);
156+
for (final IndexMetaData index : indices) {
157+
int numShards = randomIntBetween(1, 3);
140158
int primaryIdx = randomIntBetween(-1, numShards - 1); // -1 means there is no primary shard.
141159
for (int i = 0; i < numShards; i++) {
142-
ShardId shardId = new ShardId(index, i);
160+
ShardId shardId = new ShardId(index.getIndex(), i);
143161
boolean primary = (i == primaryIdx);
144-
Path path = createTempDir().resolve("indices").resolve(index.getUUID()).resolve(String.valueOf(i));
162+
Path path = createTempDir().resolve("indices").resolve(index.getIndexUUID()).resolve(String.valueOf(i));
145163
ShardRouting shardRouting = ShardRouting.newUnassigned(shardId, primary,
146164
primary ? RecoverySource.EmptyStoreRecoverySource.INSTANCE : PeerRecoverySource.INSTANCE,
147165
new UnassignedInfo(UnassignedInfo.Reason.INDEX_CREATED, null)
@@ -170,4 +188,19 @@ private IndicesStatsResponse randomIndicesStatsResponse(final Index[] indices) {
170188
shardStats.toArray(new ShardStats[shardStats.size()]), shardStats.size(), shardStats.size(), 0, emptyList()
171189
);
172190
}
191+
192+
private <T> T[] removeRandomElement(T[] array) {
193+
assert array != null;
194+
assert array.length > 0;
195+
@SuppressWarnings("unchecked")
196+
final T[] smallerArray = (T[]) Array.newInstance(array.getClass().getComponentType(), array.length - 1);
197+
final int index = randomIntBetween(0, array.length - 1);
198+
if (index > 0) {
199+
System.arraycopy(array, 0, smallerArray, 0, index);
200+
}
201+
if (index < array.length - 1) {
202+
System.arraycopy(array, index + 1, smallerArray, index, array.length - index - 1);
203+
}
204+
return smallerArray;
205+
}
173206
}

0 commit comments

Comments
 (0)