diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/ContainerEndpoint.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/ContainerEndpoint.java index 0832700e9604..ce8cc7058942 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/ContainerEndpoint.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/ContainerEndpoint.java @@ -242,6 +242,7 @@ public Response getReplicaHistoryForContainer( */ @GET @Path("/missing") + @Deprecated public Response getMissingContainers( @DefaultValue(DEFAULT_FETCH_COUNT) @QueryParam(RECON_QUERY_LIMIT) int limit diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/api/db.json b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/api/db.json index 04036416f413..1639302e23c5 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/api/db.json +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/api/db.json @@ -1260,5 +1260,333 @@ "lastUpdatedTimestamp": 1663421094507, "lastUpdatedSeqNumber": 0 } - ] + ], + "containerUnhealthyMissing": { + "missingCount": 0, + "underReplicatedCount": 1, + "overReplicatedCount": 0, + "misReplicatedCount": 0, + "containers": [ + { + "containerID": 1, + "containerState": "MISSING", + "unhealthySince": 1665590446222, + "expectedReplicaCount": 3, + "actualReplicaCount": 0, + "replicaDeltaCount": 3, + "reason": null, + "keys": 1, + "pipelineID": "a10ffab6-8ed5-414a-aaf5-79890ff3e8a1", + "replicas": [ + { + "containerId": 2, + "datanodeUuid": "15526f1b-76f2-4d8f-876c-c343c94ea476", + "datanodeHost": "ozone_datanode_2.ozone_missing1", + "firstSeenTime": 1665588176660, + "lastSeenTime": 1665590397315, + "lastBcsId": 2 + }, + { + "containerId": 2, + "datanodeUuid": "f55476ab-4687-464d-a100-1c65de4366e3", + "datanodeHost": "ozone_datanode_3.ozone_missing2", + "firstSeenTime": 1665588176616, + "lastSeenTime": 1665590392293, + "lastBcsId": 2 + }, + { + "containerId": 2, + "datanodeUuid": "7a457bcb-d63e-49cc-b3ff-8b22bf48d130", + "datanodeHost": "ozone_datanode_1.ozone_missing3", + "firstSeenTime": 1665588176660, + "lastSeenTime": 1665590272289, + "lastBcsId": 0 + } + ] + }, + { + "containerID": 2, + "containerState": "MISSING", + "unhealthySince": 1665590446222, + "expectedReplicaCount": 3, + "actualReplicaCount": 0, + "replicaDeltaCount": 3, + "reason": null, + "keys": 1, + "pipelineID": "a10ffab6-8ed5-414a-aaf5-79890ff3e8a1", + "replicas": [ + { + "containerId": 3, + "datanodeUuid": "15526f1b-76f2-4d8f-876c-c343c94ea476", + "datanodeHost": "ozone_datanode_2.ozone_missing1", + "firstSeenTime": 1665588176660, + "lastSeenTime": 1665590397315, + "lastBcsId": 2 + }, + { + "containerId": 3, + "datanodeUuid": "f55476ab-4687-464d-a100-1c65de4366e3", + "datanodeHost": "ozone_datanode_3.ozone_missing2", + "firstSeenTime": 1665588176616, + "lastSeenTime": 1665590392293, + "lastBcsId": 2 + }, + { + "containerId": 3, + "datanodeUuid": "7a457bcb-d63e-49cc-b3ff-8b22bf48d130", + "datanodeHost": "ozone_datanode_1.ozone_missing3", + "firstSeenTime": 1665588176660, + "lastSeenTime": 1665590272289, + "lastBcsId": 0 + } + ] + } + ] + }, + "containerUnhealthyUnderReplicated": { + "missingCount": 0, + "underReplicatedCount": 1, + "overReplicatedCount": 0, + "misReplicatedCount": 0, + "containers": [ + { + "containerID": 2, + "containerState": "UNDER_REPLICATED", + "unhealthySince": 1665590446222, + "expectedReplicaCount": 3, + "actualReplicaCount": 2, + "replicaDeltaCount": 1, + "reason": null, + "keys": 1, + "pipelineID": "a10ffab6-8ed5-414a-aaf5-79890ff3e8a1", + "replicas": [ + { + "containerId": 2, + "datanodeUuid": "15526f1b-76f2-4d8f-876c-c343c94ea476", + "datanodeHost": "ozone_datanode_2.ozone_UnderReplicated2", + "firstSeenTime": 1665588176660, + "lastSeenTime": 1665590397315, + "lastBcsId": 2 + }, + { + "containerId": 2, + "datanodeUuid": "f55476ab-4687-464d-a100-1c65de4366e3", + "datanodeHost": "ozone_datanode_3.ozone_underreplicated2", + "firstSeenTime": 1665588176616, + "lastSeenTime": 1665590392293, + "lastBcsId": 2 + }, + { + "containerId": 2, + "datanodeUuid": "7a457bcb-d63e-49cc-b3ff-8b22bf48d130", + "datanodeHost": "ozone_datanode_1.ozone_underreplicated2", + "firstSeenTime": 1665588176660, + "lastSeenTime": 1665590272289, + "lastBcsId": 0 + } + ] + }, + { + "containerID": 3, + "containerState": "UNDER_REPLICATED", + "unhealthySince": 1665590446222, + "expectedReplicaCount": 4, + "actualReplicaCount": 2, + "replicaDeltaCount": 2, + "reason": null, + "keys": 1, + "pipelineID": "a10ffab6-8ed5-414a-aaf5-79890ff3e8a1", + "replicas": [ + { + "containerId": 3, + "datanodeUuid": "15526f1b-76f2-4d8f-876c-c343c94ea476", + "datanodeHost": "ozone_datanode_2.ozone_underreplicated3", + "firstSeenTime": 1665588176660, + "lastSeenTime": 1665590397315, + "lastBcsId": 2 + }, + { + "containerId": 3, + "datanodeUuid": "f55476ab-4687-464d-a100-1c65de4366e3", + "datanodeHost": "ozone_datanode_3.ozone_underreplicated3", + "firstSeenTime": 1665588176616, + "lastSeenTime": 1665590392293, + "lastBcsId": 2 + }, + { + "containerId": 3, + "datanodeUuid": "7a457bcb-d63e-49cc-b3ff-8b22bf48d130", + "datanodeHost": "ozone_datanode_1.ozone_underreplicated3", + "firstSeenTime": 1665588176660, + "lastSeenTime": 1665590272289, + "lastBcsId": 0 + } + ] + } + ] + }, + "containerUnhealthyOverReplicated": { + "missingCount": 0, + "underReplicatedCount": 1, + "overReplicatedCount": 0, + "misReplicatedCount": 0, + "containers": [ + { + "containerID": 2, + "containerState": "OVER_REPLICATED", + "unhealthySince": 1665590446222, + "expectedReplicaCount": 3, + "actualReplicaCount": 2, + "replicaDeltaCount": 1, + "reason": null, + "keys": 1, + "pipelineID": "a10ffab6-8ed5-414a-aaf5-79890ff3e8a1", + "replicas": [ + { + "containerId": 2, + "datanodeUuid": "15526f1b-76f2-4d8f-876c-c343c94ea476", + "datanodeHost": "ozone_datanode_2.ozone_overreplicated2", + "firstSeenTime": 1665588176660, + "lastSeenTime": 1665590397315, + "lastBcsId": 2 + }, + { + "containerId": 2, + "datanodeUuid": "f55476ab-4687-464d-a100-1c65de4366e3", + "datanodeHost": "ozone_datanode_3.ozone_overreplicated22", + "firstSeenTime": 1665588176616, + "lastSeenTime": 1665590392293, + "lastBcsId": 2 + }, + { + "containerId": 2, + "datanodeUuid": "7a457bcb-d63e-49cc-b3ff-8b22bf48d130", + "datanodeHost": "ozone_datanode_1.ozone_overreplicated2", + "firstSeenTime": 1665588176660, + "lastSeenTime": 1665590272289, + "lastBcsId": 0 + } + ] + }, + { + "containerID": 3, + "containerState": "OVER_REPLICATED", + "unhealthySince": 1665590446222, + "expectedReplicaCount": 4, + "actualReplicaCount": 2, + "replicaDeltaCount": 2, + "reason": null, + "keys": 1, + "pipelineID": "a10ffab6-8ed5-414a-aaf5-79890ff3e8a1", + "replicas": [ + { + "containerId": 3, + "datanodeUuid": "15526f1b-76f2-4d8f-876c-c343c94ea476", + "datanodeHost": "ozone_datanode_2.ozone_overreplicated3", + "firstSeenTime": 1665588176660, + "lastSeenTime": 1665590397315, + "lastBcsId": 2 + }, + { + "containerId": 3, + "datanodeUuid": "f55476ab-4687-464d-a100-1c65de4366e3", + "datanodeHost": "ozone_datanode_3.ozone_overreplicated3", + "firstSeenTime": 1665588176616, + "lastSeenTime": 1665590392293, + "lastBcsId": 2 + }, + { + "containerId": 3, + "datanodeUuid": "7a457bcb-d63e-49cc-b3ff-8b22bf48d130", + "datanodeHost": "ozone_datanode_1.ozone_overreplicated3", + "firstSeenTime": 1665588176660, + "lastSeenTime": 1665590272289, + "lastBcsId": 0 + } + ] + } + ] + }, + "containerUnhealthyMisReplicated": { + "missingCount": 0, + "underReplicatedCount": 1, + "overReplicatedCount": 0, + "misReplicatedCount": 0, + "containers": [ + { + "containerID": 2, + "containerState": "MIS_REPLICATED", + "unhealthySince": 1665590446222, + "expectedReplicaCount": 3, + "actualReplicaCount": 2, + "replicaDeltaCount": 1, + "reason": null, + "keys": 1, + "pipelineID": "a10ffab6-8ed5-414a-aaf5-79890ff3e8a1", + "replicas": [ + { + "containerId": 2, + "datanodeUuid": "15526f1b-76f2-4d8f-876c-c343c94ea476", + "datanodeHost": "ozone_datanode_2.ozone_misreplicated2", + "firstSeenTime": 1665588176660, + "lastSeenTime": 1665590397315, + "lastBcsId": 2 + }, + { + "containerId": 2, + "datanodeUuid": "f55476ab-4687-464d-a100-1c65de4366e3", + "datanodeHost": "ozone_datanode_3.ozone_misreplicated2", + "firstSeenTime": 1665588176616, + "lastSeenTime": 1665590392293, + "lastBcsId": 2 + }, + { + "containerId": 2, + "datanodeUuid": "7a457bcb-d63e-49cc-b3ff-8b22bf48d130", + "datanodeHost": "ozone_datanode_1.ozone_misreplicated2", + "firstSeenTime": 1665588176660, + "lastSeenTime": 1665590272289, + "lastBcsId": 0 + } + ] + }, + { + "containerID": 3, + "containerState": "MIS_REPLICATED", + "unhealthySince": 1665590446222, + "expectedReplicaCount": 4, + "actualReplicaCount": 2, + "replicaDeltaCount": 2, + "reason": null, + "keys": 1, + "pipelineID": "a10ffab6-8ed5-414a-aaf5-79890ff3e8a1", + "replicas": [ + { + "containerId": 3, + "datanodeUuid": "15526f1b-76f2-4d8f-876c-c343c94ea476", + "datanodeHost": "ozone_datanode_2.ozone_misreplicated3", + "firstSeenTime": 1665588176660, + "lastSeenTime": 1665590397315, + "lastBcsId": 2 + }, + { + "containerId": 3, + "datanodeUuid": "f55476ab-4687-464d-a100-1c65de4366e3", + "datanodeHost": "ozone_datanode_3.ozone_misreplicated3", + "firstSeenTime": 1665588176616, + "lastSeenTime": 1665590392293, + "lastBcsId": 2 + }, + { + "containerId": 3, + "datanodeUuid": "7a457bcb-d63e-49cc-b3ff-8b22bf48d130", + "datanodeHost": "ozone_datanode_1.ozone_misreplicated3", + "firstSeenTime": 1665588176660, + "lastSeenTime": 1665590272289, + "lastBcsId": 0 + } + ] + } + ] + } } \ No newline at end of file diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/api/routes.json b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/api/routes.json index 4aa303fe29d9..0b0a4bb9d69c 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/api/routes.json +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/api/routes.json @@ -1,7 +1,6 @@ { "/api/v1/*": "/$1", "/containers/:id/keys": "/keys", - "/containers/missing": "/missingContainers", "/utilization/fileCount": "/fileSizeCounts", "/namespace/du?path=/&files=true": "/root", "/namespace/du?path=/vol:id&files=true": "/volume", @@ -23,5 +22,10 @@ "/namespace/du?path=/clunky&files=true": "/clunky", "/namespace/summary?path=*": "/metadata", "/namespace/quota?path=*": "/quota", - "/task/status": "/taskStatus" + "/task/status": "/taskStatus", + "/containers/missing": "/missingContainers", + "/containers/unhealthy/MISSING": "/containerUnhealthyMissing", + "/containers/unhealthy/UNDER_REPLICATED": "/containerUnhealthyUnderReplicated", + "/containers/unhealthy/OVER_REPLICATED": "/containerUnhealthyOverReplicated", + "/containers/unhealthy/MIS_REPLICATED": "/containerUnhealthyMisReplicated" } \ No newline at end of file diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/components/navBar/navBar.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/components/navBar/navBar.tsx index acc055040448..d381e8e4d41f 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/components/navBar/navBar.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/components/navBar/navBar.tsx @@ -66,6 +66,11 @@ class NavBar extends React.Component { Pipelines + + + Containers + + Insights diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/missingContainers/missingContainers.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/missingContainers/missingContainers.tsx index 6a6dca304dcb..c6a76b5b97d2 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/missingContainers/missingContainers.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/missingContainers/missingContainers.tsx @@ -18,7 +18,7 @@ import React from 'react'; import axios from 'axios'; -import {Icon, Table, Tooltip} from 'antd'; +import {Icon, Table, Tooltip, Tabs} from 'antd'; import {PaginationConfig} from 'antd/lib/pagination'; import filesize from 'filesize'; import moment from 'moment'; @@ -26,6 +26,7 @@ import {showDataFetchError, timeFormat} from 'utils/common'; import './missingContainers.less'; const size = filesize.partial({standard: 'iec'}); +const {TabPane} = Tabs; interface IMissingContainerResponse { containerID: number; @@ -35,6 +36,19 @@ interface IMissingContainerResponse { pipelineID: string; } +interface IContainerResponse { + containerID: number; + containerState: string; + unhealthySince: string; + expectedReplicaCount: number; + actualReplicaCount: number; + replicaDeltaCount: number; + reason: string; + keys: number; + pipelineID: string; + replicas: IContainerReplicas[]; +} + export interface IContainerReplica { containerId: number; datanodeHost: string; @@ -42,11 +56,28 @@ export interface IContainerReplica { lastReportTimestamp: number; } +export interface IContainerReplicas { + containerId: number; + datanodeUuid: string; + datanodeHost: string; + firstSeenTime: number; + lastSeenTime: number; + lastBcsId: number; +} + export interface IMissingContainersResponse { totalCount: number; containers: IMissingContainerResponse[]; } +interface IUnhealthyContainersResponse { + missingCount: number; + underReplicatedCount: number; + overReplicatedCount: number; + misReplicatedCount: number; + containers: IContainerResponse[]; +} + interface IKeyResponse { Volume: string; Bucket: string; @@ -63,30 +94,79 @@ interface IContainerKeysResponse { keys: IKeyResponse[]; } -const COLUMNS = [ +const KEY_TABLE_COLUMNS = [ + { + title: 'Volume', + dataIndex: 'Volume', + key: 'Volume' + }, + { + title: 'Bucket', + dataIndex: 'Bucket', + key: 'Bucket' + }, + { + title: 'Key', + dataIndex: 'Key', + key: 'Key' + }, + { + title: 'Size', + dataIndex: 'DataSize', + key: 'DataSize', + render: (dataSize: number) =>
{size(dataSize)}
+ }, + { + title: 'Date Created', + dataIndex: 'CreationTime', + key: 'CreationTime', + render: (date: string) => moment(date).format('lll') + }, + { + title: 'Date Modified', + dataIndex: 'ModificationTime', + key: 'ModificationTime', + render: (date: string) => moment(date).format('lll') + } +]; + +const CONTAINER_TAB_COLUMNS = [ { title: 'Container ID', dataIndex: 'containerID', key: 'containerID', - sorter: (a: IMissingContainerResponse, b: IMissingContainerResponse) => a.containerID - b.containerID + sorter: (a: IContainerResponse, b: IContainerResponse) => a.containerID - b.containerID }, { title: 'No. of Keys', dataIndex: 'keys', key: 'keys', - sorter: (a: IMissingContainerResponse, b: IMissingContainerResponse) => a.keys - b.keys + sorter: (a: IContainerResponse, b: IContainerResponse) => a.keys - b.keys + }, + { + title: 'Active/Expected Replica(s)', + dataIndex: 'expectedReplicaCount', + key: 'expectedReplicaCount', + render: (expectedReplicaCount: number, record: IContainerResponse) => { + const actualReplicaCount = record.actualReplicaCount; + return ( + + {actualReplicaCount} / {expectedReplicaCount} + + ); + } }, { title: 'Datanodes', dataIndex: 'replicas', key: 'replicas', - render: (replicas: IContainerReplica[]) => ( + render: (replicas: IContainerReplicas[]) => (
- {replicas.map(replica => { + {replicas && replicas.map(replica => { const tooltip = (
-
First Report Time: {timeFormat(replica.firstReportTimestamp)}
-
Last Report Time: {timeFormat(replica.lastReportTimestamp)}
+
First Report Time: {timeFormat(replica.firstSeenTime)}
+
Last Report Time: {timeFormat(replica.lastSeenTime)}
); return ( @@ -111,50 +191,14 @@ const COLUMNS = [ title: 'Pipeline ID', dataIndex: 'pipelineID', key: 'pipelineID', - sorter: (a: IMissingContainerResponse, b: IMissingContainerResponse) => a.pipelineID.localeCompare(b.pipelineID) - }, - { - title: 'Missing Since', - dataIndex: 'missingSince', - key: 'missingSince', - render: (missingSince: number) => timeFormat(missingSince), - sorter: (a: IMissingContainerResponse, b: IMissingContainerResponse) => a.missingSince - b.missingSince - } -]; - -const KEY_TABLE_COLUMNS = [ - { - title: 'Volume', - dataIndex: 'Volume', - key: 'Volume' - }, - { - title: 'Bucket', - dataIndex: 'Bucket', - key: 'Bucket' - }, - { - title: 'Key', - dataIndex: 'Key', - key: 'Key' - }, - { - title: 'Size', - dataIndex: 'DataSize', - key: 'DataSize', - render: (dataSize: number) =>
{size(dataSize)}
+ sorter: (a: IContainerResponse, b: IContainerResponse) => a.pipelineID.localeCompare(b.pipelineID) }, { - title: 'Date Created', - dataIndex: 'CreationTime', - key: 'CreationTime', - render: (date: string) => moment(date).format('lll') - }, - { - title: 'Date Modified', - dataIndex: 'ModificationTime', - key: 'ModificationTime', - render: (date: string) => moment(date).format('lll') + title: 'Unhealthy Since', + dataIndex: 'unhealthySince', + key: 'unhealthySince', + render: (unhealthySince: number) => timeFormat(unhealthySince), + sorter: (a: IContainerResponse, b: IContainerResponse) => a.unhealthySince - b.unhealthySince } ]; @@ -171,7 +215,10 @@ interface IExpandedRowState { interface IMissingContainersState { loading: boolean; - dataSource: IMissingContainerResponse[]; + missingDataSource: IContainerResponse[]; + underReplicatedDataSource: IContainerResponse[]; + overReplicatedDataSource: IContainerResponse[]; + misReplicatedDataSource: IContainerResponse[]; totalCount: number; expandedRowData: IExpandedRow; } @@ -181,7 +228,10 @@ export class MissingContainers extends React.Component, I super(props); this.state = { loading: false, - dataSource: [], + missingDataSource: [], + underReplicatedDataSource: [], + overReplicatedDataSource: [], + misReplicatedDataSource: [], totalCount: 0, expandedRowData: {} }; @@ -192,16 +242,36 @@ export class MissingContainers extends React.Component, I this.setState({ loading: true }); - axios.get('/api/v1/containers/missing').then(response => { - const missingContainersResponse: IMissingContainersResponse = response.data; - const totalCount = missingContainersResponse.totalCount; - const missingContainers: IMissingContainerResponse[] = missingContainersResponse.containers; + + axios.all([ + axios.get('/api/v1/containers/unhealthy/MISSING'), + axios.get('/api/v1/containers/unhealthy/UNDER_REPLICATED'), + axios.get('/api/v1/containers/unhealthy/OVER_REPLICATED'), + axios.get('/api/v1/containers/unhealthy/MIS_REPLICATED') + ]).then(axios.spread((missingContainersResponse, underReplicatedResponse, overReplicatedResponse, misReplicatedResponse, allReplicatedResponse) => { + + const missingContainersResponseData: IUnhealthyContainersResponse = missingContainersResponse.data; + const totalCount = missingContainersResponseData.missingCount; + const missingContainers: IContainerResponse[] = missingContainersResponseData.containers; + + const underReplicatedResponseData: IUnhealthyContainersResponse = underReplicatedResponse.data; + const uContainers: IContainerResponse[] = underReplicatedResponseData.containers; + + const overReplicatedResponseData: IUnhealthyContainersResponse = overReplicatedResponse.data; + const oContainers: IContainerResponse[] = overReplicatedResponseData.containers; + + const misReplicatedResponseData: IUnhealthyContainersResponse = misReplicatedResponse.data; + const mContainers: IContainerResponse[] = misReplicatedResponseData.containers; + this.setState({ loading: false, - dataSource: missingContainers, + missingDataSource: missingContainers, + underReplicatedDataSource: uContainers, + overReplicatedDataSource: oContainers, + misReplicatedDataSource: mContainers, totalCount }); - }).catch(error => { + })).catch(error => { this.setState({ loading: false }); @@ -212,7 +282,7 @@ export class MissingContainers extends React.Component, I onShowSizeChange = (current: number, pageSize: number) => { console.log(current, pageSize); }; - + onRowExpandClick = (expanded: boolean, record: IMissingContainerResponse) => { if (expanded) { this.setState(({expandedRowData}) => { @@ -270,7 +340,7 @@ export class MissingContainers extends React.Component, I }; render() { - const {dataSource, loading, totalCount} = this.state; + const {missingDataSource, loading, underReplicatedDataSource, overReplicatedDataSource, misReplicatedDataSource} = this.state; const paginationConfig: PaginationConfig = { showTotal: (total: number, range) => `${range[0]}-${range[1]} of ${total} missing containers`, showSizeChanger: true, @@ -279,14 +349,39 @@ export class MissingContainers extends React.Component, I return (
- Missing Containers ({totalCount}) + Containers
- + + +
+ + +
+ + +
+ + +
+ + );