Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ private void updateContainerReplica(final DatanodeDetails datanodeDetails,
.setReplicaIndex(replicaProto.getReplicaIndex())
.setBytesUsed(replicaProto.getUsed())
.setEmpty(replicaProto.getIsEmpty())
.setDataChecksum(replicaProto.getDataChecksum())
.setChecksums(new ContainerChecksums(replicaProto.getDataChecksum()))
.build();

if (replica.getState().equals(State.DELETED)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* 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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need this class on the datanode too, so it needs to be in a more general package.

@echonesis echonesis Jul 24, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it.
I will move it into the path under hadoop-hdds/common.


import java.util.Objects;

/**
* Wrapper for container checksums (data, metadata, etc.).
* Provides equality, hash, and hex string rendering.
*/
public class ContainerChecksums {
Comment thread
echonesis marked this conversation as resolved.
Outdated
private final long dataChecksum;
private final Long metadataChecksum; // nullable for future use

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the recently merged #8565 for how we are planning to handle unset checksums. We can add this handling into the ContainerChecksums class so that the -1 placeholder gives us functionality similar to the has checks in protobuf objects.


public ContainerChecksums(long dataChecksum) {
this(dataChecksum, null);
}

public ContainerChecksums(long dataChecksum, Long metadataChecksum) {
this.dataChecksum = dataChecksum;
this.metadataChecksum = metadataChecksum;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could add factory methods (and make constructor private) to reduce accidental mismatch of intended and actual source (data or metadata).

Something like:

public static ContainerChecksums unknown(); // use constant value
public static ContainerChecksums dataOnly(long dataChecksum);
public static ContainerChecksums metadataOnly(long metadataChecksum);
public static ContainerChecksums of(long dataChecksum, long metadataChecksum);


public long getDataChecksum() {
return dataChecksum;
}

public Long getMetadataChecksum() {
return metadataChecksum;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ContainerChecksums)) {
return false;
}
ContainerChecksums that = (ContainerChecksums) obj;
return dataChecksum == that.dataChecksum &&
Objects.equals(metadataChecksum, that.metadataChecksum);
}

@Override
public int hashCode() {
return Objects.hash(dataChecksum, metadataChecksum);
}

@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("data=").append(Long.toHexString(dataChecksum));
if (metadataChecksum != null) {
sb.append(", metadata=").append(Long.toHexString(metadataChecksum));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 should be the displayed value for any checksum that is not set.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. I will set the default metadataChecksum value to be 0.

return sb.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public final class ContainerReplica implements Comparable<ContainerReplica> {
private final long keyCount;
private final long bytesUsed;
private final boolean isEmpty;
private final long dataChecksum;
private final ContainerChecksums checksums;

private ContainerReplica(ContainerReplicaBuilder b) {
this.containerID = Objects.requireNonNull(b.containerID, "containerID == null");
Expand All @@ -57,7 +57,7 @@ private ContainerReplica(ContainerReplicaBuilder b) {
this.replicaIndex = b.replicaIndex;
this.isEmpty = b.isEmpty;
this.sequenceId = b.sequenceId;
this.dataChecksum = b.dataChecksum;
this.checksums = Objects.requireNonNull(b.checksums, "checksums == null");
}

public ContainerID getContainerID() {
Expand Down Expand Up @@ -122,8 +122,8 @@ public boolean isEmpty() {
return isEmpty;
}

public long getDataChecksum() {
return dataChecksum;
public ContainerChecksums getChecksums() {
return checksums;
}

@Override
Expand Down Expand Up @@ -180,7 +180,8 @@ public ContainerReplicaBuilder toBuilder() {
.setOriginNodeId(originDatanodeId)
.setReplicaIndex(replicaIndex)
.setSequenceId(sequenceId)
.setEmpty(isEmpty);
.setEmpty(isEmpty)
.setChecksums(checksums);
}

@Override
Expand All @@ -194,7 +195,7 @@ public String toString() {
+ ", keyCount=" + keyCount
+ ", bytesUsed=" + bytesUsed
+ ", " + (isEmpty ? "empty" : "non-empty")
+ ", dataChecksum=" + dataChecksum
+ ", checksums=" + checksums
+ '}';
}

Expand All @@ -212,7 +213,7 @@ public static class ContainerReplicaBuilder {
private long keyCount;
private int replicaIndex;
private boolean isEmpty;
private long dataChecksum;
private ContainerChecksums checksums;

/**
* Set Container Id.
Expand Down Expand Up @@ -287,8 +288,8 @@ public ContainerReplicaBuilder setEmpty(boolean empty) {
return this;
}

public ContainerReplicaBuilder setDataChecksum(long dataChecksum) {
this.dataChecksum = dataChecksum;
public ContainerReplicaBuilder setChecksums(ContainerChecksums checksums) {
this.checksums = checksums;
return this;
}

Expand All @@ -298,6 +299,9 @@ public ContainerReplicaBuilder setDataChecksum(long dataChecksum) {
* @return ContainerReplicaBuilder
*/
public ContainerReplica build() {
if (this.checksums == null) {
this.checksums = new ContainerChecksums(0L);
}
return new ContainerReplica(this);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ public List<HddsProtos.SCMContainerReplicaProto> getContainerReplicas(
.setKeyCount(r.getKeyCount())
.setSequenceID(r.getSequenceId())
.setReplicaIndex(r.getReplicaIndex())
.setDataChecksum(r.getDataChecksum())
.setDataChecksum(r.getChecksums().getDataChecksum())
.build()
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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 static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.Test;

class ContainerChecksumsTest {
Comment thread
echonesis marked this conversation as resolved.
Outdated
@Test
void testEqualsAndHashCode() {
ContainerChecksums c1 = new ContainerChecksums(123L);
ContainerChecksums c2 = new ContainerChecksums(123L);
ContainerChecksums c3 = new ContainerChecksums(456L);
ContainerChecksums c4 = new ContainerChecksums(123L, 789L);
ContainerChecksums c5 = new ContainerChecksums(123L, 789L);
ContainerChecksums c6 = new ContainerChecksums(123L, 790L);

assertEquals(c1, c2);
assertEquals(c1.hashCode(), c2.hashCode());
assertNotEquals(c1, c3);
assertNotEquals(c1, c4);
assertEquals(c4, c5);
assertNotEquals(c4, c6);
}

@Test
void testToString() {
ContainerChecksums c1 = new ContainerChecksums(0x1234ABCDL);
assertTrue(c1.toString().contains("data=1234abcd"));
assertFalse(c1.toString().contains("metadata="));
Comment thread
echonesis marked this conversation as resolved.
Outdated

ContainerChecksums c2 = new ContainerChecksums(0x1234ABCDL, 0xDEADBEEFL);
assertTrue(c2.toString().contains("data=1234abcd"));
assertTrue(c2.toString().contains("metadata=deadbeef"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1205,7 +1205,7 @@ public void testWithNoContainerDataChecksum() throws Exception {

// All replicas should start with an empty data checksum in SCM.
boolean contOneDataChecksumsEmpty = containerManager.getContainerReplicas(contID).stream()
.allMatch(r -> r.getDataChecksum() == 0);
.allMatch(r -> r.getChecksums().getDataChecksum() == 0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could reduce change by keeping the getDataChecksum() method in ContainerReplica and delegate to checksums.

assertTrue(contOneDataChecksumsEmpty, "Replicas of container one should not yet have any data checksums.");

// Send a report to SCM from one datanode that still does not have a data checksum.
Expand All @@ -1222,7 +1222,7 @@ public void testWithNoContainerDataChecksum() throws Exception {
// Regardless of which datanode sent the report, none of them have checksums, so all replica's data checksums
// should remain empty.
boolean containerDataChecksumEmpty = containerManager.getContainerReplicas(contID).stream()
.allMatch(r -> r.getDataChecksum() == 0);
.allMatch(r -> r.getChecksums().getDataChecksum() == 0);
assertTrue(containerDataChecksumEmpty, "Replicas of the container should not have any data checksums.");
}

Expand Down Expand Up @@ -1254,7 +1254,7 @@ public void testWithContainerDataChecksum() throws Exception {

// All replicas should start with an empty data checksum in SCM.
boolean dataChecksumsEmpty = containerManager.getContainerReplicas(contID).stream()
.allMatch(r -> r.getDataChecksum() == 0);
.allMatch(r -> r.getChecksums().getDataChecksum() == 0);
assertTrue(dataChecksumsEmpty, "Replicas of container one should not yet have any data checksums.");

// For each datanode, send a container report with a mismatched checksum.
Expand All @@ -1278,7 +1278,7 @@ public void testWithContainerDataChecksum() throws Exception {
int numReplicasChecked = 0;
for (ContainerReplica replica: containerManager.getContainerReplicas(contID)) {
long expectedChecksum = createUniqueDataChecksumForReplica(contID, replica.getDatanodeDetails().getUuidString());
assertEquals(expectedChecksum, replica.getDataChecksum());
assertEquals(expectedChecksum, replica.getChecksums().getDataChecksum());
numReplicasChecked++;
}
assertEquals(numNodes, numReplicasChecked);
Expand All @@ -1304,7 +1304,7 @@ public void testWithContainerDataChecksum() throws Exception {
numReplicasChecked = 0;
for (ContainerReplica replica: containerManager.getContainerReplicas(contID)) {
long expectedChecksum = createMatchingDataChecksumForReplica(contID);
assertEquals(expectedChecksum, replica.getDataChecksum());
assertEquals(expectedChecksum, replica.getChecksums().getDataChecksum());
numReplicasChecked++;
}
assertEquals(numNodes, numReplicasChecked);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -648,7 +648,7 @@ public void testWithNoContainerDataChecksum() throws Exception {

// All replicas should start with an empty data checksum in SCM.
boolean contOneDataChecksumsEmpty = containerManager.getContainerReplicas(contID).stream()
.allMatch(r -> r.getDataChecksum() == 0);
.allMatch(r -> r.getChecksums().getDataChecksum() == 0);
assertTrue(contOneDataChecksumsEmpty, "Replicas of container one should not yet have any data checksums.");

// Send a report to SCM from one datanode that still does not have a data checksum.
Expand All @@ -663,7 +663,7 @@ public void testWithNoContainerDataChecksum() throws Exception {
// Regardless of which datanode sent the report, none of them have checksums, so all replica's data checksums
// should remain empty.
boolean containerDataChecksumEmpty = containerManager.getContainerReplicas(contID).stream()
.allMatch(r -> r.getDataChecksum() == 0);
.allMatch(r -> r.getChecksums().getDataChecksum() == 0);
assertTrue(containerDataChecksumEmpty, "Replicas of the container should not have any data checksums.");
}

Expand Down Expand Up @@ -695,7 +695,7 @@ public void testWithContainerDataChecksum() throws Exception {

// All replicas should start with a zero data checksum in SCM.
boolean dataChecksumsEmpty = containerManager.getContainerReplicas(contID).stream()
.allMatch(r -> r.getDataChecksum() == 0);
.allMatch(r -> r.getChecksums().getDataChecksum() == 0);
assertTrue(dataChecksumsEmpty, "Replicas of container one should not yet have any data checksums.");

// For each datanode, send a container report with a mismatched checksum.
Expand All @@ -721,7 +721,7 @@ public void testWithContainerDataChecksum() throws Exception {
for (ContainerReplica replica: containerManager.getContainerReplicas(contID)) {
long expectedChecksum = createUniqueDataChecksumForReplica(
contID, replica.getDatanodeDetails().getUuidString());
assertEquals(expectedChecksum, replica.getDataChecksum());
assertEquals(expectedChecksum, replica.getChecksums().getDataChecksum());
numReplicasChecked++;
}
assertEquals(numNodes, numReplicasChecked);
Expand All @@ -748,7 +748,7 @@ public void testWithContainerDataChecksum() throws Exception {
numReplicasChecked = 0;
for (ContainerReplica replica: containerManager.getContainerReplicas(contID)) {
long expectedChecksum = createMatchingDataChecksumForReplica(contID);
assertEquals(expectedChecksum, replica.getDataChecksum());
assertEquals(expectedChecksum, replica.getChecksums().getDataChecksum());
numReplicasChecked++;
}
assertEquals(numNodes, numReplicasChecked);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ public void testReplicasAreReportedForClosedContainerAfterRestart()
GenericTestUtils.waitFor(() ->
getContainerReplicas(newContainer).size() == 3, 200, 30000);
for (ContainerReplica replica : getContainerReplicas(newContainer)) {
assertNotEquals(0, replica.getDataChecksum());
assertNotEquals(0, replica.getChecksums().getDataChecksum());
}
}

Expand Down Expand Up @@ -201,7 +201,7 @@ public void testCloseClosedContainer()
}

for (ContainerReplica replica : getContainerReplicas(container)) {
assertNotEquals(0, replica.getDataChecksum());
assertNotEquals(0, replica.getChecksums().getDataChecksum());
}

assertThrows(IOException.class,
Expand Down Expand Up @@ -277,9 +277,9 @@ public void testContainerChecksumForClosedContainer() throws Exception {

// Wait for SCM to receive container reports with non-zero checksums for all replicas
GenericTestUtils.waitFor(() -> getContainerReplicas(containerInfo1).stream()
.allMatch(r -> r.getDataChecksum() != 0), 200, 5000);
.allMatch(r -> r.getChecksums().getDataChecksum() != 0), 200, 5000);
GenericTestUtils.waitFor(() -> getContainerReplicas(containerInfo2).stream()
.allMatch(r -> r.getDataChecksum() != 0), 200, 5000);
.allMatch(r -> r.getChecksums().getDataChecksum() != 0), 200, 5000);
}

private boolean checkContainerCloseInDatanode(HddsDatanodeService hddsDatanode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ void testCorruptionDetected(TestContainerCorruptions corruption)
assertTrue(containerChecksumFileExists(containerID));

waitForScmToSeeReplicaState(containerID, CLOSED);
long initialReportedDataChecksum = getContainerReplica(containerID).getDataChecksum();
long initialReportedDataChecksum = getContainerReplica(containerID).getChecksums().getDataChecksum();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this use case we only need to use the ContainerChecksums objects and compare them. We don't need to further extract the long values.

There is a new assertReplicaChecksumMatches introduced in #8565 that can take the ContainerChecksums object as a parameter instead of a long.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it.
I've updated assertReplicaChecksumMatches by replacing long with ContainerChecksums.


corruption.applyTo(container);

Expand All @@ -98,7 +98,7 @@ void testCorruptionDetected(TestContainerCorruptions corruption)

// Wait for SCM to get a report of the unhealthy replica with a different checksum than before.
waitForScmToSeeReplicaState(containerID, UNHEALTHY);
long newReportedDataChecksum = getContainerReplica(containerID).getDataChecksum();
long newReportedDataChecksum = getContainerReplica(containerID).getChecksums().getDataChecksum();
if (corruption == TestContainerCorruptions.MISSING_METADATA_DIR ||
corruption == TestContainerCorruptions.MISSING_CONTAINER_DIR) {
// In these cases, the new tree will not be able to be written since it exists in the metadata directory.
Expand Down
Loading