You are viewing a plain text version of this content. The canonical link for it is here.
Posted to common-commits@hadoop.apache.org by ae...@apache.org on 2019/02/19 06:37:47 UTC

[hadoop] branch trunk updated: HDDS-1085. Create an OM API to serve snapshots to Recon server. Contributed by Aravindan Vijayan.

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

aengineer pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/hadoop.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 588b4c4  HDDS-1085. Create an OM API to serve snapshots to Recon server. Contributed by Aravindan Vijayan.
588b4c4 is described below

commit 588b4c4d78d3cc7d684b07176dad9bd7ec603ff1
Author: Anu Engineer <ae...@apache.org>
AuthorDate: Mon Feb 18 22:35:36 2019 -0800

    HDDS-1085. Create an OM API to serve snapshots to Recon server.
    Contributed by Aravindan Vijayan.
---
 .../java/org/apache/hadoop/ozone/OzoneConsts.java  |   3 +
 .../hadoop/utils/db/DBCheckpointSnapshot.java      |  53 ++++++++
 .../java/org/apache/hadoop/utils/db/DBStore.java   |   7 +
 .../hadoop/utils/db/RDBCheckpointManager.java      | 130 +++++++++++++++++++
 .../java/org/apache/hadoop/utils/db/RDBStore.java  |  33 +++++
 .../org/apache/hadoop/utils/db/TestRDBStore.java   |  94 +++++++++-----
 hadoop-ozone/common/pom.xml                        |   4 +
 .../main/java/org/apache/hadoop/ozone/OmUtils.java |  62 +++++++++
 .../org/apache/hadoop/ozone/om/OMConfigKeys.java   |   6 +
 .../java/org/apache/hadoop/ozone/TestOmUtils.java  |  44 +++++++
 .../hadoop/ozone/om/OMDbSnapshotServlet.java       | 142 +++++++++++++++++++++
 .../org/apache/hadoop/ozone/om/OzoneManager.java   |   4 +
 .../hadoop/ozone/om/OzoneManagerHttpServer.java    |   1 +
 13 files changed, 553 insertions(+), 30 deletions(-)

diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java
index f44acfd..2931a54 100644
--- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java
@@ -114,10 +114,13 @@ public final class OzoneConsts {
   public static final String DN_CONTAINER_DB = "-dn-"+ CONTAINER_DB_SUFFIX;
   public static final String DELETED_BLOCK_DB = "deletedBlock.db";
   public static final String OM_DB_NAME = "om.db";
+  public static final String OM_DB_CHECKPOINTS_DIR_NAME = "om.db.checkpoints";
   public static final String OZONE_MANAGER_TOKEN_DB_NAME = "om-token.db";
   public static final String SCM_DB_NAME = "scm.db";
 
   public static final String STORAGE_DIR_CHUNKS = "chunks";
+  public static final String OZONE_DB_CHECKPOINT_REQUEST_FLUSH =
+      "flushBeforeCheckpoint";
 
   /**
    * Supports Bucket Versioning.
diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/DBCheckpointSnapshot.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/DBCheckpointSnapshot.java
new file mode 100644
index 0000000..afb51b7
--- /dev/null
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/DBCheckpointSnapshot.java
@@ -0,0 +1,53 @@
+/*
+ * 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.utils.db;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+/**
+ * Generic DB Checkpoint interface.
+ */
+public interface DBCheckpointSnapshot {
+
+  /**
+   * Get Snapshot location.
+   */
+  Path getCheckpointLocation();
+
+  /**
+   * Get Snapshot creation timestamp.
+   */
+  long getCheckpointTimestamp();
+
+  /**
+   * Get last sequence number of Snapshot.
+   */
+  long getLatestSequenceNumber();
+
+  /**
+   * Destroy the contents of the specified checkpoint to ensure
+   * proper cleanup of the footprint on disk.
+   *
+   * @throws IOException if I/O error happens
+   */
+  void cleanupCheckpoint() throws IOException;
+
+}
diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/DBStore.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/DBStore.java
index 3965b9d..b669bfa 100644
--- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/DBStore.java
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/DBStore.java
@@ -137,5 +137,12 @@ public interface DBStore extends AutoCloseable {
    */
   void commitBatchOperation(BatchOperation operation) throws IOException;
 
+  /**
+   * Get current snapshot of OM DB store as an artifact stored on
+   * the local filesystem.
+   * @return An object that encapsulates the checkpoint information along with
+   * location.
+   */
+  DBCheckpointSnapshot getCheckpointSnapshot(boolean flush) throws IOException;
 
 }
diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/RDBCheckpointManager.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/RDBCheckpointManager.java
new file mode 100644
index 0000000..fe43e32
--- /dev/null
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/RDBCheckpointManager.java
@@ -0,0 +1,130 @@
+/*
+ * 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.utils.db;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.rocksdb.Checkpoint;
+import org.rocksdb.RocksDB;
+import org.rocksdb.RocksDBException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * RocksDB Checkpoint Manager, used to create and cleanup checkpoints.
+ */
+public class RDBCheckpointManager {
+
+  private final Checkpoint checkpoint;
+  private final RocksDB db;
+  public static final String RDB_CHECKPOINT_DIR_PREFIX = "rdb_checkpoint_";
+  private static final Logger LOG =
+      LoggerFactory.getLogger(RDBCheckpointManager.class);
+  public static final String JAVA_TMP_DIR = "java.io.tmpdir";
+  private String checkpointNamePrefix = "";
+
+  public RDBCheckpointManager(RocksDB rocksDB) {
+    this.db = rocksDB;
+    this.checkpoint = Checkpoint.create(rocksDB);
+  }
+
+  /**
+   * Create a checkpoint manager with a prefix to be added to the
+   * snapshots created.
+   *
+   * @param rocksDB          DB instance
+   * @param checkpointPrefix prefix string.
+   */
+  public RDBCheckpointManager(RocksDB rocksDB, String checkpointPrefix) {
+    this.db = rocksDB;
+    this.checkpointNamePrefix = checkpointPrefix;
+    this.checkpoint = Checkpoint.create(rocksDB);
+  }
+
+  /**
+   * Create RocksDB snapshot by saving a checkpoint to a directory.
+   *
+   * @param parentDir The directory where the checkpoint needs to be created.
+   * @return RocksDB specific Checkpoint information object.
+   */
+  public RocksDBCheckpointSnapshot createCheckpointSnapshot(String parentDir)
+      throws IOException {
+    try {
+      long currentTime = System.currentTimeMillis();
+
+      String checkpointDir = StringUtils.EMPTY;
+      if (StringUtils.isNotEmpty(checkpointNamePrefix)) {
+        checkpointDir += checkpointNamePrefix;
+      }
+      checkpointDir += "_" + RDB_CHECKPOINT_DIR_PREFIX + currentTime;
+
+      Path checkpointPath = Paths.get(parentDir, checkpointDir);
+      checkpoint.createCheckpoint(checkpointPath.toString());
+
+      return new RocksDBCheckpointSnapshot(
+          checkpointPath,
+          currentTime,
+          db.getLatestSequenceNumber()); //Best guesstimate here. Not accurate.
+
+    } catch (RocksDBException e) {
+      LOG.error("Unable to create RocksDB Snapshot.", e);
+    }
+    return null;
+  }
+
+  class RocksDBCheckpointSnapshot implements DBCheckpointSnapshot {
+
+    private Path checkpointLocation;
+    private long checkpointTimestamp;
+    private long latestSequenceNumber;
+
+    RocksDBCheckpointSnapshot(Path checkpointLocation,
+                              long snapshotTimestamp,
+                              long latestSequenceNumber) {
+      this.checkpointLocation = checkpointLocation;
+      this.checkpointTimestamp = snapshotTimestamp;
+      this.latestSequenceNumber = latestSequenceNumber;
+    }
+
+    @Override
+    public Path getCheckpointLocation() {
+      return this.checkpointLocation;
+    }
+
+    @Override
+    public long getCheckpointTimestamp() {
+      return this.checkpointTimestamp;
+    }
+
+    @Override
+    public long getLatestSequenceNumber() {
+      return this.latestSequenceNumber;
+    }
+
+    @Override
+    public void cleanupCheckpoint() throws IOException {
+      FileUtils.deleteDirectory(checkpointLocation.toFile());
+    }
+  }
+}
diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/RDBStore.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/RDBStore.java
index b79e81b..6850eec 100644
--- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/RDBStore.java
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/RDBStore.java
@@ -19,9 +19,12 @@
 
 package org.apache.hadoop.utils.db;
 
+import static org.apache.hadoop.ozone.OzoneConsts.OM_DB_CHECKPOINTS_DIR_NAME;
+
 import javax.management.ObjectName;
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Hashtable;
@@ -39,6 +42,7 @@ import org.apache.ratis.thirdparty.com.google.common.annotations.VisibleForTesti
 import org.rocksdb.ColumnFamilyDescriptor;
 import org.rocksdb.ColumnFamilyHandle;
 import org.rocksdb.DBOptions;
+import org.rocksdb.FlushOptions;
 import org.rocksdb.RocksDB;
 import org.rocksdb.RocksDBException;
 import org.rocksdb.WriteOptions;
@@ -58,6 +62,8 @@ public class RDBStore implements DBStore {
   private final CodecRegistry codecRegistry;
   private final Hashtable<String, ColumnFamilyHandle> handleTable;
   private ObjectName statMBeanName;
+  private RDBCheckpointManager checkPointManager;
+  private final String checkpointsParentDir;
 
   @VisibleForTesting
   public RDBStore(File dbFile, DBOptions options,
@@ -108,6 +114,17 @@ public class RDBStore implements DBStore {
         }
       }
 
+      //create checkpoints directory if not exists.
+      checkpointsParentDir = Paths.get(dbLocation.getParent(),
+          OM_DB_CHECKPOINTS_DIR_NAME).toString();
+      File checkpointsDir = new File(checkpointsParentDir);
+      if (!checkpointsDir.exists()) {
+        checkpointsDir.mkdir();
+      }
+
+      //Initialize checkpoint manager
+      checkPointManager = new RDBCheckpointManager(db, "om");
+
     } catch (RocksDBException e) {
       throw toIOException(
           "Failed init RocksDB, db path : " + dbFile.getAbsolutePath(), e);
@@ -246,4 +263,20 @@ public class RDBStore implements DBStore {
     }
     return returnList;
   }
+
+  @Override
+  public DBCheckpointSnapshot getCheckpointSnapshot(boolean flush)
+      throws IOException {
+    if (flush) {
+      final FlushOptions flushOptions =
+          new FlushOptions().setWaitForFlush(true);
+      try {
+        db.flush(flushOptions);
+      } catch (RocksDBException e) {
+        LOG.error("Unable to Flush RocksDB data before creating snapshot", e);
+      }
+    }
+    return checkPointManager.createCheckpointSnapshot(checkpointsParentDir);
+  }
+
 }
