You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sentry.apache.org by ak...@apache.org on 2017/06/27 20:43:28 UTC

sentry git commit: SENTRY-1781: Persist new HMS snapshots with a new generation ID (Sergio Pena, reviewed by Alex Kolbasov)

Repository: sentry
Updated Branches:
  refs/heads/sentry-ha-redesign 3105a1bfb -> 627469f31


SENTRY-1781: Persist new HMS snapshots with a new generation ID (Sergio Pena, reviewed by Alex Kolbasov)


Project: http://git-wip-us.apache.org/repos/asf/sentry/repo
Commit: http://git-wip-us.apache.org/repos/asf/sentry/commit/627469f3
Tree: http://git-wip-us.apache.org/repos/asf/sentry/tree/627469f3
Diff: http://git-wip-us.apache.org/repos/asf/sentry/diff/627469f3

Branch: refs/heads/sentry-ha-redesign
Commit: 627469f314444fe52dfce20c07b49acfa1830edf
Parents: 3105a1b
Author: Alexander Kolbasov <ak...@cloudera.com>
Authored: Tue Jun 27 13:41:00 2017 -0700
Committer: Alexander Kolbasov <ak...@cloudera.com>
Committed: Tue Jun 27 13:41:00 2017 -0700

----------------------------------------------------------------------
 .../db/service/persistent/SentryStore.java      | 158 +++++++++++++------
 .../db/service/persistent/TestSentryStore.java  | 142 +++++++++++++++++
 2 files changed, 249 insertions(+), 51 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/sentry/blob/627469f3/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStore.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStore.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStore.java
index 27999cf..9ad97bc 100644
--- a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStore.java
+++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/SentryStore.java
@@ -53,6 +53,7 @@ import org.apache.sentry.core.common.utils.SentryConstants;
 import org.apache.sentry.core.model.db.AccessConstants;
 import org.apache.sentry.core.model.db.DBModelAuthorizable.AuthorizableType;
 import org.apache.sentry.provider.db.service.model.MAuthzPathsMapping;
+import org.apache.sentry.provider.db.service.model.MAuthzPathsSnapshotId;
 import org.apache.sentry.provider.db.service.model.MSentryChange;
 import org.apache.sentry.provider.db.service.model.MSentryGroup;
 import org.apache.sentry.provider.db.service.model.MSentryHmsNotification;
@@ -126,7 +127,7 @@ public class SentryStore {
   public static final long EMPTY_NOTIFICATION_ID = 0L;
 
   // Representation for empty HMS snapshots not found on MAuthzPathsSnapshotId
-  public static final long EMPTY_AUTHZ_SNAPSHOT_ID = 0L;
+  public static final long EMPTY_PATHS_SNAPSHOT_ID = 0L;
 
   // For counters, representation of the "unknown value"
   private static final long COUNT_VALUE_UNKNOWN = -1L;
@@ -480,6 +481,7 @@ public class SentryStore {
               pm.newQuery(MAuthzPathsMapping.class).deletePersistentAll();
               pm.newQuery(MPath.class).deletePersistentAll();
               pm.newQuery(MSentryHmsNotification.class).deletePersistentAll();
+              pm.newQuery(MAuthzPathsSnapshotId.class).deletePersistentAll();
               return null;
             }
           });
