This is an automated email from the ASF dual-hosted git repository.

vivekratnavel pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hadoop-ozone.git


The following commit(s) were added to refs/heads/master by this push:
     new 34debd3  HDDS-3571. Recon: Display leader count in Datanodes page 
(#919)
34debd3 is described below

commit 34debd34e6ed5eda1c4e337b9b7c04716c2ddabe
Author: Vivek Ratnavel Subramanian <[email protected]>
AuthorDate: Thu May 14 14:17:21 2020 -0700

    HDDS-3571. Recon: Display leader count in Datanodes page (#919)
---
 .../hadoop/ozone/recon/api/NodeEndpoint.java       |  29 ++++--
 .../ozone/recon/api/types/DatanodeMetadata.java    | 106 +++++++++++++++++---
 .../ozone/recon/api/types/DatanodePipeline.java    |   8 +-
 .../webapps/recon/ozone-recon-web/api/db.json      | 111 ++++++++++++++-------
 .../webapps/recon/ozone-recon-web/src/app.less     |  19 +++-
 .../recon/ozone-recon-web/src/utils/themeIcons.tsx |  15 ++-
 .../src/views/datanodes/datanodes.tsx              |  26 ++++-
 .../src/views/pipelines/pipelines.tsx              |   5 +-
 .../hadoop/ozone/recon/api/TestEndpoints.java      |  35 +++++--
 9 files changed, 273 insertions(+), 81 deletions(-)

diff --git 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/NodeEndpoint.java
 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/NodeEndpoint.java
index d59bee6..2924435 100644
--- 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/NodeEndpoint.java
+++ 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/NodeEndpoint.java
@@ -38,9 +38,11 @@ import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.hadoop.ozone.recon.scm.ReconPipelineManager;
 import org.slf4j.Logger;
@@ -81,31 +83,44 @@ public class NodeEndpoint {
       String hostname = datanode.getHostName();
       Set<PipelineID> pipelineIDs = nodeManager.getPipelines(datanode);
       List<DatanodePipeline> pipelines = new ArrayList<>();
+      AtomicInteger leaderCount = new AtomicInteger();
+      DatanodeMetadata.Builder builder = DatanodeMetadata.newBuilder();
       pipelineIDs.forEach(pipelineID -> {
         try {
           Pipeline pipeline = pipelineManager.getPipeline(pipelineID);
+          String leaderNode = pipeline.getLeaderNode().getHostName();
           DatanodePipeline datanodePipeline = new DatanodePipeline(
               pipelineID.getId(),
               pipeline.getType().toString(),
-              pipeline.getFactor().getNumber()
+              pipeline.getFactor().getNumber(),
+              leaderNode
           );
           pipelines.add(datanodePipeline);
+          if (pipeline.getLeaderId().equals(datanode.getUuid())) {
+            leaderCount.getAndIncrement();
+          }
         } catch (PipelineNotFoundException ex) {
           LOG.warn("Cannot get pipeline {} for datanode {}, pipeline not 
found",
               pipelineID.getId(), hostname, ex);
+        } catch (IOException ioEx) {
+          LOG.warn("Cannot get leader node of pipeline with id {}.",
+              pipelineID.getId(), ioEx);
         }
       });
-      int containers;
       try {
-        containers = nodeManager.getContainers(datanode).size();
+        int containers = nodeManager.getContainers(datanode).size();
+        builder.withContainers(containers);
       } catch (NodeNotFoundException ex) {
-        containers = 0;
         LOG.warn("Cannot get containers, datanode {} not found.",
             datanode.getUuid(), ex);
       }
-      long heartbeat = nodeManager.getLastHeartbeat(datanode);
-      datanodes.add(new DatanodeMetadata(hostname, nodeState, heartbeat,
-          storageReport, pipelines, containers));
+      datanodes.add(builder.withHostname(hostname)
+          .withDatanodeStorageReport(storageReport)
+          .withLastHeartbeat(nodeManager.getLastHeartbeat(datanode))
+          .withState(nodeState)
+          .withPipelines(pipelines)
+          .withLeaderCount(leaderCount.get())
+          .build());
     });
 
     DatanodesResponse datanodesResponse =
diff --git 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/types/DatanodeMetadata.java
 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/types/DatanodeMetadata.java
index 2378e8b..44490f1 100644
--- 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/types/DatanodeMetadata.java
+++ 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/types/DatanodeMetadata.java
@@ -17,6 +17,7 @@
  */
 package org.apache.hadoop.ozone.recon.api.types;
 
+import com.google.common.base.Preconditions;
 import org.apache.hadoop.hdds.protocol.proto.HddsProtos.NodeState;
 
 import javax.xml.bind.annotation.XmlAccessType;
@@ -28,7 +29,7 @@ import java.util.List;
  * Metadata object that represents a Datanode.
  */
 @XmlAccessorType(XmlAccessType.FIELD)
-public class DatanodeMetadata {
+public final class DatanodeMetadata {
 
   @XmlElement(name = "hostname")
   private String hostname;
@@ -48,18 +49,17 @@ public class DatanodeMetadata {
   @XmlElement(name = "containers")
   private int containers;
 
-  public DatanodeMetadata(String hostname,
-                          NodeState state,
-                          long lastHeartbeat,
-                          DatanodeStorageReport storageReport,
-                          List<DatanodePipeline> pipelines,
-                          int containers) {
-    this.hostname = hostname;
-    this.state = state;
-    this.lastHeartbeat = lastHeartbeat;
-    this.datanodeStorageReport = storageReport;
-    this.pipelines = pipelines;
-    this.containers = containers;
+  @XmlElement(name = "leaderCount")
+  private int leaderCount;
+
+  private DatanodeMetadata(Builder builder) {
+    this.hostname = builder.hostname;
+    this.state = builder.state;
+    this.lastHeartbeat = builder.lastHeartbeat;
+    this.datanodeStorageReport = builder.datanodeStorageReport;
+    this.pipelines = builder.pipelines;
+    this.containers = builder.containers;
+    this.leaderCount = builder.leaderCount;
   }
 
   public String getHostname() {
@@ -85,4 +85,84 @@ public class DatanodeMetadata {
   public int getContainers() {
     return containers;
   }
+
+  public int getLeaderCount() {
+    return leaderCount;
+  }
+
+  /**
+   * Returns new builder class that builds a DatanodeMetadata.
+   *
+   * @return Builder
+   */
+  public static Builder newBuilder() {
+    return new Builder();
+  }
+
+  /**
+   * Builder for DatanodeMetadata.
+   */
+  @SuppressWarnings("checkstyle:hiddenfield")
+  public static final class Builder {
+    private String hostname;
+    private NodeState state;
+    private long lastHeartbeat;
+    private DatanodeStorageReport datanodeStorageReport;
+    private List<DatanodePipeline> pipelines;
+    private int containers;
+    private int leaderCount;
+
+    public Builder() {
+      this.containers = 0;
+      this.leaderCount = 0;
+    }
+
+    public Builder withHostname(String hostname) {
+      this.hostname = hostname;
+      return this;
+    }
+
+    public Builder withState(NodeState state) {
+      this.state = state;
+      return this;
+    }
+
+    public Builder withLastHeartbeat(long lastHeartbeat) {
+      this.lastHeartbeat = lastHeartbeat;
+      return this;
+    }
+
+    public Builder withDatanodeStorageReport(DatanodeStorageReport 
+                                                 datanodeStorageReport) {
+      this.datanodeStorageReport = datanodeStorageReport;
+      return this;
+    }
+
+    public Builder withPipelines(List<DatanodePipeline> pipelines) {
+      this.pipelines = pipelines;
+      return this;
+    }
+
+    public Builder withContainers(int containers) {
+      this.containers = containers;
+      return this;
+    }
+
+    public Builder withLeaderCount(int leaderCount) {
+      this.leaderCount = leaderCount;
+      return this;
+    }
+
+    /**
+     * Constructs DatanodeMetadata.
+     *
+     * @return instance of DatanodeMetadata.
+     */
+    public DatanodeMetadata build() {
+      Preconditions.checkNotNull(hostname);
+      Preconditions.checkNotNull(state);
+
+      return new DatanodeMetadata(this);
+    }
+  }
 }
diff --git 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/types/DatanodePipeline.java
 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/types/DatanodePipeline.java
index 6cc663b..cc38cd5 100644
--- 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/types/DatanodePipeline.java
+++ 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/types/DatanodePipeline.java
@@ -26,12 +26,14 @@ public class DatanodePipeline {
   private UUID pipelineID;
   private String replicationType;
   private int replicationFactor;
+  private String leaderNode;
 
   public DatanodePipeline(UUID pipelineID, String replicationType,
-                          int replicationFactor) {
+                          int replicationFactor, String leaderNode) {
     this.pipelineID = pipelineID;
     this.replicationType = replicationType;
     this.replicationFactor = replicationFactor;
+    this.leaderNode = leaderNode;
   }
 
   public UUID getPipelineID() {
@@ -45,4 +47,8 @@ public class DatanodePipeline {
   public int getReplicationFactor() {
     return replicationFactor;
   }
+
+  public String getLeaderNode() {
+    return leaderNode;
+  }
 }
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 a198bed..77d3a69 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
@@ -29,15 +29,18 @@
           {
             "pipelineID": "02e3d908-ff01-4ce6-ad75-f3ec79bcc71a",
             "replicationType": "RATIS",
-            "replicationFactor": 3
+            "replicationFactor": 3,
+            "leaderNode": "localhost1.storage.enterprise.com"
           },
           {
             "pipelineID": "09d3a478-ff01-4ce6-ad75-f3ec79bcc71a",
             "replicationType": "RATIS",
-            "replicationFactor": 1
+            "replicationFactor": 1,
+            "leaderNode": "localhost1.storage.enterprise.com"
           }
         ],
-        "containers": 80
+        "containers": 80,
+        "leaderCount": 2
       },
       {
         "hostname": "localhost2.storage.enterprise.com",
@@ -52,15 +55,18 @@
           {
             "pipelineID": "02e3d908-ff01-4ce6-ad75-f3ec79bcc71a",
             "replicationType": "RATIS",
-            "replicationFactor": 3
+            "replicationFactor": 3,
+            "leaderNode": "localhost1.storage.enterprise.com"
           },
           {
             "pipelineID": "05e3d908-ff01-4ce6-ad75-f3ec79bcc7982",
             "replicationType": "RATIS",
-            "replicationFactor": 1
+            "replicationFactor": 1,
+            "leaderNode": "localhost2.storage.enterprise.com"
           }
         ],
-        "containers": 8192
+        "containers": 8192,
+        "leaderCount": 1
       },
       {
         "hostname": "localhost3.storage.enterprise.com",
@@ -75,20 +81,24 @@
           {
             "pipelineID": "02e3d908-ff01-4ce6-ad75-f3ec79bcc71a",
             "replicationType": "RATIS",
-            "replicationFactor": 3
+            "replicationFactor": 3,
+            "leaderNode": "localhost1.storage.enterprise.com"
           },
           {
             "pipelineID": "05e3d908-ff01-4ce6-ad75-f3ec79bcc7982",
             "replicationType": "RATIS",
-            "replicationFactor": 1
+            "replicationFactor": 1,
+            "leaderNode": "localhost3.storage.enterprise.com"
           },
           {
             "pipelineID": "02e3d908-ff01-4ce6-ad75-f3ec79bcc71a",
             "replicationType": "STAND_ALONE",
-            "replicationFactor": 1
+            "replicationFactor": 1,
+            "leaderNode": "localhost3.storage.enterprise.com"
           }
         ],
-        "containers": 43
+        "containers": 43,
+        "leaderCount": 2
       },
       {
         "hostname": "localhost4.storage.enterprise.com",
@@ -100,7 +110,8 @@
           "remaining": 110737488355328
         },
         "pipelines": [],
-        "containers": 0
+        "containers": 0,
+        "leaderCount": 0
       },
       {
         "hostname": "localhost5.storage.enterprise.com",
@@ -115,15 +126,18 @@
           {
             "pipelineID": "02e3d908-ff01-4ce6-ad75-f3ec79bcc71a",
             "replicationType": "RATIS",
-            "replicationFactor": 3
+            "replicationFactor": 3,
+            "leaderNode": "localhost5.storage.enterprise.com"
           },
           {
             "pipelineID": "05e3d908-ff01-4ce6-ad75-f3ec79bcc7982",
             "replicationType": "RATIS",
-            "replicationFactor": 1
+            "replicationFactor": 1,
+            "leaderNode": "localhost5.storage.enterprise.com"
           }
         ],
-        "containers": 643
+        "containers": 643,
+        "leaderCount": 2
       },
       {
         "hostname": "localhost6.storage.enterprise.com",
@@ -138,15 +152,18 @@
           {
             "pipelineID": "02e3d908-ff01-4ce6-ad75-f3ec79bcc71a",
             "replicationType": "RATIS",
-            "replicationFactor": 3
+            "replicationFactor": 3,
+            "leaderNode": "localhost5.storage.enterprise.com"
           },
           {
             "pipelineID": "05e3d908-ff01-4ce6-ad75-f3ec79bcc7982",
             "replicationType": "RATIS",
-            "replicationFactor": 1
+            "replicationFactor": 1,
+            "leaderNode": "localhost6.storage.enterprise.com"
           }
         ],
-        "containers": 5
+        "containers": 5,
+        "leaderCount": 1
       },
       {
         "hostname": "localhost7.storage.enterprise.com",
@@ -161,20 +178,24 @@
           {
             "pipelineID": "02e3d908-ff01-4ce6-ad75-f3ec79bcc71a",
             "replicationType": "RATIS",
-            "replicationFactor": 3
+            "replicationFactor": 3,
+            "leaderNode": "localhost5.storage.enterprise.com"
           },
           {
             "pipelineID": "05e3d908-ff01-4ce6-ad75-f3ec79bcc7982",
             "replicationType": "RATIS",
-            "replicationFactor": 1
+            "replicationFactor": 1,
+            "leaderNode": "localhost7.storage.enterprise.com"
           },
           {
             "pipelineID": "05e3d908-ff01-4ce6-ad75-f3ec79bcc7982",
             "replicationType": "STAND_ALONE",
-            "replicationFactor": 1
+            "replicationFactor": 1,
+            "leaderNode": "localhost7.storage.enterprise.com"
           }
         ],
-        "containers": 64
+        "containers": 64,
+        "leaderCount": 2
       },
       {
         "hostname": "localhost8.storage.enterprise.com",
@@ -189,15 +210,18 @@
           {
             "pipelineID": "02e3d908-ff01-4ce6-ad75-f3ec79bcc71a",
             "replicationType": "RATIS",
-            "replicationFactor": 3
+            "replicationFactor": 3,
+            "leaderNode": "localhost5.storage.enterprise.com"
           },
           {
             "pipelineID": "05e3d908-ff01-4ce6-ad75-f3ec79bcc7982",
             "replicationType": "RATIS",
-            "replicationFactor": 1
+            "replicationFactor": 1,
+            "leaderNode": "localhost8.storage.enterprise.com"
           }
         ],
