This is an automated email from the ASF dual-hosted git repository.
devmadhuu pushed a commit to branch HDDS-11233
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/HDDS-11233 by this push:
new 413db6b491d HDDS-16586. Add support for displaying storage type of
replicas in container info command. (#11347)
413db6b491d is described below
commit 413db6b491df4239a508f9efb39e0aa6ffc2ef03
Author: Anurag Parvatikar <[email protected]>
AuthorDate: Thu Oct 1 17:00:38 2026 +0530
HDDS-16586. Add support for displaying storage type of replicas in
container info command. (#11347)
---
.../hdds/scm/container/ContainerReplicaInfo.java | 28 +++++++-
.../scm/container/TestContainerReplicaInfo.java | 30 +++++++++
.../container/keyvalue/KeyValueContainerData.java | 5 ++
.../container/keyvalue/TestKeyValueContainer.java | 48 ++++++++++++++
.../interface-client/src/main/proto/hdds.proto | 2 +
.../proto/ScmServerDatanodeHeartbeatProtocol.proto | 1 +
.../container/AbstractContainerReportHandler.java | 3 +
.../hdds/scm/container/ContainerReplica.java | 16 +++++
.../hdds/scm/server/SCMClientProtocolServer.java | 14 ++--
.../TestIncrementalContainerReportHandler.java | 70 ++++++++++++++++++++
.../scm/server/TestSCMClientProtocolServer.java | 70 ++++++++++++++++++++
.../hdds/scm/cli/container/InfoSubcommand.java | 13 +++-
.../hdds/scm/cli/container/TestInfoSubCommand.java | 77 ++++++++++++++++++++++
.../TestAllocateContainerWithStorageTier.java | 59 +++++++++++++++++
14 files changed, 429 insertions(+), 7 deletions(-)
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
index 2aaa45d695d..82f3e6b74e6 100644
---
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
@@ -18,6 +18,8 @@
package org.apache.hadoop.hdds.scm.container;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import org.apache.hadoop.fs.StorageType;
+import org.apache.hadoop.hdds.client.StorageTypeUtils;
import org.apache.hadoop.hdds.protocol.DatanodeDetails;
import org.apache.hadoop.hdds.protocol.DatanodeID;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
@@ -38,6 +40,8 @@ public final class ContainerReplicaInfo {
private int replicaIndex = -1;
@JsonSerialize(using = JsonUtils.ChecksumSerializer.class)
private long dataChecksum;
+ private StorageType storageType;
+ private StorageType volumeStorageType;
public static ContainerReplicaInfo fromProto(
HddsProtos.SCMContainerReplicaProto proto) {
@@ -52,7 +56,11 @@ public static ContainerReplicaInfo fromProto(
.setBytesUsed(proto.getBytesUsed())
.setReplicaIndex(
proto.hasReplicaIndex() ? (int)proto.getReplicaIndex() : -1)
- .setDataChecksum(proto.getDataChecksum());
+ .setDataChecksum(proto.getDataChecksum())
+ .setStorageType(proto.hasStorageType()
+ ? StorageTypeUtils.getFromProtobuf(proto.getStorageType()) : null)
+ .setVolumeStorageType(proto.hasVolumeStorageType()
+ ? StorageTypeUtils.getFromProtobuf(proto.getVolumeStorageType()) :
null);
return builder.build();
}
@@ -95,6 +103,14 @@ public long getDataChecksum() {
return dataChecksum;
}
+ public StorageType getStorageType() {
+ return storageType;
+ }
+
+ public StorageType getVolumeStorageType() {
+ return volumeStorageType;
+ }
+
/**
* Builder for ContainerReplicaInfo class.
*/
@@ -147,6 +163,16 @@ public Builder setDataChecksum(long dataChecksum) {
return this;
}
+ public Builder setStorageType(StorageType storageType) {
+ subject.storageType = storageType;
+ return this;
+ }
+
+ public Builder setVolumeStorageType(StorageType volumeStorageType) {
+ subject.volumeStorageType = volumeStorageType;
+ return this;
+ }
+
public ContainerReplicaInfo build() {
return subject;
}
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
index 9026a9845a3..d2e81255df6 100644
---
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
@@ -17,9 +17,11 @@
package org.apache.hadoop.hdds.scm.container;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.UUID;
+import org.apache.hadoop.fs.StorageType;
import org.apache.hadoop.hdds.protocol.DatanodeDetails;
import org.apache.hadoop.hdds.protocol.MockDatanodeDetails;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
@@ -57,6 +59,34 @@ public void testObjectCreatedFromProto() {
assertEquals(proto.getState(), info.getState());
// If replicaIndex is not in the proto, then -1 should be returned
assertEquals(-1, info.getReplicaIndex());
+ // Storage fields are absent in the proto, so they must be null rather than
+ // the protobuf defaults, otherwise they would show up in JSON output for
+ // datanodes that do not report them.
+ assertThat(info.getStorageType()).isNull();
+ assertThat(info.getVolumeStorageType()).isNull();
+ }
+
+ @Test
+ public void testObjectCreatedFromProtoWithStorageFields() {
+ HddsProtos.SCMContainerReplicaProto proto =
+ HddsProtos.SCMContainerReplicaProto.newBuilder()
+ .setKeyCount(10)
+ .setBytesUsed(12345)
+ .setContainerID(567)
+ .setPlaceOfBirth(UUID.randomUUID().toString())
+ .setSequenceID(5)
+ .setDatanodeDetails(MockDatanodeDetails.randomDatanodeDetails()
+ .getProtoBufMessage())
+ .setState("CLOSED")
+ .setStorageType(HddsProtos.StorageTypeProto.SSD)
+ .setVolumeStorageType(HddsProtos.StorageTypeProto.SSD)
+ .build();
+
+ ContainerReplicaInfo info = ContainerReplicaInfo.fromProto(proto);
+
+ // The container was stamped SSD and sits on the SSD volume.
+ assertThat(info.getStorageType()).isEqualTo(StorageType.SSD);
+ assertThat(info.getVolumeStorageType()).isEqualTo(StorageType.SSD);
}
@Test
diff --git
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/KeyValueContainerData.java
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/KeyValueContainerData.java
index 34c7b23be8a..44c05dc43b2 100644
---
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/KeyValueContainerData.java
+++
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/KeyValueContainerData.java
@@ -60,6 +60,7 @@
import org.apache.hadoop.ozone.container.common.impl.ContainerData;
import org.apache.hadoop.ozone.container.common.impl.ContainerLayoutVersion;
import org.apache.hadoop.ozone.container.common.interfaces.DBHandle;
+import org.apache.hadoop.ozone.container.common.volume.HddsVolume;
import
org.apache.hadoop.ozone.container.keyvalue.helpers.KeyValueContainerUtil;
import org.apache.hadoop.ozone.container.upgrade.VersionedDatanodeFeatures;
import org.yaml.snakeyaml.nodes.Tag;
@@ -288,6 +289,10 @@ ContainerReplicaProto buildContainerReplicaProto() throws
StorageContainerExcept
if (getStorageType() != null) {
builder.setStorageType(StorageTypeUtils.getStorageTypeProto(getStorageType()));
}
+ final HddsVolume volume = getVolume();
+ if (volume != null) {
+
builder.setVolumeStorageType(StorageTypeUtils.getStorageTypeProto(volume.getStorageType()));
+ }
return builder.build();
}
diff --git
a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/keyvalue/TestKeyValueContainer.java
b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/keyvalue/TestKeyValueContainer.java
index 811f3ab5257..6ec0493289f 100644
---
a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/keyvalue/TestKeyValueContainer.java
+++
b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/keyvalue/TestKeyValueContainer.java
@@ -72,6 +72,8 @@
import org.apache.hadoop.hdds.conf.ConfigurationSource;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos.StorageTypeProto;
+import
org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.ContainerReplicaProto;
import
org.apache.hadoop.hdds.scm.container.common.helpers.StorageContainerException;
import org.apache.hadoop.hdds.utils.db.CodecBuffer;
import org.apache.hadoop.hdds.utils.db.DBProfile;
@@ -233,6 +235,52 @@ public void testCreateContainerWithStorageType(
eq(StorageType.SSD));
}
+ @ContainerTestVersionInfo.ContainerTest
+ public void testContainerReportHasVolumeStorageType(
+ ContainerTestVersionInfo versionInfo) throws Exception {
+ init(versionInfo);
+
+ HddsVolume ssdVolume = new HddsVolume.Builder(
+ new File(folder, "ssd-volume").getAbsolutePath())
+ .conf(CONF)
+ .datanodeUuid(datanodeId.toString())
+ .storageType(StorageType.SSD)
+ .build();
+ StorageVolumeUtil.checkVolume(ssdVolume, scmId, scmId, CONF, null, null);
+ hddsVolumes.add(ssdVolume);
+
+ when(volumeChoosingPolicy.chooseVolume(anyList(), anyLong(),
+ eq(StorageType.SSD))).thenReturn(ssdVolume);
+
+ keyValueContainer.create(volumeSet, volumeChoosingPolicy, scmId,
+ StorageType.SSD);
+
+ ContainerReplicaProto report = keyValueContainer.getContainerReport();
+
+ // The container was created on an SSD volume, so both types agree.
+ assertThat(report.getStorageType()).isEqualTo(StorageTypeProto.SSD);
+ assertThat(report.getVolumeStorageType()).isEqualTo(StorageTypeProto.SSD);
+ }
+
+ @ContainerTestVersionInfo.ContainerTest
+ public void testContainerReportVolumeStorageTypeReflectsRelabelledVolume(
+ ContainerTestVersionInfo versionInfo) throws Exception {
+ init(versionInfo);
+ createContainer();
+
+ // Simulate an operator relabelling the volume: the container keeps the
type
+ // it was stamped with at creation, while the volume now reports a new one.
+ HddsVolume relabelled = mock(HddsVolume.class);
+ when(relabelled.getStorageType()).thenReturn(StorageType.ARCHIVE);
+ keyValueContainerData.setVolume(relabelled);
+
+ ContainerReplicaProto report = keyValueContainer.getContainerReport();
+
+ assertThat(report.getStorageType()).isEqualTo(StorageTypeProto.DISK);
+ assertThat(report.getVolumeStorageType())
+ .isEqualTo(StorageTypeProto.ARCHIVE);
+ }
+
/**
* Tests repair of containers affected by the bug reported in HDDS-6235.
*/
diff --git a/hadoop-hdds/interface-client/src/main/proto/hdds.proto
b/hadoop-hdds/interface-client/src/main/proto/hdds.proto
index b396af1966f..920ebd38e3f 100644
--- a/hadoop-hdds/interface-client/src/main/proto/hdds.proto
+++ b/hadoop-hdds/interface-client/src/main/proto/hdds.proto
@@ -484,6 +484,8 @@ message SCMContainerReplicaProto {
required int64 bytesUsed = 7;
optional int64 replicaIndex = 8;
optional int64 dataChecksum = 9;
+ optional StorageTypeProto storageType = 10;
+ optional StorageTypeProto volumeStorageType = 11;
}
message KeyContainerIDList {
diff --git
a/hadoop-hdds/interface-server/src/main/proto/ScmServerDatanodeHeartbeatProtocol.proto
b/hadoop-hdds/interface-server/src/main/proto/ScmServerDatanodeHeartbeatProtocol.proto
index f196d9cb7ce..2b0f285ca9a 100644
---
a/hadoop-hdds/interface-server/src/main/proto/ScmServerDatanodeHeartbeatProtocol.proto
+++
b/hadoop-hdds/interface-server/src/main/proto/ScmServerDatanodeHeartbeatProtocol.proto
@@ -236,6 +236,7 @@ message ContainerReplicaProto {
optional bool isEmpty = 15 [default = false];
optional int64 dataChecksum = 16;
optional StorageTypeProto storageType = 17;
+ optional StorageTypeProto volumeStorageType = 18;
}
message CommandStatusReportsProto {
diff --git
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/AbstractContainerReportHandler.java
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/AbstractContainerReportHandler.java
index 8b53459787f..22f95f10ab1 100644
---
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/AbstractContainerReportHandler.java
+++
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/AbstractContainerReportHandler.java
@@ -414,6 +414,9 @@ private void updateContainerReplica(final DatanodeDetails
datanodeDetails,
if (replicaProto.hasStorageType()) {
replicaBuilder.setStorageType(StorageTypeUtils.getFromProtobuf(replicaProto.getStorageType()));
}
+ if (replicaProto.hasVolumeStorageType()) {
+
replicaBuilder.setVolumeStorageType(StorageTypeUtils.getFromProtobuf(replicaProto.getVolumeStorageType()));
+ }
ContainerReplica replica =
replicaBuilder.setChecksums(ContainerChecksums.of(replicaProto.getDataChecksum()))
.build();
diff --git
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/ContainerReplica.java
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/ContainerReplica.java
index 7a296d0d9b2..bb6b3a6ec47 100644
---
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/ContainerReplica.java
+++
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/ContainerReplica.java
@@ -52,6 +52,7 @@ public final class ContainerReplica implements
Comparable<ContainerReplica> {
private final boolean isEmpty;
private final ContainerChecksums checksums;
private final StorageType storageType;
+ private final StorageType volumeStorageType;
private ContainerReplica(ContainerReplicaBuilder b) {
this.containerID = Objects.requireNonNull(b.containerID, "containerID ==
null");
@@ -65,6 +66,7 @@ private ContainerReplica(ContainerReplicaBuilder b) {
this.sequenceId = b.sequenceId;
this.checksums = Objects.requireNonNull(b.checksums, "checksums == null");
this.storageType = b.storageType;
+ this.volumeStorageType = b.volumeStorageType;
}
public ContainerID getContainerID() {
@@ -142,6 +144,12 @@ public StorageType getStorageType() {
return storageType;
}
+ /** @return the storage type of the volume holding this replica, or null if
not reported. */
+ @Nullable
+ public StorageType getVolumeStorageType() {
+ return volumeStorageType;
+ }
+
@Override
public int hashCode() {
return new HashCodeBuilder(61, 71)
@@ -204,6 +212,7 @@ public ContainerReplicaBuilder toBuilder() {
.setSequenceId(sequenceId)
.setEmpty(isEmpty)
.setStorageType(storageType)
+ .setVolumeStorageType(volumeStorageType)
.setChecksums(checksums);
}
@@ -220,6 +229,7 @@ public String toString() {
+ ", " + (isEmpty ? "empty" : "non-empty")
+ ", checksums=" + checksums
+ ", storageType=" + storageType
+ + ", volumeStorageType=" + volumeStorageType
+ '}';
}
@@ -239,6 +249,7 @@ public static class ContainerReplicaBuilder {
private boolean isEmpty;
private ContainerChecksums checksums;
private StorageType storageType;
+ private StorageType volumeStorageType;
/**
* Set Container Id.
@@ -323,6 +334,11 @@ public ContainerReplicaBuilder setStorageType(StorageType
storageType) {
return this;
}
+ public ContainerReplicaBuilder setVolumeStorageType(StorageType
volumeStorageType) {
+ this.volumeStorageType = volumeStorageType;
+ return this;
+ }
+
/**
* Constructs new ContainerReplicaBuilder.
*
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 79330033d80..a43a0012964 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
@@ -52,6 +52,7 @@
import org.apache.hadoop.fs.CommonConfigurationKeysPublic;
import org.apache.hadoop.hdds.client.ReplicationConfig;
import org.apache.hadoop.hdds.client.StorageTier;
+import org.apache.hadoop.hdds.client.StorageTypeUtils;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.conf.ReconfigurationHandler;
import org.apache.hadoop.hdds.protocol.DatanodeDetails;
@@ -365,7 +366,7 @@ public List<HddsProtos.SCMContainerReplicaProto>
getContainerReplicas(
Set<ContainerReplica> replicas = getScm().getContainerManager()
.getContainerReplicas(ContainerID.valueOf(containerId));
for (ContainerReplica r : replicas) {
- results.add(
+ HddsProtos.SCMContainerReplicaProto.Builder builder =
HddsProtos.SCMContainerReplicaProto.newBuilder()
.setContainerID(containerId)
.setState(r.getState().toString())
@@ -375,9 +376,14 @@ public List<HddsProtos.SCMContainerReplicaProto>
getContainerReplicas(
.setKeyCount(r.getKeyCount())
.setSequenceID(r.getSequenceId())
.setReplicaIndex(r.getReplicaIndex())
- .setDataChecksum(r.getDataChecksum())
- .build()
- );
+ .setDataChecksum(r.getDataChecksum());
+ if (r.getStorageType() != null) {
+
builder.setStorageType(StorageTypeUtils.getStorageTypeProto(r.getStorageType()));
+ }
+ if (r.getVolumeStorageType() != null) {
+
builder.setVolumeStorageType(StorageTypeUtils.getStorageTypeProto(r.getVolumeStorageType()));
+ }
+ results.add(builder.build());
}
AUDIT.logReadSuccess(buildAuditMessageForSuccess(
SCMAction.GET_CONTAINER_WITH_PIPELINE_BATCH,
diff --git
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/TestIncrementalContainerReportHandler.java
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/TestIncrementalContainerReportHandler.java
index 23455f1aae3..815a14f7a39 100644
---
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/TestIncrementalContainerReportHandler.java
+++
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/TestIncrementalContainerReportHandler.java
@@ -27,6 +27,7 @@
import static
org.apache.hadoop.hdds.scm.container.TestContainerReportHandler.createUniqueDataChecksumForReplica;
import static
org.apache.hadoop.hdds.scm.container.TestContainerReportHandler.getContainerReportsProto;
import static
org.apache.hadoop.hdds.upgrade.HDDSLayoutVersionManager.maxLayoutVersion;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -796,6 +797,75 @@ public void testReplicaStorageTypeValidation() throws
IOException {
}
}
+ @Test
+ public void testReplicaVolumeStorageType() throws IOException {
+ final IncrementalContainerReportHandler reportHandler =
+ new IncrementalContainerReportHandler(
+ nodeManager, containerManager, scmContext);
+ final ContainerInfo container = getContainer(LifeCycleState.CLOSING);
+ final DatanodeDetails datanode = randomDatanodeDetails();
+ nodeManager.register(datanode, null, null);
+ containerStateManager.addContainer(container.getProtobuf());
+
+ // The container was stamped SSD at creation but now sits on an ARCHIVE
+ // volume, which is what a relabelled or migrated replica looks like.
+ final ContainerReplicaProto replicaProto = newReplicaProto(container,
datanode)
+ .setStorageType(StorageTypeUtils.getStorageTypeProto(StorageType.SSD))
+ .setVolumeStorageType(
+ StorageTypeUtils.getStorageTypeProto(StorageType.ARCHIVE))
+ .build();
+ reportHandler.onMessage(new IncrementalContainerReportFromDatanode(
+ datanode, getIncrementalContainerReportProto(replicaProto)),
publisher);
+
+ final ContainerReplica replica = getOnlyReplica(container);
+ assertThat(replica.getStorageType()).isEqualTo(StorageType.SSD);
+ assertThat(replica.getVolumeStorageType()).isEqualTo(StorageType.ARCHIVE);
+ }
+
+ @Test
+ public void testReplicaStorageFieldsNullWhenNotReported() throws IOException
{
+ final IncrementalContainerReportHandler reportHandler =
+ new IncrementalContainerReportHandler(
+ nodeManager, containerManager, scmContext);
+ final ContainerInfo container = getContainer(LifeCycleState.CLOSING);
+ final DatanodeDetails datanode = randomDatanodeDetails();
+ nodeManager.register(datanode, null, null);
+ containerStateManager.addContainer(container.getProtobuf());
+
+ // A datanode running an older version reports none of the storage fields.
+ reportHandler.onMessage(new
IncrementalContainerReportFromDatanode(datanode,
+ getIncrementalContainerReportProto(
+ newReplicaProto(container, datanode).build())), publisher);
+
+ final ContainerReplica replica = getOnlyReplica(container);
+ assertThat(replica.getStorageType()).isNull();
+ assertThat(replica.getVolumeStorageType()).isNull();
+ }
+
+ private ContainerReplica getOnlyReplica(ContainerInfo container) {
+ final Set<ContainerReplica> replicas =
+ containerStateManager.getContainerReplicas(container.containerID());
+ assertThat(replicas).hasSize(1);
+ return replicas.iterator().next();
+ }
+
+ private static ContainerReplicaProto.Builder newReplicaProto(
+ ContainerInfo container, DatanodeDetails datanode) {
+ return ContainerReplicaProto.newBuilder()
+ .setContainerID(container.containerID().getIdForTesting())
+ .setState(ContainerReplicaProto.State.CLOSED)
+ .setOriginNodeId(datanode.getUuidString())
+ .setSize(5368709120L)
+ .setUsed(2000000000L)
+ .setKeyCount(100000000L)
+ .setReadCount(0)
+ .setWriteCount(0)
+ .setReadBytes(0)
+ .setWriteBytes(0)
+ .setBlockCommitSequenceId(10000L)
+ .setDeleteTransactionId(0);
+ }
+
private void addIncrContainerReport(ContainerInfo container, DatanodeDetails
datanode,
IncrementalContainerReportHandler reportHandler, StorageType
storageType) {
final IncrementalContainerReportProto containerReport1 =
diff --git
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/server/TestSCMClientProtocolServer.java
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/server/TestSCMClientProtocolServer.java
index 964fcb3b0d0..b366aed9e7c 100644
---
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/server/TestSCMClientProtocolServer.java
+++
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/server/TestSCMClientProtocolServer.java
@@ -19,6 +19,7 @@
import static
org.apache.hadoop.hdds.protocol.proto.HddsProtos.LifeCycleState.CLOSED;
import static
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_READONLY_ADMINISTRATORS;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -36,22 +37,28 @@
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
+import org.apache.hadoop.fs.StorageType;
import org.apache.hadoop.hdds.client.RatisReplicationConfig;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.conf.ReconfigurationHandler;
+import org.apache.hadoop.hdds.protocol.MockDatanodeDetails;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos.LifeCycleState;
+import
org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.ContainerReplicaProto;
import
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.DecommissionScmRequestProto;
import
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.DecommissionScmResponseProto;
import org.apache.hadoop.hdds.scm.HddsTestUtils;
+import org.apache.hadoop.hdds.scm.container.ContainerID;
import org.apache.hadoop.hdds.scm.container.ContainerInfo;
import org.apache.hadoop.hdds.scm.container.ContainerManagerImpl;
+import org.apache.hadoop.hdds.scm.container.ContainerReplica;
import org.apache.hadoop.hdds.scm.ha.SCMContext;
import org.apache.hadoop.hdds.scm.ha.SCMHAManagerStub;
import org.apache.hadoop.hdds.scm.ha.SCMNodeDetails;
import org.apache.hadoop.hdds.scm.pipeline.PipelineID;
import
org.apache.hadoop.hdds.scm.protocol.StorageContainerLocationProtocolServerSideTranslatorPB;
import org.apache.hadoop.hdds.utils.ProtocolMessageMetrics;
+import org.apache.hadoop.ozone.ClientVersion;
import org.apache.hadoop.ozone.container.common.SCMTestUtils;
import org.apache.hadoop.security.AccessControlException;
import org.apache.hadoop.security.UserGroupInformation;
@@ -194,6 +201,69 @@ public void testListContainerPaginationHasNoDuplicates()
throws Exception {
}
}
+ @Test
+ public void testGetContainerReplicasCopiesStorageFields() throws Exception {
+ final long containerId = 1L;
+ // Stamped SSD at creation, but currently sitting on an ARCHIVE volume.
+ ContainerReplica replica = newReplica(containerId)
+ .setStorageType(StorageType.SSD)
+ .setVolumeStorageType(StorageType.ARCHIVE)
+ .build();
+
+ SCMClientProtocolServer scmServer = new SCMClientProtocolServer(
+ new OzoneConfiguration(), mockStorageContainerManager(containerId,
replica),
+ mock(ReconfigurationHandler.class));
+ try {
+ List<HddsProtos.SCMContainerReplicaProto> replicas =
+ scmServer.getContainerReplicas(containerId,
ClientVersion.CURRENT_VERSION);
+
+ assertThat(replicas).hasSize(1);
+ HddsProtos.SCMContainerReplicaProto proto = replicas.get(0);
+
assertThat(proto.getStorageType()).isEqualTo(HddsProtos.StorageTypeProto.SSD);
+
assertThat(proto.getVolumeStorageType()).isEqualTo(HddsProtos.StorageTypeProto.ARCHIVE);
+ } finally {
+ scmServer.stop();
+ }
+ }
+
+ @Test
+ public void testGetContainerReplicasOmitsUnsetStorageFields() throws
Exception {
+ final long containerId = 1L;
+ // A replica reported by a datanode that does not send the storage fields.
+ ContainerReplica replica = newReplica(containerId).build();
+
+ SCMClientProtocolServer scmServer = new SCMClientProtocolServer(
+ new OzoneConfiguration(), mockStorageContainerManager(containerId,
replica),
+ mock(ReconfigurationHandler.class));
+ try {
+ List<HddsProtos.SCMContainerReplicaProto> replicas =
+ scmServer.getContainerReplicas(containerId,
ClientVersion.CURRENT_VERSION);
+
+ assertThat(replicas).hasSize(1);
+ HddsProtos.SCMContainerReplicaProto proto = replicas.get(0);
+ assertThat(proto.hasStorageType()).isFalse();
+ assertThat(proto.hasVolumeStorageType()).isFalse();
+ } finally {
+ scmServer.stop();
+ }
+ }
+
+ private static ContainerReplica.ContainerReplicaBuilder newReplica(long
containerId) {
+ return ContainerReplica.newBuilder()
+ .setContainerID(ContainerID.valueOf(containerId))
+ .setContainerState(ContainerReplicaProto.State.CLOSED)
+ .setDatanodeDetails(MockDatanodeDetails.randomDatanodeDetails())
+ .setSequenceId(1L);
+ }
+
+ private StorageContainerManager mockStorageContainerManager(
+ long containerId, ContainerReplica... replicas) throws IOException {
+ StorageContainerManager scmMock = mockStorageContainerManager();
+
when(scmMock.getContainerManager().getContainerReplicas(ContainerID.valueOf(containerId)))
+ .thenReturn(new HashSet<>(Arrays.asList(replicas)));
+ return scmMock;
+ }
+
private StorageContainerManager mockStorageContainerManager() {
List<ContainerInfo> infos = new ArrayList<>();
for (int i = 0; i < 10; i++) {
diff --git
a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/container/InfoSubcommand.java
b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/container/InfoSubcommand.java
index ea296ba9870..070d7eb4db4 100644
---
a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/container/InfoSubcommand.java
+++
b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/container/InfoSubcommand.java
@@ -59,6 +59,11 @@ public class InfoSubcommand extends ScmSubcommand {
description = "Format output as JSON")
private boolean json;
+ @CommandLine.Option(names = { "--with-storagetype" },
+ defaultValue = "false",
+ description = "Output Container and Volume StorageType info")
+ private boolean withStorageType;
+
@CommandLine.Mixin
private ContainerIDParameters containerList;
@@ -177,7 +182,7 @@ private void printDetails(ScmClient scmClient, long
containerID) throws IOExcept
if (replicas != null) {
String replicaStr = replicas.stream()
.sorted(Comparator.comparing(ContainerReplicaInfo::getReplicaIndex))
- .map(InfoSubcommand::buildReplicaDetails)
+ .map(replica -> buildReplicaDetails(replica, withStorageType))
.collect(Collectors.joining("," + System.lineSeparator()));
System.out.printf("Replicas: [%s]%n", replicaStr);
}
@@ -188,7 +193,7 @@ private static String buildDatanodeDetails(DatanodeDetails
details) {
return details.getUuidString() + "/" + details.getHostName();
}
- private static String buildReplicaDetails(ContainerReplicaInfo replica) {
+ private static String buildReplicaDetails(ContainerReplicaInfo replica,
boolean withStorageType) {
StringBuilder sb = new StringBuilder()
.append("State: ").append(replica.getState()).append(';');
if (replica.getReplicaIndex() != -1) {
@@ -197,6 +202,10 @@ private static String
buildReplicaDetails(ContainerReplicaInfo replica) {
sb.append(" SequenceId: ").append(replica.getSequenceId()).append(';')
.append(" Origin:
").append(replica.getPlaceOfBirth().toString()).append(';')
.append(" Location:
").append(buildDatanodeDetails(replica.getDatanodeDetails()));
+ if (withStorageType) {
+ sb.append("; ContainerStorageType: ").append(replica.getStorageType())
+ .append("; VolumeStorageType:
").append(replica.getVolumeStorageType());
+ }
return sb.toString();
}
diff --git
a/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/hdds/scm/cli/container/TestInfoSubCommand.java
b/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/hdds/scm/cli/container/TestInfoSubCommand.java
index 86e3bfdf273..3008d806886 100644
---
a/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/hdds/scm/cli/container/TestInfoSubCommand.java
+++
b/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/hdds/scm/cli/container/TestInfoSubCommand.java
@@ -43,6 +43,7 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
+import org.apache.hadoop.fs.StorageType;
import org.apache.hadoop.hdds.client.ECReplicationConfig;
import org.apache.hadoop.hdds.client.RatisReplicationConfig;
import org.apache.hadoop.hdds.protocol.DatanodeDetails;
@@ -331,6 +332,82 @@ private void testJsonOutput() throws IOException {
assertTrue(matcher.matches());
}
+ @Test
+ public void testStorageTypesNotPrintedByDefault() throws Exception {
+ when(scmClient.getContainerReplicas(anyLong()))
+ .thenReturn(getReplicasWithStorageInfo());
+ cmd = new InfoSubcommand();
+ new CommandLine(cmd).parseArgs("1");
+ cmd.execute(scmClient);
+
+ String output = outContent.toString(DEFAULT_ENCODING);
+ // Storage types stay hidden unless --with-storagetype is passed.
+ assertThat(output).doesNotContain("ContainerStorageType");
+ assertThat(output).doesNotContain("VolumeStorageType");
+ }
+
+ @Test
+ public void testStorageTypesPrintedWithFlag() throws Exception {
+ when(scmClient.getContainerReplicas(anyLong()))
+ .thenReturn(getReplicasWithStorageInfo());
+ cmd = new InfoSubcommand();
+ new CommandLine(cmd).parseArgs("1", "--with-storagetype");
+ cmd.execute(scmClient);
+
+ String output = outContent.toString(DEFAULT_ENCODING);
+ // Both ContainerStorageType and VolumeStorageType
+ // must appear, and the volume line must not echo the container's value.
+ assertThat(output).contains("ContainerStorageType: SSD");
+ assertThat(output).contains("VolumeStorageType: ARCHIVE");
+ }
+
+ @Test
+ public void testJsonIncludesStorageFields() throws Exception {
+ when(scmClient.getContainerReplicas(anyLong()))
+ .thenReturn(getReplicasWithStorageInfo());
+ cmd = new InfoSubcommand();
+ new CommandLine(cmd).parseArgs("1", "--json");
+ cmd.execute(scmClient);
+
+ // JSON carries the fields regardless of --with-storagetype, for scripting.
+ String output = outContent.toString(DEFAULT_ENCODING);
+ assertThat(output).contains("\"storageType\" : \"SSD\"");
+ assertThat(output).contains("\"volumeStorageType\" : \"ARCHIVE\"");
+ }
+
+ @Test
+ public void testJsonOmitsStorageFieldsWhenNotReported() throws Exception {
+
when(scmClient.getContainerReplicas(anyLong())).thenReturn(getReplicas(true));
+ cmd = new InfoSubcommand();
+ new CommandLine(cmd).parseArgs("1", "--json");
+ cmd.execute(scmClient);
+
+ // Null fields are dropped by the NON_NULL serialization inclusion, so an
+ // unset type must not surface as "storageType" : "".
+ String output = outContent.toString(DEFAULT_ENCODING);
+ assertThat(output).doesNotContain("storageType");
+ }
+
+ private List<ContainerReplicaInfo> getReplicasWithStorageInfo() {
+ List<ContainerReplicaInfo> replicas = new ArrayList<>();
+ int index = 1;
+ for (DatanodeDetails dn : datanodes) {
+ replicas.add(new ContainerReplicaInfo.Builder()
+ .setContainerID(1)
+ .setBytesUsed(1234)
+ .setState("CLOSED")
+ .setPlaceOfBirth(dn.getID())
+ .setDatanodeDetails(dn)
+ .setKeyCount(1)
+ .setSequenceId(1)
+ .setReplicaIndex(index++)
+ .setStorageType(StorageType.SSD)
+ .setVolumeStorageType(StorageType.ARCHIVE)
+ .build());
+ }
+ return replicas;
+ }
+
private List<ContainerReplicaInfo> getReplicas(boolean includeIndex) {
List<ContainerReplicaInfo> replicas = new ArrayList<>();
int index = 1;
diff --git
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/container/TestAllocateContainerWithStorageTier.java
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/container/TestAllocateContainerWithStorageTier.java
index ac54c014cc4..857269477e9 100644
---
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/container/TestAllocateContainerWithStorageTier.java
+++
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/container/TestAllocateContainerWithStorageTier.java
@@ -17,8 +17,10 @@
package org.apache.hadoop.hdds.scm.container;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.emptyList;
import static
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_DATANODE_PIPELINE_LIMIT;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -29,8 +31,10 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.function.BooleanSupplier;
import org.apache.hadoop.fs.StorageType;
import org.apache.hadoop.hdds.client.RatisReplicationConfig;
import org.apache.hadoop.hdds.client.ReplicationConfig;
@@ -38,11 +42,18 @@
import org.apache.hadoop.hdds.client.StorageTier;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos.ReplicationFactor;
+import org.apache.hadoop.hdds.scm.cli.ContainerOperationClient;
+import org.apache.hadoop.hdds.scm.client.ScmClient;
import org.apache.hadoop.hdds.scm.pipeline.Pipeline;
import org.apache.hadoop.hdds.scm.pipeline.Pipeline.PipelineState;
import org.apache.hadoop.hdds.scm.pipeline.PipelineManager;
import org.apache.hadoop.hdds.scm.server.StorageContainerManager;
+import org.apache.hadoop.ozone.DataTestUtil;
import org.apache.hadoop.ozone.MiniOzoneCluster;
+import org.apache.hadoop.ozone.client.OzoneBucket;
+import org.apache.hadoop.ozone.client.OzoneClient;
+import org.apache.hadoop.ozone.client.io.OzoneOutputStream;
+import org.apache.ozone.test.GenericTestUtils;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
@@ -222,6 +233,54 @@ public void testAllocateContainerWithStorageTierOnlyDisk()
throws Exception {
}
}
+ /**
+ * A replica written to a real datanode volume must report that volume's
+ * storage type back to SCM over a real heartbeat, so that
+ * {@code ozone admin container info} can display it.
+ */
+ @Test
+ public void testReplicaReportsVolumeStorageType() throws Exception {
+ // All volumes are DISK so that the only available tier is DISK, which
keeps
+ // pipeline creation deterministic under the pipeline limit of 1.
+ createCluster(Arrays.asList(
+ Arrays.asList(StorageType.DISK, StorageType.DISK),
+ Arrays.asList(StorageType.DISK, StorageType.DISK),
+ Arrays.asList(StorageType.DISK, StorageType.DISK)));
+ try (OzoneClient client = cluster.newClient();
+ ScmClient scmClient = new
ContainerOperationClient(cluster.getConf())) {
+ OzoneBucket bucket = DataTestUtil.createVolumeAndBucket(client);
+ byte[] data = "storage type reporting".getBytes(UTF_8);
+ try (OzoneOutputStream out = bucket.createKey("key1", data.length,
+ ratisThree, new HashMap<>())) {
+ out.write(data);
+ }
+
+ long containerId = bucket.getKey("key1").getOzoneKeyLocations().get(0)
+ .getContainerID();
+
+ // Wait for the datanodes to report the container to SCM.
+ GenericTestUtils.waitFor((BooleanSupplier) () -> {
+ try {
+ List<ContainerReplicaInfo> reported =
+ scmClient.getContainerReplicas(containerId);
+ return reported.size() == ratisThree.getRequiredNodes()
+ && reported.stream().allMatch(r -> r.getVolumeStorageType() !=
null);
+ } catch (IOException e) {
+ return false;
+ }
+ }, 500, 30000);
+
+ for (ContainerReplicaInfo replica :
scmClient.getContainerReplicas(containerId)) {
+ // On a freshly written container the stamped type and the volume's
+ // current type agree; a mismatch here would mean drift.
+ assertThat(replica.getStorageType()).isEqualTo(StorageType.DISK);
+ assertThat(replica.getVolumeStorageType()).isEqualTo(StorageType.DISK);
+ }
+ } finally {
+ cleanUp();
+ }
+ }
+
private void assertContainer(ContainerID containerID, ContainerManager
manager,
ReplicationConfig replicationConfig, StorageTier expectedStorageTier,
int expectedTotalCount)
throws IOException {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]