\ No newline at end of file
diff --git a/hadoop-hdds/common/src/test/java/org/apache/hadoop/utils/db/TestRDBStore.java b/hadoop-hdds/common/src/test/java/org/apache/hadoop/utils/db/TestRDBStore.java
index 462d2e4..2a9b77d 100644
--- a/hadoop-hdds/common/src/test/java/org/apache/hadoop/utils/db/TestRDBStore.java
+++ b/hadoop-hdds/common/src/test/java/org/apache/hadoop/utils/db/TestRDBStore.java
@@ -20,9 +20,11 @@
 package org.apache.hadoop.utils.db;
 
 import javax.management.MBeanServer;
+
 import java.io.IOException;
 import java.lang.management.ManagementFactory;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -87,21 +89,26 @@ public class TestRDBStore {
     }
   }
 
+  private void insertRandomData(RDBStore dbStore, int familyIndex)
+      throws Exception {
+    try (Table firstTable = dbStore.getTable(families.get(familyIndex))) {
+      Assert.assertNotNull("Table cannot be null", firstTable);
+      for (int x = 0; x < 100; x++) {
+        byte[] key =
+          RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
+        byte[] value =
+          RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
+        firstTable.put(key, value);
+      }
+    }
+  }
+
   @Test
   public void compactDB() throws Exception {
     try (RDBStore newStore =
              new RDBStore(folder.newFolder(), options, configSet)) {
       Assert.assertNotNull("DB Store cannot be null", newStore);
-      try (Table firstTable = newStore.getTable(families.get(1))) {
-        Assert.assertNotNull("Table cannot be null", firstTable);
-        for (int x = 0; x < 100; x++) {
-          byte[] key =
-              RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
-          byte[] value =
-              RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
-          firstTable.put(key, value);
-        }
-      }
+      insertRandomData(newStore, 1);
       // This test does not assert anything if there is any error this test
       // will throw and fail.
       newStore.compactDB();
@@ -171,29 +178,13 @@ public class TestRDBStore {
     try (RDBStore newStore =
              new RDBStore(folder.newFolder(), options, configSet)) {
       Assert.assertNotNull("DB Store cannot be null", newStore);
+
       // Write 100 keys to the first table.
-      try (Table firstTable = newStore.getTable(families.get(1))) {
-        Assert.assertNotNull("Table cannot be null", firstTable);
-        for (int x = 0; x < 100; x++) {
-          byte[] key =
-              RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
-          byte[] value =
-              RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
-          firstTable.put(key, value);
-        }
-      }
+      insertRandomData(newStore, 1);
 
       // Write 100 keys to the secondTable table.
-      try (Table secondTable = newStore.getTable(families.get(2))) {
-        Assert.assertNotNull("Table cannot be null", secondTable);
-        for (int x = 0; x < 100; x++) {
-          byte[] key =
-              RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
-          byte[] value =
-              RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
-          secondTable.put(key, value);
-        }
-      }
+      insertRandomData(newStore, 2);
+
       // Let us make sure that our estimate is not off by 10%
       Assert.assertTrue(newStore.getEstimatedKeyCount() > 180
           || newStore.getEstimatedKeyCount() < 220);
@@ -255,4 +246,47 @@ public class TestRDBStore {
     }
     Assert.assertEquals(0, count);
   }
+
+  @Test
+  public void testRocksDBCheckpoint() throws Exception {
+    try (RDBStore newStore =
+             new RDBStore(folder.newFolder(), options, configSet)) {
+      Assert.assertNotNull("DB Store cannot be null", newStore);
+
+      insertRandomData(newStore, 1);
+      DBCheckpointSnapshot checkpointSnapshot =
+          newStore.getCheckpointSnapshot(true);
+      Assert.assertNotNull(checkpointSnapshot);
+
+      RDBStore restoredStoreFromCheckPoint =
+          new RDBStore(checkpointSnapshot.getCheckpointLocation().toFile(),
+              options, configSet);
+
+      // Let us make sure that our estimate is not off by 10%
+      Assert.assertTrue(
+          restoredStoreFromCheckPoint.getEstimatedKeyCount() > 90
+          || restoredStoreFromCheckPoint.getEstimatedKeyCount() < 110);
+      checkpointSnapshot.cleanupCheckpoint();
+    }
+
+  }
+
+  @Test
+  public void testRocksDBCheckpointCleanup() throws Exception {
+    try (RDBStore newStore =
+             new RDBStore(folder.newFolder(), options, configSet)) {
+      Assert.assertNotNull("DB Store cannot be null", newStore);
+
+      insertRandomData(newStore, 1);
+      DBCheckpointSnapshot checkpointSnapshot =
+          newStore.getCheckpointSnapshot(true);
+      Assert.assertNotNull(checkpointSnapshot);
+
+      Assert.assertTrue(Files.exists(
+          checkpointSnapshot.getCheckpointLocation()));
+      checkpointSnapshot.cleanupCheckpoint();
+      Assert.assertFalse(Files.exists(
+          checkpointSnapshot.getCheckpointLocation()));
+    }
+  }
 }
\ No newline at end of file
diff --git a/hadoop-ozone/common/pom.xml b/hadoop-ozone/common/pom.xml
index add22b4..64b855e 100644
--- a/hadoop-ozone/common/pom.xml
+++ b/hadoop-ozone/common/pom.xml
@@ -30,6 +30,10 @@ http://maven.apache.org/xsd/maven-4.0.0.xsd">
 
   <dependencies>
 
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-compress</artifactId>
+    </dependency>
   </dependencies>
 
   <build>
diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
index 74d5f5a..093fac2 100644
--- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
@@ -18,16 +18,25 @@
 package org.apache.hadoop.ozone;
 
 import com.google.common.base.Joiner;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Optional;
+import java.util.zip.GZIPOutputStream;
 
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
+import org.apache.commons.compress.utils.IOUtils;
 import org.apache.commons.lang3.RandomStringUtils;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hdds.scm.ScmUtils;
@@ -287,4 +296,57 @@ public final class OmUtils {
       return coll;
     }
   }
+
+  /**
+   * Given a source directory, create a tar.gz file from it.
+   *
+   * @param sourcePath the path to the directory to be archived.
+   * @return tar.gz file
+   * @throws IOException
+   */
+  public static File createTarFile(Path sourcePath) throws IOException {
+    TarArchiveOutputStream tarOs = null;
+    try {
+      String sourceDir = sourcePath.toString();
+      String fileName = sourceDir.concat(".tar.gz");
+      FileOutputStream fileOutputStream = new FileOutputStream(fileName);
+      GZIPOutputStream gzipOutputStream =
+          new GZIPOutputStream(new BufferedOutputStream(fileOutputStream));
+      tarOs = new TarArchiveOutputStream(gzipOutputStream);
+      File folder = new File(sourceDir);
+      File[] filesInDir = folder.listFiles();
+      for (File file : filesInDir) {
+        addFilesToArchive(file.getName(), file, tarOs);
+      }
+      return new File(fileName);
+    } finally {
+      try {
+        org.apache.hadoop.io.IOUtils.closeStream(tarOs);
+      } catch (Exception e) {
+        LOG.error("Exception encountered when closing " +
+            "TAR file output stream: " + e);
+      }
+    }
+  }
+
+  private static void addFilesToArchive(String source, File file,
+                                        TarArchiveOutputStream
+                                            tarFileOutputStream)
+      throws IOException {
+    tarFileOutputStream.putArchiveEntry(new TarArchiveEntry(file, source));
+    if (file.isFile()) {
+      FileInputStream fileInputStream = new FileInputStream(file);
+      BufferedInputStream bufferedInputStream =
+          new BufferedInputStream(fileInputStream);
+      IOUtils.copy(bufferedInputStream, tarFileOutputStream);
+      tarFileOutputStream.closeArchiveEntry();
+      fileInputStream.close();
+    } else if (file.isDirectory()) {
+      tarFileOutputStream.closeArchiveEntry();
+      for (File cFile : file.listFiles()) {
+        addFilesToArchive(cFile.getAbsolutePath(), cFile, tarFileOutputStream);
+      }
+    }
+  }
+
 }
diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/OMConfigKeys.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/OMConfigKeys.java
index ba6211c..9bcd38b 100644
--- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/OMConfigKeys.java
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/OMConfigKeys.java
@@ -204,4 +204,10 @@ public final class OMConfigKeys {
       "ozone.manager.delegation.token.max-lifetime";
   public static final long    DELEGATION_TOKEN_MAX_LIFETIME_DEFAULT =
       7*24*60*60*1000; // 7 days
+
+  public static final String OZONE_DB_SNAPSHOT_TRANSFER_RATE_KEY =
+      "ozone.manager.db.snapshot.transfer.bandwidthPerSec";
+  public static final long OZONE_DB_SNAPSHOT_TRANSFER_RATE_DEFAULT =
+      0;  //no throttling
+
 }
diff --git a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/TestOmUtils.java b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/TestOmUtils.java
index 2001598..a788d0c 100644
--- a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/TestOmUtils.java
+++ b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/TestOmUtils.java
@@ -19,17 +19,23 @@
 package org.apache.hadoop.ozone;
 
 import org.apache.commons.io.FileUtils;
+import org.apache.hadoop.io.IOUtils;
 import org.apache.hadoop.test.PathUtils;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hdds.HddsConfigKeys;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.ozone.om.OMConfigKeys;
+import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.junit.rules.Timeout;
 
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.nio.file.Paths;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -88,4 +94,42 @@ public class TestOmUtils {
     thrown.expect(IllegalArgumentException.class);
     OmUtils.getOmDbDir(new OzoneConfiguration());
   }
