From fc1aa0c2cd72512df41f1dac4a53431c2cd6231d Mon Sep 17 00:00:00 2001 From: pswaao88 Date: Fri, 21 Nov 2025 18:42:49 +0900 Subject: [PATCH 1/5] Add tier_preference and node.role columns to _cat/shards API --- docs/changelog/138405.yaml | 5 +++++ .../rest/action/cat/RestShardsAction.java | 17 ++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 docs/changelog/138405.yaml diff --git a/docs/changelog/138405.yaml b/docs/changelog/138405.yaml new file mode 100644 index 0000000000000..ea929c417b27d --- /dev/null +++ b/docs/changelog/138405.yaml @@ -0,0 +1,5 @@ +pr: 138405 +summary: Add tier_preference and node.role columns to _cat/shards API +area: Data Management +type: enhancement +issues: [136895] 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..b48f8736a9352 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; @@ -261,7 +263,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 +437,15 @@ 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( + getOrNull( + state.getState().metadata().findIndex(shard.index()).orElse(null), + IndexMetadata::getSettings, + s -> s.get("index.routing.allocation.include._tier_preference") + ) + ); + table.addCell(getOrNull(state.getState().nodes().get(shard.currentNodeId()), DiscoveryNode::getRoleAbbreviationString, s -> s)); + table.endRow(); } From 93daa6b615f8a9bdef117d46d2445eb15dc1d439 Mon Sep 17 00:00:00 2001 From: pswaao88 Date: Sat, 22 Nov 2025 00:28:21 +0900 Subject: [PATCH 2/5] fix: resolve CI failure by handling null --- .../rest/action/cat/RestShardsAction.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) 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 b48f8736a9352..9661fb297fd2e 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 @@ -9,6 +9,8 @@ package org.elasticsearch.rest.action.cat; +import java.util.Optional; + import org.elasticsearch.action.admin.cluster.state.ClusterStateRequest; import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse; import org.elasticsearch.action.admin.indices.stats.CommonStats; @@ -438,14 +440,19 @@ Table buildTable(RestRequest request, ClusterStateResponse state, IndicesStatsRe table.addCell(getOrNull(commonStats, CommonStats::getSparseVectorStats, SparseVectorStats::getValueCount)); table.addCell( - getOrNull( + 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(getOrNull( + state.getState().nodes().get(shard.currentNodeId()), + DiscoveryNode::getRoleAbbreviationString, + s -> s + )).orElse("-") ); - table.addCell(getOrNull(state.getState().nodes().get(shard.currentNodeId()), DiscoveryNode::getRoleAbbreviationString, s -> s)); - table.endRow(); } From db16b70eb3d59a4e83ba51f97b68e383cdcbd7f2 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Fri, 21 Nov 2025 16:01:23 +0000 Subject: [PATCH 3/5] [CI] Auto commit changes from spotless --- .../rest/action/cat/RestShardsAction.java | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) 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 9661fb297fd2e..b2ba5b177253d 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 @@ -9,8 +9,6 @@ package org.elasticsearch.rest.action.cat; -import java.util.Optional; - import org.elasticsearch.action.admin.cluster.state.ClusterStateRequest; import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse; import org.elasticsearch.action.admin.indices.stats.CommonStats; @@ -55,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; @@ -440,18 +439,18 @@ Table buildTable(RestRequest request, ClusterStateResponse state, IndicesStatsRe 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("") + 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(getOrNull( - state.getState().nodes().get(shard.currentNodeId()), - DiscoveryNode::getRoleAbbreviationString, - s -> s - )).orElse("-") + Optional.ofNullable( + getOrNull(state.getState().nodes().get(shard.currentNodeId()), DiscoveryNode::getRoleAbbreviationString, s -> s) + ).orElse("-") ); table.endRow(); } From 96611d31a27c7d508d9cd0a8cb77d684ffda06b7 Mon Sep 17 00:00:00 2001 From: pswaao88 Date: Sun, 23 Nov 2025 17:22:10 +0900 Subject: [PATCH 4/5] Fix: Resolve BWC NPE, update tests, and fix changelog --- docs/changelog/138405.yaml | 2 +- .../test/cat.shards/10_basic.yml | 8 ++-- .../rest/action/cat/RestShardsAction.java | 24 +++++----- .../action/cat/RestShardsActionTests.java | 45 +++++++++++++++++++ 4 files changed, 63 insertions(+), 16 deletions(-) diff --git a/docs/changelog/138405.yaml b/docs/changelog/138405.yaml index ea929c417b27d..45c38eb700451 100644 --- a/docs/changelog/138405.yaml +++ b/docs/changelog/138405.yaml @@ -1,5 +1,5 @@ pr: 138405 summary: Add tier_preference and node.role columns to _cat/shards API -area: Data Management +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 9661fb297fd2e..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 @@ -9,8 +9,6 @@ package org.elasticsearch.rest.action.cat; -import java.util.Optional; - import org.elasticsearch.action.admin.cluster.state.ClusterStateRequest; import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse; import org.elasticsearch.action.admin.indices.stats.CommonStats; @@ -55,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; @@ -440,18 +439,19 @@ Table buildTable(RestRequest request, ClusterStateResponse state, IndicesStatsRe 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("") + 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(getOrNull( - state.getState().nodes().get(shard.currentNodeId()), - DiscoveryNode::getRoleAbbreviationString, - s -> s - )).orElse("-") + 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); + } + } + } From 558125f6cfa9290c0a28b477fb2d92ad68e27ac5 Mon Sep 17 00:00:00 2001 From: pswaao88 Date: Wed, 26 Nov 2025 14:50:14 +0900 Subject: [PATCH 5/5] Meta: Adjust changelog issue format to follow convention --- docs/changelog/138405.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/changelog/138405.yaml b/docs/changelog/138405.yaml index 45c38eb700451..ada4ffb2be2b0 100644 --- a/docs/changelog/138405.yaml +++ b/docs/changelog/138405.yaml @@ -2,4 +2,5 @@ pr: 138405 summary: Add tier_preference and node.role columns to _cat/shards API area: CAT APIs type: enhancement -issues: [136895] +issues: + - 136895