diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/components/overviewCard/overviewCard.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/components/overviewCard/overviewCard.tsx index 93c0cae1f16c..78ba56e54891 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/components/overviewCard/overviewCard.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/components/overviewCard/overviewCard.tsx @@ -139,8 +139,14 @@ class OverviewCard extends React.Component { render() { let { icon, data, title, loading, hoverable, storageReport, linkToUrl, error } = this.props; + let meta = ; - const errorClass = error ? 'card-error' : ''; + let errorClass = error ? 'card-error' : ''; + + if (typeof data === 'string' && data === 'N/A'){ + errorClass = 'card-error'; + } + if (storageReport) { meta = (
@@ -156,7 +162,7 @@ class OverviewCard extends React.Component { return ( - + diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/utils/axiosRequestHelper.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/utils/axiosRequestHelper.tsx index 45fa3b58b6da..41774088c503 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/utils/axiosRequestHelper.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/utils/axiosRequestHelper.tsx @@ -48,11 +48,11 @@ export const AxiosPutHelper = ( } } -export const AxiosAllGetHelper = ( +export const PromiseAllSettledGetHelper = ( urls: string[], controller: AbortController, message: string = '' -): { requests: Promise[]>; controller: AbortController } => { +): { requests: Promise>[]>; controller: AbortController } => { controller && controller.abort(message); controller = new AbortController(); // generate new AbortController for the upcoming request @@ -64,7 +64,7 @@ export const AxiosAllGetHelper = ( }); return { - requests: axios.all(axiosGetRequests), + requests: Promise.allSettled(axiosGetRequests), controller: controller } } diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/diskUsage/diskUsage.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/diskUsage/diskUsage.tsx index 60b779bdf089..091d8a146d3b 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/diskUsage/diskUsage.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/diskUsage/diskUsage.tsx @@ -556,12 +556,6 @@ export class DiskUsage extends React.Component, IDUState> ) - console.log(plotData); - console.log(plotData.map((value) => { - return { - name: value.name - } - })) const eChartsOptions = { title: { text: `Disk Usage for ${returnPath} (Total Size: ${byteToSize(duResponse.size, 1)})`, diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/insights/insights.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/insights/insights.tsx index 2412febdee3b..f273f758ea96 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/insights/insights.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/insights/insights.tsx @@ -17,17 +17,17 @@ */ import React from 'react'; -import axios from 'axios'; +import axios, { CanceledError, AxiosError } from 'axios'; import filesize from 'filesize'; -import { Row, Col, Tabs } from 'antd'; +import { Row, Col, Tabs, message } from 'antd'; import { LoadingOutlined } from '@ant-design/icons'; import { ActionMeta, ValueType } from 'react-select'; -import { format, type EChartsOption } from 'echarts'; +import { graphic, type EChartsOption } from 'echarts'; import { EChart } from '@/components/eChart/eChart'; import { MultiSelect, IOption } from '@/components/multiSelect/multiSelect'; import { showDataFetchError } from '@/utils/common'; -import { AxiosAllGetHelper } from '@/utils/axiosRequestHelper'; +import { PromiseAllSettledGetHelper, PromiseAllSettledError } from '@/utils/axiosRequestHelper'; import './insights.less'; @@ -58,6 +58,8 @@ interface IInsightsState { bucketOptions: IOption[]; volumeOptions: IOption[]; isBucketSelectionDisabled: boolean; + fileCountError: string | undefined; + containerSizeError: string | undefined; } const allVolumesOption: IOption = { @@ -86,7 +88,9 @@ export class Insights extends React.Component, IInsightsS selectedVolumes: [], bucketOptions: [], volumeOptions: [], - isBucketSelectionDisabled: false + isBucketSelectionDisabled: false, + fileCountError: undefined, + containerSizeError: undefined }; } @@ -195,9 +199,81 @@ export class Insights extends React.Component, IInsightsS return (size(value)); }); - console.log(xyFileCountMap); - console.log(xContainerCountValues); - console.log(xyContainerCountMap); + let renderFileCountError = (this.state.fileCountError) ? { + type: 'group', + left: 'center', + top: 'middle', + z: 100, + children: [ + { + type: 'rect', + left: 'center', + top: 'middle', + z: 100, + shape: { + width: 500, + height: 40 + }, + style: { + fill: '#FC909B' + } + }, + { + type: 'text', + left: 'center', + top: 'middle', + z: 100, + style: { + text: `No data available. ${this.state.fileCountError}`, + font: '20px sans-serif' + } + } + ] + } : undefined + let renderContainerSizeError = (this.state.containerSizeError) ? { + type: 'group', + left: 'center', + top: 'middle', + z: 100, + children: [ + { + type: 'rect', + left: 'center', + top: 'middle', + z: 100, + shape: { + width: 500, + height: 500 + }, + style: { + fill: 'rgba(256, 256, 256, 0.5)' + } + }, + { + type: 'rect', + left: 'center', + top: 'middle', + z: 100, + shape: { + width: 500, + height: 40 + }, + style: { + fill: '#FC909B' + } + }, + { + type: 'text', + left: 'center', + top: 'middle', + z: 100, + style: { + text: `No data available. ${this.state.containerSizeError}`, + font: '20px sans-serif' + } + } + ] + } : undefined this.setState({ fileCountData: { @@ -224,7 +300,8 @@ export class Insights extends React.Component, IInsightsS }, data: Array.from(xyFileCountMap.values()), type: 'bar' - } + }, + graphic: renderFileCountError }, containerCountData: { title: { @@ -250,7 +327,8 @@ export class Insights extends React.Component, IInsightsS name: xContainerCountValues[idx] } }), - } + }, + graphic: renderContainerSizeError } }); } @@ -261,15 +339,50 @@ export class Insights extends React.Component, IInsightsS this.setState({ isLoading: true }); - const { requests, controller } = AxiosAllGetHelper([ + const { requests, controller } = PromiseAllSettledGetHelper([ '/api/v1/utilization/fileCount', '/api/v1/utilization/containerCount' ], cancelInsightSignal); cancelInsightSignal = controller; - requests.then(axios.spread((fileCountresponse, containerCountresponse) => { - const fileCountsResponse: IFileCountResponse[] = fileCountresponse.data; - const containerCountResponse: IContainerCountResponse[] = containerCountresponse.data; + requests.then(axios.spread(( + fileCountresponse: Awaited>, + containerCountresponse: Awaited> + ) => { + let fileAPIError = undefined; + let containerAPIError = undefined; + let responseError = [ + fileCountresponse, + containerCountresponse + ].filter((resp) => resp.status === 'rejected'); + + if (responseError.length !== 0) { + responseError.forEach((err) => { + if (err.reason.toString().includes("CanceledError")) { + throw new CanceledError('canceled', "ERR_CANCELED"); + } + else { + if (err.reason.config.url.includes("fileCount")) { + fileAPIError = err.reason.toString(); + } + else { + containerAPIError = err.reason.toString(); + } + } + }) + } + + const fileCountsResponse: IFileCountResponse[] = fileCountresponse.value?.data ?? [{ + volume: '0', + bucket: '0', + fileSize: '0', + count: 0 + }]; + const containerCountResponse: IContainerCountResponse[] = containerCountresponse.value?.data ?? [{ + containerSize: 0, + count: 0 + }]; + // Construct volume -> bucket[] map for populating filters // Ex: vol1 -> [bucket1, bucket2], vol2 -> [bucket1] const volumeBucketMap: Map> = fileCountsResponse.reduce( @@ -280,7 +393,7 @@ export class Insights extends React.Component, IInsightsS const buckets = Array.from(map.get(volume)!); map.set(volume, new Set([...buckets, bucket])); } else { - map.set(volume, new Set().add(bucket)); + map.set(volume, new Set().add(bucket)); } return map; @@ -297,7 +410,9 @@ export class Insights extends React.Component, IInsightsS volumeBucketMap, fileCountsResponse, containerCountResponse, - volumeOptions + volumeOptions, + fileCountError: fileAPIError, + containerSizeError: containerAPIError }, () => { this.updatePlotData(); // Select all volumes by default @@ -305,7 +420,7 @@ export class Insights extends React.Component, IInsightsS }); })).catch(error => { this.setState({ - isLoading: false + isLoading: false, }); showDataFetchError(error.toString()); }); diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/overview/overview.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/overview/overview.tsx index 5b27b3f0d021..bb2e5e02f2c6 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/overview/overview.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/overview/overview.tsx @@ -17,7 +17,7 @@ */ import React from 'react'; -import axios from 'axios'; +import axios, { CanceledError } from 'axios'; import moment from 'moment'; import filesize from 'filesize'; import { Row, Col, Tooltip } from 'antd'; @@ -28,25 +28,25 @@ import OverviewCard from '@/components/overviewCard/overviewCard'; import AutoReloadPanel from '@/components/autoReloadPanel/autoReloadPanel'; import { AutoReloadHelper } from '@/utils/autoReloadHelper'; import { showDataFetchError, byteToSize } from '@/utils/common'; -import { AxiosAllGetHelper, AxiosGetHelper, cancelRequests } from '@/utils/axiosRequestHelper'; +import { AxiosGetHelper, cancelRequests, PromiseAllSettledGetHelper } from '@/utils/axiosRequestHelper'; import './overview.less'; const size = filesize.partial({ round: 1 }); interface IClusterStateResponse { - missingContainers: number; - totalDatanodes: number; - healthyDatanodes: number; - pipelines: number; + missingContainers: number | string; + totalDatanodes: number | string; + healthyDatanodes: number | string; + pipelines: number | string; storageReport: IStorageReport; - containers: number; - volumes: number; - buckets: number; - keys: number; - openContainers: number; - deletedContainers: number; - keysPendingDeletion: number; + containers: number | string; + volumes: number | string; + buckets: number | string; + keys: number | string; + openContainers: number | string; + deletedContainers: number | string; + keysPendingDeletion: number | string; scmServiceId: string; omServiceId: string; } @@ -54,25 +54,25 @@ interface IClusterStateResponse { interface IOverviewState { loading: boolean; datanodes: string; - pipelines: number; + pipelines: number | string; storageReport: IStorageReport; - containers: number; - volumes: number; - buckets: number; - keys: number; - missingContainersCount: number; - lastRefreshed: number; - lastUpdatedOMDBDelta: number; - lastUpdatedOMDBFull: number; + containers: number | string; + volumes: number | string; + buckets: number | string; + keys: number | string; + missingContainersCount: number | string; + lastRefreshed: number | string; + lastUpdatedOMDBDelta: number | string; + lastUpdatedOMDBFull: number | string; omStatus: string; - openContainers: number; - deletedContainers: number; - openSummarytotalUnrepSize: number; - openSummarytotalRepSize: number; - openSummarytotalOpenKeys: number; - deletePendingSummarytotalUnrepSize: number; - deletePendingSummarytotalRepSize: number; - deletePendingSummarytotalDeletedKeys: number; + openContainers: number | string; + deletedContainers: number | string; + openSummarytotalUnrepSize: number | string; + openSummarytotalRepSize: number | string; + openSummarytotalOpenKeys: number | string; + deletePendingSummarytotalUnrepSize: number | string; + deletePendingSummarytotalRepSize: number | string; + deletePendingSummarytotalDeletedKeys: number | string; scmServiceId: string; omServiceId: string; } @@ -127,7 +127,7 @@ export class Overview extends React.Component, IOverviewS cancelOverviewSignal ]); - const { requests, controller } = AxiosAllGetHelper([ + const { requests, controller } = PromiseAllSettledGetHelper([ '/api/v1/clusterState', '/api/v1/task/status', '/api/v1/keys/open/summary', @@ -135,17 +135,65 @@ export class Overview extends React.Component, IOverviewS ], cancelOverviewSignal); cancelOverviewSignal = controller; - requests.then(axios.spread((clusterStateResponse, taskstatusResponse, openResponse, deletePendingResponse) => { + requests.then(axios.spread(( + clusterStateResponse: Awaited>, + taskstatusResponse: Awaited>, + openResponse: Awaited>, + deletePendingResponse: Awaited> + ) => { + let responseError = [ + clusterStateResponse, + taskstatusResponse, + openResponse, + deletePendingResponse + ].filter((resp) => resp.status === 'rejected'); - const clusterState: IClusterStateResponse = clusterStateResponse.data; - const taskStatus = taskstatusResponse.data; + if (responseError.length !== 0) { + responseError.forEach((err) => { + if (err.reason.toString().includes("CanceledError")){ + throw new CanceledError('canceled', "ERR_CANCELED"); + } + else { + const reqMethod = err.reason.config.method; + const reqURL = err.reason.config.url + showDataFetchError(`Failed to ${reqMethod} URL ${reqURL}\n${err.reason.toString()}`); + } + }) + } + + const clusterState: IClusterStateResponse = clusterStateResponse.value?.data ?? { + missingContainers: 'N/A', + totalDatanodes: 'N/A', + healthyDatanodes: 'N/A', + pipelines: 'N/A', + storageReport: { + capacity: 0, + used: 0, + remaining: 0, + committed: 0 + }, + containers: 'N/A', + volumes: 'N/A', + buckets: 'N/A', + keys: 'N/A', + openContainers: 'N/A', + deletedContainers: 'N/A', + keysPendingDeletion: 'N/A', + scmServiceId: 'N/A', + omServiceId: 'N/A', + }; + const taskStatus = taskstatusResponse.value?.data ?? [{ + taskName: 'N/A', lastUpdatedTimestamp: 0, lastUpdatedSeqNumber: 0 + }]; const missingContainersCount = clusterState.missingContainers; const omDBDeltaObject = taskStatus && taskStatus.find((item: any) => item.taskName === 'OmDeltaRequest'); const omDBFullObject = taskStatus && taskStatus.find((item: any) => item.taskName === 'OmSnapshotRequest'); this.setState({ loading: false, - datanodes: `${clusterState.healthyDatanodes}/${clusterState.totalDatanodes}`, + datanodes: clusterState.healthyDatanodes !== 'N/A' + ? `${clusterState.healthyDatanodes}/${clusterState.totalDatanodes}` + : `N/A`, storageReport: clusterState.storageReport, pipelines: clusterState.pipelines, containers: clusterState.containers, @@ -158,12 +206,12 @@ export class Overview extends React.Component, IOverviewS lastRefreshed: Number(moment()), lastUpdatedOMDBDelta: omDBDeltaObject && omDBDeltaObject.lastUpdatedTimestamp, lastUpdatedOMDBFull: omDBFullObject && omDBFullObject.lastUpdatedTimestamp, - openSummarytotalUnrepSize: openResponse.data && openResponse.data.totalUnreplicatedDataSize, - openSummarytotalRepSize: openResponse.data && openResponse.data.totalReplicatedDataSize, - openSummarytotalOpenKeys: openResponse.data && openResponse.data.totalOpenKeys, - deletePendingSummarytotalUnrepSize: deletePendingResponse.data && deletePendingResponse.data.totalUnreplicatedDataSize, - deletePendingSummarytotalRepSize: deletePendingResponse.data && deletePendingResponse.data.totalReplicatedDataSize, - deletePendingSummarytotalDeletedKeys: deletePendingResponse.data && deletePendingResponse.data.totalDeletedKeys, + openSummarytotalUnrepSize: openResponse.value?.data?.totalUnreplicatedDataSize, + openSummarytotalRepSize: openResponse.value?.data?.totalReplicatedDataSize, + openSummarytotalOpenKeys: openResponse.value?.data?.totalOpenKeys, + deletePendingSummarytotalUnrepSize: deletePendingResponse.value?.data?.totalUnreplicatedDataSize, + deletePendingSummarytotalRepSize: deletePendingResponse.value?.data?.totalReplicatedDataSize, + deletePendingSummarytotalDeletedKeys: deletePendingResponse.value?.data?.totalDeletedKeys, scmServiceId: clusterState.scmServiceId, omServiceId: clusterState.omServiceId }); @@ -221,43 +269,89 @@ export class Overview extends React.Component, IOverviewS keys, missingContainersCount, lastRefreshed, lastUpdatedOMDBDelta, lastUpdatedOMDBFull, omStatus, openContainers, deletedContainers, scmServiceId, omServiceId } = this.state; - const datanodesElement = ( - - {datanodes} HEALTHY - - ); + let openKeysError: boolean = false; + let pendingDeleteKeysError: boolean = false; + if ([ + openSummarytotalRepSize, + openSummarytotalUnrepSize, + openSummarytotalOpenKeys].some( + (data) => data === undefined + )) { + openKeysError = true; + } + + if ([ + deletePendingSummarytotalRepSize, + deletePendingSummarytotalUnrepSize, + deletePendingSummarytotalDeletedKeys].some( + (data) => data === undefined + )) { + pendingDeleteKeysError = true; + } + const datanodesElement = datanodes !== 'N/A' + ? ( + + {datanodes} HEALTHY + + ) + : ( + + + + {datanodes} + UNHEALTHY + + + ) const openSummaryData = (
- {openSummarytotalRepSize !== undefined ? byteToSize(openSummarytotalRepSize, 1) : '0'} Total Replicated Data Size
- {openSummarytotalUnrepSize !== undefined ? byteToSize(openSummarytotalUnrepSize, 1) : '0'} Total UnReplicated Data Size
- {openSummarytotalOpenKeys !== undefined ? openSummarytotalOpenKeys : '0'} Total Open Keys + {openSummarytotalRepSize !== undefined ? byteToSize(openSummarytotalRepSize, 1) : 'N/A'} Total Replicated Data Size
+ {openSummarytotalUnrepSize !== undefined ? byteToSize(openSummarytotalUnrepSize, 1) : 'N/A'} Total UnReplicated Data Size
+ {openSummarytotalOpenKeys !== undefined ? openSummarytotalOpenKeys : 'N/A'} Total Open Keys
); const deletePendingSummaryData = (
- {deletePendingSummarytotalRepSize !== undefined ? byteToSize(deletePendingSummarytotalRepSize, 1) : '0'} Total Replicated Data Size
- {deletePendingSummarytotalUnrepSize !== undefined ? byteToSize(deletePendingSummarytotalUnrepSize, 1) : '0'} Total UnReplicated Data Size
- {deletePendingSummarytotalDeletedKeys !== undefined ? deletePendingSummarytotalDeletedKeys : '0'} Total Pending Delete Keys + {deletePendingSummarytotalRepSize !== undefined ? byteToSize(deletePendingSummarytotalRepSize, 1) : 'N/A'} Total Replicated Data Size
+ {deletePendingSummarytotalUnrepSize !== undefined ? byteToSize(deletePendingSummarytotalUnrepSize, 1) : 'N/A'} Total UnReplicated Data Size
+ {deletePendingSummarytotalDeletedKeys !== undefined ? deletePendingSummarytotalDeletedKeys : 'N/A'} Total Pending Delete Keys
); const containersTooltip = missingContainersCount === 1 ? 'container is missing' : 'containers are missing'; - const containersLink = missingContainersCount > 0 ? '/MissingContainers' : '/Containers'; + const containersLink = missingContainersCount as number > 0 ? '/MissingContainers' : '/Containers'; const volumesLink = '/Volumes'; const bucketsLink = '/Buckets'; - const containersElement = missingContainersCount > 0 ? ( - - 1000 ? `1000+ Containers are missing. For more information, go to the Containers page.` : `${missingContainersCount} ${containersTooltip}`}> - - - {containers - missingContainersCount}/{containers} - - ) : -
- {containers.toString()} - - ({openContainers}) - -
+ const containersElement = missingContainersCount !== 'N/A' + ? missingContainersCount as number > 0 + ? ( + 1000 + ? `1000+ Containers are missing. For more information, go to the Containers page.` + : `${missingContainersCount} ${containersTooltip}`}> + + + {(containers as number) - (missingContainersCount as number)}/{containers} + + ) + : + (
+ {containers.toString()} + + ({openContainers}) + +
) + : ( + + + + {missingContainersCount} + + + ) const clusterCapacity = `${size(storageReport.capacity - storageReport.remaining)}/${size(storageReport.capacity)}`; return (
@@ -272,6 +366,7 @@ export class Overview extends React.Component, IOverviewS @@ -290,7 +385,7 @@ export class Overview extends React.Component, IOverviewS 0} linkToUrl={containersLink} /> + error={missingContainersCount as number > 0 || missingContainersCount === 'N/A'} linkToUrl={containersLink} /> @@ -305,10 +400,22 @@ export class Overview extends React.Component, IOverviewS - + - + {scmServiceId &&