Repository: sentry
Updated Branches:
  refs/heads/master 441a4f64a -> 73a5eabea


SENTRY-1520: Provide mechanism for triggering HMS full snapshot (Vadim Spector, 
Reviewed by: Sravya Tirukkovalur, Alexander Kolbasov, Kalyan Kalvagadda and Hao 
Hao)

Change-Id: I026e55a464c8967a6d41a4aff40ad902c52fc090


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

Branch: refs/heads/master
Commit: 73a5eabeaef5f9b76a9366bc74d5cbd99a32bc3f
Parents: 441a4f6
Author: hahao <hao....@cloudera.com>
Authored: Tue Nov 22 15:51:23 2016 -0800
Committer: hahao <hao....@cloudera.com>
Committed: Tue Nov 22 15:54:38 2016 -0800

----------------------------------------------------------------------
 .../sentry/core/common/utils/SigUtils.java      |  97 +++++++++++
 .../apache/sentry/hdfs/ServiceConstants.java    |   1 +
 .../org/apache/sentry/hdfs/SentryPlugin.java    | 167 ++++++++++++++++++-
 3 files changed, 260 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/sentry/blob/73a5eabe/sentry-core/sentry-core-common/src/main/java/org/apache/sentry/core/common/utils/SigUtils.java
----------------------------------------------------------------------
diff --git 
a/sentry-core/sentry-core-common/src/main/java/org/apache/sentry/core/common/utils/SigUtils.java
 
b/sentry-core/sentry-core-common/src/main/java/org/apache/sentry/core/common/utils/SigUtils.java
new file mode 100644
index 0000000..1f16e14
--- /dev/null
+++ 
b/sentry-core/sentry-core-common/src/main/java/org/apache/sentry/core/common/utils/SigUtils.java
@@ -0,0 +1,97 @@
+/*
+ * 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.sentry.core.common.utils;
+
+import sun.misc.Signal;
+import sun.misc.SignalHandler;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class facilitates handling signals in Sentry.
+ * <p>
+ * It defines its own callback interface to isolate the client code
+ * from the dependency on sun.misc.*. The sun.misc.* package is not officially
+ * supported, yet, per many indications, it is very unlikely to be dropped in
+ * the future JDK releases. If needed it can be re-implemented using JNI,
+ * though we'd rather not ever take this path.
+ * <p>
+ * This class relies on sun.misc.SignalHandler which registers signal handlers 
via static
+ * method handle(). Therefore, SigUtils also only exposes a static method 
registerListener()
+ * <p>
+ * This class, as sun.misc.SignalHandler, supports a single signal listener 
per each signal.
+ * The same signal listener can be used to handle multiple signals, but 
setting a listener
+ * for the same signal twice will replace an old listener with the new one.
+ * method.
+ */
+public final class SigUtils {
+
+  private static final Logger LOGGER = LoggerFactory.getLogger(SigUtils.class);
+
+  private SigUtils() {} // to prevent class instantiation
+
+  /**
+   * interface to be implemented by a class that wants to be notified of a 
signal
+   */
+  public interface SigListener {
+    void onSignal(String signalName);
+  }
+
+  /* private wrapper class around SignalHandler, to delegate signal handling
+   * to a registered signal listener.
+   */
+  private static class SigHandler implements SignalHandler {
+    private final SigListener listener;
+    SigHandler(SigListener listener) {
+      this.listener = listener;
+    }
+    @Override
+    public void handle(Signal sig) {
+      if (sig != null) {
+        listener.onSignal(sig.toString());
+        // signalling is a rare yet important event - let's always log it
+        LOGGER.info("Signal propagated: " + sig);
+      } else {
+        LOGGER.error("Internal Error: null signal received");
+      }
+    }
+  }
+
+  /**
+   * Register signal listener for a specific signal.
+   *
+   * Only one listener per specific signal is supported. Calling this method
+   * multiple times will keep overwriting a previously set listener.
+   *
+   * @NotNull sigName
+   * @NotNull sigListener
+   * @param sigName signal name, as in UNIX "kill" command, e.g. INT, USR1, 
etc, without "SIG" prefix
+   * @param sigListener signal notification callback object
+   * @throws IllegalArgumentException if invalid signal name or a signal is 
already handled by OS or JVM
+   */
+  public static void registerSigListener(String sigName, SigListener 
sigListener) {
+    if (sigListener == null) {
+      throw new IllegalArgumentException("NULL signal listener");
+    }
+    if (sigName == null) {
+      throw new IllegalArgumentException("NULL signal name");
+    }
+    Signal.handle(new Signal(sigName), new SigHandler(sigListener));
+    LOGGER.info("Signal Listener registered for signal " + sigName);
+  }
+}

