This is an automated email from the ASF dual-hosted git repository.

hemant pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/master by this push:
     new 27c1513fd9 HDDS-9983. Changed snapshot list API to return continuation 
token rather than using last element from the previous page's response. (#6542)
27c1513fd9 is described below

commit 27c1513fd92346a3e91d3705d057a07b10d7abad
Author: Hemant Kumar <[email protected]>
AuthorDate: Wed May 29 23:05:09 2024 -0700

    HDDS-9983. Changed snapshot list API to return continuation token rather 
than using last element from the previous page's response. (#6542)
---
 .../apache/hadoop/ozone/client/ObjectStore.java    | 92 ++++++++++------------
 .../ozone/client/protocol/ClientProtocol.java      |  9 ++-
 .../apache/hadoop/ozone/client/rpc/RpcClient.java  | 14 ++--
 .../ozone/om/protocol/OzoneManagerProtocol.java    |  9 ++-
 ...OzoneManagerProtocolClientSideTranslatorPB.java | 20 ++++-
 .../ozone/snapshot/ListSnapshotResponse.java       | 52 ++++++++++++
 .../client/rpc/TestOzoneRpcClientAbstract.java     | 18 ++---
 .../src/main/proto/OmClientProtocol.proto          |  1 +
 .../apache/hadoop/ozone/om/OMMetadataManager.java  |  7 +-
 .../hadoop/ozone/om/OmMetadataManagerImpl.java     | 40 +++++-----
 .../org/apache/hadoop/ozone/om/OzoneManager.java   |  7 +-
 .../protocolPB/OzoneManagerRequestHandler.java     | 18 +++--
 .../hadoop/ozone/om/TestOmMetadataManager.java     | 61 +++++++-------
 .../ozone/BasicRootedOzoneClientAdapterImpl.java   |  3 +-
 .../hadoop/ozone/client/ClientProtocolStub.java    |  3 +-
 .../ozone/shell/snapshot/ListSnapshotHandler.java  |  2 +-
 16 files changed, 205 insertions(+), 151 deletions(-)

diff --git 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/ObjectStore.java
 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/ObjectStore.java
index e96d0f84a4..e77f5580cc 100644
--- 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/ObjectStore.java
+++ 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/ObjectStore.java
@@ -24,6 +24,7 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.NoSuchElementException;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.hadoop.hdds.conf.ConfigurationSource;
 import org.apache.hadoop.crypto.key.KeyProvider;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
@@ -46,6 +47,7 @@ import org.apache.hadoop.ozone.om.helpers.TenantUserList;
 import org.apache.hadoop.ozone.security.OzoneTokenIdentifier;
 import org.apache.hadoop.ozone.security.acl.OzoneObj;
 import org.apache.hadoop.ozone.snapshot.CancelSnapshotDiffResponse;
+import org.apache.hadoop.ozone.snapshot.ListSnapshotResponse;
 import org.apache.hadoop.ozone.snapshot.SnapshotDiffResponse;
 import org.apache.hadoop.security.UserGroupInformation;
 