@@ -2528,24 +2530,36 @@ public class SentryStore {
 
   /**
    * Retrieves an up-to-date hive paths snapshot from {@code MAuthzPathsMapping} table.
-   * The snapshot is represented by a hiveObj to paths map.
+   * The snapshot is represented by a snapshot ID, and a map from hiveObj to paths.
    *
    * @return a mapping of hiveObj to &lt Paths &gt.
    */
   private Map<String, Set<String>> retrieveFullPathsImageCore(PersistenceManager pm) {
-    Map<String, Set<String>> retVal = new HashMap<>();
+    long currentSnapshotID = getCurrentAuthzPathsSnapshotID(pm);
+    if (currentSnapshotID <= EMPTY_PATHS_SNAPSHOT_ID) {
+      return Collections.emptyMap();
+    }
+
     Query query = pm.newQuery(MAuthzPathsMapping.class);
-    Iterable<MAuthzPathsMapping> authzToPathsMappings =
-        (Iterable<MAuthzPathsMapping>) query.execute();
+    query.setFilter("this.authzSnapshotID == currentSnapshotID");
+    query.declareParameters("long currentSnapshotID");
+    Collection<MAuthzPathsMapping> authzToPathsMappings =
+        (Collection<MAuthzPathsMapping>) query.execute(currentSnapshotID);
+
+    if (authzToPathsMappings.isEmpty()) {
+      return Collections.emptyMap();
+    }
 
+    Map<String, Set<String>> retVal = new HashMap<>(authzToPathsMappings.size());
     for (MAuthzPathsMapping authzToPaths : authzToPathsMappings) {
       retVal.put(authzToPaths.getAuthzObjName(), authzToPaths.getPathStrings());
     }
+
     return retVal;
   }
 
   /**
-   * Persist an up-to-date hive snapshot into Sentry DB in a single transaction.
+   * Persist an up-to-date HMS snapshot into Sentry DB in a single transaction.
    *
    * @param authzPaths Mapping of hiveObj to &lt Paths &lt
    * @throws Exception
@@ -2555,8 +2569,13 @@ public class SentryStore {
       new TransactionBlock() {
         public Object execute(PersistenceManager pm) throws Exception {
           pm.setDetachAllOnCommit(false); // No need to detach objects
+
+          long snapshotID = getCurrentAuthzPathsSnapshotID(pm);
+          long nextSnapshotID = snapshotID + 1;
+
+          pm.makePersistent(new MAuthzPathsSnapshotId(nextSnapshotID));
           for (Map.Entry<String, Set<String>> authzPath : authzPaths.entrySet()) {
-            createAuthzPathsMappingCore(pm, authzPath.getKey(), authzPath.getValue());
+            pm.makePersistent(new MAuthzPathsMapping(nextSnapshotID, authzPath.getKey(), authzPath.getValue()));
           }
           return null;
         }
@@ -2564,26 +2583,13 @@ public class SentryStore {
   }
 
   /**
-   * Create an entry for the given authzObj and with a set of paths in
-   * the authzObj -> [Paths] mapping.
+   * Gets the last authorization path snapshot ID persisted.
    *
-   * @param authzObj an authzObj
-   * @param paths a set of paths need to be added into the authzObj -> [Paths] mapping
-   * @throws SentryAlreadyExistsException if this authzObj has already exist
-   *          in the mapping.
+   * @param pm The PersistenceManager object.
+   * @return the last persisted snapshot ID. It returns 0 if no rows are found.
    */
-  private void createAuthzPathsMappingCore(PersistenceManager pm, String authzObj,
-      Set<String> paths) throws SentryAlreadyExistsException {
-
-    MAuthzPathsMapping mAuthzPathsMapping = getMAuthzPathsMappingCore(pm, authzObj);
-
-    if (mAuthzPathsMapping == null) {
-      mAuthzPathsMapping =
-          new MAuthzPathsMapping(EMPTY_AUTHZ_SNAPSHOT_ID, authzObj, paths);
-      pm.makePersistent(mAuthzPathsMapping);
-    } else {
-      throw new SentryAlreadyExistsException("AuthzObj: " + authzObj);
-    }
+  private static long getCurrentAuthzPathsSnapshotID(PersistenceManager pm) {
+    return getMaxPersistedIDCore(pm, MAuthzPathsSnapshotId.class, "authzSnapshotID", EMPTY_PATHS_SNAPSHOT_ID);
   }
 
   /**
@@ -2618,9 +2624,14 @@ public class SentryStore {
    */
   private void addAuthzPathsMappingCore(PersistenceManager pm, String authzObj,
         Collection<String> paths) {
-    MAuthzPathsMapping mAuthzPathsMapping = getMAuthzPathsMappingCore(pm, authzObj);
+    long currentSnapshotID = getCurrentAuthzPathsSnapshotID(pm);
+    if (currentSnapshotID <= EMPTY_PATHS_SNAPSHOT_ID) {
+      LOGGER.error("AuthzObj: {} cannot be persisted if an paths snapshot ID does not exist yet.");
+    }
+
+    MAuthzPathsMapping mAuthzPathsMapping = getMAuthzPathsMappingCore(pm, currentSnapshotID, authzObj);
     if (mAuthzPathsMapping == null) {
-      mAuthzPathsMapping = new MAuthzPathsMapping(EMPTY_AUTHZ_SNAPSHOT_ID, authzObj, paths);
+      mAuthzPathsMapping = new MAuthzPathsMapping(currentSnapshotID, authzObj, paths);
     } else {
       for (String path : paths) {
         mAuthzPathsMapping.addPath(new MPath(path));
@@ -2659,7 +2670,12 @@ public class SentryStore {
    */
   private void deleteAuthzPathsMappingCore(PersistenceManager pm, String authzObj,
                                            Iterable<String> paths) {
-    MAuthzPathsMapping mAuthzPathsMapping = getMAuthzPathsMappingCore(pm, authzObj);
+    long currentSnapshotID = getCurrentAuthzPathsSnapshotID(pm);
+    if (currentSnapshotID <= EMPTY_PATHS_SNAPSHOT_ID) {
+      LOGGER.error("No paths snapshot ID is found. Cannot delete authzoObj: {}", authzObj);
+    }
+
+    MAuthzPathsMapping mAuthzPathsMapping = getMAuthzPathsMappingCore(pm, currentSnapshotID, authzObj);
     if (mAuthzPathsMapping != null) {
       for (String path : paths) {
         MPath mPath = mAuthzPathsMapping.getPath(path);
@@ -2672,7 +2688,8 @@ public class SentryStore {
       }
       pm.makePersistent(mAuthzPathsMapping);
     } else {
-      LOGGER.error("nonexistent authzObj: {}", authzObj);
+      LOGGER.error("nonexistent authzObj: {} on current paths snapshot ID #{}",
+          authzObj, currentSnapshotID);
     }
   }
 
@@ -2703,7 +2720,12 @@ public class SentryStore {
    * @throws SentryNoSuchObjectException if cannot find the existing authzObj
    */
   private void deleteAllAuthzPathsMappingCore(PersistenceManager pm, String authzObj) {
-    MAuthzPathsMapping mAuthzPathsMapping = getMAuthzPathsMappingCore(pm, authzObj);
+    long currentSnapshotID = getCurrentAuthzPathsSnapshotID(pm);
+    if (currentSnapshotID <= EMPTY_PATHS_SNAPSHOT_ID) {
+      LOGGER.error("No paths snapshot ID is found. Cannot delete authzoObj: {}", authzObj);
+    }
+
+    MAuthzPathsMapping mAuthzPathsMapping = getMAuthzPathsMappingCore(pm, currentSnapshotID, authzObj);
     if (mAuthzPathsMapping != null) {
       for (MPath mPath : mAuthzPathsMapping.getPaths()) {
         mAuthzPathsMapping.removePath(mPath);
@@ -2711,7 +2733,8 @@ public class SentryStore {
       }
       pm.deletePersistent(mAuthzPathsMapping);
     } else {
-      LOGGER.error("nonexistent authzObj: {}", authzObj);
+      LOGGER.error("nonexistent authzObj: {} on current paths snapshot ID #{}",
+          authzObj, currentSnapshotID);
     }
   }
 
@@ -2752,7 +2775,12 @@ public class SentryStore {
    */
   private void renameAuthzPathsMappingCore(PersistenceManager pm, String oldObj,
         String newObj, String oldPath, String newPath) {
-    MAuthzPathsMapping mAuthzPathsMapping = getMAuthzPathsMappingCore(pm, oldObj);
+    long currentSnapshotID = getCurrentAuthzPathsSnapshotID(pm);
+    if (currentSnapshotID <= EMPTY_PATHS_SNAPSHOT_ID) {
+      LOGGER.error("No paths snapshot ID is found. Cannot rename authzoObj: {}", oldObj);
+    }
+
+    MAuthzPathsMapping mAuthzPathsMapping = getMAuthzPathsMappingCore(pm, currentSnapshotID, oldObj);
     if (mAuthzPathsMapping != null) {
       MPath mOldPath = mAuthzPathsMapping.getPath(oldPath);
       if (mOldPath == null) {
@@ -2765,7 +2793,8 @@ public class SentryStore {
       mAuthzPathsMapping.setAuthzObjName(newObj);
       pm.makePersistent(mAuthzPathsMapping);
     } else {
-      LOGGER.error("nonexistent authzObj: {}", oldObj);
+      LOGGER.error("nonexistent authzObj: {} on current paths snapshot ID #{}",
+          oldObj, currentSnapshotID);
     }
   }
 
@@ -2800,12 +2829,18 @@ public class SentryStore {
    */
   private void renameAuthzObjCore(PersistenceManager pm, String oldObj,
       String newObj) {
-    MAuthzPathsMapping mAuthzPathsMapping = getMAuthzPathsMappingCore(pm, oldObj);
+    long currentSnapshotID = getCurrentAuthzPathsSnapshotID(pm);
+    if (currentSnapshotID <= EMPTY_PATHS_SNAPSHOT_ID) {
+      LOGGER.error("No paths snapshot ID is found. Cannot rename authzoObj: {}", oldObj);
+    }
+
+    MAuthzPathsMapping mAuthzPathsMapping = getMAuthzPathsMappingCore(pm, currentSnapshotID, oldObj);
     if (mAuthzPathsMapping != null) {
       mAuthzPathsMapping.setAuthzObjName(newObj);
       pm.makePersistent(mAuthzPathsMapping);
     } else {
-      LOGGER.error("nonexistent authzObj: {}", oldObj);
+      LOGGER.error("nonexistent authzObj: {} on current paths snapshot ID #{}",
+          oldObj, currentSnapshotID);
     }
   }
 
@@ -2862,9 +2897,14 @@ public class SentryStore {
   private void updateAuthzPathsMappingCore(PersistenceManager pm, String authzObj,
         String oldPath, String newPath) {
 
-    MAuthzPathsMapping mAuthzPathsMapping = getMAuthzPathsMappingCore(pm, authzObj);
+    long currentSnapshotID = getCurrentAuthzPathsSnapshotID(pm);
+    if (currentSnapshotID <= EMPTY_PATHS_SNAPSHOT_ID) {
+      LOGGER.error("No paths snapshot ID is found. Cannot update authzoObj: {}", authzObj);
+    }
+
+    MAuthzPathsMapping mAuthzPathsMapping = getMAuthzPathsMappingCore(pm, currentSnapshotID, authzObj);
     if (mAuthzPathsMapping == null) {
-      mAuthzPathsMapping = new MAuthzPathsMapping(EMPTY_AUTHZ_SNAPSHOT_ID, authzObj, Sets.newHashSet(newPath));
+      mAuthzPathsMapping = new MAuthzPathsMapping(currentSnapshotID, authzObj, Sets.newHashSet(newPath));
     } else {
       MPath mOldPath = mAuthzPathsMapping.getPath(oldPath);
       if (mOldPath == null) {
@@ -2883,12 +2923,12 @@ public class SentryStore {
    * Get the MAuthzPathsMapping object from authzObj
    */
   private MAuthzPathsMapping getMAuthzPathsMappingCore(PersistenceManager pm,
-        String authzObj) {
+        long authzSnapshotID, String authzObj) {
     Query query = pm.newQuery(MAuthzPathsMapping.class);
-    query.setFilter("this.authzObjName == authzObjName");
-    query.declareParameters("java.lang.String authzObjName");
+    query.setFilter("this.authzSnapshotID == authzSnapshotID && this.authzObjName == authzObjName");
+    query.declareParameters("long authzSnapshotID, java.lang.String authzObjName");
     query.setUnique(true);
-    return (MAuthzPathsMapping) query.execute(authzObj);
+    return (MAuthzPathsMapping) query.execute(authzSnapshotID, authzObj);
   }
 
   /**
@@ -2910,12 +2950,34 @@ public class SentryStore {
     return ((List<MAuthzPathsMapping>) query.execute()).isEmpty();
   }
 
+  /**
+   * Generic method used to query the maximum number (or ID) of a column from a specified class.
+   *
+   * @param pm The PersistenceManager object.
+   * @param clazz The class name to query.
+   * @param columnName The column name to query.
+   * @return the maximum number persisted on the class. It returns NULL if the class has no rows.
+   */
+  private static long getMaxPersistedIDCore(PersistenceManager pm, Class clazz, String columnName, long defaultValue) {
+    Query query = pm.newQuery(clazz);
+    query.setResult(String.format("max(%s)", columnName));
+    Long maxValue = (Long) query.execute();
+    return (maxValue != null) ? maxValue : defaultValue;
+  }
+
   @VisibleForTesting
   List<MPath> getMPaths() throws Exception {
     return tm.executeTransaction(new TransactionBlock<List<MPath>>() {
       public List<MPath> execute(PersistenceManager pm) throws Exception {
-        Query query = pm.newQuery(MPath.class);
-        return (List<MPath>) query.execute();
+        long currentSnapshotID = getCurrentAuthzPathsSnapshotID(pm);
+
+        Query query = pm.newQuery("SQL",
+            "SELECT p.PATH_NAME FROM AUTHZ_PATH p " +
+               "JOIN AUTHZ_PATHS_MAPPING a ON a.AUTHZ_OBJ_ID = p.AUTHZ_OBJ_ID " +
+               "WHERE a.AUTHZ_SNAPSHOT_ID = ?"
+        );
+        query.setResultClass(MPath.class);
+        return (List<MPath>) query.execute(currentSnapshotID);
       }
     });
   }
@@ -3480,10 +3542,7 @@ public class SentryStore {
    */
   static <T extends MSentryChange> Long getLastProcessedChangeIDCore(
       PersistenceManager pm, Class<T> changeCls) {
-    Query query = pm.newQuery(changeCls);
-    query.setResult("max(changeID)");
-    Long changeID = (Long) query.execute();
-    return changeID == null ? EMPTY_CHANGE_ID : changeID;
+    return getMaxPersistedIDCore(pm, changeCls, "changeID", EMPTY_CHANGE_ID);
   }
 
   /**
@@ -3498,10 +3557,7 @@ public class SentryStore {
    */
   static Long getLastProcessedNotificationIDCore(
       PersistenceManager pm) {
-    Query query = pm.newQuery(MSentryHmsNotification.class);
-    query.setResult("max(notificationId)");
-    Long notificationId = (Long) query.execute();
-    return notificationId == null ? EMPTY_NOTIFICATION_ID : notificationId;
+    return getMaxPersistedIDCore(pm, MSentryHmsNotification.class, "notificationId", EMPTY_NOTIFICATION_ID);
   }
 
   /**

http://git-wip-us.apache.org/repos/asf/sentry/blob/627469f3/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/persistent/TestSentryStore.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/persistent/TestSentryStore.java b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/persistent/TestSentryStore.java
index 29878c5..ac266fe 100644
--- a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/persistent/TestSentryStore.java
+++ b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/persistent/TestSentryStore.java
@@ -2463,6 +2463,9 @@ public class TestSentryStore extends org.junit.Assert {
 
   @Test
   public void testAddDeleteAuthzPathsMapping() throws Exception {
+    // Persist an empty image so that we can add paths to it.
+    sentryStore.persistFullPathsImage(new HashMap<String, Set<String>>());
+
     // Add "db1.table1" authzObj
     Long lastNotificationId = sentryStore.getLastProcessedNotificationID();
     PathsUpdate addUpdate = new PathsUpdate(1, false);
@@ -2605,6 +2608,142 @@ public class TestSentryStore extends org.junit.Assert {
   }
 
   @Test
+  public void testPersistAndReplaceANewPathsImage() throws Exception {
+    Map<String, Set<String>> authzPaths = new HashMap<>();
+
+    // First image to persist (this will be replaced later)
+    authzPaths.put("db1.table1", Sets.newHashSet("/user/hive/warehouse/db2.db/table1.1",
+        "/user/hive/warehouse/db2.db/table1.2"));
+    authzPaths.put("db1.table2", Sets.newHashSet("/user/hive/warehouse/db2.db/table2.1",
+        "/user/hive/warehouse/db2.db/table2.2"));
+    sentryStore.persistFullPathsImage(authzPaths);
+
+    // Second image to persist (it should replace first image)
+    authzPaths.clear();
+    authzPaths.put("db3.table1", Sets.newHashSet("/another-warehouse/db2.db/table1.1",
+        "/another-warehouse/db2.db/table1.2"));
+    authzPaths.put("db3.table2", Sets.newHashSet("/another-warehouse/db2.db/table2.1",
+        "/another-warehouse/db2.db/table2.2"));
+    authzPaths.put("db4.table2", Sets.newHashSet("/another-warehouse/db2.db/table2.1",
+        "/another-warehouse/db2.db/table2.3"));
+    sentryStore.persistFullPathsImage(authzPaths);
+
+    PathsImage pathsImage = sentryStore.retrieveFullPathsImage();
+    Map<String, Set<String>> pathImage = pathsImage.getPathImage();
+    assertEquals(3, pathImage.size());
+
+    for (Map.Entry<String, Set<String>> entry : pathImage.entrySet()) {
+      assertEquals(2, entry.getValue().size());
+    }
+
+    assertEquals(2, pathImage.get("db4.table2").size());
+
+    assertEquals(Sets.newHashSet("/another-warehouse/db2.db/table1.1",
+        "/another-warehouse/db2.db/table1.2"),
+        pathImage.get("db3.table1"));
+    assertEquals(Sets.newHashSet("/another-warehouse/db2.db/table2.1",
+        "/another-warehouse/db2.db/table2.2"),
+        pathImage.get("db3.table2"));
+    assertEquals(Sets.newHashSet("/another-warehouse/db2.db/table2.1",
+        "/another-warehouse/db2.db/table2.3"),
+        pathImage.get("db4.table2"));
+
+    assertEquals(6, sentryStore.getMPaths().size());
+  }
+
+  @Test
+  public void testAddDeleteAfterReplacingANewPathsImage() throws Exception {
+    // Add some paths first (these should be replaced)
+    PathsUpdate addUpdate = new PathsUpdate(1, false);
+    addUpdate.newPathChange("db1.table").addToAddPaths(Arrays.asList("db1", "tbl1"));
+    addUpdate.newPathChange("db1.table").addToAddPaths(Arrays.asList("db1", "tbl2"));
+    sentryStore.addAuthzPathsMapping("db1.table", Sets.newHashSet("db1/tbl1", "db1/tbl2"), addUpdate);
+
+    // Persist a new image that contains a new image ID (it replaces previous paths)
+    Map<String, Set<String>> authzPaths = new HashMap<>();
+    authzPaths.put("db2.table3", Sets.newHashSet("/user/hive/warehouse/db2.db/table1.1",
+        "/user/hive/warehouse/db2.db/table1.2"));
+    sentryStore.persistFullPathsImage(authzPaths);
+
+    // Add new paths
+    PathsUpdate newAddUpdate = new PathsUpdate(2, false);
+    newAddUpdate.newPathChange("db2.table").addToAddPaths(Arrays.asList("db2", "tbl1"));
+    newAddUpdate.newPathChange("db2.table").addToAddPaths(Arrays.asList("db2", "tbl2"));
+    sentryStore.addAuthzPathsMapping("db2.table", Sets.newHashSet("db2/tbl1", "db2/tbl2"), newAddUpdate);
+    PathsImage pathsImage = sentryStore.retrieveFullPathsImage();
+    Map<String, Set<String>> pathImage = pathsImage.getPathImage();
+    assertEquals(2, pathImage.size());
+    assertEquals(2, pathImage.get("db2.table").size());
+    assertEquals(4, sentryStore.getMPaths().size());
+
+    // Delete one path
+    PathsUpdate delUpdate = new PathsUpdate(3, false);
+    delUpdate.newPathChange("db2.table").addToDelPaths(Arrays.asList("db2", "tbl1"));
+    sentryStore.deleteAuthzPathsMapping("db2.table", Sets.newHashSet("db2/tbl1"), delUpdate);
+    pathsImage = sentryStore.retrieveFullPathsImage();
+    pathImage = pathsImage.getPathImage();
+    assertEquals(2, pathImage.size());
+    assertEquals(1, pathImage.get("db2.table").size());
+    assertEquals(3, sentryStore.getMPaths().size());
+
+    Long lastNotificationId = sentryStore.getLastProcessedNotificationID();
+    assertEquals(3, lastNotificationId.longValue());
+  }
+
+  @Test
+  public void testRenameUpdateAfterReplacingANewPathsImage() throws Exception {
+    Map<String, Set<String>> authzPaths = new HashMap<>();
+
+    // First image to persist (this will be replaced later)
+    authzPaths.put("db1.table1", Sets.newHashSet("/user/hive/warehouse/db2.db/table1.1",
+        "/user/hive/warehouse/db2.db/table1.2"));
+    authzPaths.put("db1.table2", Sets.newHashSet("/user/hive/warehouse/db2.db/table2.1",
+        "/user/hive/warehouse/db2.db/table2.2"));
+    sentryStore.persistFullPathsImage(authzPaths);
+
+    // Second image to persist (it should replace first image)
+    authzPaths.clear();
+    authzPaths.put("db3.table1", Sets.newHashSet("/another-warehouse/db3.db/table1.1",
+        "/another-warehouse/db3.db/table1.2"));
+    authzPaths.put("db3.table2", Sets.newHashSet("/another-warehouse/db3.db/table2.1",
+        "/another-warehouse/db3.db/table2.2"));
+    sentryStore.persistFullPathsImage(authzPaths);
+
+    // Rename path of 'db1.table1' from 'db1.table1' to 'db1.newTable1'
+    PathsUpdate renameUpdate = new PathsUpdate(1, false);
+    renameUpdate.newPathChange("db3.table1")
+        .addToDelPaths(Arrays.asList("another-warehouse", "db3.db", "table1.1"));
+    renameUpdate.newPathChange("db1.newTable1")
+        .addToAddPaths(Arrays.asList("user", "hive", "warehouse", "db1.db", "newTable1"));
+    sentryStore.renameAuthzPathsMapping("db3.table1", "db1.newTable1",
+        "/another-warehouse/db3.db/table1.1", "user/hive/warehouse/db1.db/newTable1", renameUpdate);
+    Map<String, Set<String>> pathsImage = sentryStore.retrieveFullPathsImage().getPathImage();
+    assertEquals(2, pathsImage.size());
+    assertEquals(4, sentryStore.getMPaths().size());
+    assertTrue(pathsImage.containsKey("db1.newTable1"));
+    assertEquals(Sets.newHashSet("/another-warehouse/db3.db/table1.2",
+        "user/hive/warehouse/db1.db/newTable1"),
+        pathsImage.get("db1.newTable1"));
+
+    // Update path of 'db1.newTable2' from 'db1.newTable1' to 'db1.newTable2'
+    PathsUpdate update = new PathsUpdate(2, false);
+    update.newPathChange("db1.newTable1")
+        .addToDelPaths(Arrays.asList("user", "hive", "warehouse", "db1.db", "newTable1"));
+    update.newPathChange("db1.newTable1")
+        .addToAddPaths(Arrays.asList("user", "hive", "warehouse", "db1.db", "newTable2"));
+    sentryStore.updateAuthzPathsMapping("db1.newTable2",
+        "user/hive/warehouse/db1.db/newTable1",
+        "user/hive/warehouse/db1.db/newTable2",
+        update);
+    pathsImage = sentryStore.retrieveFullPathsImage().getPathImage();
+    assertEquals(3, pathsImage.size());
+    assertEquals(5, sentryStore.getMPaths().size());
+    assertTrue(pathsImage.containsKey("db1.newTable2"));
+    assertEquals(Sets.newHashSet("user/hive/warehouse/db1.db/newTable2"),
+        pathsImage.get("db1.newTable2"));
+  }
+
+  @Test
   public void testQueryParamBuilder() {
     QueryParamBuilder paramBuilder;
     paramBuilder = newQueryParamBuilder();
@@ -3052,6 +3191,9 @@ public class TestSentryStore extends org.junit.Assert {
     addUpdate.newPathChange("db1.table").
       addToAddPaths(Arrays.asList("db1", "tbl2"));
 
+    // Persist an empty image so that we can add paths to it.
+    sentryStore.persistFullPathsImage(new HashMap<String, Set<String>>());
+
     assertEquals(sentryStore.isAuthzPathsMappingEmpty(), true);
     sentryStore.addAuthzPathsMapping("db1.table",
       Sets.newHashSet("db1/tbl1", "db1/tbl2"), addUpdate);