+
+  @Test
+  public void testCreateTarFile() throws Exception {
+
+    File tempSnapshotDir = null;
+    FileInputStream fis = null;
+    FileOutputStream fos = null;
+    File tarFile = null;
+
+    try {
+      String testDirName = System.getProperty("java.io.tmpdir");
+      if (!testDirName.endsWith("/")) {
+        testDirName += "/";
+      }
+      testDirName += "TestCreateTarFile_Dir" + System.currentTimeMillis();
+      tempSnapshotDir = new File(testDirName);
+      tempSnapshotDir.mkdirs();
+
+      File file = new File(testDirName + "/temp1.txt");
+      FileWriter writer = new FileWriter(file);
+      writer.write("Test data 1");
+      writer.close();
+
+      file = new File(testDirName + "/temp2.txt");
+      writer = new FileWriter(file);
+      writer.write("Test data 2");
+      writer.close();
+
+      tarFile = OmUtils.createTarFile(Paths.get(testDirName));
+      Assert.assertNotNull(tarFile);
+
+    } finally {
+      IOUtils.closeStream(fis);
+      IOUtils.closeStream(fos);
+      FileUtils.deleteDirectory(tempSnapshotDir);
+      FileUtils.deleteQuietly(tarFile);
+    }
+  }
 }
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OMDbSnapshotServlet.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OMDbSnapshotServlet.java
new file mode 100644
index 0000000..287a684
--- /dev/null
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OMDbSnapshotServlet.java
@@ -0,0 +1,142 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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 static org.apache.hadoop.ozone.OzoneConsts.
+    OZONE_DB_CHECKPOINT_REQUEST_FLUSH;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdfs.server.namenode.TransferFsImage;
+import org.apache.hadoop.hdfs.util.DataTransferThrottler;
+import org.apache.hadoop.io.IOUtils;
+import org.apache.hadoop.ozone.OmUtils;
+import org.apache.hadoop.ozone.OzoneConsts;
+import org.apache.hadoop.utils.db.DBStore;
+import org.apache.hadoop.utils.db.DBCheckpointSnapshot;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Provides the current checkpoint Snapshot of the OM DB. (tar.gz)
+ */
+public class OMDbSnapshotServlet extends HttpServlet {
+
+  private static final Logger LOG =
+      LoggerFactory.getLogger(OMDbSnapshotServlet.class);
+
+  private transient DBStore omDbStore;
+  private DataTransferThrottler throttler = null;
+
+  @Override
+  public void init() throws ServletException {
+
+    OzoneManager om = (OzoneManager) getServletContext()
+        .getAttribute(OzoneConsts.OM_CONTEXT_ATTRIBUTE);
+
+    if (om == null) {
+      LOG.error("Unable to initialize OMDbSnapshotServlet. OM is null");
+      return;
+    }
+
+    omDbStore = om.getMetadataManager().getStore();
+    OzoneConfiguration configuration = om.getConfiguration();
+    long transferBandwidth = configuration.getLongBytes(
+        OMConfigKeys.OZONE_DB_SNAPSHOT_TRANSFER_RATE_KEY,
+        OMConfigKeys.OZONE_DB_SNAPSHOT_TRANSFER_RATE_DEFAULT);
+
+    if (transferBandwidth > 0) {
+      throttler = new DataTransferThrottler(transferBandwidth);
+    }
+  }
+
+  /**
+   * Process a GET request for the Ozone Manager DB checkpoint snapshot.
+   *
+   * @param request  The servlet request we are processing
+   * @param response The servlet response we are creating
+   */
+  @Override
+  public void doGet(HttpServletRequest request, HttpServletResponse response) {
+
+    LOG.info("Received request to obtain OM DB checkpoint snapshot");
+    if (omDbStore == null) {
+      LOG.error(
+          "Unable to process metadata snapshot request. DB Store is null");
+      response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+      return;
+    }
+
+    FileInputStream checkpointFileInputStream = null;
+    File checkPointTarFile = null;
+    try {
+
+      boolean flush = false;
+      String flushParam =
+          request.getParameter(OZONE_DB_CHECKPOINT_REQUEST_FLUSH);
+      if (StringUtils.isNotEmpty(flushParam)) {
+        flush = Boolean.valueOf(flushParam);
+      }
+
+      DBCheckpointSnapshot checkpoint = omDbStore.getCheckpointSnapshot(flush);
+      if (checkpoint == null) {
+        LOG.error("Unable to process metadata snapshot request. " +
+            "Checkpoint request returned null.");
+        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+        return;
+      }
+      LOG.info("Tar location = " + checkPointTarFile.getAbsolutePath());
+      checkPointTarFile = OmUtils.createTarFile(
+          checkpoint.getCheckpointLocation());
+      LOG.info("Tar location = " + checkPointTarFile.getAbsolutePath());
+      response.setContentType("application/x-tgz");
+      response.setHeader("Content-Disposition",
+          "attachment; filename=\"" +
+              checkPointTarFile.getName() + "\"");
+
+      checkpointFileInputStream = new FileInputStream(checkPointTarFile);
+      TransferFsImage.copyFileToStream(response.getOutputStream(),
+          checkPointTarFile,
+          checkpointFileInputStream,
+          throttler);
+
+      checkpoint.cleanupCheckpoint();
+    } catch (IOException e) {
+      LOG.error(
+          "Unable to process metadata snapshot request. ", e);
+      response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+    } finally {
+      if (checkPointTarFile != null) {
+        FileUtils.deleteQuietly(checkPointTarFile);
+      }
+      IOUtils.closeStream(checkpointFileInputStream);
+    }
+  }
+
+}
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 3104de3..3061c96 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
@@ -2428,6 +2428,10 @@ public final class OzoneManager extends ServiceRuntimeInfoImpl
     return LOG;
   }
 
+  public OzoneConfiguration getConfiguration() {
+    return configuration;
+  }
+
   public static void setTestSecureOmFlag(boolean testSecureOmFlag) {
     OzoneManager.testSecureOmFlag = testSecureOmFlag;
   }
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 8f7f058..a53096c 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
@@ -32,6 +32,7 @@ public class OzoneManagerHttpServer extends BaseHttpServer {
       throws IOException {
     super(conf, "ozoneManager");
     addServlet("serviceList", "/serviceList", ServiceListJSONServlet.class);
+    addServlet("dbSnapshot", "/dbSnapshot", OMDbSnapshotServlet.class);
     getWebAppContext().setAttribute(OzoneConsts.OM_CONTEXT_ATTRIBUTE, om);
   }
 


---------------------------------------------------------------------
To unsubscribe, e-mail: common-commits-unsubscribe@hadoop.apache.org
For additional commands, e-mail: common-commits-help@hadoop.apache.org