-        "containers": 21
+        "containers": 21,
+        "leaderCount": 1
       },
       {
         "hostname": "localhost9.storage.enterprise.com",
@@ -212,15 +236,18 @@
           {
             "pipelineID": "02e3d908-ff01-4ce6-ad75-f3ec79bcc71a",
             "replicationType": "RATIS",
-            "replicationFactor": 3
+            "replicationFactor": 3,
+            "leaderNode": "localhost11.storage.enterprise.com"
           },
           {
             "pipelineID": "05e3d908-ff01-4ce6-ad75-f3ec79bcc7982",
             "replicationType": "RATIS",
-            "replicationFactor": 1
+            "replicationFactor": 1,
+            "leaderNode": "localhost9.storage.enterprise.com"
           }
         ],
-        "containers": 897
+        "containers": 897,
+        "leaderCount": 1
       },
       {
         "hostname": "localhost10.storage.enterprise.com",
@@ -235,20 +262,24 @@
           {
             "pipelineID": "02e3d908-ff01-4ce6-ad75-f3ec79bcc71a",
             "replicationType": "RATIS",
-            "replicationFactor": 3
+            "replicationFactor": 3,
+            "leaderNode": "localhost11.storage.enterprise.com"
           },
           {
             "pipelineID": "05e3d908-ff01-4ce6-ad75-f3ec79bcc7982",
             "replicationType": "RATIS",
-            "replicationFactor": 1
+            "replicationFactor": 1,
+            "leaderNode": "localhost10.storage.enterprise.com"
           },
           {
             "pipelineID": "01f2e105-ff01-4ce6-ad75-f3ec79bcc7982",
             "replicationType": "STAND_ALONE",
-            "replicationFactor": 1
+            "replicationFactor": 1,
+            "leaderNode": "localhost10.storage.enterprise.com"
           }
         ],
