Repository: hbase
Updated Branches:
  refs/heads/master ca74ec774 -> 330b0d05b


http://git-wip-us.apache.org/repos/asf/hbase/blob/330b0d05/hbase-zookeeper/src/main/java/org/apache/hadoop/hbase/zookeeper/ZKWatcher.java
----------------------------------------------------------------------
diff --git 
a/hbase-zookeeper/src/main/java/org/apache/hadoop/hbase/zookeeper/ZKWatcher.java
 
b/hbase-zookeeper/src/main/java/org/apache/hadoop/hbase/zookeeper/ZKWatcher.java
new file mode 100644
index 0000000..d0b0081
--- /dev/null
+++ 
b/hbase-zookeeper/src/main/java/org/apache/hadoop/hbase/zookeeper/ZKWatcher.java
@@ -0,0 +1,634 @@
+/*
+ *
+ * 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.hbase.zookeeper;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.Abortable;
+import org.apache.hadoop.hbase.AuthUtil;
+import org.apache.hadoop.hbase.ZooKeeperConnectionException;
+import org.apache.yetus.audience.InterfaceAudience;
+import org.apache.hadoop.hbase.security.Superusers;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.WatchedEvent;
+import org.apache.zookeeper.Watcher;
+import org.apache.zookeeper.ZooDefs.Ids;
+import org.apache.zookeeper.ZooDefs.Perms;
+import org.apache.zookeeper.data.ACL;
+import org.apache.zookeeper.data.Id;
+import org.apache.zookeeper.data.Stat;
+
+/**
+ * Acts as the single ZooKeeper Watcher.  One instance of this is instantiated
+ * for each Master, RegionServer, and client process.
+ *
+ * <p>This is the only class that implements {@link Watcher}.  Other internal
+ * classes which need to be notified of ZooKeeper events must register with
+ * the local instance of this watcher via {@link #registerListener}.
+ *
+ * <p>This class also holds and manages the connection to ZooKeeper.  Code to
+ * deal with connection related events and exceptions are handled here.
+ */
+@InterfaceAudience.Private
+public class ZKWatcher implements Watcher, Abortable, Closeable {
+  private static final Log LOG = LogFactory.getLog(ZKWatcher.class);
+
+  // Identifier for this watcher (for logging only).  It is made of the prefix
+  // passed on construction and the zookeeper sessionid.
+  private String prefix;
+  private String identifier;
+
+  // zookeeper quorum
+  private String quorum;
+
+  // zookeeper connection
+  private final RecoverableZooKeeper recoverableZooKeeper;
+
+  // abortable in case of zk failure
+  protected Abortable abortable;
+  // Used if abortable is null
+  private boolean aborted = false;
+
+  public final ZNodePaths znodePaths;
+
+  // listeners to be notified
+  private final List<ZKListener> listeners = new CopyOnWriteArrayList<>();
+
+  // Used by ZKUtil:waitForZKConnectionIfAuthenticating to wait for SASL
+  // negotiation to complete
+  public CountDownLatch saslLatch = new CountDownLatch(1);
+
+
+
+  private final Configuration conf;
+
+  /* A pattern that matches a Kerberos name, borrowed from Hadoop's 
KerberosName */
+  private static final Pattern NAME_PATTERN = 
Pattern.compile("([^/@]*)(/([^/@]*))?@([^/@]*)");
+
+  /**
+   * Instantiate a ZooKeeper connection and watcher.
+   * @param identifier string that is passed to RecoverableZookeeper to be 
used as
+   * identifier for this instance. Use null for default.
+   * @throws IOException
+   * @throws ZooKeeperConnectionException
+   */
+  public ZKWatcher(Configuration conf, String identifier,
+                   Abortable abortable) throws ZooKeeperConnectionException, 
IOException {
+    this(conf, identifier, abortable, false);
+  }
+
+  /**
+   * Instantiate a ZooKeeper connection and watcher.
+   * @param conf
+   * @param identifier string that is passed to RecoverableZookeeper to be 
used as identifier for
+   *          this instance. Use null for default.
+   * @param abortable Can be null if there is on error there is no host to 
abort: e.g. client
+   *          context.
+   * @param canCreateBaseZNode
+   * @throws IOException
+   * @throws ZooKeeperConnectionException
+   */
+  public ZKWatcher(Configuration conf, String identifier,
+                   Abortable abortable, boolean canCreateBaseZNode)
+  throws IOException, ZooKeeperConnectionException {
+    this.conf = conf;
+    this.quorum = ZKConfig.getZKQuorumServersString(conf);
+    this.prefix = identifier;
+    // Identifier will get the sessionid appended later below down when we
+    // handle the syncconnect event.
+    this.identifier = identifier + "0x0";
+    this.abortable = abortable;
+    this.znodePaths = new ZNodePaths(conf);
+    PendingWatcher pendingWatcher = new PendingWatcher();
+    this.recoverableZooKeeper = ZKUtil.connect(conf, quorum, pendingWatcher, 
identifier);
+    pendingWatcher.prepare(this);
+    if (canCreateBaseZNode) {
+      try {
+        createBaseZNodes();
+      } catch (ZooKeeperConnectionException zce) {
+        try {
+          this.recoverableZooKeeper.close();
+        } catch (InterruptedException ie) {
+          LOG.debug("Encountered InterruptedException when closing " + 
this.recoverableZooKeeper);
+          Thread.currentThread().interrupt();
+        }
+        throw zce;
+      }
+    }
+  }
+
+  private void createBaseZNodes() throws ZooKeeperConnectionException {
+    try {
+      // Create all the necessary "directories" of znodes
+      ZKUtil.createWithParents(this, znodePaths.baseZNode);
+      ZKUtil.createAndFailSilent(this, znodePaths.rsZNode);
+      ZKUtil.createAndFailSilent(this, znodePaths.drainingZNode);
+      ZKUtil.createAndFailSilent(this, znodePaths.tableZNode);
+      ZKUtil.createAndFailSilent(this, znodePaths.splitLogZNode);
+      ZKUtil.createAndFailSilent(this, znodePaths.backupMasterAddressesZNode);
+      ZKUtil.createAndFailSilent(this, znodePaths.tableLockZNode);
+      ZKUtil.createAndFailSilent(this, znodePaths.masterMaintZNode);
+    } catch (KeeperException e) {
+      throw new ZooKeeperConnectionException(
+          prefix("Unexpected KeeperException creating base node"), e);
+    }
+  }
+
+  /** Returns whether the znode is supposed to be readable by the client
+   * and DOES NOT contain sensitive information (world readable).*/
+  public boolean isClientReadable(String node) {
+    // Developer notice: These znodes are world readable. DO NOT add more 
znodes here UNLESS
+    // all clients need to access this data to work. Using zk for sharing data 
to clients (other
+    // than service lookup case is not a recommended design pattern.
+    return
+        node.equals(znodePaths.baseZNode) ||
+        znodePaths.isAnyMetaReplicaZNode(node) ||
+        node.equals(znodePaths.masterAddressZNode) ||
+        node.equals(znodePaths.clusterIdZNode)||
+        node.equals(znodePaths.rsZNode) ||
+        // /hbase/table and /hbase/table/foo is allowed, /hbase/table-lock is 
not
+        node.equals(znodePaths.tableZNode) ||
+        node.startsWith(znodePaths.tableZNode + "/");
+  }
+
+  /**
+   * On master start, we check the znode ACLs under the root directory and set 
the ACLs properly
+   * if needed. If the cluster goes from an unsecure setup to a secure setup, 
this step is needed
+   * so that the existing znodes created with open permissions are now changed 
with restrictive
+   * perms.
+   */
+  public void checkAndSetZNodeAcls() {
+    if (!ZKUtil.isSecureZooKeeper(getConfiguration())) {
+      LOG.info("not a secure deployment, proceeding");
+      return;
+    }
+
+    // Check the base znodes permission first. Only do the recursion if base 
znode's perms are not
+    // correct.
+    try {
+      List<ACL> actualAcls = recoverableZooKeeper.getAcl(znodePaths.baseZNode, 
new Stat());
+
+      if (!isBaseZnodeAclSetup(actualAcls)) {
+        LOG.info("setting znode ACLs");
+        setZnodeAclsRecursive(znodePaths.baseZNode);
+      }
+    } catch(KeeperException.NoNodeException nne) {
+      return;
+    } catch(InterruptedException ie) {
+      interruptedExceptionNoThrow(ie, false);
+    } catch (IOException|KeeperException e) {
+      LOG.warn("Received exception while checking and setting zookeeper ACLs", 
e);
+    }
+  }
+
+  /**
+   * Set the znode perms recursively. This will do post-order recursion, so 
that baseZnode ACLs
+   * will be set last in case the master fails in between.
+   * @param znode
+   */
+  private void setZnodeAclsRecursive(String znode) throws KeeperException, 
InterruptedException {
+    List<String> children = recoverableZooKeeper.getChildren(znode, false);
+
+    for (String child : children) {
+      setZnodeAclsRecursive(ZNodePaths.joinZNode(znode, child));
+    }
+    List<ACL> acls = ZKUtil.createACL(this, znode, true);
+    LOG.info("Setting ACLs for znode:" + znode + " , acl:" + acls);
+    recoverableZooKeeper.setAcl(znode, acls, -1);
+  }
+
+  /**
+   * Checks whether the ACLs returned from the base znode (/hbase) is set for 
secure setup.
+   * @param acls acls from zookeeper
+   * @return whether ACLs are set for the base znode
+   * @throws IOException
+   */
+  private boolean isBaseZnodeAclSetup(List<ACL> acls) throws IOException {
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Checking znode ACLs");
+    }
+    String[] superUsers = conf.getStrings(Superusers.SUPERUSER_CONF_KEY);
+    // Check whether ACL set for all superusers
+    if (superUsers != null && !checkACLForSuperUsers(superUsers, acls)) {
+      return false;
+    }
+
+    // this assumes that current authenticated user is the same as zookeeper 
client user
+    // configured via JAAS
+    String hbaseUser = 
UserGroupInformation.getCurrentUser().getShortUserName();
+
+    if (acls.isEmpty()) {
+      if (LOG.isDebugEnabled()) {
+        LOG.debug("ACL is empty");
+      }
+      return false;
+    }
+
+    for (ACL acl : acls) {
+      int perms = acl.getPerms();
+      Id id = acl.getId();
+      // We should only set at most 3 possible ACLs for 3 Ids. One for 
everyone, one for superuser
+      // and one for the hbase user
+      if (Ids.ANYONE_ID_UNSAFE.equals(id)) {
+        if (perms != Perms.READ) {
+          if (LOG.isDebugEnabled()) {
+            LOG.debug(String.format("permissions for '%s' are not correct: 
have 0x%x, want 0x%x",
+              id, perms, Perms.READ));
+          }
+          return false;
+        }
+      } else if (superUsers != null && isSuperUserId(superUsers, id)) {
+        if (perms != Perms.ALL) {
+          if (LOG.isDebugEnabled()) {
+            LOG.debug(String.format("permissions for '%s' are not correct: 
have 0x%x, want 0x%x",
+              id, perms, Perms.ALL));
+          }
+          return false;
+        }
+      } else if ("sasl".equals(id.getScheme())) {
+        String name = id.getId();
+        // If ZooKeeper recorded the Kerberos full name in the ACL, use only 
the shortname
+        Matcher match = NAME_PATTERN.matcher(name);
+        if (match.matches()) {
+          name = match.group(1);
+        }
+        if (name.equals(hbaseUser)) {
+          if (perms != Perms.ALL) {
+            if (LOG.isDebugEnabled()) {
+              LOG.debug(String.format("permissions for '%s' are not correct: 
have 0x%x, want 0x%x",
+                id, perms, Perms.ALL));
+            }
+            return false;
+          }
+        } else {
+          if (LOG.isDebugEnabled()) {
+            LOG.debug("Unexpected shortname in SASL ACL: " + id);
+          }
+          return false;
+        }
+      } else {
+        if (LOG.isDebugEnabled()) {
+          LOG.debug("unexpected ACL id '" + id + "'");
+        }
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /*
+   * Validate whether ACL set for all superusers.
+   */
+  private boolean checkACLForSuperUsers(String[] superUsers, List<ACL> acls) {
+    for (String user : superUsers) {
+      boolean hasAccess = false;
+      // TODO: Validate super group members also when ZK supports setting node 
ACL for groups.
+      if (!AuthUtil.isGroupPrincipal(user)) {
+        for (ACL acl : acls) {
+          if (user.equals(acl.getId().getId())) {
+            if (acl.getPerms() == Perms.ALL) {
+              hasAccess = true;
+            } else {
+              if (LOG.isDebugEnabled()) {
+                LOG.debug(String.format(
+                  "superuser '%s' does not have correct permissions: have 
0x%x, want 0x%x",
+                  acl.getId().getId(), acl.getPerms(), Perms.ALL));
+              }
+            }
+            break;
+          }
+        }
+        if (!hasAccess) {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
+  /*
+   * Validate whether ACL ID is superuser.
+   */
+  public static boolean isSuperUserId(String[] superUsers, Id id) {
+    for (String user : superUsers) {
+      // TODO: Validate super group members also when ZK supports setting node 
ACL for groups.
+      if (!AuthUtil.isGroupPrincipal(user) && new Id("sasl", user).equals(id)) 
{
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public String toString() {
+    return this.identifier + ", quorum=" + quorum + ", baseZNode=" + 
znodePaths.baseZNode;
+  }
+
+  /**
+   * Adds this instance's identifier as a prefix to the passed <code>str</code>
+   * @param str String to amend.
+   * @return A new string with this instance's identifier as prefix: e.g.
+   * if passed 'hello world', the returned string could be
+   */
+  public String prefix(final String str) {
+    return this.toString() + " " + str;
+  }
+
+  /**
+   * Get the znodes corresponding to the meta replicas from ZK
+   * @return list of znodes
+   * @throws KeeperException
+   */
+  public List<String> getMetaReplicaNodes() throws KeeperException {
+    List<String> childrenOfBaseNode = ZKUtil.listChildrenNoWatch(this, 
znodePaths.baseZNode);
+    List<String> metaReplicaNodes = new ArrayList<>(2);
+    if (childrenOfBaseNode != null) {
+      String pattern = 
conf.get("zookeeper.znode.metaserver","meta-region-server");
+      for (String child : childrenOfBaseNode) {
+        if (child.startsWith(pattern)) metaReplicaNodes.add(child);
+      }
+    }
+    return metaReplicaNodes;
+  }
+
+  /**
+   * Register the specified listener to receive ZooKeeper events.
+   * @param listener
+   */
+  public void registerListener(ZKListener listener) {
+    listeners.add(listener);
+  }
+
+  /**
+   * Register the specified listener to receive ZooKeeper events and add it as
+   * the first in the list of current listeners.
+   * @param listener
+   */
+  public void registerListenerFirst(ZKListener listener) {
+    listeners.add(0, listener);
+  }
+
+  public void unregisterListener(ZKListener listener) {
+    listeners.remove(listener);
+  }
+
+  /**
+   * Clean all existing listeners
+   */
+  public void unregisterAllListeners() {
+    listeners.clear();
+  }
+
+  /**
+   * Get a copy of current registered listeners
+   */
+  public List<ZKListener> getListeners() {
+    return new ArrayList<>(listeners);
+  }
+
+  /**
+   * @return The number of currently registered listeners
+   */
+  public int getNumberOfListeners() {
+    return listeners.size();
+  }
+
+  /**
+   * Get the connection to ZooKeeper.
+   * @return connection reference to zookeeper
+   */
+  public RecoverableZooKeeper getRecoverableZooKeeper() {
+    return recoverableZooKeeper;
+  }
+
+  public void reconnectAfterExpiration() throws IOException, KeeperException, 
InterruptedException {
+    recoverableZooKeeper.reconnectAfterExpiration();
+  }
+
+  /**
+   * Get the quorum address of this instance.
+   * @return quorum string of this zookeeper connection instance
+   */
+  public String getQuorum() {
+    return quorum;
+  }
+
+  /**
+   * Get the znodePaths.
+   * <p>
+   * Mainly used for mocking as mockito can not mock a field access.
+   */
+  public ZNodePaths getZNodePaths() {
+    return znodePaths;
+  }
+
+  /**
+   * Method called from ZooKeeper for events and connection status.
+   * <p>
+   * Valid events are passed along to listeners.  Connection status changes
+   * are dealt with locally.
+   */
+  @Override
+  public void process(WatchedEvent event) {
+    LOG.debug(prefix("Received ZooKeeper Event, " +
+        "type=" + event.getType() + ", " +
+        "state=" + event.getState() + ", " +
+        "path=" + event.getPath()));
+
+    switch(event.getType()) {
+
+      // If event type is NONE, this is a connection status change
+      case None: {
+        connectionEvent(event);
+        break;
+      }
+
+      // Otherwise pass along to the listeners
+
+      case NodeCreated: {
+        for(ZKListener listener : listeners) {
+          listener.nodeCreated(event.getPath());
+        }
+        break;
+      }
+
+      case NodeDeleted: {
+        for(ZKListener listener : listeners) {
+          listener.nodeDeleted(event.getPath());
+        }
+        break;
+      }
+
+      case NodeDataChanged: {
+        for(ZKListener listener : listeners) {
+          listener.nodeDataChanged(event.getPath());
+        }
+        break;
+      }
+
+      case NodeChildrenChanged: {
+        for(ZKListener listener : listeners) {
+          listener.nodeChildrenChanged(event.getPath());
+        }
+        break;
+      }
+    }
+  }
+
+  // Connection management
+
+  /**
+   * Called when there is a connection-related event via the Watcher callback.
+   * <p>
+   * If Disconnected or Expired, this should shutdown the cluster. But, since
+   * we send a KeeperException.SessionExpiredException along with the abort
+   * call, it's possible for the Abortable to catch it and try to create a new
+   * session with ZooKeeper. This is what the client does in HCM.
+   * <p>
+   * @param event
+   */
+  private void connectionEvent(WatchedEvent event) {
+    switch(event.getState()) {
+      case SyncConnected:
+        this.identifier = this.prefix + "-0x" +
+          Long.toHexString(this.recoverableZooKeeper.getSessionId());
+        // Update our identifier.  Otherwise ignore.
+        LOG.debug(this.identifier + " connected");
+        break;
+
+      // Abort the server if Disconnected or Expired
+      case Disconnected:
+        LOG.debug(prefix("Received Disconnected from ZooKeeper, ignoring"));
+        break;
+
+      case Expired:
+        String msg = prefix(this.identifier + " received expired from " +
+          "ZooKeeper, aborting");
+        // TODO: One thought is to add call to ZKListener so say,
+        // ZKNodeTracker can zero out its data values.
+        if (this.abortable != null) {
+          this.abortable.abort(msg, new 
KeeperException.SessionExpiredException());
+        }
+        break;
+
+      case ConnectedReadOnly:
+      case SaslAuthenticated:
+      case AuthFailed:
+        break;
+
+      default:
+        throw new IllegalStateException("Received event is not valid: " + 
event.getState());
+    }
+  }
+
+  /**
+   * Forces a synchronization of this ZooKeeper client connection.
+   * <p>
+   * Executing this method before running other methods will ensure that the
+   * subsequent operations are up-to-date and consistent as of the time that
+   * the sync is complete.
+   * <p>
+   * This is used for compareAndSwap type operations where we need to read the
+   * data of an existing node and delete or transition that node, utilizing the
+   * previously read version and data.  We want to ensure that the version read
+   * is up-to-date from when we begin the operation.
+   */
+  public void sync(String path) throws KeeperException {
+    this.recoverableZooKeeper.sync(path, null, null);
+  }
+
+  /**
+   * Handles KeeperExceptions in client calls.
+   * <p>
+   * This may be temporary but for now this gives one place to deal with these.
+   * <p>
+   * TODO: Currently this method rethrows the exception to let the caller 
handle
+   * <p>
+   * @param ke
+   * @throws KeeperException
+   */
+  public void keeperException(KeeperException ke)
+  throws KeeperException {
+    LOG.error(prefix("Received unexpected KeeperException, re-throwing 
exception"), ke);
+    throw ke;
+  }
+
+  /**
+   * Handles InterruptedExceptions in client calls.
+   * @param ie the InterruptedException instance thrown
+   * @throws KeeperException the exception to throw, transformed from the 
InterruptedException
+   */
+  public void interruptedException(InterruptedException ie) throws 
KeeperException {
+    interruptedExceptionNoThrow(ie, true);
+    // Throw a system error exception to let upper level handle it
+    throw new KeeperException.SystemErrorException();
+  }
+
+  /**
+   * Log the InterruptedException and interrupt current thread
+   * @param ie The IterruptedException to log
+   * @param throwLater Whether we will throw the exception latter
+   */
+  public void interruptedExceptionNoThrow(InterruptedException ie, boolean 
throwLater) {
+    LOG.debug(prefix("Received InterruptedException, will interrupt current 
thread"
+        + (throwLater ? " and rethrow a SystemErrorException" : "")),
+      ie);
+    // At least preserve interrupt.
+    Thread.currentThread().interrupt();
+  }
+
+  /**
+   * Close the connection to ZooKeeper.
+   *
+   */
+  @Override
+  public void close() {
+    try {
+      recoverableZooKeeper.close();
+    } catch (InterruptedException e) {
+      Thread.currentThread().interrupt();
+    }
+  }
+
+  public Configuration getConfiguration() {
+    return conf;
+  }
+
+  @Override
+  public void abort(String why, Throwable e) {
+    if (this.abortable != null) this.abortable.abort(why, e);
+    else this.aborted = true;
+  }
+
+  @Override
+  public boolean isAborted() {
+    return this.abortable == null? this.aborted: this.abortable.isAborted();
+  }
+}

http://git-wip-us.apache.org/repos/asf/hbase/blob/330b0d05/hbase-zookeeper/src/test/java/org/apache/hadoop/hbase/zookeeper/TestInstancePending.java
----------------------------------------------------------------------
diff --git 
a/hbase-zookeeper/src/test/java/org/apache/hadoop/hbase/zookeeper/TestInstancePending.java
 
b/hbase-zookeeper/src/test/java/org/apache/hadoop/hbase/zookeeper/TestInstancePending.java
new file mode 100644
index 0000000..e67c9fd
--- /dev/null
+++ 
b/hbase-zookeeper/src/test/java/org/apache/hadoop/hbase/zookeeper/TestInstancePending.java
@@ -0,0 +1,49 @@
+/*
+ * 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.hbase.zookeeper;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.hadoop.hbase.testclassification.SmallTests;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+@Category(SmallTests.class)
+public class TestInstancePending {
+  @Test(timeout = 1000)
+  public void test() throws Exception {
+    final InstancePending<String> pending = new InstancePending<>();
+    final AtomicReference<String> getResultRef = new AtomicReference<>();
+
+    new Thread() {
+      @Override
+      public void run() {
+        getResultRef.set(pending.get());
+      }
+    }.start();
+
+    Thread.sleep(100);
+    Assert.assertNull(getResultRef.get());
+
+    pending.prepare("abc");
+    Thread.sleep(100);
+    Assert.assertEquals("abc", getResultRef.get());
+  }
+}

http://git-wip-us.apache.org/repos/asf/hbase/blob/330b0d05/hbase-zookeeper/src/test/java/org/apache/hadoop/hbase/zookeeper/TestZKMetrics.java
----------------------------------------------------------------------
diff --git 
a/hbase-zookeeper/src/test/java/org/apache/hadoop/hbase/zookeeper/TestZKMetrics.java
 
b/hbase-zookeeper/src/test/java/org/apache/hadoop/hbase/zookeeper/TestZKMetrics.java
new file mode 100644
index 0000000..2811cc5
--- /dev/null
+++ 
b/hbase-zookeeper/src/test/java/org/apache/hadoop/hbase/zookeeper/TestZKMetrics.java
@@ -0,0 +1,80 @@
+/*
+ *
+ * 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.hbase.zookeeper;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import org.apache.hadoop.hbase.testclassification.SmallTests;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+@Category(SmallTests.class)
+public class TestZKMetrics {
+
+  @Test
+  public void testRegisterExceptions() {
+    MetricsZooKeeperSource zkSource = mock(MetricsZooKeeperSourceImpl.class);
+    ZKMetrics metricsZK = new ZKMetrics(zkSource);
+    metricsZK.registerAuthFailedException();
+    metricsZK.registerConnectionLossException();
+    metricsZK.registerConnectionLossException();
+    metricsZK.registerDataInconsistencyException();
+    metricsZK.registerInvalidACLException();
+    metricsZK.registerNoAuthException();
+    metricsZK.registerOperationTimeoutException();
+    metricsZK.registerOperationTimeoutException();
+    metricsZK.registerRuntimeInconsistencyException();
+    metricsZK.registerSessionExpiredException();
+    metricsZK.registerSystemErrorException();
+    metricsZK.registerSystemErrorException();
+    metricsZK.registerFailedZKCall();
+
+    verify(zkSource, times(1)).incrementAuthFailedCount();
+    // ConnectionLoss Exception was registered twice.
+    verify(zkSource, times(2)).incrementConnectionLossCount();
+    verify(zkSource, times(1)).incrementDataInconsistencyCount();
+    verify(zkSource, times(1)).incrementInvalidACLCount();
+    verify(zkSource, times(1)).incrementNoAuthCount();
+    // OperationTimeout Exception was registered twice.
+    verify(zkSource, times(2)).incrementOperationTimeoutCount();
+    verify(zkSource, times(1)).incrementRuntimeInconsistencyCount();
+    verify(zkSource, times(1)).incrementSessionExpiredCount();
+    // SystemError Exception was registered twice.
+    verify(zkSource, times(2)).incrementSystemErrorCount();
+    verify(zkSource, times(1)).incrementTotalFailedZKCalls();
+  }
+
+  @Test
+  public void testLatencyHistogramUpdates() {
+    MetricsZooKeeperSource zkSource = mock(MetricsZooKeeperSourceImpl.class);
+    ZKMetrics metricsZK = new ZKMetrics(zkSource);
+    long latency = 100;
+
+    metricsZK.registerReadOperationLatency(latency);
+    metricsZK.registerReadOperationLatency(latency);
+    metricsZK.registerWriteOperationLatency(latency);
+    metricsZK.registerSyncOperationLatency(latency);
+    // Read Operation Latency update was registered twice.
+    verify(zkSource, times(2)).recordReadOperationLatency(latency);
+    verify(zkSource, times(1)).recordWriteOperationLatency(latency);
+    verify(zkSource, times(1)).recordSyncOperationLatency(latency);
+  }
+}

http://git-wip-us.apache.org/repos/asf/hbase/blob/330b0d05/hbase-zookeeper/src/test/java/org/apache/hadoop/hbase/zookeeper/TestZKUtil.java
----------------------------------------------------------------------
diff --git 
a/hbase-zookeeper/src/test/java/org/apache/hadoop/hbase/zookeeper/TestZKUtil.java
 
b/hbase-zookeeper/src/test/java/org/apache/hadoop/hbase/zookeeper/TestZKUtil.java
new file mode 100644
index 0000000..7006040
--- /dev/null
+++ 
b/hbase-zookeeper/src/test/java/org/apache/hadoop/hbase/zookeeper/TestZKUtil.java
@@ -0,0 +1,113 @@
+/*
+ * 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.hbase.zookeeper;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.HBaseConfiguration;
+import org.apache.hadoop.hbase.ZooKeeperConnectionException;
+import org.apache.hadoop.hbase.security.Superusers;
+import org.apache.hadoop.hbase.testclassification.SmallTests;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.ZooDefs.Ids;
+import org.apache.zookeeper.ZooDefs.Perms;
+import org.apache.zookeeper.data.ACL;
+import org.apache.zookeeper.data.Id;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.mockito.Mockito;
+
+/**
+ *
+ */
+@Category({SmallTests.class})
+public class TestZKUtil {
+
+  @Test
+  public void testUnsecure() throws ZooKeeperConnectionException, IOException {
+    Configuration conf = HBaseConfiguration.create();
+    conf.set(Superusers.SUPERUSER_CONF_KEY, "user1");
+    String node = "/hbase/testUnsecure";
+    ZKWatcher watcher = new ZKWatcher(conf, node, null, false);
+    List<ACL> aclList = ZKUtil.createACL(watcher, node, false);
+    Assert.assertEquals(aclList.size(), 1);
+    Assert.assertTrue(aclList.contains(Ids.OPEN_ACL_UNSAFE.iterator().next()));
+  }
+
+  @Test
+  public void testSecuritySingleSuperuser() throws 
ZooKeeperConnectionException, IOException {
+    Configuration conf = HBaseConfiguration.create();
+    conf.set(Superusers.SUPERUSER_CONF_KEY, "user1");
+    String node = "/hbase/testSecuritySingleSuperuser";
+    ZKWatcher watcher = new ZKWatcher(conf, node, null, false);
+    List<ACL> aclList = ZKUtil.createACL(watcher, node, true);
+    Assert.assertEquals(aclList.size(), 2); // 1+1, since ACL will be set for 
the creator by default
+    Assert.assertTrue(aclList.contains(new ACL(Perms.ALL, new Id("sasl", 
"user1"))));
+    Assert.assertTrue(aclList.contains(Ids.CREATOR_ALL_ACL.iterator().next()));
+  }
+
+  @Test
+  public void testCreateACL() throws ZooKeeperConnectionException, IOException 
{
+    Configuration conf = HBaseConfiguration.create();
+    conf.set(Superusers.SUPERUSER_CONF_KEY, 
"user1,@group1,user2,@group2,user3");
+    String node = "/hbase/testCreateACL";
+    ZKWatcher watcher = new ZKWatcher(conf, node, null, false);
+    List<ACL> aclList = ZKUtil.createACL(watcher, node, true);
+    Assert.assertEquals(aclList.size(), 4); // 3+1, since ACL will be set for 
the creator by default
+    Assert.assertFalse(aclList.contains(new ACL(Perms.ALL, new Id("sasl", 
"@group1"))));
+    Assert.assertFalse(aclList.contains(new ACL(Perms.ALL, new Id("sasl", 
"@group2"))));
+    Assert.assertTrue(aclList.contains(new ACL(Perms.ALL, new Id("sasl", 
"user1"))));
+    Assert.assertTrue(aclList.contains(new ACL(Perms.ALL, new Id("sasl", 
"user2"))));
+    Assert.assertTrue(aclList.contains(new ACL(Perms.ALL, new Id("sasl", 
"user3"))));
+  }
+
+  @Test
+  public void testCreateACLWithSameUser() throws ZooKeeperConnectionException, 
IOException {
+    Configuration conf = HBaseConfiguration.create();
+    conf.set(Superusers.SUPERUSER_CONF_KEY, "user4,@group1,user5,user6");
+    
UserGroupInformation.setLoginUser(UserGroupInformation.createRemoteUser("user4"));
+    String node = "/hbase/testCreateACL";
+    ZKWatcher watcher = new ZKWatcher(conf, node, null, false);
+    List<ACL> aclList = ZKUtil.createACL(watcher, node, true);
+    Assert.assertEquals(aclList.size(), 3); // 3, since service user the same 
as one of superuser
+    Assert.assertFalse(aclList.contains(new ACL(Perms.ALL, new Id("sasl", 
"@group1"))));
+    Assert.assertTrue(aclList.contains(new ACL(Perms.ALL, new Id("auth", 
""))));
+    Assert.assertTrue(aclList.contains(new ACL(Perms.ALL, new Id("sasl", 
"user5"))));
+    Assert.assertTrue(aclList.contains(new ACL(Perms.ALL, new Id("sasl", 
"user6"))));
+  }
+
+  @Test(expected = KeeperException.SystemErrorException.class)
+  public void testInterruptedDuringAction()
+      throws ZooKeeperConnectionException, IOException, KeeperException, 
InterruptedException {
+    final RecoverableZooKeeper recoverableZk = 
Mockito.mock(RecoverableZooKeeper.class);
+    ZKWatcher zkw = new ZKWatcher(HBaseConfiguration.create(), "unittest", 
null) {
+      @Override
+      public RecoverableZooKeeper getRecoverableZooKeeper() {
+        return recoverableZk;
+      }
+    };
+    Mockito.doThrow(new InterruptedException()).when(recoverableZk)
+        .getChildren(zkw.znodePaths.baseZNode, null);
+    ZKUtil.listChildrenNoWatch(zkw, zkw.znodePaths.baseZNode);
+  }
+}

http://git-wip-us.apache.org/repos/asf/hbase/blob/330b0d05/hbase-zookeeper/src/test/java/org/apache/hadoop/hbase/zookeeper/TestZKWatcher.java
----------------------------------------------------------------------
diff --git 
a/hbase-zookeeper/src/test/java/org/apache/hadoop/hbase/zookeeper/TestZKWatcher.java
 
b/hbase-zookeeper/src/test/java/org/apache/hadoop/hbase/zookeeper/TestZKWatcher.java
new file mode 100644
index 0000000..bd4575d
--- /dev/null
+++ 
b/hbase-zookeeper/src/test/java/org/apache/hadoop/hbase/zookeeper/TestZKWatcher.java
@@ -0,0 +1,57 @@
+/**
+ * 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.hbase.zookeeper;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+
+import org.apache.hadoop.hbase.HBaseConfiguration;
+import org.apache.hadoop.hbase.testclassification.SmallTests;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+@Category({ SmallTests.class })
+public class TestZKWatcher {
+
+  @Test
+  public void testIsClientReadable() throws IOException {
+    ZKWatcher watcher =
+      new ZKWatcher(HBaseConfiguration.create(), "testIsClientReadable", null, 
false);
+
+    assertTrue(watcher.isClientReadable(watcher.znodePaths.baseZNode));
+    
assertTrue(watcher.isClientReadable(watcher.znodePaths.getZNodeForReplica(0)));
+    
assertTrue(watcher.isClientReadable(watcher.znodePaths.masterAddressZNode));
+    assertTrue(watcher.isClientReadable(watcher.znodePaths.clusterIdZNode));
+    assertTrue(watcher.isClientReadable(watcher.znodePaths.tableZNode));
+    assertTrue(
+      
watcher.isClientReadable(ZNodePaths.joinZNode(watcher.znodePaths.tableZNode, 
"foo")));
+    assertTrue(watcher.isClientReadable(watcher.znodePaths.rsZNode));
+
+    assertFalse(watcher.isClientReadable(watcher.znodePaths.tableLockZNode));
+    assertFalse(watcher.isClientReadable(watcher.znodePaths.balancerZNode));
+    
assertFalse(watcher.isClientReadable(watcher.znodePaths.regionNormalizerZNode));
+    
assertFalse(watcher.isClientReadable(watcher.znodePaths.clusterStateZNode));
+    assertFalse(watcher.isClientReadable(watcher.znodePaths.drainingZNode));
+    assertFalse(watcher.isClientReadable(watcher.znodePaths.splitLogZNode));
+    
assertFalse(watcher.isClientReadable(watcher.znodePaths.backupMasterAddressesZNode));
+
+    watcher.close();
+  }
+}

http://git-wip-us.apache.org/repos/asf/hbase/blob/330b0d05/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index b423085..ee35212 100755
--- a/pom.xml
+++ b/pom.xml
@@ -92,6 +92,7 @@
     <module>hbase-metrics</module>
     <module>hbase-spark-it</module>
     <module>hbase-backup</module>
+    <module>hbase-zookeeper</module>
   </modules>
   <!--Add apache snapshots in case we want to use unreleased versions of 
plugins:
       e.g. surefire 2.18-SNAPSHOT-->
@@ -1789,6 +1790,11 @@
         <type>test-jar</type>
         <scope>test</scope>
       </dependency>
+      <dependency>
+        <artifactId>hbase-zookeeper</artifactId>
+        <groupId>org.apache.hbase</groupId>
+        <version>${project.version}</version>
+      </dependency>
       <!-- General dependencies -->
       <dependency>
         <groupId>com.github.stephenc.findbugs</groupId>

Reply via email to