diff --git a/dev-support/pom.xml b/dev-support/pom.xml
index 2da4ab5b8e38..5e47a0ec6105 100644
--- a/dev-support/pom.xml
+++ b/dev-support/pom.xml
@@ -17,7 +17,7 @@
org.apache.ozone
ozone-main
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
ozone-dev-support
Apache Ozone Dev Support
diff --git a/hadoop-hdds/annotations/pom.xml b/hadoop-hdds/annotations/pom.xml
index 35d0e63ef3de..49f759b9c662 100644
--- a/hadoop-hdds/annotations/pom.xml
+++ b/hadoop-hdds/annotations/pom.xml
@@ -17,11 +17,11 @@
org.apache.ozone
hdds
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
hdds-annotation-processing
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
jar
Apache Ozone Annotation Processing
Apache Ozone annotation processing tools for validating custom
diff --git a/hadoop-hdds/client/pom.xml b/hadoop-hdds/client/pom.xml
index 9c94e152a9f2..5e50aaabd942 100644
--- a/hadoop-hdds/client/pom.xml
+++ b/hadoop-hdds/client/pom.xml
@@ -17,12 +17,12 @@
org.apache.ozone
hdds-hadoop-dependency-client
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
../hadoop-dependency-client
hdds-client
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
jar
Apache Ozone HDDS Client
Apache Ozone Distributed Data Store Client Library
diff --git a/hadoop-hdds/client/src/main/java/org/apache/hadoop/ozone/client/io/BoundedElasticByteBufferPool.java b/hadoop-hdds/client/src/main/java/org/apache/hadoop/ozone/client/io/BoundedElasticByteBufferPool.java
new file mode 100644
index 000000000000..17311ddb5da9
--- /dev/null
+++ b/hadoop-hdds/client/src/main/java/org/apache/hadoop/ozone/client/io/BoundedElasticByteBufferPool.java
@@ -0,0 +1,148 @@
+/*
+ * 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.ozone.client.io;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ComparisonChain;
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.atomic.AtomicLong;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.hadoop.io.ByteBufferPool;
+
+/**
+ * A bounded version of ElasticByteBufferPool that limits the total size
+ * of buffers that can be cached in the pool. This prevents unbounded memory
+ * growth in long-lived rpc clients like S3 Gateway.
+ *
+ * When the pool reaches its maximum size, newly returned buffers are not
+ * added back to the pool and will be garbage collected instead.
+ */
+public class BoundedElasticByteBufferPool implements ByteBufferPool {
+ private final TreeMap buffers = new TreeMap<>();
+ private final TreeMap directBuffers = new TreeMap<>();
+ private final long maxPoolSize;
+ private final AtomicLong currentPoolSize = new AtomicLong(0);
+
+ /**
+ * A logical timestamp counter used for creating unique Keys in the TreeMap.
+ * This is used as the insertionTime for the Key instead of System.nanoTime()
+ * to guarantee uniqueness and avoid a potential spin-wait in putBuffer
+ * if two buffers of the same capacity are added at the same nanosecond.
+ */
+ private long logicalTimestamp = 0;
+
+ public BoundedElasticByteBufferPool(long maxPoolSize) {
+ super();
+ this.maxPoolSize = maxPoolSize;
+ }
+
+ private TreeMap getBufferTree(boolean direct) {
+ return direct ? this.directBuffers : this.buffers;
+ }
+
+ @Override
+ public synchronized ByteBuffer getBuffer(boolean direct, int length) {
+ TreeMap tree = this.getBufferTree(direct);
+ Map.Entry entry = tree.ceilingEntry(new Key(length, 0L));
+ if (entry == null) {
+ // Pool is empty or has no suitable buffer. Allocate a new one.
+ return direct ? ByteBuffer.allocateDirect(length) : ByteBuffer.allocate(length);
+ }
+ tree.remove(entry.getKey());
+ ByteBuffer buffer = entry.getValue();
+
+ // Decrement the size because we are taking a buffer OUT of the pool.
+ currentPoolSize.addAndGet(-buffer.capacity());
+ buffer.clear();
+ return buffer;
+ }
+
+ @Override
+ public synchronized void putBuffer(ByteBuffer buffer) {
+ if (buffer == null) {
+ return;
+ }
+
+ if (currentPoolSize.get() + buffer.capacity() > maxPoolSize) {
+ // Pool is full, do not add the buffer back.
+ // It will be garbage collected by JVM.
+ return;
+ }
+
+ buffer.clear();
+ TreeMap tree = getBufferTree(buffer.isDirect());
+ Key key = new Key(buffer.capacity(), logicalTimestamp++);
+
+ tree.put(key, buffer);
+ // Increment the size because we have successfully added buffer back to the pool.
+ currentPoolSize.addAndGet(buffer.capacity());
+ }
+
+ /**
+ * Get the current size of buffers in the pool.
+ *
+ * @return Current pool size in bytes
+ */
+ @VisibleForTesting
+ public synchronized long getCurrentPoolSize() {
+ return currentPoolSize.get();
+ }
+
+ /**
+ * The Key for the buffer TreeMaps.
+ * This is copied directly from the original ElasticByteBufferPool.
+ */
+ protected static final class Key implements Comparable {
+ private final int capacity;
+ private final long insertionTime;
+
+ Key(int capacity, long insertionTime) {
+ this.capacity = capacity;
+ this.insertionTime = insertionTime;
+ }
+
+ @Override
+ public int compareTo(Key other) {
+ return ComparisonChain.start()
+ .compare(this.capacity, other.capacity)
+ .compare(this.insertionTime, other.insertionTime)
+ .result();
+ }
+
+ @Override
+ public boolean equals(Object rhs) {
+ if (rhs == null) {
+ return false;
+ }
+ try {
+ Key o = (Key) rhs;
+ return compareTo(o) == 0;
+ } catch (ClassCastException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder().append(capacity).append(insertionTime)
+ .toHashCode();
+ }
+ }
+}
diff --git a/hadoop-hdds/client/src/test/java/org/apache/hadoop/ozone/client/io/TestBoundedElasticByteBufferPool.java b/hadoop-hdds/client/src/test/java/org/apache/hadoop/ozone/client/io/TestBoundedElasticByteBufferPool.java
new file mode 100644
index 000000000000..f32b81bfe8cb
--- /dev/null
+++ b/hadoop-hdds/client/src/test/java/org/apache/hadoop/ozone/client/io/TestBoundedElasticByteBufferPool.java
@@ -0,0 +1,121 @@
+/*
+ * 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.ozone.client.io;
+
+import java.nio.ByteBuffer;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for BoundedElasticByteBufferPool.
+ */
+public class TestBoundedElasticByteBufferPool {
+
+ private static final int MB = 1024 * 1024;
+ private static final long MAX_POOL_SIZE = 3L * MB; // 3MB
+
+ @Test
+ public void testLogicalTimestampOrdering() {
+ // Pool with plenty of capacity
+ BoundedElasticByteBufferPool pool = new BoundedElasticByteBufferPool(MAX_POOL_SIZE);
+ int bufferSize = 5 * 1024; // 5KB
+
+ // Create and add three distinct buffers of the same size
+ ByteBuffer buffer1 = ByteBuffer.allocate(bufferSize);
+ ByteBuffer buffer2 = ByteBuffer.allocate(bufferSize);
+ ByteBuffer buffer3 = ByteBuffer.allocate(bufferSize);
+
+ // Store their unique identity hash codes
+ int hash1 = System.identityHashCode(buffer1);
+ int hash2 = System.identityHashCode(buffer2);
+ int hash3 = System.identityHashCode(buffer3);
+
+ pool.putBuffer(buffer1);
+ pool.putBuffer(buffer2);
+ pool.putBuffer(buffer3);
+
+ // The pool should now contain 15KB data
+ Assertions.assertEquals(bufferSize * 3L, pool.getCurrentPoolSize());
+
+ // Get the buffers back. They should come back in the same
+ // order they were put in (FIFO).
+ ByteBuffer retrieved1 = pool.getBuffer(false, bufferSize);
+ ByteBuffer retrieved2 = pool.getBuffer(false, bufferSize);
+ ByteBuffer retrieved3 = pool.getBuffer(false, bufferSize);
+
+ // Verify we got the exact same buffer instances back in FIFO order
+ Assertions.assertEquals(hash1, System.identityHashCode(retrieved1));
+ Assertions.assertEquals(hash2, System.identityHashCode(retrieved2));
+ Assertions.assertEquals(hash3, System.identityHashCode(retrieved3));
+
+ // The pool should now be empty
+ Assertions.assertEquals(0, pool.getCurrentPoolSize());
+ }
+
+ /**
+ * Verifies the core feature: the pool stops caching buffers
+ * once its maximum size is reached.
+ */
+ @Test
+ public void testPoolBoundingLogic() {
+ BoundedElasticByteBufferPool pool = new BoundedElasticByteBufferPool(MAX_POOL_SIZE);
+
+ ByteBuffer buffer1 = ByteBuffer.allocate(2 * MB);
+ ByteBuffer buffer2 = ByteBuffer.allocate(1 * MB);
+ ByteBuffer buffer3 = ByteBuffer.allocate(3 * MB);
+
+ int hash1 = System.identityHashCode(buffer1);
+ int hash2 = System.identityHashCode(buffer2);
+ int hash3 = System.identityHashCode(buffer3);
+
+ // 1. Put buffer 1 (Pool size: 2MB, remaining: 1MB)
+ pool.putBuffer(buffer1);
+ Assertions.assertEquals(2 * MB, pool.getCurrentPoolSize());
+
+ // 2. Put buffer 2 (Pool size: 2MB + 1MB = 3MB, remaining: 0)
+ // The check is (current(2MB) + new(1MB)) > max(3MB), which is false.
+ // So, the buffer IS added.
+ pool.putBuffer(buffer2);
+ Assertions.assertEquals(3 * MB, pool.getCurrentPoolSize());
+
+ // 3. Put buffer 3 (Capacity 3MB)
+ // The check is (current(3MB) + new(3MB)) > max(3MB), which is true.
+ // This buffer should be REJECTED.
+ pool.putBuffer(buffer3);
+ // The pool size should NOT change.
+ Assertions.assertEquals(3 * MB, pool.getCurrentPoolSize());
+
+ // 4. Get buffers back
+ ByteBuffer retrieved1 = pool.getBuffer(false, 2 * MB);
+ ByteBuffer retrieved2 = pool.getBuffer(false, 1 * MB);
+
+ // The pool should now be empty
+ Assertions.assertEquals(0, pool.getCurrentPoolSize());
+
+ // 5. Ask for a third buffer.
+ // Since buffer3 was rejected, this should be a NEWLY allocated buffer.
+ ByteBuffer retrieved3 = pool.getBuffer(false, 3 * MB);
+
+ // Verify that we got the first two buffers from the pool
+ Assertions.assertEquals(hash1, System.identityHashCode(retrieved1));
+ Assertions.assertEquals(hash2, System.identityHashCode(retrieved2));
+
+ // Verify that the third buffer is a NEW instance, not buffer3
+ Assertions.assertNotEquals(hash3, System.identityHashCode(retrieved3));
+ }
+}
diff --git a/hadoop-hdds/common/pom.xml b/hadoop-hdds/common/pom.xml
index 6fdf1dffa45e..64d332bdb0c9 100644
--- a/hadoop-hdds/common/pom.xml
+++ b/hadoop-hdds/common/pom.xml
@@ -17,11 +17,11 @@
org.apache.ozone
hdds-hadoop-dependency-client
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
../hadoop-dependency-client
hdds-common
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
jar
Apache Ozone HDDS Common
Apache Ozone Distributed Data Store Common
diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/upgrade/HDDSLayoutFeature.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/upgrade/HDDSLayoutFeature.java
index f6ac0a4872cc..30a574ddcbad 100644
--- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/upgrade/HDDSLayoutFeature.java
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/upgrade/HDDSLayoutFeature.java
@@ -42,7 +42,8 @@ public enum HDDSLayoutFeature implements LayoutFeature {
"to DatanodeDetails."),
HBASE_SUPPORT(8, "Datanode RocksDB Schema Version 3 has an extra table " +
"for the last chunk of blocks to support HBase.)"),
- WITNESSED_CONTAINER_DB_PROTO_VALUE(9, "ContainerID table schema to use value type as proto");
+ WITNESSED_CONTAINER_DB_PROTO_VALUE(9, "ContainerID table schema to use value type as proto"),
+ STORAGE_DATA_DISTRIBUTION(10, "ContainerID table schema to use value type as proto");
////////////////////////////// //////////////////////////////
diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java
index db66fed22fe9..ceca7d0c8824 100644
--- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java
@@ -690,6 +690,10 @@ public final class OzoneConfigKeys {
"ozone.security.crypto.compliance.mode";
public static final String OZONE_SECURITY_CRYPTO_COMPLIANCE_MODE_UNRESTRICTED = "unrestricted";
+ public static final String OZONE_CLIENT_ELASTIC_BYTE_BUFFER_POOL_MAX_SIZE =
+ "ozone.client.elastic.byte.buffer.pool.max.size";
+ public static final String OZONE_CLIENT_ELASTIC_BYTE_BUFFER_POOL_MAX_SIZE_DEFAULT = "16GB";
+
/**
* There is no need to instantiate this class.
*/
diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java
index cb4490c2c1db..aecbdfae615d 100644
--- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java
@@ -221,6 +221,7 @@ public final class OzoneConsts {
public static final String OM_SST_FILE_INFO_START_KEY = "startKey";
public static final String OM_SST_FILE_INFO_END_KEY = "endKey";
public static final String OM_SST_FILE_INFO_COL_FAMILY = "columnFamily";
+ public static final String OM_SLD_TXN_INFO = "transactionInfo";
// YAML fields for .container files
public static final String CONTAINER_ID = "containerID";
diff --git a/hadoop-hdds/common/src/main/resources/ozone-default.xml b/hadoop-hdds/common/src/main/resources/ozone-default.xml
index a6c8d61fff9e..0bfa98f991b9 100644
--- a/hadoop-hdds/common/src/main/resources/ozone-default.xml
+++ b/hadoop-hdds/common/src/main/resources/ozone-default.xml
@@ -465,6 +465,18 @@
Socket timeout for Ozone client. Unit could be defined with
postfix (ns,ms,s,m,h,d)
+
+ ozone.client.elastic.byte.buffer.pool.max.size
+ 16GB
+ OZONE, CLIENT
+
+ The maximum total size of buffers that can be cached in the client-side
+ ByteBufferPool. This pool is used heavily during EC read and write operations.
+ Setting a limit prevents unbounded memory growth in long-lived rpc clients
+ like the S3 Gateway. Once this limit is reached, used buffers are not
+ put back to the pool and will be garbage collected.
+
+
ozone.key.deleting.limit.per.task
50000
diff --git a/hadoop-hdds/config/pom.xml b/hadoop-hdds/config/pom.xml
index 45e32b47db23..44c7d02253c6 100644
--- a/hadoop-hdds/config/pom.xml
+++ b/hadoop-hdds/config/pom.xml
@@ -17,10 +17,10 @@
org.apache.ozone
hdds
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
hdds-config
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
jar
Apache Ozone HDDS Config
Apache Ozone Distributed Data Store Config Tools
diff --git a/hadoop-hdds/container-service/pom.xml b/hadoop-hdds/container-service/pom.xml
index 0c455d269591..ce6c7863b94c 100644
--- a/hadoop-hdds/container-service/pom.xml
+++ b/hadoop-hdds/container-service/pom.xml
@@ -17,10 +17,10 @@
org.apache.ozone
hdds
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
hdds-container-service
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
jar
Apache Ozone HDDS Container Service
Apache Ozone Distributed Data Store Container Service
diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/ContainerSet.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/ContainerSet.java
index baf6d48a9492..97b958d42e5b 100644
--- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/ContainerSet.java
+++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/ContainerSet.java
@@ -205,6 +205,10 @@ private boolean addContainer(Container> container, boolean overwrite) throws
recoveringContainerMap.put(
clock.millis() + recoveringTimeout, containerId);
}
+ HddsVolume volume = container.getContainerData().getVolume();
+ if (volume != null) {
+ volume.addContainer(containerId);
+ }
return true;
} else {
LOG.warn("Container already exists with container Id {}", containerId);
@@ -299,6 +303,10 @@ private boolean removeContainer(long containerId, boolean markMissing, boolean r
"containerMap", containerId);
return false;
} else {
+ HddsVolume volume = removed.getContainerData().getVolume();
+ if (volume != null) {
+ volume.removeContainer(containerId);
+ }
LOG.debug("Container with containerId {} is removed from containerMap",
containerId);
return true;
@@ -409,13 +417,19 @@ public Iterator> getRecoveringContainerIterator() {
*/
public Iterator> getContainerIterator(HddsVolume volume) {
Preconditions.checkNotNull(volume);
- Preconditions.checkNotNull(volume.getStorageID());
- String volumeUuid = volume.getStorageID();
- return containerMap.values().stream()
- .filter(x -> volumeUuid.equals(x.getContainerData().getVolume()
- .getStorageID()))
- .sorted(ContainerDataScanOrder.INSTANCE)
- .iterator();
+ Iterator containerIdIterator = volume.getContainerIterator();
+
+ List> containers = new ArrayList<>();
+ while (containerIdIterator.hasNext()) {
+ Long containerId = containerIdIterator.next();
+ Container> container = containerMap.get(containerId);
+ if (container != null) {
+ containers.add(container);
+ }
+ }
+ containers.sort(ContainerDataScanOrder.INSTANCE);
+
+ return containers.iterator();
}
/**
@@ -426,11 +440,7 @@ public Iterator> getContainerIterator(HddsVolume volume) {
*/
public long containerCount(HddsVolume volume) {
Preconditions.checkNotNull(volume);
- Preconditions.checkNotNull(volume.getStorageID());
- String volumeUuid = volume.getStorageID();
- return containerMap.values().stream()
- .filter(x -> volumeUuid.equals(x.getContainerData().getVolume()
- .getStorageID())).count();
+ return volume.getContainerCount();
}
/**
diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/DatanodeQueueMetrics.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/DatanodeQueueMetrics.java
index d47e0c0936ac..c0ed734da692 100644
--- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/DatanodeQueueMetrics.java
+++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/DatanodeQueueMetrics.java
@@ -25,6 +25,7 @@
import java.util.Map;
import org.apache.commons.text.WordUtils;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.SCMCommandProto;
+import org.apache.hadoop.hdfs.util.EnumCounters;
import org.apache.hadoop.metrics2.MetricsCollector;
import org.apache.hadoop.metrics2.MetricsInfo;
import org.apache.hadoop.metrics2.MetricsRecordBuilder;
@@ -114,20 +115,20 @@ private void initializeQueues() {
public void getMetrics(MetricsCollector collector, boolean b) {
MetricsRecordBuilder builder = collector.addRecord(METRICS_SOURCE_NAME);
- Map tmpMap =
+ EnumCounters tmpEnum =
datanodeStateMachine.getContext().getCommandQueueSummary();
for (Map.Entry entry:
stateContextCommandQueueMap.entrySet()) {
builder.addGauge(entry.getValue(),
- (long) tmpMap.getOrDefault(entry.getKey(), 0));
+ tmpEnum.get(entry.getKey()));
}
- tmpMap = datanodeStateMachine.getCommandDispatcher()
+ tmpEnum = datanodeStateMachine.getCommandDispatcher()
.getQueuedCommandCount();
for (Map.Entry entry:
commandDispatcherQueueMap.entrySet()) {
builder.addGauge(entry.getValue(),
- (long) tmpMap.getOrDefault(entry.getKey(), 0));
+ tmpEnum.get(entry.getKey()));
}
for (Map.Entry entry:
diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/DatanodeStateMachine.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/DatanodeStateMachine.java
index 1bd888c84a61..3b61050c4af4 100644
--- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/DatanodeStateMachine.java
+++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/DatanodeStateMachine.java
@@ -23,7 +23,6 @@
import java.io.IOException;
import java.time.Clock;
import java.time.ZoneId;
-import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
@@ -48,6 +47,7 @@
import org.apache.hadoop.hdds.upgrade.HDDSLayoutVersionManager;
import org.apache.hadoop.hdds.utils.IOUtils;
import org.apache.hadoop.hdds.utils.NettyMetrics;
+import org.apache.hadoop.hdfs.util.EnumCounters;
import org.apache.hadoop.ozone.HddsDatanodeService;
import org.apache.hadoop.ozone.HddsDatanodeStopService;
import org.apache.hadoop.ozone.container.checksum.DNContainerOperationClient;
@@ -620,23 +620,21 @@ public void join() throws InterruptedException {
* (single) thread, or queues it in the handler where a thread pool executor
* will process it. The total commands queued in the datanode is therefore
* the sum those in the CommandQueue and the dispatcher queues.
- * @return A map containing a count for each known command.
+ * @return EnumCounters containing a count for each known command.
*/
- public Map getQueuedCommandCount() {
- // This is a "sparse map" - there is not guaranteed to be an entry for
- // every command type
- Map commandQSummary =
+ public EnumCounters getQueuedCommandCount() {
+ // Get command counts from StateContext command queue
+ EnumCounters commandQSummary =
context.getCommandQueueSummary();
- // This map will contain an entry for every command type which is registered
+ // This EnumCounters will contain an entry for every command type which is registered
// with the dispatcher, and that should be all command types the DN knows
- // about. Any commands with nothing in the queue will return a count of
+ // about. Any commands with nothing in the queue will have a count of
// zero.
- Map dispatcherQSummary =
+ EnumCounters dispatcherQSummary =
commandDispatcher.getQueuedCommandCount();
- // Merge the "sparse" map into the fully populated one returning a count
+ // Merge the two EnumCounters into the fully populated one having a count
// for all known command types.
- commandQSummary.forEach((k, v)
- -> dispatcherQSummary.merge(k, v, Integer::sum));
+ dispatcherQSummary.add(commandQSummary);
return dispatcherQSummary;
}
diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/StateContext.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/StateContext.java
index 305b7b55a229..a7ea469f0c82 100644
--- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/StateContext.java
+++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/StateContext.java
@@ -71,6 +71,7 @@
import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.PipelineReport;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.PipelineReportsProto;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.SCMCommandProto;
+import org.apache.hadoop.hdfs.util.EnumCounters;
import org.apache.hadoop.ozone.container.common.statemachine.commandhandler.ClosePipelineCommandHandler;
import org.apache.hadoop.ozone.container.common.states.DatanodeState;
import org.apache.hadoop.ozone.container.common.states.datanode.InitDatanodeState;
@@ -796,12 +797,12 @@ public void addCommand(SCMCommand> command) {
this.addCmdStatus(command);
}
- public Map getCommandQueueSummary() {
- Map summary = new HashMap<>();
+ public EnumCounters getCommandQueueSummary() {
+ EnumCounters summary = new EnumCounters<>(SCMCommandProto.Type.class);
lock.lock();
try {
for (SCMCommand> cmd : commandQueue) {
- summary.put(cmd.getType(), summary.getOrDefault(cmd.getType(), 0) + 1);
+ summary.add(cmd.getType(), 1);
}
} finally {
lock.unlock();
diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/commandhandler/CommandDispatcher.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/commandhandler/CommandDispatcher.java
index ece91ffdd1c2..482878e6f58a 100644
--- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/commandhandler/CommandDispatcher.java
+++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/commandhandler/CommandDispatcher.java
@@ -24,6 +24,7 @@
import java.util.List;
import java.util.Map;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.SCMCommandProto.Type;
+import org.apache.hadoop.hdfs.util.EnumCounters;
import org.apache.hadoop.ozone.container.common.helpers.CommandHandlerMetrics;
import org.apache.hadoop.ozone.container.common.statemachine.SCMConnectionManager;
import org.apache.hadoop.ozone.container.common.statemachine.StateContext;
@@ -115,15 +116,15 @@ public void stop() {
/**
* For each registered handler, call its getQueuedCount method to retrieve the
- * number of queued commands. The returned map will contain an entry for every
+ * number of queued commands. The returned EnumCounters will contain an entry for every
* registered command in the dispatcher, with a value of zero if there are no
* queued commands.
- * @return A Map of CommandType where the value is the queued command count.
+ * @return EnumCounters of CommandType with the queued command count.
*/
- public Map getQueuedCommandCount() {
- Map counts = new HashMap<>();
+ public EnumCounters getQueuedCommandCount() {
+ EnumCounters counts = new EnumCounters<>(Type.class);
for (Map.Entry entry : handlerMap.entrySet()) {
- counts.put(entry.getKey(), entry.getValue().getQueuedCount());
+ counts.set(entry.getKey(), entry.getValue().getQueuedCount());
}
return counts;
}
diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/states/endpoint/HeartbeatEndpointTask.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/states/endpoint/HeartbeatEndpointTask.java
index 2681cdf90d5e..0959d78bdb20 100644
--- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/states/endpoint/HeartbeatEndpointTask.java
+++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/states/endpoint/HeartbeatEndpointTask.java
@@ -30,7 +30,6 @@
import java.time.ZonedDateTime;
import java.util.LinkedList;
import java.util.List;
-import java.util.Map;
import java.util.concurrent.Callable;
import org.apache.hadoop.hdds.conf.ConfigurationSource;
import org.apache.hadoop.hdds.protocol.DatanodeDetails;
@@ -45,6 +44,7 @@
import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.SCMHeartbeatRequestProto;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.SCMHeartbeatResponseProto;
import org.apache.hadoop.hdds.upgrade.HDDSLayoutVersionManager;
+import org.apache.hadoop.hdfs.util.EnumCounters;
import org.apache.hadoop.ozone.container.common.helpers.DeletedContainerBlocksSummary;
import org.apache.hadoop.ozone.container.common.statemachine.EndpointStateMachine;
import org.apache.hadoop.ozone.container.common.statemachine.EndpointStateMachine.EndPointStates;
@@ -242,14 +242,16 @@ private void addPipelineActions(
*/
private void addQueuedCommandCounts(
SCMHeartbeatRequestProto.Builder requestBuilder) {
- Map commandCount =
+ EnumCounters commandCount =
context.getParent().getQueuedCommandCount();
CommandQueueReportProto.Builder reportProto =
CommandQueueReportProto.newBuilder();
- for (Map.Entry entry
- : commandCount.entrySet()) {
- reportProto.addCommand(entry.getKey())
- .addCount(entry.getValue());
+ for (SCMCommandProto.Type type : SCMCommandProto.Type.values()) {
+ long count = commandCount.get(type);
+ if (count > 0) {
+ reportProto.addCommand(type)
+ .addCount((int) count);
+ }
}
requestBuilder.setCommandQueueReport(reportProto.build());
}
diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/HddsVolume.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/HddsVolume.java
index d6f404dd17ea..0988064e5fe8 100644
--- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/HddsVolume.java
+++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/HddsVolume.java
@@ -25,9 +25,11 @@
import jakarta.annotation.Nullable;
import java.io.File;
import java.io.IOException;
+import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
+import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
@@ -93,6 +95,8 @@ public class HddsVolume extends StorageVolume {
private final AtomicLong committedBytes = new AtomicLong(); // till Open containers become full
private Function gatherContainerUsages = (K) -> 0L;
+ private final ConcurrentSkipListSet containerIds = new ConcurrentSkipListSet<>();
+
// Mentions the type of volume
private final VolumeType type = VolumeType.DATA_VOLUME;
// The dedicated DbVolume that the db instance of this HddsVolume resides.
@@ -529,6 +533,22 @@ public long getContainers() {
return 0;
}
+ public void addContainer(long containerId) {
+ containerIds.add(containerId);
+ }
+
+ public void removeContainer(long containerId) {
+ containerIds.remove(containerId);
+ }
+
+ public Iterator getContainerIterator() {
+ return containerIds.iterator();
+ }
+
+ public long getContainerCount() {
+ return containerIds.size();
+ }
+
/**
* Pick a DbVolume for HddsVolume and init db instance.
* Use the HddsVolume directly if no DbVolume found.
diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/ozoneimpl/OzoneContainer.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/ozoneimpl/OzoneContainer.java
index df45715d9962..a77eec922776 100644
--- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/ozoneimpl/OzoneContainer.java
+++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/ozoneimpl/OzoneContainer.java
@@ -610,11 +610,13 @@ public ContainerSet getContainerSet() {
public Long gatherContainerUsages(HddsVolume storageVolume) {
AtomicLong usages = new AtomicLong();
- containerSet.getContainerMapIterator().forEachRemaining(e -> {
- if (e.getValue().getContainerData().getVolume().getStorageID().equals(storageVolume.getStorageID())) {
- usages.addAndGet(e.getValue().getContainerData().getBytesUsed());
+ Iterator containerIdIterator = storageVolume.getContainerIterator();
+ while (containerIdIterator.hasNext()) {
+ Container> container = containerSet.getContainer(containerIdIterator.next());
+ if (container != null) {
+ usages.addAndGet(container.getContainerData().getBytesUsed());
}
- });
+ }
return usages.get();
}
/**
diff --git a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/impl/TestContainerSet.java b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/impl/TestContainerSet.java
index 8c54dd848af4..efb4be86e8dc 100644
--- a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/impl/TestContainerSet.java
+++ b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/impl/TestContainerSet.java
@@ -28,6 +28,7 @@
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -40,6 +41,7 @@
import java.util.Optional;
import java.util.Random;
import java.util.UUID;
+import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.LongStream;
import org.apache.hadoop.conf.StorageUnit;
@@ -69,6 +71,33 @@ private void setLayoutVersion(ContainerLayoutVersion layoutVersion) {
this.layoutVersion = layoutVersion;
}
+ /**
+ * Create a mock {@link HddsVolume} to track container IDs.
+ */
+ private HddsVolume mockHddsVolume(String storageId) {
+ HddsVolume volume = mock(HddsVolume.class);
+ when(volume.getStorageID()).thenReturn(storageId);
+
+ ConcurrentSkipListSet containerIds = new ConcurrentSkipListSet<>();
+
+ doAnswer(inv -> {
+ Long containerId = inv.getArgument(0);
+ containerIds.add(containerId);
+ return null;
+ }).when(volume).addContainer(any(Long.class));
+
+ doAnswer(inv -> {
+ Long containerId = inv.getArgument(0);
+ containerIds.remove(containerId);
+ return null;
+ }).when(volume).removeContainer(any(Long.class));
+
+ when(volume.getContainerIterator()).thenAnswer(inv -> containerIds.iterator());
+ when(volume.getContainerCount()).thenAnswer(inv -> (long) containerIds.size());
+
+ return volume;
+ }
+
@ContainerLayoutTestInfo.ContainerTest
public void testAddGetRemoveContainer(ContainerLayoutVersion layout)
throws StorageContainerException {
@@ -157,10 +186,8 @@ public void testIteratorsAndCount(ContainerLayoutVersion layout)
public void testIteratorPerVolume(ContainerLayoutVersion layout)
throws StorageContainerException {
setLayoutVersion(layout);
- HddsVolume vol1 = mock(HddsVolume.class);
- when(vol1.getStorageID()).thenReturn("uuid-1");
- HddsVolume vol2 = mock(HddsVolume.class);
- when(vol2.getStorageID()).thenReturn("uuid-2");
+ HddsVolume vol1 = mockHddsVolume("uuid-1");
+ HddsVolume vol2 = mockHddsVolume("uuid-2");
ContainerSet containerSet = newContainerSet();
for (int i = 0; i < 10; i++) {
@@ -202,8 +229,7 @@ public void testIteratorPerVolume(ContainerLayoutVersion layout)
public void iteratorIsOrderedByScanTime(ContainerLayoutVersion layout)
throws StorageContainerException {
setLayoutVersion(layout);
- HddsVolume vol = mock(HddsVolume.class);
- when(vol.getStorageID()).thenReturn("uuid-1");
+ HddsVolume vol = mockHddsVolume("uuid-1");
Random random = new Random();
ContainerSet containerSet = newContainerSet();
int containerCount = 50;
@@ -375,4 +401,102 @@ private ContainerSet createContainerSet() throws StorageContainerException {
return containerSet;
}
+ /**
+ * Test that containerCount per volume returns correct count.
+ */
+ @ContainerLayoutTestInfo.ContainerTest
+ public void testContainerCountPerVolume(ContainerLayoutVersion layout)
+ throws StorageContainerException {
+ setLayoutVersion(layout);
+ HddsVolume vol1 = mockHddsVolume("uuid-1");
+ HddsVolume vol2 = mockHddsVolume("uuid-2");
+ HddsVolume vol3 = mockHddsVolume("uuid-3");
+
+ ContainerSet containerSet = newContainerSet();
+
+ // Add 100 containers to vol1, 50 to vol2, 0 to vol3
+ for (int i = 0; i < 100; i++) {
+ KeyValueContainerData kvData = new KeyValueContainerData(i,
+ layout,
+ (long) StorageUnit.GB.toBytes(5), UUID.randomUUID().toString(),
+ UUID.randomUUID().toString());
+ kvData.setVolume(vol1);
+ kvData.setState(ContainerProtos.ContainerDataProto.State.CLOSED);
+ containerSet.addContainer(new KeyValueContainer(kvData, new OzoneConfiguration()));
+ }
+
+ for (int i = 100; i < 150; i++) {
+ KeyValueContainerData kvData = new KeyValueContainerData(i,
+ layout,
+ (long) StorageUnit.GB.toBytes(5), UUID.randomUUID().toString(),
+ UUID.randomUUID().toString());
+ kvData.setVolume(vol2);
+ kvData.setState(ContainerProtos.ContainerDataProto.State.CLOSED);
+ containerSet.addContainer(new KeyValueContainer(kvData, new OzoneConfiguration()));
+ }
+
+ // Verify counts
+ assertEquals(100, containerSet.containerCount(vol1));
+ assertEquals(50, containerSet.containerCount(vol2));
+ assertEquals(0, containerSet.containerCount(vol3));
+
+ // Remove some containers and verify counts are updated
+ containerSet.removeContainer(0);
+ containerSet.removeContainer(1);
+ containerSet.removeContainer(100);
+ assertEquals(98, containerSet.containerCount(vol1));
+ assertEquals(49, containerSet.containerCount(vol2));
+ }
+
+ /**
+ * Test that per-volume iterator only returns containers from that volume.
+ */
+ @ContainerLayoutTestInfo.ContainerTest
+ public void testContainerIteratorPerVolume(ContainerLayoutVersion layout)
+ throws StorageContainerException {
+ setLayoutVersion(layout);
+ HddsVolume vol1 = mockHddsVolume("uuid-11");
+ HddsVolume vol2 = mockHddsVolume("uuid-12");
+
+ ContainerSet containerSet = newContainerSet();
+
+ // Add containers with specific IDs to each volume
+ List vol1Ids = new ArrayList<>();
+ List vol2Ids = new ArrayList<>();
+
+ for (int i = 0; i < 20; i++) {
+ KeyValueContainerData kvData = new KeyValueContainerData(i,
+ layout,
+ (long) StorageUnit.GB.toBytes(5), UUID.randomUUID().toString(),
+ UUID.randomUUID().toString());
+ if (i % 2 == 0) {
+ kvData.setVolume(vol1);
+ vol1Ids.add((long) i);
+ } else {
+ kvData.setVolume(vol2);
+ vol2Ids.add((long) i);
+ }
+ kvData.setState(ContainerProtos.ContainerDataProto.State.CLOSED);
+ containerSet.addContainer(new KeyValueContainer(kvData, new OzoneConfiguration()));
+ }
+
+ // Verify iterator only returns containers from vol1
+ Iterator> iter1 = containerSet.getContainerIterator(vol1);
+ List foundVol1Ids = new ArrayList<>();
+ while (iter1.hasNext()) {
+ foundVol1Ids.add(iter1.next().getContainerData().getContainerID());
+ }
+ assertEquals(vol1Ids.size(), foundVol1Ids.size());
+ assertTrue(foundVol1Ids.containsAll(vol1Ids));
+
+ // Verify iterator only returns containers from vol2
+ Iterator> iter2 = containerSet.getContainerIterator(vol2);
+ List foundVol2Ids = new ArrayList<>();
+ while (iter2.hasNext()) {
+ foundVol2Ids.add(iter2.next().getContainerData().getContainerID());
+ }
+ assertEquals(vol2Ids.size(), foundVol2Ids.size());
+ assertTrue(foundVol2Ids.containsAll(vol2Ids));
+ }
+
}
diff --git a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/statemachine/TestStateContext.java b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/statemachine/TestStateContext.java
index 73e4b8f43686..8d79335591b9 100644
--- a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/statemachine/TestStateContext.java
+++ b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/statemachine/TestStateContext.java
@@ -59,6 +59,7 @@
import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.PipelineReportsProto;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.SCMCommandProto;
import org.apache.hadoop.hdds.scm.pipeline.PipelineID;
+import org.apache.hadoop.hdfs.util.EnumCounters;
import org.apache.hadoop.ozone.container.common.impl.ContainerSet;
import org.apache.hadoop.ozone.container.common.statemachine.DatanodeStateMachine.DatanodeStates;
import org.apache.hadoop.ozone.container.common.states.DatanodeState;
@@ -709,15 +710,15 @@ public void testCommandQueueSummary() throws IOException {
ctx.addCommand(new CloseContainerCommand(1, PipelineID.randomId()));
ctx.addCommand(new ReconcileContainerCommand(4, Collections.emptySet()));
- Map summary = ctx.getCommandQueueSummary();
+ EnumCounters summary = ctx.getCommandQueueSummary();
assertEquals(3,
- summary.get(SCMCommandProto.Type.replicateContainerCommand).intValue());
+ summary.get(SCMCommandProto.Type.replicateContainerCommand));
assertEquals(2,
- summary.get(SCMCommandProto.Type.closePipelineCommand).intValue());
+ summary.get(SCMCommandProto.Type.closePipelineCommand));
assertEquals(1,
- summary.get(SCMCommandProto.Type.closeContainerCommand).intValue());
+ summary.get(SCMCommandProto.Type.closeContainerCommand));
assertEquals(1,
- summary.get(SCMCommandProto.Type.reconcileContainerCommand).intValue());
+ summary.get(SCMCommandProto.Type.reconcileContainerCommand));
}
@Test
diff --git a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/statemachine/commandhandler/TestDeleteBlocksCommandHandler.java b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/statemachine/commandhandler/TestDeleteBlocksCommandHandler.java
index b2e1c72e487a..50b08c7aa2f2 100644
--- a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/statemachine/commandhandler/TestDeleteBlocksCommandHandler.java
+++ b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/statemachine/commandhandler/TestDeleteBlocksCommandHandler.java
@@ -49,6 +49,7 @@
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
@@ -104,13 +105,31 @@ private void prepareTest(ContainerTestVersionInfo versionInfo)
setup();
}
+ /**
+ * Create a mock {@link HddsVolume} to track container IDs.
+ */
+ private HddsVolume mockHddsVolume(String storageId) {
+ HddsVolume volume = mock(HddsVolume.class);
+ when(volume.getStorageID()).thenReturn(storageId);
+
+ ConcurrentSkipListSet containerIds = new ConcurrentSkipListSet<>();
+
+ doAnswer(inv -> {
+ Long containerId = inv.getArgument(0);
+ containerIds.add(containerId);
+ return null;
+ }).when(volume).addContainer(any(Long.class));
+
+ when(volume.getContainerIterator()).thenAnswer(inv -> containerIds.iterator());
+ return volume;
+ }
+
private void setup() throws Exception {
OzoneConfiguration conf = new OzoneConfiguration();
ContainerLayoutVersion layout = ContainerLayoutVersion.FILE_PER_BLOCK;
OzoneContainer ozoneContainer = mock(OzoneContainer.class);
containerSet = newContainerSet();
- volume1 = mock(HddsVolume.class);
- when(volume1.getStorageID()).thenReturn("uuid-1");
+ volume1 = mockHddsVolume("uuid-1");
for (int i = 0; i <= 10; i++) {
KeyValueContainerData data =
new KeyValueContainerData(i,
diff --git a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/states/endpoint/TestHeartbeatEndpointTask.java b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/states/endpoint/TestHeartbeatEndpointTask.java
index 11c145ee38ae..c04d2c758842 100644
--- a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/states/endpoint/TestHeartbeatEndpointTask.java
+++ b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/states/endpoint/TestHeartbeatEndpointTask.java
@@ -32,10 +32,8 @@
import com.google.protobuf.Proto2Utils;
import java.net.InetSocketAddress;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.Map;
import java.util.OptionalLong;
import java.util.Set;
import java.util.UUID;
@@ -53,6 +51,7 @@
import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.SCMHeartbeatRequestProto;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.SCMHeartbeatResponseProto;
import org.apache.hadoop.hdds.upgrade.HDDSLayoutVersionManager;
+import org.apache.hadoop.hdfs.util.EnumCounters;
import org.apache.hadoop.ozone.container.common.statemachine.DatanodeStateMachine;
import org.apache.hadoop.ozone.container.common.statemachine.DatanodeStateMachine.DatanodeStates;
import org.apache.hadoop.ozone.container.common.statemachine.EndpointStateMachine;
@@ -102,13 +101,16 @@ public void handlesReconstructContainerCommand() throws Exception {
StateContext context = new StateContext(conf, DatanodeStates.RUNNING,
datanodeStateMachine, "");
+ when(datanodeStateMachine.getQueuedCommandCount())
+ .thenReturn(new EnumCounters<>(SCMCommandProto.Type.class));
+
// WHEN
HeartbeatEndpointTask task = getHeartbeatEndpointTask(conf, context, scm);
task.call();
// THEN
assertEquals(1, context.getCommandQueueSummary()
- .get(reconstructECContainersCommand).intValue());
+ .get(reconstructECContainersCommand));
}
@Test
@@ -138,13 +140,16 @@ public void testHandlesReconcileContainerCommand() throws Exception {
StateContext context = new StateContext(conf, DatanodeStates.RUNNING,
datanodeStateMachine, "");
+ when(datanodeStateMachine.getQueuedCommandCount())
+ .thenReturn(new EnumCounters<>(SCMCommandProto.Type.class));
+
// WHEN
HeartbeatEndpointTask task = getHeartbeatEndpointTask(conf, context, scm);
task.call();
// THEN
assertEquals(1, context.getCommandQueueSummary()
- .get(reconcileContainerCommand).intValue());
+ .get(reconcileContainerCommand));
}
@Test
@@ -165,8 +170,12 @@ public void testheartbeatWithoutReports() throws Exception {
.build());
OzoneConfiguration conf = new OzoneConfiguration();
+ DatanodeStateMachine datanodeStateMachine = mock(DatanodeStateMachine.class);
StateContext context = new StateContext(conf, DatanodeStates.RUNNING,
- mock(DatanodeStateMachine.class), "");
+ datanodeStateMachine, "");
+
+ when(datanodeStateMachine.getQueuedCommandCount())
+ .thenReturn(new EnumCounters<>(SCMCommandProto.Type.class));
context.setTermOfLeaderSCM(1);
HeartbeatEndpointTask endpointTask = getHeartbeatEndpointTask(
conf, context, scm);
@@ -185,9 +194,12 @@ public void testheartbeatWithoutReports() throws Exception {
@Test
public void testheartbeatWithNodeReports() throws Exception {
OzoneConfiguration conf = new OzoneConfiguration();
+ DatanodeStateMachine datanodeStateMachine = mock(DatanodeStateMachine.class);
StateContext context = new StateContext(conf, DatanodeStates.RUNNING,
- mock(DatanodeStateMachine.class), "");
+ datanodeStateMachine, "");
+ when(datanodeStateMachine.getQueuedCommandCount())
+ .thenReturn(new EnumCounters<>(SCMCommandProto.Type.class));
StorageContainerDatanodeProtocolClientSideTranslatorPB scm =
mock(
StorageContainerDatanodeProtocolClientSideTranslatorPB.class);
@@ -217,8 +229,12 @@ public void testheartbeatWithNodeReports() throws Exception {
@Test
public void testheartbeatWithContainerReports() throws Exception {
OzoneConfiguration conf = new OzoneConfiguration();
+ DatanodeStateMachine datanodeStateMachine = mock(DatanodeStateMachine.class);
StateContext context = new StateContext(conf, DatanodeStates.RUNNING,
- mock(DatanodeStateMachine.class), "");
+ datanodeStateMachine, "");
+
+ when(datanodeStateMachine.getQueuedCommandCount())
+ .thenReturn(new EnumCounters<>(SCMCommandProto.Type.class));
StorageContainerDatanodeProtocolClientSideTranslatorPB scm =
mock(
@@ -249,8 +265,12 @@ public void testheartbeatWithContainerReports() throws Exception {
@Test
public void testheartbeatWithCommandStatusReports() throws Exception {
OzoneConfiguration conf = new OzoneConfiguration();
+ DatanodeStateMachine datanodeStateMachine = mock(DatanodeStateMachine.class);
StateContext context = new StateContext(conf, DatanodeStates.RUNNING,
- mock(DatanodeStateMachine.class), "");
+ datanodeStateMachine, "");
+
+ when(datanodeStateMachine.getQueuedCommandCount())
+ .thenReturn(new EnumCounters<>(SCMCommandProto.Type.class));
StorageContainerDatanodeProtocolClientSideTranslatorPB scm =
mock(
@@ -282,8 +302,12 @@ public void testheartbeatWithCommandStatusReports() throws Exception {
@Test
public void testheartbeatWithContainerActions() throws Exception {
OzoneConfiguration conf = new OzoneConfiguration();
+ DatanodeStateMachine datanodeStateMachine = mock(DatanodeStateMachine.class);
StateContext context = new StateContext(conf, DatanodeStates.RUNNING,
- mock(DatanodeStateMachine.class), "");
+ datanodeStateMachine, "");
+
+ when(datanodeStateMachine.getQueuedCommandCount())
+ .thenReturn(new EnumCounters<>(SCMCommandProto.Type.class));
StorageContainerDatanodeProtocolClientSideTranslatorPB scm =
mock(
@@ -320,10 +344,10 @@ public void testheartbeatWithAllReports() throws Exception {
datanodeStateMachine, "");
// Return a Map of command counts when the heartbeat logic requests it
- final Map commands = new HashMap<>();
+ final EnumCounters commands = new EnumCounters<>(SCMCommandProto.Type.class);
int count = 1;
for (SCMCommandProto.Type cmd : SCMCommandProto.Type.values()) {
- commands.put(cmd, count++);
+ commands.set(cmd, count++);
}
when(datanodeStateMachine.getQueuedCommandCount())
.thenReturn(commands);
@@ -358,10 +382,16 @@ public void testheartbeatWithAllReports() throws Exception {
assertTrue(heartbeat.hasContainerActions());
assertTrue(heartbeat.hasCommandQueueReport());
CommandQueueReportProto queueCount = heartbeat.getCommandQueueReport();
- assertEquals(queueCount.getCommandCount(), commands.size());
- assertEquals(queueCount.getCountCount(), commands.size());
- for (int i = 0; i < commands.size(); i++) {
- assertEquals(commands.get(queueCount.getCommand(i)).intValue(),
+ int commandCount = 0;
+ for (SCMCommandProto.Type type : SCMCommandProto.Type.values()) {
+ if (commands.get(type) > 0) {
+ commandCount++;
+ }
+ }
+ assertEquals(queueCount.getCommandCount(), commandCount);
+ assertEquals(queueCount.getCountCount(), commandCount);
+ for (int i = 0; i < commandCount; i++) {
+ assertEquals(commands.get(queueCount.getCommand(i)),
queueCount.getCount(i));
}
}
diff --git a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/ozoneimpl/TestOzoneContainer.java b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/ozoneimpl/TestOzoneContainer.java
index 4cca1dd21cd0..91c3f8ed58c2 100644
--- a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/ozoneimpl/TestOzoneContainer.java
+++ b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/ozoneimpl/TestOzoneContainer.java
@@ -21,6 +21,10 @@
import static org.apache.hadoop.ozone.container.common.ContainerTestUtils.createDbInstancesForTestIfNeeded;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import com.google.common.base.Preconditions;
import java.io.File;
@@ -30,9 +34,11 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
+import java.util.concurrent.ConcurrentSkipListSet;
import org.apache.commons.io.FileUtils;
import org.apache.hadoop.conf.StorageUnit;
import org.apache.hadoop.hdds.HddsConfigKeys;
@@ -110,6 +116,25 @@ public void cleanUp() {
}
}
+ /**
+ * Create a mock {@link HddsVolume} to track container IDs.
+ */
+ private HddsVolume mockHddsVolume(String storageId) {
+ HddsVolume volume = mock(HddsVolume.class);
+ when(volume.getStorageID()).thenReturn(storageId);
+
+ ConcurrentSkipListSet containerIds = new ConcurrentSkipListSet<>();
+
+ doAnswer(inv -> {
+ Long containerId = inv.getArgument(0);
+ containerIds.add(containerId);
+ return null;
+ }).when(volume).addContainer(any(Long.class));
+
+ when(volume.getContainerIterator()).thenAnswer(inv -> containerIds.iterator());
+ return volume;
+ }
+
@ContainerTestVersionInfo.ContainerTest
public void testBuildContainerMap(ContainerTestVersionInfo versionInfo)
throws Exception {
@@ -117,9 +142,14 @@ public void testBuildContainerMap(ContainerTestVersionInfo versionInfo)
// Format the volumes
List volumes =
StorageVolumeUtil.getHddsVolumesList(volumeSet.getVolumesList());
+
+ // Create mock volumes with tracking, mapped by storage ID
+ Map mockVolumeMap = new HashMap<>();
for (HddsVolume volume : volumes) {
volume.format(clusterId);
commitSpaceMap.put(getVolumeKey(volume), Long.valueOf(0));
+ // Create mock for each real volume
+ mockVolumeMap.put(volume.getStorageID(), mockHddsVolume(volume.getStorageID()));
}
List containerDatas = new ArrayList<>();
// Add containers to disk
@@ -140,6 +170,12 @@ public void testBuildContainerMap(ContainerTestVersionInfo versionInfo)
keyValueContainerData, conf);
keyValueContainer.create(volumeSet, volumeChoosingPolicy, clusterId);
myVolume = keyValueContainer.getContainerData().getVolume();
+
+ // Track container in mock volume
+ HddsVolume mockVolume = mockVolumeMap.get(myVolume.getStorageID());
+ if (mockVolume != null) {
+ mockVolume.addContainer(i);
+ }
freeBytes = addBlocks(keyValueContainer, 2, 3, 65536);
@@ -158,7 +194,13 @@ public void testBuildContainerMap(ContainerTestVersionInfo versionInfo)
assertEquals(numTestContainers, containerset.containerCount());
verifyCommittedSpace(ozoneContainer);
// container usage here, nrOfContainer * blocks * chunksPerBlock * datalen
- assertEquals(10 * 2 * 3 * 65536, ozoneContainer.gatherContainerUsages(volumes.get(0)));
+ // Use mock volumes to verify container usage
+ long totalUsage = 0;
+ for (HddsVolume volume : volumes) {
+ HddsVolume mockVolume = mockVolumeMap.get(volume.getStorageID());
+ totalUsage += ozoneContainer.gatherContainerUsages(mockVolume);
+ }
+ assertEquals(10 * 2 * 3 * 65536, totalUsage);
Set missingContainers = new HashSet<>();
for (int i = 0; i < numTestContainers; i++) {
if (i % 2 == 0) {
diff --git a/hadoop-hdds/crypto-api/pom.xml b/hadoop-hdds/crypto-api/pom.xml
index 474359f916b0..801c7b0d036c 100644
--- a/hadoop-hdds/crypto-api/pom.xml
+++ b/hadoop-hdds/crypto-api/pom.xml
@@ -17,11 +17,11 @@
org.apache.ozone
hdds
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
hdds-crypto-api
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
Apache Ozone HDDS Crypto
Apache Ozone Distributed Data Store cryptographic functions
diff --git a/hadoop-hdds/crypto-default/pom.xml b/hadoop-hdds/crypto-default/pom.xml
index 7194bb7e6e54..49e7065476ef 100644
--- a/hadoop-hdds/crypto-default/pom.xml
+++ b/hadoop-hdds/crypto-default/pom.xml
@@ -17,11 +17,11 @@
org.apache.ozone
hdds
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
hdds-crypto-default
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
Apache Ozone HDDS Crypto - Default
Default implementation of Apache Ozone Distributed Data Store's cryptographic functions
diff --git a/hadoop-hdds/docs/content/feature/Snapshot.md b/hadoop-hdds/docs/content/feature/Snapshot.md
index d6dcf7ea8da3..2e013e3a3c41 100644
--- a/hadoop-hdds/docs/content/feature/Snapshot.md
+++ b/hadoop-hdds/docs/content/feature/Snapshot.md
@@ -100,6 +100,32 @@ Manage snapshots using `ozone sh` or `ozone fs` (Hadoop-compatible) commands:
Output prefixes: `+` (add), `-` (delete), `M` (modify), `R` (rename). Use `-p`, `-t` for pagination.
Manage diff jobs: `ozone sh snapshot listDiff /vol1/bucket1`, `ozone sh snapshot cancelDiff `.
+* **List Snapshot Diff Jobs:** Lists snapshot diff jobs for a bucket.
+ ```shell
+ ozone sh snapshot listDiff /vol1/bucket1
+ ```
+ By default, lists jobs with `in_progress` status. Use `--job-status` to filter by specific status:
+ ```shell
+ # List jobs with specific status (queued, in_progress, done, failed, rejected)
+ ozone sh snapshot listDiff /vol1/bucket1 --job-status done
+ ```
+ Use `--all-status` to list all jobs regardless of status:
+ ```shell
+ # List all snapshot diff jobs regardless of status
+ ozone sh snapshot listDiff /vol1/bucket1 --all-status
+ ```
+ **Note:** The difference between `--all-status` and `-all` (or `-a`):
+ * `--all-status`: Controls which jobs to show based on status (lists all jobs regardless of status)
+ * `-all` (or `-a`): Controls the number of results returned (pagination option, removes pagination limit, **not related to snapshot diff job status**)
+
+ For example:
+ ```shell
+ # List all jobs regardless of status, with pagination limit removed
+ ozone sh snapshot listDiff /vol1/bucket1 --all-status -all
+ # Or limit results to 10 items
+ ozone sh snapshot listDiff /vol1/bucket1 --all-status -l 10
+ ```
+
* **Rename Snapshot:**
```shell
ozone sh snapshot rename /vol1/bucket1
diff --git a/hadoop-hdds/docs/content/interface/HttpFS.md b/hadoop-hdds/docs/content/interface/HttpFS.md
index cebe0d315b02..a4eb7271a115 100644
--- a/hadoop-hdds/docs/content/interface/HttpFS.md
+++ b/hadoop-hdds/docs/content/interface/HttpFS.md
@@ -45,24 +45,102 @@ HttpFS has built-in security supporting Hadoop pseudo authentication and Kerbero
HttpFS service itself is a Jetty based web-application that uses the Hadoop FileSystem API to talk to the cluster, it is a separate service which provides access to Ozone via a REST APIs. It should be started in addition to other regular Ozone components.
-To try it out, you can start a Docker Compose dev cluster that has an HttpFS gateway.
+To try it out, follow the instructions from the link below to start the Ozone cluster with Docker Compose.
-Extract the release tarball, go to the `compose/ozone` directory and start the cluster:
+https://ozone.apache.org/docs/edge/start/startfromdockerhub.html
```bash
-docker-compose up -d --scale datanode=3
+docker compose up -d --scale datanode=3
```
-You can/should find now the HttpFS gateway in docker with the name `ozone_httpfs`.
-HttpFS HTTP web-service API calls are HTTP REST calls that map to an Ozone file system operation. For example, using the `curl` Unix command.
+You can/should find now the HttpFS gateway in docker with the name like `ozone_httpfs`,
+and it can be accessed through `localhost:14000`.
+HttpFS HTTP web-service API calls are HTTP REST calls that map to an Ozone file system operation.
-E.g. in the docker cluster you can execute commands like these:
+Here's some example usage:
-* `curl -i -X PUT "http://httpfs:14000/webhdfs/v1/vol1?op=MKDIRS&user.name=hdfs"` creates a volume called `vol1`.
+### Create a volume
+```bash
+# creates a volume called `volume1`.
+curl -i -X PUT "http://localhost:14000/webhdfs/v1/volume1?op=MKDIRS&user.name=hdfs"
+```
+
+Example Output:
+
+```bash
+HTTP/1.1 200 OK
+Date: Sat, 18 Oct 2025 07:51:21 GMT
+Cache-Control: no-cache
+Expires: Sat, 18 Oct 2025 07:51:21 GMT
+Pragma: no-cache
+Content-Type: application/json
+X-Content-Type-Options: nosniff
+X-XSS-Protection: 1; mode=block
+Set-Cookie: hadoop.auth="u=hdfs&p=hdfs&t=simple-dt&e=1760809881100&s=OCdVOi8eyMguFySkmEJxm5EkRfj6NbAM9agi5Gue1Iw="; Path=/; HttpOnly
+Content-Length: 17
+
+{"boolean":true}
+```
+
+### Create a bucket
+
+```bash
+# creates a bucket called `bucket1`.
+curl -i -X PUT "http://localhost:14000/webhdfs/v1/volume1/bucket1?op=MKDIRS&user.name=hdfs"
+```
+
+Example Output:
+
+```bash
+HTTP/1.1 200 OK
+Date: Sat, 18 Oct 2025 07:52:06 GMT
+Cache-Control: no-cache
+Expires: Sat, 18 Oct 2025 07:52:06 GMT
+Pragma: no-cache
+Content-Type: application/json
+X-Content-Type-Options: nosniff
+X-XSS-Protection: 1; mode=block
+Set-Cookie: hadoop.auth="u=hdfs&p=hdfs&t=simple-dt&e=1760809926682&s=yvOaeaRCVJZ+z+nZQ/rM/Y01pzEmS9Pe2mE9f0b+TWw="; Path=/; HttpOnly
+Content-Length: 17
+
+{"boolean":true}
+```
+
+### Upload a file
-* `$ curl 'http://httpfs-host:14000/webhdfs/v1/user/foo/README.txt?op=OPEN&user.name=foo'` returns the content of the key `/user/foo/README.txt`.
+```bash
+echo "hello" >> ./README.txt
+curl -i -X PUT "http://localhost:14000/webhdfs/v1/volume1/bucket1/user/foo/README.txt?op=CREATE&data=true&user.name=hdfs" -T ./README.txt -H "Content-Type: application/octet-stream"
+```
+Example Output:
+
+```bash
+HTTP/1.1 100 Continue
+
+HTTP/1.1 201 Created
+Date: Sat, 18 Oct 2025 08:33:33 GMT
+Cache-Control: no-cache
+Expires: Sat, 18 Oct 2025 08:33:33 GMT
+Pragma: no-cache
+X-Content-Type-Options: nosniff
+X-XSS-Protection: 1; mode=block
+Set-Cookie: hadoop.auth="u=hdfs&p=hdfs&t=simple-dt&e=1760812413286&s=09t7xKu/p/fjCJiQNL3bvW/Q7mTw28IbeNqDGlslZ6w="; Path=/; HttpOnly
+Location: http://localhost:14000/webhdfs/v1/volume1/bucket1/user/foo/README.txt
+Content-Type: application/json
+Content-Length: 84
+
+{"Location":"http://localhost:14000/webhdfs/v1/volume1/bucket1/user/foo/README.txt"}
+```
+
+### Read the file content
+
+```bash
+# returns the content of the key `/user/foo/README.txt`.
+curl 'http://localhost:14000/webhdfs/v1/volume1/bucket1/user/foo/README.txt?op=OPEN&user.name=foo'
+hello
+```
## Supported operations
@@ -110,10 +188,8 @@ Set ACL | not implemented in Ozone FileSystem API
Get ACL Status | not implemented in Ozone FileSystem API
Check access | not implemented in Ozone FileSystem API
-
-
## Hadoop user and developer documentation about HttpFS
* [HttpFS Server Setup](https://hadoop.apache.org/docs/stable/hadoop-hdfs-httpfs/ServerSetup.html)
-* [Using HTTP Tools](https://hadoop.apache.org/docs/stable/hadoop-hdfs-httpfs/ServerSetup.html)
\ No newline at end of file
+* [Using HTTP Tools](https://hadoop.apache.org/docs/stable/hadoop-hdfs-httpfs/ServerSetup.html)
diff --git a/hadoop-hdds/docs/content/security/SecuringS3.md b/hadoop-hdds/docs/content/security/SecuringS3.md
index 85c064c407fd..561531d2d8bc 100644
--- a/hadoop-hdds/docs/content/security/SecuringS3.md
+++ b/hadoop-hdds/docs/content/security/SecuringS3.md
@@ -37,18 +37,32 @@ The user needs to `kinit` first and once they have authenticated via kerberos
## Obtain Secrets
-* S3 clients can get the secret access id and user secret from OzoneManager.
+S3 clients can get the secret access id and user secret from OzoneManager.
+### Using the command line
+
+For a regular user to get their own secret:
```bash
ozone s3 getsecret
```
-* Or by sending request to /secret S3 REST endpoint.
+An Ozone administrator can get a secret for a specific user by using the `-u` flag:
+```bash
+ozone s3 getsecret -u
+```
+
+### Using the REST API
+A user can get their own secret by making a `PUT` request to the `/secret` endpoint:
```bash
curl -X PUT --negotiate -u : https://localhost:9879/secret
```
+An Ozone administrator can get a secret for a specific user by appending the username to the path:
+```bash
+curl -X PUT --negotiate -u : https://localhost:9879/secret/
+```
+
This command will talk to ozone, validate the user via Kerberos and generate
the AWS credentials. The values will be printed out on the screen. You can
set these values up in your _.aws_ file for automatic access while working
@@ -114,3 +128,112 @@ curl -X DELETE --negotiate -u : -v "http://localhost:9879/secret?username=testus
For a working example of these operations, refer to the [Secret Revoke Robot Test](https://raw.githubusercontent.com/apache/ozone/refs/heads/master/hadoop-ozone/dist/src/main/smoketest/s3/secretrevoke.robot). This test demonstrates both the default secret revocation and the revocation by username.
> **Note:** Ensure your Kerberos authentication is correctly configured, as secret revocation is a privileged operation.
+
+## External S3 Secret Storage with HashiCorp Vault
+
+By default, S3 secrets are stored in the Ozone Manager's RocksDB. For enhanced security, Ozone can be configured to use HashiCorp Vault as an external secret storage backend.
+
+### Configuration
+
+To enable Vault integration, you need to configure the following properties in `ozone-site.xml`:
+
+| Property | Description |
+| -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
+| `ozone.secret.s3.store.provider` | The S3 secret storage provider to use. Set this to `org.apache.hadoop.ozone.s3.remote.vault.VaultS3SecretStorageProvider` to enable Vault. |
+| `ozone.secret.s3.store.remote.vault.address` | The address of the Vault server (e.g., `http://vault:8200`). |
+| `ozone.secret.s3.store.remote.vault.namespace` | The Vault namespace to use. |
+| `ozone.secret.s3.store.remote.vault.enginever` | The version of the Vault secrets engine (e.g., `2`). |
+| `ozone.secret.s3.store.remote.vault.secretpath` | The path where the secrets are stored in Vault. |
+| `ozone.secret.s3.store.remote.vault.auth` | The authentication method to use with Vault. Supported values are `TOKEN` and `APPROLE`. |
+| `ozone.secret.s3.store.remote.vault.auth.token` | The Vault authentication token. Required if `ozone.secret.s3.store.remote.vault.auth` is set to `TOKEN`. |
+| `ozone.secret.s3.store.remote.vault.auth.approle.id` | The AppRole RoleID. Required if `ozone.secret.s3.store.remote.vault.auth` is set to `APPROLE`. |
+| `ozone.secret.s3.store.remote.vault.auth.approle.secret` | The AppRole SecretID. Required if `ozone.secret.s3.store.remote.vault.auth` is set to `APPROLE`. |
+| `ozone.secret.s3.store.remote.vault.auth.approle.path` | The AppRole path. Required if `ozone.secret.s3.store.remote.vault.auth` is set to `APPROLE`. |
+| `ozone.secret.s3.store.remote.vault.trust.store.type` | The type of the trust store (e.g., `JKS`). |
+| `ozone.secret.s3.store.remote.vault.trust.store.path` | The path to the trust store file. |
+| `ozone.secret.s3.store.remote.vault.trust.store.password` | The password for the trust store. |
+| `ozone.secret.s3.store.remote.vault.key.store.type` | The type of the key store (e.g., `JKS`). |
+| `ozone.secret.s3.store.remote.vault.key.store.path` | The path to the key store file. |
+| `ozone.secret.s3.store.remote.vault.key.store.password` | The password for the key store. |
+
+### Example
+
+Here is an example of how to configure Ozone to use Vault for S3 secret storage with token authentication:
+
+```xml
+
+ ozone.secret.s3.store.provider
+ org.apache.hadoop.ozone.s3.remote.vault.VaultS3SecretStorageProvider
+
+
+ ozone.secret.s3.store.remote.vault.address
+ http://localhost:8200
+
+
+ ozone.secret.s3.store.remote.vault.enginever
+ 2
+
+
+ ozone.secret.s3.store.remote.vault.secretpath
+ secret
+
+
+ ozone.secret.s3.store.remote.vault.auth
+ TOKEN
+
+
+ ozone.secret.s3.store.remote.vault.auth.token
+ your-vault-token
+
+```
+
+### Example with SSL
+
+Here is an example of how to configure Ozone to use Vault for S3 secret storage with SSL:
+
+```xml
+
+ ozone.secret.s3.store.provider
+ org.apache.hadoop.ozone.s3.remote.vault.VaultS3SecretStorageProvider
+
+
+ ozone.secret.s3.store.remote.vault.address
+ https://localhost:8200
+
+
+ ozone.secret.s3.store.remote.vault.enginever
+ 2
+
+
+ ozone.secret.s3.store.remote.vault.secretpath
+ secret
+
+
+ ozone.secret.s3.store.remote.vault.auth
+ TOKEN
+
+
+ ozone.secret.s3.store.remote.vault.auth.token
+ your-vault-token
+
+
+ ozone.secret.s3.store.remote.vault.trust.store.path
+ /path/to/truststore.jks
+
+
+ ozone.secret.s3.store.remote.vault.trust.store.password
+ truststore-password
+
+
+ ozone.secret.s3.store.remote.vault.key.store.path
+ /path/to/keystore.jks
+
+
+ ozone.secret.s3.store.remote.vault.key.store.password
+ keystore-password
+
+```
+
+### References
+
+* [HashiCorp Vault Documentation](https://developer.hashicorp.com/vault/docs)
diff --git a/hadoop-hdds/docs/content/tools/Admin.md b/hadoop-hdds/docs/content/tools/Admin.md
index e89331230fbd..c2f6093180a0 100644
--- a/hadoop-hdds/docs/content/tools/Admin.md
+++ b/hadoop-hdds/docs/content/tools/Admin.md
@@ -172,3 +172,49 @@ $ ozone admin om lof --service-id=om-service-test1 --length=3 --prefix=/volumelo
```
Note in JSON output mode, field `contToken` won't show up at all in the result if there are no more entries after the batch (i.e. when `hasMore` is `false`).
+
+
+## Snapshot Defragmentation Trigger
+
+The snapshot defrag command triggers the Snapshot Defragmentation Service to run immediately on a specific Ozone Manager node.
+This command manually initiates the snapshot defragmentation process which compacts snapshot data and removes fragmentation to improve storage efficiency.
+
+This command only works on Ozone Manager HA clusters.
+
+```bash
+$ ozone admin om snapshot defrag --help
+Usage: ozone admin om snapshot defrag [-hV] [--no-wait] [--node-id=]
+ [-id=]
+Triggers the Snapshot Defragmentation Service to run immediately. This command
+manually initiates the snapshot defragmentation process which compacts
+snapshot data and removes fragmentation to improve storage efficiency. This
+command works only on OzoneManager HA cluster.
+ -h, --help Show this help message and exit.
+ --no-wait Do not wait for the defragmentation task to
+ complete. The command will return immediately
+ after triggering the task.
+ --node-id= NodeID of the OM to trigger snapshot
+ defragmentation on.
+ -id, --service-id=
+ Ozone Manager Service ID
+ -V, --version Print version information and exit.
+```
+
+### Example usages
+
+- Trigger snapshot defragmentation on OM node `om3` in service `omservice` and wait for completion:
+
+```bash
+$ ozone admin om snapshot defrag --service-id=omservice --node-id=om3
+Triggering Snapshot Defrag Service ...
+Snapshot defragmentation completed successfully.
+```
+
+- Trigger snapshot defragmentation without waiting for completion:
+
+```bash
+$ ozone admin om snapshot defrag --service-id=omservice --node-id=om3 --no-wait
+Triggering Snapshot Defrag Service ...
+Snapshot defragmentation task has been triggered successfully and is running in the background.
+```
+
diff --git a/hadoop-hdds/docs/pom.xml b/hadoop-hdds/docs/pom.xml
index 8bb357e19744..5215ecd635bf 100644
--- a/hadoop-hdds/docs/pom.xml
+++ b/hadoop-hdds/docs/pom.xml
@@ -17,10 +17,10 @@
org.apache.ozone
hdds
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
hdds-docs
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
jar
Apache Ozone Documentation
Apache Ozone Documentation
diff --git a/hadoop-hdds/erasurecode/pom.xml b/hadoop-hdds/erasurecode/pom.xml
index 53578449df23..c74e7c3f5524 100644
--- a/hadoop-hdds/erasurecode/pom.xml
+++ b/hadoop-hdds/erasurecode/pom.xml
@@ -17,11 +17,11 @@
org.apache.ozone
hdds-hadoop-dependency-client
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
../hadoop-dependency-client
hdds-erasurecode
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
jar
Apache Ozone HDDS Erasurecode
Apache Ozone Distributed Data Store Earsurecode utils
diff --git a/hadoop-hdds/framework/pom.xml b/hadoop-hdds/framework/pom.xml
index 5c368525010a..c30cde6de28f 100644
--- a/hadoop-hdds/framework/pom.xml
+++ b/hadoop-hdds/framework/pom.xml
@@ -17,10 +17,10 @@
org.apache.ozone
hdds
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
hdds-server-framework
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
jar
Apache Ozone HDDS Server Framework
Apache Ozone Distributed Data Store Server Framework
diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/scm/client/ScmClient.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/scm/client/ScmClient.java
index 7677ed58707f..5f91345d98f5 100644
--- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/scm/client/ScmClient.java
+++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/scm/client/ScmClient.java
@@ -29,6 +29,7 @@
import org.apache.hadoop.hdds.protocol.DatanodeDetails;
import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.ContainerDataProto;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos.DeletedBlocksTransactionSummary;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.ContainerBalancerStatusInfoResponseProto;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.DecommissionScmResponseProto;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.StartContainerBalancerResponseProto;
@@ -125,7 +126,7 @@ void deleteContainer(long containerId, Pipeline pipeline, boolean force)
* @throws IOException
*/
ContainerListResult listContainer(long startContainerID,
- int count) throws IOException;
+ int count) throws IOException;
/**
* Lists a range of containers and get their info.
@@ -139,9 +140,9 @@ ContainerListResult listContainer(long startContainerID,
* @throws IOException
*/
ContainerListResult listContainer(long startContainerID, int count,
- HddsProtos.LifeCycleState state,
- HddsProtos.ReplicationType replicationType,
- ReplicationConfig replicationConfig)
+ HddsProtos.LifeCycleState state,
+ HddsProtos.ReplicationType replicationType,
+ ReplicationConfig replicationConfig)
throws IOException;
/**
@@ -180,8 +181,8 @@ ContainerDataProto readContainer(long containerID)
*/
@Deprecated
ContainerWithPipeline createContainer(HddsProtos.ReplicationType type,
- HddsProtos.ReplicationFactor replicationFactor,
- String owner) throws IOException;
+ HddsProtos.ReplicationFactor replicationFactor,
+ String owner) throws IOException;
ContainerWithPipeline createContainer(ReplicationConfig replicationConfig, String owner) throws IOException;
@@ -206,8 +207,8 @@ ContainerWithPipeline createContainer(HddsProtos.ReplicationType type,
* @throws IOException
*/
List queryNode(HddsProtos.NodeOperationalState opState,
- HddsProtos.NodeState nodeState, HddsProtos.QueryScope queryScope,
- String poolName) throws IOException;
+ HddsProtos.NodeState nodeState, HddsProtos.QueryScope queryScope,
+ String poolName) throws IOException;
/**
* Returns a node with the given UUID.
@@ -256,7 +257,7 @@ List recommissionNodes(List hosts)
* @throws IOException
*/
List startMaintenanceNodes(List hosts,
- int endHours, boolean force) throws IOException;
+ int endHours, boolean force) throws IOException;
/**
* Creates a specified replication pipeline.
@@ -266,7 +267,7 @@ List startMaintenanceNodes(List hosts,
* @throws IOException
*/
Pipeline createReplicationPipeline(HddsProtos.ReplicationType type,
- HddsProtos.ReplicationFactor factor, HddsProtos.NodePool nodePool)
+ HddsProtos.ReplicationFactor factor, HddsProtos.NodePool nodePool)
throws IOException;
/**
@@ -411,6 +412,12 @@ StartContainerBalancerResponseProto startContainerBalancer(
*/
void transferLeadership(String newLeaderId) throws IOException;
+ /**
+ * Get deleted block summary.
+ * @throws IOException
+ */
+ DeletedBlocksTransactionSummary getDeletedBlockSummary() throws IOException;
+
/**
* Get usage information of datanode by address or uuid.
*
diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/scm/protocol/StorageContainerLocationProtocol.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/scm/protocol/StorageContainerLocationProtocol.java
index 56411453fc8e..92ddfa7eb8dc 100644
--- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/scm/protocol/StorageContainerLocationProtocol.java
+++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/scm/protocol/StorageContainerLocationProtocol.java
@@ -17,6 +17,7 @@
package org.apache.hadoop.hdds.scm.protocol;
+import jakarta.annotation.Nullable;
import java.io.Closeable;
import java.io.IOException;
import java.util.Collections;
@@ -31,6 +32,7 @@
import org.apache.hadoop.hdds.protocol.DatanodeDetails;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos.DeletedBlocksTransactionInfo;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos.DeletedBlocksTransactionSummary;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.ContainerBalancerStatusInfoResponseProto;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.DecommissionScmResponseProto;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.StartContainerBalancerResponseProto;
@@ -361,6 +363,14 @@ List getFailedDeletedBlockTxn(int count,
@Deprecated
int resetDeletedBlockRetryCount(List txIDs) throws IOException;
+
+ /**
+ * Get deleted block summary.
+ * @throws IOException
+ */
+ @Nullable
+ DeletedBlocksTransactionSummary getDeletedBlockSummary() throws IOException;
+
/**
* Check if SCM is in safe mode.
*
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 2a85e6e40071..502d9a4fe98f 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
@@ -24,6 +24,7 @@
import com.google.common.base.Preconditions;
import com.google.protobuf.RpcController;
import com.google.protobuf.ServiceException;
+import jakarta.annotation.Nullable;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
@@ -42,6 +43,7 @@
import org.apache.hadoop.hdds.protocol.DatanodeDetails;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos.DeletedBlocksTransactionInfo;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos.DeletedBlocksTransactionSummary;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos.GetScmInfoResponseProto;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos.ReplicationFactor;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos.TransferLeadershipRequestProto;
@@ -78,6 +80,8 @@
import org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetContainerWithPipelineRequestProto;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetContainersOnDecomNodeRequestProto;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetContainersOnDecomNodeResponseProto;
+import org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetDeletedBlocksTxnSummaryRequestProto;
+import org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetDeletedBlocksTxnSummaryResponseProto;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetExistContainerWithPipelinesInBatchRequestProto;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetMetricsRequestProto;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetMetricsResponseProto;
@@ -801,6 +805,18 @@ public int resetDeletedBlockRetryCount(List txIDs)
return 0;
}
+ @Nullable
+ @Override
+ public DeletedBlocksTransactionSummary getDeletedBlockSummary() throws IOException {
+ GetDeletedBlocksTxnSummaryRequestProto request =
+ GetDeletedBlocksTxnSummaryRequestProto.newBuilder().build();
+ ScmContainerLocationResponse scmContainerLocationResponse = submitRequest(Type.GetDeletedBlocksTransactionSummary,
+ builder -> builder.setGetDeletedBlocksTxnSummaryRequest(request));
+ GetDeletedBlocksTxnSummaryResponseProto response =
+ scmContainerLocationResponse.getGetDeletedBlocksTxnSummaryResponse();
+ return response.hasSummary() ? response.getSummary() : null;
+ }
+
/**
* Check if SCM is in safe mode.
*
diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/db/RocksDatabase.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/db/RocksDatabase.java
index 9eeb69ece3d8..b93626060c80 100644
--- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/db/RocksDatabase.java
+++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/db/RocksDatabase.java
@@ -159,8 +159,9 @@ static RocksDatabase open(File dbFile, ManagedDBOptions dbOptions,
List descriptors = null;
ManagedRocksDB db = null;
final Map columnFamilies = new HashMap<>();
+ List extra = null;
try {
- final List extra = getExtraColumnFamilies(dbFile, families);
+ extra = getExtraColumnFamilies(dbFile, families);
descriptors = Stream.concat(families.stream(), extra.stream())
.map(TableConfig::getDescriptor)
.collect(Collectors.toList());
@@ -178,6 +179,10 @@ static RocksDatabase open(File dbFile, ManagedDBOptions dbOptions,
} catch (RocksDBException e) {
close(columnFamilies, db, descriptors, writeOptions, dbOptions);
throw toRocksDatabaseException(RocksDatabase.class, "open " + dbFile, e);
+ } finally {
+ if (extra != null) {
+ extra.forEach(TableConfig::close);
+ }
}
}
diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/ozone/common/BlockGroup.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/ozone/common/BlockGroup.java
index 5e8b0e1724be..4ef34193aa2b 100644
--- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/ozone/common/BlockGroup.java
+++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/ozone/common/BlockGroup.java
@@ -20,7 +20,6 @@
import java.util.ArrayList;
import java.util.List;
import org.apache.hadoop.hdds.client.BlockID;
-import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
import org.apache.hadoop.hdds.protocol.proto.ScmBlockLocationProtocolProtos.KeyBlocks;
/**
@@ -29,15 +28,16 @@
public final class BlockGroup {
private String groupID;
- private List blockIDs;
+ private List deletedBlocks;
+ public static final long SIZE_NOT_AVAILABLE = -1;
- private BlockGroup(String groupID, List blockIDs) {
+ private BlockGroup(String groupID, List deletedBlocks) {
this.groupID = groupID;
- this.blockIDs = blockIDs;
+ this.deletedBlocks = deletedBlocks;
}
- public List getBlockIDList() {
- return blockIDs;
+ public List getDeletedBlocks() {
+ return deletedBlocks;
}
public String getGroupID() {
@@ -46,8 +46,10 @@ public String getGroupID() {
public KeyBlocks getProto() {
KeyBlocks.Builder kbb = KeyBlocks.newBuilder();
- for (BlockID block : blockIDs) {
- kbb.addBlocks(block.getProtobuf());
+ for (DeletedBlock deletedBlock : deletedBlocks) {
+ kbb.addBlocks(deletedBlock.getBlockID().getProtobuf());
+ kbb.addSize(deletedBlock.getSize());
+ kbb.addReplicatedSize(deletedBlock.getReplicatedSize());
}
return kbb.setKey(groupID).build();
}
@@ -58,13 +60,23 @@ public KeyBlocks getProto() {
* @return a group of blocks.
*/
public static BlockGroup getFromProto(KeyBlocks proto) {
- List blockIDs = new ArrayList<>();
- for (HddsProtos.BlockID block : proto.getBlocksList()) {
- blockIDs.add(new BlockID(block.getContainerBlockID().getContainerID(),
- block.getContainerBlockID().getLocalID()));
+ List deletedBlocksList = new ArrayList<>();
+ for (int i = 0; i < proto.getBlocksCount(); i++) {
+ long repSize = SIZE_NOT_AVAILABLE;
+ long size = SIZE_NOT_AVAILABLE;
+ if (proto.getSizeCount() > i) {
+ size = proto.getSize(i);
+ }
+ if (proto.getReplicatedSizeCount() > i) {
+ repSize = proto.getReplicatedSize(i);
+ }
+ BlockID block = new BlockID(proto.getBlocks(i).getContainerBlockID().getContainerID(),
+ proto.getBlocks(i).getContainerBlockID().getLocalID());
+ deletedBlocksList.add(new DeletedBlock(block, size, repSize));
}
return BlockGroup.newBuilder().setKeyName(proto.getKey())
- .addAllBlockIDs(blockIDs).build();
+ .addAllDeletedBlocks(deletedBlocksList)
+ .build();
}
public static Builder newBuilder() {
@@ -75,7 +87,7 @@ public static Builder newBuilder() {
public String toString() {
return "BlockGroup[" +
"groupID='" + groupID + '\'' +
- ", blockIDs=" + blockIDs +
+ ", deletedBlocks=" + deletedBlocks +
']';
}
@@ -85,21 +97,20 @@ public String toString() {
public static class Builder {
private String groupID;
- private List blockIDs;
+ private List deletedBlocks;
public Builder setKeyName(String blockGroupID) {
this.groupID = blockGroupID;
return this;
}
- public Builder addAllBlockIDs(List keyBlocks) {
- this.blockIDs = keyBlocks;
+ public Builder addAllDeletedBlocks(List deletedBlockList) {
+ this.deletedBlocks = deletedBlockList;
return this;
}
public BlockGroup build() {
- return new BlockGroup(groupID, blockIDs);
+ return new BlockGroup(groupID, deletedBlocks);
}
}
-
}
diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/ozone/common/DeletedBlock.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/ozone/common/DeletedBlock.java
new file mode 100644
index 000000000000..b611541578ea
--- /dev/null
+++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/ozone/common/DeletedBlock.java
@@ -0,0 +1,58 @@
+/*
+ * 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.ozone.common;
+
+import org.apache.hadoop.hdds.client.BlockID;
+
+/**
+ * DeletedBlock of Ozone (BlockID + usedBytes).
+ */
+public class DeletedBlock {
+
+ private BlockID blockID;
+ private long size;
+ private long replicatedSize;
+
+ public DeletedBlock(BlockID blockID, long size, long replicatedSize) {
+ this.blockID = blockID;
+ this.size = size;
+ this.replicatedSize = replicatedSize;
+ }
+
+ public BlockID getBlockID() {
+ return this.blockID;
+ }
+
+ public long getSize() {
+ return this.size;
+ }
+
+ public long getReplicatedSize() {
+ return this.replicatedSize;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(64);
+ sb.append(" localID: ").append(blockID.getContainerBlockID().getLocalID());
+ sb.append(" containerID: ").append(blockID.getContainerBlockID().getContainerID());
+ sb.append(" size: ").append(size);
+ sb.append(" replicatedSize: ").append(replicatedSize);
+ return sb.toString();
+ }
+}
diff --git a/hadoop-hdds/hadoop-dependency-client/pom.xml b/hadoop-hdds/hadoop-dependency-client/pom.xml
index 747518f7960e..980d02531ad1 100644
--- a/hadoop-hdds/hadoop-dependency-client/pom.xml
+++ b/hadoop-hdds/hadoop-dependency-client/pom.xml
@@ -17,10 +17,10 @@
org.apache.ozone
hdds
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
hdds-hadoop-dependency-client
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
pom
Apache Ozone HDDS Hadoop Client dependencies
Apache Ozone Distributed Data Store Hadoop client dependencies
diff --git a/hadoop-hdds/interface-admin/pom.xml b/hadoop-hdds/interface-admin/pom.xml
index 0d0dedb35c5b..e6887b955da4 100644
--- a/hadoop-hdds/interface-admin/pom.xml
+++ b/hadoop-hdds/interface-admin/pom.xml
@@ -17,10 +17,10 @@
org.apache.ozone
hdds
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
hdds-interface-admin
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
jar
Apache Ozone HDDS Admin Interface
Apache Ozone Distributed Data Store Admin interface
diff --git a/hadoop-hdds/interface-admin/src/main/proto/ScmAdminProtocol.proto b/hadoop-hdds/interface-admin/src/main/proto/ScmAdminProtocol.proto
index 3dfdea4c7324..f80a50a3be97 100644
--- a/hadoop-hdds/interface-admin/src/main/proto/ScmAdminProtocol.proto
+++ b/hadoop-hdds/interface-admin/src/main/proto/ScmAdminProtocol.proto
@@ -86,6 +86,7 @@ message ScmContainerLocationRequest {
optional GetMetricsRequestProto getMetricsRequest = 47;
optional ContainerBalancerStatusInfoRequestProto containerBalancerStatusInfoRequest = 48;
optional ReconcileContainerRequestProto reconcileContainerRequest = 49;
+ optional GetDeletedBlocksTxnSummaryRequestProto getDeletedBlocksTxnSummaryRequest = 50;
}
message ScmContainerLocationResponse {
@@ -143,6 +144,7 @@ message ScmContainerLocationResponse {
optional GetMetricsResponseProto getMetricsResponse = 47;
optional ContainerBalancerStatusInfoResponseProto containerBalancerStatusInfoResponse = 48;
optional ReconcileContainerResponseProto reconcileContainerResponse = 49;
+ optional GetDeletedBlocksTxnSummaryResponseProto getDeletedBlocksTxnSummaryResponse = 50;
enum Status {
OK = 1;
@@ -199,6 +201,7 @@ enum Type {
GetMetrics = 43;
GetContainerBalancerStatusInfo = 44;
ReconcileContainer = 45;
+ GetDeletedBlocksTransactionSummary = 46;
}
/**
@@ -545,6 +548,13 @@ message ResetDeletedBlockRetryCountResponseProto {
required int32 resetCount = 1;
}
+message GetDeletedBlocksTxnSummaryRequestProto {
+}
+
+message GetDeletedBlocksTxnSummaryResponseProto {
+ optional DeletedBlocksTransactionSummary summary = 1;
+}
+
message FinalizeScmUpgradeRequestProto {
required string upgradeClientId = 1;
}
diff --git a/hadoop-hdds/interface-client/pom.xml b/hadoop-hdds/interface-client/pom.xml
index 18d02e29225a..d6e7c00fe0ed 100644
--- a/hadoop-hdds/interface-client/pom.xml
+++ b/hadoop-hdds/interface-client/pom.xml
@@ -17,10 +17,10 @@
org.apache.ozone
hdds
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
hdds-interface-client
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
jar
Apache Ozone HDDS Client Interface
Apache Ozone Distributed Data Store Client interface
diff --git a/hadoop-hdds/interface-client/src/main/proto/hdds.proto b/hadoop-hdds/interface-client/src/main/proto/hdds.proto
index 504f1a7ebdf1..284124696d6b 100644
--- a/hadoop-hdds/interface-client/src/main/proto/hdds.proto
+++ b/hadoop-hdds/interface-client/src/main/proto/hdds.proto
@@ -514,6 +514,14 @@ message DeletedBlocksTransactionInfo {
optional int32 count = 4;
}
+message DeletedBlocksTransactionSummary {
+ optional int64 firstTxID = 1; // starting ID of transaction to be counted into summary
+ optional uint64 totalTransactionCount = 2;
+ optional uint64 totalBlockCount = 3;
+ optional uint64 totalBlockSize = 4;
+ optional uint64 totalBlockReplicatedSize = 5;
+}
+
message CompactionFileInfoProto {
optional string fileName = 1;
optional string startKey = 2;
diff --git a/hadoop-hdds/interface-server/pom.xml b/hadoop-hdds/interface-server/pom.xml
index 90c462658d8e..d6a6353d9b2f 100644
--- a/hadoop-hdds/interface-server/pom.xml
+++ b/hadoop-hdds/interface-server/pom.xml
@@ -17,10 +17,10 @@
org.apache.ozone
hdds
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
hdds-interface-server
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
jar
Apache Ozone HDDS Server Interface
Apache Ozone Distributed Data Store Server interface
diff --git a/hadoop-hdds/interface-server/src/main/proto/ScmServerDatanodeHeartbeatProtocol.proto b/hadoop-hdds/interface-server/src/main/proto/ScmServerDatanodeHeartbeatProtocol.proto
index e48ed4d1c595..e4b3a2de56fd 100644
--- a/hadoop-hdds/interface-server/src/main/proto/ScmServerDatanodeHeartbeatProtocol.proto
+++ b/hadoop-hdds/interface-server/src/main/proto/ScmServerDatanodeHeartbeatProtocol.proto
@@ -366,9 +366,9 @@ message DeletedBlocksTransaction {
required int64 txID = 1;
required int64 containerID = 2;
repeated int64 localID = 3;
- // the retry time of sending deleting command to datanode.
- // We don't have to store the retry count in DB.
- optional int32 count = 4 [deprecated=true];
+ optional int32 count = 4;
+ optional uint64 totalBlockSize = 5;
+ optional uint64 totalBlockReplicatedSize = 6;
}
// ACK message datanode sent to SCM, contains the result of
diff --git a/hadoop-hdds/interface-server/src/main/proto/ScmServerProtocol.proto b/hadoop-hdds/interface-server/src/main/proto/ScmServerProtocol.proto
index fc24d2562f9c..4c794fe7dc18 100644
--- a/hadoop-hdds/interface-server/src/main/proto/ScmServerProtocol.proto
+++ b/hadoop-hdds/interface-server/src/main/proto/ScmServerProtocol.proto
@@ -181,6 +181,8 @@ message DeleteScmKeyBlocksRequestProto {
message KeyBlocks {
required string key = 1;
repeated BlockID blocks = 2;
+ repeated uint64 size = 3;
+ repeated uint64 replicatedSize = 4;
}
/**
diff --git a/hadoop-hdds/managed-rocksdb/pom.xml b/hadoop-hdds/managed-rocksdb/pom.xml
index c1c4685df40f..5e6976500f96 100644
--- a/hadoop-hdds/managed-rocksdb/pom.xml
+++ b/hadoop-hdds/managed-rocksdb/pom.xml
@@ -17,10 +17,10 @@
org.apache.ozone
hdds
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
hdds-managed-rocksdb
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
jar
Apache Ozone HDDS Managed RocksDB
Apache Ozone Managed RocksDB library
diff --git a/hadoop-hdds/pom.xml b/hadoop-hdds/pom.xml
index 220d1fe40917..ba60486c1463 100644
--- a/hadoop-hdds/pom.xml
+++ b/hadoop-hdds/pom.xml
@@ -17,11 +17,11 @@
org.apache.ozone
ozone-main
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
hdds
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
pom
Apache Ozone HDDS
Apache Ozone Distributed Data Store Project
diff --git a/hadoop-hdds/rocks-native/pom.xml b/hadoop-hdds/rocks-native/pom.xml
index 0c7e8fa7e2da..74fdb749d252 100644
--- a/hadoop-hdds/rocks-native/pom.xml
+++ b/hadoop-hdds/rocks-native/pom.xml
@@ -17,7 +17,7 @@
org.apache.ozone
hdds
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
hdds-rocks-native
Apache Ozone HDDS RocksDB Tools
diff --git a/hadoop-hdds/rocksdb-checkpoint-differ/pom.xml b/hadoop-hdds/rocksdb-checkpoint-differ/pom.xml
index 345be1b9fa82..e991b8702990 100644
--- a/hadoop-hdds/rocksdb-checkpoint-differ/pom.xml
+++ b/hadoop-hdds/rocksdb-checkpoint-differ/pom.xml
@@ -17,11 +17,11 @@
org.apache.ozone
hdds
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
rocksdb-checkpoint-differ
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
jar
Apache Ozone Checkpoint Differ for RocksDB
Apache Ozone Checkpoint Differ for RocksDB
diff --git a/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/compaction/log/CompactionFileInfo.java b/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/compaction/log/CompactionFileInfo.java
index e44c2e8522e1..535bf115ea8e 100644
--- a/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/compaction/log/CompactionFileInfo.java
+++ b/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/compaction/log/CompactionFileInfo.java
@@ -22,6 +22,7 @@
import java.util.Objects;
import org.apache.hadoop.hdds.StringUtils;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.ozone.rocksdb.util.SstFileInfo;
import org.rocksdb.LiveFileMetaData;
/**
diff --git a/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/compaction/log/SstFileInfo.java b/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdb/util/SstFileInfo.java
similarity index 93%
rename from hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/compaction/log/SstFileInfo.java
rename to hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdb/util/SstFileInfo.java
index b1887ec3d1e0..50f8c4c54d06 100644
--- a/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/compaction/log/SstFileInfo.java
+++ b/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdb/util/SstFileInfo.java
@@ -15,7 +15,9 @@
* limitations under the License.
*/
-package org.apache.ozone.compaction.log;
+package org.apache.ozone.rocksdb.util;
+
+import static org.apache.commons.io.FilenameUtils.getBaseName;
import java.util.Objects;
import org.apache.hadoop.hdds.StringUtils;
@@ -39,7 +41,7 @@ public SstFileInfo(String fileName, String startRange, String endRange, String c
}
public SstFileInfo(LiveFileMetaData fileMetaData) {
- this(fileMetaData.fileName(), StringUtils.bytes2String(fileMetaData.smallestKey()),
+ this(getBaseName(fileMetaData.fileName()), StringUtils.bytes2String(fileMetaData.smallestKey()),
StringUtils.bytes2String(fileMetaData.largestKey()),
StringUtils.bytes2String(fileMetaData.columnFamilyName()));
}
diff --git a/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/CompactionNode.java b/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/CompactionNode.java
index 7dddb6a3b77b..969c0e0b00ed 100644
--- a/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/CompactionNode.java
+++ b/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/CompactionNode.java
@@ -17,20 +17,17 @@
package org.apache.ozone.rocksdiff;
+import java.util.Objects;
import org.apache.ozone.compaction.log.CompactionFileInfo;
+import org.apache.ozone.rocksdb.util.SstFileInfo;
/**
* Node in the compaction DAG that represents an SST file.
*/
-public class CompactionNode {
- // Name of the SST file
- private final String fileName;
+public class CompactionNode extends SstFileInfo {
private final long snapshotGeneration;
private final long totalNumberOfKeys;
private long cumulativeKeysReverseTraversal;
- private final String startKey;
- private final String endKey;
- private final String columnFamily;
/**
* CompactionNode constructor.
@@ -38,13 +35,10 @@ public class CompactionNode {
* @param seqNum Snapshot generation (sequence number)
*/
public CompactionNode(String file, long seqNum, String startKey, String endKey, String columnFamily) {
- fileName = file;
+ super(file, startKey, endKey, columnFamily);
totalNumberOfKeys = 0L;
snapshotGeneration = seqNum;
cumulativeKeysReverseTraversal = 0L;
- this.startKey = startKey;
- this.endKey = endKey;
- this.columnFamily = columnFamily;
}
public CompactionNode(CompactionFileInfo compactionFileInfo) {
@@ -54,11 +48,7 @@ public CompactionNode(CompactionFileInfo compactionFileInfo) {
@Override
public String toString() {
- return String.format("Node{%s}", fileName);
- }
-
- public String getFileName() {
- return fileName;
+ return String.format("Node{%s}", getFileName());
}
public long getSnapshotGeneration() {
@@ -73,18 +63,6 @@ public long getCumulativeKeysReverseTraversal() {
return cumulativeKeysReverseTraversal;
}
- public String getStartKey() {
- return startKey;
- }
-
- public String getEndKey() {
- return endKey;
- }
-
- public String getColumnFamily() {
- return columnFamily;
- }
-
public void setCumulativeKeysReverseTraversal(
long cumulativeKeysReverseTraversal) {
this.cumulativeKeysReverseTraversal = cumulativeKeysReverseTraversal;
@@ -93,4 +71,16 @@ public void setCumulativeKeysReverseTraversal(
public void addCumulativeKeysReverseTraversal(long diff) {
this.cumulativeKeysReverseTraversal += diff;
}
+
+ // Not changing previous behaviour.
+ @Override
+ public final boolean equals(Object o) {
+ return this == o;
+ }
+
+ // Having hashcode only on the basis of the filename.
+ @Override
+ public int hashCode() {
+ return Objects.hash(getFileName());
+ }
}
diff --git a/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/RocksDiffUtils.java b/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/RocksDiffUtils.java
index 5bc14b1d9497..86577147b62b 100644
--- a/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/RocksDiffUtils.java
+++ b/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/RocksDiffUtils.java
@@ -30,6 +30,7 @@
import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
import org.apache.ozone.compaction.log.CompactionFileInfo;
+import org.apache.ozone.rocksdb.util.SstFileInfo;
import org.rocksdb.LiveFileMetaData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -106,7 +107,7 @@ public static void filterRelevantSstFiles(Set inputFiles,
}
@VisibleForTesting
- static boolean shouldSkipNode(CompactionNode node,
+ static boolean shouldSkipNode(SstFileInfo node,
Map columnFamilyToPrefixMap) {
// This is for backward compatibility. Before the compaction log table
// migration, startKey, endKey and columnFamily information is not persisted
diff --git a/hadoop-hdds/rocksdb-checkpoint-differ/src/test/java/org/apache/ozone/compaction/log/TestSstFileInfo.java b/hadoop-hdds/rocksdb-checkpoint-differ/src/test/java/org/apache/ozone/compaction/log/TestSstFileInfo.java
new file mode 100644
index 000000000000..660e3e75a1d7
--- /dev/null
+++ b/hadoop-hdds/rocksdb-checkpoint-differ/src/test/java/org/apache/ozone/compaction/log/TestSstFileInfo.java
@@ -0,0 +1,47 @@
+/*
+ * 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.ozone.compaction.log;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.apache.hadoop.hdds.StringUtils;
+import org.apache.ozone.rocksdb.util.SstFileInfo;
+import org.junit.jupiter.api.Test;
+import org.rocksdb.LiveFileMetaData;
+
+/**
+ * Test class for Base SstFileInfo class.
+ */
+public class TestSstFileInfo {
+
+ @Test
+ public void testSstFileInfo() {
+ String smallestKey = "/smallestKey/1";
+ String largestKey = "/largestKey/2";
+ String columnFamily = "columnFamily/123";
+ LiveFileMetaData lfm = mock(LiveFileMetaData.class);
+ when(lfm.fileName()).thenReturn("/1.sst");
+ when(lfm.columnFamilyName()).thenReturn(StringUtils.string2Bytes(columnFamily));
+ when(lfm.smallestKey()).thenReturn(StringUtils.string2Bytes(smallestKey));
+ when(lfm.largestKey()).thenReturn(StringUtils.string2Bytes(largestKey));
+ SstFileInfo expectedSstFileInfo = new SstFileInfo("1", smallestKey, largestKey, columnFamily);
+ assertEquals(expectedSstFileInfo, new SstFileInfo(lfm));
+ }
+}
diff --git a/hadoop-hdds/server-scm/pom.xml b/hadoop-hdds/server-scm/pom.xml
index 811d439f7dd8..68c17ecdf3ab 100644
--- a/hadoop-hdds/server-scm/pom.xml
+++ b/hadoop-hdds/server-scm/pom.xml
@@ -17,10 +17,10 @@
org.apache.ozone
hdds
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
hdds-server-scm
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
jar
Apache Ozone HDDS SCM Server
Apache Ozone Distributed Data Store Storage Container Manager Server
diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/BlockManagerImpl.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/BlockManagerImpl.java
index 9b46968424cc..6b0136abf664 100644
--- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/BlockManagerImpl.java
+++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/BlockManagerImpl.java
@@ -46,6 +46,7 @@
import org.apache.hadoop.hdds.scm.server.StorageContainerManager;
import org.apache.hadoop.metrics2.util.MBeans;
import org.apache.hadoop.ozone.common.BlockGroup;
+import org.apache.hadoop.ozone.common.DeletedBlock;
import org.apache.hadoop.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -92,13 +93,13 @@ public BlockManagerImpl(final ConfigurationSource conf,
this.writableContainerFactory = scm.getWritableContainerFactory();
mxBean = MBeans.register("BlockManager", "BlockManagerImpl", this);
- metrics = ScmBlockDeletingServiceMetrics.create();
+ metrics = ScmBlockDeletingServiceMetrics.create(this);
// SCM block deleting transaction log and deleting service.
deletedBlockLog = new DeletedBlockLogImpl(conf,
scm,
scm.getContainerManager(),
- scm.getScmHAManager().getDBTransactionBuffer(),
+ scm.getScmHAManager().asSCMHADBTransactionBuffer(),
metrics);
@@ -219,21 +220,20 @@ public void deleteBlocks(List keyBlocksInfoList)
throw new SCMException("SafeModePrecheck failed for deleteBlocks",
SCMException.ResultCodes.SAFE_MODE_EXCEPTION);
}
- Map> containerBlocks = new HashMap<>();
- // TODO: track the block size info so that we can reclaim the container
- // TODO: used space when the block is deleted.
+ Map> containerBlocks = new HashMap<>();
for (BlockGroup bg : keyBlocksInfoList) {
if (LOG.isDebugEnabled()) {
LOG.debug("Deleting blocks {}",
- StringUtils.join(",", bg.getBlockIDList()));
+ StringUtils.join(",", bg.getDeletedBlocks()));
}
- for (BlockID block : bg.getBlockIDList()) {
+ for (DeletedBlock deletedBlock : bg.getDeletedBlocks()) {
+ BlockID block = deletedBlock.getBlockID();
long containerID = block.getContainerID();
if (containerBlocks.containsKey(containerID)) {
- containerBlocks.get(containerID).add(block.getLocalID());
+ containerBlocks.get(containerID).add(deletedBlock);
} else {
- List item = new ArrayList<>();
- item.add(block.getLocalID());
+ List item = new ArrayList<>();
+ item.add(deletedBlock);
containerBlocks.put(containerID, item);
}
}
diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLog.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLog.java
index b1283ef773c9..63ab44de346a 100644
--- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLog.java
+++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLog.java
@@ -17,6 +17,8 @@
package org.apache.hadoop.hdds.scm.block;
+import com.google.protobuf.ByteString;
+import jakarta.annotation.Nullable;
import java.io.Closeable;
import java.io.IOException;
import java.util.List;
@@ -24,8 +26,10 @@
import java.util.Set;
import org.apache.hadoop.hdds.protocol.DatanodeDetails;
import org.apache.hadoop.hdds.protocol.DatanodeID;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos.DeletedBlocksTransactionSummary;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.DeletedBlocksTransaction;
import org.apache.hadoop.hdds.utils.db.Table;
+import org.apache.hadoop.ozone.common.DeletedBlock;
import org.apache.hadoop.ozone.protocol.commands.SCMCommand;
/**
@@ -100,7 +104,7 @@ void recordTransactionCreated(
* @param containerBlocksMap a map of containerBlocks.
* @throws IOException
*/
- void addTransactions(Map> containerBlocksMap)
+ void addTransactions(Map> containerBlocksMap)
throws IOException;
/**
@@ -115,8 +119,13 @@ void addTransactions(Map> containerBlocksMap)
/**
* Reinitialize the delete log from the db.
* @param deletedBlocksTXTable delete transaction table
+ * @param statefulConfigTable stateful service config table
*/
- void reinitialize(Table deletedBlocksTXTable);
+ void reinitialize(Table deletedBlocksTXTable,
+ Table statefulConfigTable) throws IOException;
int getTransactionToDNsCommitMapSize();
+
+ @Nullable
+ DeletedBlocksTransactionSummary getTransactionSummary();
}
diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLogImpl.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLogImpl.java
index 9418a8be024d..21fdd9cd0f49 100644
--- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLogImpl.java
+++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLogImpl.java
@@ -23,6 +23,7 @@
import static org.apache.hadoop.hdds.scm.ha.SequenceIdGenerator.DEL_TXN_ID;
import com.google.common.annotations.VisibleForTesting;
+import com.google.protobuf.ByteString;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
@@ -36,6 +37,7 @@
import org.apache.hadoop.hdds.conf.StorageUnit;
import org.apache.hadoop.hdds.protocol.DatanodeDetails;
import org.apache.hadoop.hdds.protocol.DatanodeID;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos.DeletedBlocksTransactionSummary;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.CommandStatus;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.ContainerBlocksDeletionACKProto;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.DeletedBlocksTransaction;
@@ -49,12 +51,15 @@
import org.apache.hadoop.hdds.scm.container.replication.ContainerHealthResult;
import org.apache.hadoop.hdds.scm.container.replication.ReplicationManager;
import org.apache.hadoop.hdds.scm.ha.SCMContext;
+import org.apache.hadoop.hdds.scm.ha.SCMHADBTransactionBuffer;
import org.apache.hadoop.hdds.scm.ha.SequenceIdGenerator;
-import org.apache.hadoop.hdds.scm.metadata.DBTransactionBuffer;
import org.apache.hadoop.hdds.scm.server.StorageContainerManager;
import org.apache.hadoop.hdds.server.events.EventHandler;
import org.apache.hadoop.hdds.server.events.EventPublisher;
+import org.apache.hadoop.hdds.upgrade.HDDSLayoutFeature;
import org.apache.hadoop.hdds.utils.db.Table;
+import org.apache.hadoop.ozone.common.DeletedBlock;
+import org.apache.hadoop.ozone.container.upgrade.VersionedDatanodeFeatures;
import org.apache.hadoop.ozone.protocol.commands.SCMCommand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -83,8 +88,7 @@ public class DeletedBlockLogImpl
private final SCMContext scmContext;
private final SequenceIdGenerator sequenceIdGen;
private final ScmBlockDeletingServiceMetrics metrics;
- private final SCMDeletedBlockTransactionStatusManager
- transactionStatusManager;
+ private SCMDeletedBlockTransactionStatusManager transactionStatusManager;
private long scmCommandTimeoutMs = Duration.ofSeconds(300).toMillis();
private long lastProcessedTransactionId = -1;
@@ -94,8 +98,8 @@ public class DeletedBlockLogImpl
public DeletedBlockLogImpl(ConfigurationSource conf,
StorageContainerManager scm,
ContainerManager containerManager,
- DBTransactionBuffer dbTxBuffer,
- ScmBlockDeletingServiceMetrics metrics) {
+ SCMHADBTransactionBuffer dbTxBuffer,
+ ScmBlockDeletingServiceMetrics metrics) throws IOException {
this.containerManager = containerManager;
this.lock = new ReentrantLock();
@@ -106,12 +110,14 @@ public DeletedBlockLogImpl(ConfigurationSource conf,
.setContainerManager(containerManager)
.setRatisServer(scm.getScmHAManager().getRatisServer())
.setSCMDBTransactionBuffer(dbTxBuffer)
+ .setStatefulConfigTable(scm.getScmMetadataStore().getStatefulServiceConfigTable())
.build();
this.scmContext = scm.getScmContext();
this.sequenceIdGen = scm.getSequenceIdGen();
this.metrics = metrics;
this.transactionStatusManager =
new SCMDeletedBlockTransactionStatusManager(deletedBlockLogStateManager,
+ scm.getScmMetadataStore().getStatefulServiceConfigTable(),
containerManager, metrics, scmCommandTimeoutMs);
int limit = (int) conf.getStorageSize(
ScmConfigKeys.OZONE_SCM_HA_RAFT_LOG_APPENDER_QUEUE_BYTE_LIMIT,
@@ -125,7 +131,7 @@ public DeletedBlockLogImpl(ConfigurationSource conf,
}
@VisibleForTesting
- void setDeletedBlockLogStateManager(DeletedBlockLogStateManager manager) {
+ public void setDeletedBlockLogStateManager(DeletedBlockLogStateManager manager) {
this.deletedBlockLogStateManager = manager;
}
@@ -133,6 +139,10 @@ void setDeletedBlockLogStateManager(DeletedBlockLogStateManager manager) {
void setDeleteBlocksFactorPerDatanode(int deleteBlocksFactorPerDatanode) {
this.deletionFactorPerDatanode = deleteBlocksFactorPerDatanode;
}
+
+ public DeletedBlockLogStateManager getDeletedBlockLogStateManager() {
+ return deletedBlockLogStateManager;
+ }
/**
* {@inheritDoc}
@@ -147,13 +157,23 @@ public void incrementCount(List txIDs)
}
private DeletedBlocksTransaction constructNewTransaction(
- long txID, long containerID, List blocks) {
- return DeletedBlocksTransaction.newBuilder()
+ long txID, long containerID, List blocks) {
+ List localIdList = blocks.stream().map(b -> b.getBlockID().getLocalID()).collect(Collectors.toList());
+ DeletedBlocksTransaction.Builder builder = DeletedBlocksTransaction.newBuilder()
.setTxID(txID)
.setContainerID(containerID)
- .addAllLocalID(blocks)
- .setCount(0)
- .build();
+ .addAllLocalID(localIdList)
+ .setCount(0);
+
+ if (VersionedDatanodeFeatures.isFinalized(HDDSLayoutFeature.STORAGE_DATA_DISTRIBUTION)) {
+ long replicatedSize = blocks.stream().mapToLong(DeletedBlock::getReplicatedSize).sum();
+ // even when HDDSLayoutFeature.DATA_DISTRIBUTION is finalized, old OM can still call the old API
+ if (replicatedSize >= 0) {
+ builder.setTotalBlockReplicatedSize(replicatedSize);
+ builder.setTotalBlockSize(blocks.stream().mapToLong(DeletedBlock::getSize).sum());
+ }
+ }
+ return builder.build();
}
@Override
@@ -176,11 +196,13 @@ public int getNumOfValidTransactions() throws IOException {
@Override
public void reinitialize(
- Table deletedTable) {
+ Table deletedTable, Table statefulConfigTable)
+ throws IOException {
// we don't need to handle SCMDeletedBlockTransactionStatusManager and
// deletedBlockLogStateManager, since they will be cleared
// when becoming leader.
- deletedBlockLogStateManager.reinitialize(deletedTable);
+ deletedBlockLogStateManager.reinitialize(deletedTable, statefulConfigTable);
+ transactionStatusManager.reinitialize(statefulConfigTable);
}
/**
@@ -206,13 +228,13 @@ public void onFlush() {
* @throws IOException
*/
@Override
- public void addTransactions(Map> containerBlocksMap)
+ public void addTransactions(Map> containerBlocksMap)
throws IOException {
lock.lock();
try {
ArrayList txsToBeAdded = new ArrayList<>();
long currentBatchSizeBytes = 0;
- for (Map.Entry< Long, List< Long > > entry :
+ for (Map.Entry> entry :
containerBlocksMap.entrySet()) {
long nextTXID = sequenceIdGen.getNextId(DEL_TXN_ID);
DeletedBlocksTransaction tx = constructNewTransaction(nextTXID,
@@ -222,14 +244,14 @@ public void addTransactions(Map> containerBlocksMap)
currentBatchSizeBytes += txSize;
if (currentBatchSizeBytes >= logAppenderQueueByteLimit) {
- deletedBlockLogStateManager.addTransactionsToDB(txsToBeAdded);
+ transactionStatusManager.addTransactions(txsToBeAdded);
metrics.incrBlockDeletionTransactionCreated(txsToBeAdded.size());
txsToBeAdded.clear();
currentBatchSizeBytes = 0;
}
}
if (!txsToBeAdded.isEmpty()) {
- deletedBlockLogStateManager.addTransactionsToDB(txsToBeAdded);
+ transactionStatusManager.addTransactions(txsToBeAdded);
metrics.incrBlockDeletionTransactionCreated(txsToBeAdded.size());
}
} finally {
@@ -367,6 +389,12 @@ public DatanodeDeletedBlockTransactions getTransactions(
DeletedBlocksTransaction txn = keyValue.getValue();
final ContainerID id = ContainerID.valueOf(txn.getContainerID());
final ContainerInfo container = containerManager.getContainer(id);
+ if (VersionedDatanodeFeatures.isFinalized(HDDSLayoutFeature.STORAGE_DATA_DISTRIBUTION) &&
+ txn.hasTotalBlockReplicatedSize()) {
+ transactionStatusManager.getTxSizeMap().put(txn.getTxID(),
+ new SCMDeletedBlockTransactionStatusManager.TxBlockInfo(txn.getLocalIDCount(),
+ txn.getTotalBlockSize(), txn.getTotalBlockReplicatedSize()));
+ }
try {
// HDDS-7126. When container is under replicated, it is possible
// that container is deleted, but transactions are not deleted.
@@ -411,6 +439,7 @@ public DatanodeDeletedBlockTransactions getTransactions(
deletedBlockLogStateManager.removeTransactionsFromDB(txIDs);
getSCMDeletedBlockTransactionStatusManager().removeTransactionFromDNsCommitMap(txIDs);
getSCMDeletedBlockTransactionStatusManager().removeTransactionFromDNsRetryCountMap(txIDs);
+ transactionStatusManager.removeTransactions(txIDs);
metrics.incrBlockDeletionTransactionCompleted(txIDs.size());
}
}
@@ -430,6 +459,11 @@ public void setScmCommandTimeoutMs(long scmCommandTimeoutMs) {
return transactionStatusManager;
}
+ @VisibleForTesting
+ public void setSCMDeletedBlockTransactionStatusManager(SCMDeletedBlockTransactionStatusManager manager) {
+ this.transactionStatusManager = manager;
+ }
+
@Override
public void recordTransactionCreated(DatanodeID dnId, long scmCmdId,
Set dnTxSet) {
@@ -442,6 +476,11 @@ public int getTransactionToDNsCommitMapSize() {
return getSCMDeletedBlockTransactionStatusManager().getTransactionToDNsCommitMapSize();
}
+ @Override
+ public DeletedBlocksTransactionSummary getTransactionSummary() {
+ return transactionStatusManager.getTransactionSummary();
+ }
+
@Override
public void onDatanodeDead(DatanodeID dnId) {
getSCMDeletedBlockTransactionStatusManager().onDatanodeDead(dnId);
diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLogStateManager.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLogStateManager.java
index 060b07bbdf93..f22718ce9ef2 100644
--- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLogStateManager.java
+++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLogStateManager.java
@@ -17,8 +17,10 @@
package org.apache.hadoop.hdds.scm.block;
+import com.google.protobuf.ByteString;
import java.io.IOException;
import java.util.ArrayList;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos.DeletedBlocksTransactionSummary;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.DeletedBlocksTransaction;
import org.apache.hadoop.hdds.scm.metadata.Replicate;
import org.apache.hadoop.hdds.utils.db.Table;
@@ -29,7 +31,14 @@
*/
public interface DeletedBlockLogStateManager {
@Replicate
- void addTransactionsToDB(ArrayList txs)
+ void addTransactionsToDB(ArrayList txs,
+ DeletedBlocksTransactionSummary summary) throws IOException;
+
+ @Replicate
+ void addTransactionsToDB(ArrayList txs) throws IOException;
+
+ @Replicate
+ void removeTransactionsFromDB(ArrayList txIDs, DeletedBlocksTransactionSummary summary)
throws IOException;
@Replicate
@@ -49,7 +58,10 @@ int resetRetryCountOfTransactionInDB(ArrayList txIDs)
Table.KeyValueIterator getReadOnlyIterator()
throws IOException;
+ ArrayList getTransactionsFromDB(ArrayList txIDs) throws IOException;
+
void onFlush();
- void reinitialize(Table deletedBlocksTXTable);
+ void reinitialize(Table deletedBlocksTXTable,
+ Table statefulConfigTable);
}
diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLogStateManagerImpl.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLogStateManagerImpl.java
index b6976c3c3f38..533bd59ba2d4 100644
--- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLogStateManagerImpl.java
+++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLogStateManagerImpl.java
@@ -18,20 +18,23 @@
package org.apache.hadoop.hdds.scm.block;
import com.google.common.base.Preconditions;
+import com.google.protobuf.ByteString;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.hadoop.hdds.conf.ConfigurationSource;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos.DeletedBlocksTransactionSummary;
import org.apache.hadoop.hdds.protocol.proto.SCMRatisProtocol.RequestType;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.DeletedBlocksTransaction;
import org.apache.hadoop.hdds.scm.container.ContainerID;
import org.apache.hadoop.hdds.scm.container.ContainerManager;
+import org.apache.hadoop.hdds.scm.ha.SCMHADBTransactionBuffer;
import org.apache.hadoop.hdds.scm.ha.SCMRatisServer;
-import org.apache.hadoop.hdds.scm.metadata.DBTransactionBuffer;
import org.apache.hadoop.hdds.utils.db.CodecException;
import org.apache.hadoop.hdds.utils.db.RocksDatabaseException;
import org.apache.hadoop.hdds.utils.db.Table;
@@ -50,17 +53,23 @@ public class DeletedBlockLogStateManagerImpl
LoggerFactory.getLogger(DeletedBlockLogStateManagerImpl.class);
private Table deletedTable;
+ private Table statefulConfigTable;
private ContainerManager containerManager;
- private final DBTransactionBuffer transactionBuffer;
+ private final SCMHADBTransactionBuffer transactionBuffer;
private final Set deletingTxIDs;
+ private final Set skippingRetryTxIDs;
+ public static final String SERVICE_NAME = DeletedBlockLogStateManager.class.getSimpleName();
public DeletedBlockLogStateManagerImpl(ConfigurationSource conf,
Table deletedTable,
- ContainerManager containerManager, DBTransactionBuffer txBuffer) {
+ Table statefulServiceConfigTable,
+ ContainerManager containerManager, SCMHADBTransactionBuffer txBuffer) {
this.deletedTable = deletedTable;
this.containerManager = containerManager;
this.transactionBuffer = txBuffer;
this.deletingTxIDs = ConcurrentHashMap.newKeySet();
+ this.skippingRetryTxIDs = ConcurrentHashMap.newKeySet();
+ this.statefulConfigTable = statefulServiceConfigTable;
}
@Override
@@ -139,8 +148,26 @@ public void removeFromDB() {
}
@Override
- public void addTransactionsToDB(ArrayList txs)
- throws IOException {
+ public void addTransactionsToDB(ArrayList txs,
+ DeletedBlocksTransactionSummary summary) throws IOException {
+ Map containerIdToTxnIdMap = new HashMap<>();
+ transactionBuffer.pauseAutoFlush();
+ try {
+ for (DeletedBlocksTransaction tx : txs) {
+ long tid = tx.getTxID();
+ containerIdToTxnIdMap.compute(ContainerID.valueOf(tx.getContainerID()),
+ (k, v) -> v != null && v > tid ? v : tid);
+ transactionBuffer.addToBuffer(deletedTable, tx.getTxID(), tx);
+ }
+ transactionBuffer.addToBuffer(statefulConfigTable, SERVICE_NAME, summary.toByteString());
+ } finally {
+ transactionBuffer.resumeAutoFlush();
+ }
+ containerManager.updateDeleteTransactionId(containerIdToTxnIdMap);
+ }
+
+ @Override
+ public void addTransactionsToDB(ArrayList txs) throws IOException {
Map containerIdToTxnIdMap = new HashMap<>();
for (DeletedBlocksTransaction tx : txs) {
long tid = tx.getTxID();
@@ -152,11 +179,27 @@ public void addTransactionsToDB(ArrayList txs)
}
@Override
- public void removeTransactionsFromDB(ArrayList txIDs)
+ public void removeTransactionsFromDB(ArrayList txIDs, DeletedBlocksTransactionSummary summary)
throws IOException {
if (deletingTxIDs != null) {
deletingTxIDs.addAll(txIDs);
}
+ transactionBuffer.pauseAutoFlush();
+ try {
+ for (Long txID : txIDs) {
+ transactionBuffer.removeFromBuffer(deletedTable, txID);
+ }
+ transactionBuffer.addToBuffer(statefulConfigTable, SERVICE_NAME, summary.toByteString());
+ } finally {
+ transactionBuffer.resumeAutoFlush();
+ }
+ }
+
+ @Override
+ public void removeTransactionsFromDB(ArrayList txIDs) throws IOException {
+ if (deletingTxIDs != null) {
+ deletingTxIDs.addAll(txIDs);
+ }
for (Long txID : txIDs) {
transactionBuffer.removeFromBuffer(deletedTable, txID);
}
@@ -181,6 +224,27 @@ public int resetRetryCountOfTransactionInDB(ArrayList txIDs)
return 0;
}
+ @Override
+ public ArrayList getTransactionsFromDB(ArrayList txIDs) throws IOException {
+ Objects.requireNonNull(txIDs, "txIds cannot be null.");
+ ArrayList transactions = new ArrayList<>();
+ for (long txId: txIDs) {
+ try {
+ DeletedBlocksTransaction transaction = deletedTable.get(txId);
+ if (transaction == null) {
+ LOG.debug("txId {} is not found in deletedTable.", txId);
+ continue;
+ }
+ transactions.add(transaction);
+ } catch (IOException ex) {
+ LOG.error("Could not get deleted block transaction {}.", txId, ex);
+ throw ex;
+ }
+ }
+ LOG.debug("Get {} DeletedBlocksTransactions for {} input txIDs", transactions.size(), txIDs.size());
+ return transactions;
+ }
+
@Override
public void onFlush() {
// onFlush() can be invoked only when ratis is enabled.
@@ -190,7 +254,7 @@ public void onFlush() {
@Override
public void reinitialize(
- Table deletedBlocksTXTable) {
+ Table deletedBlocksTXTable, Table configTable) {
// Before Reinitialization, flush will be called from Ratis StateMachine.
// Just the DeletedDb will be loaded here.
@@ -199,6 +263,7 @@ public void reinitialize(
// before reinitialization. Just update deletedTable here.
Preconditions.checkArgument(deletingTxIDs.isEmpty());
this.deletedTable = deletedBlocksTXTable;
+ this.statefulConfigTable = configTable;
}
public static Builder newBuilder() {
@@ -211,9 +276,10 @@ public static Builder newBuilder() {
public static class Builder {
private ConfigurationSource conf;
private SCMRatisServer scmRatisServer;
- private Table table;
- private DBTransactionBuffer transactionBuffer;
+ private Table deletedBlocksTransactionTable;
+ private SCMHADBTransactionBuffer transactionBuffer;
private ContainerManager containerManager;
+ private Table statefulServiceConfigTable;
public Builder setConfiguration(final ConfigurationSource config) {
conf = config;
@@ -227,11 +293,11 @@ public Builder setRatisServer(final SCMRatisServer ratisServer) {
public Builder setDeletedBlocksTable(
final Table deletedBlocksTable) {
- table = deletedBlocksTable;
+ deletedBlocksTransactionTable = deletedBlocksTable;
return this;
}
- public Builder setSCMDBTransactionBuffer(DBTransactionBuffer buffer) {
+ public Builder setSCMDBTransactionBuffer(SCMHADBTransactionBuffer buffer) {
this.transactionBuffer = buffer;
return this;
}
@@ -241,12 +307,17 @@ public Builder setContainerManager(ContainerManager contManager) {
return this;
}
- public DeletedBlockLogStateManager build() {
+ public Builder setStatefulConfigTable(final Table table) {
+ this.statefulServiceConfigTable = table;
+ return this;
+ }
+
+ public DeletedBlockLogStateManager build() throws IOException {
Preconditions.checkNotNull(conf);
- Preconditions.checkNotNull(table);
+ Preconditions.checkNotNull(deletedBlocksTransactionTable);
final DeletedBlockLogStateManager impl = new DeletedBlockLogStateManagerImpl(
- conf, table, containerManager, transactionBuffer);
+ conf, deletedBlocksTransactionTable, statefulServiceConfigTable, containerManager, transactionBuffer);
return scmRatisServer.getProxyHandler(RequestType.BLOCK,
DeletedBlockLogStateManager.class, impl);
diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/SCMDeletedBlockTransactionStatusManager.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/SCMDeletedBlockTransactionStatusManager.java
index f6136322f630..09d20afb4a96 100644
--- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/SCMDeletedBlockTransactionStatusManager.java
+++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/SCMDeletedBlockTransactionStatusManager.java
@@ -18,11 +18,14 @@
package org.apache.hadoop.hdds.scm.block;
import static java.lang.Math.min;
+import static org.apache.hadoop.hdds.scm.block.DeletedBlockLogStateManagerImpl.SERVICE_NAME;
import static org.apache.hadoop.hdds.scm.block.SCMDeletedBlockTransactionStatusManager.SCMDeleteBlocksCommandStatusManager.CmdStatus;
import static org.apache.hadoop.hdds.scm.block.SCMDeletedBlockTransactionStatusManager.SCMDeleteBlocksCommandStatusManager.CmdStatus.SENT;
import static org.apache.hadoop.hdds.scm.block.SCMDeletedBlockTransactionStatusManager.SCMDeleteBlocksCommandStatusManager.CmdStatus.TO_BE_SENT;
import com.google.common.annotations.VisibleForTesting;
+import com.google.protobuf.ByteString;
+import jakarta.annotation.Nullable;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
@@ -36,15 +39,22 @@
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
import org.apache.hadoop.hdds.protocol.DatanodeDetails;
import org.apache.hadoop.hdds.protocol.DatanodeID;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos.DeletedBlocksTransactionSummary;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.CommandStatus;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.ContainerBlocksDeletionACKProto.DeleteBlockTransactionResult;
+import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.DeletedBlocksTransaction;
import org.apache.hadoop.hdds.scm.container.ContainerID;
import org.apache.hadoop.hdds.scm.container.ContainerInfo;
import org.apache.hadoop.hdds.scm.container.ContainerManager;
import org.apache.hadoop.hdds.scm.container.ContainerReplica;
+import org.apache.hadoop.hdds.upgrade.HDDSLayoutFeature;
+import org.apache.hadoop.hdds.utils.db.Table;
+import org.apache.hadoop.ozone.container.upgrade.VersionedDatanodeFeatures;
import org.apache.hadoop.ozone.protocol.commands.SCMCommand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -61,6 +71,9 @@ public class SCMDeletedBlockTransactionStatusManager {
private final Map> transactionToDNsCommitMap;
// Maps txId to its retry counts;
private final Map transactionToRetryCountMap;
+ // an in memory map to cache the size of each transaction sending to DN.
+ private Map txSizeMap;
+
// The access to DeletedBlocksTXTable is protected by
// DeletedBlockLogStateManager.
private final DeletedBlockLogStateManager deletedBlockLogStateManager;
@@ -68,6 +81,23 @@ public class SCMDeletedBlockTransactionStatusManager {
private final ScmBlockDeletingServiceMetrics metrics;
private final long scmCommandTimeoutMs;
+ private Table statefulConfigTable;
+ public static final HddsProtos.DeletedBlocksTransactionSummary EMPTY_SUMMARY =
+ HddsProtos.DeletedBlocksTransactionSummary.newBuilder()
+ .setFirstTxID(Long.MAX_VALUE)
+ .setTotalTransactionCount(0)
+ .setTotalBlockCount(0)
+ .setTotalBlockSize(0)
+ .setTotalBlockReplicatedSize(0)
+ .build();
+ private final AtomicLong totalTxCount = new AtomicLong(0);
+ private final AtomicLong totalBlockCount = new AtomicLong(0);
+ private final AtomicLong totalBlocksSize = new AtomicLong(0);
+ private final AtomicLong totalReplicatedBlocksSize = new AtomicLong(0);
+ private long firstTxIdForDataDistribution = Long.MAX_VALUE;
+ private boolean isFirstTxIdForDataDistributionSet = false;
+ private static boolean disableDataDistributionForTest;
+
/**
* Before the DeletedBlockTransaction is executed on DN and reported to
* SCM, it is managed by this {@link SCMDeleteBlocksCommandStatusManager}.
@@ -80,17 +110,21 @@ public class SCMDeletedBlockTransactionStatusManager {
public SCMDeletedBlockTransactionStatusManager(
DeletedBlockLogStateManager deletedBlockLogStateManager,
+ Table statefulServiceConfigTable,
ContainerManager containerManager,
- ScmBlockDeletingServiceMetrics metrics, long scmCommandTimeoutMs) {
+ ScmBlockDeletingServiceMetrics metrics, long scmCommandTimeoutMs) throws IOException {
// maps transaction to dns which have committed it.
this.deletedBlockLogStateManager = deletedBlockLogStateManager;
+ this.statefulConfigTable = statefulServiceConfigTable;
this.metrics = metrics;
this.containerManager = containerManager;
this.scmCommandTimeoutMs = scmCommandTimeoutMs;
this.transactionToDNsCommitMap = new ConcurrentHashMap<>();
this.transactionToRetryCountMap = new ConcurrentHashMap<>();
+ this.txSizeMap = new ConcurrentHashMap<>();
this.scmDeleteBlocksCommandStatusManager =
new SCMDeleteBlocksCommandStatusManager(metrics);
+ this.initDataDistributionData();
}
/**
@@ -392,6 +426,7 @@ public void clear() {
transactionToRetryCountMap.clear();
scmDeleteBlocksCommandStatusManager.clear();
transactionToDNsCommitMap.clear();
+ txSizeMap.clear();
}
public void cleanAllTimeoutSCMCommand(long timeoutMs) {
@@ -415,6 +450,76 @@ private boolean alreadyExecuted(DatanodeID dnId, long txId) {
.contains(dnId);
}
+ @VisibleForTesting
+ public void addTransactions(ArrayList txList) throws IOException {
+ if (txList.isEmpty()) {
+ return;
+ }
+ if (VersionedDatanodeFeatures.isFinalized(HDDSLayoutFeature.STORAGE_DATA_DISTRIBUTION) &&
+ !disableDataDistributionForTest) {
+ for (DeletedBlocksTransaction tx: txList) {
+ if (tx.hasTotalBlockSize()) {
+ if (!isFirstTxIdForDataDistributionSet) {
+ // set the first transaction ID for data distribution
+ isFirstTxIdForDataDistributionSet = true;
+ firstTxIdForDataDistribution = tx.getTxID();
+ }
+ incrDeletedBlocksSummary(tx);
+ }
+ }
+ deletedBlockLogStateManager.addTransactionsToDB(txList, getSummary());
+ return;
+ }
+ deletedBlockLogStateManager.addTransactionsToDB(txList);
+ }
+
+ private void incrDeletedBlocksSummary(DeletedBlocksTransaction tx) {
+ totalTxCount.addAndGet(1);
+ totalBlockCount.addAndGet(tx.getLocalIDCount());
+ totalBlocksSize.addAndGet(tx.getTotalBlockSize());
+ totalReplicatedBlocksSize.addAndGet(tx.getTotalBlockReplicatedSize());
+ }
+
+ @VisibleForTesting
+ public void removeTransactions(ArrayList txIDs) throws IOException {
+ if (txIDs.isEmpty()) {
+ return;
+ }
+ if (VersionedDatanodeFeatures.isFinalized(HDDSLayoutFeature.STORAGE_DATA_DISTRIBUTION) &&
+ !disableDataDistributionForTest) {
+ ArrayList txToQueryList = new ArrayList<>();
+ for (Long txID: txIDs) {
+ if (txID >= firstTxIdForDataDistribution) {
+ TxBlockInfo txBlockInfo = txSizeMap.remove(txID);
+ if (txBlockInfo != null) {
+ // txBlockInfosToBeDeleted.add(txBlockInfo);
+ descDeletedBlocksSummary(txBlockInfo);
+ metrics.incrBlockDeletionTransactionSizeFromCache();
+ } else {
+ // Fetch the transaction from DB to get the size. This happens during
+ // 1. SCM leader transfer, deletion command send by one SCM,
+ // while the deletion ack received by a different SCM
+ // 2. SCM restarts, txBlockInfoMap is empty, while receiving the deletion ack from DN
+ txToQueryList.add(txID);
+ metrics.incrBlockDeletionTransactionSizeFromDB();
+ }
+ }
+ }
+ if (!txToQueryList.isEmpty()) {
+ ArrayList txList =
+ deletedBlockLogStateManager.getTransactionsFromDB(txToQueryList);
+ if (txList.size() != txToQueryList.size()) {
+ LOG.info("Failed to get all transactions from DB: " + txToQueryList.size() + ", got: " + txList.size());
+ }
+ txList.stream().filter(t -> t.hasTotalBlockSize()).forEach(t -> descDeletedBlocksSummary(t));
+ }
+ deletedBlockLogStateManager.removeTransactionsFromDB(txIDs, getSummary());
+ return;
+ }
+
+ deletedBlockLogStateManager.removeTransactionsFromDB(txIDs);
+ }
+
/**
* Commits a transaction means to delete all footprints of a transaction
* from the log. This method doesn't guarantee all transactions can be
@@ -483,7 +588,7 @@ public void commitTransactions(List transactionRes
}
}
try {
- deletedBlockLogStateManager.removeTransactionsFromDB(txIDsToBeDeleted);
+ removeTransactions(txIDsToBeDeleted);
metrics.incrBlockDeletionTransactionCompleted(txIDsToBeDeleted.size());
} catch (IOException e) {
LOG.warn("Could not commit delete block transactions: "
@@ -491,6 +596,30 @@ public void commitTransactions(List transactionRes
}
}
+ public DeletedBlocksTransactionSummary getSummary() {
+ return DeletedBlocksTransactionSummary.newBuilder()
+ .setFirstTxID(firstTxIdForDataDistribution)
+ .setTotalTransactionCount(totalTxCount.get())
+ .setTotalBlockCount(totalBlockCount.get())
+ .setTotalBlockSize(totalBlocksSize.get())
+ .setTotalBlockReplicatedSize(totalReplicatedBlocksSize.get())
+ .build();
+ }
+
+ private void descDeletedBlocksSummary(TxBlockInfo txBlockInfo) {
+ totalTxCount.addAndGet(-1);
+ totalBlockCount.addAndGet(-txBlockInfo.getTotalBlockCount());
+ totalBlocksSize.addAndGet(-txBlockInfo.getTotalBlockSize());
+ totalReplicatedBlocksSize.addAndGet(-txBlockInfo.getTotalReplicatedBlockSize());
+ }
+
+ private void descDeletedBlocksSummary(DeletedBlocksTransaction tx) {
+ totalTxCount.addAndGet(-1);
+ totalBlockCount.addAndGet(-tx.getLocalIDCount());
+ totalBlocksSize.addAndGet(-tx.getTotalBlockSize());
+ totalReplicatedBlocksSize.addAndGet(-tx.getTotalBlockReplicatedSize());
+ }
+
@VisibleForTesting
void commitSCMCommandStatus(List deleteBlockStatus, DatanodeID dnId) {
processSCMCommandStatus(deleteBlockStatus, dnId);
@@ -545,4 +674,96 @@ public void removeTransactionFromDNsCommitMap(List txIds) {
public void removeTransactionFromDNsRetryCountMap(List txIds) {
txIds.forEach(transactionToRetryCountMap::remove);
}
+
+ public void reinitialize(Table configTable) throws IOException {
+ // DB onFlush() will be called before reinitialization.
+ this.statefulConfigTable = configTable;
+ this.initDataDistributionData();
+ }
+
+ @VisibleForTesting
+ public Map getTxSizeMap() {
+ return txSizeMap;
+ }
+
+ @VisibleForTesting
+ public static void setDisableDataDistributionForTest(boolean disabled) {
+ disableDataDistributionForTest = disabled;
+ }
+
+ @Nullable
+ public DeletedBlocksTransactionSummary getTransactionSummary() {
+ if (!VersionedDatanodeFeatures.isFinalized(HDDSLayoutFeature.STORAGE_DATA_DISTRIBUTION)) {
+ return null;
+ }
+ return DeletedBlocksTransactionSummary.newBuilder()
+ .setFirstTxID(firstTxIdForDataDistribution)
+ .setTotalTransactionCount(totalTxCount.get())
+ .setTotalBlockCount(totalBlockCount.get())
+ .setTotalBlockSize(totalBlocksSize.get())
+ .setTotalBlockReplicatedSize(totalReplicatedBlocksSize.get())
+ .build();
+ }
+
+ private void initDataDistributionData() throws IOException {
+ if (VersionedDatanodeFeatures.isFinalized(HDDSLayoutFeature.STORAGE_DATA_DISTRIBUTION)) {
+ DeletedBlocksTransactionSummary summary = loadDeletedBlocksSummary();
+ if (summary != null) {
+ isFirstTxIdForDataDistributionSet = true;
+ firstTxIdForDataDistribution = summary.getFirstTxID();
+ totalTxCount.set(summary.getTotalTransactionCount());
+ totalBlockCount.set(summary.getTotalBlockCount());
+ totalBlocksSize.set(summary.getTotalBlockSize());
+ totalReplicatedBlocksSize.set(summary.getTotalBlockReplicatedSize());
+ LOG.info("Data distribution is enabled with totalBlockCount {} totalBlocksSize {} lastTxIdBeforeUpgrade {}",
+ totalBlockCount.get(), totalBlocksSize.get(), firstTxIdForDataDistribution);
+ }
+ } else {
+ LOG.info(HDDSLayoutFeature.STORAGE_DATA_DISTRIBUTION + " is not finalized");
+ }
+ }
+
+ private DeletedBlocksTransactionSummary loadDeletedBlocksSummary() throws IOException {
+ String propertyName = DeletedBlocksTransactionSummary.class.getSimpleName();
+ try {
+ ByteString byteString = statefulConfigTable.get(SERVICE_NAME);
+ if (byteString == null) {
+ // for a new Ozone cluster, property not found is an expected state.
+ LOG.info("Property {} for service {} not found. ", propertyName, SERVICE_NAME);
+ return null;
+ }
+ return DeletedBlocksTransactionSummary.parseFrom(byteString);
+ } catch (IOException e) {
+ LOG.error("Failed to get property {} for service {}. DataDistribution function will be disabled.",
+ propertyName, SERVICE_NAME, e);
+ throw new IOException("Failed to get property " + propertyName, e);
+ }
+ }
+
+ /**
+ * Block size information of a transaction.
+ */
+ public static class TxBlockInfo {
+ private long totalBlockCount;
+ private long totalBlockSize;
+ private long totalReplicatedBlockSize;
+
+ public TxBlockInfo(long blockCount, long blockSize, long replicatedSize) {
+ this.totalBlockCount = blockCount;
+ this.totalBlockSize = blockSize;
+ this.totalReplicatedBlockSize = replicatedSize;
+ }
+
+ public long getTotalBlockCount() {
+ return totalBlockCount;
+ }
+
+ public long getTotalBlockSize() {
+ return totalBlockSize;
+ }
+
+ public long getTotalReplicatedBlockSize() {
+ return totalReplicatedBlockSize;
+ }
+ }
}
diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/ScmBlockDeletingServiceMetrics.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/ScmBlockDeletingServiceMetrics.java
index cbfdddda7ca9..154d2915b95d 100644
--- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/ScmBlockDeletingServiceMetrics.java
+++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/ScmBlockDeletingServiceMetrics.java
@@ -20,6 +20,7 @@
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.hadoop.hdds.protocol.DatanodeID;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
import org.apache.hadoop.metrics2.MetricsCollector;
import org.apache.hadoop.metrics2.MetricsInfo;
import org.apache.hadoop.metrics2.MetricsRecordBuilder;
@@ -45,6 +46,7 @@ public final class ScmBlockDeletingServiceMetrics implements MetricsSource {
public static final String SOURCE_NAME =
SCMBlockDeletingService.class.getSimpleName();
private final MetricsRegistry registry;
+ private final BlockManager blockManager;
/**
* Given all commands are finished and no new coming deletes from OM.
@@ -100,15 +102,38 @@ public final class ScmBlockDeletingServiceMetrics implements MetricsSource {
private final Map numCommandsDatanode = new ConcurrentHashMap<>();
- private ScmBlockDeletingServiceMetrics() {
+ @Metric(about = "The number of transactions whose totalBlockSize is fetched from in memory cache")
+ private MutableGaugeLong numBlockDeletionTransactionSizeFromCache;
+
+ @Metric(about = "The number of transactions whose totalBlockSize is fetched from DB")
+ private MutableGaugeLong numBlockDeletionTransactionSizeFromDB;
+
+ private static final MetricsInfo NUM_BLOCK_DELETION_TRANSACTIONS = Interns.info(
+ "numBlockDeletionTransactions",
+ "The number of transactions in DB.");
+
+ private static final MetricsInfo NUM_BLOCK_OF_ALL_DELETION_TRANSACTIONS = Interns.info(
+ "numBlockOfAllDeletionTransactions",
+ "The number of blocks in all transactions in DB.");
+
+ private static final MetricsInfo BLOCK_SIZE_OF_ALL_DELETION_TRANSACTIONS = Interns.info(
+ "blockSizeOfAllDeletionTransactions",
+ "The size of all blocks in all transactions in DB.");
+
+ private static final MetricsInfo REPLICATED_BLOCK_SIZE_OF_ALL_DELETION_TRANSACTIONS = Interns.info(
+ "replicatedBlockSizeOfAllDeletionTransactions",
+ "The replicated size of all blocks in all transactions in DB.");
+
+ private ScmBlockDeletingServiceMetrics(BlockManager blockManager) {
this.registry = new MetricsRegistry(SOURCE_NAME);
+ this.blockManager = blockManager;
}
- public static synchronized ScmBlockDeletingServiceMetrics create() {
+ public static synchronized ScmBlockDeletingServiceMetrics create(BlockManager blockManager) {
if (instance == null) {
MetricsSystem ms = DefaultMetricsSystem.instance();
instance = ms.register(SOURCE_NAME, "SCMBlockDeletingService",
- new ScmBlockDeletingServiceMetrics());
+ new ScmBlockDeletingServiceMetrics(blockManager));
}
return instance;
@@ -163,6 +188,14 @@ public void incrProcessedTransaction() {
this.numProcessedTransactions.incr();
}
+ public void incrBlockDeletionTransactionSizeFromCache() {
+ this.numBlockDeletionTransactionSizeFromCache.incr();
+ }
+
+ public void incrBlockDeletionTransactionSizeFromDB() {
+ this.numBlockDeletionTransactionSizeFromDB.incr();
+ }
+
public void setNumBlockDeletionTransactionDataNodes(long dataNodes) {
this.numBlockDeletionTransactionDataNodes.set(dataNodes);
}
@@ -240,6 +273,14 @@ public long getNumBlockDeletionTransactionDataNodes() {
return numBlockDeletionTransactionDataNodes.value();
}
+ public long getNumBlockDeletionTransactionSizeFromCache() {
+ return numBlockDeletionTransactionSizeFromCache.value();
+ }
+
+ public long getNumBlockDeletionTransactionSizeFromDB() {
+ return numBlockDeletionTransactionSizeFromDB.value();
+ }
+
@Override
public void getMetrics(MetricsCollector metricsCollector, boolean all) {
MetricsRecordBuilder builder = metricsCollector.addRecord(SOURCE_NAME);
@@ -256,6 +297,21 @@ public void getMetrics(MetricsCollector metricsCollector, boolean all) {
numBlockDeletionTransactionDataNodes.snapshot(builder, all);
numBlockAddedForDeletionToDN.snapshot(builder, all);
+ // add metrics for deleted block transaction summary
+ HddsProtos.DeletedBlocksTransactionSummary summary = blockManager.getDeletedBlockLog().getTransactionSummary();
+ if (summary != null) {
+ numBlockDeletionTransactionSizeFromCache.snapshot(builder, all);
+ numBlockDeletionTransactionSizeFromDB.snapshot(builder, all);
+ builder = builder.endRecord().addRecord(SOURCE_NAME)
+ .addGauge(NUM_BLOCK_DELETION_TRANSACTIONS, summary.getTotalTransactionCount());
+ builder = builder.endRecord().addRecord(SOURCE_NAME)
+ .addGauge(NUM_BLOCK_OF_ALL_DELETION_TRANSACTIONS, summary.getTotalBlockCount());
+ builder = builder.endRecord().addRecord(SOURCE_NAME)
+ .addGauge(BLOCK_SIZE_OF_ALL_DELETION_TRANSACTIONS, summary.getTotalBlockSize());
+ builder = builder.endRecord().addRecord(SOURCE_NAME)
+ .addGauge(REPLICATED_BLOCK_SIZE_OF_ALL_DELETION_TRANSACTIONS, summary.getTotalBlockReplicatedSize());
+ }
+
MetricsRecordBuilder recordBuilder = builder;
for (Map.Entry e : numCommandsDatanode.entrySet()) {
recordBuilder = recordBuilder.endRecord().addRecord(SOURCE_NAME)
@@ -399,7 +455,10 @@ public String toString() {
.append(numBlockDeletionTransactionFailureOnDatanodes.value()).append('\t')
.append("numBlockAddedForDeletionToDN = ")
.append(numBlockAddedForDeletionToDN.value()).append('\t')
- .append("numDeletionCommandsPerDatanode = ").append(numCommandsDatanode);
+ .append("numDeletionCommandsPerDatanode = ").append(numCommandsDatanode)
+ .append("numBlockDeletionTransactionSizeReFetch = ")
+ .append(numBlockDeletionTransactionSizeFromCache.value()).append('\t')
+ .append(numBlockDeletionTransactionSizeFromDB.value()).append('\t');
return buffer.toString();
}
}
diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/placement/metrics/SCMPerformanceMetrics.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/placement/metrics/SCMPerformanceMetrics.java
index a01effa3a20b..5bb6e01b28de 100644
--- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/placement/metrics/SCMPerformanceMetrics.java
+++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/placement/metrics/SCMPerformanceMetrics.java
@@ -117,5 +117,13 @@ public void updateDeleteKeySuccessBlocks(long keys) {
public void updateDeleteKeyFailedBlocks(long keys) {
deleteKeyBlocksFailure.incr(keys);
}
+
+ public long getDeleteKeySuccessBlocks() {
+ return deleteKeyBlocksSuccess.value();
+ }
+
+ public long getDeleteKeyFailedBlocks() {
+ return deleteKeyBlocksFailure.value();
+ }
}
diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHADBTransactionBuffer.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHADBTransactionBuffer.java
index f404fd03f1d3..579d24ee7a5c 100644
--- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHADBTransactionBuffer.java
+++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHADBTransactionBuffer.java
@@ -42,6 +42,10 @@ public interface SCMHADBTransactionBuffer
AtomicReference getLatestSnapshotRef();
void flush() throws IOException;
+
+ void pauseAutoFlush();
+
+ void resumeAutoFlush();
boolean shouldFlush(long snapshotWaitTime);
diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHADBTransactionBufferImpl.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHADBTransactionBufferImpl.java
index 387b1001c2b1..23dd76e50735 100644
--- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHADBTransactionBufferImpl.java
+++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHADBTransactionBufferImpl.java
@@ -54,6 +54,7 @@ public class SCMHADBTransactionBufferImpl implements SCMHADBTransactionBuffer {
private final AtomicLong txFlushPending = new AtomicLong(0);
private long lastSnapshotTimeMs = 0;
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
+ private boolean autoFlushEnabled = true;
public SCMHADBTransactionBufferImpl(StorageContainerManager scm)
throws IOException {
@@ -121,6 +122,28 @@ public AtomicReference getLatestSnapshotRef() {
return latestSnapshot;
}
+ @Override
+ public void pauseAutoFlush() {
+ rwLock.writeLock().lock();
+ try {
+ autoFlushEnabled = false;
+ LOG.debug("Auto flush is paused for SCM HA DB transaction buffer.");
+ } finally {
+ rwLock.writeLock().unlock();
+ }
+ }
+
+ @Override
+ public void resumeAutoFlush() {
+ rwLock.writeLock().lock();
+ try {
+ autoFlushEnabled = true;
+ LOG.debug("Auto flush is resumed for SCM HA DB transaction buffer.");
+ } finally {
+ rwLock.writeLock().unlock();
+ }
+ }
+
@Override
public void flush() throws IOException {
rwLock.writeLock().lock();
@@ -179,7 +202,7 @@ public boolean shouldFlush(long snapshotWaitTime) {
rwLock.readLock().lock();
try {
long timeDiff = scm.getSystemClock().millis() - lastSnapshotTimeMs;
- return txFlushPending.get() > 0 && timeDiff > snapshotWaitTime;
+ return autoFlushEnabled && txFlushPending.get() > 0 && timeDiff > snapshotWaitTime;
} finally {
rwLock.readLock().unlock();
}
diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHADBTransactionBufferStub.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHADBTransactionBufferStub.java
index 64a16d335b2a..7dca3718dec2 100644
--- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHADBTransactionBufferStub.java
+++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHADBTransactionBufferStub.java
@@ -117,6 +117,14 @@ public void flush() throws IOException {
}
}
+ @Override
+ public void pauseAutoFlush() {
+ }
+
+ @Override
+ public void resumeAutoFlush() {
+ }
+
@Override
public boolean shouldFlush(long snapshotWaitTime) {
return true;
diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHAManagerImpl.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHAManagerImpl.java
index 00915406a4ce..a3f20476dc38 100644
--- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHAManagerImpl.java
+++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHAManagerImpl.java
@@ -445,7 +445,7 @@ public void startServices() throws IOException {
scm.getPipelineManager().reinitialize(metadataStore.getPipelineTable());
scm.getContainerManager().reinitialize(metadataStore.getContainerTable());
scm.getScmBlockManager().getDeletedBlockLog().reinitialize(
- metadataStore.getDeletedBlocksTXTable());
+ metadataStore.getDeletedBlocksTXTable(), metadataStore.getStatefulServiceConfigTable());
scm.getStatefulServiceStateManager().reinitialize(
metadataStore.getStatefulServiceConfigTable());
if (OzoneSecurityUtil.isSecurityEnabled(conf)) {
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 b9d4b9d6aef5..3b061aa10c01 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
@@ -83,6 +83,8 @@
import org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetContainerWithPipelineResponseProto;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetContainersOnDecomNodeRequestProto;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetContainersOnDecomNodeResponseProto;
+import org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetDeletedBlocksTxnSummaryRequestProto;
+import org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetDeletedBlocksTxnSummaryResponseProto;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetExistContainerWithPipelinesInBatchRequestProto;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetExistContainerWithPipelinesInBatchResponseProto;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetFailedDeletedBlocksTxnRequestProto;
@@ -712,6 +714,14 @@ public ScmContainerLocationResponse processRequest(
getResetDeletedBlockRetryCount(
request.getResetDeletedBlockRetryCountRequest()))
.build();
+ case GetDeletedBlocksTransactionSummary:
+ return ScmContainerLocationResponse.newBuilder()
+ .setCmdType(request.getCmdType())
+ .setStatus(Status.OK)
+ .setGetDeletedBlocksTxnSummaryResponse(
+ getDeletedBlocksTxnSummary(
+ request.getGetDeletedBlocksTxnSummaryRequest()))
+ .build();
case TransferLeadership:
return ScmContainerLocationResponse.newBuilder()
.setCmdType(request.getCmdType())
@@ -1344,6 +1354,18 @@ public GetFailedDeletedBlocksTxnResponseProto getFailedDeletedBlocksTxn(
.build();
}
+ public GetDeletedBlocksTxnSummaryResponseProto getDeletedBlocksTxnSummary(
+ GetDeletedBlocksTxnSummaryRequestProto request) throws IOException {
+ HddsProtos.DeletedBlocksTransactionSummary summary = impl.getDeletedBlockSummary();
+ if (summary == null) {
+ return GetDeletedBlocksTxnSummaryResponseProto.newBuilder().build();
+ } else {
+ return GetDeletedBlocksTxnSummaryResponseProto.newBuilder()
+ .setSummary(summary)
+ .build();
+ }
+ }
+
public TransferLeadershipResponseProto transferScmLeadership(
TransferLeadershipRequestProto request) throws IOException {
String newLeaderId = request.getNewLeaderId();
diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/OzoneStorageContainerManager.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/OzoneStorageContainerManager.java
index c4089bbca5c1..31230f071d59 100644
--- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/OzoneStorageContainerManager.java
+++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/OzoneStorageContainerManager.java
@@ -24,7 +24,10 @@
import org.apache.hadoop.hdds.scm.container.ContainerManager;
import org.apache.hadoop.hdds.scm.container.balancer.ContainerBalancer;
import org.apache.hadoop.hdds.scm.container.replication.ReplicationManager;
+import org.apache.hadoop.hdds.scm.ha.SCMHAManager;
import org.apache.hadoop.hdds.scm.ha.SCMNodeDetails;
+import org.apache.hadoop.hdds.scm.ha.SequenceIdGenerator;
+import org.apache.hadoop.hdds.scm.metadata.SCMMetadataStore;
import org.apache.hadoop.hdds.scm.node.NodeManager;
import org.apache.hadoop.hdds.scm.pipeline.PipelineManager;
@@ -59,4 +62,10 @@ public interface OzoneStorageContainerManager {
SCMNodeDetails getScmNodeDetails();
ReconfigurationHandler getReconfigurationHandler();
+
+ SCMMetadataStore getScmMetadataStore();
+
+ SCMHAManager getScmHAManager();
+
+ SequenceIdGenerator getSequenceIdGen();
}
diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMBlockProtocolServer.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMBlockProtocolServer.java
index 60c6384ba822..d2b2b6cbe43c 100644
--- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMBlockProtocolServer.java
+++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMBlockProtocolServer.java
@@ -45,7 +45,6 @@
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.fs.CommonConfigurationKeysPublic;
-import org.apache.hadoop.hdds.client.BlockID;
import org.apache.hadoop.hdds.client.ReplicationConfig;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.protocol.DatanodeDetails;
@@ -80,6 +79,7 @@
import org.apache.hadoop.ozone.audit.SCMAction;
import org.apache.hadoop.ozone.common.BlockGroup;
import org.apache.hadoop.ozone.common.DeleteBlockGroupResult;
+import org.apache.hadoop.ozone.common.DeletedBlock;
import org.apache.hadoop.util.Time;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -268,7 +268,7 @@ public List deleteKeyBlocks(
List keyBlocksInfoList) throws IOException {
long totalBlocks = 0;
for (BlockGroup bg : keyBlocksInfoList) {
- totalBlocks += bg.getBlockIDList().size();
+ totalBlocks += bg.getDeletedBlocks().size();
}
if (LOG.isDebugEnabled()) {
LOG.debug("SCM is informed by OM to delete {} keys. Total blocks to deleted {}.",
@@ -312,8 +312,8 @@ public List deleteKeyBlocks(
}
for (BlockGroup bg : keyBlocksInfoList) {
List blockResult = new ArrayList<>();
- for (BlockID b : bg.getBlockIDList()) {
- blockResult.add(new DeleteBlockResult(b, resultCode));
+ for (DeletedBlock b : bg.getDeletedBlocks()) {
+ blockResult.add(new DeleteBlockResult(b.getBlockID(), resultCode));
}
results.add(new DeleteBlockGroupResult(bg.getGroupID(), blockResult));
}
@@ -478,4 +478,8 @@ public AuditMessage buildAuditMessageForFailure(AuditAction op, Map txIDs) throws IOException {
return 0;
}
+ @Nullable
+ @Override
+ public DeletedBlocksTransactionSummary getDeletedBlockSummary() {
+ final Map auditMap = Maps.newHashMap();
+ try {
+ DeletedBlocksTransactionSummary summary =
+ scm.getScmBlockManager().getDeletedBlockLog().getTransactionSummary();
+ AUDIT.logReadSuccess(buildAuditMessageForSuccess(
+ SCMAction.GET_DELETED_BLOCK_SUMMARY, auditMap));
+ return summary;
+ } catch (Exception ex) {
+ AUDIT.logReadFailure(buildAuditMessageForFailure(
+ SCMAction.GET_DELETED_BLOCK_SUMMARY, auditMap, ex));
+ throw ex;
+ }
+ }
+
/**
* Check if SCM is in safe mode.
*
diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/StorageContainerManager.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/StorageContainerManager.java
index 4253e7b1c114..6b0d6bb97e60 100644
--- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/StorageContainerManager.java
+++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/StorageContainerManager.java
@@ -184,6 +184,7 @@
import org.apache.hadoop.ozone.OzoneConfigKeys;
import org.apache.hadoop.ozone.OzoneSecurityUtil;
import org.apache.hadoop.ozone.common.Storage.StorageState;
+import org.apache.hadoop.ozone.container.upgrade.VersionedDatanodeFeatures;
import org.apache.hadoop.ozone.lease.LeaseManager;
import org.apache.hadoop.ozone.lease.LeaseManagerNotRunningException;
import org.apache.hadoop.ozone.upgrade.DefaultUpgradeFinalizationExecutor;
@@ -679,6 +680,7 @@ private void initializeSystemManagers(OzoneConfiguration conf,
scmLayoutVersionManager = new HDDSLayoutVersionManager(
scmStorageConfig.getLayoutVersion());
+ VersionedDatanodeFeatures.initialize(scmLayoutVersionManager);
UpgradeFinalizationExecutor
finalizationExecutor;
@@ -1805,6 +1807,7 @@ public NodeDecommissionManager getScmDecommissionManager() {
/**
* Returns SCMHAManager.
*/
+ @Override
public SCMHAManager getScmHAManager() {
return scmHAManager;
}
@@ -1957,6 +1960,7 @@ public SCMContext getScmContext() {
/**
* Returns SequenceIdGen.
*/
+ @Override
public SequenceIdGenerator getSequenceIdGen() {
return sequenceIdGen;
}
@@ -1995,6 +1999,7 @@ public Map getContainerStateCount() {
* Returns the SCM metadata Store.
* @return SCMMetadataStore
*/
+ @Override
public SCMMetadataStore getScmMetadataStore() {
return scmMetadataStore;
}
diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/ozone/audit/SCMAction.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/ozone/audit/SCMAction.java
index 95e13146deed..52cd943c4dbb 100644
--- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/ozone/audit/SCMAction.java
+++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/ozone/audit/SCMAction.java
@@ -67,7 +67,8 @@ public enum SCMAction implements AuditAction {
GET_METRICS,
QUERY_NODE,
GET_PIPELINE,
- RECONCILE_CONTAINER;
+ RECONCILE_CONTAINER,
+ GET_DELETED_BLOCK_SUMMARY;
@Override
public String getAction() {
diff --git a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/block/TestDeletedBlockLog.java b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/block/TestDeletedBlockLog.java
index bc0c5cba4d1a..6db9504c7ad2 100644
--- a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/block/TestDeletedBlockLog.java
+++ b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/block/TestDeletedBlockLog.java
@@ -17,9 +17,12 @@
package org.apache.hadoop.hdds.scm.block;
+import static org.apache.hadoop.hdds.scm.block.SCMDeletedBlockTransactionStatusManager.EMPTY_SUMMARY;
+import static org.apache.hadoop.ozone.common.BlockGroup.SIZE_NOT_AVAILABLE;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doAnswer;
@@ -44,8 +47,10 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.apache.commons.lang3.RandomUtils;
import org.apache.hadoop.hdds.HddsConfigKeys;
+import org.apache.hadoop.hdds.client.BlockID;
import org.apache.hadoop.hdds.client.RatisReplicationConfig;
import org.apache.hadoop.hdds.client.StandaloneReplicationConfig;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
@@ -61,6 +66,7 @@
import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.SCMCommandProto.Type;
import org.apache.hadoop.hdds.scm.HddsTestUtils;
import org.apache.hadoop.hdds.scm.ScmConfigKeys;
+import org.apache.hadoop.hdds.scm.block.SCMDeletedBlockTransactionStatusManager.TxBlockInfo;
import org.apache.hadoop.hdds.scm.container.ContainerID;
import org.apache.hadoop.hdds.scm.container.ContainerInfo;
import org.apache.hadoop.hdds.scm.container.ContainerManager;
@@ -75,14 +81,18 @@
import org.apache.hadoop.hdds.scm.server.SCMConfigurator;
import org.apache.hadoop.hdds.scm.server.StorageContainerManager;
import org.apache.hadoop.hdds.utils.db.Table;
+import org.apache.hadoop.ozone.common.DeletedBlock;
import org.apache.hadoop.ozone.protocol.commands.CommandStatus;
import org.apache.hadoop.ozone.protocol.commands.DeleteBlocksCommand;
import org.apache.hadoop.ozone.protocol.commands.SCMCommand;
+import org.apache.ozone.test.GenericTestUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
/**
@@ -96,6 +106,7 @@ public class TestDeletedBlockLog {
@TempDir
private File testDir;
private ContainerManager containerManager;
+ private BlockManager blockManager;
private Table containerTable;
private StorageContainerManager scm;
private List dnList;
@@ -121,7 +132,9 @@ public void setup() throws Exception {
containerTable = scm.getScmMetadataStore().getContainerTable();
scmHADBTransactionBuffer =
new SCMHADBTransactionBufferStub(scm.getScmMetadataStore().getStore());
- metrics = mock(ScmBlockDeletingServiceMetrics.class);
+ blockManager = mock(BlockManager.class);
+ when(blockManager.getDeletedBlockLog()).thenReturn(deletedBlockLog);
+ metrics = ScmBlockDeletingServiceMetrics.create(blockManager);
deletedBlockLog = new DeletedBlockLogImpl(conf,
scm,
containerManager,
@@ -198,34 +211,36 @@ private void updateContainerMetadata(long cid,
@AfterEach
public void tearDown() throws Exception {
+ ScmBlockDeletingServiceMetrics.unRegister();
deletedBlockLog.close();
scm.stop();
scm.join();
}
- private Map> generateData(int dataSize) throws IOException {
+ private Map> generateData(int dataSize) throws IOException {
return generateData(dataSize, HddsProtos.LifeCycleState.CLOSED);
}
- private Map> generateData(int dataSize,
+ private Map> generateData(int txCount,
HddsProtos.LifeCycleState state) throws IOException {
- Map> blockMap = new HashMap<>();
- int continerIDBase = RandomUtils.secure().randomInt(0, 100);
+ Map> blockMap = new HashMap<>();
+ long continerIDBase = RandomUtils.secure().randomLong(0, 100);
int localIDBase = RandomUtils.secure().randomInt(0, 1000);
- for (int i = 0; i < dataSize; i++) {
+ long blockSize = 1024 * 1024 * 64;
+ for (int i = 0; i < txCount; i++) {
+ List blocks = new ArrayList<>();
long containerID = continerIDBase + i;
updateContainerMetadata(containerID, state);
- List blocks = new ArrayList<>();
for (int j = 0; j < BLOCKS_PER_TXN; j++) {
long localID = localIDBase + j;
- blocks.add(localID);
+ blocks.add(new DeletedBlock(new BlockID(containerID, localID), blockSize + j, blockSize + j));
}
blockMap.put(containerID, blocks);
}
return blockMap;
}
- private void addTransactions(Map> containerBlocksMap,
+ private void addTransactions(Map> containerBlocksMap,
boolean shouldFlush) throws IOException {
deletedBlockLog.addTransactions(containerBlocksMap);
if (shouldFlush) {
@@ -338,15 +353,15 @@ private void mockContainerHealthResult(Boolean healthy) {
public void testAddTransactionsIsBatched() throws Exception {
conf.setStorageSize(ScmConfigKeys.OZONE_SCM_HA_RAFT_LOG_APPENDER_QUEUE_BYTE_LIMIT, 1, StorageUnit.KB);
- DeletedBlockLogStateManager mockStateManager = mock(DeletedBlockLogStateManager.class);
+ SCMDeletedBlockTransactionStatusManager mockStatusManager = mock(SCMDeletedBlockTransactionStatusManager.class);
DeletedBlockLogImpl log = new DeletedBlockLogImpl(conf, scm, containerManager, scmHADBTransactionBuffer, metrics);
- log.setDeletedBlockLogStateManager(mockStateManager);
+ log.setSCMDeletedBlockTransactionStatusManager(mockStatusManager);
- Map> containerBlocksMap = generateData(100);
+ Map> containerBlocksMap = generateData(100);
log.addTransactions(containerBlocksMap);
- verify(mockStateManager, atLeast(2)).addTransactionsToDB(any());
+ verify(mockStatusManager, atLeast(2)).addTransactions(any());
}
@Test
@@ -576,7 +591,7 @@ public void testFailedAndTimeoutSCMCommandCanBeResend() throws Exception {
@Test
public void testDNOnlyOneNodeHealthy() throws Exception {
- Map> deletedBlocks = generateData(50);
+ Map> deletedBlocks = generateData(50);
addTransactions(deletedBlocks, true);
mockContainerHealthResult(false);
DatanodeDeletedBlockTransactions transactions
@@ -588,12 +603,12 @@ public void testDNOnlyOneNodeHealthy() throws Exception {
@Test
public void testInadequateReplicaCommit() throws Exception {
- Map> deletedBlocks = generateData(50);
+ Map> deletedBlocks = generateData(50);
addTransactions(deletedBlocks, true);
long containerID;
// let the first 30 container only consisting of only two unhealthy replicas
int count = 0;
- for (Map.Entry> entry : deletedBlocks.entrySet()) {
+ for (Map.Entry> entry : deletedBlocks.entrySet()) {
containerID = entry.getKey();
mockInadequateReplicaUnhealthyContainerInfo(containerID, count);
count += 1;
@@ -695,9 +710,9 @@ public void testDeletedBlockTransactions() throws IOException {
long containerID;
// Creates {TXNum} TX in the log.
- Map> deletedBlocks = generateData(txNum);
+ Map> deletedBlocks = generateData(txNum);
addTransactions(deletedBlocks, true);
- for (Map.Entry> entry :deletedBlocks.entrySet()) {
+ for (Map.Entry> entry :deletedBlocks.entrySet()) {
count++;
containerID = entry.getKey();
// let the container replication factor to be ONE
@@ -717,10 +732,11 @@ public void testDeletedBlockTransactions() throws IOException {
// add two transactions for same container
containerID = blocks.get(0).getContainerID();
- Map> deletedBlocksMap = new HashMap<>();
+ Map> deletedBlocksMap = new HashMap<>();
long localId = RandomUtils.secure().randomLong();
- deletedBlocksMap.put(containerID, new LinkedList<>(
- Collections.singletonList(localId)));
+ List blockIDList = new ArrayList<>();
+ blockIDList.add(new DeletedBlock(new BlockID(containerID, localId), SIZE_NOT_AVAILABLE, SIZE_NOT_AVAILABLE));
+ deletedBlocksMap.put(containerID, blockIDList);
addTransactions(deletedBlocksMap, true);
blocks = getTransactions(txNum * BLOCKS_PER_TXN * ONE);
// Only newly added Blocks will be sent, as previously sent transactions
@@ -747,7 +763,7 @@ public void testGetTransactionsWithMaxBlocksPerDatanode(int maxAllowedBlockNum)
DatanodeDetails dnId1 = dnList.get(0), dnId2 = dnList.get(1);
// Creates {TXNum} TX in the log.
- Map> deletedBlocks = generateData(txNum);
+ Map> deletedBlocks = generateData(txNum);
addTransactions(deletedBlocks, true);
List containerIds = new ArrayList<>(deletedBlocks.keySet());
for (int i = 0; i < containerIds.size(); i++) {
@@ -778,7 +794,7 @@ public void testDeletedBlockTransactionsOfDeletedContainer() throws IOException
List blocks;
// Creates {TXNum} TX in the log.
- Map> deletedBlocks = generateData(txNum,
+ Map> deletedBlocks = generateData(txNum,
HddsProtos.LifeCycleState.DELETED);
addTransactions(deletedBlocks, true);
@@ -787,6 +803,147 @@ public void testDeletedBlockTransactionsOfDeletedContainer() throws IOException
assertEquals(0, blocks.size());
}
+ @ParameterizedTest
+ @ValueSource(ints = {1, 10, 25, 50, 100})
+ public void testTransactionSerializedSize(int blockCount) {
+ long txID = 10000000;
+ long containerID = 1000000;
+ List blocks = new ArrayList<>();
+ for (int i = 0; i < blockCount; i++) {
+ blocks.add(new DeletedBlock(new BlockID(containerID, 100000000 + i), 128 * 1024 * 1024, 128 * 1024 * 1024));
+ }
+ List localIdList = blocks.stream().map(b -> b.getBlockID().getLocalID()).collect(Collectors.toList());
+ DeletedBlocksTransaction tx1 = DeletedBlocksTransaction.newBuilder()
+ .setTxID(txID)
+ .setContainerID(containerID)
+ .addAllLocalID(localIdList)
+ .setCount(0)
+ .setTotalBlockSize(blocks.stream().mapToLong(DeletedBlock::getSize).sum())
+ .setTotalBlockReplicatedSize(blocks.stream().mapToLong(DeletedBlock::getReplicatedSize).sum())
+ .build();
+ DeletedBlocksTransaction tx2 = DeletedBlocksTransaction.newBuilder()
+ .setTxID(txID)
+ .setContainerID(containerID)
+ .addAllLocalID(localIdList)
+ .setCount(0)
+ .build();
+ /*
+ * 1 blocks tx with totalBlockSize size is 26
+ * 1 blocks tx without totalBlockSize size is 16
+ * 10 blocks tx with totalBlockSize size is 73
+ * 10 blocks tx without totalBlockSize size is 61
+ * 25 blocks tx with totalBlockSize size is 148
+ * 25 blocks tx without totalBlockSize size is 136
+ * 50 blocks tx with totalBlockSize size is 273
+ * 50 blocks tx without totalBlockSize size is 261
+ * 100 blocks tx with totalBlockSize size is 523
+ * 100 blocks tx without totalBlockSize size is 511
+ */
+ System.out.println(blockCount + " blocks tx with totalBlockSize size is " + tx1.getSerializedSize());
+ System.out.println(blockCount + " blocks tx without totalBlockSize size is " + tx2.getSerializedSize());
+ }
+
+ public static Stream values() {
+ return Stream.of(
+ arguments(100, false, false),
+ arguments(100, true, false),
+ arguments(100, true, true),
+ arguments(1000, false, false),
+ arguments(1000, true, false),
+ arguments(1000, true, true),
+ arguments(1000, false, false),
+ arguments(1000, true, false),
+ arguments(1000, true, true),
+ arguments(100000, false, false),
+ arguments(100000, true, false),
+ arguments(100000, true, true)
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("values")
+ public void testAddRemoveTransactionPerformance(int txCount, boolean dataDistributionFinalized, boolean cacheEnabled)
+ throws Exception {
+ Map> data = generateData(txCount);
+ SCMDeletedBlockTransactionStatusManager statusManager =
+ deletedBlockLog.getSCMDeletedBlockTransactionStatusManager();
+ HddsProtos.DeletedBlocksTransactionSummary summary = statusManager.getTransactionSummary();
+ assertEquals(EMPTY_SUMMARY, summary);
+
+ SCMDeletedBlockTransactionStatusManager.setDisableDataDistributionForTest(!dataDistributionFinalized);
+ long startTime = System.nanoTime();
+ deletedBlockLog.addTransactions(data);
+ scmHADBTransactionBuffer.flush();
+ /**
+ * Before DataDistribution is enabled
+ * - 979 ms to add 100 txs to DB
+ * - 275 ms to add 1000 txs to DB
+ * - 1106 ms to add 10000 txs to DB
+ * - 11103 ms to add 100000 txs to DB
+ * After DataDistribution is enabled
+ * - 908 ms to add 100 txs to DB
+ * - 351 ms to add 1000 txs to DB
+ * - 2875 ms to add 10000 txs to DB
+ * - 12446 ms to add 100000 txs to DB
+ */
+ System.out.println((System.nanoTime() - startTime) / 100000 + " ms to add " + txCount + " txs to DB, " +
+ "dataDistributionFinalized " + dataDistributionFinalized);
+ summary = statusManager.getTransactionSummary();
+ if (dataDistributionFinalized) {
+ assertEquals(txCount, summary.getTotalTransactionCount());
+ } else {
+ assertEquals(0, summary.getTotalTransactionCount());
+ }
+
+ ArrayList txIdList = data.keySet().stream().collect(Collectors.toCollection(ArrayList::new));
+ long initialHitFromCacheCount = metrics.getNumBlockDeletionTransactionSizeFromCache();
+ long initialHitFromDBCount = metrics.getNumBlockDeletionTransactionSizeFromDB();
+
+ if (dataDistributionFinalized && cacheEnabled) {
+ Map txSizeMap = statusManager.getTxSizeMap();
+ for (Map.Entry> entry : data.entrySet()) {
+ List deletedBlockList = entry.getValue();
+ TxBlockInfo txBlockInfo = new TxBlockInfo(deletedBlockList.size(),
+ deletedBlockList.stream().map(DeletedBlock::getSize).reduce(0L, Long::sum),
+ deletedBlockList.stream().map(DeletedBlock::getReplicatedSize).reduce(0L, Long::sum));
+ txSizeMap.put(entry.getKey(), txBlockInfo);
+ }
+ }
+ startTime = System.nanoTime();
+ statusManager.removeTransactions(txIdList);
+ scmHADBTransactionBuffer.flush();
+ /**
+ * Before DataDistribution is enabled
+ * - 19 ms to remove 100 txs from DB
+ * - 26 ms to remove 1000 txs from DB
+ * - 142 ms to remove 10000 txs from DB
+ * - 2571 ms to remove 100000 txs from DB
+ * After DataDistribution is enabled (all cache miss)
+ * - 62 ms to remove 100 txs from DB
+ * - 186 ms to remove 1000 txs from DB
+ * - 968 ms to remove 10000 txs from DB
+ * - 8635 ms to remove 100000 txs from DB
+ * After DataDistribution is enabled (all cache hit)
+ * - 40 ms to remove 100 txs from DB
+ * - 112 ms to remove 1000 txs from DB
+ * - 412 ms to remove 10000 txs from DB
+ * - 3499 ms to remove 100000 txs from DB
+ */
+ System.out.println((System.nanoTime() - startTime) / 100000 + " ms to remove " + txCount + " txs from DB, " +
+ "dataDistributionFinalized " + dataDistributionFinalized + ", cacheEnabled " + cacheEnabled);
+ if (dataDistributionFinalized) {
+ if (cacheEnabled) {
+ GenericTestUtils.waitFor(() ->
+ metrics.getNumBlockDeletionTransactionSizeFromCache() - initialHitFromCacheCount == txCount, 100, 5000);
+ assertEquals(0, metrics.getNumBlockDeletionTransactionSizeFromDB() - initialHitFromDBCount);
+ } else {
+ GenericTestUtils.waitFor(() ->
+ metrics.getNumBlockDeletionTransactionSizeFromDB() - initialHitFromDBCount == txCount, 100, 5000);
+ assertEquals(0, metrics.getNumBlockDeletionTransactionSizeFromCache() - initialHitFromCacheCount);
+ }
+ }
+ }
+
private void mockStandAloneContainerInfo(long containerID, DatanodeDetails dd)
throws IOException {
List dns = Collections.singletonList(dd);
diff --git a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/block/TestSCMBlockDeletingService.java b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/block/TestSCMBlockDeletingService.java
index bc60c8c4ff28..de4b13e5b7d0 100644
--- a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/block/TestSCMBlockDeletingService.java
+++ b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/block/TestSCMBlockDeletingService.java
@@ -72,7 +72,7 @@ public void setup() throws Exception {
nodeManager = mock(NodeManager.class);
eventPublisher = mock(EventPublisher.class);
conf = new OzoneConfiguration();
- metrics = ScmBlockDeletingServiceMetrics.create();
+ metrics = ScmBlockDeletingServiceMetrics.create(mock(BlockManager.class));
when(nodeManager.getTotalDatanodeCommandCount(any(),
any())).thenReturn(0);
SCMServiceManager scmServiceManager = mock(SCMServiceManager.class);
diff --git a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/safemode/AbstractContainerSafeModeRuleTest.java b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/safemode/AbstractContainerSafeModeRuleTest.java
new file mode 100644
index 000000000000..7bfdecc71964
--- /dev/null
+++ b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/safemode/AbstractContainerSafeModeRuleTest.java
@@ -0,0 +1,206 @@
+/*
+ * 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.safemode;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.apache.hadoop.hdds.conf.ConfigurationSource;
+import org.apache.hadoop.hdds.protocol.DatanodeDetails;
+import org.apache.hadoop.hdds.protocol.DatanodeID;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos.LifeCycleState;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos.ReplicationType;
+import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.ContainerReplicaProto;
+import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.ContainerReportsProto;
+import org.apache.hadoop.hdds.scm.container.ContainerID;
+import org.apache.hadoop.hdds.scm.container.ContainerInfo;
+import org.apache.hadoop.hdds.scm.container.ContainerManager;
+import org.apache.hadoop.hdds.scm.container.ContainerNotFoundException;
+import org.apache.hadoop.hdds.scm.server.SCMDatanodeProtocolServer.NodeRegistrationContainerReport;
+import org.apache.hadoop.hdds.server.events.EventQueue;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+
+/**
+ * Abstract base class for container safe mode rule tests.
+ */
+public abstract class AbstractContainerSafeModeRuleTest {
+ private List containers;
+ private AbstractContainerSafeModeRule rule;
+
+ @BeforeEach
+ public void setup() throws ContainerNotFoundException {
+ final ContainerManager containerManager = mock(ContainerManager.class);
+ final ConfigurationSource conf = mock(ConfigurationSource.class);
+ final EventQueue eventQueue = mock(EventQueue.class);
+ final SCMSafeModeManager safeModeManager = mock(SCMSafeModeManager.class);
+ final SafeModeMetrics metrics = mock(SafeModeMetrics.class);
+
+ when(safeModeManager.getSafeModeMetrics()).thenReturn(metrics);
+ containers = new ArrayList<>();
+ when(containerManager.getContainers(getReplicationType())).thenReturn(containers);
+ when(containerManager.getContainer(any(ContainerID.class))).thenAnswer(invocation -> {
+ ContainerID id = invocation.getArgument(0);
+ return containers.stream()
+ .filter(c -> c.containerID().equals(id))
+ .findFirst()
+ .orElseThrow(ContainerNotFoundException::new);
+ });
+
+ rule = createRule(eventQueue, conf, containerManager, safeModeManager);
+ rule.setValidateBasedOnReportProcessing(false);
+ }
+
+ @Test
+ public void testRefreshInitializeContainers() {
+ containers.add(mockContainer(LifeCycleState.OPEN, 1L));
+ containers.add(mockContainer(LifeCycleState.CLOSED, 2L));
+ rule.refresh(true);
+
+ assertEquals(0.0, rule.getCurrentContainerThreshold());
+ }
+
+ @ParameterizedTest
+ @EnumSource(value = LifeCycleState.class,
+ names = {"OPEN", "CLOSING", "QUASI_CLOSED", "CLOSED", "DELETING", "DELETED", "RECOVERING"})
+ public void testValidateReturnsTrueAndFalse(LifeCycleState state) {
+ containers.add(mockContainer(state, 1L));
+ rule.refresh(true);
+
+ boolean expected = state != LifeCycleState.QUASI_CLOSED && state != LifeCycleState.CLOSED;
+ assertEquals(expected, rule.validate());
+ }
+
+ @Test
+ public void testProcessContainer() {
+ long containerId = 123L;
+ containers.add(mockContainer(LifeCycleState.CLOSED, containerId));
+ rule.refresh(true);
+
+ assertEquals(0.0, rule.getCurrentContainerThreshold());
+
+ // Send as many distinct reports as the container's minReplica requires
+ int minReplica = rule.getMinReplica(ContainerID.valueOf(containerId));
+ for (int i = 0; i < minReplica; i++) {
+ rule.process(getNewContainerReport(containerId));
+ }
+
+ assertEquals(1.0, rule.getCurrentContainerThreshold());
+ }
+
+ private NodeRegistrationContainerReport getNewContainerReport(long containerID) {
+ ContainerReplicaProto replica = mock(ContainerReplicaProto.class);
+ ContainerReportsProto containerReport = mock(ContainerReportsProto.class);
+ NodeRegistrationContainerReport report = mock(NodeRegistrationContainerReport.class);
+ DatanodeDetails datanodeDetails = mock(DatanodeDetails.class);
+
+ when(replica.getContainerID()).thenReturn(containerID);
+ when(containerReport.getReportsList()).thenReturn(Collections.singletonList(replica));
+ when(report.getReport()).thenReturn(containerReport);
+ when(report.getDatanodeDetails()).thenReturn(datanodeDetails);
+ when(datanodeDetails.getID()).thenReturn(DatanodeID.randomID());
+
+ return report;
+ }
+
+ @Test
+ public void testAllContainersClosed() {
+ containers.add(mockContainer(LifeCycleState.CLOSED, 11L));
+ containers.add(mockContainer(LifeCycleState.CLOSED, 32L));
+ rule.refresh(true);
+
+ assertEquals(0.0, rule.getCurrentContainerThreshold(), "Threshold should be 0.0 when all containers are closed");
+ assertFalse(rule.validate(), "Validate should return false when all containers are closed");
+ }
+
+ @Test
+ public void testAllContainersOpen() {
+ containers.add(mockContainer(LifeCycleState.OPEN, 11L));
+ containers.add(mockContainer(LifeCycleState.OPEN, 32L));
+ rule.refresh(true);
+
+ assertEquals(1.0, rule.getCurrentContainerThreshold(), "Threshold should be 1.0 when all containers are open");
+ assertTrue(rule.validate(), "Validate should return true when all containers are open");
+ }
+
+ @Test
+ public void testDuplicateContainerIdsInReports() {
+ long containerId = 42L;
+ containers.add(mockContainer(LifeCycleState.OPEN, containerId));
+ rule.refresh(true);
+
+ ContainerReplicaProto replica = mock(ContainerReplicaProto.class);
+ ContainerReportsProto containerReport = mock(ContainerReportsProto.class);
+ NodeRegistrationContainerReport report = mock(NodeRegistrationContainerReport.class);
+ DatanodeDetails datanodeDetails = mock(DatanodeDetails.class);
+
+ when(replica.getContainerID()).thenReturn(containerId);
+ when(containerReport.getReportsList()).thenReturn(Collections.singletonList(replica));
+ when(report.getReport()).thenReturn(containerReport);
+ when(report.getDatanodeDetails()).thenReturn(datanodeDetails);
+ when(datanodeDetails.getID()).thenReturn(DatanodeID.randomID());
+
+ rule.process(report);
+ rule.process(report);
+
+ assertEquals(1.0, rule.getCurrentContainerThreshold(), "Duplicated containers should be counted only once");
+ }
+
+ @Test
+ public void testValidateBasedOnReportProcessingTrue() {
+ rule.setValidateBasedOnReportProcessing(true);
+ long containerId = 1L;
+ containers.add(mockContainer(LifeCycleState.OPEN, containerId));
+ rule.refresh(true);
+
+ ContainerReplicaProto replica = mock(ContainerReplicaProto.class);
+ ContainerReportsProto reportsProto = mock(ContainerReportsProto.class);
+ NodeRegistrationContainerReport report = mock(NodeRegistrationContainerReport.class);
+ DatanodeDetails datanodeDetails = mock(DatanodeDetails.class);
+
+ when(replica.getContainerID()).thenReturn(containerId);
+ when(reportsProto.getReportsList()).thenReturn(Collections.singletonList(replica));
+ when(report.getReport()).thenReturn(reportsProto);
+ when(report.getDatanodeDetails()).thenReturn(datanodeDetails);
+ when(datanodeDetails.getID()).thenReturn(DatanodeID.randomID());
+
+ rule.process(report);
+
+ assertTrue(rule.validate(), "Should validate based on reported containers");
+ }
+
+ protected abstract ReplicationType getReplicationType();
+
+ protected abstract AbstractContainerSafeModeRule createRule(
+ EventQueue eventQueue,
+ ConfigurationSource conf,
+ ContainerManager containerManager,
+ SCMSafeModeManager safeModeManager
+ );
+
+ protected abstract ContainerInfo mockContainer(LifeCycleState state, long containerID);
+}
diff --git a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/safemode/TestECContainerSafeModeRule.java b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/safemode/TestECContainerSafeModeRule.java
index 23dcbfd979a2..8390747cf5c8 100644
--- a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/safemode/TestECContainerSafeModeRule.java
+++ b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/safemode/TestECContainerSafeModeRule.java
@@ -17,187 +17,39 @@
package org.apache.hadoop.hdds.scm.safemode;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
import org.apache.hadoop.hdds.client.ECReplicationConfig;
import org.apache.hadoop.hdds.conf.ConfigurationSource;
-import org.apache.hadoop.hdds.protocol.DatanodeDetails;
-import org.apache.hadoop.hdds.protocol.DatanodeID;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos.LifeCycleState;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos.ReplicationType;
-import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.ContainerReplicaProto;
-import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.ContainerReportsProto;
import org.apache.hadoop.hdds.scm.container.ContainerID;
import org.apache.hadoop.hdds.scm.container.ContainerInfo;
import org.apache.hadoop.hdds.scm.container.ContainerManager;
-import org.apache.hadoop.hdds.scm.container.ContainerNotFoundException;
-import org.apache.hadoop.hdds.scm.server.SCMDatanodeProtocolServer.NodeRegistrationContainerReport;
import org.apache.hadoop.hdds.server.events.EventQueue;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.EnumSource;
/**
* This class tests ECContainerSafeModeRule.
*/
-public class TestECContainerSafeModeRule {
- private List containers;
- private ECContainerSafeModeRule rule;
-
- @BeforeEach
- public void setup() throws ContainerNotFoundException {
- final ContainerManager containerManager = mock(ContainerManager.class);
- final ConfigurationSource conf = mock(ConfigurationSource.class);
- final EventQueue eventQueue = mock(EventQueue.class);
- final SCMSafeModeManager safeModeManager = mock(SCMSafeModeManager.class);
- final SafeModeMetrics metrics = mock(SafeModeMetrics.class);
- containers = new ArrayList<>();
- when(containerManager.getContainers(ReplicationType.EC)).thenReturn(containers);
- when(containerManager.getContainer(any(ContainerID.class))).thenAnswer(invocation -> {
- ContainerID id = invocation.getArgument(0);
- return containers.stream()
- .filter(c -> c.containerID().equals(id))
- .findFirst()
- .orElseThrow(ContainerNotFoundException::new);
- });
-
- when(safeModeManager.getSafeModeMetrics()).thenReturn(metrics);
-
- rule = new ECContainerSafeModeRule(eventQueue, conf, containerManager, safeModeManager);
- rule.setValidateBasedOnReportProcessing(false);
- }
-
- @Test
- public void testRefreshInitializeECContainers() {
- containers.add(mockECContainer(LifeCycleState.CLOSED, 1L));
- containers.add(mockECContainer(LifeCycleState.OPEN, 2L));
-
- rule.refresh(true);
-
- assertEquals(0.0, rule.getCurrentContainerThreshold());
- }
-
- @ParameterizedTest
- @EnumSource(value = LifeCycleState.class,
- names = {"OPEN", "CLOSING", "QUASI_CLOSED", "CLOSED", "DELETING", "DELETED", "RECOVERING"})
- public void testValidateReturnsTrueAndFalse(LifeCycleState state) {
- containers.add(mockECContainer(state, 1L));
- rule.refresh(true);
- boolean expected = state != LifeCycleState.QUASI_CLOSED && state != LifeCycleState.CLOSED;
- assertEquals(expected, rule.validate());
- }
-
- @Test
- public void testProcessECContainer() {
- long containerId = 123L;
- containers.add(mockECContainer(LifeCycleState.CLOSED, containerId));
- rule.refresh(true);
-
- assertEquals(0.0, rule.getCurrentContainerThreshold());
-
- // We need at least 3 replicas to be reported to validate the rule
- rule.process(getNewContainerReport(containerId));
- rule.process(getNewContainerReport(containerId));
- rule.process(getNewContainerReport(containerId));
-
- assertEquals(1.0, rule.getCurrentContainerThreshold());
- }
-
- private NodeRegistrationContainerReport getNewContainerReport(long containerID) {
- DatanodeDetails datanode = mock(DatanodeDetails.class);
- ContainerReportsProto containerReport = mock(ContainerReportsProto.class);
- NodeRegistrationContainerReport report = mock(NodeRegistrationContainerReport.class);
- ContainerReplicaProto replica = mock(ContainerReplicaProto.class);
-
- when(report.getDatanodeDetails()).thenReturn(datanode);
- when(datanode.getID()).thenReturn(DatanodeID.randomID());
- when(replica.getContainerID()).thenReturn(containerID);
- when(containerReport.getReportsList()).thenReturn(Collections.singletonList(replica));
- when(report.getReport()).thenReturn(containerReport);
- return report;
- }
-
- @Test
- public void testAllContainersClosed() {
- containers.add(mockECContainer(LifeCycleState.CLOSED, 11L));
- containers.add(mockECContainer(LifeCycleState.CLOSED, 32L));
-
- rule.refresh(true);
-
- assertEquals(0.0, rule.getCurrentContainerThreshold(), "Threshold should be 0.0 when all containers are closed");
- assertFalse(rule.validate(), "Validate should return false when all containers are closed");
- }
-
- @Test
- public void testAllContainersOpen() {
- containers.add(mockECContainer(LifeCycleState.OPEN, 11L));
- containers.add(mockECContainer(LifeCycleState.OPEN, 32L));
-
- rule.refresh(true);
-
- assertEquals(1.0, rule.getCurrentContainerThreshold(), "Threshold should be 1.0 when all containers are open");
- assertTrue(rule.validate(), "Validate should return true when all containers are open");
+public class TestECContainerSafeModeRule extends AbstractContainerSafeModeRuleTest {
+ @Override
+ protected ReplicationType getReplicationType() {
+ return ReplicationType.EC;
}
- @Test
- public void testDuplicateContainerIdsInReports() {
- long containerId = 42L;
- containers.add(mockECContainer(LifeCycleState.OPEN, containerId));
-
- rule.refresh(true);
-
- ContainerReplicaProto replica = mock(ContainerReplicaProto.class);
- ContainerReportsProto containerReport = mock(ContainerReportsProto.class);
- NodeRegistrationContainerReport report = mock(NodeRegistrationContainerReport.class);
- DatanodeDetails datanodeDetails = mock(DatanodeDetails.class);
-
- when(replica.getContainerID()).thenReturn(containerId);
- when(containerReport.getReportsList()).thenReturn(Collections.singletonList(replica));
- when(report.getReport()).thenReturn(containerReport);
- when(report.getDatanodeDetails()).thenReturn(datanodeDetails);
- when(datanodeDetails.getID()).thenReturn(DatanodeID.randomID());
-
- rule.process(report);
- rule.process(report);
-
- assertEquals(1.0, rule.getCurrentContainerThreshold(), "Duplicated containers should be counted only once");
- }
-
- @Test
- public void testValidateBasedOnReportProcessingTrue() throws Exception {
- rule.setValidateBasedOnReportProcessing(true);
- long containerId = 1L;
- containers.add(mockECContainer(LifeCycleState.OPEN, containerId));
-
- rule.refresh(true);
-
- ContainerReplicaProto replica = mock(ContainerReplicaProto.class);
- ContainerReportsProto reportsProto = mock(ContainerReportsProto.class);
- NodeRegistrationContainerReport report = mock(NodeRegistrationContainerReport.class);
- DatanodeDetails datanodeDetails = mock(DatanodeDetails.class);
-
- when(replica.getContainerID()).thenReturn(containerId);
- when(reportsProto.getReportsList()).thenReturn(Collections.singletonList(replica));
- when(report.getReport()).thenReturn(reportsProto);
- when(report.getDatanodeDetails()).thenReturn(datanodeDetails);
- when(datanodeDetails.getID()).thenReturn(DatanodeID.randomID());
-
-
- rule.process(report);
-
- assertTrue(rule.validate(), "Should validate based on reported containers");
+ @Override
+ protected AbstractContainerSafeModeRule createRule(
+ EventQueue eventQueue,
+ ConfigurationSource conf,
+ ContainerManager containerManager,
+ SCMSafeModeManager safeModeManager
+ ) {
+ return new ECContainerSafeModeRule(eventQueue, conf, containerManager, safeModeManager);
}
- private static ContainerInfo mockECContainer(LifeCycleState state, long containerID) {
+ @Override
+ protected ContainerInfo mockContainer(LifeCycleState state, long containerID) {
ContainerInfo container = mock(ContainerInfo.class);
when(container.getReplicationType()).thenReturn(ReplicationType.EC);
when(container.getState()).thenReturn(state);
diff --git a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/safemode/TestRatisContainerSafeModeRule.java b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/safemode/TestRatisContainerSafeModeRule.java
index 58929ffdd3fb..d6b34ec8e755 100644
--- a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/safemode/TestRatisContainerSafeModeRule.java
+++ b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/safemode/TestRatisContainerSafeModeRule.java
@@ -17,186 +17,41 @@
package org.apache.hadoop.hdds.scm.safemode;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
import org.apache.hadoop.hdds.client.RatisReplicationConfig;
import org.apache.hadoop.hdds.conf.ConfigurationSource;
-import org.apache.hadoop.hdds.protocol.DatanodeDetails;
-import org.apache.hadoop.hdds.protocol.DatanodeID;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos.LifeCycleState;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos.ReplicationType;
-import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.ContainerReplicaProto;
-import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.ContainerReportsProto;
import org.apache.hadoop.hdds.scm.container.ContainerID;
import org.apache.hadoop.hdds.scm.container.ContainerInfo;
import org.apache.hadoop.hdds.scm.container.ContainerManager;
-import org.apache.hadoop.hdds.scm.container.ContainerNotFoundException;
-import org.apache.hadoop.hdds.scm.server.SCMDatanodeProtocolServer.NodeRegistrationContainerReport;
import org.apache.hadoop.hdds.server.events.EventQueue;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.EnumSource;
/**
* This class tests RatisContainerSafeModeRule.
*/
-public class TestRatisContainerSafeModeRule {
+public class TestRatisContainerSafeModeRule extends AbstractContainerSafeModeRuleTest {
- private List containers;
- private RatisContainerSafeModeRule rule;
-
- @BeforeEach
- public void setup() throws ContainerNotFoundException {
- final ContainerManager containerManager = mock(ContainerManager.class);
- final ConfigurationSource conf = mock(ConfigurationSource.class);
- final EventQueue eventQueue = mock(EventQueue.class);
- final SCMSafeModeManager safeModeManager = mock(SCMSafeModeManager.class);
- final SafeModeMetrics metrics = mock(SafeModeMetrics.class);
-
- when(safeModeManager.getSafeModeMetrics()).thenReturn(metrics);
- containers = new ArrayList<>();
- when(containerManager.getContainers(ReplicationType.RATIS)).thenReturn(containers);
- when(containerManager.getContainer(any(ContainerID.class))).thenAnswer(invocation -> {
- ContainerID id = invocation.getArgument(0);
- return containers.stream()
- .filter(c -> c.containerID().equals(id))
- .findFirst()
- .orElseThrow(ContainerNotFoundException::new);
- });
-
- rule = new RatisContainerSafeModeRule(eventQueue, conf, containerManager, safeModeManager);
- rule.setValidateBasedOnReportProcessing(false);
- }
-
- @Test
- public void testRefreshInitializeRatisContainers() {
- containers.add(mockRatisContainer(LifeCycleState.CLOSED, 1L));
- containers.add(mockRatisContainer(LifeCycleState.OPEN, 2L));
-
- rule.refresh(true);
-
- assertEquals(0.0, rule.getCurrentContainerThreshold());
- }
-
- @ParameterizedTest
- @EnumSource(value = LifeCycleState.class,
- names = {"OPEN", "CLOSING", "QUASI_CLOSED", "CLOSED", "DELETING", "DELETED", "RECOVERING"})
- public void testValidateReturnsTrueAndFalse(LifeCycleState state) {
- containers.add(mockRatisContainer(state, 1L));
- rule.refresh(true);
-
- boolean expected = state != LifeCycleState.QUASI_CLOSED && state != LifeCycleState.CLOSED;
- assertEquals(expected, rule.validate());
+ @Override
+ protected ReplicationType getReplicationType() {
+ return ReplicationType.RATIS;
}
- @Test
- public void testProcessRatisContainer() {
- long containerId = 123L;
- containers.add(mockRatisContainer(LifeCycleState.CLOSED, containerId));
-
- rule.refresh(true);
-
- assertEquals(0.0, rule.getCurrentContainerThreshold());
-
- ContainerReplicaProto replica = mock(ContainerReplicaProto.class);
- List replicas = new ArrayList<>();
- replicas.add(replica);
- ContainerReportsProto containerReport = mock(ContainerReportsProto.class);
- NodeRegistrationContainerReport report = mock(NodeRegistrationContainerReport.class);
- DatanodeDetails datanodeDetails = mock(DatanodeDetails.class);
-
- when(replica.getContainerID()).thenReturn(containerId);
- when(containerReport.getReportsList()).thenReturn(replicas);
- when(report.getReport()).thenReturn(containerReport);
- when(report.getDatanodeDetails()).thenReturn(datanodeDetails);
- when(datanodeDetails.getID()).thenReturn(DatanodeID.randomID());
-
- rule.process(report);
-
- assertEquals(1.0, rule.getCurrentContainerThreshold());
- }
-
- @Test
- public void testAllContainersClosed() throws ContainerNotFoundException {
- containers.add(mockRatisContainer(LifeCycleState.CLOSED, 11L));
- containers.add(mockRatisContainer(LifeCycleState.CLOSED, 32L));
-
- rule.refresh(true);
-
- assertEquals(0.0, rule.getCurrentContainerThreshold(), "Threshold should be 0.0 when all containers are closed");
- assertFalse(rule.validate(), "Validate should return false when all containers are closed");
+ @Override
+ protected AbstractContainerSafeModeRule createRule(
+ EventQueue eventQueue,
+ ConfigurationSource conf,
+ ContainerManager containerManager,
+ SCMSafeModeManager safeModeManager
+ ) {
+ return new RatisContainerSafeModeRule(eventQueue, conf, containerManager, safeModeManager);
}
- @Test
- public void testAllContainersOpen() {
- containers.add(mockRatisContainer(LifeCycleState.OPEN, 11L));
- containers.add(mockRatisContainer(LifeCycleState.OPEN, 32L));
-
- rule.refresh(false);
-
- assertEquals(1.0, rule.getCurrentContainerThreshold(), "Threshold should be 1.0 when all containers are open");
- assertTrue(rule.validate(), "Validate should return true when all containers are open");
- }
-
- @Test
- public void testDuplicateContainerIdsInReports() {
- long containerId = 42L;
- containers.add(mockRatisContainer(LifeCycleState.OPEN, containerId));
-
- rule.refresh(true);
-
- ContainerReplicaProto replica = mock(ContainerReplicaProto.class);
- ContainerReportsProto containerReport = mock(ContainerReportsProto.class);
- NodeRegistrationContainerReport report = mock(NodeRegistrationContainerReport.class);
- DatanodeDetails datanodeDetails = mock(DatanodeDetails.class);
-
- when(replica.getContainerID()).thenReturn(containerId);
- when(containerReport.getReportsList()).thenReturn(Collections.singletonList(replica));
- when(report.getReport()).thenReturn(containerReport);
- when(report.getDatanodeDetails()).thenReturn(datanodeDetails);
- when(datanodeDetails.getID()).thenReturn(DatanodeID.randomID());
-
- rule.process(report);
- rule.process(report);
-
- assertEquals(1.0, rule.getCurrentContainerThreshold(), "Duplicated containers should be counted only once");
- }
-
- @Test
- public void testValidateBasedOnReportProcessingTrue() throws Exception {
- rule.setValidateBasedOnReportProcessing(true);
- long containerId = 1L;
- containers.add(mockRatisContainer(LifeCycleState.OPEN, containerId));
-
- rule.refresh(false);
-
- ContainerReplicaProto replica = mock(ContainerReplicaProto.class);
- ContainerReportsProto reportsProto = mock(ContainerReportsProto.class);
- NodeRegistrationContainerReport report = mock(NodeRegistrationContainerReport.class);
- DatanodeDetails datanodeDetails = mock(DatanodeDetails.class);
-
- when(replica.getContainerID()).thenReturn(containerId);
- when(reportsProto.getReportsList()).thenReturn(Collections.singletonList(replica));
- when(report.getReport()).thenReturn(reportsProto);
- when(report.getDatanodeDetails()).thenReturn(datanodeDetails);
- when(datanodeDetails.getID()).thenReturn(DatanodeID.randomID());
-
- rule.process(report);
-
- assertTrue(rule.validate(), "Should validate based on reported containers");
- }
-
- private static ContainerInfo mockRatisContainer(LifeCycleState state, long containerID) {
+ @Override
+ protected ContainerInfo mockContainer(LifeCycleState state, long containerID) {
ContainerInfo container = mock(ContainerInfo.class);
when(container.getReplicationType()).thenReturn(ReplicationType.RATIS);
when(container.getState()).thenReturn(state);
@@ -207,5 +62,4 @@ private static ContainerInfo mockRatisContainer(LifeCycleState state, long conta
.thenReturn(RatisReplicationConfig.getInstance(HddsProtos.ReplicationFactor.THREE));
return container;
}
-
}
diff --git a/hadoop-hdds/test-utils/pom.xml b/hadoop-hdds/test-utils/pom.xml
index 4dd86c7dc1ff..7b59c9196e06 100644
--- a/hadoop-hdds/test-utils/pom.xml
+++ b/hadoop-hdds/test-utils/pom.xml
@@ -17,10 +17,10 @@
org.apache.ozone
hdds
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
hdds-test-utils
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
jar
Apache Ozone HDDS Test Utils
Apache Ozone Distributed Data Store Test Utils
diff --git a/hadoop-ozone/cli-admin/pom.xml b/hadoop-ozone/cli-admin/pom.xml
index 7357cdeb3bff..9d713e43bf91 100644
--- a/hadoop-ozone/cli-admin/pom.xml
+++ b/hadoop-ozone/cli-admin/pom.xml
@@ -17,12 +17,12 @@
org.apache.ozone
hdds-hadoop-dependency-client
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
../../hadoop-hdds/hadoop-dependency-client
ozone-cli-admin
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
jar
Apache Ozone CLI Admin
Apache Ozone CLI Admin
diff --git a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/ContainerOperationClient.java b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/ContainerOperationClient.java
index 133166dec487..6bad18d29018 100644
--- a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/ContainerOperationClient.java
+++ b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/ContainerOperationClient.java
@@ -39,6 +39,7 @@
import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.ContainerDataProto;
import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.ReadContainerResponseProto;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos.DeletedBlocksTransactionSummary;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.ContainerBalancerStatusInfoResponseProto;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.DecommissionScmResponseProto;
import org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.StartContainerBalancerResponseProto;
@@ -175,7 +176,7 @@ public ContainerWithPipeline createContainer(String owner)
* @throws IOException
*/
public void createContainer(XceiverClientSpi client,
- long containerId) throws IOException {
+ long containerId) throws IOException {
String encodedToken = getEncodedContainerToken(containerId);
ContainerProtocolCalls.createContainer(client, containerId, encodedToken);
@@ -184,7 +185,7 @@ public void createContainer(XceiverClientSpi client,
// creation state.
if (LOG.isDebugEnabled()) {
LOG.debug("Created container {} machines {}", containerId,
- client.getPipeline().getNodes());
+ client.getPipeline().getNodes());
}
}
@@ -199,7 +200,7 @@ public String getEncodedContainerToken(long containerId) throws IOException {
@Override
public ContainerWithPipeline createContainer(HddsProtos.ReplicationType type,
- HddsProtos.ReplicationFactor factor, String owner) throws IOException {
+ HddsProtos.ReplicationFactor factor, String owner) throws IOException {
ReplicationConfig replicationConfig =
ReplicationConfig.fromProtoTypeAndFactor(replicationType, factor);
return createContainer(replicationConfig, owner);
@@ -210,7 +211,7 @@ public ContainerWithPipeline createContainer(ReplicationConfig replicationConfig
XceiverClientSpi client = null;
XceiverClientManager clientManager = getXceiverClientManager();
try {
- ContainerWithPipeline containerWithPipeline =
+ ContainerWithPipeline containerWithPipeline =
storageContainerLocationClient.allocateContainer(replicationConfig, owner);
Pipeline pipeline = containerWithPipeline.getPipeline();
// connect to pipeline leader and allocate container on leader datanode.
@@ -259,14 +260,14 @@ public List recommissionNodes(List hosts)
@Override
public List startMaintenanceNodes(List hosts,
- int endHours, boolean force) throws IOException {
+ int endHours, boolean force) throws IOException {
return storageContainerLocationClient.startMaintenanceNodes(
hosts, endHours, force);
}
@Override
public Pipeline createReplicationPipeline(HddsProtos.ReplicationType type,
- HddsProtos.ReplicationFactor factor, HddsProtos.NodePool nodePool)
+ HddsProtos.ReplicationFactor factor, HddsProtos.NodePool nodePool)
throws IOException {
return storageContainerLocationClient.createReplicationPipeline(type,
factor, nodePool);
@@ -317,7 +318,7 @@ public void close() {
@Override
public void deleteContainer(long containerId, Pipeline pipeline,
- boolean force) throws IOException {
+ boolean force) throws IOException {
XceiverClientSpi client = null;
XceiverClientManager clientManager = getXceiverClientManager();
try {
@@ -348,7 +349,7 @@ public void deleteContainer(long containerID, boolean force)
@Override
public ContainerListResult listContainer(long startContainerID,
- int count) throws IOException {
+ int count) throws IOException {
if (count > maxCountOfContainerList) {
LOG.warn("Attempting to list {} containers. However, this exceeds" +
" the cluster's current limit of {}. The results will be capped at the" +
@@ -361,9 +362,9 @@ public ContainerListResult listContainer(long startContainerID,
@Override
public ContainerListResult listContainer(long startContainerID,
- int count, HddsProtos.LifeCycleState state,
- HddsProtos.ReplicationType repType,
- ReplicationConfig replicationConfig) throws IOException {
+ int count, HddsProtos.LifeCycleState state,
+ HddsProtos.ReplicationType repType,
+ ReplicationConfig replicationConfig) throws IOException {
if (count > maxCountOfContainerList) {
LOG.warn("Attempting to list {} containers. However, this exceeds" +
" the cluster's current limit of {}. The results will be capped at the" +
@@ -376,7 +377,7 @@ public ContainerListResult listContainer(long startContainerID,
@Override
public ContainerDataProto readContainer(long containerID,
- Pipeline pipeline) throws IOException {
+ Pipeline pipeline) throws IOException {
XceiverClientManager clientManager = getXceiverClientManager();
String encodedToken = getEncodedContainerToken(containerID);
XceiverClientSpi client = null;
@@ -396,8 +397,7 @@ public ContainerDataProto readContainer(long containerID,
}
}
- public Map
- readContainerFromAllNodes(long containerID, Pipeline pipeline)
+ public Map readContainerFromAllNodes(long containerID, Pipeline pipeline)
throws IOException, InterruptedException {
XceiverClientManager clientManager = getXceiverClientManager();
String encodedToken = getEncodedContainerToken(containerID);
@@ -434,8 +434,7 @@ public ContainerWithPipeline getContainerWithPipeline(long containerId)
}
@Override
- public List
- getContainerReplicas(long containerId) throws IOException {
+ public List getContainerReplicas(long containerId) throws IOException {
List protos =
storageContainerLocationClient.getContainerReplicas(containerId,
ClientVersion.CURRENT_VERSION);
@@ -550,6 +549,11 @@ public void transferLeadership(String newLeaderId) throws IOException {
storageContainerLocationClient.transferLeadership(newLeaderId);
}
+ @Override
+ public DeletedBlocksTransactionSummary getDeletedBlockSummary() throws IOException {
+ return storageContainerLocationClient.getDeletedBlockSummary();
+ }
+
@Override
public List getDatanodeUsageInfo(
String address, String uuid) throws IOException {
diff --git a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/om/OMAdmin.java b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/om/OMAdmin.java
index d536b81be140..e096a55b95c6 100644
--- a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/om/OMAdmin.java
+++ b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/om/OMAdmin.java
@@ -29,6 +29,7 @@
import org.apache.hadoop.ozone.OmUtils;
import org.apache.hadoop.ozone.admin.OzoneAdmin;
import org.apache.hadoop.ozone.admin.om.lease.LeaseSubCommand;
+import org.apache.hadoop.ozone.admin.om.snapshot.SnapshotSubCommand;
import org.apache.hadoop.ozone.client.OzoneClientException;
import org.apache.hadoop.ozone.client.OzoneClientFactory;
import org.apache.hadoop.ozone.client.protocol.ClientProtocol;
@@ -59,7 +60,8 @@
UpdateRangerSubcommand.class,
TransferOmLeaderSubCommand.class,
FetchKeySubCommand.class,
- LeaseSubCommand.class
+ LeaseSubCommand.class,
+ SnapshotSubCommand.class
})
@MetaInfServices(AdminSubcommand.class)
public class OMAdmin implements AdminSubcommand {
diff --git a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/om/snapshot/DefragSubCommand.java b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/om/snapshot/DefragSubCommand.java
new file mode 100644
index 000000000000..6062353d60ba
--- /dev/null
+++ b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/om/snapshot/DefragSubCommand.java
@@ -0,0 +1,121 @@
+/*
+ * 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.ozone.admin.om.snapshot;
+
+import java.io.IOException;
+import java.util.concurrent.Callable;
+import org.apache.hadoop.hdds.cli.HddsVersionProvider;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.admin.om.OMAdmin;
+import org.apache.hadoop.ozone.om.helpers.OMNodeDetails;
+import org.apache.hadoop.ozone.om.protocolPB.OMAdminProtocolClientSideImpl;
+import org.apache.hadoop.security.UserGroupInformation;
+import picocli.CommandLine;
+
+/**
+ * Handler of ozone admin om snapshot defrag command.
+ */
+@CommandLine.Command(
+ name = "defrag",
+ description = "Triggers the Snapshot Defragmentation Service to run " +
+ "immediately. This command manually initiates the snapshot " +
+ "defragmentation process which compacts snapshot data and removes " +
+ "fragmentation to improve storage efficiency. " +
+ "This command works only on OzoneManager HA cluster.",
+ mixinStandardHelpOptions = true,
+ versionProvider = HddsVersionProvider.class
+)
+public class DefragSubCommand implements Callable {
+
+ @CommandLine.ParentCommand
+ private SnapshotSubCommand parent;
+
+ @CommandLine.Option(
+ names = {"-id", "--service-id"},
+ description = "Ozone Manager Service ID"
+ )
+ private String omServiceId;
+
+ @CommandLine.Option(
+ names = {"--node-id"},
+ description = "NodeID of the OM to trigger snapshot defragmentation on.",
+ required = false
+ )
+ private String nodeId;
+
+ @CommandLine.Option(
+ names = {"--no-wait"},
+ description = "Do not wait for the defragmentation task to complete. " +
+ "The command will return immediately after triggering the task.",
+ defaultValue = "false"
+ )
+ private boolean noWait;
+
+ @Override
+ public Void call() throws Exception {
+ // Navigate up to get OMAdmin
+ OMAdmin omAdmin = getOMAdmin();
+ OzoneConfiguration conf = omAdmin.getParent().getOzoneConf();
+ OMNodeDetails omNodeDetails = OMNodeDetails.getOMNodeDetailsFromConf(
+ conf, omServiceId, nodeId);
+
+ if (omNodeDetails == null) {
+ System.err.println("Error: OMNodeDetails could not be determined with given " +
+ "service ID and node ID.");
+ return null;
+ }
+
+ try (OMAdminProtocolClientSideImpl omAdminProtocolClient = createClient(conf, omNodeDetails)) {
+ execute(omAdminProtocolClient);
+ } catch (IOException ex) {
+ System.err.println("Failed to trigger snapshot defragmentation: " +
+ ex.getMessage());
+ throw ex;
+ }
+
+ return null;
+ }
+
+ protected OMAdminProtocolClientSideImpl createClient(
+ OzoneConfiguration conf, OMNodeDetails omNodeDetails) throws IOException {
+ return OMAdminProtocolClientSideImpl.createProxyForSingleOM(conf,
+ UserGroupInformation.getCurrentUser(), omNodeDetails);
+ }
+
+ protected void execute(OMAdminProtocolClientSideImpl omAdminProtocolClient)
+ throws IOException {
+ System.out.println("Triggering Snapshot Defrag Service ...");
+ boolean result = omAdminProtocolClient.triggerSnapshotDefrag(noWait);
+
+ if (noWait) {
+ System.out.println("Snapshot defragmentation task has been triggered " +
+ "successfully and is running in the background.");
+ } else {
+ if (result) {
+ System.out.println("Snapshot defragmentation completed successfully.");
+ } else {
+ System.out.println("Snapshot defragmentation task failed or was interrupted.");
+ }
+ }
+ }
+
+ private OMAdmin getOMAdmin() {
+ // The parent hierarchy is: DefragSubCommand -> SnapshotSubCommand -> OMAdmin
+ return parent.getParent();
+ }
+}
diff --git a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/om/snapshot/SnapshotSubCommand.java b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/om/snapshot/SnapshotSubCommand.java
new file mode 100644
index 000000000000..48ca9e365ff7
--- /dev/null
+++ b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/om/snapshot/SnapshotSubCommand.java
@@ -0,0 +1,41 @@
+/*
+ * 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.ozone.admin.om.snapshot;
+
+import org.apache.hadoop.ozone.admin.om.OMAdmin;
+import picocli.CommandLine;
+
+/**
+ * Handler of ozone admin om snapshot command.
+ */
+@CommandLine.Command(
+ name = "snapshot",
+ description = "Command for all snapshot related operations.",
+ subcommands = {
+ DefragSubCommand.class
+ }
+)
+public class SnapshotSubCommand {
+
+ @CommandLine.ParentCommand
+ private OMAdmin parent;
+
+ public OMAdmin getParent() {
+ return parent;
+ }
+}
diff --git a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/om/snapshot/package-info.java b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/om/snapshot/package-info.java
new file mode 100644
index 000000000000..00fd11817ccb
--- /dev/null
+++ b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/om/snapshot/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.
+ */
+
+/**
+ * Command line for Ozone Manager snapshot operations.
+ */
+package org.apache.hadoop.ozone.admin.om.snapshot;
diff --git a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/scm/DeletedBlocksTxnCommands.java b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/scm/DeletedBlocksTxnCommands.java
new file mode 100644
index 000000000000..b816cee2d7b6
--- /dev/null
+++ b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/scm/DeletedBlocksTxnCommands.java
@@ -0,0 +1,36 @@
+/*
+ * 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.ozone.admin.scm;
+
+import org.apache.hadoop.hdds.cli.HddsVersionProvider;
+import picocli.CommandLine;
+
+/**
+ * Subcommand to group container related operations.
+ */
+@CommandLine.Command(
+ name = "deletedBlocksTxn",
+ description = "SCM deleted blocks transaction specific operations",
+ mixinStandardHelpOptions = true,
+ versionProvider = HddsVersionProvider.class,
+ subcommands = {
+ GetDeletedBlockSummarySubcommand.class,
+ })
+public class DeletedBlocksTxnCommands {
+
+}
diff --git a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/scm/GetDeletedBlockSummarySubcommand.java b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/scm/GetDeletedBlockSummarySubcommand.java
new file mode 100644
index 000000000000..34c54db27097
--- /dev/null
+++ b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/scm/GetDeletedBlockSummarySubcommand.java
@@ -0,0 +1,56 @@
+/*
+ * 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.ozone.admin.scm;
+
+import java.io.IOException;
+import org.apache.hadoop.hdds.cli.HddsVersionProvider;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.scm.cli.ScmSubcommand;
+import org.apache.hadoop.hdds.scm.client.ScmClient;
+import picocli.CommandLine;
+
+/**
+ * Handler of getting deleted blocks summary from SCM side.
+ */
+@CommandLine.Command(
+ name = "summary",
+ description = "get DeletedBlocksTransaction summary",
+ mixinStandardHelpOptions = true,
+ versionProvider = HddsVersionProvider.class)
+public class GetDeletedBlockSummarySubcommand extends ScmSubcommand {
+
+ @Override
+ public void execute(ScmClient client) throws IOException {
+ HddsProtos.DeletedBlocksTransactionSummary summary = client.getDeletedBlockSummary();
+ if (summary == null) {
+ System.out.println("DeletedBlocksTransaction summary is not available");
+ } else {
+ System.out.println("DeletedBlocksTransaction summary:");
+ System.out.println(" Start from tx ID: " +
+ summary.getFirstTxID());
+ System.out.println(" Total number of transactions: " +
+ summary.getTotalTransactionCount());
+ System.out.println(" Total number of blocks: " +
+ summary.getTotalBlockCount());
+ System.out.println(" Total size of blocks: " +
+ summary.getTotalBlockSize());
+ System.out.println(" Total replicated size of blocks: " +
+ summary.getTotalBlockReplicatedSize());
+ }
+ }
+}
diff --git a/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/ozone/admin/om/snapshot/TestDefragSubCommand.java b/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/ozone/admin/om/snapshot/TestDefragSubCommand.java
new file mode 100644
index 000000000000..105a79f987d8
--- /dev/null
+++ b/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/ozone/admin/om/snapshot/TestDefragSubCommand.java
@@ -0,0 +1,162 @@
+/*
+ * 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.ozone.admin.om.snapshot;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.om.helpers.OMNodeDetails;
+import org.apache.hadoop.ozone.om.protocolPB.OMAdminProtocolClientSideImpl;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import picocli.CommandLine;
+
+/**
+ * Unit tests to validate the DefragSubCommand class includes
+ * the correct output when executed against a mock client.
+ */
+public class TestDefragSubCommand {
+
+ private TestableDefragSubCommand cmd;
+ private OMAdminProtocolClientSideImpl omAdminClient;
+ private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
+ private final ByteArrayOutputStream errContent = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+ private final PrintStream originalErr = System.err;
+ private static final String DEFAULT_ENCODING = StandardCharsets.UTF_8.name();
+
+ /**
+ * Testable version of DefragSubCommand that allows injecting a mock client.
+ */
+ private static class TestableDefragSubCommand extends DefragSubCommand {
+ private final OMAdminProtocolClientSideImpl mockClient;
+
+ TestableDefragSubCommand(OMAdminProtocolClientSideImpl mockClient) {
+ this.mockClient = mockClient;
+ }
+
+ @Override
+ protected OMAdminProtocolClientSideImpl createClient(
+ OzoneConfiguration conf, OMNodeDetails omNodeDetails) {
+ return mockClient;
+ }
+ }
+
+ @BeforeEach
+ public void setup() throws Exception {
+ omAdminClient = mock(OMAdminProtocolClientSideImpl.class);
+ cmd = new TestableDefragSubCommand(omAdminClient);
+
+ // Mock close() to do nothing - needed for try-with-resources
+ doNothing().when(omAdminClient).close();
+
+
+ System.setOut(new PrintStream(outContent, false, DEFAULT_ENCODING));
+ System.setErr(new PrintStream(errContent, false, DEFAULT_ENCODING));
+ }
+
+ @AfterEach
+ public void tearDown() {
+ System.setOut(originalOut);
+ System.setErr(originalErr);
+ }
+
+ @Test
+ public void testTriggerSnapshotDefragWithWait() throws Exception {
+ // Mock the client to return success
+ when(omAdminClient.triggerSnapshotDefrag(false)).thenReturn(true);
+
+ // Execute the command (default behavior: wait for completion)
+ CommandLine c = new CommandLine(cmd);
+ c.parseArgs();
+ cmd.execute(omAdminClient);
+
+ // Verify the client method was called with correct parameter
+ verify(omAdminClient).triggerSnapshotDefrag(eq(false));
+
+ // Verify output contains success message
+ String output = outContent.toString(DEFAULT_ENCODING);
+ assertTrue(output.contains("Triggering Snapshot Defrag Service"));
+ assertTrue(output.contains("Snapshot defragmentation completed successfully"));
+ }
+
+ @Test
+ public void testTriggerSnapshotDefragWithWaitFailure() throws Exception {
+ // Mock the client to return failure
+ when(omAdminClient.triggerSnapshotDefrag(false)).thenReturn(false);
+
+ // Execute the command
+ CommandLine c = new CommandLine(cmd);
+ c.parseArgs();
+ cmd.execute(omAdminClient);
+
+ // Verify the client method was called
+ verify(omAdminClient).triggerSnapshotDefrag(eq(false));
+
+ // Verify output contains failure message
+ String output = outContent.toString(DEFAULT_ENCODING);
+ assertTrue(output.contains("Triggering Snapshot Defrag"));
+ assertTrue(output.contains("Snapshot defragmentation task failed or was interrupted"));
+ }
+
+ @Test
+ public void testTriggerSnapshotDefragWithServiceIdAndNodeId() throws Exception {
+ // Mock the client with both service ID and node ID
+ when(omAdminClient.triggerSnapshotDefrag(false)).thenReturn(true);
+
+ // Execute the command with service ID and node ID
+ CommandLine c = new CommandLine(cmd);
+ c.parseArgs("--service-id", "om-service-1", "--node-id", "om1");
+ cmd.execute(omAdminClient);
+
+ // Verify the client method was called
+ verify(omAdminClient).triggerSnapshotDefrag(eq(false));
+
+ // Verify success message
+ String output = outContent.toString(DEFAULT_ENCODING);
+ assertTrue(output.contains("Snapshot defragmentation completed successfully"));
+ }
+
+ @Test
+ public void testTriggerSnapshotDefragWithAllOptions() throws Exception {
+ // Test with service-id, node-id, and no-wait options
+ when(omAdminClient.triggerSnapshotDefrag(true)).thenReturn(true);
+
+ // Execute the command with multiple options
+ CommandLine c = new CommandLine(cmd);
+ c.parseArgs("--service-id", "om-service-1", "--node-id", "om1", "--no-wait");
+ cmd.execute(omAdminClient);
+
+ // Verify the client method was called
+ verify(omAdminClient).triggerSnapshotDefrag(eq(true));
+
+ // Verify output for background execution
+ String output = outContent.toString(DEFAULT_ENCODING);
+ assertTrue(output.contains("triggered successfully and is running in the background"));
+ }
+}
+
diff --git a/hadoop-ozone/cli-shell/pom.xml b/hadoop-ozone/cli-shell/pom.xml
index f7eeee7583c2..89d326efbd31 100644
--- a/hadoop-ozone/cli-shell/pom.xml
+++ b/hadoop-ozone/cli-shell/pom.xml
@@ -17,11 +17,11 @@
org.apache.ozone
hdds-hadoop-dependency-client
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
../../hadoop-hdds/hadoop-dependency-client
ozone-cli-shell
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
jar
Apache Ozone CLI Shell
Apache Ozone CLI Shell
diff --git a/hadoop-ozone/client/pom.xml b/hadoop-ozone/client/pom.xml
index 126f9a725842..603a87e3fe64 100644
--- a/hadoop-ozone/client/pom.xml
+++ b/hadoop-ozone/client/pom.xml
@@ -17,11 +17,11 @@
org.apache.ozone
hdds-hadoop-dependency-client
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
../../hadoop-hdds/hadoop-dependency-client
ozone-client
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
jar
Apache Ozone Client
Apache Ozone Client
diff --git a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/OzoneSnapshot.java b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/OzoneSnapshot.java
index b7bf7051caeb..360fd4cef6da 100644
--- a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/OzoneSnapshot.java
+++ b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/OzoneSnapshot.java
@@ -192,7 +192,7 @@ public static OzoneSnapshot fromSnapshotInfo(SnapshotInfo snapshotInfo) {
snapshotInfo.getSnapshotStatus(),
snapshotInfo.getSnapshotId(),
snapshotInfo.getSnapshotPath(),
- snapshotInfo.getCheckpointDir(),
+ snapshotInfo.getCheckpointDirName(0),
snapshotInfo.getReferencedSize(),
snapshotInfo.getReferencedReplicatedSize(),
snapshotInfo.getExclusiveSize() + snapshotInfo.getExclusiveSizeDeltaFromDirDeepCleaning(),
@@ -222,4 +222,22 @@ public int hashCode() {
return Objects.hash(volumeName, bucketName, name, creationTime, snapshotStatus, snapshotId, snapshotPath,
checkpointDir, referencedSize, referencedReplicatedSize, exclusiveSize, exclusiveReplicatedSize);
}
+
+ @Override
+ public String toString() {
+ return "OzoneSnapshot{" +
+ "bucketName='" + bucketName + '\'' +
+ ", volumeName='" + volumeName + '\'' +
+ ", name='" + name + '\'' +
+ ", creationTime=" + creationTime +
+ ", snapshotStatus=" + snapshotStatus +
+ ", snapshotId=" + snapshotId +
+ ", snapshotPath='" + snapshotPath + '\'' +
+ ", checkpointDir='" + checkpointDir + '\'' +
+ ", referencedSize=" + referencedSize +
+ ", referencedReplicatedSize=" + referencedReplicatedSize +
+ ", exclusiveSize=" + exclusiveSize +
+ ", exclusiveReplicatedSize=" + exclusiveReplicatedSize +
+ '}';
+ }
}
diff --git a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
index ee4070c9eba4..d4ebf0be1b38 100644
--- a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
+++ b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
@@ -18,6 +18,8 @@
package org.apache.hadoop.ozone.client.rpc;
import static org.apache.hadoop.ozone.OzoneAcl.LINK_BUCKET_DEFAULT_ACL;
+import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_CLIENT_ELASTIC_BYTE_BUFFER_POOL_MAX_SIZE;
+import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_CLIENT_ELASTIC_BYTE_BUFFER_POOL_MAX_SIZE_DEFAULT;
import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_CLIENT_KEY_PROVIDER_CACHE_EXPIRY;
import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_CLIENT_KEY_PROVIDER_CACHE_EXPIRY_DEFAULT;
import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_CLIENT_REQUIRED_OM_VERSION_MIN_KEY;
@@ -71,6 +73,7 @@
import org.apache.hadoop.hdds.client.ReplicationFactor;
import org.apache.hadoop.hdds.client.ReplicationType;
import org.apache.hadoop.hdds.conf.ConfigurationSource;
+import org.apache.hadoop.hdds.conf.StorageUnit;
import org.apache.hadoop.hdds.protocol.DatanodeDetails;
import org.apache.hadoop.hdds.protocol.StorageType;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
@@ -88,7 +91,6 @@
import org.apache.hadoop.hdds.tracing.TracingUtil;
import org.apache.hadoop.hdds.utils.IOUtils;
import org.apache.hadoop.io.ByteBufferPool;
-import org.apache.hadoop.io.ElasticByteBufferPool;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.ozone.OzoneAcl;
import org.apache.hadoop.ozone.OzoneConfigKeys;
@@ -110,6 +112,7 @@
import org.apache.hadoop.ozone.client.VolumeArgs;
import org.apache.hadoop.ozone.client.io.BlockInputStreamFactory;
import org.apache.hadoop.ozone.client.io.BlockInputStreamFactoryImpl;
+import org.apache.hadoop.ozone.client.io.BoundedElasticByteBufferPool;
import org.apache.hadoop.ozone.client.io.CipherOutputStreamOzone;
import org.apache.hadoop.ozone.client.io.ECBlockInputStream;
import org.apache.hadoop.ozone.client.io.ECKeyOutputStream;
@@ -318,7 +321,11 @@ public void onRemoval(
}
}
}).build();
- this.byteBufferPool = new ElasticByteBufferPool();
+ long maxPoolSize = (long) conf.getStorageSize(
+ OZONE_CLIENT_ELASTIC_BYTE_BUFFER_POOL_MAX_SIZE,
+ OZONE_CLIENT_ELASTIC_BYTE_BUFFER_POOL_MAX_SIZE_DEFAULT,
+ StorageUnit.GB);
+ this.byteBufferPool = new BoundedElasticByteBufferPool(maxPoolSize);
this.blockInputStreamFactory = BlockInputStreamFactoryImpl
.getInstance(byteBufferPool, ecReconstructExecutor);
this.clientMetrics = ContainerClientMetrics.acquire();
diff --git a/hadoop-ozone/client/src/test/java/org/apache/hadoop/ozone/client/TestOzoneSnapshot.java b/hadoop-ozone/client/src/test/java/org/apache/hadoop/ozone/client/TestOzoneSnapshot.java
index 8980e28b59b4..028e937a9c2e 100644
--- a/hadoop-ozone/client/src/test/java/org/apache/hadoop/ozone/client/TestOzoneSnapshot.java
+++ b/hadoop-ozone/client/src/test/java/org/apache/hadoop/ozone/client/TestOzoneSnapshot.java
@@ -19,6 +19,7 @@
import static org.apache.hadoop.ozone.om.helpers.SnapshotInfo.SnapshotStatus.SNAPSHOT_ACTIVE;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import java.util.UUID;
@@ -40,7 +41,7 @@ private SnapshotInfo getMockedSnapshotInfo(UUID snapshotId) {
when(snapshotInfo.getSnapshotStatus()).thenReturn(SNAPSHOT_ACTIVE);
when(snapshotInfo.getSnapshotId()).thenReturn(snapshotId);
when(snapshotInfo.getSnapshotPath()).thenReturn("volume/bucket");
- when(snapshotInfo.getCheckpointDir()).thenReturn("checkpointDir");
+ when(snapshotInfo.getCheckpointDirName(eq(0))).thenReturn("checkpointDir");
when(snapshotInfo.getReferencedSize()).thenReturn(1000L);
when(snapshotInfo.getReferencedReplicatedSize()).thenReturn(3000L);
when(snapshotInfo.getExclusiveSize()).thenReturn(4000L);
diff --git a/hadoop-ozone/common/pom.xml b/hadoop-ozone/common/pom.xml
index 1ecafebb8b3f..afbb9c4f14f8 100644
--- a/hadoop-ozone/common/pom.xml
+++ b/hadoop-ozone/common/pom.xml
@@ -17,11 +17,11 @@
org.apache.ozone
hdds-hadoop-dependency-client
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
../../hadoop-hdds/hadoop-dependency-client
ozone-common
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
jar
Apache Ozone Common
Apache Ozone Common
diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/SnapshotInfo.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/SnapshotInfo.java
index cbc3709ea1e8..a26422cd81fb 100644
--- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/SnapshotInfo.java
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/SnapshotInfo.java
@@ -71,7 +71,6 @@ public final class SnapshotInfo implements Auditable, CopyObject {
private UUID pathPreviousSnapshotId;
private UUID globalPreviousSnapshotId;
private String snapshotPath; // snapshot mask
- private String checkpointDir;
/**
* RocksDB's transaction sequence number at the time of checkpoint creation.
*/
@@ -99,7 +98,6 @@ private SnapshotInfo(Builder b) {
this.pathPreviousSnapshotId = b.pathPreviousSnapshotId;
this.globalPreviousSnapshotId = b.globalPreviousSnapshotId;
this.snapshotPath = b.snapshotPath;
- this.checkpointDir = b.checkpointDir;
this.dbTxSequenceNumber = b.dbTxSequenceNumber;
this.deepClean = b.deepClean;
this.sstFiltered = b.sstFiltered;
@@ -150,10 +148,6 @@ public void setSnapshotPath(String snapshotPath) {
this.snapshotPath = snapshotPath;
}
- public void setCheckpointDir(String checkpointDir) {
- this.checkpointDir = checkpointDir;
- }
-
public boolean isDeepCleaned() {
return deepClean;
}
@@ -202,10 +196,6 @@ public String getSnapshotPath() {
return snapshotPath;
}
- public String getCheckpointDir() {
- return checkpointDir;
- }
-
public boolean isSstFiltered() {
return sstFiltered;
}
@@ -231,7 +221,6 @@ public SnapshotInfo.Builder toBuilder() {
.setPathPreviousSnapshotId(pathPreviousSnapshotId)
.setGlobalPreviousSnapshotId(globalPreviousSnapshotId)
.setSnapshotPath(snapshotPath)
- .setCheckpointDir(checkpointDir)
.setDbTxSequenceNumber(dbTxSequenceNumber)
.setDeepClean(deepClean)
.setSstFiltered(sstFiltered)
@@ -260,7 +249,6 @@ public static class Builder {
private UUID pathPreviousSnapshotId;
private UUID globalPreviousSnapshotId;
private String snapshotPath;
- private String checkpointDir;
private long dbTxSequenceNumber;
private boolean deepClean;
private boolean sstFiltered;
@@ -339,12 +327,6 @@ public Builder setSnapshotPath(String snapshotPath) {
return this;
}
- /** @param checkpointDir - Snapshot checkpoint directory. */
- public Builder setCheckpointDir(String checkpointDir) {
- this.checkpointDir = checkpointDir;
- return this;
- }
-
/** @param dbTxSequenceNumber - RDB latest transaction sequence number. */
public Builder setDbTxSequenceNumber(long dbTxSequenceNumber) {
this.dbTxSequenceNumber = dbTxSequenceNumber;
@@ -459,7 +441,6 @@ public OzoneManagerProtocolProtos.SnapshotInfo getProtobuf() {
}
sib.setSnapshotPath(snapshotPath)
- .setCheckpointDir(checkpointDir)
.setDbTxSequenceNumber(dbTxSequenceNumber)
.setDeepClean(deepClean);
return sib.build();
@@ -544,7 +525,6 @@ public static SnapshotInfo getFromProtobuf(
}
osib.setSnapshotPath(snapshotInfoProto.getSnapshotPath())
- .setCheckpointDir(snapshotInfoProto.getCheckpointDir())
.setDbTxSequenceNumber(snapshotInfoProto.getDbTxSequenceNumber());
return osib.build();
@@ -562,17 +542,20 @@ public Map toAuditMap() {
/**
* Get the name of the checkpoint directory.
*/
- public static String getCheckpointDirName(UUID snapshotId) {
+ public static String getCheckpointDirName(UUID snapshotId, int version) {
Objects.requireNonNull(snapshotId,
"SnapshotId is needed to create checkpoint directory");
- return OM_SNAPSHOT_SEPARATOR + snapshotId;
+ if (version == 0) {
+ return OM_SNAPSHOT_SEPARATOR + snapshotId;
+ }
+ return OM_SNAPSHOT_SEPARATOR + snapshotId + OM_SNAPSHOT_SEPARATOR + version;
}
/**
* Get the name of the checkpoint directory, (non-static).
*/
- public String getCheckpointDirName() {
- return getCheckpointDirName(getSnapshotId());
+ public String getCheckpointDirName(int version) {
+ return getCheckpointDirName(getSnapshotId(), version);
}
public long getDbTxSequenceNumber() {
@@ -703,10 +686,6 @@ public static SnapshotInfo newInstance(String volumeName,
.setBucketName(bucketName)
.setDeepClean(false)
.setDeepCleanedDeletedDir(false);
-
- if (snapshotId != null) {
- builder.setCheckpointDir(getCheckpointDirName(snapshotId));
- }
return builder.build();
}
@@ -729,7 +708,6 @@ public boolean equals(Object o) {
Objects.equals(
globalPreviousSnapshotId, that.globalPreviousSnapshotId) &&
snapshotPath.equals(that.snapshotPath) &&
- checkpointDir.equals(that.checkpointDir) &&
deepClean == that.deepClean &&
sstFiltered == that.sstFiltered &&
referencedSize == that.referencedSize &&
@@ -746,7 +724,7 @@ public int hashCode() {
return Objects.hash(snapshotId, name, volumeName, bucketName,
snapshotStatus,
creationTime, deletionTime, pathPreviousSnapshotId,
- globalPreviousSnapshotId, snapshotPath, checkpointDir,
+ globalPreviousSnapshotId, snapshotPath,
deepClean, sstFiltered,
referencedSize, referencedReplicatedSize,
exclusiveSize, exclusiveReplicatedSize, deepCleanedDeletedDir, lastTransactionInfo, createTransactionInfo);
@@ -773,7 +751,6 @@ public String toString() {
", pathPreviousSnapshotId: '" + pathPreviousSnapshotId + '\'' +
", globalPreviousSnapshotId: '" + globalPreviousSnapshotId + '\'' +
", snapshotPath: '" + snapshotPath + '\'' +
- ", checkpointDir: '" + checkpointDir + '\'' +
", dbTxSequenceNumber: '" + dbTxSequenceNumber + '\'' +
", deepClean: '" + deepClean + '\'' +
", sstFiltered: '" + sstFiltered + '\'' +
diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/lock/FlatResource.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/lock/FlatResource.java
index 73f8357252f2..45534197866d 100644
--- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/lock/FlatResource.java
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/lock/FlatResource.java
@@ -26,7 +26,12 @@ public enum FlatResource implements Resource {
// Background services lock on a Snapshot.
SNAPSHOT_GC_LOCK("SNAPSHOT_GC_LOCK"),
// Lock acquired on a Snapshot's RocksDB Handle.
- SNAPSHOT_DB_LOCK("SNAPSHOT_DB_LOCK");
+ SNAPSHOT_DB_LOCK("SNAPSHOT_DB_LOCK"),
+ // Lock acquired on a Snapshot's Local Data.
+ SNAPSHOT_LOCAL_DATA_LOCK("SNAPSHOT_LOCAL_DATA_LOCK"),
+ // Lock acquired on a Snapshot's RocksDB contents.
+ SNAPSHOT_DB_CONTENT_LOCK("SNAPSHOT_DB_CONTENT_LOCK");
+
private String name;
private IOzoneManagerLock.ResourceManager resourceManager;
diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OMAdminProtocol.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OMAdminProtocol.java
index 8588620074d1..cb6baf79fe7e 100644
--- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OMAdminProtocol.java
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OMAdminProtocol.java
@@ -45,4 +45,12 @@ public interface OMAdminProtocol extends Closeable {
* @param columnFamily
*/
void compactOMDB(String columnFamily) throws IOException;
+
+ /**
+ * Triggers the Snapshot Defragmentation Service to run immediately.
+ * @param noWait if true, return immediately without waiting for completion
+ * @return true if defragmentation completed successfully (when noWait is false),
+ * or if the task was triggered successfully (when noWait is true)
+ */
+ boolean triggerSnapshotDefrag(boolean noWait) throws IOException;
}
diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OMAdminProtocolClientSideImpl.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OMAdminProtocolClientSideImpl.java
index f7d22713b329..7ae8a30b73af 100644
--- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OMAdminProtocolClientSideImpl.java
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OMAdminProtocolClientSideImpl.java
@@ -47,6 +47,8 @@
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerAdminProtocolProtos.OMConfigurationRequest;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerAdminProtocolProtos.OMConfigurationResponse;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerAdminProtocolProtos.OMNodeInfo;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerAdminProtocolProtos.TriggerSnapshotDefragRequest;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerAdminProtocolProtos.TriggerSnapshotDefragResponse;
import org.apache.hadoop.security.UserGroupInformation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -232,6 +234,32 @@ public void compactOMDB(String columnFamily) throws IOException {
}
}
+ @Override
+ public boolean triggerSnapshotDefrag(boolean noWait) throws IOException {
+ TriggerSnapshotDefragRequest request = TriggerSnapshotDefragRequest.newBuilder()
+ .setNoWait(noWait)
+ .build();
+ TriggerSnapshotDefragResponse response;
+ try {
+ response = rpcProxy.triggerSnapshotDefrag(NULL_RPC_CONTROLLER, request);
+ } catch (ServiceException e) {
+ throw ProtobufHelper.getRemoteException(e);
+ }
+ if (!response.getSuccess()) {
+ throwException("Request to trigger snapshot defragmentation" +
+ ", sent to " + omPrintInfo + " failed with error: " +
+ response.getErrorMsg());
+ }
+ if (response.hasResult()) {
+ return response.getResult();
+ } else {
+ throwException("Missing result in TriggerSnapshotDefragResponse from " + omPrintInfo +
+ ". This likely indicates a server error.");
+ // Unreachable, required for compilation
+ return false;
+ }
+ }
+
private void throwException(String errorMsg)
throws IOException {
throw new IOException("Request Failed. Error: " + errorMsg);
diff --git a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/TestOmUtils.java b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/TestOmUtils.java
index b08c041fd56c..9aea06fd7969 100644
--- a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/TestOmUtils.java
+++ b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/TestOmUtils.java
@@ -31,6 +31,7 @@
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import java.io.File;
@@ -257,5 +258,72 @@ public void testGetActiveNonListenerOMNodeIdsFiltering() {
assertEquals(expected.size(), result.size());
assertTrue(result.containsAll(expected));
}
-}
+ @Test
+ void testGetOMEpoch() {
+ assertEquals(2, OmUtils.getOMEpoch());
+ assertEquals(OmUtils.EPOCH_WHEN_RATIS_ENABLED, OmUtils.getOMEpoch());
+ }
+
+ @Test
+ void testAddEpochToTxId() {
+ assertEquals(0L, OmUtils.addEpochToTxId(0, 0));
+ assertEquals(1L << 62, OmUtils.addEpochToTxId(1, 0));
+ assertEquals(2L << 62, OmUtils.addEpochToTxId(2, 0));
+ assertEquals(3L << 62, OmUtils.addEpochToTxId(3, 0));
+
+ long txId = 12345L;
+ long expected = (2L << 62) | (txId << 8);
+ assertEquals(expected, OmUtils.addEpochToTxId(2, txId));
+
+ long maxTxId = OmUtils.MAX_TRXN_ID;
+ long maxExpected = (2L << 62) | (maxTxId << 8);
+ assertEquals(maxExpected, OmUtils.addEpochToTxId(2, maxTxId));
+
+ // Verify bit structure
+ long result = OmUtils.addEpochToTxId(2, 0x123456789ABCDL);
+ assertEquals(2L, result >>> 62);
+ assertEquals(0x123456789ABCDL, (result & 0x3FFFFFFFFFFFFFFFL) >>> 8);
+ }
+
+ // Intentionally no tests for getTxIdFromObjectId(); this helper is not
+ // used in production paths and may be removed in the future.
+
+ @Test
+ void testGetObjectIdFromTxId() {
+ long txId = 12345L;
+ long epoch = 2L;
+ long expected = OmUtils.addEpochToTxId(epoch, txId);
+ assertEquals(expected, OmUtils.getObjectIdFromTxId(epoch, txId));
+
+ for (long e = 0; e <= 3; e++) {
+ long result = OmUtils.getObjectIdFromTxId(e, txId);
+ assertEquals(e, result >>> 62);
+ assertEquals(txId, (result & 0x3FFFFFFFFFFFFFFFL) >>> 8);
+ }
+
+ long maxTxId = OmUtils.MAX_TRXN_ID;
+ long maxResult = OmUtils.getObjectIdFromTxId(epoch, maxTxId);
+ assertEquals(epoch, maxResult >>> 62);
+ assertEquals(maxTxId, (maxResult & 0x3FFFFFFFFFFFFFFFL) >>> 8);
+ }
+
+ @Test
+ void testGetObjectIdFromTxIdValidation() {
+ long validTxId = OmUtils.MAX_TRXN_ID;
+ // Test valid case - should not throw exception
+ try {
+ OmUtils.getObjectIdFromTxId(2, validTxId);
+ } catch (Exception e) {
+ fail("Valid txId should not throw exception: " + e.getMessage());
+ }
+
+ long invalidTxId = (1L << 54) - 1; // MAX_TRXN_ID + 1
+ IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
+ () -> OmUtils.getObjectIdFromTxId(2, invalidTxId));
+ assertTrue(exception.getMessage().contains("TransactionID exceeds max limit"));
+ }
+
+ // Consistency checks between epoch and txId are covered by
+ // testAddEpochToTxId() and testGetObjectIdFromTxId().
+}
diff --git a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestOMNodeDetails.java b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestOMNodeDetails.java
new file mode 100644
index 000000000000..9a9951c20a29
--- /dev/null
+++ b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestOMNodeDetails.java
@@ -0,0 +1,429 @@
+/*
+ * 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.ozone.om.helpers;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.URL;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.ha.ConfUtils;
+import org.apache.hadoop.ozone.om.OMConfigKeys;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerAdminProtocolProtos.NodeState;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerAdminProtocolProtos.OMNodeInfo;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test OMNodeDetails.
+ */
+public class TestOMNodeDetails {
+
+ private static final String OM_SERVICE_ID = "om-service";
+ private static final String OM_NODE_ID = "om-01";
+ private static final String HOST_ADDRESS = "localhost";
+ private static final int RPC_PORT = 9862;
+ private static final int RATIS_PORT = 9873;
+ private static final String HTTP_ADDRESS = "0.0.0.0:9874";
+ private static final String HTTPS_ADDRESS = "0.0.0.0:9875";
+
+ /**
+ * Test builder with InetSocketAddress.
+ */
+ @Test
+ public void testBuilderWithInetSocketAddress() {
+ InetSocketAddress rpcAddr = new InetSocketAddress(HOST_ADDRESS, RPC_PORT);
+
+ OMNodeDetails nodeDetails = new OMNodeDetails.Builder()
+ .setOMServiceId(OM_SERVICE_ID)
+ .setOMNodeId(OM_NODE_ID)
+ .setRpcAddress(rpcAddr)
+ .setRatisPort(RATIS_PORT)
+ .setHttpAddress(HTTP_ADDRESS)
+ .setHttpsAddress(HTTPS_ADDRESS)
+ .setIsListener(false)
+ .build();
+
+ assertEquals(OM_SERVICE_ID, nodeDetails.getServiceId());
+ assertEquals(OM_NODE_ID, nodeDetails.getNodeId());
+ assertEquals(RPC_PORT, nodeDetails.getRpcPort());
+ assertEquals(RATIS_PORT, nodeDetails.getRatisPort());
+ assertEquals(HTTP_ADDRESS, nodeDetails.getHttpAddress());
+ assertEquals(HTTPS_ADDRESS, nodeDetails.getHttpsAddress());
+ assertEquals(HOST_ADDRESS, nodeDetails.getHostAddress());
+ }
+
+ /**
+ * Test builder with host address string.
+ */
+ @Test
+ public void testBuilderWithHostAddressString() {
+ OMNodeDetails nodeDetails = new OMNodeDetails.Builder()
+ .setOMServiceId(OM_SERVICE_ID)
+ .setOMNodeId(OM_NODE_ID)
+ .setHostAddress(HOST_ADDRESS)
+ .setRpcPort(RPC_PORT)
+ .setRatisPort(RATIS_PORT)
+ .setHttpAddress(HTTP_ADDRESS)
+ .setHttpsAddress(HTTPS_ADDRESS)
+ .build();
+
+ assertEquals(OM_SERVICE_ID, nodeDetails.getServiceId());
+ assertEquals(OM_NODE_ID, nodeDetails.getNodeId());
+ assertEquals(RPC_PORT, nodeDetails.getRpcPort());
+ assertEquals(RATIS_PORT, nodeDetails.getRatisPort());
+ }
+
+ /**
+ * Test isRatisListener flag.
+ */
+ @Test
+ public void testRatisListenerFlag() {
+ OMNodeDetails nonListener = new OMNodeDetails.Builder()
+ .setOMServiceId(OM_SERVICE_ID)
+ .setOMNodeId(OM_NODE_ID)
+ .setHostAddress(HOST_ADDRESS)
+ .setRpcPort(RPC_PORT)
+ .setRatisPort(RATIS_PORT)
+ .setHttpAddress(HTTP_ADDRESS)
+ .setHttpsAddress(HTTPS_ADDRESS)
+ .setIsListener(false)
+ .build();
+
+ assertFalse(nonListener.isRatisListener());
+
+ OMNodeDetails listener = new OMNodeDetails.Builder()
+ .setOMServiceId(OM_SERVICE_ID)
+ .setOMNodeId(OM_NODE_ID + "-listener")
+ .setHostAddress(HOST_ADDRESS)
+ .setRpcPort(RPC_PORT + 1)
+ .setRatisPort(RATIS_PORT + 1)
+ .setHttpAddress(HTTP_ADDRESS)
+ .setHttpsAddress(HTTPS_ADDRESS)
+ .setIsListener(true)
+ .build();
+
+ assertTrue(listener.isRatisListener());
+
+ nonListener.setRatisListener();
+ assertTrue(nonListener.isRatisListener());
+ }
+
+ /**
+ * Test decommissioned state.
+ */
+ @Test
+ public void testDecommissionedState() {
+ OMNodeDetails nodeDetails = new OMNodeDetails.Builder()
+ .setOMServiceId(OM_SERVICE_ID)
+ .setOMNodeId(OM_NODE_ID)
+ .setHostAddress(HOST_ADDRESS)
+ .setRpcPort(RPC_PORT)
+ .setRatisPort(RATIS_PORT)
+ .setHttpAddress(HTTP_ADDRESS)
+ .setHttpsAddress(HTTPS_ADDRESS)
+ .build();
+
+ assertFalse(nodeDetails.isDecommissioned());
+
+ nodeDetails.setDecommissioningState();
+ assertTrue(nodeDetails.isDecommissioned());
+ }
+
+ /**
+ * Test toString method.
+ */
+ @Test
+ public void testToString() {
+ OMNodeDetails nodeDetails = new OMNodeDetails.Builder()
+ .setOMServiceId(OM_SERVICE_ID)
+ .setOMNodeId(OM_NODE_ID)
+ .setHostAddress(HOST_ADDRESS)
+ .setRpcPort(RPC_PORT)
+ .setRatisPort(RATIS_PORT)
+ .setHttpAddress(HTTP_ADDRESS)
+ .setHttpsAddress(HTTPS_ADDRESS)
+ .setIsListener(true)
+ .build();
+
+ String result = nodeDetails.toString();
+ assertTrue(result.contains("omServiceId=" + OM_SERVICE_ID));
+ assertTrue(result.contains("omNodeId=" + OM_NODE_ID));
+ assertTrue(result.contains("rpcPort=" + RPC_PORT));
+ assertTrue(result.contains("ratisPort=" + RATIS_PORT));
+ assertTrue(result.contains("httpAddress=" + HTTP_ADDRESS));
+ assertTrue(result.contains("httpsAddress=" + HTTPS_ADDRESS));
+ assertTrue(result.contains("isListener=true"));
+ }
+
+ /**
+ * Test getOMPrintInfo method.
+ */
+ @Test
+ public void testGetOMPrintInfo() {
+ OMNodeDetails nodeDetails = new OMNodeDetails.Builder()
+ .setOMServiceId(OM_SERVICE_ID)
+ .setOMNodeId(OM_NODE_ID)
+ .setHostAddress(HOST_ADDRESS)
+ .setRpcPort(RPC_PORT)
+ .setRatisPort(RATIS_PORT)
+ .setHttpAddress(HTTP_ADDRESS)
+ .setHttpsAddress(HTTPS_ADDRESS)
+ .build();
+
+ String result = nodeDetails.getOMPrintInfo();
+ assertEquals(OM_NODE_ID + "[" + HOST_ADDRESS + ":" + RPC_PORT + "]", result);
+ }
+
+ /**
+ * Test getRpcPort method.
+ */
+ @Test
+ public void testGetRpcPort() {
+ OMNodeDetails nodeDetails = new OMNodeDetails.Builder()
+ .setOMServiceId(OM_SERVICE_ID)
+ .setOMNodeId(OM_NODE_ID)
+ .setHostAddress(HOST_ADDRESS)
+ .setRpcPort(RPC_PORT)
+ .setRatisPort(RATIS_PORT)
+ .setHttpAddress(HTTP_ADDRESS)
+ .setHttpsAddress(HTTPS_ADDRESS)
+ .build();
+
+ assertEquals(RPC_PORT, nodeDetails.getRpcPort());
+ }
+
+ /**
+ * Test protobuf conversion for active node.
+ */
+ @Test
+ public void testProtobufConversionActiveNode() {
+ OMNodeDetails nodeDetails = new OMNodeDetails.Builder()
+ .setOMServiceId(OM_SERVICE_ID)
+ .setOMNodeId(OM_NODE_ID)
+ .setHostAddress(HOST_ADDRESS)
+ .setRpcPort(RPC_PORT)
+ .setRatisPort(RATIS_PORT)
+ .setHttpAddress(HTTP_ADDRESS)
+ .setHttpsAddress(HTTPS_ADDRESS)
+ .setIsListener(false)
+ .build();
+
+ OMNodeInfo protobuf = nodeDetails.getProtobuf();
+
+ assertEquals(OM_NODE_ID, protobuf.getNodeID());
+ assertEquals(HOST_ADDRESS, protobuf.getHostAddress());
+ assertEquals(RPC_PORT, protobuf.getRpcPort());
+ assertEquals(RATIS_PORT, protobuf.getRatisPort());
+ assertEquals(NodeState.ACTIVE, protobuf.getNodeState());
+ assertFalse(protobuf.getIsListener());
+
+ OMNodeDetails restored = OMNodeDetails.getFromProtobuf(protobuf);
+ assertEquals(nodeDetails.getNodeId(), restored.getNodeId());
+ assertEquals(nodeDetails.getHostAddress(), restored.getHostAddress());
+ assertEquals(nodeDetails.getRpcPort(), restored.getRpcPort());
+ assertEquals(nodeDetails.getRatisPort(), restored.getRatisPort());
+ assertEquals(nodeDetails.isDecommissioned(), restored.isDecommissioned());
+ assertEquals(nodeDetails.isRatisListener(), restored.isRatisListener());
+ }
+
+ /**
+ * Test protobuf conversion for decommissioned node.
+ */
+ @Test
+ public void testProtobufConversionDecommissionedNode() {
+ OMNodeDetails nodeDetails = new OMNodeDetails.Builder()
+ .setOMServiceId(OM_SERVICE_ID)
+ .setOMNodeId(OM_NODE_ID)
+ .setHostAddress(HOST_ADDRESS)
+ .setRpcPort(RPC_PORT)
+ .setRatisPort(RATIS_PORT)
+ .setHttpAddress(HTTP_ADDRESS)
+ .setHttpsAddress(HTTPS_ADDRESS)
+ .build();
+
+ nodeDetails.setDecommissioningState();
+
+ OMNodeInfo protobuf = nodeDetails.getProtobuf();
+ assertEquals(NodeState.DECOMMISSIONED, protobuf.getNodeState());
+
+ OMNodeDetails restored = OMNodeDetails.getFromProtobuf(protobuf);
+ assertTrue(restored.isDecommissioned());
+ }
+
+ /**
+ * Test protobuf conversion for listener node.
+ */
+ @Test
+ public void testProtobufConversionListenerNode() {
+ OMNodeDetails nodeDetails = new OMNodeDetails.Builder()
+ .setOMServiceId(OM_SERVICE_ID)
+ .setOMNodeId(OM_NODE_ID)
+ .setHostAddress(HOST_ADDRESS)
+ .setRpcPort(RPC_PORT)
+ .setRatisPort(RATIS_PORT)
+ .setHttpAddress(HTTP_ADDRESS)
+ .setHttpsAddress(HTTPS_ADDRESS)
+ .setIsListener(true)
+ .build();
+
+ OMNodeInfo protobuf = nodeDetails.getProtobuf();
+ assertTrue(protobuf.getIsListener());
+
+ OMNodeDetails restored = OMNodeDetails.getFromProtobuf(protobuf);
+ assertTrue(restored.isRatisListener());
+ }
+
+ /**
+ * Test getOMDBCheckpointEndpointUrl for HTTP.
+ */
+ @Test
+ public void testGetOMDBCheckpointEndpointUrlHttp() throws IOException {
+ OMNodeDetails nodeDetails = new OMNodeDetails.Builder()
+ .setOMServiceId(OM_SERVICE_ID)
+ .setOMNodeId(OM_NODE_ID)
+ .setHostAddress(HOST_ADDRESS)
+ .setRpcPort(RPC_PORT)
+ .setRatisPort(RATIS_PORT)
+ .setHttpAddress(HOST_ADDRESS + ":9874")
+ .setHttpsAddress(HOST_ADDRESS + ":9875")
+ .build();
+
+ URL urlWithoutFlush = nodeDetails.getOMDBCheckpointEndpointUrl(true, false);
+ assertNotNull(urlWithoutFlush);
+ assertEquals("http", urlWithoutFlush.getProtocol());
+ assertEquals(HOST_ADDRESS + ":9874", urlWithoutFlush.getAuthority());
+ assertNotNull(urlWithoutFlush.getQuery());
+ assertTrue(urlWithoutFlush.getQuery().contains("flushBeforeCheckpoint=false"));
+
+ URL urlWithFlush = nodeDetails.getOMDBCheckpointEndpointUrl(true, true);
+ assertNotNull(urlWithFlush);
+ assertTrue(urlWithFlush.getQuery().contains("flushBeforeCheckpoint=true"));
+ assertTrue(urlWithFlush.getQuery().contains("includeSnapshotData=true"));
+ }
+
+ /**
+ * Test getOMDBCheckpointEndpointUrl for HTTPS.
+ */
+ @Test
+ public void testGetOMDBCheckpointEndpointUrlHttps() throws IOException {
+ OMNodeDetails nodeDetails = new OMNodeDetails.Builder()
+ .setOMServiceId(OM_SERVICE_ID)
+ .setOMNodeId(OM_NODE_ID)
+ .setHostAddress(HOST_ADDRESS)
+ .setRpcPort(RPC_PORT)
+ .setRatisPort(RATIS_PORT)
+ .setHttpAddress(HOST_ADDRESS + ":9874")
+ .setHttpsAddress(HOST_ADDRESS + ":9875")
+ .build();
+
+ URL url = nodeDetails.getOMDBCheckpointEndpointUrl(false, false);
+ assertNotNull(url);
+ assertEquals("https", url.getProtocol());
+ assertEquals(HOST_ADDRESS + ":9875", url.getAuthority());
+ }
+
+ /**
+ * Test getOMNodeAddressFromConf.
+ */
+ @Test
+ public void testGetOMNodeAddressFromConf() {
+ OzoneConfiguration conf = new OzoneConfiguration();
+
+ String configKey = "ozone.om.address.om-service.om-01";
+ String expectedAddress = "localhost:9862";
+ conf.set(configKey, expectedAddress);
+
+ String address = OMNodeDetails.getOMNodeAddressFromConf(conf, "om-service", "om-01");
+ assertEquals(expectedAddress, address);
+
+ String missingAddress = OMNodeDetails.getOMNodeAddressFromConf(conf, "nonexistent", "node");
+ assertNull(missingAddress);
+ }
+
+ /**
+ * Test getOMNodeDetailsFromConf with valid configuration.
+ */
+ @Test
+ public void testGetOMNodeDetailsFromConfValid() throws Exception {
+ OzoneConfiguration conf = new OzoneConfiguration();
+
+ String serviceId = "om-service";
+ String nodeId = "om-01";
+
+ conf.set(ConfUtils.addKeySuffixes(OMConfigKeys.OZONE_OM_ADDRESS_KEY, serviceId, nodeId),
+ "localhost:9862");
+ conf.set(ConfUtils.addKeySuffixes(OMConfigKeys.OZONE_OM_RATIS_PORT_KEY, serviceId, nodeId),
+ "9873");
+ conf.set(ConfUtils.addKeySuffixes(OMConfigKeys.OZONE_OM_HTTP_ADDRESS_KEY, serviceId, nodeId),
+ "localhost:9874");
+ conf.set(ConfUtils.addKeySuffixes(OMConfigKeys.OZONE_OM_HTTPS_ADDRESS_KEY, serviceId, nodeId),
+ "localhost:9875");
+
+ OMNodeDetails nodeDetails = OMNodeDetails.getOMNodeDetailsFromConf(conf, serviceId, nodeId);
+
+ assertNotNull(nodeDetails);
+ assertEquals(serviceId, nodeDetails.getServiceId());
+ assertEquals(nodeId, nodeDetails.getNodeId());
+ assertEquals(9862, nodeDetails.getRpcPort());
+ assertEquals(9873, nodeDetails.getRatisPort());
+ }
+
+ /**
+ * Test getOMNodeDetailsFromConf with missing configuration.
+ */
+ @Test
+ public void testGetOMNodeDetailsFromConfMissing() throws Exception {
+ OzoneConfiguration conf = new OzoneConfiguration();
+
+ OMNodeDetails nodeDetails = OMNodeDetails.getOMNodeDetailsFromConf(conf, "nonexistent", "node");
+ assertNull(nodeDetails);
+
+ String serviceId = "om-service";
+ String nodeId = "om-01";
+
+ nodeDetails = OMNodeDetails.getOMNodeDetailsFromConf(conf, serviceId, null);
+ assertNull(nodeDetails);
+ nodeDetails = OMNodeDetails.getOMNodeDetailsFromConf(conf, null, nodeId);
+ assertNull(nodeDetails);
+ }
+
+ /**
+ * Test setRatisAddress in builder.
+ */
+ @Test
+ public void testSetRatisAddress() {
+ InetSocketAddress ratisAddr = new InetSocketAddress("192.168.1.100", 9873);
+
+ OMNodeDetails nodeDetails = new OMNodeDetails.Builder()
+ .setOMServiceId(OM_SERVICE_ID)
+ .setOMNodeId(OM_NODE_ID)
+ .setRatisAddress(ratisAddr)
+ .setRpcPort(RPC_PORT)
+ .setHttpAddress(HTTP_ADDRESS)
+ .setHttpsAddress(HTTPS_ADDRESS)
+ .build();
+
+ assertEquals("192.168.1.100", nodeDetails.getHostAddress());
+ assertEquals(9873, nodeDetails.getRatisPort());
+ }
+}
diff --git a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestOmSnapshotInfo.java b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestOmSnapshotInfo.java
index 98cc035b3c07..e7695debd619 100644
--- a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestOmSnapshotInfo.java
+++ b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestOmSnapshotInfo.java
@@ -45,7 +45,6 @@ public class TestOmSnapshotInfo {
private static final UUID GLOBAL_PREVIOUS_SNAPSHOT_ID =
PATH_PREVIOUS_SNAPSHOT_ID;
private static final String SNAPSHOT_PATH = "test/path";
- private static final String CHECKPOINT_DIR = "checkpoint.testdir";
private static final long DB_TX_SEQUENCE_NUMBER = 12345L;
private SnapshotInfo createSnapshotInfo() {
@@ -60,7 +59,6 @@ private SnapshotInfo createSnapshotInfo() {
.setPathPreviousSnapshotId(PATH_PREVIOUS_SNAPSHOT_ID)
.setGlobalPreviousSnapshotId(GLOBAL_PREVIOUS_SNAPSHOT_ID)
.setSnapshotPath(SNAPSHOT_PATH)
- .setCheckpointDir(CHECKPOINT_DIR)
.setDbTxSequenceNumber(DB_TX_SEQUENCE_NUMBER)
.setDeepClean(false)
.setSstFiltered(false)
@@ -86,7 +84,6 @@ private OzoneManagerProtocolProtos.SnapshotInfo createSnapshotInfoProto() {
.setPathPreviousSnapshotID(toProtobuf(PATH_PREVIOUS_SNAPSHOT_ID))
.setGlobalPreviousSnapshotID(toProtobuf(GLOBAL_PREVIOUS_SNAPSHOT_ID))
.setSnapshotPath(SNAPSHOT_PATH)
- .setCheckpointDir(CHECKPOINT_DIR)
.setDbTxSequenceNumber(DB_TX_SEQUENCE_NUMBER)
.setDeepClean(false)
.setSstFiltered(false)
diff --git a/hadoop-ozone/csi/pom.xml b/hadoop-ozone/csi/pom.xml
index 511c9b08cea2..9c44a8809853 100644
--- a/hadoop-ozone/csi/pom.xml
+++ b/hadoop-ozone/csi/pom.xml
@@ -17,10 +17,10 @@
org.apache.ozone
ozone
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
ozone-csi
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
jar
Apache Ozone CSI service
Apache Ozone CSI service
diff --git a/hadoop-ozone/datanode/pom.xml b/hadoop-ozone/datanode/pom.xml
index 60c3bfac2ae4..a91604198157 100644
--- a/hadoop-ozone/datanode/pom.xml
+++ b/hadoop-ozone/datanode/pom.xml
@@ -17,10 +17,10 @@
org.apache.ozone
ozone
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
ozone-datanode
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
jar
Apache Ozone Datanode
diff --git a/hadoop-ozone/dist/pom.xml b/hadoop-ozone/dist/pom.xml
index b5a62d4835d9..ceb45e8c9860 100644
--- a/hadoop-ozone/dist/pom.xml
+++ b/hadoop-ozone/dist/pom.xml
@@ -17,10 +17,10 @@
org.apache.ozone
ozone
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
ozone-dist
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
jar
Apache Ozone Distribution
diff --git a/hadoop-ozone/dist/src/main/smoketest/s3/mpu_lib.robot b/hadoop-ozone/dist/src/main/smoketest/s3/mpu_lib.robot
index 0aaa0affec1d..fed0c539a074 100644
--- a/hadoop-ozone/dist/src/main/smoketest/s3/mpu_lib.robot
+++ b/hadoop-ozone/dist/src/main/smoketest/s3/mpu_lib.robot
@@ -42,6 +42,7 @@ Upload MPU part
IF '${expected_rc}' == '0'
Should contain ${result} ETag
${etag} = Execute echo '${result}' | jq -r '.ETag'
+ ${etag} = Replace String ${etag} \" ${EMPTY}
${md5sum} = Execute md5sum ${file} | awk '{print $1}'
Should Be Equal As Strings ${etag} ${md5sum}
RETURN ${etag}
diff --git a/hadoop-ozone/fault-injection-test/mini-chaos-tests/pom.xml b/hadoop-ozone/fault-injection-test/mini-chaos-tests/pom.xml
index bf7a1636749e..97d3076e889e 100644
--- a/hadoop-ozone/fault-injection-test/mini-chaos-tests/pom.xml
+++ b/hadoop-ozone/fault-injection-test/mini-chaos-tests/pom.xml
@@ -17,11 +17,11 @@
org.apache.ozone
ozone-fault-injection-test
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
mini-chaos-tests
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
Apache Ozone Mini Ozone Chaos Tests
Apache Ozone Mini Ozone Chaos Tests
diff --git a/hadoop-ozone/fault-injection-test/network-tests/pom.xml b/hadoop-ozone/fault-injection-test/network-tests/pom.xml
index 878efae01349..75b265ee0aad 100644
--- a/hadoop-ozone/fault-injection-test/network-tests/pom.xml
+++ b/hadoop-ozone/fault-injection-test/network-tests/pom.xml
@@ -17,7 +17,7 @@
org.apache.ozone
ozone-fault-injection-test
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
ozone-network-tests
jar
diff --git a/hadoop-ozone/fault-injection-test/pom.xml b/hadoop-ozone/fault-injection-test/pom.xml
index 3ba13e168546..1651e7e1529e 100644
--- a/hadoop-ozone/fault-injection-test/pom.xml
+++ b/hadoop-ozone/fault-injection-test/pom.xml
@@ -17,10 +17,10 @@
org.apache.ozone
ozone
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
ozone-fault-injection-test
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
pom
Apache Ozone Fault Injection Tests
Apache Ozone Fault Injection Tests
diff --git a/hadoop-ozone/freon/pom.xml b/hadoop-ozone/freon/pom.xml
index bceadd99f8ca..c0cf09fdb34c 100644
--- a/hadoop-ozone/freon/pom.xml
+++ b/hadoop-ozone/freon/pom.xml
@@ -17,10 +17,10 @@
org.apache.ozone
ozone
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
ozone-freon
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
jar
Apache Ozone Freon
Apache Ozone Freon
diff --git a/hadoop-ozone/httpfsgateway/pom.xml b/hadoop-ozone/httpfsgateway/pom.xml
index 058b60c5a0f4..d4df054b4da4 100644
--- a/hadoop-ozone/httpfsgateway/pom.xml
+++ b/hadoop-ozone/httpfsgateway/pom.xml
@@ -19,10 +19,10 @@
org.apache.ozone
ozone
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
ozone-httpfsgateway
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
jar
Apache Ozone HttpFS
diff --git a/hadoop-ozone/insight/pom.xml b/hadoop-ozone/insight/pom.xml
index 471f9e09a729..fd64cd841f6e 100644
--- a/hadoop-ozone/insight/pom.xml
+++ b/hadoop-ozone/insight/pom.xml
@@ -17,10 +17,10 @@
org.apache.ozone
ozone
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
ozone-insight
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
jar
Apache Ozone Insight Tool
Apache Ozone Insight Tool
diff --git a/hadoop-ozone/integration-test-recon/pom.xml b/hadoop-ozone/integration-test-recon/pom.xml
index 45d8a3ee2486..a26835c4a6af 100644
--- a/hadoop-ozone/integration-test-recon/pom.xml
+++ b/hadoop-ozone/integration-test-recon/pom.xml
@@ -17,10 +17,10 @@
org.apache.ozone
ozone
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
ozone-integration-test-recon
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
jar
Apache Ozone Recon Integration Tests
Apache Ozone Integration Tests with Recon
diff --git a/hadoop-ozone/integration-test-s3/pom.xml b/hadoop-ozone/integration-test-s3/pom.xml
index 1c41eee0d6d0..30eb3db975f0 100644
--- a/hadoop-ozone/integration-test-s3/pom.xml
+++ b/hadoop-ozone/integration-test-s3/pom.xml
@@ -17,10 +17,10 @@
org.apache.ozone
ozone
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
ozone-integration-test-s3
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
jar
Apache Ozone S3 Integration Tests
Apache Ozone Integration Tests with S3 Gateway
diff --git a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java
index 00b482629932..016ab60537fb 100644
--- a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java
+++ b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java
@@ -21,6 +21,7 @@
import static org.apache.hadoop.ozone.s3.awssdk.S3SDKTestUtils.calculateDigest;
import static org.apache.hadoop.ozone.s3.awssdk.S3SDKTestUtils.createFile;
import static org.apache.hadoop.ozone.s3.util.S3Consts.CUSTOM_METADATA_HEADER_PREFIX;
+import static org.apache.hadoop.ozone.s3.util.S3Utils.stripQuotes;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -1231,7 +1232,7 @@ private void completeMPU(String keyName, String uploadId, List complet
for (PartETag part : completedParts) {
completionXml.append(" \n");
completionXml.append(" ").append(part.getPartNumber()).append("\n");
- completionXml.append(" ").append(part.getETag()).append("\n");
+ completionXml.append(" ").append(stripQuotes(part.getETag())).append("\n");
completionXml.append(" \n");
}
completionXml.append("");
diff --git a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java
index 925e2e75df5a..119849281acc 100644
--- a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java
+++ b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java
@@ -20,6 +20,7 @@
import static org.apache.hadoop.ozone.OzoneConsts.MB;
import static org.apache.hadoop.ozone.s3.awssdk.S3SDKTestUtils.calculateDigest;
import static org.apache.hadoop.ozone.s3.awssdk.S3SDKTestUtils.createFile;
+import static org.apache.hadoop.ozone.s3.util.S3Utils.stripQuotes;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -970,7 +971,7 @@ private String buildCompleteMultipartUploadXml(List parts) {
for (CompletedPart part : parts) {
xml.append(" \n");
xml.append(" ").append(part.partNumber()).append("\n");
- xml.append(" ").append(part.eTag()).append("\n");
+ xml.append(" ").append(stripQuotes(part.eTag())).append("\n");
xml.append(" \n");
}
xml.append("");
@@ -1142,11 +1143,11 @@ private List uploadParts(String bucketName, String key, String up
RequestBody.fromByteBuffer(bb));
assertEquals(DatatypeConverter.printHexBinary(
- calculateDigest(fileInputStream, 0, partSize)).toLowerCase(), partResponse.eTag());
+ calculateDigest(fileInputStream, 0, partSize)).toLowerCase(), stripQuotes(partResponse.eTag()));
CompletedPart part = CompletedPart.builder()
.partNumber(partNumber)
- .eTag(partResponse.eTag())
+ .eTag(stripQuotes(partResponse.eTag()))
.build();
completedParts.add(part);
@@ -1643,7 +1644,7 @@ public void testCompleteMultipartUpload() {
CompletedMultipartUpload completedUpload = CompletedMultipartUpload.builder()
.parts(
- CompletedPart.builder().partNumber(1).eTag(uploadPartResponse.eTag()).build()
+ CompletedPart.builder().partNumber(1).eTag(stripQuotes(uploadPartResponse.eTag())).build()
).build();
diff --git a/hadoop-ozone/integration-test/pom.xml b/hadoop-ozone/integration-test/pom.xml
index d70cea608970..df9da45b3b6d 100644
--- a/hadoop-ozone/integration-test/pom.xml
+++ b/hadoop-ozone/integration-test/pom.xml
@@ -17,10 +17,10 @@
org.apache.ozone
ozone
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
ozone-integration-test
- 2.1.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
jar
Apache Ozone Integration Tests
Apache Ozone Integration Tests
diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestOzoneFsSnapshot.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestOzoneFsSnapshot.java
index d02319a4cab6..6a97796af32b 100644
--- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestOzoneFsSnapshot.java
+++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestOzoneFsSnapshot.java
@@ -547,7 +547,7 @@ private String createSnapshot() throws Exception {
SnapshotInfo snapshotInfo = ozoneManager.getMetadataManager()
.getSnapshotInfoTable()
.get(SnapshotInfo.getTableKey(VOLUME, BUCKET, snapshotName));
- String snapshotDirName = getSnapshotPath(conf, snapshotInfo) +
+ String snapshotDirName = getSnapshotPath(conf, snapshotInfo, 0) +
OM_KEY_PREFIX + "CURRENT";
GenericTestUtils.waitFor(() -> new File(snapshotDirName).exists(),
1000, 100000);
diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/TestStorageContainerManager.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/TestStorageContainerManager.java
index e407e30bffc6..7276dc871eac 100644
--- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/TestStorageContainerManager.java
+++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/TestStorageContainerManager.java
@@ -25,6 +25,7 @@
import static org.apache.hadoop.hdds.scm.HddsTestUtils.mockRemoteUser;
import static org.apache.hadoop.hdds.scm.HddsWhiteboxTestUtils.setInternalState;
import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_BLOCK_DELETING_SERVICE_INTERVAL;
+import static org.apache.hadoop.ozone.common.BlockGroup.SIZE_NOT_AVAILABLE;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -64,11 +65,13 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang3.RandomUtils;
import org.apache.hadoop.fs.FileUtil;
import org.apache.hadoop.hdds.HddsConfigKeys;
import org.apache.hadoop.hdds.HddsUtils;
+import org.apache.hadoop.hdds.client.BlockID;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.protocol.DatanodeDetails;
import org.apache.hadoop.hdds.protocol.DatanodeID;
@@ -118,6 +121,7 @@
import org.apache.hadoop.ozone.OzoneConsts;
import org.apache.hadoop.ozone.OzoneTestUtils;
import org.apache.hadoop.ozone.TestDataUtil;
+import org.apache.hadoop.ozone.common.DeletedBlock;
import org.apache.hadoop.ozone.container.ContainerTestHelper;
import org.apache.hadoop.ozone.container.common.helpers.BlockData;
import org.apache.hadoop.ozone.container.common.interfaces.DBHandle;
@@ -267,7 +271,7 @@ private void testBlockDeletionTransactions(MiniOzoneCluster cluster) throws Exce
OzoneTestUtils.closeContainers(keyInfo.getKeyLocationVersions(),
cluster.getStorageContainerManager());
}
- Map> containerBlocks = createDeleteTXLog(
+ Map> containerBlocks = createDeleteTXLog(
cluster.getStorageContainerManager(),
delLog, keyLocations, cluster);
@@ -285,10 +289,12 @@ private void testBlockDeletionTransactions(MiniOzoneCluster cluster) throws Exce
// but unknown block IDs.
for (Long containerID : containerBlocks.keySet()) {
// Add 2 TXs per container.
- Map> deletedBlocks = new HashMap<>();
- List blocks = new ArrayList<>();
- blocks.add(RandomUtils.secure().randomLong());
- blocks.add(RandomUtils.secure().randomLong());
+ Map> deletedBlocks = new HashMap<>();
+ List blocks = new ArrayList<>();
+ blocks.add(new DeletedBlock(new BlockID(containerID, RandomUtils.secure().randomLong()),
+ SIZE_NOT_AVAILABLE, SIZE_NOT_AVAILABLE));
+ blocks.add(new DeletedBlock(new BlockID(containerID, RandomUtils.secure().randomLong()),
+ SIZE_NOT_AVAILABLE, SIZE_NOT_AVAILABLE));
deletedBlocks.put(containerID, blocks);
addTransactions(cluster.getStorageContainerManager(), delLog,
deletedBlocks);
@@ -464,7 +470,7 @@ public void testBlockDeletingThrottling() throws Exception {
}
}
- private Map> createDeleteTXLog(
+ private Map