-        "containers": 6754
+        "containers": 6754,
+        "leaderCount": 2
       },
       {
         "hostname": "localhost11.storage.enterprise.com",
@@ -263,15 +294,18 @@
           {
             "pipelineID": "02e3d908-ff01-4ce6-ad75-f3ec79bcc71a",
             "replicationType": "RATIS",
-            "replicationFactor": 3
+            "replicationFactor": 3,
+            "leaderNode": "localhost11.storage.enterprise.com"
           },
           {
             "pipelineID": "05e3d908-ff01-4ce6-ad75-f3ec79bcc7982",
             "replicationType": "RATIS",
-            "replicationFactor": 1
+            "replicationFactor": 1,
+            "leaderNode": "localhost11.storage.enterprise.com"
           }
         ],
-        "containers": 78
+        "containers": 78,
+        "leaderCount": 2
       },
       {
         "hostname": "localhost12.storage.enterprise.com",
@@ -286,15 +320,18 @@
           {
             "pipelineID": "02e3d908-ff01-4ce6-ad75-f3ec79bcc71a",
             "replicationType": "RATIS",
-            "replicationFactor": 3
+            "replicationFactor": 3,
+            "leaderNode": "localhost11.storage.enterprise.com"
           },
           {
             "pipelineID": "05e3d908-ff01-4ce6-ad75-f3ec79bcc7982",
             "replicationType": "RATIS",
-            "replicationFactor": 1
+            "replicationFactor": 1,
+            "leaderNode": "localhost12.storage.enterprise.com"
           }
         ],
