This is an automated email from the ASF dual-hosted git repository.
capistrant pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git
The following commit(s) were added to refs/heads/master by this push:
new ad106a84bcb feat: Server view improvements for historical detail
(decommissioning, turbo segment loading, cloning) (#19253)
ad106a84bcb is described below
commit ad106a84bcbc410183a5bd3878a4ab4a50a88838
Author: Lucas Capistrant <[email protected]>
AuthorDate: Sat May 2 14:46:35 2026 -0500
feat: Server view improvements for historical detail (decommissioning,
turbo segment loading, cloning) (#19253)
* fix bug where clone status not reset when going from N to 0 clone mappings
* Make the services view show if historical is in any mode like turbo or
decom or clone
* remove outer catch all on advice of review
* prettier fix
* stop mocking where not necessary
this was causing NPEs
* Remove more unneeded mocking of clone status manager
* Remove all caps, add aggregated stats, some tidying up of types and error
reporting
* better casing and nicer error msg
* fix more unneeded mock locations that were very quietly causing the CI
fails.
* uncovered more issues
* checkstyle fix
---
.../server/coordinator/duty/CloneHistoricals.java | 5 +-
.../server/coordinator/DruidCoordinatorTest.java | 35 +-
.../simulate/CoordinatorSimulationBuilder.java | 3 +-
.../__snapshots__/services-view.spec.tsx.snap | 668 +++++++++++----------
.../src/views/services-view/services-view.tsx | 315 +++++++---
5 files changed, 601 insertions(+), 425 deletions(-)
diff --git
a/server/src/main/java/org/apache/druid/server/coordinator/duty/CloneHistoricals.java
b/server/src/main/java/org/apache/druid/server/coordinator/duty/CloneHistoricals.java
index da201127f60..c12193c9b0e 100644
---
a/server/src/main/java/org/apache/druid/server/coordinator/duty/CloneHistoricals.java
+++
b/server/src/main/java/org/apache/druid/server/coordinator/duty/CloneHistoricals.java
@@ -68,7 +68,10 @@ public class CloneHistoricals implements CoordinatorDuty
final DruidCluster cluster = params.getDruidCluster();
if (cloneServers.isEmpty()) {
- // No servers to be cloned.
+ if (!cloneStatusManager.getStatusForAllServers().isEmpty()) {
+ // Clear the status manager if the dynamic config no longer has
mappings to avoid showing stale clone statuses.
+ cloneStatusManager.updateStatus(Map.of());
+ }
return params;
}
diff --git
a/server/src/test/java/org/apache/druid/server/coordinator/DruidCoordinatorTest.java
b/server/src/test/java/org/apache/druid/server/coordinator/DruidCoordinatorTest.java
index d41e91e4474..332e6ffbada 100644
---
a/server/src/test/java/org/apache/druid/server/coordinator/DruidCoordinatorTest.java
+++
b/server/src/test/java/org/apache/druid/server/coordinator/DruidCoordinatorTest.java
@@ -19,7 +19,6 @@
package org.apache.druid.server.coordinator;
-import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
@@ -34,7 +33,6 @@ import org.apache.druid.client.ServerInventoryView;
import org.apache.druid.common.config.JacksonConfigManager;
import org.apache.druid.curator.discovery.LatchableServiceAnnouncer;
import org.apache.druid.discovery.DruidLeaderSelector;
-import org.apache.druid.jackson.DefaultObjectMapper;
import org.apache.druid.java.util.common.Intervals;
import org.apache.druid.java.util.common.concurrent.ScheduledExecutorFactory;
import org.apache.druid.java.util.common.concurrent.ScheduledExecutors;
@@ -96,7 +94,6 @@ public class DruidCoordinatorTest
{
private static final long COORDINATOR_START_DELAY = 1;
private static final long COORDINATOR_PERIOD = 100;
- private static final ObjectMapper OBJECT_MAPPER = new DefaultObjectMapper();
private DruidCoordinator coordinator;
private SegmentsMetadataManager segmentsMetadataManager;
@@ -172,7 +169,7 @@ public class DruidCoordinatorTest
new CompactionStatusTracker(),
EasyMock.niceMock(CoordinatorDynamicConfigSyncer.class),
EasyMock.niceMock(BrokerDynamicConfigSyncer.class),
- EasyMock.niceMock(CloneStatusManager.class)
+ new CloneStatusManager()
);
}
@@ -463,7 +460,7 @@ public class DruidCoordinatorTest
{
EasyMock.expect(segmentsMetadataManager.isPollingDatabasePeriodically())
.andReturn(true).anyTimes();
- EasyMock.replay(segmentsMetadataManager);
+ EasyMock.replay(segmentsMetadataManager, metadataRuleManager);
CoordinatorCustomDutyGroups emptyCustomDutyGroups = new
CoordinatorCustomDutyGroups(ImmutableSet.of());
coordinator = new DruidCoordinator(
@@ -485,7 +482,7 @@ public class DruidCoordinatorTest
new CompactionStatusTracker(),
EasyMock.niceMock(CoordinatorDynamicConfigSyncer.class),
EasyMock.niceMock(BrokerDynamicConfigSyncer.class),
- EasyMock.niceMock(CloneStatusManager.class)
+ new CloneStatusManager()
);
coordinator.start();
@@ -512,7 +509,7 @@ public class DruidCoordinatorTest
{
EasyMock.expect(segmentsMetadataManager.isPollingDatabasePeriodically())
.andReturn(true).anyTimes();
- EasyMock.replay(segmentsMetadataManager);
+ EasyMock.replay(segmentsMetadataManager, metadataRuleManager);
CoordinatorCustomDutyGroup group = new CoordinatorCustomDutyGroup(
"group1",
Duration.standardSeconds(1),
@@ -538,7 +535,7 @@ public class DruidCoordinatorTest
new CompactionStatusTracker(),
EasyMock.niceMock(CoordinatorDynamicConfigSyncer.class),
EasyMock.niceMock(BrokerDynamicConfigSyncer.class),
- EasyMock.niceMock(CloneStatusManager.class)
+ new CloneStatusManager()
);
coordinator.start();
// Since CompactSegments is not enabled in Custom Duty Group, then
CompactSegments must be created in IndexingServiceDuties
@@ -565,7 +562,7 @@ public class DruidCoordinatorTest
{
EasyMock.expect(segmentsMetadataManager.isPollingDatabasePeriodically())
.andReturn(true).anyTimes();
- EasyMock.replay(segmentsMetadataManager);
+ EasyMock.replay(segmentsMetadataManager, metadataRuleManager);
CoordinatorCustomDutyGroup compactSegmentCustomGroup = new
CoordinatorCustomDutyGroup(
"group1",
Duration.standardSeconds(1),
@@ -591,7 +588,7 @@ public class DruidCoordinatorTest
new CompactionStatusTracker(),
EasyMock.niceMock(CoordinatorDynamicConfigSyncer.class),
EasyMock.niceMock(BrokerDynamicConfigSyncer.class),
- EasyMock.niceMock(CloneStatusManager.class)
+ new CloneStatusManager()
);
coordinator.start();
@@ -656,7 +653,7 @@ public class DruidCoordinatorTest
EasyMock.expect(segmentsMetadataManager.isPollingDatabasePeriodically()).andReturn(true).anyTimes();
EasyMock.expect(serverInventoryView.isStarted()).andReturn(true).anyTimes();
EasyMock.expect(serverInventoryView.getInventory()).andReturn(Collections.emptyList()).anyTimes();
- EasyMock.replay(serverInventoryView, loadQueueTaskMaster,
segmentsMetadataManager);
+ EasyMock.replay(serverInventoryView, loadQueueTaskMaster,
segmentsMetadataManager, metadataRuleManager);
// Create CoordinatorCustomDutyGroups
// We will have two groups and each group has one duty
@@ -702,14 +699,18 @@ public class DruidCoordinatorTest
new CompactionStatusTracker(),
EasyMock.niceMock(CoordinatorDynamicConfigSyncer.class),
EasyMock.niceMock(BrokerDynamicConfigSyncer.class),
- EasyMock.niceMock(CloneStatusManager.class)
+ new CloneStatusManager()
);
coordinator.start();
-
- // Wait until group 1 duty ran for latch1 to countdown
- latch1.await();
- // Wait until group 2 duty ran for latch2 to countdown
- latch2.await();
+ try {
+ // Wait until group 1 duty ran for latch1 to countdown
+ latch1.await();
+ // Wait until group 2 duty ran for latch2 to countdown
+ latch2.await();
+ }
+ finally {
+ coordinator.stop();
+ }
}
@Test(timeout = 60_000L)
diff --git
a/server/src/test/java/org/apache/druid/server/coordinator/simulate/CoordinatorSimulationBuilder.java
b/server/src/test/java/org/apache/druid/server/coordinator/simulate/CoordinatorSimulationBuilder.java
index 166728d6d7e..b6e45ad2aea 100644
---
a/server/src/test/java/org/apache/druid/server/coordinator/simulate/CoordinatorSimulationBuilder.java
+++
b/server/src/test/java/org/apache/druid/server/coordinator/simulate/CoordinatorSimulationBuilder.java
@@ -522,11 +522,10 @@ public class CoordinatorSimulationBuilder
this.configSyncer =
EasyMock.niceMock(CoordinatorDynamicConfigSyncer.class);
this.brokerConfigSyncer =
EasyMock.niceMock(BrokerDynamicConfigSyncer.class);
- this.cloneStatusManager = EasyMock.niceMock(CloneStatusManager.class);
+ this.cloneStatusManager = new CloneStatusManager();
mocks.add(configSyncer);
mocks.add(brokerConfigSyncer);
- mocks.add(cloneStatusManager);
}
private void setUp() throws Exception
diff --git
a/web-console/src/views/services-view/__snapshots__/services-view.spec.tsx.snap
b/web-console/src/views/services-view/__snapshots__/services-view.spec.tsx.snap
index afd053ddc75..3f265d48186 100644
---
a/web-console/src/views/services-view/__snapshots__/services-view.spec.tsx.snap
+++
b/web-console/src/views/services-view/__snapshots__/services-view.spec.tsx.snap
@@ -74,340 +74,344 @@ exports[`ServicesView renders data 1`] = `
<Context.Provider
value={{}}
>
- <ReactTable
- AggregatedComponent={[Function]}
- ExpanderComponent={[Function]}
- FilterComponent={[Function]}
- LoadingComponent={[Function]}
- NoDataComponent={[Function]}
- PadRowComponent={[Function]}
- PaginationComponent={[Function]}
- PivotValueComponent={[Function]}
- ResizerComponent={[Function]}
- TableComponent={[Function]}
- TbodyComponent={[Function]}
- TdComponent={[Function]}
- TfootComponent={[Function]}
- ThComponent={[Function]}
- TheadComponent={[Function]}
- TrComponent={[Function]}
- TrGroupComponent={[Function]}
- aggregatedKey="_aggregated"
- className="centered-table -striped -highlight padded-header"
- collapseOnDataChange={true}
- collapseOnPageChange={true}
- collapseOnSortingChange={true}
- column={
- {
- "Aggregated": undefined,
- "Cell": undefined,
- "Expander": undefined,
- "Filter": undefined,
- "Footer": undefined,
- "Header": undefined,
- "Pivot": undefined,
- "PivotValue": undefined,
- "Placeholder": undefined,
- "aggregate": undefined,
- "className": "",
- "filterAll": false,
- "filterMethod": undefined,
- "filterable": undefined,
- "footerClassName": "",
- "footerStyle": {},
- "getFooterProps": [Function],
- "getHeaderProps": [Function],
- "getProps": [Function],
- "headerClassName": "",
- "headerStyle": {},
- "minResizeWidth": 11,
- "minWidth": 100,
- "resizable": undefined,
- "show": true,
- "sortMethod": undefined,
- "sortable": undefined,
- "style": {},
- }
- }
- columns={
- [
- {
- "Aggregated": [Function],
- "Cell": [Function],
- "Header": "Service",
- "accessor": "service",
- "show": true,
- "width": 300,
- },
- {
- "Cell": [Function],
- "Filter": [Function],
- "Header": "Type",
- "accessor": "service_type",
- "show": true,
- "width": 150,
- },
- {
- "Cell": [Function],
- "Header": "Tier",
- "accessor": [Function],
- "id": "tier",
- "show": true,
- "width": 180,
- },
- {
- "Aggregated": [Function],
- "Cell": [Function],
- "Header": "Host",
- "accessor": "host",
- "show": true,
- "width": 200,
- },
- {
- "Aggregated": [Function],
- "Cell": [Function],
- "Header": "Port",
- "accessor": [Function],
- "id": "port",
- "show": true,
- "width": 100,
- },
- {
- "Aggregated": [Function],
- "Cell": [Function],
- "Header": "Assigned size",
- "accessor": "curr_size",
- "className": "padded",
- "filterable": false,
- "id": "curr_size",
- "show": true,
- "width": 100,
- },
- {
- "Aggregated": [Function],
- "Cell": [Function],
- "Header": "Effective size",
- "accessor": "effective_size",
- "className": "padded",
- "filterable": false,
- "id": "effective_size",
- "show": true,
- "width": 100,
- },
- {
- "Aggregated": [Function],
- "Cell": [Function],
- "Header": "Usage",
- "accessor": [Function],
- "className": "padded",
- "filterable": false,
- "id": "usage",
- "show": true,
- "width": 140,
- },
- {
- "Aggregated": [Function],
- "Cell": [Function],
- "Header": "Start time",
- "accessor": "start_time",
- "filterMethod": [Function],
- "id": "start_time",
- "show": true,
- "width": 220,
- },
- {
- "Aggregated": [Function],
- "Cell": [Function],
- "Header": "Version",
- "accessor": "version",
- "show": true,
- "width": 200,
- },
- {
- "Aggregated": [Function],
- "Cell": [Function],
- "Header": <React.Fragment>
- Build
- <br />
- revision
- </React.Fragment>,
- "accessor": "build_revision",
- "show": true,
- "width": 200,
- },
- {
- "Aggregated": [Function],
- "Cell": [Function],
- "Header": <React.Fragment>
- Available
- <br />
- processors
- </React.Fragment>,
- "accessor": "available_processors",
- "className": "padded",
- "filterable": false,
- "show": true,
- "width": 100,
- },
- {
- "Aggregated": [Function],
- "Cell": [Function],
- "Header": "Total memory",
- "accessor": "total_memory",
- "className": "padded",
- "filterable": false,
- "show": true,
- "width": 120,
- },
- {
- "Aggregated": [Function],
- "Cell": [Function],
- "Header": "Labels",
- "accessor": "labels",
- "className": "padded",
- "filterable": false,
- "show": true,
- "width": 200,
- },
- {
- "Aggregated": [Function],
- "Cell": [Function],
- "Header": "Detail",
- "accessor": "service",
- "className": "padded",
- "filterable": false,
- "id": "queue",
- "show": true,
- "width": 400,
- },
- {
- "Aggregated": [Function],
- "Cell": [Function],
- "Header": "Actions",
- "accessor": "service",
- "filterable": false,
- "id": "actions",
- "show": true,
- "sortable": false,
- "width": 70,
- },
- ]
- }
- data={
- [
- [
+ <Context.Provider>
+ <Context.Provider>
+ <ReactTable
+ AggregatedComponent={[Function]}
+ ExpanderComponent={[Function]}
+ FilterComponent={[Function]}
+ LoadingComponent={[Function]}
+ NoDataComponent={[Function]}
+ PadRowComponent={[Function]}
+ PaginationComponent={[Function]}
+ PivotValueComponent={[Function]}
+ ResizerComponent={[Function]}
+ TableComponent={[Function]}
+ TbodyComponent={[Function]}
+ TdComponent={[Function]}
+ TfootComponent={[Function]}
+ ThComponent={[Function]}
+ TheadComponent={[Function]}
+ TrComponent={[Function]}
+ TrGroupComponent={[Function]}
+ aggregatedKey="_aggregated"
+ className="centered-table -striped -highlight padded-header"
+ collapseOnDataChange={true}
+ collapseOnPageChange={true}
+ collapseOnSortingChange={true}
+ column={
{
- "curr_size": 0,
- "host": "localhost",
- "is_leader": 0,
- "max_size": 0,
- "plaintext_port": 8082,
- "service": "localhost:8082",
- "service_type": "broker",
- "start_time": 0,
- "tier": null,
- "tls_port": -1,
- },
+ "Aggregated": undefined,
+ "Cell": undefined,
+ "Expander": undefined,
+ "Filter": undefined,
+ "Footer": undefined,
+ "Header": undefined,
+ "Pivot": undefined,
+ "PivotValue": undefined,
+ "Placeholder": undefined,
+ "aggregate": undefined,
+ "className": "",
+ "filterAll": false,
+ "filterMethod": undefined,
+ "filterable": undefined,
+ "footerClassName": "",
+ "footerStyle": {},
+ "getFooterProps": [Function],
+ "getHeaderProps": [Function],
+ "getProps": [Function],
+ "headerClassName": "",
+ "headerStyle": {},
+ "minResizeWidth": 11,
+ "minWidth": 100,
+ "resizable": undefined,
+ "show": true,
+ "sortMethod": undefined,
+ "sortable": undefined,
+ "style": {},
+ }
+ }
+ columns={
+ [
+ {
+ "Aggregated": [Function],
+ "Cell": [Function],
+ "Header": "Service",
+ "accessor": "service",
+ "show": true,
+ "width": 300,
+ },
+ {
+ "Cell": [Function],
+ "Filter": [Function],
+ "Header": "Type",
+ "accessor": "service_type",
+ "show": true,
+ "width": 150,
+ },
+ {
+ "Cell": [Function],
+ "Header": "Tier",
+ "accessor": [Function],
+ "id": "tier",
+ "show": true,
+ "width": 180,
+ },
+ {
+ "Aggregated": [Function],
+ "Cell": [Function],
+ "Header": "Host",
+ "accessor": "host",
+ "show": true,
+ "width": 200,
+ },
+ {
+ "Aggregated": [Function],
+ "Cell": [Function],
+ "Header": "Port",
+ "accessor": [Function],
+ "id": "port",
+ "show": true,
+ "width": 100,
+ },
+ {
+ "Aggregated": [Function],
+ "Cell": [Function],
+ "Header": "Assigned size",
+ "accessor": "curr_size",
+ "className": "padded",
+ "filterable": false,
+ "id": "curr_size",
+ "show": true,
+ "width": 100,
+ },
+ {
+ "Aggregated": [Function],
+ "Cell": [Function],
+ "Header": "Effective size",
+ "accessor": "effective_size",
+ "className": "padded",
+ "filterable": false,
+ "id": "effective_size",
+ "show": true,
+ "width": 100,
+ },
+ {
+ "Aggregated": [Function],
+ "Cell": [Function],
+ "Header": "Usage",
+ "accessor": [Function],
+ "className": "padded",
+ "filterable": false,
+ "id": "usage",
+ "show": true,
+ "width": 140,
+ },
+ {
+ "Aggregated": [Function],
+ "Cell": [Function],
+ "Header": "Start time",
+ "accessor": "start_time",
+ "filterMethod": [Function],
+ "id": "start_time",
+ "show": true,
+ "width": 220,
+ },
+ {
+ "Aggregated": [Function],
+ "Cell": [Function],
+ "Header": "Version",
+ "accessor": "version",
+ "show": true,
+ "width": 200,
+ },
+ {
+ "Aggregated": [Function],
+ "Cell": [Function],
+ "Header": <React.Fragment>
+ Build
+ <br />
+ revision
+ </React.Fragment>,
+ "accessor": "build_revision",
+ "show": true,
+ "width": 200,
+ },
+ {
+ "Aggregated": [Function],
+ "Cell": [Function],
+ "Header": <React.Fragment>
+ Available
+ <br />
+ processors
+ </React.Fragment>,
+ "accessor": "available_processors",
+ "className": "padded",
+ "filterable": false,
+ "show": true,
+ "width": 100,
+ },
+ {
+ "Aggregated": [Function],
+ "Cell": [Function],
+ "Header": "Total memory",
+ "accessor": "total_memory",
+ "className": "padded",
+ "filterable": false,
+ "show": true,
+ "width": 120,
+ },
+ {
+ "Aggregated": [Function],
+ "Cell": [Function],
+ "Header": "Labels",
+ "accessor": "labels",
+ "className": "padded",
+ "filterable": false,
+ "show": true,
+ "width": 200,
+ },
+ {
+ "Aggregated": [Function],
+ "Cell": [Function],
+ "Header": "Detail",
+ "accessor": "service",
+ "className": "padded wrapped",
+ "filterable": false,
+ "id": "queue",
+ "show": true,
+ "width": 400,
+ },
+ {
+ "Aggregated": [Function],
+ "Cell": [Function],
+ "Header": "Actions",
+ "accessor": "service",
+ "filterable": false,
+ "id": "actions",
+ "show": true,
+ "sortable": false,
+ "width": 70,
+ },
+ ]
+ }
+ data={
+ [
+ [
+ {
+ "curr_size": 0,
+ "host": "localhost",
+ "is_leader": 0,
+ "max_size": 0,
+ "plaintext_port": 8082,
+ "service": "localhost:8082",
+ "service_type": "broker",
+ "start_time": 0,
+ "tier": null,
+ "tls_port": -1,
+ },
+ {
+ "curr_size": 179744287,
+ "host": "localhost",
+ "is_leader": 0,
+ "max_size": 3000000000n,
+ "plaintext_port": 8083,
+ "segmentsToDrop": 0,
+ "segmentsToDropSize": 0,
+ "segmentsToLoad": 0,
+ "segmentsToLoadSize": 0,
+ "service": "localhost:8083",
+ "service_type": "historical",
+ "start_time": 0,
+ "tier": "_default_tier",
+ "tls_port": -1,
+ },
+ ],
+ ]
+ }
+ defaultExpanded={{}}
+ defaultFilterMethod={[Function]}
+ defaultFiltered={[]}
+ defaultPage={0}
+ defaultPageSize={50}
+ defaultResized={[]}
+ defaultSortDesc={false}
+ defaultSortMethod={[Function]}
+ defaultSorted={[]}
+ expanderDefaults={
{
- "curr_size": 179744287,
- "host": "localhost",
- "is_leader": 0,
- "max_size": 3000000000n,
- "plaintext_port": 8083,
- "segmentsToDrop": 0,
- "segmentsToDropSize": 0,
- "segmentsToLoad": 0,
- "segmentsToLoadSize": 0,
- "service": "localhost:8083",
- "service_type": "historical",
- "start_time": 0,
- "tier": "_default_tier",
- "tls_port": -1,
- },
- ],
- ]
- }
- defaultExpanded={{}}
- defaultFilterMethod={[Function]}
- defaultFiltered={[]}
- defaultPage={0}
- defaultPageSize={50}
- defaultResized={[]}
- defaultSortDesc={false}
- defaultSortMethod={[Function]}
- defaultSorted={[]}
- expanderDefaults={
- {
- "filterable": false,
- "resizable": false,
- "sortable": false,
- "width": 35,
- }
- }
- filterable={true}
- filtered={[]}
- freezeWhenExpanded={false}
- getLoadingProps={[Function]}
- getNoDataProps={[Function]}
- getPaginationProps={[Function]}
- getProps={[Function]}
- getResizerProps={[Function]}
- getTableProps={[Function]}
- getTbodyProps={[Function]}
- getTdProps={[Function]}
- getTfootProps={[Function]}
- getTfootTdProps={[Function]}
- getTfootTrProps={[Function]}
- getTheadFilterProps={[Function]}
- getTheadFilterThProps={[Function]}
- getTheadFilterTrProps={[Function]}
- getTheadGroupProps={[Function]}
- getTheadGroupThProps={[Function]}
- getTheadGroupTrProps={[Function]}
- getTheadProps={[Function]}
- getTheadThProps={[Function]}
- getTheadTrProps={[Function]}
- getTrGroupProps={[Function]}
- getTrProps={[Function]}
- groupedByPivotKey="_groupedByPivot"
- indexKey="_index"
- loading={false}
- loadingText="Loading..."
- multiSort={true}
- nestingLevelKey="_nestingLevel"
- nextText="Next"
- noDataText=""
- ofText="of"
- onFetchData={[Function]}
- onFilteredChange={[Function]}
- originalKey="_original"
- pageJumpText="jump to page"
- pageSizeOptions={
- [
- 50,
- 100,
- 200,
- ]
- }
- pageText="Page"
- pivotBy={[]}
- pivotDefaults={{}}
- pivotIDKey="_pivotID"
- pivotValKey="_pivotVal"
- previousText="Previous"
- resizable={true}
- resolveData={[Function]}
- rowsSelectorText="rows per page"
- rowsText="rows"
- showPageJump={true}
- showPageSizeOptions={true}
- showPagination={false}
- showPaginationBottom={true}
- showPaginationTop={false}
- sortable={true}
- style={{}}
- subRowsKey="_subRows"
- />
+ "filterable": false,
+ "resizable": false,
+ "sortable": false,
+ "width": 35,
+ }
+ }
+ filterable={true}
+ filtered={[]}
+ freezeWhenExpanded={false}
+ getLoadingProps={[Function]}
+ getNoDataProps={[Function]}
+ getPaginationProps={[Function]}
+ getProps={[Function]}
+ getResizerProps={[Function]}
+ getTableProps={[Function]}
+ getTbodyProps={[Function]}
+ getTdProps={[Function]}
+ getTfootProps={[Function]}
+ getTfootTdProps={[Function]}
+ getTfootTrProps={[Function]}
+ getTheadFilterProps={[Function]}
+ getTheadFilterThProps={[Function]}
+ getTheadFilterTrProps={[Function]}
+ getTheadGroupProps={[Function]}
+ getTheadGroupThProps={[Function]}
+ getTheadGroupTrProps={[Function]}
+ getTheadProps={[Function]}
+ getTheadThProps={[Function]}
+ getTheadTrProps={[Function]}
+ getTrGroupProps={[Function]}
+ getTrProps={[Function]}
+ groupedByPivotKey="_groupedByPivot"
+ indexKey="_index"
+ loading={false}
+ loadingText="Loading..."
+ multiSort={true}
+ nestingLevelKey="_nestingLevel"
+ nextText="Next"
+ noDataText=""
+ ofText="of"
+ onFetchData={[Function]}
+ onFilteredChange={[Function]}
+ originalKey="_original"
+ pageJumpText="jump to page"
+ pageSizeOptions={
+ [
+ 50,
+ 100,
+ 200,
+ ]
+ }
+ pageText="Page"
+ pivotBy={[]}
+ pivotDefaults={{}}
+ pivotIDKey="_pivotID"
+ pivotValKey="_pivotVal"
+ previousText="Previous"
+ resizable={true}
+ resolveData={[Function]}
+ rowsSelectorText="rows per page"
+ rowsText="rows"
+ showPageJump={true}
+ showPageSizeOptions={true}
+ showPagination={false}
+ showPaginationBottom={true}
+ showPaginationTop={false}
+ sortable={true}
+ style={{}}
+ subRowsKey="_subRows"
+ />
+ </Context.Provider>
+ </Context.Provider>
</Context.Provider>
</div>
`;
diff --git a/web-console/src/views/services-view/services-view.tsx
b/web-console/src/views/services-view/services-view.tsx
index 705a8b6f8f6..a6e41769f6f 100644
--- a/web-console/src/views/services-view/services-view.tsx
+++ b/web-console/src/views/services-view/services-view.tsx
@@ -38,7 +38,7 @@ import {
ViewControlBar,
} from '../../components';
import { AsyncActionDialog, ServiceTableActionDialog } from '../../dialogs';
-import type { QueryWithContext } from '../../druid-models';
+import type { CoordinatorDynamicConfig, QueryWithContext } from
'../../druid-models';
import { getConsoleViewIcon } from '../../druid-models';
import type { Capabilities, CapabilitiesMode } from '../../helpers';
import {
@@ -59,6 +59,7 @@ import {
formatDurationWithMsIfNeeded,
formatInteger,
getApiArray,
+ getApiArrayFromKey,
hasOverlayOpen,
LocalStorageBackedVisibility,
LocalStorageKeys,
@@ -167,13 +168,36 @@ interface ServiceResultRow {
readonly total_memory: number;
}
+interface CloneStatusInfo {
+ readonly sourceServer: string;
+ readonly targetServer: string;
+ readonly state: string;
+ readonly segmentLoadsRemaining: number;
+ readonly segmentDropsRemaining: number;
+ readonly bytesToLoad: number;
+}
+
+interface ServerModeInfo {
+ readonly turboLoadingNodes: Set<string>;
+ readonly decommissioningNodes: Set<string>;
+}
+
interface ServicesWithAuxiliaryInfo {
readonly services: ServiceResultRow[];
readonly loadQueueInfo: Record<string, LoadQueueInfo>;
+ readonly cloneStatus: Record<string, CloneStatusInfo>;
+ readonly serverMode: ServerModeInfo;
readonly workerInfo: Record<string, WorkerInfo>;
}
export const LoadQueueInfoContext = createContext<Record<string,
LoadQueueInfo>>({});
+export const CloneStatusContext = createContext<Record<string,
CloneStatusInfo>>({});
+
+const DEFAULT_SERVER_MODE: ServerModeInfo = {
+ turboLoadingNodes: new Set(),
+ decommissioningNodes: new Set(),
+};
+export const ServerModeContext =
createContext<ServerModeInfo>(DEFAULT_SERVER_MODE);
interface LoadQueueInfo {
readonly segmentsToDrop: NumberLike;
@@ -220,6 +244,140 @@ function aggregateLoadQueueInfos(loadQueueInfos:
LoadQueueInfo[]): LoadQueueInfo
};
}
+interface DetailCellProps {
+ original: ServiceResultRow;
+ workerInfoLookup: Record<string, WorkerInfo>;
+}
+
+function DetailCell({ original, workerInfoLookup }: DetailCellProps) {
+ const { service_type, service, is_leader } = original;
+ const loadQueueInfoContext = useContext(LoadQueueInfoContext);
+ const cloneStatusContext = useContext(CloneStatusContext);
+ const serverModeInfo = useContext(ServerModeContext);
+
+ switch (service_type) {
+ case 'middle_manager':
+ case 'indexer': {
+ const workerInfo = workerInfoLookup[service];
+ if (!workerInfo) return null;
+
+ if (workerInfo.worker.version === '') return <>Disabled</>;
+
+ const details: string[] = [];
+ if (workerInfo.lastCompletedTaskTime) {
+ details.push(`Last completed task:
${formatDate(workerInfo.lastCompletedTaskTime)}`);
+ }
+ if (workerInfo.blacklistedUntil) {
+ details.push(`Blacklisted until:
${formatDate(workerInfo.blacklistedUntil)}`);
+ }
+ return <>{details.join(' ') || null}</>;
+ }
+
+ case 'coordinator':
+ case 'overlord':
+ return <>{is_leader === 1 ? 'Leader' : ''}</>;
+
+ case 'historical': {
+ const loadQueueInfo = loadQueueInfoContext[service];
+ const cloneInfo = cloneStatusContext[service];
+
+ const parts: string[] = [];
+ if (serverModeInfo.decommissioningNodes.has(service)) {
+ parts.push('Decommissioning');
+ }
+ if (serverModeInfo.turboLoadingNodes.has(service)) {
+ parts.push('Turbo Loading');
+ }
+ if (loadQueueInfo) {
+ parts.push(formatLoadQueueInfo(loadQueueInfo));
+ }
+ if (cloneInfo) {
+ if (cloneInfo.state === 'SOURCE_SERVER_MISSING') {
+ parts.push(`Clone of ${cloneInfo.sourceServer} (source missing)`);
+ } else if (cloneInfo.segmentLoadsRemaining > 0 ||
cloneInfo.segmentDropsRemaining > 0) {
+ const details: string[] = [];
+ if (cloneInfo.segmentLoadsRemaining > 0) {
+ details.push(
+ `${pluralIfNeeded(
+ cloneInfo.segmentLoadsRemaining,
+ 'segment',
+ )} to load (${formatBytesCompact(cloneInfo.bytesToLoad)})`,
+ );
+ }
+ if (cloneInfo.segmentDropsRemaining > 0) {
+ details.push(`${pluralIfNeeded(cloneInfo.segmentDropsRemaining,
'segment')} to drop`);
+ }
+ parts.push(`Cloning from ${cloneInfo.sourceServer}:
${details.join(', ')}`);
+ } else {
+ parts.push(`Clone of ${cloneInfo.sourceServer} (synced)`);
+ }
+ }
+ return <>{parts.join('; ') || null}</>;
+ }
+
+ default:
+ return null;
+ }
+}
+
+interface AggregatedDetailCellProps {
+ subRows: { _original: ServiceResultRow }[];
+}
+
+function AggregatedDetailCell({ subRows }: AggregatedDetailCellProps) {
+ const loadQueueInfoContext = useContext(LoadQueueInfoContext);
+ const cloneStatusContext = useContext(CloneStatusContext);
+ const serverModeInfo = useContext(ServerModeContext);
+ const historicalRows = subRows.map(r => r._original).filter(r =>
r.service_type === 'historical');
+ if (!historicalRows.length) return null;
+
+ const parts: string[] = [];
+
+ const decommissioningCount = historicalRows.filter(r =>
+ serverModeInfo.decommissioningNodes.has(r.service),
+ ).length;
+ if (decommissioningCount) {
+ parts.push(`${decommissioningCount} Decommissioning`);
+ }
+
+ const turboLoadingCount = historicalRows.filter(r =>
+ serverModeInfo.turboLoadingNodes.has(r.service),
+ ).length;
+ if (turboLoadingCount) {
+ parts.push(`${turboLoadingCount} Turbo Loading`);
+ }
+
+ const loadQueueInfos: LoadQueueInfo[] = filterMap(
+ historicalRows,
+ r => loadQueueInfoContext[r.service],
+ );
+ if (loadQueueInfos.length) {
+ parts.push(formatLoadQueueInfo(aggregateLoadQueueInfos(loadQueueInfos)));
+ }
+
+ let clonedCount = 0;
+ let cloningCount = 0;
+ let cloningErrorCount = 0;
+ for (const row of historicalRows) {
+ const cloneInfo = cloneStatusContext[row.service];
+ if (!cloneInfo) continue;
+ if (cloneInfo.state === 'SOURCE_SERVER_MISSING') {
+ cloningErrorCount++;
+ } else if (cloneInfo.segmentLoadsRemaining > 0 ||
cloneInfo.segmentDropsRemaining > 0) {
+ cloningCount++;
+ } else {
+ clonedCount++;
+ }
+ }
+ const cloneParts: string[] = [];
+ if (clonedCount) cloneParts.push(`${clonedCount} cloned`);
+ if (cloningCount) cloneParts.push(`${cloningCount} cloning`);
+ if (cloningErrorCount) cloneParts.push(pluralIfNeeded(cloningErrorCount,
'cloning error'));
+ if (cloneParts.length) parts.push(cloneParts.join(', '));
+
+ return <>{parts.join('; ') || null}</>;
+}
+
function defaultDisplayFn(value: any): string {
if (value === undefined || value === null) return '';
return String(value);
@@ -366,6 +524,54 @@ ORDER BY
});
}
+ if (capabilities.hasCoordinatorAccess() &&
visibleColumns.shown('Detail')) {
+ auxiliaryQueries.push(async (servicesWithAuxiliaryInfo, signal) => {
+ const [cloneStatusResp, configResp] = await Promise.all([
+ getApiArrayFromKey<CloneStatusInfo>(
+ '/druid/coordinator/v1/config/cloneStatus',
+ 'cloneStatus',
+ signal,
+ ).catch(() => {
+ AppToaster.show({
+ icon: IconNames.ERROR,
+ intent: Intent.DANGER,
+ message: 'There was an error getting the clone status map',
+ });
+ return [] as CloneStatusInfo[];
+ }),
+ Api.instance
+ .get<CoordinatorDynamicConfig>('/druid/coordinator/v1/config',
{ signal })
+ .then(r => r.data)
+ .catch(() => {
+ AppToaster.show({
+ icon: IconNames.ERROR,
+ intent: Intent.DANGER,
+ message: 'There was an error getting the coordinator
dynamic config',
+ });
+ return null;
+ }),
+ ]);
+
+ const cloneStatusLookup: Record<string, CloneStatusInfo> =
lookupBy(
+ cloneStatusResp,
+ s => s.targetServer,
+ );
+
+ return {
+ ...servicesWithAuxiliaryInfo,
+ cloneStatus: cloneStatusLookup,
+ ...(configResp
+ ? {
+ serverMode: {
+ turboLoadingNodes: new
Set<string>(configResp.turboLoadingNodes || []),
+ decommissioningNodes: new
Set<string>(configResp.decommissioningNodes || []),
+ },
+ }
+ : {}),
+ };
+ });
+ }
+
if (capabilities.hasOverlordAccess()) {
auxiliaryQueries.push(async (servicesWithAuxiliaryInfo, signal) => {
try {
@@ -400,7 +606,13 @@ ORDER BY
}
return new ResultWithAuxiliaryWork<ServicesWithAuxiliaryInfo>(
- { services, loadQueueInfo: {}, workerInfo: {} },
+ {
+ services,
+ loadQueueInfo: {},
+ cloneStatus: {},
+ serverMode: DEFAULT_SERVER_MODE,
+ workerInfo: {},
+ },
auxiliaryQueries,
);
},
@@ -451,30 +663,36 @@ ORDER BY
const { filters, onFiltersChange } = this.props;
const { servicesState, groupServicesBy, visibleColumns } = this.state;
- const { services, loadQueueInfo, workerInfo } = servicesState.data || {
+ const { services, loadQueueInfo, cloneStatus, serverMode, workerInfo } =
servicesState.data || {
services: [],
loadQueueInfo: {},
+ cloneStatus: {},
+ serverMode: DEFAULT_SERVER_MODE,
workerInfo: {},
};
return (
<LoadQueueInfoContext.Provider value={loadQueueInfo}>
- <ReactTable
- data={services}
- loading={servicesState.loading}
- noDataText={
- servicesState.isEmpty() ? 'No services' :
servicesState.getErrorMessage() || ''
- }
- filterable
- filtered={filters.toFilters()}
- className={`centered-table ${DEFAULT_TABLE_CLASS_NAME}`}
- onFilteredChange={filters =>
onFiltersChange(TableFilters.fromFilters(filters))}
- pivotBy={groupServicesBy ? [groupServicesBy] : []}
- defaultPageSize={STANDARD_TABLE_PAGE_SIZE}
- pageSizeOptions={STANDARD_TABLE_PAGE_SIZE_OPTIONS}
- showPagination={services.length > STANDARD_TABLE_PAGE_SIZE}
- columns={this.getTableColumns(visibleColumns, filters,
onFiltersChange, workerInfo)}
- />
+ <CloneStatusContext.Provider value={cloneStatus}>
+ <ServerModeContext.Provider value={serverMode}>
+ <ReactTable
+ data={services}
+ loading={servicesState.loading}
+ noDataText={
+ servicesState.isEmpty() ? 'No services' :
servicesState.getErrorMessage() || ''
+ }
+ filterable
+ filtered={filters.toFilters()}
+ className={`centered-table ${DEFAULT_TABLE_CLASS_NAME}`}
+ onFilteredChange={filters =>
onFiltersChange(TableFilters.fromFilters(filters))}
+ pivotBy={groupServicesBy ? [groupServicesBy] : []}
+ defaultPageSize={STANDARD_TABLE_PAGE_SIZE}
+ pageSizeOptions={STANDARD_TABLE_PAGE_SIZE_OPTIONS}
+ showPagination={services.length > STANDARD_TABLE_PAGE_SIZE}
+ columns={this.getTableColumns(visibleColumns, filters,
onFiltersChange, workerInfo)}
+ />
+ </ServerModeContext.Provider>
+ </CloneStatusContext.Provider>
</LoadQueueInfoContext.Provider>
);
}
@@ -817,61 +1035,12 @@ ORDER BY
id: 'queue',
width: 400,
filterable: false,
- className: 'padded',
+ className: 'padded wrapped',
accessor: 'service',
- Cell: ({ original }) => {
- const { service_type, service, is_leader } = original;
- const loadQueueInfoContext = useContext(LoadQueueInfoContext);
-
- switch (service_type) {
- case 'middle_manager':
- case 'indexer': {
- const workerInfo = workerInfoLookup[service];
- if (!workerInfo) return null;
-
- if (workerInfo.worker.version === '') return 'Disabled';
-
- const details: string[] = [];
- if (workerInfo.lastCompletedTaskTime) {
- details.push(
- `Last completed task:
${formatDate(workerInfo.lastCompletedTaskTime)}`,
- );
- }
- if (workerInfo.blacklistedUntil) {
- details.push(`Blacklisted until:
${formatDate(workerInfo.blacklistedUntil)}`);
- }
- return details.join(' ') || null;
- }
-
- case 'coordinator':
- case 'overlord':
- return is_leader === 1 ? 'Leader' : '';
-
- case 'historical': {
- const loadQueueInfo = loadQueueInfoContext[service];
- if (!loadQueueInfo) return null;
-
- return formatLoadQueueInfo(loadQueueInfo);
- }
-
- default:
- return null;
- }
- },
- Aggregated: ({ subRows }) => {
- const loadQueueInfoContext = useContext(LoadQueueInfoContext);
- const originalRows = subRows.map(r => r._original);
- if (!originalRows.some(r => r.service_type === 'historical'))
return '';
-
- const loadQueueInfos: LoadQueueInfo[] = filterMap(
- originalRows,
- r => loadQueueInfoContext[r.service],
- );
-
- return loadQueueInfos.length
- ? formatLoadQueueInfo(aggregateLoadQueueInfos(loadQueueInfos))
- : '';
- },
+ Cell: ({ original }) => (
+ <DetailCell original={original}
workerInfoLookup={workerInfoLookup} />
+ ),
+ Aggregated: ({ subRows }) => <AggregatedDetailCell subRows={subRows}
/>,
},
{
Header: ACTION_COLUMN_LABEL,
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]