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 3a16ebe   HDDS-6177. Extend container info command to include replica 
details  (#2995)
3a16ebe is described below

commit 3a16ebe62ee5e0c827ee87f00a51e67633df76af
Author: Stephen O'Donnell <[email protected]>
AuthorDate: Wed Jan 26 09:05:04 2022 +0000

     HDDS-6177. Extend container info command to include replica details  
(#2995)
---
 .../apache/hadoop/hdds/scm/client/ScmClient.java   |  11 +
 .../hdds/scm/container/ContainerReplicaInfo.java   | 129 +++++++++++
 .../protocol/StorageContainerLocationProtocol.java |   9 +
 .../scm/container/TestContainerReplicaInfo.java    |  59 +++++
 .../hadoop/hdds/scm/container/package-info.java    |  21 ++
 ...inerLocationProtocolClientSideTranslatorPB.java |  21 ++
 .../src/main/proto/ScmAdminProtocol.proto          |  12 +
 .../interface-client/src/main/proto/hdds.proto     |   9 +
 ...inerLocationProtocolServerSideTranslatorPB.java |  17 ++
 .../hdds/scm/server/SCMClientProtocolServer.java   |  22 ++
 hadoop-hdds/tools/pom.xml                          |   9 +
 .../hdds/scm/cli/ContainerOperationClient.java     |  21 ++
 .../hdds/scm/cli/container/InfoSubcommand.java     |  61 ++++-
 .../hdds/scm/cli/container/TestInfoSubCommand.java | 249 +++++++++++++++++++++
 14 files changed, 648 insertions(+), 2 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 342a3e7..b1bf420 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
@@ -20,6 +20,7 @@ package org.apache.hadoop.hdds.scm.client;
 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.common.helpers.ContainerWithPipeline;
 import org.apache.hadoop.hdds.scm.container.ContainerInfo;
 import org.apache.hadoop.hdds.scm.pipeline.Pipeline;
@@ -71,6 +72,16 @@ public interface ScmClient extends Closeable {
       throws IOException;
 
   /**
+   * Gets the list of ReplicaInfo known by SCM for a given container.
+   * @param containerId - The Container ID
+   * @return List of ContainerReplicaInfo for the container or an empty list
+   *         if none.
+   * @throws IOException
+   */
+  List<ContainerReplicaInfo> getContainerReplicas(
+      long containerId) throws IOException;
+
+  /**
    * Close a container.
    *
    * @param containerId - ID of the container.
diff --git 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/container/ContainerReplicaInfo.java
 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/container/ContainerReplicaInfo.java
new file mode 100644
index 0000000..b30dff7
--- /dev/null
+++ 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/container/ContainerReplicaInfo.java
@@ -0,0 +1,129 @@
+/*
+ * 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.container;
+
+import org.apache.hadoop.hdds.protocol.DatanodeDetails;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+
+import java.util.UUID;
+
+/**
+ * Class which stores ContainerReplica details on the client.
+ */
+public final class ContainerReplicaInfo {
+
+  private long containerID;
+  private String state;
+  private DatanodeDetails datanodeDetails;
+  private UUID placeOfBirth;
+  private long sequenceId;
+  private long keyCount;
+  private long bytesUsed;
+
+  public static ContainerReplicaInfo fromProto(
+      HddsProtos.SCMContainerReplicaProto proto) {
+    ContainerReplicaInfo.Builder builder = new ContainerReplicaInfo.Builder();
+    builder.setContainerID(proto.getContainerID())
+        .setState(proto.getState())
+        .setDatanodeDetails(DatanodeDetails
+            .getFromProtoBuf(proto.getDatanodeDetails()))
+        .setPlaceOfBirth(UUID.fromString(proto.getPlaceOfBirth()))
+        .setSequenceId(proto.getSequenceID())
+        .setKeyCount(proto.getKeyCount())
+        .setBytesUsed(proto.getBytesUsed());
+    return builder.build();
+  }
+
+  private ContainerReplicaInfo() {
+  }
+
+  public long getContainerID() {
+    return containerID;
+  }
+
+  public String getState() {
+    return state;
+  }
+
+  public DatanodeDetails getDatanodeDetails() {
+    return datanodeDetails;
+  }
+
+  public UUID getPlaceOfBirth() {
+    return placeOfBirth;
+  }
+
+  public long getSequenceId() {
+    return sequenceId;
+  }
+
+  public long getKeyCount() {
+    return keyCount;
+  }
+
+  public long getBytesUsed() {
+    return bytesUsed;
+  }
+
+  /**
+   * Builder for ContainerReplicaInfo class.
+   */
+  public static class Builder {
+
+    private final ContainerReplicaInfo subject = new ContainerReplicaInfo();
+
+    public Builder setContainerID(long containerID) {
+      subject.containerID = containerID;
+      return this;
+    }
+
+    public Builder setState(String state) {
+      subject.state = state;
+      return this;
+    }
+
+    public Builder setDatanodeDetails(DatanodeDetails datanodeDetails) {
+      subject.datanodeDetails = datanodeDetails;
+      return this;
+    }
+
+    public Builder setPlaceOfBirth(UUID placeOfBirth) {
+      subject.placeOfBirth = placeOfBirth;
+      return this;
+    }
+
+    public Builder setSequenceId(long sequenceId) {
+      subject.sequenceId = sequenceId;
+      return this;
+    }
+
+    public Builder setKeyCount(long keyCount) {
+      subject.keyCount = keyCount;
+      return this;
+    }
+
+    public Builder setBytesUsed(long bytesUsed) {
+      subject.bytesUsed = bytesUsed;
+      return this;
+    }
+
+    public ContainerReplicaInfo build() {
+      return subject;
+    }
+  }
+}
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 264a599..579f351 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
@@ -95,6 +95,15 @@ public interface StorageContainerLocationProtocol extends 
Closeable {
       throws IOException;
 
   /**
+   * Gets the list of ReplicaInfo known by SCM for a given container.
+   * @param containerId ID of the container
+   * @return List of ReplicaInfo for the container or an empty list if none.
+   * @throws IOException
+   */
+  List<HddsProtos.SCMContainerReplicaProto>
+      getContainerReplicas(long containerId) throws IOException;
+
+  /**
    * Ask SCM the location of a batch of containers. SCM responds with a group 
of
    * nodes where these containers and their replicas are located.
    *
diff --git 
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/scm/container/TestContainerReplicaInfo.java
 
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/scm/container/TestContainerReplicaInfo.java
new file mode 100644
index 0000000..195baca
--- /dev/null
+++ 
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/scm/container/TestContainerReplicaInfo.java
@@ -0,0 +1,59 @@
+/*
+ * 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.container;
+
+import org.apache.hadoop.hdds.protocol.DatanodeDetails;
+import org.apache.hadoop.hdds.protocol.MockDatanodeDetails;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.UUID;
+
+/**
+ * Test for the ContainerReplicaInfo class.
+ */
+public class TestContainerReplicaInfo {
+
+  @Test
+  public void testObjectCreatedFromProto() {
+    HddsProtos.SCMContainerReplicaProto proto =
+        HddsProtos.SCMContainerReplicaProto.newBuilder()
+            .setKeyCount(10)
+            .setBytesUsed(12345)
+            .setContainerID(567)
+            .setPlaceOfBirth(UUID.randomUUID().toString())
+            .setSequenceID(5)
+            .setDatanodeDetails(MockDatanodeDetails.randomDatanodeDetails()
+                .getProtoBufMessage())
+            .setState("OPEN")
+            .build();
+
+    ContainerReplicaInfo info = ContainerReplicaInfo.fromProto(proto);
+
+    Assert.assertEquals(proto.getContainerID(), info.getContainerID());
+    Assert.assertEquals(proto.getBytesUsed(), info.getBytesUsed());
+    Assert.assertEquals(proto.getKeyCount(), info.getKeyCount());
+    Assert.assertEquals(proto.getPlaceOfBirth(),
+        info.getPlaceOfBirth().toString());
+    Assert.assertEquals(DatanodeDetails.getFromProtoBuf(
+        proto.getDatanodeDetails()), info.getDatanodeDetails());
+    Assert.assertEquals(proto.getSequenceID(), info.getSequenceId());
+    Assert.assertEquals(proto.getState(), info.getState());
+  }
+}
\ No newline at end of file
diff --git 
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/scm/container/package-info.java
 
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/scm/container/package-info.java
new file mode 100644
index 0000000..2f459fb
--- /dev/null
+++ 
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/scm/container/package-info.java
@@ -0,0 +1,21 @@
+/**
+ * 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.
+ */
+/**
+ Test cases for SCM container client classes.
+ */
+package org.apache.hadoop.hdds.scm.container;
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 b745822..62139eb 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
@@ -45,6 +45,7 @@ import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolPro
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.ForceExitSafeModeRequestProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.ForceExitSafeModeResponseProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetContainerRequestProto;
+import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetContainerReplicasRequestProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetContainerTokenRequestProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetContainerTokenResponseProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetContainerWithPipelineBatchRequestProto;
@@ -255,6 +256,26 @@ public final class 
StorageContainerLocationProtocolClientSideTranslatorPB
    * {@inheritDoc}
    */
   @Override
+  public List<HddsProtos.SCMContainerReplicaProto>
+      getContainerReplicas(long containerID) throws IOException {
+    Preconditions.checkState(containerID >= 0,
+        "Container ID cannot be negative");
+
+    GetContainerReplicasRequestProto request =
+        GetContainerReplicasRequestProto.newBuilder()
+            .setTraceID(TracingUtil.exportCurrentSpan())
+            .setContainerID(containerID).build();
+
+    ScmContainerLocationResponse response =
+        submitRequest(Type.GetContainerReplicas,
+            (builder) -> builder.setGetContainerReplicasRequest(request));
+    return 
response.getGetContainerReplicasResponse().getContainerReplicaList();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
   public List<ContainerWithPipeline> getContainerWithPipelineBatch(
       List<Long> containerIDs) throws IOException {
     for (Long containerID: containerIDs) {
diff --git a/hadoop-hdds/interface-admin/src/main/proto/ScmAdminProtocol.proto 
b/hadoop-hdds/interface-admin/src/main/proto/ScmAdminProtocol.proto
index f8c6aa7..313a7c8 100644
--- a/hadoop-hdds/interface-admin/src/main/proto/ScmAdminProtocol.proto
+++ b/hadoop-hdds/interface-admin/src/main/proto/ScmAdminProtocol.proto
@@ -75,6 +75,7 @@ message ScmContainerLocationRequest {
   optional FinalizeScmUpgradeRequestProto finalizeScmUpgradeRequest = 36;
   optional QueryUpgradeFinalizationProgressRequestProto 
queryUpgradeFinalizationProgressRequest = 37;
   optional GetContainerCountRequestProto getContainerCountRequest = 38;
+  optional GetContainerReplicasRequestProto getContainerReplicasRequest = 39;
 }
 
 message ScmContainerLocationResponse {
@@ -121,6 +122,7 @@ message ScmContainerLocationResponse {
   optional FinalizeScmUpgradeResponseProto finalizeScmUpgradeResponse = 36;
   optional QueryUpgradeFinalizationProgressResponseProto 
queryUpgradeFinalizationProgressResponse = 37;
   optional GetContainerCountResponseProto getContainerCountResponse = 38;
+  optional GetContainerReplicasResponseProto getContainerReplicasResponse = 39;
 
   enum Status {
     OK = 1;
@@ -165,6 +167,7 @@ enum Type {
   FinalizeScmUpgrade = 31;
   QueryUpgradeFinalizationProgress = 32;
   GetContainerCount = 33;
+  GetContainerReplicas = 34;
 }
 
 /**
@@ -213,6 +216,15 @@ message GetContainerWithPipelineResponseProto {
   required ContainerWithPipeline containerWithPipeline = 1;
 }
 
+message GetContainerReplicasRequestProto {
+  required int64 containerID = 1;
+  optional string traceID = 2;
+}
+
+message GetContainerReplicasResponseProto {
+  repeated SCMContainerReplicaProto containerReplica = 1;
+}
+
 message GetContainerWithPipelineBatchRequestProto {
   repeated int64 containerIDs = 1;
   optional string traceID = 2;
diff --git a/hadoop-hdds/interface-client/src/main/proto/hdds.proto 
b/hadoop-hdds/interface-client/src/main/proto/hdds.proto
index 133f4c6..bc1b35a 100644
--- a/hadoop-hdds/interface-client/src/main/proto/hdds.proto
+++ b/hadoop-hdds/interface-client/src/main/proto/hdds.proto
@@ -379,3 +379,12 @@ message ContainerReplicaHistoryProto {
     required int64 bcsId = 4;
 }
 
+message SCMContainerReplicaProto {
+    required int64 containerID = 1;
+    required string state = 2;
+    required DatanodeDetailsProto datanodeDetails = 3;
+    required string placeOfBirth = 4;
+    required int64 sequenceID = 5;
+    required int64 keyCount = 6;
+    required int64 bytesUsed = 7;
+}
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 b839e99..17da776 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
@@ -43,6 +43,8 @@ import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolPro
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.FinalizeScmUpgradeResponseProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.ForceExitSafeModeRequestProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.ForceExitSafeModeResponseProto;
+import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetContainerReplicasRequestProto;
+import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetContainerReplicasResponseProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetContainerRequestProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetContainerResponseProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetContainerTokenRequestProto;
@@ -405,6 +407,13 @@ public final class 
StorageContainerLocationProtocolServerSideTranslatorPB
           .setGetContainerCountResponse(getContainerCount(
                   request.getGetContainerCountRequest()))
           .build();
+      case GetContainerReplicas:
+        return ScmContainerLocationResponse.newBuilder()
+          .setCmdType(request.getCmdType())
+          .setStatus(Status.OK)
+          .setGetContainerReplicasResponse(getContainerReplicas(
+              request.getGetContainerReplicasRequest()))
+          .build();
       default:
         throw new IllegalArgumentException(
             "Unknown command type: " + request.getCmdType());
@@ -416,6 +425,14 @@ public final class 
StorageContainerLocationProtocolServerSideTranslatorPB
     }
   }
 
+  public GetContainerReplicasResponseProto getContainerReplicas(
+      GetContainerReplicasRequestProto request) throws IOException {
+    List<HddsProtos.SCMContainerReplicaProto> replicas
+        = impl.getContainerReplicas(request.getContainerID());
+    return GetContainerReplicasResponseProto.newBuilder()
+        .addAllContainerReplica(replicas).build();
+  }
+
   public ContainerResponseProto allocateContainer(ContainerRequestProto 
request,
       int clientVersion) throws IOException {
     ContainerWithPipeline cp = impl
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 fa1d316..0cdb571 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
@@ -290,6 +290,28 @@ public class SCMClientProtocolServer implements
   }
 
   @Override
+  public List<HddsProtos.SCMContainerReplicaProto>
+      getContainerReplicas(long containerId) throws IOException {
+    List<HddsProtos.SCMContainerReplicaProto> results = new ArrayList<>();
+
+    Set<ContainerReplica> replicas = getScm().getContainerManager()
+        .getContainerReplicas(ContainerID.valueOf(containerId));
+    for (ContainerReplica r : replicas) {
+      results.add(
+          HddsProtos.SCMContainerReplicaProto.newBuilder()
+              .setContainerID(containerId)
+              .setState(r.getState().toString())
+              .setDatanodeDetails(r.getDatanodeDetails().getProtoBufMessage())
+              .setBytesUsed(r.getBytesUsed())
+              .setPlaceOfBirth(r.getOriginDatanodeId().toString())
+              .setKeyCount(r.getKeyCount())
+              .setSequenceID(r.getSequenceId()).build()
+      );
+    }
+    return results;
+  }
+
+  @Override
   public List<ContainerWithPipeline> getContainerWithPipelineBatch(
       List<Long> containerIDs) throws IOException {
     getScm().checkAdminAccess(null);
diff --git a/hadoop-hdds/tools/pom.xml b/hadoop-hdds/tools/pom.xml
index 9a3bce4..277bff6 100644
--- a/hadoop-hdds/tools/pom.xml
+++ b/hadoop-hdds/tools/pom.xml
@@ -83,5 +83,14 @@ https://maven.apache.org/xsd/maven-4.0.0.xsd";>
       <artifactId>mockito-core</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-log4j12</artifactId>
+      <version>${slf4j.version}</version>
+    </dependency>
   </dependencies>
 </project>
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 123a74d..ca4e41b 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
@@ -33,6 +33,7 @@ import org.apache.hadoop.hdds.scm.XceiverClientSpi;
 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.common.helpers.ContainerWithPipeline;
 import org.apache.hadoop.hdds.scm.pipeline.Pipeline;
 import org.apache.hadoop.hdds.scm.protocol.StorageContainerLocationProtocol;
@@ -45,6 +46,7 @@ import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.security.cert.X509Certificate;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -481,6 +483,25 @@ public class ContainerOperationClient implements ScmClient 
{
   }
 
   /**
+   * Gets the list of ReplicaInfo known by SCM for a given container.
+   * @param containerId - The Container ID
+   * @return List of ContainerReplicaInfo for the container or an empty list
+   *         if none.
+   * @throws IOException
+   */
+  @Override
+  public List<ContainerReplicaInfo>
+      getContainerReplicas(long containerId) throws IOException {
+    List<HddsProtos.SCMContainerReplicaProto> protos
+        = storageContainerLocationClient.getContainerReplicas(containerId);
+    List<ContainerReplicaInfo> replicas = new ArrayList<>();
+    for (HddsProtos.SCMContainerReplicaProto p : protos) {
+      replicas.add(ContainerReplicaInfo.fromProto(p));
+    }
+    return replicas;
+  }
+
+  /**
    * Close a container.
    *
    * @throws IOException
diff --git 
a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/container/InfoSubcommand.java
 
b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/container/InfoSubcommand.java
index 65884d2..7b0daa6 100644
--- 
a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/container/InfoSubcommand.java
+++ 
b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/container/InfoSubcommand.java
@@ -18,6 +18,7 @@
 package org.apache.hadoop.hdds.scm.cli.container;
 
 import java.io.IOException;
+import java.util.List;
 import java.util.stream.Collectors;
 
 import org.apache.hadoop.hdds.cli.GenericParentCommand;
@@ -25,10 +26,13 @@ import org.apache.hadoop.hdds.cli.HddsVersionProvider;
 import org.apache.hadoop.hdds.protocol.DatanodeDetails;
 import org.apache.hadoop.hdds.scm.cli.ScmSubcommand;
 import org.apache.hadoop.hdds.scm.client.ScmClient;
+import org.apache.hadoop.hdds.scm.container.ContainerInfo;
+import org.apache.hadoop.hdds.scm.container.ContainerReplicaInfo;
 import org.apache.hadoop.hdds.scm.container.common.helpers
     .ContainerWithPipeline;
 
 import com.google.common.base.Preconditions;
+import org.apache.hadoop.hdds.scm.pipeline.Pipeline;
 import org.apache.hadoop.hdds.server.JsonUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -67,13 +71,23 @@ public class InfoSubcommand extends ScmSubcommand {
     final ContainerWithPipeline container = scmClient.
         getContainerWithPipeline(containerID);
     Preconditions.checkNotNull(container, "Container cannot be null");
+    List<ContainerReplicaInfo> replicas = null;
+    try {
+      replicas = scmClient.getContainerReplicas(containerID);
+    } catch (IOException e) {
+      LOG.error("Unable to retrieve the replica details", e);
+    }
 
     if (json) {
-      LOG.info(JsonUtils.toJsonStringWithDefaultPrettyPrinter(container));
+      ContainerWithPipelineAndReplicas wrapper =
+          new ContainerWithPipelineAndReplicas(container.getContainerInfo(),
+              container.getPipeline(), replicas);
+      LOG.info(JsonUtils.toJsonStringWithDefaultPrettyPrinter(wrapper));
     } else {
       // Print container report info.
       LOG.info("Container id: {}", containerID);
-      boolean verbose = spec.root().userObject() instanceof 
GenericParentCommand
+      boolean verbose = spec != null
+          && spec.root().userObject() instanceof GenericParentCommand
           && ((GenericParentCommand) spec.root().userObject()).isVerbose();
       if (verbose) {
         LOG.info("Pipeline Info: {}", container.getPipeline());
@@ -87,10 +101,53 @@ public class InfoSubcommand extends ScmSubcommand {
           InfoSubcommand::buildDatanodeDetails)
           .collect(Collectors.joining(",\n"));
       LOG.info("Datanodes: [{}]", machinesStr);
+
+      // Print the replica details if available
+      if (replicas != null) {
+        String replicaStr = replicas.stream().map(
+            InfoSubcommand::buildReplicaDetails)
+            .collect(Collectors.joining(",\n"));
+        LOG.info("Replicas: [{}]", replicaStr);
+      }
     }
   }
 
   private static String buildDatanodeDetails(DatanodeDetails details) {
     return details.getUuidString() + "/" + details.getHostName();
   }
+
+  private static String buildReplicaDetails(ContainerReplicaInfo replica) {
+    StringBuilder sb = new StringBuilder();
+    sb.append("State: " + replica.getState() + ";");
+    sb.append(" Origin: " + replica.getPlaceOfBirth().toString() + ";");
+    sb.append(" Location: "
+        + buildDatanodeDetails(replica.getDatanodeDetails()));
+    return sb.toString();
+  }
+
+  private static class ContainerWithPipelineAndReplicas {
+
+    private ContainerInfo containerInfo;
+    private Pipeline pipeline;
+    private List<ContainerReplicaInfo> replicas;
+
+    ContainerWithPipelineAndReplicas(ContainerInfo container, Pipeline 
pipeline,
+                                     List<ContainerReplicaInfo> replicas) {
+      this.containerInfo = container;
+      this.pipeline = pipeline;
+      this.replicas = replicas;
+    }
+
+    public ContainerInfo getContainerInfo() {
+      return containerInfo;
+    }
+
+    public Pipeline getPipeline() {
+      return pipeline;
+    }
+
+    public List<ContainerReplicaInfo> getReplicas() {
+      return replicas;
+    }
+  }
 }
diff --git 
a/hadoop-hdds/tools/src/test/java/org/apache/hadoop/hdds/scm/cli/container/TestInfoSubCommand.java
 
b/hadoop-hdds/tools/src/test/java/org/apache/hadoop/hdds/scm/cli/container/TestInfoSubCommand.java
new file mode 100644
index 0000000..ad43f9e
--- /dev/null
+++ 
b/hadoop-hdds/tools/src/test/java/org/apache/hadoop/hdds/scm/cli/container/TestInfoSubCommand.java
@@ -0,0 +1,249 @@
+/*
+ * 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.client.RatisReplicationConfig;
+import org.apache.hadoop.hdds.protocol.DatanodeDetails;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.scm.client.ScmClient;
+import org.apache.hadoop.hdds.scm.container.ContainerInfo;
+import org.apache.hadoop.hdds.scm.container.ContainerReplicaInfo;
+import 
org.apache.hadoop.hdds.scm.container.common.helpers.ContainerWithPipeline;
+import org.apache.hadoop.hdds.scm.pipeline.Pipeline;
+import org.apache.hadoop.hdds.scm.pipeline.PipelineID;
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.spi.LoggingEvent;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import picocli.CommandLine;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import static 
org.apache.hadoop.hdds.protocol.proto.HddsProtos.LifeCycleState.CLOSED;
+import static 
org.apache.hadoop.hdds.protocol.proto.HddsProtos.ReplicationFactor.THREE;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for InfoSubCommand class.
+ */
+public class TestInfoSubCommand {
+
+  private ScmClient scmClient;
+  private InfoSubcommand cmd;
+  private List<DatanodeDetails> datanodes;
+  private Logger logger;
+  private TestAppender appender;
+
+  @Before
+  public void setup() throws IOException {
+    scmClient = mock(ScmClient.class);
+    datanodes = createDatanodeDetails(3);
+    Mockito.when(scmClient.getContainerWithPipeline(anyLong()))
+        .thenReturn(getContainerWithPipeline());
+
+    appender = new TestAppender();
+    logger = Logger.getLogger(
+        org.apache.hadoop.hdds.scm.cli.container.InfoSubcommand.class);
+    logger.addAppender(appender);
+  }
+
+  @After
+  public void after() {
+    logger.removeAppender(appender);
+  }
+
+  @Test
+  public void testReplicasIncludedInOutput() throws Exception {
+    Mockito.when(scmClient.getContainerReplicas(anyLong()))
+        .thenReturn(getReplicas());
+    cmd = new InfoSubcommand();
+    CommandLine c = new CommandLine(cmd);
+    c.parseArgs("1");
+    cmd.execute(scmClient);
+
+    // Ensure we have a line for Replicas:
+    List<LoggingEvent> logs = appender.getLog();
+    List<LoggingEvent> replica = logs.stream()
+        .filter(m -> m.getRenderedMessage().matches("(?s)^Replicas:.*"))
+        .collect(Collectors.toList());
+    Assert.assertEquals(1, replica.size());
+    
+    // Ensure each DN UUID is mentioned in the message:
+    for (DatanodeDetails dn : datanodes) {
+      Pattern pattern = Pattern.compile(".*" + dn.getUuid().toString() + ".*",
+          Pattern.DOTALL);
+      Matcher matcher = pattern.matcher(replica.get(0).getRenderedMessage());
+      Assert.assertTrue(matcher.matches());
+    }
+  }
+
+  @Test
+  public void testReplicasNotOutputIfError() throws IOException {
+    Mockito.when(scmClient.getContainerReplicas(anyLong()))
+        .thenThrow(new IOException("Error getting Replicas"));
+    cmd = new InfoSubcommand();
+    CommandLine c = new CommandLine(cmd);
+    c.parseArgs("1");
+    cmd.execute(scmClient);
+
+    // Ensure we have no lines for Replicas:
+    List<LoggingEvent> logs = appender.getLog();
+    List<LoggingEvent> replica = logs.stream()
+        .filter(m -> m.getRenderedMessage().matches("(?s)^Replicas:.*"))
+        .collect(Collectors.toList());
+    Assert.assertEquals(0, replica.size());
+
+    // Ensure we have an error logged:
+    List<LoggingEvent> error = logs.stream()
+        .filter(m -> m.getLevel() == Level.ERROR)
+        .collect(Collectors.toList());
+    Assert.assertEquals(1, error.size());
+    Assert.assertTrue(error.get(0).getRenderedMessage()
+        .matches("(?s)^Unable to retrieve the replica details.*"));
+  }
+
+  @Test
+  public void testReplicasNotOutputIfErrorWithJson() throws IOException {
+    Mockito.when(scmClient.getContainerReplicas(anyLong()))
+        .thenThrow(new IOException("Error getting Replicas"));
+    cmd = new InfoSubcommand();
+    CommandLine c = new CommandLine(cmd);
+    c.parseArgs("1", "--json");
+    cmd.execute(scmClient);
+
+    List<LoggingEvent> logs = appender.getLog();
+    Assert.assertEquals(2, logs.size());
+    String error = logs.get(0).getRenderedMessage();
+    String json = logs.get(1).getRenderedMessage();
+
+    Assert.assertTrue(error
+        .matches("(?s)^Unable to retrieve the replica details.*"));
+    Assert.assertFalse(json.matches("(?s).*replicas.*"));
+  }
+
+  @Test
+  public void testReplicasOutputWithJson() throws IOException {
+    Mockito.when(scmClient.getContainerReplicas(anyLong()))
+        .thenReturn(getReplicas());
+    cmd = new InfoSubcommand();
+    CommandLine c = new CommandLine(cmd);
+    c.parseArgs("1", "--json");
+    cmd.execute(scmClient);
+
+    List<LoggingEvent> logs = appender.getLog();
+    Assert.assertEquals(1, logs.size());
+
+    // Ensure each DN UUID is mentioned in the message after replicas:
+    String json = logs.get(0).getRenderedMessage();
+    Assert.assertTrue(json.matches("(?s).*replicas.*"));
+    for (DatanodeDetails dn : datanodes) {
+      Pattern pattern = Pattern.compile(
+          ".*replicas.*" + dn.getUuid().toString() + ".*", Pattern.DOTALL);
+      Matcher matcher = pattern.matcher(json);
+      Assert.assertTrue(matcher.matches());
+    }
+  }
+
+  private List<ContainerReplicaInfo> getReplicas() {
+    List<ContainerReplicaInfo> replicas = new ArrayList<>();
+    for (DatanodeDetails dn : datanodes) {
+      ContainerReplicaInfo container =  new ContainerReplicaInfo.Builder()
+          .setContainerID(1)
+          .setBytesUsed(1234)
+          .setState("CLOSED")
+          .setPlaceOfBirth(dn.getUuid())
+          .setDatanodeDetails(dn)
+          .setKeyCount(1)
+          .setSequenceId(1).build();
+      replicas.add(container);
+    }
+    return replicas;
+  }
+
+  private ContainerWithPipeline getContainerWithPipeline() {
+    Pipeline pipeline = new Pipeline.Builder()
+        .setState(Pipeline.PipelineState.CLOSED)
+        .setReplicationConfig(new RatisReplicationConfig(THREE))
+        .setId(PipelineID.randomId())
+        .setNodes(datanodes)
+        .build();
+
+    ContainerInfo container = new ContainerInfo.Builder()
+        .setSequenceId(1)
+        .setPipelineID(pipeline.getId())
+        .setUsedBytes(1234)
+        .setReplicationConfig(new RatisReplicationConfig(THREE))
+        .setNumberOfKeys(1)
+        .setState(CLOSED)
+        .build();
+
+    return new ContainerWithPipeline(container, pipeline);
+  }
+
+  private List<DatanodeDetails> createDatanodeDetails(int count) {
+    List<DatanodeDetails> dns = new ArrayList<>();
+    for (int i = 0; i < count; i++) {
+      HddsProtos.DatanodeDetailsProto dnd =
+          HddsProtos.DatanodeDetailsProto.newBuilder()
+              .setHostName("host" + i)
+              .setIpAddress("1.2.3." + i + 1)
+              .setNetworkLocation("/default")
+              .setNetworkName("host" + i)
+              .addPorts(HddsProtos.Port.newBuilder()
+                  .setName("ratis").setValue(5678).build())
+              .setUuid(UUID.randomUUID().toString())
+              .build();
+      dns.add(DatanodeDetails.getFromProtoBuf(dnd));
+    }
+    return dns;
+  }
+
+  private static class TestAppender extends AppenderSkeleton {
+    private final List<LoggingEvent> log = new ArrayList<>();
+
+    @Override
+    public boolean requiresLayout() {
+      return false;
+    }
+
+    @Override
+    protected void append(final LoggingEvent loggingEvent) {
+      log.add(loggingEvent);
+    }
+
+    @Override
+    public void close() {
+    }
+
+    public List<LoggingEvent> getLog() {
+      return new ArrayList<>(log);
+    }
+  }
+}

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

Reply via email to