http://git-wip-us.apache.org/repos/asf/sentry/blob/73a5eabe/sentry-hdfs/sentry-hdfs-common/src/main/java/org/apache/sentry/hdfs/ServiceConstants.java
----------------------------------------------------------------------
diff --git 
a/sentry-hdfs/sentry-hdfs-common/src/main/java/org/apache/sentry/hdfs/ServiceConstants.java
 
b/sentry-hdfs/sentry-hdfs-common/src/main/java/org/apache/sentry/hdfs/ServiceConstants.java
index 2c0ae82..23552c2 100644
--- 
a/sentry-hdfs/sentry-hdfs-common/src/main/java/org/apache/sentry/hdfs/ServiceConstants.java
+++ 
b/sentry-hdfs/sentry-hdfs-common/src/main/java/org/apache/sentry/hdfs/ServiceConstants.java
@@ -66,6 +66,7 @@ public class ServiceConstants {
     public static final int 
SENTRY_HDFS_SYNC_METASTORE_CACHE_MAX_PART_PER_RPC_DEFAULT = 100;
     public static final String 
SENTRY_HDFS_SYNC_METASTORE_CACHE_MAX_TABLES_PER_RPC = 
"sentry.hdfs.sync.metastore.cache.max-tables-per-rpc";
     public static final int 
SENTRY_HDFS_SYNC_METASTORE_CACHE_MAX_TABLES_PER_RPC_DEFAULT = 100;
+    public static final String SENTRY_SERVICE_FULL_UPDATE_SIGNAL = 
"sentry.hdfs.sync.full-update-signal";
   }
 
   public static class ClientConfig {

http://git-wip-us.apache.org/repos/asf/sentry/blob/73a5eabe/sentry-hdfs/sentry-hdfs-service/src/main/java/org/apache/sentry/hdfs/SentryPlugin.java
----------------------------------------------------------------------
diff --git 
a/sentry-hdfs/sentry-hdfs-service/src/main/java/org/apache/sentry/hdfs/SentryPlugin.java
 
b/sentry-hdfs/sentry-hdfs-service/src/main/java/org/apache/sentry/hdfs/SentryPlugin.java
index f3926a2..3695709 100644
--- 
a/sentry-hdfs/sentry-hdfs-service/src/main/java/org/apache/sentry/hdfs/SentryPlugin.java
+++ 
b/sentry-hdfs/sentry-hdfs-service/src/main/java/org/apache/sentry/hdfs/SentryPlugin.java
@@ -22,10 +22,12 @@ import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
 
 import com.codahale.metrics.Timer;
 import org.apache.hadoop.conf.Configuration;
+import org.apache.sentry.core.common.utils.SigUtils;
 import org.apache.sentry.hdfs.ServiceConstants.ServerConfig;
 import org.apache.sentry.hdfs.UpdateForwarder.ExternalImageRetriever;
 import org.apache.sentry.hdfs.service.thrift.TPermissionsUpdate;
@@ -46,10 +48,74 @@ import 
org.apache.sentry.provider.db.service.thrift.TSentryPrivilege;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class SentryPlugin implements SentryPolicyStorePlugin {
+  /**
+   * SentryPlugin facilitates HDFS synchronization between HMS and NameNode.
+   * <p>
+   * Normally, synchronization happens via partial (incremental) updates:
+   * <ol>
+   * <li>
+   * Whenever updates happen on HMS, they are immediately pushed to Sentry.
+   * Commonly, it's a single update per remote call.
+   * <li>
+   * The NameNode periodically asks Sentry for updates. Sentry may return zero
+   * or more updates previously received from HMS.
+   * </ol>
+   * <p>
+   * Each individual update is assigned a corresponding sequence number. Those
+   * numbers serve to detect the out-of-sync situations between HMS and Sentry 
and
+   * between Sentry and NameNode. Detecting out-of-sync situation triggers full
+   * update between the components that are out-of-sync.
+   * <p>
+   * SentryPlugin also implements signal-triggered mechanism of full path
+   * updates from HMS to Sentry and from Sentry to NameNode, to address
+   * mission-critical out-of-sync situations that may be encountered in the 
field.
+   * Those out-of-sync situations may not be detectable via the exsiting 
sequence
+   * numbers mechanism (most likely due to the implementation bugs).
+   * <p>
+   * To facilitate signal-triggered full update from HMS to Sentry and from 
Sentry
+   * to the NameNode, the following 3 boolean variables are defined:
+   * fullUpdateHMS, fullUpdateHMSWait, and fullUpdateNN.
+   * <ol>
+   * <li>
+   * The purpose of fullUpdateHMS is to ensure that Sentry asks HMS for full
+   * update, and does so only once per signal.
+   * <li>
+   * The purpose of fullUpdateNN is to ensure that Sentry sends full update
+   * to NameNode, and does so only once per signal.
+   * <li>
+   * The purpose of fullUpdateHMSWait is to ensure that NN update only happens
+   * after HMS update.
+   * </ol>
+   * The details:
+   * <ol>
+   * <li>
+   * Upon receiving a signal, fullUpdateHMS, fullUpdateHMSWait, and 
fullUpdateNN
+   * are all set to true.
+   * <li>
+   * On the next call to getLastSeenHMSPathSeqNum() from HMS, Sentry checks if
+   * fullUpdateHMS == true. If yes, it returns invalid (zero) sequence number
+   * to HMS, so HMS would push full update by calling 
handlePathUpdateNotification()
+   * next time. fullUpdateHMS is immediately reset to false, to only trigger 
one
+   * full update request to HMS per signal.
+   * <li>
+   * When HMS calls handlePathUpdateNotification(), Sentry checks if the update
+   * is a full image. If it is, fullUpdateHMSWait is set to false.
+   * <li>
+   * When NameNode calls getAllPathsUpdatesFrom() asking for partial update,
+   * Sentry checks if both fullUpdateNN == true and fullUpdateHMSWait == false.
+   * If yes, it sends full update back to NameNode and immediately resets
+   * fullUpdateNN to false.
+   * </ol>
+   */
+
+public class SentryPlugin implements SentryPolicyStorePlugin, 
SigUtils.SigListener {
 
   private static final Logger LOGGER = 
LoggerFactory.getLogger(SentryPlugin.class);
 
+  private final AtomicBoolean fullUpdateHMSWait = new AtomicBoolean(false);
+  private final AtomicBoolean fullUpdateHMS = new AtomicBoolean(false);
+  private final AtomicBoolean fullUpdateNN = new AtomicBoolean(false);
+
   public static volatile SentryPlugin instance;
 
   static class PermImageRetriever implements 
ExternalImageRetriever<PermissionsUpdate> {
@@ -98,9 +164,25 @@ public class SentryPlugin implements 
SentryPolicyStorePlugin {
   private final AtomicLong permSeqNum = new AtomicLong(5);
   private PermImageRetriever permImageRetriever;
   private boolean outOfSync = false;
-
+  /*
+   * This number is smaller than starting sequence numbers used by NN and HMS
+   * so in both cases its effect is to creat appearence of out-of-sync
+   * updates on the Sentry server (as if there were no previous updates at 
all).
+   * It, in turn, triggers a) pushing full update from HMS to Sentry and
+   * b) pulling full update from Sentry to NameNode.
+   */
+  private static final long NO_LAST_SEEN_HMS_PATH_SEQ_NUM = 0L;
+
+  /*
+   * Call from HMS to get the last known update sequence #.
+   */
   long getLastSeenHMSPathSeqNum() {
-    return pathsUpdater.getLastSeen();
+    if (!fullUpdateHMS.getAndSet(false)) {
+      return pathsUpdater.getLastSeen();
+    } else {
+      LOGGER.info("SIGNAL HANDLING: asking for full update from HMS");
+      return NO_LAST_SEEN_HMS_PATH_SEQ_NUM;
+    }
   }
 
   @Override
@@ -120,20 +202,87 @@ public class SentryPlugin implements 
SentryPolicyStorePlugin {
         permImageRetriever, 100, initUpdateRetryDelayMs);
     LOGGER.info("Sentry HDFS plugin initialized !!");
     instance = this;
+
+    // register signal handler(s) if any signal(s) are configured
+    String[] sigs = 
conf.getStrings(ServerConfig.SENTRY_SERVICE_FULL_UPDATE_SIGNAL, null);
+    if (sigs != null && sigs.length != 0) {
+      for (String sig : sigs) {
+        try {
+          LOGGER.info("SIGNAL HANDLING: Registering Signal Handler For " + 
sig);
+          SigUtils.registerSigListener(sig, this);
+        } catch (Exception e) {
+          LOGGER.error("SIGNAL HANDLING: Signal Handle Registration Failure", 
e);
+        }
+      }
+    }
   }
 
+  /**
+   * Request for update from NameNode.
+   * Full update to NameNode should happen only after full update from HMS.
+   */
   public List<PathsUpdate> getAllPathsUpdatesFrom(long pathSeqNum) {
-    return pathsUpdater.getAllUpdatesFrom(pathSeqNum);
+    if (!fullUpdateNN.get()) {
+      // Most common case - Sentry is NOT handling a full update.
+      return pathsUpdater.getAllUpdatesFrom(pathSeqNum);
+    } else if (!fullUpdateHMSWait.get()) {
+      /*
+       * Sentry is in the middle of signal-triggered full update.
+       * It already got a full update from HMS
+       */
+      LOGGER.info("SIGNAL HANDLING: sending full update to NameNode");
+      fullUpdateNN.set(false); // don't do full NN update till the next signal
+      List<PathsUpdate> updates = 
pathsUpdater.getAllUpdatesFrom(NO_LAST_SEEN_HMS_PATH_SEQ_NUM);
+      /*
+       * This code branch is only called when Sentry is in the middle of a 
full update
+       * (fullUpdateNN == true) and Sentry has already received full update 
from HMS
+       * (fullUpdateHMSWait == false). It means that the local cache has a 
full update
+       * from HMS.
+       *
+       * The full update list is expected to contain the last full update as 
the first
+       * element, followed by zero or more subsequent partial updates.
+       *
+       * Returning NULL, empty, or partial update instead would be 
unexplainable, so
+       * it should be logged.
+       */
+      if (updates != null) {
+        if (!updates.isEmpty()) {
+          if (updates.get(0).hasFullImage()) {
+            LOGGER.info("SIGNAL HANDLING: Confirmed full update to NameNode");
+          } else {
+            LOGGER.warn("SIGNAL HANDLING: Sending partial instead of full 
update to NameNode (???)");
+          }
+        } else {
+          LOGGER.warn("SIGNAL HANDLING: Sending empty instead of full update 
to NameNode (???)");
+        }
+      } else {
+        LOGGER.warn("SIGNAL HANDLING: returned NULL instead of full update to 
NameNode (???)");
+      }
+      return updates;
+    } else {
+      // Sentry is handling a full update, but not yet received full update 
from HMS
+      LOGGER.warn("SIGNAL HANDLING: sending partial update to NameNode: still 
waiting for full update from HMS");
+      return pathsUpdater.getAllUpdatesFrom(pathSeqNum);
+    }
   }
 
   public List<PermissionsUpdate> getAllPermsUpdatesFrom(long permSeqNum) {
     return permsUpdater.getAllUpdatesFrom(permSeqNum);
   }
 
+  /*
+   * Handle partial (most common) or full update from HMS
+   */
   public void handlePathUpdateNotification(PathsUpdate update)
       throws SentryPluginException {
     pathsUpdater.handleUpdateNotification(update);
-    LOGGER.debug("Recieved Authz Path update [" + update.getSeqNum() + "]..");
+    if (!update.hasFullImage()) { // most common case of partial update
+      LOGGER.debug("Recieved Authz Path update [" + update.getSeqNum() + 
"]..");
+    } else { // rare case of full update
+      LOGGER.warn("Recieved Authz Path FULL update [" + update.getSeqNum() + 
"]..");
+      // indicate that we're ready to send full update to NameNode
+      fullUpdateHMSWait.set(false);
+    }
   }
 
   @Override
@@ -256,6 +405,14 @@ public class SentryPlugin implements 
SentryPolicyStorePlugin {
     LOGGER.debug("Authz Perm preUpdate [" + update.getSeqNum() + ", " + 
authzObj + "]..");
   }
 
+  @Override
+  public void onSignal(final String sigName) {
+    LOGGER.info("SIGNAL HANDLING: Received signal " + sigName + ", triggering 
full update");
+    fullUpdateHMS.set(true);
+    fullUpdateHMSWait.set(true);
+    fullUpdateNN.set(true);
+  }
+
   private String getAuthzObj(TSentryPrivilege privilege) {
     String authzObj = null;
     if (!SentryStore.isNULL(privilege.getDbName())) {

Reply via email to