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

siyao pushed a commit to branch HDDS-6517-Snapshot
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/HDDS-6517-Snapshot by this 
push:
     new 125eccfef2 HDDS-7607. [Snapshot] SnapshotDiff command lists unmodified 
file as modified (#4108)
125eccfef2 is described below

commit 125eccfef261f598ae65d353c3d0f2e32bbae43b
Author: Sadanand Shenoy <[email protected]>
AuthorDate: Wed Jan 4 01:02:32 2023 +0530

    HDDS-7607. [Snapshot] SnapshotDiff command lists unmodified file as 
modified (#4108)
---
 .../org/apache/ozone/rocksdb/util/RdbUtil.java     |  25 +++--
 .../ozone/om/helpers/OmKeyLocationInfoGroup.java   |  19 ++++
 .../hadoop/ozone/snapshot/SnapshotDiffReport.java  |   6 +-
 .../hadoop/ozone/om/helpers/TestOmKeyInfo.java     |   6 +-
 .../org/apache/hadoop/ozone/om/TestOmSnapshot.java |  99 ++++++++++++++++---
 .../ozone/om/snapshot/SnapshotDiffManager.java     | 106 +++++++++++++++------
 6 files changed, 201 insertions(+), 60 deletions(-)

diff --git 
a/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdb/util/RdbUtil.java
 
b/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdb/util/RdbUtil.java
index 15757df7b5..fbf6e731de 100644
--- 
a/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdb/util/RdbUtil.java
+++ 
b/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdb/util/RdbUtil.java
@@ -40,21 +40,20 @@ public final class RdbUtil {
 
   private RdbUtil() { }
 
-  public static Set<String> getKeyTableSSTFiles(final String dbLocation)
-      throws RocksDBException {
-    final List<ColumnFamilyHandle> columnFamilyHandles  = new ArrayList<>();
+  public static Set<String> getSSTFilesForComparison(final String dbLocation,
+      List<String> cfs) throws RocksDBException {
+    final List<ColumnFamilyHandle> columnFamilyHandles = new ArrayList<>();
     final List<ColumnFamilyDescriptor> cfd = new ArrayList<>();
+    for (String columnFamily : cfs) {
+      cfd.add(new ColumnFamilyDescriptor(
+          columnFamily.getBytes(StandardCharsets.UTF_8)));
+    }
     cfd.add(
-        new ColumnFamilyDescriptor(
-            "keyTable".getBytes(StandardCharsets.UTF_8)));
-    cfd.add(
-        new ColumnFamilyDescriptor(
-            "default".getBytes(StandardCharsets.UTF_8)));
-    try (DBOptions options = new DBOptions();
-         RocksDB rocksDB = RocksDB.openReadOnly(options, dbLocation,
-             cfd, columnFamilyHandles)) {
-      return rocksDB.getLiveFilesMetaData().stream().map(lfm ->
-              new File(lfm.path(), lfm.fileName()).getPath())
+        new 
ColumnFamilyDescriptor("default".getBytes(StandardCharsets.UTF_8)));
+    try (DBOptions options = new DBOptions(); RocksDB rocksDB = RocksDB
+        .openReadOnly(options, dbLocation, cfd, columnFamilyHandles)) {
+      return rocksDB.getLiveFilesMetaData().stream()
+          .map(lfm -> new File(lfm.path(), lfm.fileName()).getPath())
           .collect(Collectors.toCollection(HashSet::new));
     }
   }
diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/OmKeyLocationInfoGroup.java
 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/OmKeyLocationInfoGroup.java
index 2f02db087e..991f366664 100644
--- 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/OmKeyLocationInfoGroup.java
+++ 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/OmKeyLocationInfoGroup.java
@@ -16,6 +16,7 @@
  */
 package org.apache.hadoop.ozone.om.helpers;
 
+import com.google.common.base.Objects;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.KeyLocationList;
 
@@ -186,4 +187,22 @@ public class OmKeyLocationInfoGroup {
     }
     return sb.toString();
   }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    OmKeyLocationInfoGroup that = (OmKeyLocationInfoGroup) o;
+    return version == that.version && isMultipartKey == that.isMultipartKey
+        && Objects.equal(locationVersionMap, that.locationVersionMap);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(version, locationVersionMap, isMultipartKey);
+  }
 }
diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/snapshot/SnapshotDiffReport.java
 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/snapshot/SnapshotDiffReport.java
index 24b8af76c5..e8e387099c 100644
--- 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/snapshot/SnapshotDiffReport.java
+++ 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/snapshot/SnapshotDiffReport.java
@@ -126,9 +126,9 @@ public class SnapshotDiffReport {
       }
       if (other instanceof DiffReportEntry) {
         DiffReportEntry entry = (DiffReportEntry) other;
-        return type.equals(entry.getType())
-            && sourcePath.equals(entry.sourcePath)
-            && targetPath.equals(entry.targetPath);
+        return this.type.equals(entry.getType()) && this.sourcePath
+            .equals(entry.sourcePath) && (this.targetPath != null ?
+            this.targetPath.equals(entry.targetPath) : true);
       }
       return false;
     }