-        "containers": 543
+        "containers": 543,
+        "leaderCount": 1
       }
     ]
   },
diff --git 
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/app.less
 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/app.less
index 3aae869..43fc19d 100644
--- 
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/app.less
+++ 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/app.less
@@ -126,13 +126,13 @@ body {
   .hexagon-shape(20, 12, @orange-7);
 }
 
-.icon-text(@content) {
+.icon-text(@content, @fontcolor) {
   text-align: center;
   font-size: 12px;
   font-weight: 700;
   position: relative;
   top: -3px;
-  color: #fff;
+  color: @fontcolor;
   &:after {
     content: @content;
     position: absolute;
@@ -140,7 +140,7 @@ body {
     top: 4px;
     width: 10px;
     height: 0;
-    color: white;
+    color: @fontcolor;
     font-size: 12px;
     z-index: 2;
   }
@@ -148,14 +148,23 @@ body {
 
 .icon-text-three-dots {
   // In Unicode, \2026 is the horizontal ellipsis (...)
-  .icon-text("\2026");
+  .icon-text("\2026", #fff);
+}
+
+.icon-text-three-dots-leader {
+  // In Unicode, \2026 is the horizontal ellipsis (...)
+  .icon-text("\2026", #ffde36);
 }
 
 .icon-text-one-dot {
-  .icon-text(".");
+  .icon-text(".", #ffde36);
 }
 
 .replication-icon {
   display: inline-block;
   margin-right: 5px;
 }
+
+.pointer {
+  cursor: pointer;
+}
\ No newline at end of file
diff --git 
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/utils/themeIcons.tsx
 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/utils/themeIcons.tsx
index 7acb026..9868638 100644
--- 
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/utils/themeIcons.tsx
+++ 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/utils/themeIcons.tsx
@@ -35,17 +35,21 @@ export class FilledIcon extends React.Component {
 
 interface IRatisIconProps {
   replicationFactor: number;
+  isLeader: boolean;
 }
 
 interface IReplicationIconProps {
   replicationFactor: number;
   replicationType: string;
+  leaderNode: string;
+  isLeader: boolean;
 }
 
 export class RatisIcon extends React.PureComponent<IRatisIconProps> {
   render() {
-    const {replicationFactor} = this.props;
-    const textClass = replicationFactor >= 3 ? 'icon-text-three-dots' : 
'icon-text-one-dot';
+    const {replicationFactor, isLeader} = this.props;
+    const threeFactorClass = isLeader ? 'icon-text-three-dots-leader' : 
'icon-text-three-dots';
+    const textClass = replicationFactor >= 3 ? threeFactorClass : 
'icon-text-one-dot';
     return (
       <div className='ratis-icon'>
         <div className={textClass}>R</div>
@@ -66,11 +70,11 @@ export class StandaloneIcon extends React.PureComponent {
 
 export class ReplicationIcon extends 
React.PureComponent<IReplicationIconProps> {
   render() {
-    const {replicationType, replicationFactor} = this.props;
+    const {replicationType, replicationFactor, isLeader, leaderNode} = 
this.props;
     // Assign icons only for RATIS and STAND_ALONE types
     let icon = null;
     if (replicationType === 'RATIS') {
-      icon = <RatisIcon replicationFactor={replicationFactor}/>;
+      icon = <RatisIcon replicationFactor={replicationFactor} 
isLeader={isLeader}/>;
     } else if (replicationType === 'STAND_ALONE') {
       icon = <StandaloneIcon/>;
     }
@@ -81,10 +85,11 @@ export class ReplicationIcon extends 
React.PureComponent<IReplicationIconProps>
         <div>
           <div>Replication Type: {replicationType}</div>
           <div>Replication Factor: {replicationFactor}</div>
+          <div>Leader Node: {leaderNode}</div>
         </div>
       );
       icon = (
-        <Tooltip title={tooltip} placement='right'>
+        <Tooltip title={tooltip} placement='right' className='pointer'>
           <div className='replication-icon'>{icon}</div>
         </Tooltip>
       );
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 f3e510a..feb5b6f 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
@@ -18,7 +18,7 @@
 
 import React from 'react';
 import axios from 'axios';
-import {Table, Icon} from 'antd';
+import {Table, Icon, Tooltip} from 'antd';
 import {PaginationConfig} from 'antd/lib/pagination';
 import moment from 'moment';
 import {ReplicationIcon} from 'utils/themeIcons';
@@ -36,6 +36,7 @@ interface IDatanodeResponse {
   storageReport: IStorageReport;
   pipelines: IPipeline[];
   containers: number;
+  leaderCount: number;
 }
 
 interface IDatanodesResponse {
@@ -52,12 +53,14 @@ interface IDatanode {
   storageRemaining: number;
   pipelines: IPipeline[];
   containers: number;
+  leaderCount: number;
 }
 
 interface IPipeline {
   pipelineID: string;
   replicationType: string;
   replicationFactor: number;
+  leaderNode: string;
 }
 
 interface IDatanodesState {
@@ -117,13 +120,16 @@ const COLUMNS = [
     title: 'Pipeline ID(s)',
     dataIndex: 'pipelines',
     key: 'pipelines',
-    render: (pipelines: IPipeline[]) => {
+    render: (pipelines: IPipeline[], record: IDatanode) => {
       return (
         <div>
           {
             pipelines.map((pipeline, index) => (
               <div key={index} className='pipeline-container'>
-                <ReplicationIcon 
replicationFactor={pipeline.replicationFactor} 
replicationType={pipeline.replicationType}/>
+                <ReplicationIcon replicationFactor={pipeline.replicationFactor}
+                                 replicationType={pipeline.replicationType}
+                                 leaderNode={pipeline.leaderNode}
+                                 isLeader={pipeline.leaderNode === 
record.hostname}/>
                 {pipeline.pipelineID}
               </div>
             ))
@@ -133,6 +139,17 @@ const COLUMNS = [
     }
   },
   {
+    title: <span>
+      Leader Count&nbsp;
+      <Tooltip title='The number of Ratis Pipelines in which the given 
datanode is elected as a leader.'>
+        <Icon type='info-circle'/>
+      </Tooltip>
+    </span>,
+    dataIndex: 'leaderCount',
+    key: 'leaderCount',
+    sorter: (a: IDatanode, b: IDatanode) => a.leaderCount - b.leaderCount
+  },
+  {
     title: 'Containers',
     dataIndex: 'containers',
     key: 'containers',
@@ -171,7 +188,8 @@ export class Datanodes extends 
React.Component<Record<string, object>, IDatanode
           storageTotal: datanode.storageReport.capacity,
           storageRemaining: datanode.storageReport.remaining,
           pipelines: datanode.pipelines,
-          containers: datanode.containers
+          containers: datanode.containers,
+          leaderCount: datanode.leaderCount
         };
       });
       this.setState({
diff --git 
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/pipelines/pipelines.tsx
 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/pipelines/pipelines.tsx
index 60b09b6..f339060 100644
--- 
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/pipelines/pipelines.tsx
+++ 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/pipelines/pipelines.tsx
@@ -72,7 +72,10 @@ const COLUMNS = [
       const replicationFactor = record.replicationFactor;
       return (
         <span>
-          <ReplicationIcon replicationFactor={replicationFactor} 
replicationType={replicationType}/>
+          <ReplicationIcon replicationFactor={replicationFactor}
+                           replicationType={replicationType}
+                           leaderNode={record.leaderNode}
+                           isLeader={false}/>
           {replicationType} ({replicationFactor})
         </span>
       );
diff --git 
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestEndpoints.java
 
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestEndpoints.java
index 66c015c..9234131 100644
--- 
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestEndpoints.java
+++ 
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestEndpoints.java
@@ -90,6 +90,10 @@ public class TestEndpoints extends AbstractReconSqlDBTest {
   private ContainerReportsProto containerReportsProto;
   private DatanodeDetailsProto datanodeDetailsProto;
   private Pipeline pipeline;
+  private final String host1 = "host1.datanode";
+  private final String host2 = "host2.datanode";
+  private final String ip1 = "1.1.1.1";
+  private final String ip2 = "2.2.2.2";
 
   @Rule
   public TemporaryFolder temporaryFolder = new TemporaryFolder();
@@ -100,6 +104,10 @@ public class TestEndpoints extends AbstractReconSqlDBTest {
         temporaryFolder.newFolder());
     datanodeDetails = randomDatanodeDetails();
     datanodeDetails2 = randomDatanodeDetails();
+    datanodeDetails.setHostName(host1);
+    datanodeDetails.setIpAddress(ip1);
+    datanodeDetails2.setHostName(host2);
+    datanodeDetails2.setIpAddress(ip2);
     pipeline = getRandomPipeline(datanodeDetails);
     pipelineId = pipeline.getId().getId().toString();
 
@@ -175,9 +183,9 @@ public class TestEndpoints extends AbstractReconSqlDBTest {
             .addPipelineReport(pipelineReport).build();
     datanodeDetailsProto =
         DatanodeDetailsProto.newBuilder()
-            .setHostName("host1.datanode")
+            .setHostName(host1)
             .setUuid(datanodeId)
-            .setIpAddress("1.1.1.1")
+            .setIpAddress(ip1)
             .build();
     StorageReportProto storageReportProto1 =
         StorageReportProto.newBuilder().setStorageType(StorageTypeProto.DISK)
@@ -198,9 +206,9 @@ public class TestEndpoints extends AbstractReconSqlDBTest {
 
     DatanodeDetailsProto datanodeDetailsProto2 =
         DatanodeDetailsProto.newBuilder()
-        .setHostName("host2.datanode")
+        .setHostName(host2)
         .setUuid(datanodeId2)
-        .setIpAddress("2.2.2.2")
+        .setIpAddress(ip2)
         .build();
     StorageReportProto storageReportProto3 =
         StorageReportProto.newBuilder().setStorageType(StorageTypeProto.DISK)
@@ -267,10 +275,11 @@ public class TestEndpoints extends AbstractReconSqlDBTest 
{
     writeDataToOm(reconOMMetadataManager, "key_three");
   }
 
-  private void testDatanodeResponse(DatanodeMetadata datanodeMetadata) {
+  private void testDatanodeResponse(DatanodeMetadata datanodeMetadata)
+      throws IOException {
     String hostname = datanodeMetadata.getHostname();
     switch (hostname) {
-    case "host1.datanode":
+    case host1:
       Assert.assertEquals(75000,
           datanodeMetadata.getDatanodeStorageReport().getCapacity());
       Assert.assertEquals(15400,
@@ -285,8 +294,11 @@ public class TestEndpoints extends AbstractReconSqlDBTest {
           datanodeMetadata.getPipelines().get(0).getReplicationFactor());
       Assert.assertEquals(pipeline.getType().toString(),
           datanodeMetadata.getPipelines().get(0).getReplicationType());
+      Assert.assertEquals(pipeline.getLeaderNode().getHostName(),
+          datanodeMetadata.getPipelines().get(0).getLeaderNode());
+      Assert.assertEquals(1, datanodeMetadata.getLeaderCount());
       break;
-    case "host2.datanode":
+    case host2:
       Assert.assertEquals(130000,
           datanodeMetadata.getDatanodeStorageReport().getCapacity());
       Assert.assertEquals(17800,
@@ -295,6 +307,7 @@ public class TestEndpoints extends AbstractReconSqlDBTest {
           datanodeMetadata.getDatanodeStorageReport().getUsed());
 
       Assert.assertEquals(0, datanodeMetadata.getPipelines().size());
+      Assert.assertEquals(0, datanodeMetadata.getLeaderCount());
       break;
     default:
       Assert.fail(String.format("Datanode %s not registered",
@@ -310,7 +323,13 @@ public class TestEndpoints extends AbstractReconSqlDBTest {
     Assert.assertEquals(2, datanodesResponse.getTotalCount());
     Assert.assertEquals(2, datanodesResponse.getDatanodes().size());
 
-    datanodesResponse.getDatanodes().forEach(this::testDatanodeResponse);
+    datanodesResponse.getDatanodes().forEach(datanodeMetadata -> {
+      try {
+        testDatanodeResponse(datanodeMetadata);
+      } catch (IOException e) {
+        Assert.fail(e.getMessage());
+      }
+    });
 
     waitAndCheckConditionAfterHeartbeat(() -> {
       Response response1 = nodeEndpoint.getDatanodes();


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to