@@ -612,66 +614,45 @@ public class ObjectStore {
    * @param bucketName     bucket name
    * @param snapshotPrefix snapshot prefix to match
    * @param prevSnapshot   snapshots will be listed after this snapshot name
-   * @return list of snapshots for volume/bucket snapshot path.
+   * @return an iterator of snapshots for volume/bucket snapshot path.
    * @throws IOException
    */
-  public Iterator<? extends OzoneSnapshot> listSnapshot(
-      String volumeName, String bucketName, String snapshotPrefix,
-      String prevSnapshot) throws IOException {
-    return new SnapshotIterator(
-        volumeName, bucketName, snapshotPrefix, prevSnapshot);
-  }
-
-  /**
-   * Create an image of the current compaction log DAG in the OM.
-   * @param fileNamePrefix  file name prefix of the image file.
-   * @param graphType       type of node name to use in the graph image.
-   * @return message which tells the image name, parent dir and OM leader
-   * node information.
-   */
-  public String printCompactionLogDag(String fileNamePrefix,
-                                      String graphType) throws IOException {
-    return proxy.printCompactionLogDag(fileNamePrefix, graphType);
+  public Iterator<OzoneSnapshot> listSnapshot(String volumeName,
+                                              String bucketName,
+                                              String snapshotPrefix,
+                                              String prevSnapshot) throws 
IOException {
+    return new SnapshotIterator(volumeName, bucketName, snapshotPrefix, 
prevSnapshot);
   }
 
   /**
    * An Iterator to iterate over {@link OzoneSnapshot} list.
    */
-  private class SnapshotIterator implements Iterator<OzoneSnapshot> {
+  private final class SnapshotIterator implements Iterator<OzoneSnapshot> {
 
-    private String volumeName = null;
-    private String bucketName = null;
-    private String snapshotPrefix = null;
+    private final String volumeName;
+    private final String bucketName;
+    private final String snapshotPrefix;
+    private String lastSnapshot = null;
 
     private Iterator<OzoneSnapshot> currentIterator;
-    private OzoneSnapshot currentValue;
 
-    /**
-     * Creates an Iterator to iterate over all snapshots after
-     * prevSnapshot of specified bucket. If prevSnapshot is null it iterates
-     * from the first snapshot. The returned snapshots match snapshot prefix.
-     * @param snapshotPrefix snapshot prefix to match
-     * @param prevSnapshot snapshots will be listed after this snapshot name
-     */
-    SnapshotIterator(String volumeName, String bucketName,
-                     String snapshotPrefix, String prevSnapshot)
-        throws IOException {
+    SnapshotIterator(String volumeName,
+                     String bucketName,
+                     String snapshotPrefix,
+                     String prevSnapshot) throws IOException {
       this.volumeName = volumeName;
       this.bucketName = bucketName;
       this.snapshotPrefix = snapshotPrefix;
-      this.currentValue = null;
-      this.currentIterator = getNextListOfSnapshots(prevSnapshot).iterator();
+      // Initialized the currentIterator and continuationToken.
+      getNextListOfSnapshots(prevSnapshot);
     }
 
     @Override
     public boolean hasNext() {
-      // IMPORTANT: Without this logic, remote iteration will not work.
-      // Removing this will break the listSnapshot call if we try to
-      // list more than 1000 (ozone.client.list.cache ) snapshots.
-      if (!currentIterator.hasNext() && currentValue != null) {
+      if (!currentIterator.hasNext() && StringUtils.isNotEmpty(lastSnapshot)) {
         try {
-          currentIterator = getNextListOfSnapshots(currentValue.getName())
-              .iterator();
+          // fetch the next page if lastSnapshot is not null.
+          getNextListOfSnapshots(lastSnapshot);
         } catch (IOException e) {
           LOG.error("Error retrieving next batch of list results", e);
         }
@@ -682,24 +663,31 @@ public class ObjectStore {
     @Override
     public OzoneSnapshot next() {
       if (hasNext()) {
-        currentValue = currentIterator.next();
-        return currentValue;
+        return currentIterator.next();
       }
       throw new NoSuchElementException();
     }
 
-    /**
-     * Returns the next set of snapshot list using proxy.
-     * @param prevSnapshot previous snapshot, this will be excluded from result
-     * @return {@code List<OzoneSnapshot>}
-     */
-    private List<OzoneSnapshot> getNextListOfSnapshots(String prevSnapshot)
-        throws IOException {
-      return proxy.listSnapshot(volumeName, bucketName, snapshotPrefix,
-          prevSnapshot, listCacheSize);
+    private void getNextListOfSnapshots(String startSnapshot) throws 
IOException {
+      ListSnapshotResponse response =
+          proxy.listSnapshot(volumeName, bucketName, snapshotPrefix, 
startSnapshot, listCacheSize);
+      currentIterator = 
response.getSnapshotInfos().stream().map(OzoneSnapshot::fromSnapshotInfo).iterator();
+      lastSnapshot = response.getLastSnapshot();
     }
   }
 
+  /**
+   * Create an image of the current compaction log DAG in the OM.
+   * @param fileNamePrefix  file name prefix of the image file.
+   * @param graphType       type of node name to use in the graph image.
+   * @return message which tells the image name, parent dir and OM leader
+   * node information.
+   */
+  public String printCompactionLogDag(String fileNamePrefix,
+                                      String graphType) throws IOException {
+    return proxy.printCompactionLogDag(fileNamePrefix, graphType);
+  }
+
   /**
    * Get the differences between two snapshots.
    * @param volumeName Name of the volume to which the snapshot bucket belong
diff --git 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java
 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java
index f7b84e487d..81a2e7c25c 100644
--- 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java
+++ 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java
@@ -67,6 +67,7 @@ import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRoleI
 import org.apache.hadoop.ozone.security.OzoneTokenIdentifier;
 import org.apache.hadoop.ozone.security.acl.OzoneObj;
 import org.apache.hadoop.ozone.snapshot.CancelSnapshotDiffResponse;
+import org.apache.hadoop.ozone.snapshot.ListSnapshotResponse;
 import org.apache.hadoop.ozone.snapshot.SnapshotDiffResponse;
 import org.apache.hadoop.security.KerberosInfo;
 import org.apache.hadoop.security.token.Token;
@@ -1205,12 +1206,12 @@ public interface ClientProtocol {
    * @param volumeName     volume name
    * @param bucketName     bucket name
    * @param snapshotPrefix snapshot prefix to match
-   * @param prevSnapshot   start of the list, this snapshot is excluded
-   * @param maxListResult  max numbet of snapshots to return
-   * @return list of snapshots for volume/bucket snapshotpath.
+   * @param prevSnapshot   snapshots will be listed after this snapshot name
+   * @param maxListResult  max number of snapshots to return
+   * @return list of snapshots for volume/bucket path.
    * @throws IOException
    */
-  List<OzoneSnapshot> listSnapshot(
+  ListSnapshotResponse listSnapshot(
       String volumeName, String bucketName, String snapshotPrefix,
       String prevSnapshot, int maxListResult) throws IOException;
 
diff --git 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
index d0266c95a2..a1b35d65a5 100644
--- 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
+++ 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
@@ -142,6 +142,7 @@ import 
org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType;
 import org.apache.hadoop.ozone.security.acl.OzoneAclConfig;
 import org.apache.hadoop.ozone.security.acl.OzoneObj;
 import org.apache.hadoop.ozone.snapshot.CancelSnapshotDiffResponse;
+import org.apache.hadoop.ozone.snapshot.ListSnapshotResponse;
 import org.apache.hadoop.ozone.snapshot.SnapshotDiffResponse;
 import org.apache.hadoop.security.UserGroupInformation;
 import org.apache.hadoop.security.token.Token;
@@ -1121,23 +1122,20 @@ public class RpcClient implements ClientProtocol {
    * @param volumeName     volume name
    * @param bucketName     bucket name
    * @param snapshotPrefix snapshot prefix to match
-   * @param prevSnapshot   start of the list, this snapshot is excluded
-   * @param maxListResult  max numbet of snapshots to return
-   * @return list of snapshots for volume/bucket snapshotpath.
+   * @param prevSnapshot   snapshots will be listed after this snapshot name
+   * @param maxListResult  max number of snapshots to return
+   * @return list of snapshots for volume/bucket path.
    * @throws IOException
    */
   @Override
-  public List<OzoneSnapshot> listSnapshot(
+  public ListSnapshotResponse listSnapshot(
       String volumeName, String bucketName, String snapshotPrefix,
       String prevSnapshot, int maxListResult) throws IOException {
     Preconditions.checkArgument(StringUtils.isNotBlank(volumeName),
         "volume can't be null or empty.");
     Preconditions.checkArgument(StringUtils.isNotBlank(bucketName),
         "bucket can't be null or empty.");
-    return ozoneManagerClient.listSnapshot(volumeName, bucketName,
-            snapshotPrefix, prevSnapshot, maxListResult).stream()
-        .map(snapshotInfo -> OzoneSnapshot.fromSnapshotInfo(snapshotInfo))
-        .collect(Collectors.toList());
+    return ozoneManagerClient.listSnapshot(volumeName, bucketName, 
snapshotPrefix, prevSnapshot, maxListResult);
   }
 
   /**
diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java
 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java
index ab3f576d44..970eb9d509 100644
--- 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java
+++ 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java
@@ -70,6 +70,7 @@ import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.EchoRPC
 import org.apache.hadoop.ozone.security.OzoneDelegationTokenSelector;
 import org.apache.hadoop.ozone.security.acl.OzoneObj;
 import org.apache.hadoop.ozone.snapshot.CancelSnapshotDiffResponse;
+import org.apache.hadoop.ozone.snapshot.ListSnapshotResponse;
 import org.apache.hadoop.ozone.snapshot.SnapshotDiffResponse;
 import org.apache.hadoop.ozone.upgrade.UpgradeFinalizer.StatusAndMessages;
 import org.apache.hadoop.security.KerberosInfo;
@@ -742,12 +743,12 @@ public interface OzoneManagerProtocol
    * @param volumeName     volume name
    * @param bucketName     bucket name
    * @param snapshotPrefix snapshot prefix to match
-   * @param prevSnapshot   start of the list, this snapshot is excluded
-   * @param maxListResult  max numbet of snapshots to return
-   * @return list of snapshots for volume/bucket snapshotpath.
+   * @param prevSnapshot   snapshots will be listed after this snapshot name
+   * @param maxListResult  max number of snapshots to return
+   * @return list of snapshots for volume/bucket path.
    * @throws IOException
    */
-  default List<SnapshotInfo> listSnapshot(
+  default ListSnapshotResponse listSnapshot(
       String volumeName, String bucketName, String snapshotPrefix,
       String prevSnapshot, int maxListResult) throws IOException {
     throw new UnsupportedOperationException("OzoneManager does not require " +
diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java
 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java
index 87e5079f1d..1208df0f28 100644
--- 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java
+++ 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java
@@ -223,6 +223,7 @@ import 
org.apache.hadoop.ozone.security.proto.SecurityProtos.CancelDelegationTok
 import 
org.apache.hadoop.ozone.security.proto.SecurityProtos.GetDelegationTokenRequestProto;
 import 
org.apache.hadoop.ozone.security.proto.SecurityProtos.RenewDelegationTokenRequestProto;
 import org.apache.hadoop.ozone.snapshot.CancelSnapshotDiffResponse;
+import org.apache.hadoop.ozone.snapshot.ListSnapshotResponse;
 import org.apache.hadoop.ozone.snapshot.SnapshotDiffReportOzone;
 import org.apache.hadoop.ozone.snapshot.SnapshotDiffResponse;
 import org.apache.hadoop.ozone.snapshot.SnapshotDiffResponse.JobStatus;
@@ -1320,7 +1321,7 @@ public final class 
OzoneManagerProtocolClientSideTranslatorPB
    * {@inheritDoc}
    */
   @Override
-  public List<SnapshotInfo> listSnapshot(
+  public ListSnapshotResponse listSnapshot(
       String volumeName, String bucketName, String snapshotPrefix,
       String prevSnapshot, int maxListResult) throws IOException {
     final OzoneManagerProtocolProtos.ListSnapshotRequest.Builder
@@ -1343,11 +1344,24 @@ public final class 
OzoneManagerProtocolClientSideTranslatorPB
         .build();
     final OMResponse omResponse = submitRequest(omRequest);
     handleError(omResponse);
-    List<SnapshotInfo> snapshotInfos = omResponse.getListSnapshotResponse()
+    OzoneManagerProtocolProtos.ListSnapshotResponse response = 
omResponse.getListSnapshotResponse();
+    List<SnapshotInfo> snapshotInfos = response
         .getSnapshotInfoList().stream()
         .map(snapshotInfo -> SnapshotInfo.getFromProtobuf(snapshotInfo))
         .collect(Collectors.toList());
-    return snapshotInfos;
+
+    String lastSnapshot = null;
+
+    if (response.hasLastSnapshot()) {
+      lastSnapshot = response.getLastSnapshot();
+    } else if (snapshotInfos.size() == maxListResult) {
+      // This is to make sure that the change (HDDS-9983) is forward 
compatibility.
+      // Set lastSnapshot only when current list size is equal to maxListResult
+      // and there is possibility of more entries.
+      lastSnapshot = snapshotInfos.get(maxListResult - 1).getName();
+    }
+
+    return new ListSnapshotResponse(snapshotInfos, lastSnapshot);
   }
 
   /**
diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/snapshot/ListSnapshotResponse.java
 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/snapshot/ListSnapshotResponse.java
new file mode 100644
index 0000000000..5dda8265f6
--- /dev/null
+++ 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/snapshot/ListSnapshotResponse.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.snapshot;
+
+import org.apache.hadoop.ozone.om.helpers.SnapshotInfo;
+
+import java.util.List;
+
+/**
+ * POJO for list snapshot info API.
+ */
+public class ListSnapshotResponse {
+  private final List<SnapshotInfo> snapshotInfos;
+  private final String lastSnapshot;
+
+  public ListSnapshotResponse(List<SnapshotInfo> snapshotInfos, String 
lastSnapshot) {
+    this.snapshotInfos = snapshotInfos;
+    this.lastSnapshot = lastSnapshot;
+  }
+
+  public List<SnapshotInfo> getSnapshotInfos() {
+    return snapshotInfos;
+  }
+
+  public String getLastSnapshot() {
+    return lastSnapshot;
+  }
+
+  @Override
+  public String toString() {
+    return "ListSnapshotResponse{" +
+        "snapshotInfos: '" + snapshotInfos + '\'' +
+        ", lastSnapshot: '" + lastSnapshot + '\'' +
+        '}';
+  }
+}
diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/TestOzoneRpcClientAbstract.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/TestOzoneRpcClientAbstract.java
index d96d8d0cae..8e22335759 100644
--- 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/TestOzoneRpcClientAbstract.java
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/TestOzoneRpcClientAbstract.java
@@ -4373,7 +4373,6 @@ public abstract class TestOzoneRpcClientAbstract {
   }
 
   @Test
-  @Flaky("HDDS-9967")
   public void testListSnapshot() throws IOException {
     String volumeA = "vol-a-" + RandomStringUtils.randomNumeric(5);
     String volumeB = "vol-b-" + RandomStringUtils.randomNumeric(5);
@@ -4410,11 +4409,10 @@ public abstract class TestOzoneRpcClientAbstract {
           snapshotPrefixB + i + "-" + RandomStringUtils.randomNumeric(5));
     }
 
-    Iterator<? extends OzoneSnapshot> snapshotIter =
-        store.listSnapshot(volumeA, bucketA, null, null);
+    Iterator<OzoneSnapshot> snapshotIter = store.listSnapshot(volumeA, 
bucketA, null, null);
     int volABucketASnapshotCount = 0;
     while (snapshotIter.hasNext()) {
-      OzoneSnapshot snapshot = snapshotIter.next();
+      snapshotIter.next();
       volABucketASnapshotCount++;
     }
     assertEquals(20, volABucketASnapshotCount);
@@ -4422,26 +4420,26 @@ public abstract class TestOzoneRpcClientAbstract {
     snapshotIter = store.listSnapshot(volumeA, bucketB, null, null);
     int volABucketBSnapshotCount = 0;
     while (snapshotIter.hasNext()) {
-      OzoneSnapshot snapshot = snapshotIter.next();
+      snapshotIter.next();
       volABucketBSnapshotCount++;
     }
-    assertEquals(20, volABucketASnapshotCount);
+    assertEquals(20, volABucketBSnapshotCount);
 
     snapshotIter = store.listSnapshot(volumeB, bucketA, null, null);
     int volBBucketASnapshotCount = 0;
     while (snapshotIter.hasNext()) {
-      OzoneSnapshot snapshot = snapshotIter.next();
+      snapshotIter.next();
       volBBucketASnapshotCount++;
     }
-    assertEquals(20, volABucketASnapshotCount);
+    assertEquals(20, volBBucketASnapshotCount);
 
     snapshotIter = store.listSnapshot(volumeB, bucketB, null, null);
     int volBBucketBSnapshotCount = 0;
     while (snapshotIter.hasNext()) {
-      OzoneSnapshot snapshot = snapshotIter.next();
+      snapshotIter.next();
       volBBucketBSnapshotCount++;
     }
-    assertEquals(20, volABucketASnapshotCount);
+    assertEquals(20, volBBucketBSnapshotCount);
 
     int volABucketASnapshotACount = 0;
     snapshotIter = store.listSnapshot(volumeA, bucketA, snapshotPrefixA, null);
diff --git 
a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto 
b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
index e4559ad735..48b64b71dd 100644
--- a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
+++ b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
@@ -1982,6 +1982,7 @@ message CreateSnapshotResponse {
 
 message ListSnapshotResponse {
   repeated SnapshotInfo snapshotInfo = 1;
+  optional string lastSnapshot = 2;
 }
 
 message SnapshotDiffResponse {
diff --git 
a/hadoop-ozone/interface-storage/src/main/java/org/apache/hadoop/ozone/om/OMMetadataManager.java
 
b/hadoop-ozone/interface-storage/src/main/java/org/apache/hadoop/ozone/om/OMMetadataManager.java
index f00383cc1b..5a8da0106f 100644
--- 
a/hadoop-ozone/interface-storage/src/main/java/org/apache/hadoop/ozone/om/OMMetadataManager.java
+++ 
b/hadoop-ozone/interface-storage/src/main/java/org/apache/hadoop/ozone/om/OMMetadataManager.java
@@ -46,6 +46,7 @@ import org.apache.hadoop.ozone.om.helpers.BucketLayout;
 import org.apache.hadoop.ozone.om.lock.IOzoneManagerLock;
 import org.apache.hadoop.hdds.utils.TransactionInfo;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.ExpiredMultipartUploadsBucket;
+import org.apache.hadoop.ozone.snapshot.ListSnapshotResponse;
 import org.apache.hadoop.ozone.storage.proto.
     OzoneManagerStorageProtos.PersistedUserVolumeInfo;
 import org.apache.hadoop.ozone.security.OzoneTokenIdentifier;
@@ -256,11 +257,11 @@ public interface OMMetadataManager extends 
DBStoreHAManager {
    * @param volumeName     volume name
    * @param bucketName     bucket name
    * @param snapshotPrefix snapshot prefix to match
-   * @param prevSnapshot   start of the list, this snapshot is excluded
-   * @param maxListResult  max numbet of snapshots to return
+   * @param prevSnapshot   snapshots will be listed after this snapshot name
+   * @param maxListResult  max number of snapshots to return
    * @return list of snapshot
    */
-  List<SnapshotInfo> listSnapshot(
+  ListSnapshotResponse listSnapshot(
       String volumeName, String bucketName, String snapshotPrefix,
       String prevSnapshot, int maxListResult) throws IOException;
 
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataManagerImpl.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataManagerImpl.java
index e35ab118e6..6e25dc1f7f 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataManagerImpl.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataManagerImpl.java
@@ -93,6 +93,7 @@ import org.apache.hadoop.ozone.om.snapshot.SnapshotUtils;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.ExpiredMultipartUploadInfo;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.ExpiredMultipartUploadsBucket;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.KeyArgs;
+import org.apache.hadoop.ozone.snapshot.ListSnapshotResponse;
 import org.apache.hadoop.ozone.storage.proto
     .OzoneManagerStorageProtos.PersistedUserVolumeInfo;
 import org.apache.hadoop.ozone.security.OzoneTokenIdentifier;
@@ -1314,7 +1315,7 @@ public class OmMetadataManagerImpl implements 
OMMetadataManager,
   }
 
   @Override
-  public List<SnapshotInfo> listSnapshot(
+  public ListSnapshotResponse listSnapshot(
       String volumeName, String bucketName, String snapshotPrefix,
       String prevSnapshot, int maxListResult) throws IOException {
     if (Strings.isNullOrEmpty(volumeName)) {
@@ -1327,8 +1328,7 @@ public class OmMetadataManagerImpl implements 
OMMetadataManager,
 
     String bucketNameBytes = getBucketKey(volumeName, bucketName);
     if (getBucketTable().get(bucketNameBytes) == null) {
-      throw new OMException("Bucket " + bucketName + " not found.",
-          BUCKET_NOT_FOUND);
+      throw new OMException("Bucket " + bucketName + " not found.", 
BUCKET_NOT_FOUND);
     }
 
     String prefix;
@@ -1345,30 +1345,30 @@ public class OmMetadataManagerImpl implements 
OMMetadataManager,
     } else {
       // This allows us to seek directly to the first key with the right 
prefix.
       seek = getOzoneKey(volumeName, bucketName,
-          StringUtil.isNotBlank(
-              snapshotPrefix) ? snapshotPrefix : OM_KEY_PREFIX);
+          StringUtil.isNotBlank(snapshotPrefix) ? snapshotPrefix : 
OM_KEY_PREFIX);
     }
 
     List<SnapshotInfo> snapshotInfos =  Lists.newArrayList();
+    String lastSnapshot = null;
     try (ListIterator.MinHeapIterator snapshotIterator =
-        new ListIterator.MinHeapIterator(this, prefix, seek, volumeName,
-            bucketName, snapshotInfoTable)) {
-      try {
-        while (snapshotIterator.hasNext() && maxListResult > 0) {
-          SnapshotInfo snapshotInfo =
-              (SnapshotInfo) snapshotIterator.next().getValue();
-          if (!snapshotInfo.getName().equals(prevSnapshot)) {
-            snapshotInfos.add(snapshotInfo);
-            maxListResult--;
-          }
+             new ListIterator.MinHeapIterator(this, prefix, seek, volumeName, 
bucketName, snapshotInfoTable)) {
+      SnapshotInfo snapshotInfo = null;
+      while (snapshotIterator.hasNext() && maxListResult > 0) {
+        snapshotInfo = (SnapshotInfo) snapshotIterator.next().getValue();
+        if (!Objects.equals(snapshotInfo.getName(), prevSnapshot)) {
+          snapshotInfos.add(snapshotInfo);
+          maxListResult--;
         }
-      } catch (NoSuchElementException e) {
-        throw new IOException(e);
-      } catch (UncheckedIOException e) {
-        throw e.getCause();
       }
+      if (snapshotIterator.hasNext() && maxListResult == 0 && snapshotInfo != 
null) {
+        lastSnapshot = snapshotInfo.getName();
+      }
+    } catch (NoSuchElementException e) {
+      throw new IOException(e);
+    } catch (UncheckedIOException e) {
+      throw e.getCause();
     }
-    return snapshotInfos;
+    return new ListSnapshotResponse(snapshotInfos, lastSnapshot);
   }
 
   @Override
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
index 9e12941a55..954d942a53 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
@@ -105,6 +105,7 @@ import org.apache.hadoop.ozone.om.snapshot.ReferenceCounted;
 import org.apache.hadoop.ozone.om.upgrade.OMLayoutFeature;
 import org.apache.hadoop.ozone.security.acl.OzoneAuthorizerFactory;
 import org.apache.hadoop.ozone.snapshot.CancelSnapshotDiffResponse;
+import org.apache.hadoop.ozone.snapshot.ListSnapshotResponse;
 import org.apache.hadoop.ozone.snapshot.SnapshotDiffResponse;
 import org.apache.hadoop.ozone.util.OzoneNetUtils;
 import org.apache.hadoop.ozone.om.helpers.BucketLayout;
@@ -2991,7 +2992,7 @@ public final class OzoneManager extends 
ServiceRuntimeInfoImpl
   }
 
   @Override
-  public List<SnapshotInfo> listSnapshot(
+  public ListSnapshotResponse listSnapshot(
       String volumeName, String bucketName, String snapshotPrefix,
       String prevSnapshot, int maxListResult) throws IOException {
     metrics.incNumSnapshotLists();
@@ -3002,13 +3003,13 @@ public final class OzoneManager extends 
ServiceRuntimeInfoImpl
         omMetadataReader.checkAcls(ResourceType.BUCKET, StoreType.OZONE,
             ACLType.LIST, volumeName, bucketName, null);
       }
-      List<SnapshotInfo> snapshotInfoList =
+      ListSnapshotResponse listSnapshotResponse =
           metadataManager.listSnapshot(volumeName, bucketName,
               snapshotPrefix, prevSnapshot, maxListResult);
 
       AUDIT.logReadSuccess(buildAuditMessageForSuccess(
           OMAction.LIST_SNAPSHOT, auditMap));
-      return snapshotInfoList;
+      return listSnapshotResponse;
     } catch (Exception ex) {
       metrics.incNumSnapshotListFails();
       AUDIT.logReadFailure(buildAuditMessageForFailure(OMAction.LIST_SNAPSHOT,
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OzoneManagerRequestHandler.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OzoneManagerRequestHandler.java
index ca4925d3e5..5b8365e5a7 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OzoneManagerRequestHandler.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OzoneManagerRequestHandler.java
@@ -158,6 +158,7 @@ import static 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.
 import static 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.PartInfo;
 import static org.apache.hadoop.util.MetricUtil.captureLatencyNs;
 
+import org.apache.hadoop.ozone.snapshot.ListSnapshotResponse;
 import org.apache.hadoop.ozone.upgrade.UpgradeFinalizer.StatusAndMessages;
 import org.apache.hadoop.util.ProtobufUtils;
 import org.apache.ratis.server.protocol.TermIndex;
@@ -1450,14 +1451,19 @@ public class OzoneManagerRequestHandler implements 
RequestHandler {
   private OzoneManagerProtocolProtos.ListSnapshotResponse getSnapshots(
       OzoneManagerProtocolProtos.ListSnapshotRequest request)
       throws IOException {
-    List<SnapshotInfo> snapshotInfos = impl.listSnapshot(
+    ListSnapshotResponse implResponse = impl.listSnapshot(
         request.getVolumeName(), request.getBucketName(), request.getPrefix(),
         request.getPrevSnapshot(), request.getMaxListResult());
-    List<OzoneManagerProtocolProtos.SnapshotInfo> snapshotInfoList =
-        snapshotInfos.stream().map(SnapshotInfo::getProtobuf)
-            .collect(Collectors.toList());
-    return OzoneManagerProtocolProtos.ListSnapshotResponse.newBuilder()
-        .addAllSnapshotInfo(snapshotInfoList).build();
+
+    List<OzoneManagerProtocolProtos.SnapshotInfo> snapshotInfoList = 
implResponse.getSnapshotInfos()
+        .stream().map(SnapshotInfo::getProtobuf).collect(Collectors.toList());
+
+    OzoneManagerProtocolProtos.ListSnapshotResponse.Builder builder =
+        
OzoneManagerProtocolProtos.ListSnapshotResponse.newBuilder().addAllSnapshotInfo(snapshotInfoList);
+    if (StringUtils.isNotEmpty(implResponse.getLastSnapshot())) {
+      builder.setLastSnapshot(implResponse.getLastSnapshot());
+    }
+    return builder.build();
   }
 
   private TransferLeadershipResponseProto transferLeadership(
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOmMetadataManager.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOmMetadataManager.java
index 1e2fb6a60a..5239fa0090 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOmMetadataManager.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOmMetadataManager.java
@@ -40,6 +40,7 @@ import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Expired
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OpenKey;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OpenKeyBucket;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.PartKeyInfo;
+import org.apache.hadoop.ozone.snapshot.ListSnapshotResponse;
 import org.apache.hadoop.util.Time;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.BeforeEach;
@@ -76,6 +77,7 @@ import static 
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.VOLU
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.params.provider.Arguments.arguments;
@@ -899,68 +901,59 @@ public class TestOmMetadataManager {
     for (int i = 1; i <= 100; i++) {
       if (i % 2 == 0) {
         snapshotsASnapshotIDMap.put(prefixA + i,
-            OMRequestTestUtils.addSnapshotToTable(vol1, bucket1,
-            prefixA + i, omMetadataManager));
+            OMRequestTestUtils.addSnapshotToTable(vol1, bucket1, prefixA + i, 
omMetadataManager));
         if (i % 4 == 0) {
           snapshotsASnapshotIDMap.put(prefixA + i,
-              OMRequestTestUtils.addSnapshotToTableCache(vol1, bucket1,
-              prefixA + i, omMetadataManager));
+              OMRequestTestUtils.addSnapshotToTableCache(vol1, bucket1, 
prefixA + i, omMetadataManager));
         }
       } else {
-        OMRequestTestUtils.addSnapshotToTableCache(vol1, bucket1,
-            prefixB + i, omMetadataManager);
+        OMRequestTestUtils.addSnapshotToTableCache(vol1, bucket1, prefixB + i, 
omMetadataManager);
       }
     }
 
     //Test listing all snapshots.
-    List<SnapshotInfo> snapshotInfos = omMetadataManager.listSnapshot(vol1,
-        bucket1, null, null, 100);
+    List<SnapshotInfo> snapshotInfos = omMetadataManager.listSnapshot(vol1, 
bucket1, null, null, 100)
+        .getSnapshotInfos();
     assertEquals(100, snapshotInfos.size());
 
-    snapshotInfos = omMetadataManager.listSnapshot(vol1,
-        bucket1, prefixA, null, 50);
+    snapshotInfos = omMetadataManager.listSnapshot(vol1, bucket1, prefixA, 
null, 50)
+        .getSnapshotInfos();
     assertEquals(50, snapshotInfos.size());
     for (SnapshotInfo snapshotInfo : snapshotInfos) {
       assertTrue(snapshotInfo.getName().startsWith(prefixA));
     }
 
     String startSnapshot = prefixA + 38;
-    snapshotInfos = omMetadataManager.listSnapshot(vol1,
-        bucket1, prefixA, startSnapshot, 50);
-    assertEquals(snapshotsASnapshotIDMap.tailMap(startSnapshot).size() - 1,
-        snapshotInfos.size());
+    snapshotInfos = omMetadataManager.listSnapshot(vol1, bucket1, prefixA, 
startSnapshot, 50)
+        .getSnapshotInfos();
+    
System.out.println(snapshotInfos.stream().map(SnapshotInfo::getName).collect(Collectors.joining(",")));
+    assertEquals(snapshotsASnapshotIDMap.tailMap(startSnapshot).size() - 1, 
snapshotInfos.size());
     for (SnapshotInfo snapshotInfo : snapshotInfos) {
       assertTrue(snapshotInfo.getName().startsWith(prefixA));
-      assertEquals(snapshotInfo, snapshotsASnapshotIDMap.get(
-          snapshotInfo.getName()));
-      
assertThat(snapshotInfo.getName().compareTo(startSnapshot)).isGreaterThan(0);
+      assertEquals(snapshotInfo, 
snapshotsASnapshotIDMap.get(snapshotInfo.getName()));
+      
assertThat(snapshotInfo.getName().compareTo(startSnapshot)).isGreaterThanOrEqualTo(0);
     }
 
-    startSnapshot = null;
+    String lastSnapshot = null;
     TreeSet<String> expectedSnapshot = new TreeSet<>();
     for (int i = 1; i <= 5; i++) {
-      snapshotInfos = omMetadataManager.listSnapshot(
-          vol1, bucket1, prefixA, startSnapshot, 10);
+      ListSnapshotResponse listSnapshotResponse =
+          omMetadataManager.listSnapshot(vol1, bucket1, prefixA, lastSnapshot, 
10);
+      snapshotInfos = listSnapshotResponse.getSnapshotInfos();
+      lastSnapshot = listSnapshotResponse.getLastSnapshot();
       assertEquals(10, snapshotInfos.size());
 
       for (SnapshotInfo snapshotInfo : snapshotInfos) {
         expectedSnapshot.add(snapshotInfo.getName());
-        assertEquals(snapshotInfo, snapshotsASnapshotIDMap.get(
-            snapshotInfo.getName()));
+        assertEquals(snapshotInfo, 
snapshotsASnapshotIDMap.get(snapshotInfo.getName()));
         assertTrue(snapshotInfo.getName().startsWith(prefixA));
-        startSnapshot = snapshotInfo.getName();
       }
     }
     assertEquals(snapshotsASnapshotIDMap.keySet(), expectedSnapshot);
-
-    // As now we have iterated all 50 snapshots, calling next time should
-    // return empty list.
-    snapshotInfos = omMetadataManager.listSnapshot(vol1, bucket1,
-        prefixA, startSnapshot, 10);
-
-    assertEquals(snapshotInfos.size(), 0);
+    assertNull(lastSnapshot);
   }
 
+
   @ParameterizedTest
   @MethodSource("listSnapshotWithInvalidPathCases")
   public void testListSnapshotWithInvalidPath(String volume,
@@ -1010,15 +1003,15 @@ public class TestOmMetadataManager {
     }
 
     //Test listing snapshots only lists snapshots of specified bucket
-    List<SnapshotInfo> snapshotInfos1 = omMetadataManager.listSnapshot(vol1,
-            bucket1, null, null, Integer.MAX_VALUE);
+    List<SnapshotInfo> snapshotInfos1 = omMetadataManager.listSnapshot(vol1, 
bucket1, null, null, Integer.MAX_VALUE)
+        .getSnapshotInfos();
     assertEquals(2, snapshotInfos1.size());
     for (SnapshotInfo snapshotInfo : snapshotInfos1) {
       assertTrue(snapshotInfo.getName().startsWith(snapshotName1));
     }
 
-    List<SnapshotInfo> snapshotInfos2 = omMetadataManager.listSnapshot(vol1,
-            bucket2, null, null, Integer.MAX_VALUE);
+    List<SnapshotInfo> snapshotInfos2 = omMetadataManager.listSnapshot(vol1, 
bucket2, null, null, Integer.MAX_VALUE)
+        .getSnapshotInfos();
     assertEquals(5, snapshotInfos2.size());
     for (SnapshotInfo snapshotInfo : snapshotInfos2) {
       assertTrue(snapshotInfo.getName().startsWith(snapshotName2));
diff --git 
a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneClientAdapterImpl.java
 
b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneClientAdapterImpl.java
index af741994b7..109e19d13c 100644
--- 
a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneClientAdapterImpl.java
+++ 
b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneClientAdapterImpl.java
@@ -819,8 +819,7 @@ public class BasicRootedOzoneClientAdapterImpl
     String group = getGroupName(ugi);
     List<FileStatusAdapter> res = new ArrayList<>();
 
-    Iterator<? extends OzoneSnapshot> snapshotIter =
-        objectStore.listSnapshot(volumeName, bucketName, null, null);
+    Iterator<OzoneSnapshot> snapshotIter = 
objectStore.listSnapshot(volumeName, bucketName, null, null);
 
     while (snapshotIter.hasNext()) {
       OzoneSnapshot ozoneSnapshot = snapshotIter.next();
diff --git 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/ClientProtocolStub.java
 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/ClientProtocolStub.java
index c675a9ba6a..e70e221559 100644
--- 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/ClientProtocolStub.java
+++ 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/ClientProtocolStub.java
@@ -51,6 +51,7 @@ import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
 import org.apache.hadoop.ozone.security.OzoneTokenIdentifier;
 import org.apache.hadoop.ozone.security.acl.OzoneObj;
 import org.apache.hadoop.ozone.snapshot.CancelSnapshotDiffResponse;
+import org.apache.hadoop.ozone.snapshot.ListSnapshotResponse;
 import org.apache.hadoop.ozone.snapshot.SnapshotDiffResponse;
 import org.apache.hadoop.security.token.Token;
 
@@ -693,7 +694,7 @@ public class ClientProtocolStub implements ClientProtocol {
   }
 
   @Override
-  public List<OzoneSnapshot> listSnapshot(
+  public ListSnapshotResponse listSnapshot(
       String volumeName, String bucketName, String snapshotPrefix,
       String prevSnapshot, int maxListResult) throws IOException {
     return null;
diff --git 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/snapshot/ListSnapshotHandler.java
 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/snapshot/ListSnapshotHandler.java
index bfbaa6b6c3..4744d0dcf3 100644
--- 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/snapshot/ListSnapshotHandler.java
+++ 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/snapshot/ListSnapshotHandler.java
@@ -54,7 +54,7 @@ public class ListSnapshotHandler extends Handler {
     String volumeName = snapshotPath.getValue().getVolumeName();
     String bucketName = snapshotPath.getValue().getBucketName();
 
-    Iterator<? extends OzoneSnapshot> snapshotInfos = client.getObjectStore()
+    Iterator<OzoneSnapshot> snapshotInfos = client.getObjectStore()
         .listSnapshot(volumeName, bucketName, listOptions.getPrefix(),
             listOptions.getStartItem());
     int counter = printAsJsonArray(snapshotInfos, listOptions.getLimit());


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to