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

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


The following commit(s) were added to refs/heads/master by this push:
     new dc6f279  HDDS-6205. Add CLI command to display the latest Replication 
Manager report (#3013)
dc6f279 is described below

commit dc6f27920c756d30fe7979872d3834abd2dc241a
Author: Stephen O'Donnell <[email protected]>
AuthorDate: Fri Jan 28 15:50:38 2022 +0000

    HDDS-6205. Add CLI command to display the latest Replication Manager report 
(#3013)
---
 .../apache/hadoop/hdds/scm/client/ScmClient.java   |   9 ++
 .../scm/container/ReplicationManagerReport.java    |  95 +++++++++++-
 .../protocol/StorageContainerLocationProtocol.java |   9 ++
 .../org/apache/hadoop/ozone/audit/SCMAction.java   |   3 +-
 .../container/TestReplicationManagerReport.java    |  49 +++++++
 ...inerLocationProtocolClientSideTranslatorPB.java |  17 +++
 .../src/main/proto/ScmAdminProtocol.proto          |  11 ++
 .../interface-client/src/main/proto/hdds.proto     |  16 +++
 .../hdds/scm/container/ReplicationManager.java     |   5 +
 ...inerLocationProtocolServerSideTranslatorPB.java |  16 +++
 .../hdds/scm/server/SCMClientProtocolServer.java   |  10 ++
 .../hdds/scm/cli/ContainerOperationClient.java     |   7 +
 .../hdds/scm/cli/container/ContainerCommands.java  |   3 +-
 .../hdds/scm/cli/container/ReportSubcommand.java   | 116 +++++++++++++++
 .../scm/cli/container/TestReportSubCommand.java    | 159 +++++++++++++++++++++
 15 files changed, 521 insertions(+), 4 deletions(-)

diff --git 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/client/ScmClient.java
 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/client/ScmClient.java
index b1bf420..f1885f8 100644
--- 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/client/ScmClient.java
+++ 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/client/ScmClient.java
@@ -21,6 +21,7 @@ import org.apache.commons.lang3.tuple.Pair;
 import org.apache.hadoop.hdds.annotation.InterfaceStability;
 import org.apache.hadoop.hdds.scm.DatanodeAdminError;
 import org.apache.hadoop.hdds.scm.container.ContainerReplicaInfo;
+import org.apache.hadoop.hdds.scm.container.ReplicationManagerReport;
 import 
org.apache.hadoop.hdds.scm.container.common.helpers.ContainerWithPipeline;
 import org.apache.hadoop.hdds.scm.container.ContainerInfo;
 import org.apache.hadoop.hdds.scm.pipeline.Pipeline;
@@ -320,6 +321,14 @@ public interface ScmClient extends Closeable {
   boolean getReplicationManagerStatus() throws IOException;
 
   /**
+   * Returns the latest container summary report generated by Replication
+   * Manager.
+   * @return The latest ReplicationManagerReport.
+   * @throws IOException
+   */
+  ReplicationManagerReport getReplicationManagerReport() throws IOException;
+
+  /**
    * Start ContainerBalancer.
    */
   boolean startContainerBalancer(Optional<Double> threshold,
diff --git 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/ReplicationManagerReport.java
 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/container/ReplicationManagerReport.java
similarity index 65%
rename from 
hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/ReplicationManagerReport.java
rename to 
hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/container/ReplicationManagerReport.java
index 21bde49..6f6caf3 100644
--- 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/ReplicationManagerReport.java
+++ 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/container/ReplicationManagerReport.java
@@ -26,6 +26,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.LongAdder;
+import java.util.stream.Collectors;
 
 /**
  * This class is used by ReplicationManager. Each time ReplicationManager runs,
@@ -96,6 +97,22 @@ public class ReplicationManagerReport {
   private final Map<String, List<ContainerID>> containerSample
       = new ConcurrentHashMap<>();
 
+  public static ReplicationManagerReport fromProtobuf(
+      HddsProtos.ReplicationManagerReportProto proto) {
+    ReplicationManagerReport report = new ReplicationManagerReport();
+    report.setTimestamp(proto.getTimestamp());
+    for (HddsProtos.KeyIntValue stat : proto.getStatList()) {
+      report.setStat(stat.getKey(), stat.getValue());
+    }
+    for (HddsProtos.KeyContainerIDList sample : proto.getStatSampleList()) {
+      report.setSample(sample.getKey(), sample.getContainerList()
+          .stream()
+          .map(c -> ContainerID.getFromProtobuf(c))
+          .collect(Collectors.toList()));
+    }
+    return report;
+  }
+
   public ReplicationManagerReport() {
     stats = createStatsMap();
   }
@@ -129,16 +146,62 @@ public class ReplicationManagerReport {
     return reportTimeStamp;
   }
 
+  /**
+   * Get the stat for the given LifeCycleState. If there is no stat available
+   * for that stat -1 is returned.
+   * @param stat The requested stat.
+   * @return The stat value or -1 if it is not present
+   */
   public long getStat(HddsProtos.LifeCycleState stat) {
     return getStat(stat.toString());
   }
 
+  /**
+   * Get the stat for the given HealthState. If there is no stat available
+   * for that stat -1 is returned.
+   * @param stat The requested stat.
+   * @return The stat value or -1 if it is not present
+   */
   public long getStat(HealthState stat) {
     return getStat(stat.toString());
   }
 
+  /**
+   * Returns the stat requested, or -1 if it does not exist.
+   * @param stat The request stat
+   * @return The value of the stat or -1 if it does not exist.
+   */
   private long getStat(String stat) {
-    return stats.get(stat).longValue();
+    LongAdder val = stats.get(stat);
+    if (val == null) {
+      return -1;
+    }
+    return val.longValue();
+  }
+
+  protected void setTimestamp(long timestamp) {
+    this.reportTimeStamp = timestamp;
+  }
+
+  protected void setStat(String stat, long value) {
+    LongAdder adder = getStatAndEnsurePresent(stat);
+    if (adder.longValue() != 0) {
+      throw new IllegalStateException(stat + " is expected to be zero");
+    }
+    adder.add(value);
+  }
+
+  protected void setSample(String stat, List<ContainerID> sample) {
+    // First get the stat, as we should not receive a sample for a stat which
+    // does not exist.
+    getStatAndEnsurePresent(stat);
+    // Now check there is not already a sample for this stat
+    List<ContainerID> existingSample = containerSample.get(stat);
+    if (existingSample != null) {
+      throw new IllegalStateException(stat
+          + " is not expected to have existing samples");
+    }
+    containerSample.put(stat, sample);
   }
 
   public List<ContainerID> getSample(HddsProtos.LifeCycleState stat) {
@@ -160,11 +223,15 @@ public class ReplicationManagerReport {
   }
 
   private void increment(String stat) {
+    getStatAndEnsurePresent(stat).increment();
+  }
+
+  private LongAdder getStatAndEnsurePresent(String stat) {
     LongAdder adder = stats.get(stat);
     if (adder == null) {
       throw new IllegalArgumentException("Unexpected stat " + stat);
     }
-    adder.increment();
+    return adder;
   }
 
   private void incrementAndSample(String stat, ContainerID container) {
@@ -189,4 +256,28 @@ public class ReplicationManagerReport {
     return map;
   }
 
+  public HddsProtos.ReplicationManagerReportProto toProtobuf() {
+    HddsProtos.ReplicationManagerReportProto.Builder proto =
+        HddsProtos.ReplicationManagerReportProto.newBuilder();
+    proto.setTimestamp(getReportTimeStamp());
+
+    for (Map.Entry<String, LongAdder> e : stats.entrySet()) {
+      proto.addStat(HddsProtos.KeyIntValue.newBuilder()
+          .setKey(e.getKey())
+          .setValue(e.getValue().longValue())
+          .build());
+    }
+
+    for (Map.Entry<String, List<ContainerID>> e : containerSample.entrySet()) {
+      HddsProtos.KeyContainerIDList.Builder sample
+          = HddsProtos.KeyContainerIDList.newBuilder();
+      sample.setKey(e.getKey());
+      for (ContainerID container : e.getValue()) {
+        sample.addContainer(container.getProtobuf());
+      }
+      proto.addStatSample(sample.build());
+    }
+    return proto.build();
+  }
+
 }
diff --git 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/protocol/StorageContainerLocationProtocol.java
 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/protocol/StorageContainerLocationProtocol.java
index 579f351..9f78b31 100644
--- 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/protocol/StorageContainerLocationProtocol.java
+++ 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/protocol/StorageContainerLocationProtocol.java
@@ -25,6 +25,7 @@ import org.apache.hadoop.hdds.scm.ScmConfig;
 import org.apache.hadoop.hdds.scm.ScmInfo;
 import org.apache.hadoop.hdds.scm.container.ContainerID;
 import org.apache.hadoop.hdds.scm.container.ContainerInfo;
+import org.apache.hadoop.hdds.scm.container.ReplicationManagerReport;
 import 
org.apache.hadoop.hdds.scm.container.common.helpers.ContainerWithPipeline;
 import org.apache.hadoop.hdds.scm.pipeline.Pipeline;
 import org.apache.hadoop.ozone.upgrade.UpgradeFinalizer.StatusAndMessages;
@@ -318,6 +319,14 @@ public interface StorageContainerLocationProtocol extends 
Closeable {
   boolean getReplicationManagerStatus() throws IOException;
 
   /**
+   * Returns the latest container summary report generated by Replication
+   * Manager.
+   * @return The latest ReplicationManagerReport.
+   * @throws IOException
+   */
+  ReplicationManagerReport getReplicationManagerReport() throws IOException;
+
+  /**
    * Start ContainerBalancer.
    */
   boolean startContainerBalancer(Optional<Double> threshold,
diff --git 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/audit/SCMAction.java 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/audit/SCMAction.java
index 9b88c6a..3c1c209 100644
--- 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/audit/SCMAction.java
+++ 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/audit/SCMAction.java
@@ -48,7 +48,8 @@ public enum SCMAction implements AuditAction {
   STOP_CONTAINER_BALANCER,
   GET_CONTAINER_BALANCER_STATUS,
   GET_CONTAINER_WITH_PIPELINE_BATCH,
-  ADD_SCM;
+  ADD_SCM,
+  GET_REPLICATION_MANAGER_REPORT;
 
   @Override
   public String getAction() {
diff --git 
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/TestReplicationManagerReport.java
 
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/scm/container/TestReplicationManagerReport.java
similarity index 67%
rename from 
hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/TestReplicationManagerReport.java
rename to 
hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/scm/container/TestReplicationManagerReport.java
index 15aca61..a05f9ab 100644
--- 
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/TestReplicationManagerReport.java
+++ 
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/scm/container/TestReplicationManagerReport.java
@@ -22,7 +22,10 @@ import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
+import java.util.ArrayList;
 import java.util.List;
+import java.util.Random;
+import java.util.concurrent.ThreadLocalRandom;
 
 /**
  * Tests for the ReplicationManagerReport class.
@@ -110,4 +113,50 @@ public class TestReplicationManagerReport {
       Assert.assertEquals(new ContainerID(i), sample.get(i));
     }
   }
+
+  @Test
+  public void testSerializeToProtoAndBack() {
+    report.setTimestamp(12345);
+    Random rand = ThreadLocalRandom.current();
+    for (HddsProtos.LifeCycleState s : HddsProtos.LifeCycleState.values()) {
+      report.setStat(s.toString(), rand.nextInt(Integer.MAX_VALUE));
+    }
+    for (ReplicationManagerReport.HealthState s :
+        ReplicationManagerReport.HealthState.values()) {
+      report.setStat(s.toString(), rand.nextInt(Integer.MAX_VALUE));
+      List<ContainerID> containers = new ArrayList<>();
+      for (int i = 0; i < 10; i++) {
+        containers.add(ContainerID.valueOf(rand.nextInt(Integer.MAX_VALUE)));
+      }
+      report.setSample(s.toString(), containers);
+    }
+    HddsProtos.ReplicationManagerReportProto proto = report.toProtobuf();
+    ReplicationManagerReport newReport
+        = ReplicationManagerReport.fromProtobuf(proto);
+    Assert.assertEquals(report.getReportTimeStamp(),
+        newReport.getReportTimeStamp());
+
+    for (HddsProtos.LifeCycleState s : HddsProtos.LifeCycleState.values()) {
+      Assert.assertEquals(report.getStat(s), newReport.getStat(s));
+    }
+
+    for (ReplicationManagerReport.HealthState s :
+        ReplicationManagerReport.HealthState.values()) {
+      Assert.assertTrue(report.getSample(s).equals(newReport.getSample(s)));
+    }
+  }
+
+  @Test(expected = IllegalStateException.class)
+  public void testStatCannotBeSetTwice() {
+    report.setStat(HddsProtos.LifeCycleState.CLOSED.toString(), 10);
+    report.setStat(HddsProtos.LifeCycleState.CLOSED.toString(), 10);
+  }
+
+  @Test(expected = IllegalStateException.class)
+  public void testSampleCannotBeSetTwice() {
+    List<ContainerID> containers = new ArrayList<>();
+    containers.add(ContainerID.valueOf(1));
+    report.setSample(HddsProtos.LifeCycleState.CLOSED.toString(), containers);
+    report.setSample(HddsProtos.LifeCycleState.CLOSED.toString(), containers);
+  }
 }
diff --git 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/scm/protocolPB/StorageContainerLocationProtocolClientSideTranslatorPB.java
 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/scm/protocolPB/StorageContainerLocationProtocolClientSideTranslatorPB.java
index 62139eb..b26c0da 100644
--- 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/scm/protocolPB/StorageContainerLocationProtocolClientSideTranslatorPB.java
+++ 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/scm/protocolPB/StorageContainerLocationProtocolClientSideTranslatorPB.java
@@ -64,6 +64,8 @@ import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolPro
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.PipelineResponseProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.RecommissionNodesRequestProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.RecommissionNodesResponseProto;
+import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.ReplicationManagerReportRequestProto;
+import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.ReplicationManagerReportResponseProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.ReplicationManagerStatusRequestProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.ReplicationManagerStatusResponseProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.ContainerBalancerStatusRequestProto;
@@ -87,6 +89,7 @@ import org.apache.hadoop.hdds.scm.DatanodeAdminError;
 import org.apache.hadoop.hdds.scm.ScmInfo;
 import org.apache.hadoop.hdds.scm.container.ContainerID;
 import org.apache.hadoop.hdds.scm.container.ContainerInfo;
+import org.apache.hadoop.hdds.scm.container.ReplicationManagerReport;
 import 
org.apache.hadoop.hdds.scm.container.common.helpers.ContainerWithPipeline;
 import org.apache.hadoop.hdds.scm.pipeline.Pipeline;
 import org.apache.hadoop.hdds.scm.protocol.StorageContainerLocationProtocol;
@@ -759,6 +762,20 @@ public final class 
StorageContainerLocationProtocolClientSideTranslatorPB
   }
 
   @Override
+  public ReplicationManagerReport getReplicationManagerReport()
+      throws IOException {
+    ReplicationManagerReportRequestProto request =
+        ReplicationManagerReportRequestProto.newBuilder()
+            .setTraceID(TracingUtil.exportCurrentSpan())
+            .build();
+    ReplicationManagerReportResponseProto response =
+        submitRequest(Type.GetReplicationManagerReport,
+            builder -> builder.setReplicationManagerReportRequest(request))
+        .getGetReplicationManagerReportResponse();
+    return ReplicationManagerReport.fromProtobuf(response.getReport());
+  }
+
+  @Override
   public boolean startContainerBalancer(
       Optional<Double> threshold, Optional<Integer> iterations,
       Optional<Integer> maxDatanodesPercentageToInvolvePerIteration,
diff --git a/hadoop-hdds/interface-admin/src/main/proto/ScmAdminProtocol.proto 
b/hadoop-hdds/interface-admin/src/main/proto/ScmAdminProtocol.proto
index 313a7c8..e2d7b16 100644
--- a/hadoop-hdds/interface-admin/src/main/proto/ScmAdminProtocol.proto
+++ b/hadoop-hdds/interface-admin/src/main/proto/ScmAdminProtocol.proto
@@ -76,6 +76,7 @@ message ScmContainerLocationRequest {
   optional QueryUpgradeFinalizationProgressRequestProto 
queryUpgradeFinalizationProgressRequest = 37;
   optional GetContainerCountRequestProto getContainerCountRequest = 38;
   optional GetContainerReplicasRequestProto getContainerReplicasRequest = 39;
+  optional ReplicationManagerReportRequestProto 
replicationManagerReportRequest = 40;
 }
 
 message ScmContainerLocationResponse {
@@ -123,6 +124,7 @@ message ScmContainerLocationResponse {
   optional QueryUpgradeFinalizationProgressResponseProto 
queryUpgradeFinalizationProgressResponse = 37;
   optional GetContainerCountResponseProto getContainerCountResponse = 38;
   optional GetContainerReplicasResponseProto getContainerReplicasResponse = 39;
+  optional ReplicationManagerReportResponseProto 
getReplicationManagerReportResponse = 40;
 
   enum Status {
     OK = 1;
@@ -168,6 +170,7 @@ enum Type {
   QueryUpgradeFinalizationProgress = 32;
   GetContainerCount = 33;
   GetContainerReplicas = 34;
+  GetReplicationManagerReport = 35;
 }
 
 /**
@@ -468,6 +471,14 @@ message ReplicationManagerStatusResponseProto {
   required bool isRunning = 1;
 }
 
+message ReplicationManagerReportRequestProto {
+  optional string traceID = 1;
+}
+
+message ReplicationManagerReportResponseProto {
+  required ReplicationManagerReportProto report = 1;
+}
+
 message FinalizeScmUpgradeRequestProto {
   required string upgradeClientId = 1;
 }
diff --git a/hadoop-hdds/interface-client/src/main/proto/hdds.proto 
b/hadoop-hdds/interface-client/src/main/proto/hdds.proto
index bc1b35a..b55531e 100644
--- a/hadoop-hdds/interface-client/src/main/proto/hdds.proto
+++ b/hadoop-hdds/interface-client/src/main/proto/hdds.proto
@@ -388,3 +388,19 @@ message SCMContainerReplicaProto {
     required int64 keyCount = 6;
     required int64 bytesUsed = 7;
 }
+
+message KeyContainerIDList {
+    required string key = 1;
+    repeated ContainerID container = 2;
+}
+
+message KeyIntValue {
+    required string key = 1;
+    optional int64 value = 2;
+}
+
+message ReplicationManagerReportProto {
+    required int64 timestamp = 1;
+    repeated KeyIntValue stat = 2;
+    repeated KeyContainerIDList statSample = 3;
+}
diff --git 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/ReplicationManager.java
 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/ReplicationManager.java
index 224b1f9..7f52a06 100644
--- 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/ReplicationManager.java
+++ 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/ReplicationManager.java
@@ -370,6 +370,11 @@ public class ReplicationManager implements SCMService {
    * This in intended to be used in tests.
    */
   public synchronized void processAll() {
+    if (!shouldRun()) {
+      LOG.info("Replication Manager is not ready to run until {}ms after " +
+          "safemode exit", waitTimeInMillis);
+      return;
+    }
     final long start = clock.millis();
     final List<ContainerInfo> containers =
         containerManager.getContainers();
diff --git 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/protocol/StorageContainerLocationProtocolServerSideTranslatorPB.java
 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/protocol/StorageContainerLocationProtocolServerSideTranslatorPB.java
index 17da776..0370623 100644
--- 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/protocol/StorageContainerLocationProtocolServerSideTranslatorPB.java
+++ 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/protocol/StorageContainerLocationProtocolServerSideTranslatorPB.java
@@ -70,6 +70,8 @@ import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolPro
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.QueryUpgradeFinalizationProgressResponseProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.RecommissionNodesRequestProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.RecommissionNodesResponseProto;
+import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.ReplicationManagerReportRequestProto;
+import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.ReplicationManagerReportResponseProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.ReplicationManagerStatusRequestProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.ReplicationManagerStatusResponseProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.SCMCloseContainerRequestProto;
@@ -323,6 +325,13 @@ public final class 
StorageContainerLocationProtocolServerSideTranslatorPB
             .setReplicationManagerStatusResponse(getReplicationManagerStatus(
                 request.getSeplicationManagerStatusRequest()))
             .build();
+      case GetReplicationManagerReport:
+        return ScmContainerLocationResponse.newBuilder()
+            .setCmdType(request.getCmdType())
+            .setStatus(Status.OK)
+            
.setGetReplicationManagerReportResponse(getReplicationManagerReport(
+                request.getReplicationManagerReportRequest()))
+            .build();
       case StartContainerBalancer:
         return ScmContainerLocationResponse.newBuilder()
             .setCmdType(request.getCmdType())
@@ -731,6 +740,13 @@ public final class 
StorageContainerLocationProtocolServerSideTranslatorPB
         .setIsRunning(impl.getReplicationManagerStatus()).build();
   }
 
+  public ReplicationManagerReportResponseProto getReplicationManagerReport(
+      ReplicationManagerReportRequestProto request) throws IOException {
+    return ReplicationManagerReportResponseProto.newBuilder()
+        .setReport(impl.getReplicationManagerReport().toProtobuf())
+        .build();
+  }
+
   public StartContainerBalancerResponseProto startContainerBalancer(
       StartContainerBalancerRequestProto request)
       throws IOException {
diff --git 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMClientProtocolServer.java
 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMClientProtocolServer.java
index 0cdb571..59e4b2b 100644
--- 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMClientProtocolServer.java
+++ 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMClientProtocolServer.java
@@ -40,6 +40,7 @@ import org.apache.hadoop.hdds.scm.container.ContainerID;
 import org.apache.hadoop.hdds.scm.container.ContainerInfo;
 import org.apache.hadoop.hdds.scm.container.ContainerNotFoundException;
 import org.apache.hadoop.hdds.scm.container.ContainerReplica;
+import org.apache.hadoop.hdds.scm.container.ReplicationManagerReport;
 import 
org.apache.hadoop.hdds.scm.container.balancer.ContainerBalancerConfiguration;
 import 
org.apache.hadoop.hdds.scm.container.common.helpers.ContainerWithPipeline;
 import org.apache.hadoop.hdds.scm.container.placement.metrics.SCMNodeStat;
@@ -733,6 +734,15 @@ public class SCMClientProtocolServer implements
   }
 
   @Override
+  public ReplicationManagerReport getReplicationManagerReport()
+      throws IOException {
+    getScm().checkAdminAccess(getRemoteUser());
+    AUDIT.logWriteSuccess(buildAuditMessageForSuccess(
+        SCMAction.GET_REPLICATION_MANAGER_REPORT, null));
+    return scm.getReplicationManager().getContainerReport();
+  }
+
+  @Override
   public StatusAndMessages finalizeScmUpgrade(String upgradeClientID) throws
       IOException {
     // check admin authorization
diff --git 
a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/ContainerOperationClient.java
 
b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/ContainerOperationClient.java
index ca4e41b..4fbabf9 100644
--- 
a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/ContainerOperationClient.java
+++ 
b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/ContainerOperationClient.java
@@ -34,6 +34,7 @@ import org.apache.hadoop.hdds.scm.client.ScmClient;
 import org.apache.hadoop.hdds.scm.container.ContainerID;
 import org.apache.hadoop.hdds.scm.container.ContainerInfo;
 import org.apache.hadoop.hdds.scm.container.ContainerReplicaInfo;
+import org.apache.hadoop.hdds.scm.container.ReplicationManagerReport;
 import 
org.apache.hadoop.hdds.scm.container.common.helpers.ContainerWithPipeline;
 import org.apache.hadoop.hdds.scm.pipeline.Pipeline;
 import org.apache.hadoop.hdds.scm.protocol.StorageContainerLocationProtocol;
@@ -572,6 +573,12 @@ public class ContainerOperationClient implements ScmClient 
{
   }
 
   @Override
+  public ReplicationManagerReport getReplicationManagerReport()
+      throws IOException {
+    return storageContainerLocationClient.getReplicationManagerReport();
+  }
+
+  @Override
   public boolean startContainerBalancer(
       Optional<Double> threshold, Optional<Integer> iterations,
       Optional<Integer> maxDatanodesPercentageToInvolvePerIteration,
diff --git 
a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/container/ContainerCommands.java
 
b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/container/ContainerCommands.java
index 00d678c..f9dfc3f 100644
--- 
a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/container/ContainerCommands.java
+++ 
b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/container/ContainerCommands.java
@@ -44,7 +44,8 @@ import picocli.CommandLine.Spec;
         ListSubcommand.class,
         InfoSubcommand.class,
         CreateSubcommand.class,
-        CloseSubcommand.class
+        CloseSubcommand.class,
+        ReportSubcommand.class
     })
 @MetaInfServices(SubcommandWithParent.class)
 public class ContainerCommands implements Callable<Void>, SubcommandWithParent 
{
diff --git 
a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/container/ReportSubcommand.java
 
b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/container/ReportSubcommand.java
new file mode 100644
index 0000000..89b1a11
--- /dev/null
+++ 
b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/container/ReportSubcommand.java
@@ -0,0 +1,116 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.
+ */
+package org.apache.hadoop.hdds.scm.cli.container;
+
+import org.apache.hadoop.hdds.cli.HddsVersionProvider;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.scm.cli.ScmSubcommand;
+import org.apache.hadoop.hdds.scm.client.ScmClient;
+import org.apache.hadoop.hdds.scm.container.ContainerID;
+import org.apache.hadoop.hdds.scm.container.ReplicationManagerReport;
+import picocli.CommandLine;
+
+import java.io.IOException;
+import java.time.Instant;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * This is the handler to process the container report command.
+ */
[email protected](
+    name = "report",
+    description = "Display the container summary report",
+    mixinStandardHelpOptions = true,
+    versionProvider = HddsVersionProvider.class)
+public class ReportSubcommand extends ScmSubcommand {
+
+  @CommandLine.Spec
+  private CommandLine.Model.CommandSpec spec;
+
+  @Override
+  public void execute(ScmClient scmClient) throws IOException {
+    ReplicationManagerReport report = scmClient.getReplicationManagerReport();
+    outputHeader(report.getReportTimeStamp());
+    blankLine();
+    outputContainerStats(report);
+    blankLine();
+    outputContainerHealthStats(report);
+    blankLine();
+    outputContainerSamples(report);
+  }
+
+  private void outputHeader(long epochMs) {
+    Instant reportTime = Instant.ofEpochSecond(epochMs / 1000);
+    outputHeading("Container Summary Report generated at " + reportTime);
+
+  }
+
+  private void outputContainerStats(ReplicationManagerReport report) {
+    outputHeading("Container State Summary");
+    for (HddsProtos.LifeCycleState state : HddsProtos.LifeCycleState.values()) 
{
+      long stat = report.getStat(state);
+      if (stat != -1) {
+        output(state + ": " + stat);
+      }
+    }
+  }
+
+  private void outputContainerHealthStats(ReplicationManagerReport report) {
+    outputHeading("Container Health Summary");
+    for (ReplicationManagerReport.HealthState state
+        : ReplicationManagerReport.HealthState.values()) {
+      long stat = report.getStat(state);
+      if (stat != -1) {
+        output(state + ": " + stat);
+      }
+    }
+  }
+
+  private void outputContainerSamples(ReplicationManagerReport report) {
+    for (ReplicationManagerReport.HealthState state
+        : ReplicationManagerReport.HealthState.values()) {
+      List<ContainerID> containers = report.getSample(state);
+      if (containers.size() > 0) {
+        output("First " + ReplicationManagerReport.SAMPLE_LIMIT + " " +
+            state + " containers:");
+        output(containers
+            .stream()
+            .map(ContainerID::toString)
+            .collect(Collectors.joining(", ")));
+        blankLine();
+      }
+    }
+  }
+
+  private void blankLine() {
+    System.out.print("\n");
+  }
+
+  private void output(String s) {
+    System.out.println(s);
+  }
+
+  private void outputHeading(String s) {
+    output(s);
+    for (int i = 0; i < s.length(); i++) {
+      System.out.print("=");
+    }
+    System.out.print("\n");
+  }
+}
diff --git 
a/hadoop-hdds/tools/src/test/java/org/apache/hadoop/hdds/scm/cli/container/TestReportSubCommand.java
 
b/hadoop-hdds/tools/src/test/java/org/apache/hadoop/hdds/scm/cli/container/TestReportSubCommand.java
new file mode 100644
index 0000000..5c71076
--- /dev/null
+++ 
b/hadoop-hdds/tools/src/test/java/org/apache/hadoop/hdds/scm/cli/container/TestReportSubCommand.java
@@ -0,0 +1,159 @@
+/*
+ * 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.
+ */
+package org.apache.hadoop.hdds.scm.cli.container;
+
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.scm.client.ScmClient;
+import org.apache.hadoop.hdds.scm.container.ContainerID;
+import org.apache.hadoop.hdds.scm.container.ReplicationManagerReport;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for the Container ReportSubCommand class.
+ */
+public class TestReportSubCommand {
+
+  private ReportSubcommand cmd;
+  private static final int SEED = 10;
+  private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
+  private final ByteArrayOutputStream errContent = new ByteArrayOutputStream();
+  private final PrintStream originalOut = System.out;
+  private final PrintStream originalErr = System.err;
+  private static final String DEFAULT_ENCODING = StandardCharsets.UTF_8.name();
+
+  @Before
+  public void setup() throws UnsupportedEncodingException {
+    cmd = new ReportSubcommand();
+    System.setOut(new PrintStream(outContent, false, DEFAULT_ENCODING));
+    System.setErr(new PrintStream(errContent, false, DEFAULT_ENCODING));
+  }
+
+  @After
+  public void tearDown() {
+    System.setOut(originalOut);
+    System.setErr(originalErr);
+  }
+
+  @Test
+  public void testCorrectValuesAppearInEmptyReport() throws IOException {
+    ScmClient scmClient = mock(ScmClient.class);
+    Mockito.when(scmClient.getReplicationManagerReport())
+        .thenAnswer(invocation -> new ReplicationManagerReport());
+
+    cmd.execute(scmClient);
+
+    for (HddsProtos.LifeCycleState state : HddsProtos.LifeCycleState.values()) 
{
+      Pattern p = Pattern.compile(
+          "^"+state.toString() + ": 0$", Pattern.MULTILINE);
+      Matcher m = p.matcher(outContent.toString(DEFAULT_ENCODING));
+      assertTrue(m.find());
+    }
+
+    for (ReplicationManagerReport.HealthState state :
+        ReplicationManagerReport.HealthState.values()) {
+      Pattern p = Pattern.compile(
+          "^"+state.toString() + ": 0$", Pattern.MULTILINE);
+      Matcher m = p.matcher(outContent.toString(DEFAULT_ENCODING));
+      assertTrue(m.find());
+    }
+  }
+
+  @Test
+  public void testCorrectValuesAppearInReport() throws IOException {
+    ScmClient scmClient = mock(ScmClient.class);
+    Mockito.when(scmClient.getReplicationManagerReport())
+        .thenAnswer(invocation -> createReport());
+
+    cmd.execute(scmClient);
+
+    int counter = SEED;
+    for (HddsProtos.LifeCycleState state : HddsProtos.LifeCycleState.values()) 
{
+      Pattern p = Pattern.compile(
+          "^"+state.toString() + ": " + counter + "$", Pattern.MULTILINE);
+      Matcher m = p.matcher(outContent.toString(DEFAULT_ENCODING));
+      assertTrue(m.find());
+      counter++;
+    }
+
+    counter = SEED;
+    for (ReplicationManagerReport.HealthState state :
+        ReplicationManagerReport.HealthState.values()) {
+      Pattern p = Pattern.compile(
+          "^"+state.toString() + ": " + counter + "$", Pattern.MULTILINE);
+      Matcher m = p.matcher(outContent.toString(DEFAULT_ENCODING));
+      assertTrue(m.find());
+
+      // Check the correct samples are returned
+      p = Pattern.compile(
+          "^First 100 "+ state + " containers:\n"
+              + containerList(0, counter) + "$", Pattern.MULTILINE);
+      m = p.matcher(outContent.toString(DEFAULT_ENCODING));
+      assertTrue(m.find());
+      counter++;
+    }
+  }
+
+  private ReplicationManagerReport createReport() {
+    ReplicationManagerReport report = new ReplicationManagerReport();
+
+    int counter = SEED;
+    for (HddsProtos.LifeCycleState state : HddsProtos.LifeCycleState.values()) 
{
+      for (int i = 0; i < counter; i++) {
+        report.increment(state);
+      }
+      counter++;
+    }
+
+    // Add samples
+    counter = SEED;
+    for (ReplicationManagerReport.HealthState state
+        : ReplicationManagerReport.HealthState.values()) {
+      for (int i = 0; i < counter; i++) {
+        report.incrementAndSample(state, ContainerID.valueOf(i));
+      }
+      counter++;
+    }
+    return report;
+  }
+
+  private String containerList(int start, int end) {
+    StringBuilder sb = new StringBuilder();
+    for (int i = start; i < end; i++) {
+      if (i != start) {
+        sb.append(", ");
+      }
+      sb.append("#"+i);
+    }
+    return sb.toString();
+  }
+
+}

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

Reply via email to