diff --git 
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestOmKeyInfo.java
 
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestOmKeyInfo.java
index 50218eaf9a..f4e5fc9932 100644
--- 
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestOmKeyInfo.java
+++ 
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestOmKeyInfo.java
@@ -143,9 +143,9 @@ public class TestOmKeyInfo {
 
     OmKeyInfo cloneKey = key.copyObject();
 
-    // Because for OmKeyLocationInfoGroup we have not implemented equals()
-    // method, so it checks only references.
-    Assert.assertNotEquals(key, cloneKey);
+    // OmKeyLocationInfoGroup has now implemented equals() method.
+    // assertEquals should work now.
+    Assert.assertEquals(key, cloneKey);
 
     // Check each version content here.
     Assert.assertEquals(key.getKeyLocationVersions().size(),
diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOmSnapshot.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOmSnapshot.java
index 2d62d22879..719b07c222 100644
--- 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOmSnapshot.java
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOmSnapshot.java
@@ -17,6 +17,7 @@
 
 package org.apache.hadoop.ozone.om;
 import java.util.List;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import org.apache.commons.lang3.RandomStringUtils;
 import org.apache.hadoop.hdds.client.StandaloneReplicationConfig;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
@@ -38,6 +39,7 @@ import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
 import org.apache.hadoop.ozone.om.helpers.OzoneFileStatus;
 import org.apache.hadoop.ozone.om.helpers.SnapshotInfo;
 import org.apache.hadoop.ozone.om.protocol.OzoneManagerProtocol;
+import org.apache.hadoop.ozone.snapshot.SnapshotDiffReport;
 import org.apache.ozone.test.GenericTestUtils;
 import org.apache.ozone.test.LambdaTestUtils;
 import org.junit.Assert;
@@ -74,6 +76,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
  * Test OmSnapshot bucket interface.
  */
 @RunWith(Parameterized.class)
+@SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT")
 public class TestOmSnapshot {
   private static MiniOzoneCluster cluster = null;
   private static String volumeName;
@@ -396,23 +399,90 @@ public class TestOmSnapshot {
             () -> createSnapshot(volume, bucket));
   }
 
+  @Test
+  public void testSnapDiff() throws Exception {
+    String volume = "vol-" + RandomStringUtils.randomNumeric(5);
+    String bucket = "buck-" + RandomStringUtils.randomNumeric(5);
+    store.createVolume(volume);
+    OzoneVolume volume1 = store.getVolume(volume);
+    volume1.createBucket(bucket);
+    OzoneBucket bucket1 = volume1.getBucket(bucket);
+    // Create Key1 and take snapshot
+    String key1 = "key-1-";
+    key1 = createFileKey(bucket1, key1);
+    String snap1 = "snap" + RandomStringUtils.randomNumeric(5);
+    createSnapshot(volume, bucket, snap1);
+    // Do nothing, take another snapshot
+    String snap2 = "snap" + RandomStringUtils.randomNumeric(5);
+    createSnapshot(volume, bucket, snap2);
+    SnapshotDiffReport diff1 = store.snapshotDiff(volume, bucket, snap1, 
snap2);
+    Assert.assertTrue(diff1.getDiffList().isEmpty());
+    // Create Key2 and delete Key1, take snapshot
+    String key2 = "key-2-";
+    key2 = createFileKey(bucket1, key2);
+    bucket1.deleteKey(key1);
+    String snap3 = "snap" + RandomStringUtils.randomNumeric(5);
+    createSnapshot(volume, bucket, snap3);
+    // Diff should have 2 entries
+    SnapshotDiffReport diff2 = store.snapshotDiff(volume, bucket, snap2, 
snap3);
+    Assert.assertEquals(2, diff2.getDiffList().size());
+    Assert.assertTrue(diff2.getDiffList().contains(
+        SnapshotDiffReport.DiffReportEntry
+            .of(SnapshotDiffReport.DiffType.CREATE, key2)));
+    Assert.assertTrue(diff2.getDiffList().contains(
+        SnapshotDiffReport.DiffReportEntry
+            .of(SnapshotDiffReport.DiffType.DELETE, key1)));
+
+    // Rename Key2
+    String key2Renamed = key2 + "_renamed";
+    bucket1.renameKey(key2, key2Renamed);
+    String snap4 = "snap" + RandomStringUtils.randomNumeric(5);
+    createSnapshot(volume, bucket, snap4);
+    SnapshotDiffReport diff3 = store.snapshotDiff(volume, bucket, snap3, 
snap4);
+    Assert.assertEquals(1, diff3.getDiffList().size());
+    Assert.assertTrue(diff3.getDiffList().contains(
+        SnapshotDiffReport.DiffReportEntry
+            .of(SnapshotDiffReport.DiffType.RENAME, key2, key2Renamed)));
+
+
+    // Create a directory
+    String dir1 = "dir-1" +  RandomStringUtils.randomNumeric(5);
+    bucket1.createDirectory(dir1);
+    String snap5 = "snap" + RandomStringUtils.randomNumeric(5);
+    createSnapshot(volume, bucket, snap5);
+    SnapshotDiffReport diff4 = store.snapshotDiff(volume, bucket, snap4, 
snap5);
+    Assert.assertEquals(1, diff4.getDiffList().size());
+    // for non-fso, directories are a special type of key with "/" appended
+    // at the end.
+    if (!bucket1.getBucketLayout().isFileSystemOptimized()) {
+      dir1 = dir1 + OM_KEY_PREFIX;
+    }
+    Assert.assertTrue(diff4.getDiffList().contains(
+        SnapshotDiffReport.DiffReportEntry
+            .of(SnapshotDiffReport.DiffType.CREATE, dir1)));
+
+  }
+
   private String createSnapshot(String volName, String buckName)
       throws IOException, InterruptedException, TimeoutException {
-    String snapshotName = UUID.randomUUID().toString();
+    return createSnapshot(volName, buckName, UUID.randomUUID().toString());
+  }
+
+  private String createSnapshot(String volName, String buckName,
+      String snapshotName)
+      throws IOException, InterruptedException, TimeoutException {
     store.createSnapshot(volName, buckName, snapshotName);
-    String snapshotKeyPrefix = OmSnapshotManager
-        .getSnapshotPrefix(snapshotName);
-    SnapshotInfo snapshotInfo = leaderOzoneManager
-        .getMetadataManager()
-        .getSnapshotInfoTable()
-        .get(SnapshotInfo.getTableKey(volName, buckName, snapshotName));
-    String snapshotDirName = metaDir + OM_KEY_PREFIX +
-        OM_SNAPSHOT_DIR + OM_KEY_PREFIX + OM_DB_NAME +
-        snapshotInfo.getCheckpointDirName() + OM_KEY_PREFIX + "CURRENT";
-    GenericTestUtils.waitFor(() -> new File(snapshotDirName).exists(),
-        1000, 120000);
+    String snapshotKeyPrefix =
+        OmSnapshotManager.getSnapshotPrefix(snapshotName);
+    SnapshotInfo snapshotInfo =
+        leaderOzoneManager.getMetadataManager().getSnapshotInfoTable()
+            .get(SnapshotInfo.getTableKey(volName, buckName, snapshotName));
+    String snapshotDirName =
+        metaDir + OM_KEY_PREFIX + OM_SNAPSHOT_DIR + OM_KEY_PREFIX + OM_DB_NAME
+            + snapshotInfo.getCheckpointDirName() + OM_KEY_PREFIX + "CURRENT";
+    GenericTestUtils
+        .waitFor(() -> new File(snapshotDirName).exists(), 1000, 120000);
     return snapshotKeyPrefix;
-
   }
 
   private void deleteKeys(OzoneBucket bucket) throws IOException {
@@ -423,13 +493,14 @@ public class TestOmSnapshot {
     }
   }
 
-  private void createFileKey(OzoneBucket bucket, String keyPrefix)
+  private String createFileKey(OzoneBucket bucket, String keyPrefix)
       throws IOException {
     byte[] value = RandomStringUtils.randomAscii(10240).getBytes(UTF_8);
     String key = keyPrefix + RandomStringUtils.randomNumeric(5);
     OzoneOutputStream fileKey = bucket.createKey(key, value.length);
     fileKey.write(value);
     fileKey.close();
+    return key;
   }
 
   @Test
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 c1b811ffa4..ec70478312 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
@@ -22,17 +22,21 @@ import org.apache.hadoop.hdds.utils.db.Table;
 import org.apache.hadoop.ozone.om.OMMetadataManager;
 import org.apache.hadoop.ozone.om.OmSnapshot;
 import org.apache.hadoop.ozone.om.helpers.BucketLayout;
+import org.apache.hadoop.ozone.om.helpers.OmDirectoryInfo;
 import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
+import org.apache.hadoop.ozone.om.helpers.WithObjectID;
 import org.apache.hadoop.ozone.snapshot.SnapshotDiffReport;
 import org.apache.hadoop.ozone.snapshot.SnapshotDiffReport.DiffType;
 import org.apache.hadoop.ozone.snapshot.SnapshotDiffReport.DiffReportEntry;
 
 import org.apache.ozone.rocksdb.util.ManagedSstFileReader;
 import org.apache.ozone.rocksdb.util.RdbUtil;
+import org.jetbrains.annotations.NotNull;
 import org.rocksdb.RocksDBException;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -54,28 +58,10 @@ public class SnapshotDiffManager {
     // TODO: Once RocksDBCheckpointDiffer exposes method to get list
     //  of delta SST files, plug it in here.
 
-    Set<String> fromSnapshotFiles = RdbUtil.getKeyTableSSTFiles(fromSnapshot
-        .getMetadataManager().getStore().getDbLocation().getPath());
-    Set<String> toSnapshotFiles = RdbUtil.getKeyTableSSTFiles(toSnapshot
-        .getMetadataManager().getStore().getDbLocation().getPath());
-
-    final Set<String> deltaFiles = new HashSet<>();
-    deltaFiles.addAll(fromSnapshotFiles);
-    deltaFiles.addAll(toSnapshotFiles);
-
-    // TODO: Filter out the files.
-
-    final Stream<String> keysToCheck = new ManagedSstFileReader(deltaFiles)
-        .getKeyStream();
-
     final BucketLayout bucketLayout = getBucketLayout(volume, bucket,
         fromSnapshot.getMetadataManager());
 
-    final Table<String, OmKeyInfo> fsKeyTable = fromSnapshot
-        .getMetadataManager().getKeyTable(bucketLayout);
-    final Table<String, OmKeyInfo> tsKeyTable = toSnapshot
-        .getMetadataManager().getKeyTable(bucketLayout);
-
+    // TODO: Filter out the files.
     /*
      * The reason for having ObjectID to KeyName mapping instead of OmKeyInfo
      * is to reduce the memory footprint.
@@ -87,22 +73,65 @@ public class SnapshotDiffManager {
 
     final Set<Long> objectIDsToCheck = new HashSet<>();
 
+    // add to object ID map for key/file.
+
+    final Table<String, OmKeyInfo> fsKeyTable = fromSnapshot
+        .getMetadataManager().getKeyTable(bucketLayout);
+    final Table<String, OmKeyInfo> tsKeyTable = toSnapshot
+        .getMetadataManager().getKeyTable(bucketLayout);
+    final Set<String> deltaFilesForKeyOrFileTable =
+        getDeltaFiles(fromSnapshot, toSnapshot,
+            Collections.singletonList(fsKeyTable.getName()));
+
+    addToObjectIdMap(fsKeyTable, tsKeyTable, deltaFilesForKeyOrFileTable,
+        oldObjIdToKeyMap, newObjIdToKeyMap, objectIDsToCheck, false);
+
+    if (bucketLayout.isFileSystemOptimized()) {
+      // add to object ID map for directory.
+      final Table<String, OmDirectoryInfo> fsDirTable =
+          fromSnapshot.getMetadataManager().getDirectoryTable();
+      final Table<String, OmDirectoryInfo> tsDirTable =
+          toSnapshot.getMetadataManager().getDirectoryTable();
+      final Set<String> deltaFilesForDirTable =
+          getDeltaFiles(fromSnapshot, toSnapshot,
+              Collections.singletonList(fsDirTable.getName()));
+      addToObjectIdMap(fsDirTable, tsDirTable, deltaFilesForDirTable,
+          oldObjIdToKeyMap, newObjIdToKeyMap, objectIDsToCheck, true);
+    }
+
+    return new SnapshotDiffReport(volume, bucket, fromSnapshot.getName(),
+        toSnapshot.getName(), generateDiffReport(objectIDsToCheck,
+        oldObjIdToKeyMap, newObjIdToKeyMap));
+  }
+
+  private void addToObjectIdMap(Table<String, ? extends WithObjectID> fsTable,
+      Table<String, ? extends WithObjectID> tsTable, Set<String> deltaFiles,
+      Map<Long, String> oldObjIdToKeyMap, Map<Long, String> newObjIdToKeyMap,
+      Set<Long> objectIDsToCheck, boolean isDirectoryTable)
+      throws RocksDBException {
+    if (deltaFiles.isEmpty()) {
+      return;
+    }
+    final Stream<String> keysToCheck =
+        new ManagedSstFileReader(deltaFiles).getKeyStream();
     keysToCheck.forEach(key -> {
       try {
-        final OmKeyInfo oldKey = fsKeyTable.get(key);
-        final OmKeyInfo newKey = tsKeyTable.get(key);
+        final WithObjectID oldKey = fsTable.get(key);
+        final WithObjectID newKey = tsTable.get(key);
         if (areKeysEqual(oldKey, newKey)) {
           // We don't have to do anything.
           return;
         }
         if (oldKey != null) {
           final long oldObjId = oldKey.getObjectID();
-          oldObjIdToKeyMap.put(oldObjId, oldKey.getKeyName());
+          oldObjIdToKeyMap
+              .put(oldObjId, getKeyOrDirectoryName(isDirectoryTable, oldKey));
           objectIDsToCheck.add(oldObjId);
         }
         if (newKey != null) {
           final long newObjId = newKey.getObjectID();
-          newObjIdToKeyMap.put(newObjId, newKey.getKeyName());
+          newObjIdToKeyMap
+              .put(newObjId, getKeyOrDirectoryName(isDirectoryTable, newKey));
           objectIDsToCheck.add(newObjId);
         }
       } catch (IOException e) {
@@ -110,10 +139,33 @@ public class SnapshotDiffManager {
       }
     });
     keysToCheck.close();
+  }
 
-    return new SnapshotDiffReport(volume, bucket, fromSnapshot.getName(),
-        toSnapshot.getName(), generateDiffReport(objectIDsToCheck,
-        oldObjIdToKeyMap, newObjIdToKeyMap));
+  private String getKeyOrDirectoryName(boolean isDirectory,
+      WithObjectID object) {
+    if (isDirectory) {
+      OmDirectoryInfo directoryInfo = (OmDirectoryInfo) object;
+      return directoryInfo.getName();
+    }
+    OmKeyInfo keyInfo = (OmKeyInfo) object;
+    return keyInfo.getKeyName();
+  }
+
+  @NotNull
+  private Set<String> getDeltaFiles(OmSnapshot fromSnapshot,
+      OmSnapshot toSnapshot, List<String> tablesToLookUp)
+      throws RocksDBException {
+    Set<String> fromSnapshotFiles = RdbUtil.getSSTFilesForComparison(
+        fromSnapshot.getMetadataManager().getStore().getDbLocation().getPath(),
+        tablesToLookUp);
+    Set<String> toSnapshotFiles = RdbUtil.getSSTFilesForComparison(
+        toSnapshot.getMetadataManager().getStore().getDbLocation().getPath(),
+        tablesToLookUp);
+
+    final Set<String> deltaFiles = new HashSet<>();
+    deltaFiles.addAll(fromSnapshotFiles);
+    deltaFiles.addAll(toSnapshotFiles);
+    return deltaFiles;
   }
 
   private List<DiffReportEntry> generateDiffReport(
@@ -224,7 +276,7 @@ public class SnapshotDiffManager {
     return mManager.getBucketTable().get(bucketTableKey).getBucketLayout();
   }
 
-  private boolean areKeysEqual(OmKeyInfo oldKey, OmKeyInfo newKey) {
+  private boolean areKeysEqual(WithObjectID oldKey, WithObjectID newKey) {
     if (oldKey == null && newKey == null) {
       return true;
     }


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

Reply via email to