diff --git a/hadoop-hdds/common/src/main/resources/ozone-default.xml b/hadoop-hdds/common/src/main/resources/ozone-default.xml
index 8c8ae634e2ed..e288f5ff405b 100644
--- a/hadoop-hdds/common/src/main/resources/ozone-default.xml
+++ b/hadoop-hdds/common/src/main/resources/ozone-default.xml
@@ -491,6 +491,15 @@
and DataNode.
+
+ ozone.snapshot.key.deleting.limit.per.task
+ 20000
+ OM, PERFORMANCE
+
+ The maximum number of deleted keys to be scanned by Snapshot
+ Deleting Service per snapshot run.
+
+
ozone.om.service.ids
diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
index 249bc48e844c..e09025a5c37c 100644
--- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
@@ -309,6 +309,7 @@ public static boolean isReadOnly(
case CreateSnapshot:
case DeleteSnapshot:
case SnapshotMoveDeletedKeys:
+ case SnapshotPurge:
return false;
default:
LOG.error("CmdType {} is not categorized as readOnly or not.", cmdType);
diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/OMConfigKeys.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/OMConfigKeys.java
index 92a2150a527a..937835fdb774 100644
--- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/OMConfigKeys.java
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/OMConfigKeys.java
@@ -85,6 +85,10 @@ private OMConfigKeys() {
public static final String OZONE_KEY_DELETING_LIMIT_PER_TASK =
"ozone.key.deleting.limit.per.task";
public static final int OZONE_KEY_DELETING_LIMIT_PER_TASK_DEFAULT = 20000;
+ public static final String OZONE_SNAPSHOT_KEY_DELETING_LIMIT_PER_TASK =
+ "ozone.snapshot.key.deleting.limit.per.task";
+ public static final int OZONE_SNAPSHOT_KEY_DELETING_LIMIT_PER_TASK_DEFAULT
+ = 20000;
public static final String OZONE_OM_OPEN_KEY_CLEANUP_SERVICE_INTERVAL =
"ozone.om.open.key.cleanup.service.interval";
diff --git a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
index 8a70f64361eb..1492233a1dae 100644
--- a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
+++ b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
@@ -133,6 +133,7 @@ enum Type {
SnapshotMoveDeletedKeys = 116;
TransferLeadership = 117;
+ SnapshotPurge = 118;
}
message OMRequest {
@@ -248,6 +249,7 @@ message OMRequest {
optional SnapshotMoveDeletedKeysRequest SnapshotMoveDeletedKeysRequest = 116;
optional hdds.TransferLeadershipRequestProto TransferOmLeadershipRequest = 117;
+ optional SnapshotPurgeRequest SnapshotPurgeRequest = 118;
}
@@ -357,6 +359,7 @@ message OMResponse {
optional SnapshotMoveDeletedKeysResponse SnapshotMoveDeletedKeysResponse = 116;
optional hdds.TransferLeadershipResponseProto TransferOmLeadershipResponse = 117;
+ optional SnapshotPurgeResponse SnapshotPurgeResponse = 118;
}
enum Status {
@@ -1717,6 +1720,10 @@ message SnapshotMoveKeyInfos {
repeated KeyInfo keyInfos = 2;
}
+message SnapshotPurgeRequest {
+ repeated string snapshotDBKeys = 1;
+}
+
message DeleteTenantRequest {
optional string tenantId = 1;
}
@@ -1783,6 +1790,10 @@ message SnapshotMoveDeletedKeysResponse {
}
+message SnapshotPurgeResponse {
+
+}
+
message SnapshotDiffReportProto {
optional string volumeName = 1;
optional string bucketName = 2;
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataManagerImpl.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataManagerImpl.java
index ede9b5893a32..30eb7462299c 100644
--- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataManagerImpl.java
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataManagerImpl.java
@@ -292,6 +292,7 @@ public class OmMetadataManagerImpl implements OMMetadataManager,
private Map tableMap = new HashMap<>();
private List tableCacheMetrics = new LinkedList<>();
+ private SnapshotChainManager snapshotChainManager;
public OmMetadataManagerImpl(OzoneConfiguration conf) throws IOException {
this.lock = new OzoneManagerLock(conf);
@@ -464,6 +465,8 @@ public void start(OzoneConfiguration configuration) throws IOException {
initializeOmTables(true);
}
+
+ snapshotChainManager = new SnapshotChainManager(this);
}
public static DBStore loadDB(OzoneConfiguration configuration, File metaDir)
@@ -1632,6 +1635,15 @@ public Table getRenamedKeyTable() {
return renamedKeyTable;
}
+ /**
+ * Get Snapshot Chain Manager.
+ *
+ * @return SnapshotChainManager.
+ */
+ public SnapshotChainManager getSnapshotChainManager() {
+ return snapshotChainManager;
+ }
+
/**
* Update store used by subclass.
*
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
index 35361ef4bcdd..ac4c71657949 100644
--- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
@@ -452,7 +452,6 @@ private enum State {
// This metadata reader points to the active filesystem
private OmMetadataReader omMetadataReader;
private OmSnapshotManager omSnapshotManager;
- private SnapshotChainManager snapshotChainManager;
/** A list of property that are reconfigurable at runtime. */
private final SortedSet reconfigurableProperties =
@@ -770,7 +769,6 @@ private void instantiateServices(boolean withNewSnapshot) throws IOException {
omMetadataReader = new OmMetadataReader(keyManager, prefixManager,
this, LOG, AUDIT, metrics);
omSnapshotManager = new OmSnapshotManager(this);
- snapshotChainManager = new SnapshotChainManager(metadataManager);
// Snapshot metrics
updateActiveSnapshotMetrics();
@@ -1510,15 +1508,6 @@ public OmSnapshotManager getOmSnapshotManager() {
return omSnapshotManager;
}
- /**
- * Get Snapshot Chain Manager.
- *
- * @return SnapshotChainManager.
- */
- public SnapshotChainManager getSnapshotChainManager() {
- return snapshotChainManager;
- }
-
/**
* Get metadata manager.
*
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SnapshotChainManager.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SnapshotChainManager.java
index 0df80d299271..ff637358d0b5 100644
--- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SnapshotChainManager.java
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SnapshotChainManager.java
@@ -49,7 +49,7 @@ public class SnapshotChainManager {
snapshotChainPath;
private Map latestPathSnapshotID;
private String latestGlobalSnapshotID;
- private Map snapshotPathToTableKey;
+ private Map snapshotIdToTableKey;
private static final Logger LOG =
LoggerFactory.getLogger(SnapshotChainManager.class);
@@ -58,7 +58,7 @@ public SnapshotChainManager(OMMetadataManager metadataManager)
snapshotChainGlobal = new LinkedHashMap<>();
snapshotChainPath = new HashMap<>();
latestPathSnapshotID = new HashMap<>();
- snapshotPathToTableKey = new HashMap<>();
+ snapshotIdToTableKey = new HashMap<>();
latestGlobalSnapshotID = null;
loadFromSnapshotInfoTable(metadataManager);
}
@@ -100,8 +100,7 @@ private void addSnapshotGlobal(String snapshotID,
*/
private void addSnapshotPath(String snapshotPath,
String snapshotID,
- String prevPathID,
- String snapTableKey) throws IOException {
+ String prevPathID) throws IOException {
// set previous snapshotID to null if it is "" for
// internal in-mem structure
if (prevPathID != null && prevPathID.isEmpty()) {
@@ -139,8 +138,6 @@ private void addSnapshotPath(String snapshotPath,
.put(snapshotID,
new SnapshotChainInfo(snapshotID, prevPathID, null));
- // store snapshot ID to snapshot DB table key in the map
- snapshotPathToTableKey.put(snapshotID, snapTableKey);
// set state variable latestPath snapshot entry to this snapshotID
latestPathSnapshotID.put(snapshotPath, snapshotID);
};
@@ -272,7 +269,7 @@ private void loadFromSnapshotInfoTable(OMMetadataManager metadataManager)
snapshotChainGlobal.clear();
snapshotChainPath.clear();
latestPathSnapshotID.clear();
- snapshotPathToTableKey.clear();
+ snapshotIdToTableKey.clear();
while (keyIter.hasNext()) {
kv = keyIter.next();
@@ -292,8 +289,9 @@ public void addSnapshot(SnapshotInfo sinfo) throws IOException {
sinfo.getGlobalPreviousSnapshotID());
addSnapshotPath(sinfo.getSnapshotPath(),
sinfo.getSnapshotID(),
- sinfo.getPathPreviousSnapshotID(),
- sinfo.getTableKey());
+ sinfo.getPathPreviousSnapshotID());
+ // store snapshot ID to snapshot DB table key in the map
+ snapshotIdToTableKey.put(sinfo.getSnapshotID(), sinfo.getTableKey());
}
/**
@@ -304,9 +302,12 @@ public void addSnapshot(SnapshotInfo sinfo) throws IOException {
public boolean deleteSnapshot(SnapshotInfo sinfo) throws IOException {
boolean status;
- status = deleteSnapshotGlobal(sinfo.getSnapshotID());
- return status && deleteSnapshotPath(sinfo.getSnapshotPath(),
- sinfo.getSnapshotID());
+ status = deleteSnapshotGlobal(sinfo.getSnapshotID()) &&
+ deleteSnapshotPath(sinfo.getSnapshotPath(), sinfo.getSnapshotID());
+ if (status) {
+ snapshotIdToTableKey.remove(sinfo.getSnapshotID());
+ }
+ return status;
}
/**
@@ -521,8 +522,8 @@ public String previousPathSnapshot(String snapshotPath, String snapshotID)
.getPreviousSnapshotID();
}
- public String getTableKey(String snapshotPath) {
- return snapshotPathToTableKey.get(snapshotPath);
+ public String getTableKey(String snapshotId) {
+ return snapshotIdToTableKey.get(snapshotId);
}
@VisibleForTesting
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/utils/OzoneManagerRatisUtils.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/utils/OzoneManagerRatisUtils.java
index bc08a9059811..512c8cb92beb 100644
--- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/utils/OzoneManagerRatisUtils.java
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/utils/OzoneManagerRatisUtils.java
@@ -73,6 +73,7 @@
import org.apache.hadoop.ozone.om.request.snapshot.OMSnapshotCreateRequest;
import org.apache.hadoop.ozone.om.request.snapshot.OMSnapshotDeleteRequest;
import org.apache.hadoop.ozone.om.request.snapshot.OMSnapshotMoveDeletedKeysRequest;
+import org.apache.hadoop.ozone.om.request.snapshot.OMSnapshotPurgeRequest;
import org.apache.hadoop.ozone.om.request.upgrade.OMCancelPrepareRequest;
import org.apache.hadoop.ozone.om.request.upgrade.OMFinalizeUpgradeRequest;
import org.apache.hadoop.ozone.om.request.upgrade.OMPrepareRequest;
@@ -218,6 +219,8 @@ public static OMClientRequest createClientRequest(OMRequest omRequest,
return new OMSnapshotDeleteRequest(omRequest);
case SnapshotMoveDeletedKeys:
return new OMSnapshotMoveDeletedKeysRequest(omRequest);
+ case SnapshotPurge:
+ return new OMSnapshotPurgeRequest(omRequest);
case DeleteOpenKeys:
BucketLayout bktLayout = BucketLayout.DEFAULT;
if (omRequest.getDeleteOpenKeysRequest().hasBucketLayout()) {
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotCreateRequest.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotCreateRequest.java
index 2ee67692ff9a..a37f2edd2bba 100644
--- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotCreateRequest.java
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotCreateRequest.java
@@ -26,8 +26,8 @@
import org.apache.hadoop.ozone.OmUtils;
import org.apache.hadoop.ozone.audit.AuditLogger;
import org.apache.hadoop.ozone.audit.OMAction;
-import org.apache.hadoop.ozone.om.OMMetadataManager;
import org.apache.hadoop.ozone.om.OMMetrics;
+import org.apache.hadoop.ozone.om.OmMetadataManagerImpl;
import org.apache.hadoop.ozone.om.OzoneManager;
import org.apache.hadoop.ozone.om.SnapshotChainManager;
import org.apache.hadoop.ozone.om.exceptions.OMException;
@@ -115,9 +115,10 @@ public OMClientResponse validateAndUpdateCache(OzoneManager ozoneManager,
boolean acquiredBucketLock = false, acquiredSnapshotLock = false;
IOException exception = null;
- OMMetadataManager omMetadataManager = ozoneManager.getMetadataManager();
+ OmMetadataManagerImpl omMetadataManager = (OmMetadataManagerImpl)
+ ozoneManager.getMetadataManager();
SnapshotChainManager snapshotChainManager =
- ozoneManager.getSnapshotChainManager();
+ omMetadataManager.getSnapshotChainManager();
OMResponse.Builder omResponse = OmResponseUtil.getOMResponseBuilder(
getOmRequest());
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotMoveDeletedKeysRequest.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotMoveDeletedKeysRequest.java
index f1db67846f88..2c1f44e6777a 100644
--- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotMoveDeletedKeysRequest.java
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotMoveDeletedKeysRequest.java
@@ -19,6 +19,7 @@
package org.apache.hadoop.ozone.om.request.snapshot;
+import org.apache.hadoop.ozone.om.OmMetadataManagerImpl;
import org.apache.hadoop.ozone.om.OmSnapshot;
import org.apache.hadoop.ozone.om.OmSnapshotManager;
import org.apache.hadoop.ozone.om.OzoneManager;
@@ -57,8 +58,10 @@ public OMSnapshotMoveDeletedKeysRequest(OMRequest omRequest) {
public OMClientResponse validateAndUpdateCache(OzoneManager ozoneManager,
long trxnLogIndex, OzoneManagerDoubleBufferHelper omDoubleBufferHelper) {
OmSnapshotManager omSnapshotManager = ozoneManager.getOmSnapshotManager();
+ OmMetadataManagerImpl omMetadataManager = (OmMetadataManagerImpl)
+ ozoneManager.getMetadataManager();
SnapshotChainManager snapshotChainManager =
- ozoneManager.getSnapshotChainManager();
+ omMetadataManager.getSnapshotChainManager();
SnapshotMoveDeletedKeysRequest moveDeletedKeysRequest =
getOmRequest().getSnapshotMoveDeletedKeysRequest();
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotPurgeRequest.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotPurgeRequest.java
new file mode 100644
index 000000000000..30409c047342
--- /dev/null
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotPurgeRequest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.request.snapshot;
+
+import org.apache.hadoop.ozone.om.OzoneManager;
+import org.apache.hadoop.ozone.om.ratis.utils.OzoneManagerDoubleBufferHelper;
+import org.apache.hadoop.ozone.om.request.OMClientRequest;
+import org.apache.hadoop.ozone.om.request.util.OmResponseUtil;
+import org.apache.hadoop.ozone.om.response.OMClientResponse;
+import org.apache.hadoop.ozone.om.response.snapshot.OMSnapshotPurgeResponse;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SnapshotPurgeRequest;
+
+import java.util.List;
+
+/**
+ * Handles OMSnapshotPurge Request.
+ */
+public class OMSnapshotPurgeRequest extends OMClientRequest {
+
+ public OMSnapshotPurgeRequest(OMRequest omRequest) {
+ super(omRequest);
+ }
+
+ @Override
+ public OMClientResponse validateAndUpdateCache(OzoneManager ozoneManager,
+ long trxnLogIndex, OzoneManagerDoubleBufferHelper omDoubleBufferHelper) {
+
+ OMClientResponse omClientResponse = null;
+
+ OzoneManagerProtocolProtos.OMResponse.Builder omResponse =
+ OmResponseUtil.getOMResponseBuilder(getOmRequest());
+ SnapshotPurgeRequest snapshotPurgeRequest = getOmRequest()
+ .getSnapshotPurgeRequest();
+
+ List snapshotDbKeys = snapshotPurgeRequest
+ .getSnapshotDBKeysList();
+
+ omClientResponse = new OMSnapshotPurgeResponse(omResponse.build(),
+ snapshotDbKeys);
+ addResponseToDoubleBuffer(trxnLogIndex, omClientResponse,
+ omDoubleBufferHelper);
+
+ return omClientResponse;
+ }
+}
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/snapshot/OMSnapshotPurgeResponse.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/snapshot/OMSnapshotPurgeResponse.java
new file mode 100644
index 000000000000..9d625ea1dc5b
--- /dev/null
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/snapshot/OMSnapshotPurgeResponse.java
@@ -0,0 +1,145 @@
+/*
+ * 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.response.snapshot;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.hadoop.hdds.utils.db.BatchOperation;
+import org.apache.hadoop.hdds.utils.db.RDBStore;
+import org.apache.hadoop.ozone.om.OMMetadataManager;
+import org.apache.hadoop.ozone.om.OmMetadataManagerImpl;
+import org.apache.hadoop.ozone.om.SnapshotChainManager;
+import org.apache.hadoop.ozone.om.helpers.SnapshotInfo;
+import org.apache.hadoop.ozone.om.response.CleanupTableInfo;
+import org.apache.hadoop.ozone.om.response.OMClientResponse;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+import static org.apache.hadoop.ozone.om.OmMetadataManagerImpl.SNAPSHOT_INFO_TABLE;
+
+/**
+ * Response for OMSnapshotPurgeRequest.
+ */
+@CleanupTableInfo(cleanupTables = {SNAPSHOT_INFO_TABLE})
+public class OMSnapshotPurgeResponse extends OMClientResponse {
+ private static final Logger LOG =
+ LoggerFactory.getLogger(OMSnapshotPurgeResponse.class);
+ private List snapshotDbKeys;
+
+ public OMSnapshotPurgeResponse(@Nonnull OMResponse omResponse,
+ @Nonnull List snapshotDbKeys) {
+ super(omResponse);
+ this.snapshotDbKeys = snapshotDbKeys;
+ }
+
+ @Override
+ protected void addToDBBatch(OMMetadataManager omMetadataManager,
+ BatchOperation batchOperation) throws IOException {
+
+ OmMetadataManagerImpl metadataManager = (OmMetadataManagerImpl)
+ omMetadataManager;
+ for (String dbKey: snapshotDbKeys) {
+ SnapshotInfo snapshotInfo = omMetadataManager
+ .getSnapshotInfoTable().get(dbKey);
+ cleanupSnapshotChain(metadataManager, snapshotInfo, batchOperation);
+ // Delete Snapshot checkpoint directory.
+ deleteCheckpointDirectory(omMetadataManager, snapshotInfo);
+ omMetadataManager.getSnapshotInfoTable().deleteWithBatch(batchOperation,
+ dbKey);
+ }
+ }
+
+ /**
+ * Cleans up the snapshot chain and updates next snapshot's
+ * previousPath and previousGlobal IDs.
+ * @param metadataManager
+ * @param snapInfo
+ * @param batchOperation
+ */
+ private void cleanupSnapshotChain(OmMetadataManagerImpl metadataManager,
+ SnapshotInfo snapInfo, BatchOperation batchOperation) throws IOException {
+ SnapshotChainManager snapshotChainManager = metadataManager
+ .getSnapshotChainManager();
+
+ // Updates next path snapshot's previous snapshot ID
+ if (snapshotChainManager.hasNextPathSnapshot(
+ snapInfo.getSnapshotPath(), snapInfo.getSnapshotID())) {
+ String nextPathSnapshotId =
+ snapshotChainManager.nextPathSnapshot(
+ snapInfo.getSnapshotPath(), snapInfo.getSnapshotID());
+
+ String snapshotTableKey = snapshotChainManager
+ .getTableKey(nextPathSnapshotId);
+ SnapshotInfo nextPathSnapInfo =
+ metadataManager.getSnapshotInfoTable().get(snapshotTableKey);
+ if (nextPathSnapInfo != null) {
+ nextPathSnapInfo.setPathPreviousSnapshotID(
+ snapInfo.getPathPreviousSnapshotID());
+ metadataManager.getSnapshotInfoTable().putWithBatch(batchOperation,
+ nextPathSnapInfo.getTableKey(), nextPathSnapInfo);
+ }
+ }
+
+ // Updates next global snapshot's previous snapshot ID
+ if (snapshotChainManager.hasNextGlobalSnapshot(
+ snapInfo.getSnapshotID())) {
+ String nextGlobalSnapshotId =
+ snapshotChainManager.nextGlobalSnapshot(snapInfo.getSnapshotID());
+
+ String snapshotTableKey = snapshotChainManager
+ .getTableKey(nextGlobalSnapshotId);
+ SnapshotInfo nextGlobalSnapInfo =
+ metadataManager.getSnapshotInfoTable().get(snapshotTableKey);
+ if (nextGlobalSnapInfo != null) {
+ nextGlobalSnapInfo.setGlobalPreviousSnapshotID(
+ snapInfo.getPathPreviousSnapshotID());
+ metadataManager.getSnapshotInfoTable().putWithBatch(batchOperation,
+ nextGlobalSnapInfo.getTableKey(), nextGlobalSnapInfo);
+ }
+ }
+
+ // Removes current snapshot from the snapshot chain.
+ snapshotChainManager.deleteSnapshot(snapInfo);
+ }
+
+ /**
+ * Deletes the checkpoint directory for a snapshot.
+ * @param omMetadataManager
+ * @param snapshotInfo
+ */
+ private void deleteCheckpointDirectory(OMMetadataManager omMetadataManager,
+ SnapshotInfo snapshotInfo) {
+ RDBStore store = (RDBStore) omMetadataManager.getStore();
+ String checkpointPrefix = store.getDbLocation().getName();
+ Path snapshotDirPath = Paths.get(store.getSnapshotsParentDir(),
+ checkpointPrefix + snapshotInfo.getCheckpointDir());
+ try {
+ FileUtils.deleteDirectory(snapshotDirPath.toFile());
+ } catch (IOException ex) {
+ LOG.error("Failed to delete snapshot directory {} for snapshot {}",
+ snapshotDirPath, snapshotInfo.getTableKey(), ex);
+ }
+ }
+}
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/SnapshotDeletingService.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/SnapshotDeletingService.java
index d98acd448616..8d01ff89521e 100644
--- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/SnapshotDeletingService.java
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/SnapshotDeletingService.java
@@ -29,6 +29,7 @@
import org.apache.hadoop.hdds.utils.db.TableIterator;
import org.apache.hadoop.ozone.ClientVersion;
import org.apache.hadoop.ozone.OzoneConsts;
+import org.apache.hadoop.ozone.om.OmMetadataManagerImpl;
import org.apache.hadoop.ozone.om.OmSnapshot;
import org.apache.hadoop.ozone.om.OmSnapshotManager;
import org.apache.hadoop.ozone.om.OzoneManager;
@@ -37,12 +38,14 @@
import org.apache.hadoop.ozone.om.helpers.OMRatisHelper;
import org.apache.hadoop.ozone.om.helpers.OmBucketInfo;
import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
+import org.apache.hadoop.ozone.om.helpers.OmKeyRenameInfo;
import org.apache.hadoop.ozone.om.helpers.RepeatedOmKeyInfo;
import org.apache.hadoop.ozone.om.helpers.SnapshotInfo;
import org.apache.hadoop.ozone.om.ratis.OzoneManagerRatisServer;
-import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SnapshotMoveKeyInfos;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SnapshotMoveDeletedKeysRequest;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SnapshotMoveKeyInfos;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SnapshotPurgeRequest;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Type;
import org.apache.ratis.protocol.ClientId;
import org.apache.ratis.protocol.Message;
@@ -57,6 +60,8 @@
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
+import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_SNAPSHOT_KEY_DELETING_LIMIT_PER_TASK;
+import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_SNAPSHOT_KEY_DELETING_LIMIT_PER_TASK_DEFAULT;
import static org.apache.hadoop.ozone.OzoneConsts.OBJECT_ID_RECLAIM_BLOCKS;
import static org.apache.hadoop.ozone.om.OMConfigKeys.SNAPSHOT_DELETING_LIMIT_PER_TASK;
import static org.apache.hadoop.ozone.om.OMConfigKeys.SNAPSHOT_DELETING_LIMIT_PER_TASK_DEFAULT;
@@ -84,6 +89,7 @@ public class SnapshotDeletingService extends BackgroundService {
private final OzoneConfiguration conf;
private final AtomicLong successRunCount;
private final long snapshotDeletionPerTask;
+ private final int keyLimitPerSnapshot;
public SnapshotDeletingService(long interval, long serviceTimeout,
OzoneManager ozoneManager) throws IOException {
@@ -92,7 +98,9 @@ public SnapshotDeletingService(long interval, long serviceTimeout,
serviceTimeout);
this.ozoneManager = ozoneManager;
this.omSnapshotManager = ozoneManager.getOmSnapshotManager();
- this.chainManager = ozoneManager.getSnapshotChainManager();
+ OmMetadataManagerImpl omMetadataManager = (OmMetadataManagerImpl)
+ ozoneManager.getMetadataManager();
+ this.chainManager = omMetadataManager.getSnapshotChainManager();
this.runCount = new AtomicLong(0);
this.successRunCount = new AtomicLong(0);
this.suspended = new AtomicBoolean(false);
@@ -100,6 +108,9 @@ public SnapshotDeletingService(long interval, long serviceTimeout,
this.snapshotDeletionPerTask = conf
.getLong(SNAPSHOT_DELETING_LIMIT_PER_TASK,
SNAPSHOT_DELETING_LIMIT_PER_TASK_DEFAULT);
+ this.keyLimitPerSnapshot = conf.getInt(
+ OZONE_SNAPSHOT_KEY_DELETING_LIMIT_PER_TASK,
+ OZONE_SNAPSHOT_KEY_DELETING_LIMIT_PER_TASK_DEFAULT);
}
private class SnapshotDeletingTask implements BackgroundTask {
@@ -118,6 +129,7 @@ public BackgroundTaskResult call() throws Exception {
> iterator = snapshotInfoTable.iterator()) {
long snapshotLimit = snapshotDeletionPerTask;
+ List purgeSnapshotKeys = new ArrayList<>();
while (iterator.hasNext() && snapshotLimit > 0) {
SnapshotInfo snapInfo = iterator.next().getValue();
@@ -142,6 +154,11 @@ public BackgroundTaskResult call() throws Exception {
continue;
}
+ Table renamedKeyTable =
+ omSnapshot.getMetadataManager().getRenamedKeyTable();
+
+ long volumeId = ozoneManager.getMetadataManager()
+ .getVolumeId(snapInfo.getVolumeName());
// Get bucketInfo for the snapshot bucket to get bucket layout.
String dbBucketKey = ozoneManager.getMetadataManager().getBucketKey(
snapInfo.getVolumeName(), snapInfo.getBucketName());
@@ -182,13 +199,19 @@ public BackgroundTaskResult call() throws Exception {
String snapshotBucketKey = dbBucketKey + OzoneConsts.OM_KEY_PREFIX;
iterator.seek(snapshotBucketKey);
- while (deletedIterator.hasNext()) {
+ int deletionCount = 0;
+ while (deletedIterator.hasNext() &&
+ deletionCount <= keyLimitPerSnapshot) {
Table.KeyValue
deletedKeyValue = deletedIterator.next();
String deletedKey = deletedKeyValue.getKey();
// Exit if it is out of the bucket scope.
if (!deletedKey.startsWith(snapshotBucketKey)) {
+ // If snapshot deletedKeyTable doesn't have any
+ // entry in the snapshot scope it can be reclaimed
+ // TODO: [SNAPSHOT] Check deletedDirTable to be empty.
+ purgeSnapshotKeys.add(snapInfo.getTableKey());
break;
}
@@ -203,7 +226,8 @@ public BackgroundTaskResult call() throws Exception {
for (OmKeyInfo keyInfo: repeatedOmKeyInfo.getOmKeyInfoList()) {
splitRepeatedOmKeyInfo(toReclaim, toNextDb,
- keyInfo, previousKeyTable);
+ keyInfo, previousKeyTable, renamedKeyTable,
+ bucketInfo, volumeId);
}
// If all the KeyInfos are reclaimable in RepeatedOmKeyInfo
@@ -213,7 +237,7 @@ public BackgroundTaskResult call() throws Exception {
toReclaimList.add(toReclaim.build());
}
toNextDBList.add(toNextDb.build());
-
+ deletionCount++;
}
// Submit Move request to OM.
submitSnapshotMoveDeletedKeys(snapInfo, toReclaimList,
@@ -224,6 +248,8 @@ public BackgroundTaskResult call() throws Exception {
LOG.error("Error while running Snapshot Deleting Service", ex);
}
}
+
+ submitSnapshotPurgeRequest(purgeSnapshotKeys);
} catch (IOException e) {
LOG.error("Error while running Snapshot Deleting Service", e);
}
@@ -231,10 +257,32 @@ public BackgroundTaskResult call() throws Exception {
return BackgroundTaskResult.EmptyTaskResult.newResult();
}
+ private void submitSnapshotPurgeRequest(List purgeSnapshotKeys) {
+ if (!purgeSnapshotKeys.isEmpty()) {
+ SnapshotPurgeRequest snapshotPurgeRequest = SnapshotPurgeRequest
+ .newBuilder()
+ .addAllSnapshotDBKeys(purgeSnapshotKeys)
+ .build();
+
+ OMRequest omRequest = OMRequest.newBuilder()
+ .setCmdType(Type.SnapshotPurge)
+ .setSnapshotPurgeRequest(snapshotPurgeRequest)
+ .setClientId(clientId.toString())
+ .build();
+
+ // TODO: [SNAPSHOT] Submit request once KeyDeletingService,
+ // DirectoryDeletingService for snapshots are modified.
+ // submitRequest(omRequest);
+ }
+ }
+
private void splitRepeatedOmKeyInfo(SnapshotMoveKeyInfos.Builder toReclaim,
SnapshotMoveKeyInfos.Builder toNextDb, OmKeyInfo keyInfo,
- Table previousKeyTable) throws IOException {
- if (checkKeyReclaimable(previousKeyTable, keyInfo)) {
+ Table previousKeyTable,
+ Table renamedKeyTable,
+ OmBucketInfo bucketInfo, long volumeId) throws IOException {
+ if (checkKeyReclaimable(previousKeyTable, renamedKeyTable,
+ keyInfo, bucketInfo, volumeId)) {
// Move to next non deleted snapshot's deleted table
toNextDb.addKeyInfos(keyInfo.getProtobuf(
ClientVersion.CURRENT_VERSION));
@@ -268,9 +316,12 @@ private void submitSnapshotMoveDeletedKeys(SnapshotInfo snapInfo,
}
private boolean checkKeyReclaimable(
- Table previousKeyTable, OmKeyInfo deletedKeyInfo)
- throws IOException {
+ Table previousKeyTable,
+ Table renamedKeyTable,
+ OmKeyInfo deletedKeyInfo, OmBucketInfo bucketInfo,
+ long volumeId) throws IOException {
+ String dbKey;
// Handle case when the deleted snapshot is the first snapshot.
if (previousKeyTable == null) {
return false;
@@ -281,17 +332,48 @@ private boolean checkKeyReclaimable(
return false;
}
- //TODO: [SNAPSHOT] Handle Renamed Keys
- String dbKey = ozoneManager.getMetadataManager()
- .getOzoneKey(deletedKeyInfo.getVolumeName(),
- deletedKeyInfo.getBucketName(), deletedKeyInfo.getKeyName());
+ // Construct keyTable or fileTable DB key depending on the bucket type
+ if (bucketInfo.getBucketLayout().isFileSystemOptimized()) {
+ dbKey = ozoneManager.getMetadataManager().getOzonePathKey(
+ volumeId,
+ bucketInfo.getObjectID(),
+ deletedKeyInfo.getParentObjectID(),
+ deletedKeyInfo.getKeyName());
+ } else {
+ dbKey = ozoneManager.getMetadataManager().getOzoneKey(
+ deletedKeyInfo.getVolumeName(),
+ deletedKeyInfo.getBucketName(),
+ deletedKeyInfo.getKeyName());
+ }
+
+ // renamedKeyTable: volumeName/bucketName/objectID -> OMRenameKeyInfo
+ String dbRenameKey = ozoneManager.getMetadataManager().getRenameKey(
+ deletedKeyInfo.getVolumeName(), deletedKeyInfo.getBucketName(),
+ deletedKeyInfo.getObjectID());
+
+ OmKeyRenameInfo renamedKeyInfo = renamedKeyTable.getIfExist(dbRenameKey);
+
+ boolean isKeyRenamed = false;
+ String dbOriginalKey = null;
+ // Condition: key should not exist in renamedKeyTable of the current
+ // snapshot and keyTable of the previous snapshot.
+ // Check key exists in renamedKeyTable of the Snapshot
+ if (renamedKeyInfo != null && !renamedKeyInfo
+ .getOmKeyRenameInfoList().isEmpty()) {
+ isKeyRenamed = true;
+ dbOriginalKey = renamedKeyInfo.getOmKeyRenameInfoList().get(0);
+ }
+
+ // previousKeyTable is fileTable if the bucket is FSO,
+ // otherwise it is the keyTable.
+ OmKeyInfo prevKeyInfo = isKeyRenamed ? previousKeyTable
+ .get(dbOriginalKey) : previousKeyTable.get(dbKey);
- OmKeyInfo prevKeyInfo = previousKeyTable.get(dbKey);
- if (prevKeyInfo != null &&
- prevKeyInfo.getObjectID() == deletedKeyInfo.getObjectID()) {
- return true;
+ if (prevKeyInfo == null) {
+ return false;
}
- return false;
+
+ return prevKeyInfo.getObjectID() == deletedKeyInfo.getObjectID();
}
private SnapshotInfo getPreviousSnapshot(SnapshotInfo snapInfo)
diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotCreateRequest.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotCreateRequest.java
index e530f712cb11..391d7b99f2c9 100644
--- a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotCreateRequest.java
+++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotCreateRequest.java
@@ -29,11 +29,9 @@
import org.apache.hadoop.ozone.audit.AuditMessage;
import org.apache.hadoop.ozone.om.OMConfigKeys;
-import org.apache.hadoop.ozone.om.OMMetadataManager;
import org.apache.hadoop.ozone.om.OMMetrics;
import org.apache.hadoop.ozone.om.OmMetadataManagerImpl;
import org.apache.hadoop.ozone.om.OzoneManager;
-import org.apache.hadoop.ozone.om.SnapshotChainManager;
import org.apache.hadoop.ozone.om.exceptions.OMException;
import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
import org.apache.hadoop.ozone.om.helpers.SnapshotInfo;
@@ -71,7 +69,7 @@ public class TestOMSnapshotCreateRequest {
private OzoneManager ozoneManager;
private OMMetrics omMetrics;
- private OMMetadataManager omMetadataManager;
+ private OmMetadataManagerImpl omMetadataManager;
private BatchOperation batchOperation;
private String volumeName;
@@ -211,11 +209,7 @@ public void testPreExecuteNameLength() throws Exception {
@Test
public void testValidateAndUpdateCache() throws Exception {
- SnapshotChainManager snapshotChainManager =
- new SnapshotChainManager(omMetadataManager);
when(ozoneManager.isAdmin(any())).thenReturn(true);
- when(ozoneManager.getSnapshotChainManager())
- .thenReturn(snapshotChainManager);
OMRequest omRequest =
OMRequestTestUtils.createSnapshotRequest(
volumeName, bucketName, snapshotName);
@@ -254,11 +248,7 @@ public void testValidateAndUpdateCache() throws Exception {
@Test
public void testEmptyRenamedKeyTable() throws Exception {
- SnapshotChainManager snapshotChainManager =
- new SnapshotChainManager(omMetadataManager);
when(ozoneManager.isAdmin(any())).thenReturn(true);
- when(ozoneManager.getSnapshotChainManager())
- .thenReturn(snapshotChainManager);
OmKeyInfo toKeyInfo = addKey("key1");
OmKeyInfo fromKeyInfo = addKey("key2");
@@ -302,11 +292,7 @@ public void testEmptyRenamedKeyTable() throws Exception {
@Test
public void testEntryExists() throws Exception {
- SnapshotChainManager snapshotChainManager =
- new SnapshotChainManager(omMetadataManager);
when(ozoneManager.isAdmin(any())).thenReturn(true);
- when(ozoneManager.getSnapshotChainManager())
- .thenReturn(snapshotChainManager);
OMRequest omRequest =
OMRequestTestUtils.createSnapshotRequest(
volumeName, bucketName, snapshotName);
diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotDeleteRequest.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotDeleteRequest.java
index b8d7c74173b0..e380a5281ce3 100644
--- a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotDeleteRequest.java
+++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotDeleteRequest.java
@@ -27,11 +27,9 @@
import org.apache.hadoop.ozone.audit.AuditLogger;
import org.apache.hadoop.ozone.audit.AuditMessage;
import org.apache.hadoop.ozone.om.OMConfigKeys;
-import org.apache.hadoop.ozone.om.OMMetadataManager;
import org.apache.hadoop.ozone.om.OMMetrics;
import org.apache.hadoop.ozone.om.OmMetadataManagerImpl;
import org.apache.hadoop.ozone.om.OzoneManager;
-import org.apache.hadoop.ozone.om.SnapshotChainManager;
import org.apache.hadoop.ozone.om.exceptions.OMException;
import org.apache.hadoop.ozone.om.helpers.SnapshotInfo;
import org.apache.hadoop.ozone.om.ratis.utils.OzoneManagerDoubleBufferHelper;
@@ -68,7 +66,7 @@ public class TestOMSnapshotDeleteRequest {
private OzoneManager ozoneManager;
private OMMetrics omMetrics;
- private OMMetadataManager omMetadataManager;
+ private OmMetadataManagerImpl omMetadataManager;
private String volumeName;
private String bucketName;
@@ -259,10 +257,6 @@ public void testEntryNotExist() throws Exception {
*/
@Test
public void testEntryExists() throws Exception {
- SnapshotChainManager snapshotChainManager =
- new SnapshotChainManager(omMetadataManager);
- when(ozoneManager.getSnapshotChainManager())
- .thenReturn(snapshotChainManager);
when(ozoneManager.isAdmin(any())).thenReturn(true);
String key = SnapshotInfo.getTableKey(volumeName, bucketName, snapshotName);
diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotPurgeRequestAndResponse.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotPurgeRequestAndResponse.java
new file mode 100644
index 000000000000..02814c788e17
--- /dev/null
+++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotPurgeRequestAndResponse.java
@@ -0,0 +1,311 @@
+/*
+ * 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.request.snapshot;
+
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.utils.db.BatchOperation;
+import org.apache.hadoop.hdds.utils.db.RDBStore;
+import org.apache.hadoop.ozone.OzoneConfigKeys;
+import org.apache.hadoop.ozone.audit.AuditLogger;
+import org.apache.hadoop.ozone.om.OMConfigKeys;
+import org.apache.hadoop.ozone.om.OMMetadataManager;
+import org.apache.hadoop.ozone.om.OMMetrics;
+import org.apache.hadoop.ozone.om.OmMetadataManagerImpl;
+import org.apache.hadoop.ozone.om.OmMetadataReader;
+import org.apache.hadoop.ozone.om.OzoneManager;
+import org.apache.hadoop.ozone.om.SnapshotChainManager;
+import org.apache.hadoop.ozone.om.helpers.SnapshotInfo;
+import org.apache.hadoop.ozone.om.ratis.utils.OzoneManagerDoubleBufferHelper;
+import org.apache.hadoop.ozone.om.request.OMRequestTestUtils;
+import org.apache.hadoop.ozone.om.response.snapshot.OMSnapshotCreateResponse;
+import org.apache.hadoop.ozone.om.response.snapshot.OMSnapshotPurgeResponse;
+import org.apache.hadoop.ozone.om.upgrade.OMLayoutVersionManager;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SnapshotPurgeRequest;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Type;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.ozone.test.GenericTestUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.Mockito;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import java.util.UUID;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests OMSnapshotPurgeRequest class.
+ */
+public class TestOMSnapshotPurgeRequestAndResponse {
+
+ private BatchOperation batchOperation;
+ private List checkpointPaths = new ArrayList<>();
+
+ private OzoneManager ozoneManager;
+ private OMMetrics omMetrics;
+ private OMMetadataManager omMetadataManager;
+ private AuditLogger auditLogger;
+
+ private String volumeName;
+ private String bucketName;
+ private String keyName;
+
+
+ // Just setting ozoneManagerDoubleBuffer which does nothing.
+ private static OzoneManagerDoubleBufferHelper ozoneManagerDoubleBufferHelper =
+ ((response, transactionIndex) -> {
+ return null;
+ });
+
+ @BeforeEach
+ public void setup() throws Exception {
+ File testDir = GenericTestUtils.getRandomizedTestDir();
+ ozoneManager = Mockito.mock(OzoneManager.class);
+ OMLayoutVersionManager lvm = mock(OMLayoutVersionManager.class);
+ when(lvm.getMetadataLayoutVersion()).thenReturn(0);
+ when(ozoneManager.getVersionManager()).thenReturn(lvm);
+ when(ozoneManager.isRatisEnabled()).thenReturn(true);
+ auditLogger = Mockito.mock(AuditLogger.class);
+ when(ozoneManager.getAuditLogger()).thenReturn(auditLogger);
+ omMetrics = OMMetrics.create();
+ OzoneConfiguration ozoneConfiguration = new OzoneConfiguration();
+ ozoneConfiguration.set(OMConfigKeys.OZONE_OM_DB_DIRS,
+ testDir.getAbsolutePath());
+ ozoneConfiguration.set(OzoneConfigKeys.OZONE_METADATA_DIRS,
+ testDir.getAbsolutePath());
+ omMetadataManager = new OmMetadataManagerImpl(ozoneConfiguration);
+ when(ozoneManager.getMetrics()).thenReturn(omMetrics);
+ when(ozoneManager.getMetadataManager()).thenReturn(omMetadataManager);
+ when(ozoneManager.getConfiguration()).thenReturn(ozoneConfiguration);
+ when(ozoneManager.isAdmin(any(UserGroupInformation.class)))
+ .thenReturn(true);
+
+ OmMetadataReader omMetadataReader = Mockito.mock(OmMetadataReader.class);
+ when(ozoneManager.getOmMetadataReader()).thenReturn(omMetadataReader);
+ volumeName = UUID.randomUUID().toString();
+ bucketName = UUID.randomUUID().toString();
+ keyName = UUID.randomUUID().toString();
+ }
+
+ /**
+ * Creates volume, bucket and snapshot entries.
+ */
+ private List createSnapshots(int numSnapshotKeys)
+ throws Exception {
+
+ Random random = new Random();
+ // Add volume, bucket and key entries to OM DB.
+ OMRequestTestUtils.addVolumeAndBucketToDB(volumeName, bucketName,
+ omMetadataManager);
+
+ // Create Snapshot and CheckpointDir
+ List purgeSnapshots = new ArrayList<>(numSnapshotKeys);
+ for (int i = 1; i <= numSnapshotKeys; i++) {
+ String snapshotName = keyName + "-" + random.nextLong();
+ createSnapshotCheckpoint(snapshotName);
+ purgeSnapshots.add(SnapshotInfo.getTableKey(volumeName,
+ bucketName, snapshotName));
+ }
+
+ return purgeSnapshots;
+ }
+
+ /**
+ * Create OMRequest which encapsulates SnapshotPurgeRequest.
+ *
+ * @return OMRequest
+ */
+ private OMRequest createPurgeKeysRequest(List purgeSnapshotKeys) {
+ SnapshotPurgeRequest snapshotPurgeRequest = SnapshotPurgeRequest
+ .newBuilder()
+ .addAllSnapshotDBKeys(purgeSnapshotKeys)
+ .build();
+
+ OMRequest omRequest = OMRequest.newBuilder()
+ .setCmdType(Type.SnapshotPurge)
+ .setSnapshotPurgeRequest(snapshotPurgeRequest)
+ .setClientId(UUID.randomUUID().toString())
+ .build();
+
+ return omRequest;
+ }
+
+ /**
+ * Create snapshot and checkpoint directory.
+ */
+ private void createSnapshotCheckpoint(String snapshotName) throws Exception {
+ when(ozoneManager.isAdmin(any())).thenReturn(true);
+ batchOperation = omMetadataManager.getStore().initBatchOperation();
+ OMRequest omRequest = OMRequestTestUtils
+ .createSnapshotRequest(volumeName, bucketName, snapshotName);
+ // Pre-Execute OMSnapshotCreateRequest.
+ OMSnapshotCreateRequest omSnapshotCreateRequest =
+ TestOMSnapshotCreateRequest.doPreExecute(omRequest, ozoneManager);
+
+ // validateAndUpdateCache OMSnapshotCreateResponse.
+ OMSnapshotCreateResponse omClientResponse = (OMSnapshotCreateResponse)
+ omSnapshotCreateRequest.validateAndUpdateCache(ozoneManager, 1,
+ ozoneManagerDoubleBufferHelper);
+ // Add to batch and commit to DB.
+ omClientResponse.addToDBBatch(omMetadataManager, batchOperation);
+ omMetadataManager.getStore().commitBatchOperation(batchOperation);
+ batchOperation.close();
+
+ String key = SnapshotInfo.getTableKey(volumeName,
+ bucketName, snapshotName);
+ SnapshotInfo snapshotInfo =
+ omMetadataManager.getSnapshotInfoTable().get(key);
+ Assertions.assertNotNull(snapshotInfo);
+
+ RDBStore store = (RDBStore) omMetadataManager.getStore();
+ String checkpointPrefix = store.getDbLocation().getName();
+ Path snapshotDirPath = Paths.get(store.getSnapshotsParentDir(),
+ checkpointPrefix + snapshotInfo.getCheckpointDir());
+ //Check the DB is still there
+ Assertions.assertTrue(Files.exists(snapshotDirPath));
+ checkpointPaths.add(snapshotDirPath);
+ }
+
+ private OMSnapshotPurgeRequest preExecute(OMRequest originalOmRequest)
+ throws IOException {
+ OMSnapshotPurgeRequest omSnapshotPurgeRequest =
+ new OMSnapshotPurgeRequest(originalOmRequest);
+ OMRequest modifiedOmRequest = omSnapshotPurgeRequest
+ .preExecute(ozoneManager);
+ return new OMSnapshotPurgeRequest(modifiedOmRequest);
+ }
+
+ private void purgeSnapshots(OMRequest snapshotPurgeRequest)
+ throws IOException {
+ // Pre-Execute OMSnapshotPurgeRequest
+ OMSnapshotPurgeRequest omSnapshotPurgeRequest =
+ preExecute(snapshotPurgeRequest);
+
+ // validateAndUpdateCache for OMSnapshotPurgeRequest.
+ OMSnapshotPurgeResponse omSnapshotPurgeResponse = (OMSnapshotPurgeResponse)
+ omSnapshotPurgeRequest.validateAndUpdateCache(ozoneManager, 200L,
+ ozoneManagerDoubleBufferHelper);
+
+ // Commit to DB.
+ batchOperation = omMetadataManager.getStore().initBatchOperation();
+ omSnapshotPurgeResponse.checkAndUpdateDB(omMetadataManager, batchOperation);
+ omMetadataManager.getStore().commitBatchOperation(batchOperation);
+ }
+
+ @Test
+ public void testValidateAndUpdateCache() throws Exception {
+
+ List snapshotDbKeysToPurge = createSnapshots(10);
+ Assertions.assertFalse(omMetadataManager.getSnapshotInfoTable().isEmpty());
+ OMRequest snapshotPurgeRequest = createPurgeKeysRequest(
+ snapshotDbKeysToPurge);
+ purgeSnapshots(snapshotPurgeRequest);
+
+ // Check if the entries are deleted.
+ Assertions.assertTrue(omMetadataManager.getSnapshotInfoTable().isEmpty());
+
+ // Check if all the checkpoints are cleared.
+ for (Path checkpoint : checkpointPaths) {
+ Assertions.assertFalse(Files.exists(checkpoint));
+ }
+ }
+
+ @ParameterizedTest
+ @ValueSource(ints = {0, 1, 2, 3, 4})
+ public void testSnapshotChainCleanup(int index) throws Exception {
+ List snapshots = createSnapshots(5);
+ String snapShotToPurge = snapshots.get(index);
+
+ // Before purge, check snapshot chain
+ OmMetadataManagerImpl metadataManager =
+ (OmMetadataManagerImpl) omMetadataManager;
+ SnapshotChainManager chainManager = metadataManager
+ .getSnapshotChainManager();
+ SnapshotInfo snapInfo = metadataManager.getSnapshotInfoTable()
+ .get(snapShotToPurge);
+
+ // Get previous and next snapshotInfos to verify if the SnapInfo
+ // is changed.
+ String prevPathSnapId = null;
+ String prevGlobalSnapId = null;
+ String nextPathSnapId = null;
+ String nextGlobalSnapId = null;
+
+ if (chainManager.hasPreviousPathSnapshot(snapInfo.getSnapshotPath(),
+ snapInfo.getSnapshotID())) {
+ prevPathSnapId = chainManager.previousPathSnapshot(
+ snapInfo.getSnapshotPath(), snapInfo.getSnapshotID());
+ }
+ if (chainManager.hasPreviousGlobalSnapshot(snapInfo.getSnapshotID())) {
+ prevGlobalSnapId = chainManager.previousGlobalSnapshot(
+ snapInfo.getSnapshotID());
+ }
+ if (chainManager.hasNextPathSnapshot(snapInfo.getSnapshotPath(),
+ snapInfo.getSnapshotID())) {
+ nextPathSnapId = chainManager.nextPathSnapshot(
+ snapInfo.getSnapshotPath(), snapInfo.getSnapshotID());
+ }
+ if (chainManager.hasNextGlobalSnapshot(snapInfo.getSnapshotID())) {
+ nextGlobalSnapId = chainManager.nextGlobalSnapshot(
+ snapInfo.getSnapshotID());
+ }
+
+ long rowsInTableBeforePurge = omMetadataManager
+ .countRowsInTable(omMetadataManager.getSnapshotInfoTable());
+ // Purge Snapshot of the given index.
+ List toPurgeList = Collections.singletonList(snapShotToPurge);
+ OMRequest snapshotPurgeRequest = createPurgeKeysRequest(
+ toPurgeList);
+ purgeSnapshots(snapshotPurgeRequest);
+
+ // After purge, check snapshot chain.
+ if (nextPathSnapId != null) {
+ SnapshotInfo nextPathSnapshotInfoAfterPurge = metadataManager
+ .getSnapshotInfoTable().get(chainManager.getTableKey(nextPathSnapId));
+ Assertions.assertEquals(nextPathSnapshotInfoAfterPurge
+ .getGlobalPreviousSnapshotID(), prevPathSnapId);
+ }
+
+ if (nextGlobalSnapId != null) {
+ SnapshotInfo nextGlobalSnapshotInfoAfterPurge = metadataManager
+ .getSnapshotInfoTable().get(chainManager
+ .getTableKey(nextGlobalSnapId));
+ Assertions.assertEquals(nextGlobalSnapshotInfoAfterPurge
+ .getGlobalPreviousSnapshotID(), prevGlobalSnapId);
+ }
+
+ Assertions.assertNotEquals(rowsInTableBeforePurge, omMetadataManager
+ .countRowsInTable(omMetadataManager.getSnapshotInfoTable()));
+ }
+}