diff --git a/hadoop-hdds/framework/src/main/resources/webapps/static/ozone.js b/hadoop-hdds/framework/src/main/resources/webapps/static/ozone.js
index aac641e625e1..2835c3633e5e 100644
--- a/hadoop-hdds/framework/src/main/resources/webapps/static/ozone.js
+++ b/hadoop-hdds/framework/src/main/resources/webapps/static/ozone.js
@@ -256,6 +256,8 @@
ioLinkHref: '@',
scanner: '<',
scannerLinkHref: '@',
+ snapshot: '@',
+ snapshotLinkHref: '@'
},
templateUrl: 'static/templates/menu.html',
controller: function($http) {
diff --git a/hadoop-hdds/framework/src/main/resources/webapps/static/templates/menu.html b/hadoop-hdds/framework/src/main/resources/webapps/static/templates/menu.html
index 9a14f356d7a4..1963a6543835 100644
--- a/hadoop-hdds/framework/src/main/resources/webapps/static/templates/menu.html
+++ b/hadoop-hdds/framework/src/main/resources/webapps/static/templates/menu.html
@@ -58,5 +58,6 @@
IO Status
Data Scanner
+ Ozone Snapshot
diff --git a/hadoop-hdds/server-scm/src/main/resources/webapps/scm/scm-overview.html b/hadoop-hdds/server-scm/src/main/resources/webapps/scm/scm-overview.html
index 7bfe405850e2..bb2f25a1c325 100644
--- a/hadoop-hdds/server-scm/src/main/resources/webapps/scm/scm-overview.html
+++ b/hadoop-hdds/server-scm/src/main/resources/webapps/scm/scm-overview.html
@@ -391,4 +391,4 @@ Safemode rules statuses
{{typestat.value[1]}} |
-
\ No newline at end of file
+
diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/SnapshotDiffJob.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/SnapshotDiffJob.java
index 1e5da689f4bb..714a54cbd9b2 100644
--- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/SnapshotDiffJob.java
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/SnapshotDiffJob.java
@@ -94,7 +94,7 @@ public SnapshotDiffJob(long creationTime,
this.largestEntryKey = largestEntryKey;
}
- public static Codec getCodec() {
+ public static Codec codec() {
return CODEC;
}
@@ -228,10 +228,10 @@ public String toString() {
sb.append(", reason: ").append(reason);
}
if (status.equals(JobStatus.IN_PROGRESS) && subStatus != null) {
- sb.append(", subStatus: ").append(status);
+ sb.append(", subStatus: ").append(subStatus);
if (subStatus.equals(SubStatus.OBJECT_ID_MAP_GEN_FSO) ||
subStatus.equals(SubStatus.OBJECT_ID_MAP_GEN_OBS)) {
- sb.append(String.format(", keysProcessedPercent: %.2f", keysProcessedPct));
+ sb.append(String.format(", keysProcessedPct: %.2f", keysProcessedPct));
}
}
return sb.toString();
diff --git a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestOmSnapshotDiffJobCodec.java b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestOmSnapshotDiffJobCodec.java
index b24d225d0cc5..d22be5f9090e 100644
--- a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestOmSnapshotDiffJobCodec.java
+++ b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestOmSnapshotDiffJobCodec.java
@@ -30,7 +30,7 @@
public class TestOmSnapshotDiffJobCodec {
private final OldSnapshotDiffJobCodecForTesting oldCodec
= new OldSnapshotDiffJobCodecForTesting();
- private final Codec newCodec = SnapshotDiffJob.getCodec();
+ private final Codec newCodec = SnapshotDiffJob.codec();
@Test
public void testOldJsonSerializedDataCanBeReadByNewCodec() throws Exception {
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmSnapshotManager.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmSnapshotManager.java
index 56603a9f207b..1ee53e076fcc 100644
--- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmSnapshotManager.java
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmSnapshotManager.java
@@ -425,7 +425,7 @@ private static CodecRegistry createCodecRegistryForSnapDiff() {
// DiffReportEntry codec for Diff Report.
registry.addCodec(SnapshotDiffReportOzone.DiffReportEntry.class,
SnapshotDiffReportOzone.getDiffReportEntryCodec());
- registry.addCodec(SnapshotDiffJob.class, SnapshotDiffJob.getCodec());
+ registry.addCodec(SnapshotDiffJob.class, SnapshotDiffJob.codec());
return registry.build();
}
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/SnapshotDiffManager.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/SnapshotDiffManager.java
index 9dbf546f18d1..c1c61e74226c 100644
--- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/SnapshotDiffManager.java
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/SnapshotDiffManager.java
@@ -95,9 +95,12 @@
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.stream.Collectors;
+import javax.management.ObjectName;
import org.apache.commons.io.file.PathUtils;
import org.apache.commons.lang3.tuple.Pair;
+import org.apache.hadoop.hdds.HddsUtils;
import org.apache.hadoop.hdds.StringUtils;
+import org.apache.hadoop.hdds.annotation.InterfaceAudience;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.utils.NativeLibraryNotLoadedException;
import org.apache.hadoop.hdds.utils.db.CodecRegistry;
@@ -111,6 +114,7 @@
import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
import org.apache.hadoop.hdfs.protocol.SnapshotDiffReport.DiffReportEntry;
import org.apache.hadoop.hdfs.protocol.SnapshotDiffReport.DiffType;
+import org.apache.hadoop.metrics2.util.MBeans;
import org.apache.hadoop.ozone.OFSPath;
import org.apache.hadoop.ozone.OzoneConsts;
import org.apache.hadoop.ozone.om.OMMetadataManager;
@@ -147,7 +151,8 @@
/**
* Class to generate snapshot diff.
*/
-public class SnapshotDiffManager implements AutoCloseable {
+@InterfaceAudience.Private
+public class SnapshotDiffManager implements AutoCloseable, SnapshotDiffManagerMXBean {
private static final Logger LOG =
LoggerFactory.getLogger(SnapshotDiffManager.class);
private static final Map DIFF_TYPE_STRING_MAP =
@@ -177,6 +182,7 @@ public class SnapshotDiffManager implements AutoCloseable {
*/
private final PersistentMap snapDiffJobTable;
private final ExecutorService snapDiffExecutor;
+ private ObjectName snapshotDiffManagerBeanName;
/**
* Directory to keep hardlinks of SST files for a snapDiff job temporarily.
@@ -280,6 +286,7 @@ public SnapshotDiffManager(ManagedRocksDB db,
// When we build snapDiff HA aware, we will revisit this.
// Details: https://github.com/apache/ozone/pull/4438#discussion_r1149788226
this.loadJobsOnStartUp();
+ this.registerMXBean();
}
@VisibleForTesting
@@ -1514,8 +1521,34 @@ void loadJobsOnStartUp() {
}
}
+ @Override
+ public List getSnapshotDiffJobs() {
+ List jobs = new ArrayList<>();
+ try (ClosableIterator> iterator =
+ snapDiffJobTable.iterator()) {
+ while (iterator.hasNext()) {
+ jobs.add(iterator.next().getValue());
+ }
+ }
+ return jobs;
+ }
+
+ private void registerMXBean() {
+ this.snapshotDiffManagerBeanName = HddsUtils.registerWithJmxProperties(
+ "OzoneManager", "SnapshotDiffManager",
+ Collections.emptyMap(), this);
+ }
+
+ private void unregisterMXBean() {
+ if (this.snapshotDiffManagerBeanName != null) {
+ MBeans.unregister(this.snapshotDiffManagerBeanName);
+ this.snapshotDiffManagerBeanName = null;
+ }
+ }
+
@Override
public void close() {
+ unregisterMXBean();
if (snapDiffExecutor != null) {
closeExecutorService(snapDiffExecutor, "SnapDiffExecutor");
}
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/SnapshotDiffManagerMXBean.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/SnapshotDiffManagerMXBean.java
new file mode 100644
index 000000000000..c46689e3cb9c
--- /dev/null
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/SnapshotDiffManagerMXBean.java
@@ -0,0 +1,34 @@
+/*
+ * 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.snapshot;
+
+import java.util.List;
+import org.apache.hadoop.hdds.annotation.InterfaceAudience;
+import org.apache.hadoop.ozone.om.helpers.SnapshotDiffJob;
+
+/**
+ * JMX interface for SnapshotDiffManager.
+ */
+@InterfaceAudience.Private
+public interface SnapshotDiffManagerMXBean {
+ /**
+ * Returns all snapshot diff jobs.
+ * @return list of snapshot diff jobs
+ */
+ List getSnapshotDiffJobs();
+}
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/db/SnapshotDiffDBDefinition.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/db/SnapshotDiffDBDefinition.java
index 1add663c7f1c..bb09425a7df6 100644
--- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/db/SnapshotDiffDBDefinition.java
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/db/SnapshotDiffDBDefinition.java
@@ -51,7 +51,7 @@ public final class SnapshotDiffDBDefinition extends DBDefinition.WithMap {
public static final String SNAP_DIFF_JOB_TABLE_NAME = "snap-diff-job-table";
public static final DBColumnFamilyDefinition SNAP_DIFF_JOB_TABLE_DEF
- = new DBColumnFamilyDefinition<>(SNAP_DIFF_JOB_TABLE_NAME, StringCodec.get(), SnapshotDiffJob.getCodec());
+ = new DBColumnFamilyDefinition<>(SNAP_DIFF_JOB_TABLE_NAME, StringCodec.get(), SnapshotDiffJob.codec());
/**
* Global table to keep the diff report. Each key is prefixed by the jobId
* to improve look up and clean up. JobId comes from snap-diff-job-table.
diff --git a/hadoop-ozone/ozone-manager/src/main/resources/webapps/ozoneManager/index.html b/hadoop-ozone/ozone-manager/src/main/resources/webapps/ozoneManager/index.html
index 54accf457f34..153000c2c917 100644
--- a/hadoop-ozone/ozone-manager/src/main/resources/webapps/ozoneManager/index.html
+++ b/hadoop-ozone/ozone-manager/src/main/resources/webapps/ozoneManager/index.html
@@ -49,7 +49,9 @@
Ozone Manager
+ metrics="{ 'OM metrics' : '#!/metrics/ozoneManager', 'Rpc metrics' : '#!/metrics/rpc'}"
+ snapshot="true"
+ snapshot-link-href="#!/snapshots">
diff --git a/hadoop-ozone/ozone-manager/src/main/resources/webapps/ozoneManager/om-snapshots.html b/hadoop-ozone/ozone-manager/src/main/resources/webapps/ozoneManager/om-snapshots.html
new file mode 100644
index 000000000000..6884f5a04c1f
--- /dev/null
+++ b/hadoop-ozone/ozone-manager/src/main/resources/webapps/ozoneManager/om-snapshots.html
@@ -0,0 +1,165 @@
+
+Usage Statistics
+
+
+
+ | Metric |
+ Value |
+
+
+
+
+ | Number of Active Snapshots |
+ {{$ctrl.snapshotUsageMetrics.NumSnapshotActive}} |
+
+
+ | Number of Deleted Snapshots |
+ {{$ctrl.snapshotUsageMetrics.NumSnapshotDeleted}} |
+
+
+ | Snapshot Cache Size |
+ {{$ctrl.snapshotUsageMetrics.NumSnapshotCacheSize}} |
+
+
+
+
+
+
+Snapshot Diff Jobs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ Job ID
+ |
+
+ Status
+ |
+
+ Volume
+ |
+
+ Bucket
+ |
+
+ From Snapshot
+ |
+
+ To Snapshot
+ |
+
+ Progress
+ |
+
+ Total Diff Entries
+ |
+
+ Creation Time
+ |
+
+
+
+
+ | {{job.jobId}} |
+
+ {{job.status}}
+
+ Reason: {{job.reason}}
+
+
+ Sub-status: {{job.subStatus}}
+
+ |
+ {{job.volume}} |
+ {{job.bucket}} |
+ {{job.fromSnapshot}} |
+ {{job.toSnapshot}} |
+
+ Completed
+ {{job.keysProcessedPct | number:2}}%
+ |
+ {{job.totalDiffEntries}} |
+ {{job.creationTime | date:'yyyy-MM-dd HH:mm:ss'}} |
+
+
+
+
+
+
+
+
+ of {{lastIndex}}.
+
+ Showing {{getCurrentPageFirstItemIndex()}} to {{getCurrentPageLastItemIndex()}} of the total {{totalItems}} entries.
+
+
+
+
+
+
+
+
+
+Snapshot Internal Metrics
+
+
+
+
+ | Metric Name |
+ Value |
+
+
+
+
+ | {{metric.key}} |
+ {{metric.value}} |
+
+
+
diff --git a/hadoop-ozone/ozone-manager/src/main/resources/webapps/ozoneManager/ozoneManager.js b/hadoop-ozone/ozone-manager/src/main/resources/webapps/ozoneManager/ozoneManager.js
index 8269b6df0fbb..e98d3f7ba3a7 100644
--- a/hadoop-ozone/ozone-manager/src/main/resources/webapps/ozoneManager/ozoneManager.js
+++ b/hadoop-ozone/ozone-manager/src/main/resources/webapps/ozoneManager/ozoneManager.js
@@ -27,8 +27,110 @@
$routeProvider
.when("/metrics/ozoneManager", {
template: ""
+ })
+ .when("/snapshots", {
+ template: ""
});
});
+ angular.module('ozoneManager').component('omSnapshots', {
+ templateUrl: 'om-snapshots.html',
+ controller: function ($http, $scope) {
+ var ctrl = this;
+ ctrl.snapshotMetrics = [];
+ ctrl.snapshotDiffJobs = [];
+ ctrl.snapshotUsageMetrics = {
+ 'NumSnapshotActive': 0,
+ 'NumSnapshotDeleted': 0,
+ 'NumSnapshotCacheSize': 0
+ };
+ $scope.reverse = false;
+ $scope.columnName = "jobId";
+ let snapDiffJobsCopy = [];
+ $scope.RecordsToDisplay = "10";
+ $scope.currentPage = 1;
+ $scope.lastIndex = 0;
+
+ $http.get("jmx?qry=Hadoop:service=OzoneManager,name=OMMetrics")
+ .then(function (result) {
+ if (result.data.beans && result.data.beans.length > 0) {
+ var metrics = result.data.beans[0];
+ ctrl.snapshotUsageMetrics.NumSnapshotActive = metrics.NumSnapshotActive || 0;
+ ctrl.snapshotUsageMetrics.NumSnapshotDeleted = metrics.NumSnapshotDeleted || 0;
+ ctrl.snapshotUsageMetrics.NumSnapshotCacheSize = metrics.NumSnapshotCacheSize || 0;
+ }
+ });
+
+ $http.get("jmx?qry=Hadoop:service=OzoneManager,name=OmSnapshotInternalMetrics")
+ .then(function (result) {
+ if (result.data.beans && result.data.beans.length > 0) {
+ var metrics = result.data.beans[0];
+ for (var key in metrics) {
+ if (!isIgnoredJmxKeys(key)) {
+ ctrl.snapshotMetrics.push({key: key, value: metrics[key]});
+ }
+ }
+ }
+ });
+
+ $http.get("jmx?qry=Hadoop:service=OzoneManager,name=SnapshotDiffManager")
+ .then(function (result) {
+ if (result.data.beans && result.data.beans.length > 0) {
+ snapDiffJobsCopy = result.data.beans[0].SnapshotDiffJobs;
+ $scope.totalItems = snapDiffJobsCopy.length;
+ $scope.lastIndex = Math.ceil(snapDiffJobsCopy.length / $scope.RecordsToDisplay);
+ ctrl.snapshotDiffJobs = snapDiffJobsCopy.slice(0, $scope.RecordsToDisplay);
+ }
+ });
+
+ /*if option is 'All' display all records else display specified record on page*/
+ $scope.UpdateRecordsToShow = () => {
+ if($scope.RecordsToDisplay == 'All') {
+ $scope.lastIndex = 1;
+ ctrl.snapshotDiffJobs = snapDiffJobsCopy;
+ } else {
+ $scope.lastIndex = Math.ceil(snapDiffJobsCopy.length / $scope.RecordsToDisplay);
+ ctrl.snapshotDiffJobs = snapDiffJobsCopy.slice(0, $scope.RecordsToDisplay);
+ }
+ $scope.currentPage = 1;
+ }
+ /* Page Slicing logic */
+ $scope.handlePagination = (pageIndex, isDisabled) => {
+ if(!isDisabled && $scope.RecordsToDisplay != 'All') {
+ pageIndex = parseInt(pageIndex);
+ let startIndex = 0, endIndex = 0;
+ $scope.currentPage = pageIndex;
+ startIndex = ($scope.currentPage - 1) * parseInt($scope.RecordsToDisplay);
+ endIndex = startIndex + parseInt($scope.RecordsToDisplay);
+ ctrl.snapshotDiffJobs = snapDiffJobsCopy.slice(startIndex, endIndex);
+ }
+ }
+ /*column sort logic*/
+ $scope.columnSort = (colName) => {
+ $scope.columnName = colName;
+ $scope.reverse = !$scope.reverse;
+ }
+ /*show page*/
+ $scope.getPagesArray = function () {
+ return Array.from({ length: $scope.lastIndex }, (_, index) => index + 1);
+ };
+ /*show last item index*/
+ $scope.getCurrentPageLastItemIndex = () => {
+ if ($scope.RecordsToDisplay == 'All') {
+ return $scope.totalItems;
+ }
+
+ let endIndex = $scope.currentPage * parseInt($scope.RecordsToDisplay);
+ return Math.min(endIndex, $scope.totalItems);
+ }
+ /*show first item index*/
+ $scope.getCurrentPageFirstItemIndex = () => {
+ if ($scope.RecordsToDisplay == 'All') {
+ return 1;
+ }
+ return ($scope.currentPage - 1) * $scope.RecordsToDisplay + 1;
+ }
+ }
+ });
angular.module('ozoneManager').component('omMetrics', {
templateUrl: 'om-metrics.html',
controller: function ($http) {
diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/service/TestSnapshotDiffCleanupService.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/service/TestSnapshotDiffCleanupService.java
index 2dc9329601a4..25947fae6454 100644
--- a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/service/TestSnapshotDiffCleanupService.java
+++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/service/TestSnapshotDiffCleanupService.java
@@ -169,7 +169,7 @@ public void init() throws RocksDBException, IOException {
// DiffReportEntry codec for Diff Report.
b.addCodec(SnapshotDiffReportOzone.DiffReportEntry.class,
SnapshotDiffReportOzone.getDiffReportEntryCodec());
- b.addCodec(SnapshotDiffJob.class, SnapshotDiffJob.getCodec());
+ b.addCodec(SnapshotDiffJob.class, SnapshotDiffJob.codec());
codecRegistry = b.build();
emptyReportEntry = codecRegistry.asRawData("{}");
diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotDiffManager.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotDiffManager.java
index 6327b712dee3..4da22a90ed85 100644
--- a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotDiffManager.java
+++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotDiffManager.java
@@ -220,7 +220,7 @@ public class TestSnapshotDiffManager {
public static void initCodecRegistry() {
codecRegistry = CodecRegistry.newBuilder()
.addCodec(DiffReportEntry.class, getDiffReportEntryCodec())
- .addCodec(SnapshotDiffJob.class, SnapshotDiffJob.getCodec())
+ .addCodec(SnapshotDiffJob.class, SnapshotDiffJob.codec())
.build();
}
@@ -1365,4 +1365,21 @@ public void testGetSnapshotDiffReportJob() throws Exception {
}
}
}
+
+ @Test
+ public void testGetSnapshotDiffJobs() throws Exception {
+ // Initially no jobs
+ List jobs = snapshotDiffManager.getSnapshotDiffJobs();
+ assertEquals(0, jobs.size());
+
+ // Submit a job
+ snapshotDiffManager.getSnapshotDiffReport(VOLUME_NAME, BUCKET_NAME,
+ snapshotNames.get(0), snapshotNames.get(1),
+ "", 100, false, false);
+
+ jobs = snapshotDiffManager.getSnapshotDiffJobs();
+ assertEquals(1, jobs.size());
+ assertEquals(snapshotNames.get(0), jobs.get(0).getFromSnapshot());
+ assertEquals(snapshotNames.get(1), jobs.get(0).getToSnapshot());
+ }
}
diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotDiffManagerMXBean.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotDiffManagerMXBean.java
new file mode 100644
index 000000000000..b9e3419f53c7
--- /dev/null
+++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotDiffManagerMXBean.java
@@ -0,0 +1,153 @@
+/*
+ * 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.snapshot;
+
+import static org.apache.hadoop.hdds.utils.db.DBStoreBuilder.DEFAULT_COLUMN_FAMILY_NAME;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import javax.management.openmbean.CompositeData;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.utils.db.CodecRegistry;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedColumnFamilyOptions;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedDBOptions;
+import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB;
+import org.apache.hadoop.ozone.om.OMMetadataManager;
+import org.apache.hadoop.ozone.om.OMMetrics;
+import org.apache.hadoop.ozone.om.OmSnapshotManager;
+import org.apache.hadoop.ozone.om.OzoneManager;
+import org.apache.hadoop.ozone.om.helpers.SnapshotDiffJob;
+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.rocksdb.ColumnFamilyDescriptor;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDBException;
+
+/**
+ * Tests for SnapshotDiffManagerMXBean registration.
+ */
+public class TestSnapshotDiffManagerMXBean {
+
+ private SnapshotDiffManager snapshotDiffManager;
+ private ManagedRocksDB db;
+ private PersistentMap snapDiffJobTable;
+ private MBeanServer mbs;
+
+ @BeforeEach
+ public void setUp(@TempDir Path tempDir) throws IOException, RocksDBException {
+ OzoneConfiguration conf = new OzoneConfiguration();
+ ManagedDBOptions dbOptions = new ManagedDBOptions();
+ dbOptions.setCreateIfMissing(true);
+ ManagedColumnFamilyOptions columnFamilyOptions = new ManagedColumnFamilyOptions();
+
+ List columnFamilyDescriptors =
+ Collections.singletonList(new ColumnFamilyDescriptor(
+ DEFAULT_COLUMN_FAMILY_NAME.getBytes(StandardCharsets.UTF_8),
+ columnFamilyOptions));
+
+ List columnFamilyHandles = new ArrayList<>();
+
+ db = ManagedRocksDB.open(dbOptions, tempDir.toAbsolutePath().toString(),
+ columnFamilyDescriptors, columnFamilyHandles);
+
+ CodecRegistry codecRegistry = CodecRegistry.newBuilder()
+ .addCodec(SnapshotDiffJob.class, SnapshotDiffJob.codec())
+ .build();
+
+ ColumnFamilyHandle jobCFH = db.get().createColumnFamily(
+ new ColumnFamilyDescriptor("jobTable".getBytes(StandardCharsets.UTF_8),
+ columnFamilyOptions));
+ ColumnFamilyHandle reportCFH = db.get().createColumnFamily(
+ new ColumnFamilyDescriptor("reportTable".getBytes(StandardCharsets.UTF_8),
+ columnFamilyOptions));
+
+ snapDiffJobTable = new RocksDbPersistentMap<>(db, jobCFH,
+ codecRegistry, String.class, SnapshotDiffJob.class);
+
+ OzoneManager ozoneManager = mock(OzoneManager.class);
+ when(ozoneManager.getConfiguration()).thenReturn(conf);
+ when(ozoneManager.getMetrics()).thenReturn(mock(OMMetrics.class));
+ OMMetadataManager omMetadataManager = mock(OMMetadataManager.class);
+ org.apache.hadoop.hdds.utils.db.RDBStore rdbStore =
+ mock(org.apache.hadoop.hdds.utils.db.RDBStore.class);
+ when(rdbStore.getSnapshotMetadataDir())
+ .thenReturn(tempDir.toAbsolutePath().toString());
+ when(omMetadataManager.getStore()).thenReturn(rdbStore);
+ when(ozoneManager.getMetadataManager()).thenReturn(omMetadataManager);
+ when(ozoneManager.getOmSnapshotManager()).thenReturn(mock(OmSnapshotManager.class));
+
+ snapshotDiffManager = new SnapshotDiffManager(db, ozoneManager,
+ jobCFH, reportCFH, columnFamilyOptions, codecRegistry);
+
+ mbs = ManagementFactory.getPlatformMBeanServer();
+ }
+
+ @AfterEach
+ public void tearDown() {
+ if (snapshotDiffManager != null) {
+ snapshotDiffManager.close();
+ }
+ if (db != null) {
+ db.close();
+ }
+ }
+
+ @Test
+ public void testMXBeanRegistration() throws Exception {
+ ObjectName name = new ObjectName(
+ "Hadoop:service=OzoneManager,name=SnapshotDiffManager");
+ assertTrue(mbs.isRegistered(name), "SnapshotDiffManager MBean should be registered");
+
+ // Initially 0 jobs
+ Object jobsAttr = mbs.getAttribute(name, "SnapshotDiffJobs");
+ assertTrue(jobsAttr instanceof CompositeData[]);
+ assertEquals(0, ((CompositeData[]) jobsAttr).length);
+
+ // Add a job directly to the table
+ SnapshotDiffJob job = new SnapshotDiffJob(System.currentTimeMillis(), "job-1",
+ org.apache.hadoop.ozone.snapshot.SnapshotDiffResponse.JobStatus.QUEUED,
+ "vol", "bucket", "snap1", "snap2",
+ false, false, 0,
+ org.apache.hadoop.ozone.snapshot.SnapshotDiffResponse.SubStatus.OBJECT_ID_MAP_GEN_FSO,
+ 0, null);
+ snapDiffJobTable.put("job-1", job);
+
+ // Verify MXBean method returns the job via JMX
+ jobsAttr = mbs.getAttribute(name, "SnapshotDiffJobs");
+ assertTrue(jobsAttr instanceof CompositeData[]);
+ CompositeData[] jobs = (CompositeData[]) jobsAttr;
+ assertEquals(1, jobs.length);
+ assertEquals("job-1", jobs[0].get("jobId"));
+ assertEquals("snap1", jobs[0].get("fromSnapshot"));
+ assertEquals("snap2", jobs[0].get("toSnapshot"));
+ assertEquals("OBJECT_ID_MAP_GEN_FSO", jobs[0].get("subStatus"));
+ }
+}