From 97eee742dae3566befebfbdedaea2948c6a457eb Mon Sep 17 00:00:00 2001 From: Wei-Chiu Chuang Date: Tue, 31 Mar 2026 16:03:38 -0700 Subject: [PATCH 01/20] OM Snapshot UI -- backend code. Change-Id: Iea21514269d37a233c816a2cc55be44fe788382a --- .../om/snapshot/SnapshotDiffManager.java | 35 ++++++++++++++++++- .../snapshot/SnapshotDiffManagerMXBean.java | 35 +++++++++++++++++++ .../om/snapshot/TestSnapshotDiffManager.java | 17 +++++++++ 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/SnapshotDiffManagerMXBean.java 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..b66c8f4e0f8c 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 @@ -107,10 +107,14 @@ import org.apache.hadoop.hdds.utils.db.StringCodec; import org.apache.hadoop.hdds.utils.db.Table; import org.apache.hadoop.hdds.utils.db.TablePrefixInfo; +import javax.management.ObjectName; +import org.apache.hadoop.hdds.HddsUtils; +import org.apache.hadoop.hdds.annotation.InterfaceAudience; import org.apache.hadoop.hdds.utils.db.managed.ManagedColumnFamilyOptions; 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..62626500ef4d --- /dev/null +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/SnapshotDiffManagerMXBean.java @@ -0,0 +1,35 @@ +/* + * 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 org.apache.hadoop.hdds.annotation.InterfaceAudience; +import org.apache.hadoop.ozone.om.helpers.SnapshotDiffJob; + +import java.util.List; + +/** + * 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/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..2879780421f4 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 @@ -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()); + } } From 9e741d6f96804254e51168c6a3ac8e767eb3db78 Mon Sep 17 00:00:00 2001 From: Wei-Chiu Chuang Date: Tue, 31 Mar 2026 17:38:13 -0700 Subject: [PATCH 02/20] Frontend Change-Id: I93ba5bf171938338f00af6e1f07f27b9cee2bfe2 --- .../main/resources/webapps/static/ozone.js | 2 + .../webapps/static/templates/menu.html | 6 +- .../resources/webapps/ozoneManager/index.html | 4 +- .../webapps/ozoneManager/om-snapshots.html | 63 +++++++++++++++++++ .../webapps/ozoneManager/ozoneManager.js | 40 ++++++++++++ 5 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 hadoop-ozone/ozone-manager/src/main/resources/webapps/ozoneManager/om-snapshots.html 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..920d2ac606d6 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,7 @@
  • IO Status
  • Data Scanner
  • - - +
  • Snapshots
  • + + + 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..f7adf69c311b --- /dev/null +++ b/hadoop-ozone/ozone-manager/src/main/resources/webapps/ozoneManager/om-snapshots.html @@ -0,0 +1,63 @@ + +

    Snapshot Metrics

    + + + + + + + + + + + + + + +
    Metric NameValue
    {{metric.key}}{{metric.value}}
    + +

    Snapshot Diff Jobs

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Job IDStatusVolumeBucketFrom SnapshotTo SnapshotProgressTotal Diff EntriesCreation Time
    {{job.JobId}}{{job.Status}}{{job.Volume}}{{job.Bucket}}{{job.FromSnapshot}}{{job.ToSnapshot}}{{job.KeysProcessedPct * 100 | number:2}}%{{job.TotalDiffEntries}}{{job.CreationTime | date:'yyyy-MM-dd HH:mm:ss'}}
    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..eea3b0a8aef0 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,48 @@ $routeProvider .when("/metrics/ozoneManager", { template: "" + }) + .when("/snapshots", { + template: "" }); }); + angular.module('ozoneManager').component('omSnapshots', { + templateUrl: 'om-snapshots.html', + controller: function ($http) { + var ctrl = this; + ctrl.snapshotMetrics = []; + ctrl.snapshotDiffJobs = []; + + $http.get("jmx?qry=Hadoop:service=OzoneManager,name=OMMetrics") + .then(function (result) { + var metrics = result.data.beans[0]; + for (var key in metrics) { + if (key.match(/NumSnapshot|NumCancelSnapshotDiff|NumListSnapshotDiffJob/)) { + ctrl.snapshotMetrics.push({key: key, value: metrics[key]}); + } + } + }); + + $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=OzoneManager:name=SnapshotDiffManager") + .then(function (result) { + if (result.data.beans && result.data.beans.length > 0) { + ctrl.snapshotDiffJobs = result.data.beans[0].SnapshotDiffJobs; + } + }); + } + }); angular.module('ozoneManager').component('omMetrics', { templateUrl: 'om-metrics.html', controller: function ($http) { From 70c9ddc7c1693657340606e07804711b54898698 Mon Sep 17 00:00:00 2001 From: Wei-Chiu Chuang Date: Tue, 31 Mar 2026 19:43:15 -0700 Subject: [PATCH 03/20] Test Change-Id: I5b5a2dd86eb12555e74146bf63f461642ceba058 --- .../src/main/resources/webapps/ozoneManager/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 153000c2c917..bd7918bfa5fd 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,7 @@ Ozone Manager From dc9a30e8b5e87cf079a1e168c37a09edbbfc7924 Mon Sep 17 00:00:00 2001 From: Wei-Chiu Chuang Date: Wed, 1 Apr 2026 15:08:34 -0700 Subject: [PATCH 04/20] unit test for MXBean passed. Change-Id: Ibe1e0c84ec68936f7aa8837d9aea969b2bcc5701 --- .../ozone/om/helpers/SnapshotDiffJob.java | 4 +- .../helpers/TestOmSnapshotDiffJobCodec.java | 3 +- .../hadoop/ozone/om/OmSnapshotManager.java | 2 +- .../om/snapshot/SnapshotDiffManager.java | 1 + .../snapshot/db/SnapshotDiffDBDefinition.java | 2 +- .../webapps/ozoneManager/ozoneManager.js | 3 +- .../TestSnapshotDiffCleanupService.java | 2 +- .../om/snapshot/TestSnapshotDiffManager.java | 2 +- .../TestSnapshotDiffManagerMXBean.java | 150 ++++++++++++++++++ 9 files changed, 161 insertions(+), 8 deletions(-) create mode 100644 hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotDiffManagerMXBean.java 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..9c6e46164f25 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,9 @@ public SnapshotDiffJob(long creationTime, this.largestEntryKey = largestEntryKey; } - public static Codec getCodec() { + @java.beans.Transient + @com.fasterxml.jackson.annotation.JsonIgnore + public static Codec codec() { return CODEC; } 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..d5dc6d553e71 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,8 +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 { // Step 1: Construct a SnapshotDiffJob instance 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 b66c8f4e0f8c..0c685f763da3 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 @@ -97,6 +97,7 @@ import java.util.stream.Collectors; import org.apache.commons.io.file.PathUtils; import org.apache.commons.lang3.tuple.Pair; +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.hadoop.hdds.StringUtils; import org.apache.hadoop.hdds.conf.OzoneConfiguration; import org.apache.hadoop.hdds.utils.NativeLibraryNotLoadedException; 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/ozoneManager.js b/hadoop-ozone/ozone-manager/src/main/resources/webapps/ozoneManager/ozoneManager.js index eea3b0a8aef0..e2148d44ffd1 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 @@ -61,12 +61,13 @@ } }); - $http.get("jmx?qry=OzoneManager:name=SnapshotDiffManager") + $http.get("jmx?qry=Hadoop:service=OzoneManager,name=SnapshotDiffManager") .then(function (result) { if (result.data.beans && result.data.beans.length > 0) { ctrl.snapshotDiffJobs = result.data.beans[0].SnapshotDiffJobs; } }); + } }); angular.module('ozoneManager').component('omMetrics', { 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 2879780421f4..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(); } 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..a3e23bfa51d3 --- /dev/null +++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotDiffManagerMXBean.java @@ -0,0 +1,150 @@ +/* + * 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 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; + +import javax.management.MBeanServer; +import javax.management.ObjectName; +import javax.management.openmbean.CompositeData; +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +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; + +/** + * Tests for SnapshotDiffManagerMXBean registration. + */ +public class TestSnapshotDiffManagerMXBean { + + private SnapshotDiffManager snapshotDiffManager; + private ManagedRocksDB db; + private PersistentMap snapDiffJobTable; + private PersistentMap snapDiffReportTable; + 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(), + 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(), columnFamilyOptions)); + ColumnFamilyHandle reportCFH = db.get().createColumnFamily( + new ColumnFamilyDescriptor("reportTable".getBytes(), columnFamilyOptions)); + + snapDiffJobTable = new RocksDbPersistentMap<>(db, jobCFH, + codecRegistry, String.class, SnapshotDiffJob.class); + + snapDiffReportTable = new RocksDbPersistentMap<>(db, reportCFH, + codecRegistry, String.class, String.class); + + OzoneManager ozoneManager = mock(OzoneManager.class); + when(ozoneManager.getConfiguration()).thenReturn(conf); + when(ozoneManager.getMetrics()).thenReturn(mock(OMMetrics.class)); + OMMetadataManager omMetadataManager = mock(OMMetadataManager.class); + when(omMetadataManager.getStore()).thenReturn(mock(org.apache.hadoop.hdds.utils.db.RDBStore.class)); + 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")); + } +} From 760c22a394fd07c4fed33b1e1f2d82658713a433 Mon Sep 17 00:00:00 2001 From: Wei-Chiu Chuang Date: Wed, 1 Apr 2026 15:10:40 -0700 Subject: [PATCH 05/20] remove unnecessary annotations Change-Id: I89b3585bbd5e34364a8a4567f81de485b737d69e --- .../org/apache/hadoop/ozone/om/helpers/SnapshotDiffJob.java | 2 -- 1 file changed, 2 deletions(-) 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 9c6e46164f25..38836afdf9f8 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,8 +94,6 @@ public SnapshotDiffJob(long creationTime, this.largestEntryKey = largestEntryKey; } - @java.beans.Transient - @com.fasterxml.jackson.annotation.JsonIgnore public static Codec codec() { return CODEC; } From 669d0797eb5d588bb162b9c9b73538dbe9d77356 Mon Sep 17 00:00:00 2001 From: Wei-Chiu Chuang Date: Wed, 1 Apr 2026 17:09:31 -0700 Subject: [PATCH 06/20] wip Change-Id: I96660319acfc3b135bd58e592e9d3ff8ebd4a05c --- .../webapps/static/templates/menu.html | 2 +- .../resources/webapps/scm/scm-overview.html | 2 +- .../helpers/TestOmSnapshotDiffJobCodec.java | 1 + .../resources/webapps/ozoneManager/index.html | 4 +- .../webapps/ozoneManager/om-snapshots.html | 111 ++++++++++++++---- .../webapps/ozoneManager/ozoneManager.js | 70 +++++++++-- 6 files changed, 153 insertions(+), 37 deletions(-) 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 920d2ac606d6..81f92e0d54df 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,7 +58,7 @@
  • IO Status
  • Data Scanner
  • -
  • Snapshots
  • +
  • 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/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 d5dc6d553e71..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 @@ -31,6 +31,7 @@ public class TestOmSnapshotDiffJobCodec { private final OldSnapshotDiffJobCodecForTesting oldCodec = new OldSnapshotDiffJobCodecForTesting(); private final Codec newCodec = SnapshotDiffJob.codec(); + @Test public void testOldJsonSerializedDataCanBeReadByNewCodec() throws Exception { // Step 1: Construct a SnapshotDiffJob instance 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 bd7918bfa5fd..54accf457f34 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,9 +49,7 @@ Ozone Manager + metrics="{ 'OM metrics' : '#!/metrics/ozoneManager', 'Rpc metrics' : '#!/metrics/rpc'}"> 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 index f7adf69c311b..659f63be79de 100644 --- 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 @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. --> -

    Snapshot Metrics

    +

    Snapshot Internal Metrics

    @@ -33,31 +33,102 @@

    Snapshot Metrics

    Snapshot Diff Jobs

    +
    +
    + + +
    +
    + +
    +
    +
    - - - - - - - - - + + + + + + + + + - - - - - - - - - - + + + + + + + + + +
    Job IDStatusVolumeBucketFrom SnapshotTo SnapshotProgressTotal Diff EntriesCreation Time + Job ID + + Status + + Volume + + Bucket + + From Snapshot + + To Snapshot + + Progress + + Total Diff Entries + + Creation Time +
    {{job.JobId}}{{job.Status}}{{job.Volume}}{{job.Bucket}}{{job.FromSnapshot}}{{job.ToSnapshot}}{{job.KeysProcessedPct * 100 | number:2}}%{{job.TotalDiffEntries}}{{job.CreationTime | date:'yyyy-MM-dd HH:mm:ss'}}
    {{job.jobId}} + {{job.status}} +
    + Reason: {{job.reason}} +
    +
    {{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. + +
    +
    + +
    +
    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 e2148d44ffd1..20824105a57d 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 @@ -34,20 +34,16 @@ }); angular.module('ozoneManager').component('omSnapshots', { templateUrl: 'om-snapshots.html', - controller: function ($http) { + controller: function ($http, $scope) { var ctrl = this; ctrl.snapshotMetrics = []; ctrl.snapshotDiffJobs = []; - - $http.get("jmx?qry=Hadoop:service=OzoneManager,name=OMMetrics") - .then(function (result) { - var metrics = result.data.beans[0]; - for (var key in metrics) { - if (key.match(/NumSnapshot|NumCancelSnapshotDiff|NumListSnapshotDiffJob/)) { - ctrl.snapshotMetrics.push({key: key, value: metrics[key]}); - } - } - }); + $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=OmSnapshotInternalMetrics") .then(function (result) { @@ -64,10 +60,60 @@ $http.get("jmx?qry=Hadoop:service=OzoneManager,name=SnapshotDiffManager") .then(function (result) { if (result.data.beans && result.data.beans.length > 0) { - ctrl.snapshotDiffJobs = result.data.beans[0].SnapshotDiffJobs; + 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', { From b2a23a1dc2e6d5a5aac5999243b09bbe8274d271 Mon Sep 17 00:00:00 2001 From: Wei-Chiu Chuang Date: Wed, 1 Apr 2026 17:17:07 -0700 Subject: [PATCH 07/20] All done. Change-Id: I7e742e806d493ecca8b543f6ad19266b10247cd7 --- .../webapps/ozoneManager/om-snapshots.html | 39 +++++++++++-------- .../TestSnapshotDiffManagerMXBean.java | 1 + 2 files changed, 23 insertions(+), 17 deletions(-) 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 index 659f63be79de..7cc9b0020bb8 100644 --- 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 @@ -14,23 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. --> -

    Snapshot Internal Metrics

    - - - - - - - - - - - - - - -
    Metric NameValue
    {{metric.key}}{{metric.value}}
    -

    Snapshot Diff Jobs

    @@ -88,6 +71,9 @@

    Snapshot Diff Jobs

    Reason: {{job.reason}}
    +
    + Sub-status: {{job.subStatus}} +
    {{job.volume}} {{job.bucket}} @@ -132,3 +118,22 @@

    Snapshot Diff Jobs

    + +
    + +

    Snapshot Internal Metrics

    + + + + + + + + + + + + + + +
    Metric NameValue
    {{metric.key}}{{metric.value}}
    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 index a3e23bfa51d3..d4e587f13829 100644 --- 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 @@ -146,5 +146,6 @@ public void testMXBeanRegistration() throws Exception { 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")); } } From 6ff1ca3c3422f109b6a76f3073cec7c8aef48e4e Mon Sep 17 00:00:00 2001 From: Wei-Chiu Chuang Date: Wed, 1 Apr 2026 17:54:08 -0700 Subject: [PATCH 08/20] Added Usage Statis section Change-Id: I9f5b5040fe8835c7cc7aae98732368b12a053961 --- .../resources/webapps/ozoneManager/index.html | 4 ++- .../webapps/ozoneManager/om-snapshots.html | 26 +++++++++++++++++++ .../webapps/ozoneManager/ozoneManager.js | 15 +++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) 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 index 7cc9b0020bb8..6884f5a04c1f 100644 --- 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 @@ -14,6 +14,32 @@ See the License for the specific language governing permissions and limitations under the License. --> +

    Usage Statistics

    + + + + + + + + + + + + + + + + + + + + + +
    MetricValue
    Number of Active Snapshots{{$ctrl.snapshotUsageMetrics.NumSnapshotActive}}
    Number of Deleted Snapshots{{$ctrl.snapshotUsageMetrics.NumSnapshotDeleted}}
    Snapshot Cache Size{{$ctrl.snapshotUsageMetrics.NumSnapshotCacheSize}}
    + +
    +

    Snapshot Diff Jobs

    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 20824105a57d..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 @@ -38,6 +38,11 @@ var ctrl = this; ctrl.snapshotMetrics = []; ctrl.snapshotDiffJobs = []; + ctrl.snapshotUsageMetrics = { + 'NumSnapshotActive': 0, + 'NumSnapshotDeleted': 0, + 'NumSnapshotCacheSize': 0 + }; $scope.reverse = false; $scope.columnName = "jobId"; let snapDiffJobsCopy = []; @@ -45,6 +50,16 @@ $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) { From 931f02524c0c8c8f51637e2572d4e7ce8037a7cf Mon Sep 17 00:00:00 2001 From: Wei-Chiu Chuang Date: Wed, 1 Apr 2026 18:12:23 -0700 Subject: [PATCH 09/20] Fixed checkstyle, findbugs Change-Id: I4a2c082ddacd1868934179b4ec47beb64e046417 --- .../webapps/static/templates/menu.html | 5 +-- .../ozone/om/helpers/SnapshotDiffJob.java | 4 +- .../om/snapshot/SnapshotDiffManager.java | 7 ++- .../snapshot/SnapshotDiffManagerMXBean.java | 3 +- .../TestSnapshotDiffManagerMXBean.java | 44 +++++++++---------- 5 files changed, 29 insertions(+), 34 deletions(-) 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 81f92e0d54df..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 @@ -59,6 +59,5 @@
  • IO Status
  • Data Scanner
  • Ozone Snapshot
  • - -
    - + + 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 38836afdf9f8..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 @@ -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/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 0c685f763da3..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,10 +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 com.fasterxml.jackson.databind.ObjectMapper; +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; @@ -108,9 +110,6 @@ import org.apache.hadoop.hdds.utils.db.StringCodec; import org.apache.hadoop.hdds.utils.db.Table; import org.apache.hadoop.hdds.utils.db.TablePrefixInfo; -import javax.management.ObjectName; -import org.apache.hadoop.hdds.HddsUtils; -import org.apache.hadoop.hdds.annotation.InterfaceAudience; import org.apache.hadoop.hdds.utils.db.managed.ManagedColumnFamilyOptions; import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB; import org.apache.hadoop.hdfs.protocol.SnapshotDiffReport.DiffReportEntry; 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 index 62626500ef4d..c46689e3cb9c 100644 --- 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 @@ -17,11 +17,10 @@ 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; -import java.util.List; - /** * JMX interface for SnapshotDiffManager. */ 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 index d4e587f13829..a4913acaf014 100644 --- 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 @@ -17,6 +17,22 @@ 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; @@ -35,22 +51,6 @@ import org.rocksdb.ColumnFamilyHandle; import org.rocksdb.RocksDBException; -import javax.management.MBeanServer; -import javax.management.ObjectName; -import javax.management.openmbean.CompositeData; -import java.io.IOException; -import java.lang.management.ManagementFactory; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -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; - /** * Tests for SnapshotDiffManagerMXBean registration. */ @@ -59,7 +59,6 @@ public class TestSnapshotDiffManagerMXBean { private SnapshotDiffManager snapshotDiffManager; private ManagedRocksDB db; private PersistentMap snapDiffJobTable; - private PersistentMap snapDiffReportTable; private MBeanServer mbs; @BeforeEach @@ -71,7 +70,7 @@ public void setUp(@TempDir Path tempDir) throws IOException, RocksDBException { List columnFamilyDescriptors = Collections.singletonList(new ColumnFamilyDescriptor( - DEFAULT_COLUMN_FAMILY_NAME.getBytes(), + DEFAULT_COLUMN_FAMILY_NAME.getBytes(StandardCharsets.UTF_8), columnFamilyOptions)); List columnFamilyHandles = new ArrayList<>(); @@ -84,16 +83,15 @@ public void setUp(@TempDir Path tempDir) throws IOException, RocksDBException { .build(); ColumnFamilyHandle jobCFH = db.get().createColumnFamily( - new ColumnFamilyDescriptor("jobTable".getBytes(), columnFamilyOptions)); + new ColumnFamilyDescriptor("jobTable".getBytes(StandardCharsets.UTF_8), + columnFamilyOptions)); ColumnFamilyHandle reportCFH = db.get().createColumnFamily( - new ColumnFamilyDescriptor("reportTable".getBytes(), columnFamilyOptions)); + new ColumnFamilyDescriptor("reportTable".getBytes(StandardCharsets.UTF_8), + columnFamilyOptions)); snapDiffJobTable = new RocksDbPersistentMap<>(db, jobCFH, codecRegistry, String.class, SnapshotDiffJob.class); - snapDiffReportTable = new RocksDbPersistentMap<>(db, reportCFH, - codecRegistry, String.class, String.class); - OzoneManager ozoneManager = mock(OzoneManager.class); when(ozoneManager.getConfiguration()).thenReturn(conf); when(ozoneManager.getMetrics()).thenReturn(mock(OMMetrics.class)); From 1398b8e66c97d24973d5357580bdac7bda2ec250 Mon Sep 17 00:00:00 2001 From: Wei-Chiu Chuang Date: Wed, 1 Apr 2026 20:10:07 -0700 Subject: [PATCH 10/20] Fix test Change-Id: If87353d4cdc540b271c183f339dd704e42cb8b46 --- .../ozone/om/snapshot/TestSnapshotDiffManagerMXBean.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 index a4913acaf014..b9e3419f53c7 100644 --- 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 @@ -96,7 +96,11 @@ public void setUp(@TempDir Path tempDir) throws IOException, RocksDBException { when(ozoneManager.getConfiguration()).thenReturn(conf); when(ozoneManager.getMetrics()).thenReturn(mock(OMMetrics.class)); OMMetadataManager omMetadataManager = mock(OMMetadataManager.class); - when(omMetadataManager.getStore()).thenReturn(mock(org.apache.hadoop.hdds.utils.db.RDBStore.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)); From 99d74e8c36e7e011999a27c9f21fe85fc870d1a7 Mon Sep 17 00:00:00 2001 From: Wei-Chiu Chuang Date: Mon, 6 Apr 2026 12:28:15 -0700 Subject: [PATCH 11/20] OM-UI: Remove snapshot metrics and usage statistics from om-snapshots.html Change-Id: Ib3521a90ebcdfd3e162fe99c8e2f48f1a4a09e80 --- .../webapps/ozoneManager/om-snapshots.html | 41 ------------------- 1 file changed, 41 deletions(-) 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 index 6884f5a04c1f..ef718260ce05 100644 --- 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 @@ -14,31 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. --> -

    Usage Statistics

    - - - - - - - - - - - - - - - - - - - - - -
    MetricValue
    Number of Active Snapshots{{$ctrl.snapshotUsageMetrics.NumSnapshotActive}}
    Number of Deleted Snapshots{{$ctrl.snapshotUsageMetrics.NumSnapshotDeleted}}
    Snapshot Cache Size{{$ctrl.snapshotUsageMetrics.NumSnapshotCacheSize}}
    -

    Snapshot Diff Jobs

    @@ -145,21 +121,4 @@

    Snapshot Diff Jobs

    -
    - -

    Snapshot Internal Metrics

    - - - - - - - - - - - - - -
    Metric NameValue
    {{metric.key}}{{metric.value}}
    From 9bbffb719f1b1e5f135283bf16c1c6154657fbeb Mon Sep 17 00:00:00 2001 From: Wei-Chiu Chuang Date: Mon, 6 Apr 2026 13:20:08 -0700 Subject: [PATCH 12/20] OM-UI: Handle missing volume/bucket for snapshot list search Change-Id: I9ef3d1dad9364ce599f26c507aeb6f1887856690 --- .../ozone/om/SnapshotListJSONServlet.java | 91 +++++++++++++++++++ .../webapps/ozoneManager/om-snapshots.html | 46 ++++++++++ .../webapps/ozoneManager/ozoneManager.js | 16 ++++ 3 files changed, 153 insertions(+) create mode 100644 hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SnapshotListJSONServlet.java diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SnapshotListJSONServlet.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SnapshotListJSONServlet.java new file mode 100644 index 000000000000..6cae4cdebeb6 --- /dev/null +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SnapshotListJSONServlet.java @@ -0,0 +1,91 @@ +/* + * 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; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import java.io.IOException; +import java.io.PrintWriter; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.hadoop.ozone.OzoneConsts; +import org.apache.hadoop.ozone.snapshot.ListSnapshotResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Provides REST access to Ozone Snapshot List. + */ +public class SnapshotListJSONServlet extends HttpServlet { + + private static final Logger LOG = + LoggerFactory.getLogger(SnapshotListJSONServlet.class); + private static final long serialVersionUID = 1L; + + private transient OzoneManager om; + + @Override + public void init() throws ServletException { + this.om = (OzoneManager) getServletContext() + .getAttribute(OzoneConsts.OM_CONTEXT_ATTRIBUTE); + } + + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) { + try { + String volume = request.getParameter("volume"); + String bucket = request.getParameter("bucket"); + + if (volume == null || volume.isEmpty() || bucket == null || bucket.isEmpty()) { + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + response.getWriter().write("Both volume and bucket parameters are required."); + return; + } + + String prefix = request.getParameter("prefix"); + String startItem = request.getParameter("startItem"); + String maxKeysStr = request.getParameter("maxKeys"); + + int maxKeys = 100; + if (maxKeysStr != null) { + maxKeys = Integer.parseInt(maxKeysStr); + } + + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.enable(SerializationFeature.INDENT_OUTPUT); + response.setContentType("application/json; charset=utf8"); + PrintWriter writer = response.getWriter(); + try { + ListSnapshotResponse listSnapshotResponse = om.listSnapshot(volume, bucket, prefix, startItem, maxKeys); + writer.write(objectMapper.writeValueAsString(listSnapshotResponse.getSnapshotList())); + } finally { + if (writer != null) { + writer.close(); + } + } + } catch (IOException e) { + LOG.error("Caught an exception while processing SnapshotList request", e); + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } catch (Exception e) { + LOG.error("Unexpected error while processing SnapshotList request", e); + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + } +} 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 index ef718260ce05..49be18698fca 100644 --- 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 @@ -14,7 +14,53 @@ See the License for the specific language governing permissions and limitations under the License. --> +

    Snapshots

    +
    +
    +
    + Volume + +
    +
    +
    +
    + Bucket + +
    +
    +
    + +
    +
    + +
    + Please enter both volume and bucket to search snapshots. +
    + +
    + + + + + + + + + + + + + + + + + + + + +
    NameStatusCreation TimeSnapshot IDSnapshot Path
    {{snapshot.name}}{{snapshot.snapshotStatus}}{{snapshot.creationTime | date:'yyyy-MM-dd HH:mm:ss'}}{{snapshot.snapshotId}}{{snapshot.snapshotPath}}
    +

    Snapshot Diff Jobs

    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 e98d3f7ba3a7..789b488092c1 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 @@ -38,11 +38,27 @@ var ctrl = this; ctrl.snapshotMetrics = []; ctrl.snapshotDiffJobs = []; + ctrl.snapshots = []; ctrl.snapshotUsageMetrics = { 'NumSnapshotActive': 0, 'NumSnapshotDeleted': 0, 'NumSnapshotCacheSize': 0 }; + + ctrl.listSnapshots = function(volume, bucket) { + if (volume && bucket) { + $http.get("snapshotList?volume=" + volume + "&bucket=" + bucket) + .then(function (result) { + ctrl.snapshots = result.data; + }) + .catch(function (error) { + console.error("Error fetching snapshots:", error); + ctrl.snapshots = []; + }); + } else { + ctrl.snapshots = []; + } + }; $scope.reverse = false; $scope.columnName = "jobId"; let snapDiffJobsCopy = []; From 46ef310cf1b5ffa45c455aa4100421b98f9c62c4 Mon Sep 17 00:00:00 2001 From: Wei-Chiu Chuang Date: Mon, 6 Apr 2026 13:25:09 -0700 Subject: [PATCH 13/20] OM-UI: Add referencedSize and exclusiveSize to snapshot list table Change-Id: I019d5908930f8c292994c45ce628c86ae1b3d2fa --- .../resources/webapps/ozoneManager/om-snapshots.html | 4 ++++ .../resources/webapps/ozoneManager/ozoneManager.js | 11 +++++++++++ 2 files changed, 15 insertions(+) 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 index 49be18698fca..0c8260b0d08d 100644 --- 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 @@ -45,6 +45,8 @@

    Snapshots

    Name Status Creation Time + Referenced Size + Exclusive Size Snapshot ID Snapshot Path @@ -54,6 +56,8 @@

    Snapshots

    {{snapshot.name}} {{snapshot.snapshotStatus}} {{snapshot.creationTime | date:'yyyy-MM-dd HH:mm:ss'}} + {{$ctrl.formatBytes(snapshot.referencedSize)}} + {{$ctrl.formatBytes(snapshot.exclusiveSize)}} {{snapshot.snapshotId}} {{snapshot.snapshotPath}} 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 789b488092c1..76b1e4ebb2a4 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 @@ -59,6 +59,17 @@ ctrl.snapshots = []; } }; + + ctrl.formatBytes = function(bytes, decimals) { + if (bytes == 0) return '0 Bytes'; + if (!bytes) return 'N/A'; + var k = 1024, + dm = decimals + 1 || 3, + sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], + i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; + } + $scope.reverse = false; $scope.columnName = "jobId"; let snapDiffJobsCopy = []; From eebc18adbb245f4f5a0ec61cfab628e7fa483a56 Mon Sep 17 00:00:00 2001 From: Wei-Chiu Chuang Date: Mon, 6 Apr 2026 13:30:19 -0700 Subject: [PATCH 14/20] OM-UI: Add help text for Referenced and Exclusive sizes Change-Id: Ia5c2cb0c46314ba9fc81adca216d60ad555b4a04 --- .../src/main/resources/webapps/ozoneManager/om-snapshots.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index 0c8260b0d08d..e8b8fce1addc 100644 --- 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 @@ -45,8 +45,8 @@

    Snapshots

    Name Status Creation Time - Referenced Size - Exclusive Size + Referenced Size + Exclusive Size Snapshot ID Snapshot Path From 90ef1492bfa08aff86ba5868ad216c206cdc3ff2 Mon Sep 17 00:00:00 2001 From: Wei-Chiu Chuang Date: Mon, 6 Apr 2026 13:37:02 -0700 Subject: [PATCH 15/20] OM-UI: Fix compilation error in SnapshotListJSONServlet Change-Id: I92067bcf1879bc579528fe70b6a030936835ac3c --- .../org/apache/hadoop/ozone/om/SnapshotListJSONServlet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SnapshotListJSONServlet.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SnapshotListJSONServlet.java index 6cae4cdebeb6..9310be7c47cb 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SnapshotListJSONServlet.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SnapshotListJSONServlet.java @@ -74,7 +74,7 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) { PrintWriter writer = response.getWriter(); try { ListSnapshotResponse listSnapshotResponse = om.listSnapshot(volume, bucket, prefix, startItem, maxKeys); - writer.write(objectMapper.writeValueAsString(listSnapshotResponse.getSnapshotList())); + writer.write(objectMapper.writeValueAsString(listSnapshotResponse.getSnapshotInfos())); } finally { if (writer != null) { writer.close(); From 79c4e4f171b9becb152f21b700423ae0dfdf0915 Mon Sep 17 00:00:00 2001 From: Wei-Chiu Chuang Date: Mon, 6 Apr 2026 13:39:09 -0700 Subject: [PATCH 16/20] OM-UI: Add hover help for snapshot status Change-Id: Ic0d0d1bf55f5e6e4f6d9fb74c3d045032df93e84 --- .../src/main/resources/webapps/ozoneManager/om-snapshots.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index e8b8fce1addc..36ec4fc718be 100644 --- 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 @@ -54,7 +54,7 @@

    Snapshots

    {{snapshot.name}} - {{snapshot.snapshotStatus}} + {{snapshot.snapshotStatus}} {{snapshot.creationTime | date:'yyyy-MM-dd HH:mm:ss'}} {{$ctrl.formatBytes(snapshot.referencedSize)}} {{$ctrl.formatBytes(snapshot.exclusiveSize)}} From 9b74a5561f263718860e8c5742504e391e608d9f Mon Sep 17 00:00:00 2001 From: Wei-Chiu Chuang Date: Mon, 6 Apr 2026 13:59:12 -0700 Subject: [PATCH 17/20] OM-UI: Fix Jackson serialization cycle in SnapshotListJSONServlet Change-Id: Iccbbead33eb9c0b4dfd99ef6ff8d61b9d12ef601 --- .../hadoop/ozone/om/SnapshotListJSONServlet.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SnapshotListJSONServlet.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SnapshotListJSONServlet.java index 9310be7c47cb..c7531dc9f633 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SnapshotListJSONServlet.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SnapshotListJSONServlet.java @@ -17,6 +17,7 @@ package org.apache.hadoop.ozone.om; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import java.io.IOException; @@ -26,6 +27,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.hadoop.ozone.OzoneConsts; +import org.apache.hadoop.ozone.om.helpers.SnapshotInfo; import org.apache.hadoop.ozone.snapshot.ListSnapshotResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,6 +43,13 @@ public class SnapshotListJSONServlet extends HttpServlet { private transient OzoneManager om; + /** + * Jackson mix-in to ignore protobuf getter in SnapshotInfo. + */ + @JsonIgnoreProperties({"protobuf", "createTransactionInfo", "lastTransactionInfo"}) + private abstract static class SnapshotInfoMixin { + } + @Override public void init() throws ServletException { this.om = (OzoneManager) getServletContext() @@ -69,6 +78,7 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) { } ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.addMixIn(SnapshotInfo.class, SnapshotInfoMixin.class); objectMapper.enable(SerializationFeature.INDENT_OUTPUT); response.setContentType("application/json; charset=utf8"); PrintWriter writer = response.getWriter(); From a4a759c64b8d1ae02e029460e2e292b119499c1a Mon Sep 17 00:00:00 2001 From: Wei-Chiu Chuang Date: Mon, 6 Apr 2026 14:02:30 -0700 Subject: [PATCH 18/20] OM-UI: Add unit test for SnapshotListJSONServlet serialization Change-Id: Ib3bf244cffc2d0d8634c16040f256720ec0f30d1 --- .../ozone/om/SnapshotListJSONServlet.java | 2 +- .../ozone/om/TestSnapshotListJSONServlet.java | 52 +++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestSnapshotListJSONServlet.java diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SnapshotListJSONServlet.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SnapshotListJSONServlet.java index c7531dc9f633..97dd26a9e5ce 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SnapshotListJSONServlet.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SnapshotListJSONServlet.java @@ -47,7 +47,7 @@ public class SnapshotListJSONServlet extends HttpServlet { * Jackson mix-in to ignore protobuf getter in SnapshotInfo. */ @JsonIgnoreProperties({"protobuf", "createTransactionInfo", "lastTransactionInfo"}) - private abstract static class SnapshotInfoMixin { + abstract static class SnapshotInfoMixin { } @Override diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestSnapshotListJSONServlet.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestSnapshotListJSONServlet.java new file mode 100644 index 000000000000..a6c66545233b --- /dev/null +++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestSnapshotListJSONServlet.java @@ -0,0 +1,52 @@ +/* + * 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; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.Collections; +import java.util.UUID; +import org.apache.hadoop.ozone.om.helpers.SnapshotInfo; +import org.apache.hadoop.util.Time; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertFalse; + +/** + * Test for SnapshotListJSONServlet. + */ +public class TestSnapshotListJSONServlet { + + @Test + public void testSerialization() { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.addMixIn(SnapshotInfo.class, + SnapshotListJSONServlet.SnapshotInfoMixin.class); + + SnapshotInfo snapshotInfo = SnapshotInfo.newInstance("vol1", "bucket1", + "snap1", UUID.randomUUID(), Time.now()); + + String json = assertDoesNotThrow(() -> + objectMapper.writeValueAsString(Collections.singletonList(snapshotInfo))); + + // Verify that the problematic fields are NOT in the JSON + assertFalse(json.contains("protobuf"), "JSON should not contain protobuf field"); + assertFalse(json.contains("createTransactionInfo"), "JSON should not contain createTransactionInfo field"); + assertFalse(json.contains("lastTransactionInfo"), "JSON should not contain lastTransactionInfo field"); + } +} From 7c9e73c0e1c477548d82d58048574ebc5f22666b Mon Sep 17 00:00:00 2001 From: Wei-Chiu Chuang Date: Mon, 6 Apr 2026 14:28:01 -0700 Subject: [PATCH 19/20] OM-UI: Register snapshotList endpoint and verify name in test output Change-Id: Ib3e01aec4e1b637f97444265b3418a26bed7592e --- .../org/apache/hadoop/ozone/om/OzoneManagerHttpServer.java | 2 ++ .../hadoop/ozone/om/TestSnapshotListJSONServlet.java | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManagerHttpServer.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManagerHttpServer.java index 9c2688de812d..fd79f153de37 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManagerHttpServer.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManagerHttpServer.java @@ -36,6 +36,8 @@ public OzoneManagerHttpServer(MutableConfigurationSource conf, super(conf, "ozoneManager"); addServlet("serviceList", OZONE_OM_SERVICE_LIST_HTTP_ENDPOINT, ServiceListJSONServlet.class); + addServlet("snapshotList", "/snapshotList", + SnapshotListJSONServlet.class); addServlet("dbCheckpoint", OZONE_DB_CHECKPOINT_HTTP_ENDPOINT, OMDBCheckpointServlet.class); addServlet("dbCheckpointv2", OZONE_DB_CHECKPOINT_HTTP_ENDPOINT_V2, diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestSnapshotListJSONServlet.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestSnapshotListJSONServlet.java index a6c66545233b..a3bab726ccf4 100644 --- a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestSnapshotListJSONServlet.java +++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestSnapshotListJSONServlet.java @@ -26,6 +26,7 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Test for SnapshotListJSONServlet. @@ -38,8 +39,9 @@ public void testSerialization() { objectMapper.addMixIn(SnapshotInfo.class, SnapshotListJSONServlet.SnapshotInfoMixin.class); + String snapName = "snap1"; SnapshotInfo snapshotInfo = SnapshotInfo.newInstance("vol1", "bucket1", - "snap1", UUID.randomUUID(), Time.now()); + snapName, UUID.randomUUID(), Time.now()); String json = assertDoesNotThrow(() -> objectMapper.writeValueAsString(Collections.singletonList(snapshotInfo))); @@ -48,5 +50,8 @@ public void testSerialization() { assertFalse(json.contains("protobuf"), "JSON should not contain protobuf field"); assertFalse(json.contains("createTransactionInfo"), "JSON should not contain createTransactionInfo field"); assertFalse(json.contains("lastTransactionInfo"), "JSON should not contain lastTransactionInfo field"); + + // Verify that the expected fields ARE in the JSON + assertTrue(json.contains("\"name\":\"" + snapName + "\""), "JSON should contain snapshot name"); } } From 83d90927d640b0191f3b37f424e851759f068780 Mon Sep 17 00:00:00 2001 From: Wei-Chiu Chuang Date: Mon, 6 Apr 2026 14:45:59 -0700 Subject: [PATCH 20/20] OM-UI: Display user-friendly snapshot status text Change-Id: Ib0ad12109d4398416f5617ad96bfaca8fa3200e7 --- .../src/main/resources/webapps/ozoneManager/om-snapshots.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 index 36ec4fc718be..7f4422dcd22d 100644 --- 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 @@ -54,7 +54,9 @@

    Snapshots

    {{snapshot.name}} - {{snapshot.snapshotStatus}} + + {{snapshot.snapshotStatus === 'SNAPSHOT_ACTIVE' ? 'active' : 'deletion in progress'}} + {{snapshot.creationTime | date:'yyyy-MM-dd HH:mm:ss'}} {{$ctrl.formatBytes(snapshot.referencedSize)}} {{$ctrl.formatBytes(snapshot.exclusiveSize)}}