diff --git a/docs/changelog/138405.yaml b/docs/changelog/138405.yaml new file mode 100644 index 0000000000000..ada4ffb2be2b0 --- /dev/null +++ b/docs/changelog/138405.yaml @@ -0,0 +1,6 @@ +pr: 138405 +summary: Add tier_preference and node.role columns to _cat/shards API +area: CAT APIs +type: enhancement +issues: + - 136895 diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/cat.shards/10_basic.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/cat.shards/10_basic.yml index 45f381eab80b1..899cf90a224c1 100644 --- a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/cat.shards/10_basic.yml +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/cat.shards/10_basic.yml @@ -88,6 +88,8 @@ bulk.avg_size_in_bytes .+ \n dense_vector.value_count .+ \n sparse_vector.value_count .+ \n + tier_preference .+ \n + node.role .+ \n $/ --- "Test cat shards output": @@ -115,7 +117,7 @@ - match: $body: | - /^(index1 \s+ \d \s+ (p|r) \s+ ((STARTED|INITIALIZING|RELOCATING) \s+ (\d \s+ (\d+|\d+[.]\d+)(kb|b) \s+ (\d+|\d+[.]\d+)(kb|b) \s+)? \d{1,3}.\d{1,3}.\d{1,3}.\d{1,3} \s+ .+|UNASSIGNED \s+) \n?){10}$/ + /^(index1 \s+ \d \s+ (p|r) \s+ ((STARTED|INITIALIZING|RELOCATING) \s+ (\d \s+ (\d+|\d+[.]\d+)(kb|b) \s+ (\d+|\d+[.]\d+)(kb|b) \s+)? \d{1,3}.\d{1,3}.\d{1,3}.\d{1,3} \s+ .+|UNASSIGNED \s+ \s+ \S* \s+ \S*) \n?){10}$/ - do: indices.create: @@ -130,14 +132,14 @@ index: i* - match: $body: | - /^(index(1|2) \s+ \d \s+ (p|r) \s+ ((STARTED|INITIALIZING|RELOCATING) \s+ (\d \s+ (\d+|\d+[.]\d+)(kb|b) \s+ (\d+|\d+[.]\d+)(kb|b) \s+)? \d{1,3}.\d{1,3}.\d{1,3}.\d{1,3} \s+ .+|UNASSIGNED \s+) \n?){15}$/ + /^(index(1|2) \s+ \d \s+ (p|r) \s+ ((STARTED|INITIALIZING|RELOCATING) \s+ (\d \s+ (\d+|\d+[.]\d+)(kb|b) \s+ (\d+|\d+[.]\d+)(kb|b) \s+)? \d{1,3}.\d{1,3}.\d{1,3}.\d{1,3} \s+ .+|UNASSIGNED \s+ \s+ \S* \s+ \S*) \n?){15}$/ - do: cat.shards: index: index2 - match: $body: | - /^(index2 \s+ \d \s+ (p|r) \s+ ((STARTED|INITIALIZING|RELOCATING) \s+ (\d \s+ (\d+|\d+[.]\d+)(kb|b) \s+ (\d+|\d+[.]\d+)(kb|b) \s+)? \d{1,3}.\d{1,3}.\d{1,3}.\d{1,3} \s+ .+|UNASSIGNED \s+) \n?){5}$/ + /^(index2 \s+ \d \s+ (p|r) \s+ ((STARTED|INITIALIZING|RELOCATING) \s+ (\d \s+ (\d+|\d+[.]\d+)(kb|b) \s+ (\d+|\d+[.]\d+)(kb|b) \s+)? \d{1,3}.\d{1,3}.\d{1,3}.\d{1,3} \s+ .+|UNASSIGNED \s+ \s+ \S* \s+ \S*) \n?){5}$/ --- "Test cat shards using wildcards": diff --git a/server/src/main/java/org/elasticsearch/rest/action/cat/RestShardsAction.java b/server/src/main/java/org/elasticsearch/rest/action/cat/RestShardsAction.java index 717e6e2d8f35b..f8c192d305c25 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/cat/RestShardsAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/cat/RestShardsAction.java @@ -17,6 +17,8 @@ import org.elasticsearch.action.admin.indices.stats.ShardStats; import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.client.internal.node.NodeClient; +import org.elasticsearch.cluster.metadata.IndexMetadata; +import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.routing.ShardRouting; import org.elasticsearch.cluster.routing.UnassignedInfo; import org.elasticsearch.common.Strings; @@ -51,6 +53,7 @@ import java.time.Instant; import java.util.List; import java.util.Locale; +import java.util.Optional; import java.util.function.Function; import static org.elasticsearch.rest.RestRequest.Method.GET; @@ -261,7 +264,11 @@ protected Table getTableWithHeader(final RestRequest request) { "sparse_vector.value_count", "alias:svc,sparseVectorCount;default:false;text-align:right;desc:number of indexed sparse vectors in shard" ); - + table.addCell("tier_preference", "alias:tp;desc:preference for the tier of the index"); + table.addCell( + "node.role", + "alias:r,role,nodeRole;desc:m:master eligible node, d:data node, i:ingest node, -:coordinating node only" + ); table.endHeaders(); return table; } @@ -431,6 +438,21 @@ Table buildTable(RestRequest request, ClusterStateResponse state, IndicesStatsRe table.addCell(getOrNull(commonStats, CommonStats::getDenseVectorStats, DenseVectorStats::getValueCount)); table.addCell(getOrNull(commonStats, CommonStats::getSparseVectorStats, SparseVectorStats::getValueCount)); + table.addCell( + Optional.ofNullable( + getOrNull( + state.getState().metadata().findIndex(shard.index()).orElse(null), + IndexMetadata::getSettings, + s -> s.get("index.routing.allocation.include._tier_preference") + ) + ).orElse("") + ); + table.addCell( + Optional.ofNullable(shard.currentNodeId()) + .map(id -> state.getState().nodes().get(id)) + .map(DiscoveryNode::getRoleAbbreviationString) + .orElse("-") + ); table.endRow(); } diff --git a/server/src/test/java/org/elasticsearch/rest/action/cat/RestShardsActionTests.java b/server/src/test/java/org/elasticsearch/rest/action/cat/RestShardsActionTests.java index 47e9b5fd04324..873241c92f35b 100644 --- a/server/src/test/java/org/elasticsearch/rest/action/cat/RestShardsActionTests.java +++ b/server/src/test/java/org/elasticsearch/rest/action/cat/RestShardsActionTests.java @@ -9,12 +9,15 @@ package org.elasticsearch.rest.action.cat; +import org.elasticsearch.Version; import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse; import org.elasticsearch.action.admin.indices.stats.CommonStats; import org.elasticsearch.action.admin.indices.stats.CommonStatsFlags; import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse; import org.elasticsearch.action.admin.indices.stats.ShardStats; import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.metadata.IndexMetadata; +import org.elasticsearch.cluster.metadata.Metadata; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.node.DiscoveryNodeUtils; import org.elasticsearch.cluster.node.DiscoveryNodes; @@ -23,6 +26,7 @@ import org.elasticsearch.cluster.routing.ShardRoutingState; import org.elasticsearch.cluster.routing.TestShardRouting; import org.elasticsearch.common.Table; +import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.shard.IndexingStats; import org.elasticsearch.index.shard.ShardPath; import org.elasticsearch.test.ESTestCase; @@ -34,8 +38,11 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Optional; import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -128,6 +135,13 @@ private void mockShardStats(boolean includeCommonStats) { this.shardRoutings = new ArrayList<>(numShards); Map shardStatsMap = new HashMap<>(); String index = "index"; + Metadata metadata = mock(Metadata.class); + IndexMetadata indexMetadata = IndexMetadata.builder(index) + .settings(Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT)) + .numberOfShards(1) + .numberOfReplicas(1) + .build(); + when(metadata.findIndex(any())).thenReturn(Optional.of(indexMetadata)); for (int i = 0; i < numShards; i++) { ShardRoutingState shardRoutingState = ShardRoutingState.fromValue((byte) randomIntBetween(2, 3)); ShardRouting shardRouting = TestShardRouting.newShardRouting(index, i, localNode.getId(), randomBoolean(), shardRoutingState); @@ -186,6 +200,37 @@ private void mockShardStats(boolean includeCommonStats) { ClusterState clusterState = mock(ClusterState.class); when(clusterState.routingTable()).thenReturn(routingTable); when(clusterState.nodes()).thenReturn(discoveryNodes); + when(clusterState.metadata()).thenReturn(metadata); when(clusterStateResponse.getState()).thenReturn(clusterState); } + + public void testNewColumnsAndNullSafety() { + mockShardStats(false); + + final RestShardsAction action = new RestShardsAction(); + final Table table = action.buildTable(new FakeRestRequest(), clusterStateResponse, indicesStatsResponse); + + List headers = table.getHeaders(); + + int tierPreferenceIndex = -1; + int nodeRoleIndex = -1; + + for (int i = 0; i < headers.size(); i++) { + if ("tier_preference".equals(headers.get(i).value)) { + tierPreferenceIndex = i; + } + if ("node.role".equals(headers.get(i).value)) { + nodeRoleIndex = i; + } + } + + final List> rows = table.getRows(); + assertThat(rows.size(), equalTo(shardRoutings.size())); + + for (final List row : rows) { + assertThat(row.get(tierPreferenceIndex).value, equalTo("")); + assertNotNull(row.get(nodeRoleIndex).value); + } + } + }