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 b28c510363a4..4dc4879d29d5 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
@@ -94,7 +94,7 @@
},
{
"hostname": "localhost1.storage.enterprise.com",
- "uuid": "b5907812-a5f2-11ea-bb37-0242ac130011",
+ "uuid": "4712ba3d-4bb2-477a-9211-d9b50c013055",
"state": "HEALTHY",
"opState": "DECOMMISSIONING",
"lastHeartbeat": 1574724876059,
@@ -162,7 +162,7 @@
},
{
"hostname": "localhost3.storage.enterprise.com",
- "uuid": "b5907812-a5f2-11ea-bb37-0242ac130013",
+ "uuid": "4712ba3d-4bb2-477a-9211-d9b50c013056",
"state": "HEALTHY",
"opState": "ENTERING_MAINTENANCE",
"lastHeartbeat": 1574724876059,
@@ -6738,6 +6738,201 @@
],
"status": "OK"
},
+ "decommissioninfo": {
+ "DatanodesDecommissionInfo":[
+ {
+ "datanodeDetails": {
+ "level": 3,
+ "parent": null,
+ "cost": 0,
+ "uuid": "4712ba3d-4bb2-477a-9211-d9b50c013055",
+ "uuidString": "4712ba3d-4bb2-477a-9211-d9b50c013055",
+ "ipAddress": "172.18.0.13",
+ "hostName": "ozone-ha-datanode-6.ozone-ha_default",
+
+ "certSerialId": null,
+ "version": null,
+ "setupTime": 0,
+ "revision": null,
+ "buildDate": null,
+ "persistedOpState": "IN_SERVICE",
+ "persistedOpStateExpiryEpochSec": 0,
+ "initialVersion": 0,
+ "currentVersion": 1,
+ "decommissioned": false,
+ "maintenance": false,
+ "ipAddressAsByteString": {
+ "string": "172.18.0.13",
+ "bytes": {
+ "validUtf8": true,
+ "empty": false
+ }
+ },
+ "hostNameAsByteString": {
+ "string": "ozone-ha-datanode-6.ozone-ha_default",
+ "bytes": {
+ "validUtf8": true,
+ "empty": false
+ }
+ },
+ "networkName": "4712ba3d-4bb2-477a-9211-d9b50c013055",
+ "networkLocation": "/default-rack",
+ "networkFullPath": "/default-rack/4712ba3d-4bb2-477a-9211-d9b50c013055",
+ "numOfLeaves": 1,
+ "networkNameAsByteString": {
+ "string": "4712ba3d-4bb2-477a-9211-d9b50c013055",
+ "bytes": {
+ "validUtf8": true,
+ "empty": false
+ }
+ },
+ "networkLocationAsByteString": {
+ "string": "/default-rack",
+ "bytes": {
+ "validUtf8": true,
+ "empty": false
+ }
+ }
+ },
+ "metrics": null,
+ "containers": {}
+ },
+ {
+ "datanodeDetails": {
+ "level": 3,
+ "parent": null,
+ "cost": 0,
+ "uuid": "4712ba3d-4bb2-477a-9211-d9b50c013056",
+ "uuidString": "4712ba3d-4bb2-477a-9211-d9b50c013056",
+ "ipAddress": "172.18.0.13",
+ "hostName": "ozone-ha-datanode-6.ozone-ha_default",
+ "certSerialId": null,
+ "version": null,
+ "setupTime": 0,
+ "revision": null,
+ "buildDate": null,
+ "persistedOpState": "IN_SERVICE",
+ "persistedOpStateExpiryEpochSec": 0,
+ "initialVersion": 0,
+ "currentVersion": 1,
+ "decommissioned": false,
+ "maintenance": false,
+ "networkName": "4712ba3d-4bb2-477a-9211-d9b50c013055",
+ "networkLocation": "/default-rack",
+ "networkFullPath": "/default-rack/4712ba3d-4bb2-477a-9211-d9b50c013055",
+ "numOfLeaves": 1
+ },
+ "metrics": null,
+ "containers": {}
+ }
+ ]
+ },
+ "DatanodesDecommissionInfo": {
+ "DatanodesDecommissionInfo": [
+ {
+ "datanodeDetails": {
+ "level": 3,
+ "parent": null,
+ "cost": 0,
+ "uuid": "adc72ef9-0850-49d7-935d-e5a4e0d5be3c",
+ "uuidString": "adc72ef9-0850-49d7-935d-e5a4e0d5be3c",
+ "ipAddress": "172.18.0.10",
+ "hostName": "ozone-ha-datanode-2.ozone-ha_default",
+ "ports": [
+ {
+ "name": "HTTP",
+ "value": 9882
+ },
+ {
+ "name": "CLIENT_RPC",
+ "value": 19864
+ },
+ {
+ "name": "REPLICATION",
+ "value": 9886
+ },
+ {
+ "name": "RATIS",
+ "value": 9858
+ },
+ {
+ "name": "RATIS_ADMIN",
+ "value": 9857
+ },
+ {
+ "name": "RATIS_SERVER",
+ "value": 9856
+ },
+ {
+ "name": "RATIS_DATASTREAM",
+ "value": 9855
+ },
+ {
+ "name": "STANDALONE",
+ "value": 9859
+ }
+ ],
+ "certSerialId": null,
+ "version": null,
+ "setupTime": 0,
+ "revision": null,
+ "buildDate": null,
+ "persistedOpState": "DECOMMISSIONING",
+ "persistedOpStateExpiryEpochSec": 0,
+ "initialVersion": 0,
+ "currentVersion": 1,
+ "decommissioned": true,
+ "maintenance": false,
+ "ipAddressAsByteString": {
+ "string": "172.18.0.10",
+ "bytes": {
+ "validUtf8": true,
+ "empty": false
+ }
+ },
+ "hostNameAsByteString": {
+ "string": "ozone-ha-datanode-2.ozone-ha_default",
+ "bytes": {
+ "validUtf8": true,
+ "empty": false
+ }
+ },
+ "networkName": "adc72ef9-0850-49d7-935d-e5a4e0d5be3c",
+ "networkLocation": "/default-rack",
+ "networkFullPath": "/default-rack/adc72ef9-0850-49d7-935d-e5a4e0d5be3c",
+ "numOfLeaves": 1,
+ "networkNameAsByteString": {
+ "string": "adc72ef9-0850-49d7-935d-e5a4e0d5be3c",
+ "bytes": {
+ "validUtf8": true,
+ "empty": false
+ }
+ },
+ "networkLocationAsByteString": {
+ "string": "/default-rack",
+ "bytes": {
+ "validUtf8": true,
+ "empty": false
+ }
+ }
+ },
+ "metrics": {
+ "decommissionStartTime": "23/05/2024 07:28:35 UTC",
+ "numOfUnclosedPipelines": 2,
+ "numOfUnderReplicatedContainers": 0.0,
+ "numOfUnclosedContainers": 0.0
+ },
+ "containers": {
+ "UnderReplicated": [],
+ "UnClosed": [
+ "#6",
+ "#10",
+ "#17"
+ ]
+ }
+ }
+ ]
+ },
"datanodesRemove": {
"selectedRowKeys": [
"b5907812-a5f2-11ea-bb37-0242ac130011"
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 b2136d074b07..b4481c2854fc 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
@@ -50,5 +50,7 @@
"/containers/mismatch/deleted?limit=*": "/deleted",
"/keys/deletePending/dirs?limit=*": "/dirdeletePending",
+ "/datanodes/decommission/info": "/decommissioninfo",
+ "/datanodes/decommission/info/datanode?uuid=*": "/DatanodesDecommissionInfo",
"/datanodes/remove": "/datanodesRemove"
}
\ No newline at end of file
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 78ba56e54891..c8e19db14c8c 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
@@ -27,7 +27,8 @@ import {
FolderOpenOutlined,
FileTextOutlined,
QuestionCircleOutlined,
- DeleteOutlined
+ DeleteOutlined,
+ HourglassOutlined
} from '@ant-design/icons';
import { RouteComponentProps } from 'react-router';
import { withRouter, Link } from 'react-router-dom';
@@ -70,7 +71,8 @@ const IconSelector = ({ iconType, ...extras }: { iconType: string }) => {
'inbox': ,
'folder-open': ,
'file-text': ,
- 'delete':
+ 'delete': ,
+ 'hourglass':
}
const selectIcon = (type: string) => {
diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/datanodes/datanodes.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/datanodes/datanodes.tsx
index c4c9bf129c8d..47fe41dd7b31 100644
--- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/datanodes/datanodes.tsx
+++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/datanodes/datanodes.tsx
@@ -48,10 +48,10 @@ import { ColumnSearch } from '@/utils/columnSearch';
import { ReplicationIcon } from '@/utils/themeIcons';
import { AutoReloadHelper } from '@/utils/autoReloadHelper';
import { AxiosGetHelper, AxiosPutHelper } from '@/utils/axiosRequestHelper';
+import DecommissionSummary from './decommissionSummary';
import './datanodes.less';
-
interface IDatanodeResponse {
hostname: string;
state: DatanodeState;
@@ -190,7 +190,6 @@ const COLUMNS = [
render: (text: DatanodeOpState) => renderDatanodeOpState(text),
sorter: (a: IDatanode, b: IDatanode) => a.opState.localeCompare(b.opState)
},
-
{
title: 'Uuid',
dataIndex: 'uuid',
@@ -198,7 +197,14 @@ const COLUMNS = [
isVisible: true,
isSearchable: true,
sorter: (a: IDatanode, b: IDatanode) => a.uuid.localeCompare(b.uuid),
- defaultSortOrder: 'ascend' as const
+ defaultSortOrder: 'ascend' as const,
+ render: (uuid: IDatanode, record: IDatanode) => {
+ return (
+ //1. Compare Decommission Api's UUID with all UUID in table and show Decommission Summary
+ (decommissionUuids && decommissionUuids.includes(record.uuid) && record.opState !== 'DECOMMISSIONED') ?
+ : {uuid}
+ );
+ }
},
{
title: 'Storage Capacity',
@@ -356,6 +362,17 @@ const defaultColumns: IOption[] = COLUMNS.map(column => ({
}));
let cancelSignal: AbortController;
+let cancelDecommissionSignal: AbortController;
+let decommissionUuids: string | string[] =[];
+const COLUMN_UPDATE_DECOMMISSIONING = 'DECOMMISSIONING';
+
+type DatanodeDetails = {
+ uuid: string;
+}
+
+type DatanodeDecomissionInfo = {
+datanodeDetails: DatanodeDetails
+}
export class Datanodes extends React.Component, IDatanodesState> {
autoReload: AutoReloadHelper;
@@ -389,24 +406,34 @@ export class Datanodes extends React.Component, IDatanode
return selectedColumns;
};
- _loadData = () => {
- this.setState(prevState => ({
- loading: true,
- selectedColumns: this._getSelectedColumns(prevState.selectedColumns)
- }));
-
- const { request, controller } = AxiosGetHelper('/api/v1/datanodes', cancelSignal);
- cancelSignal = controller;
- request.then(response => {
- const datanodesResponse: IDatanodesResponse = response.data;
+ _loadData = async () => {
+ // Need to call decommission API on each interval to get updated status before datanode API to compare UUID's
+ // update Operation State Column in table Manually before rendering
+ try {
+ let decomissionResponse = await this._loadDecommisionAPI();
+ decommissionUuids = decomissionResponse.data?.DatanodesDecommissionInfo?.map((item: DatanodeDecomissionInfo) => item.datanodeDetails.uuid);
+ }
+ catch (error: any)
+ {
+ this.setState({
+ loading: false
+ });
+ decommissionUuids = [];
+ showDataFetchError(error.toString());
+ }
+ try {
+ // Call Datanode API Synchronously after completing Decommission API to render Operation Status Column
+ let datanodeApiResponse = await this._loadDataNodeAPI();
+ const datanodesResponse: IDatanodesResponse = datanodeApiResponse.data;
const totalCount = datanodesResponse.totalCount;
const datanodes: IDatanodeResponse[] = datanodesResponse.datanodes;
const dataSource: IDatanode[] = datanodes && datanodes.map(datanode => {
+ let decommissionCondition = decommissionUuids && decommissionUuids.includes(datanode.uuid) && datanode.opState !== 'DECOMMISSIONED';
return {
hostname: datanode.hostname,
uuid: datanode.uuid,
state: datanode.state,
- opState: datanode.opState,
+ opState: decommissionCondition ? COLUMN_UPDATE_DECOMMISSIONING : datanode.opState,
lastHeartbeat: datanode.lastHeartbeat,
storageUsed: datanode.storageReport.used,
storageTotal: datanode.storageReport.capacity,
@@ -423,19 +450,40 @@ export class Datanodes extends React.Component, IDatanode
networkLocation: datanode.networkLocation
};
});
-
this.setState({
loading: false,
dataSource,
totalCount,
lastUpdated: Number(moment())
});
- }).catch(error => {
+ }
+ catch (error: any) {
this.setState({
loading: false
});
+ decommissionUuids = [];
showDataFetchError(error.toString());
+ }
+ };
+
+ _loadDecommisionAPI = async () => {
+ this.setState({
+ loading: true
});
+ decommissionUuids = [];
+ const { request, controller } = await AxiosGetHelper('/api/v1/datanodes/decommission/info', cancelDecommissionSignal);
+ cancelDecommissionSignal = controller;
+ return request
+ };
+
+ _loadDataNodeAPI = async () => {
+ this.setState(prevState => ({
+ loading: true,
+ selectedColumns: this._getSelectedColumns(prevState.selectedColumns)
+ }));
+ const { request, controller } = await AxiosGetHelper('/api/v1/datanodes', cancelSignal);
+ cancelSignal = controller;
+ return request;
};
removeDatanode = async (selectedRowKeys: any) => {
@@ -462,6 +510,7 @@ export class Datanodes extends React.Component, IDatanode
componentWillUnmount(): void {
this.autoReload.stopPolling();
cancelSignal && cancelSignal.abort();
+ cancelDecommissionSignal && cancelDecommissionSignal.abort();
}
onShowSizeChange = (current: number, pageSize: number) => {
diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/datanodes/decommissionSummary.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/datanodes/decommissionSummary.tsx
new file mode 100644
index 000000000000..28527c0816e0
--- /dev/null
+++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/datanodes/decommissionSummary.tsx
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { Descriptions, Popover, Tooltip } from 'antd';
+import { InfoCircleOutlined } from '@ant-design/icons';
+import { withRouter } from 'react-router-dom';
+import { RouteComponentProps } from 'react-router';
+import axios from 'axios';
+import { showDataFetchError } from '@/utils/common';
+
+interface IDecommissionSummaryProps extends RouteComponentProps