Package com.cloudera.flume.master

Source Code of com.cloudera.flume.master.ZKClient$ZKRetryHarness

/**
* Licensed to Cloudera, Inc. under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  Cloudera, Inc. 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 com.cloudera.flume.master;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.KeeperException.Code;
import org.apache.zookeeper.KeeperException.SessionExpiredException;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.cloudera.util.BackoffPolicy;
import com.cloudera.util.FixedRetryPolicy;
import com.cloudera.util.ResultRetryable;
import com.cloudera.util.RetryHarness;
import com.cloudera.util.Retryable;
import com.google.common.base.Preconditions;

/**
* Wrapper for talking to ZooKeeper that provides a set of primitives we use
* inside Flume. Not general purpose (yet), but useful to hide the complexities
* of dealing with ZK's various error conditions.
*
*/
public class ZKClient implements Watcher {
  protected ZooKeeper zk;
  final String hosts;
  static final Logger LOG = LoggerFactory.getLogger(ZKClient.class);

  class ZKRetryHarness {
    final protected RetryHarness harness;

    public ZKRetryHarness(Retryable retry, BackoffPolicy policy) {
      harness = new RetryHarness(retry, policy, true);
    }

    /**
     * Run-time casts an exception into one of the three types that we know
     * could get thrown. It's really ugly.
     */
    protected void massageException(Exception e) throws IOException,
        KeeperException, InterruptedException {
      try {
        throw e;
      } catch (IOException i) {
        throw i;
      } catch (KeeperException k) {
        throw k;
      } catch (InterruptedException i) {
        throw i;
      } catch (Exception x) {
        throw new IOException("Unexpected exception type!", x);
      }
    }

    protected boolean attempt() throws InterruptedException, KeeperException,
        IOException {
      try {
        return harness.attempt();
      } catch (SessionExpiredException s) {       
        ZKClient.this.zk = null;
        ZKClient.this.init(ZKClient.this.initCallBack);
      } catch (Exception e) {
        massageException(e);
      }
      // Should never fall through here
      return false;
    }
  }

  class ZKRetryable<T> extends ResultRetryable<T> {
    /**
     * Takes default action for ZK exceptions. If CONNECTIONLOSS, try and
     * reconnect followed by abort. Otherwise, abort and signal failure.
     */
    protected void defaultHandleException(KeeperException k)
        throws KeeperException, IOException, InterruptedException {
      if (k.code() == Code.CONNECTIONLOSS) {
        throw k; // Continue
      } else {
        harness.doAbort();
        throw k;
      }
    }
  }

  /**
   * Creates a new client, but does not connect.
   */
  public ZKClient(String hosts) {
    this.hosts = hosts;
  }

  /**
   * Called when client successfully connects.
   */
  abstract public static class InitCallback {
    abstract public void success(ZKClient client) throws IOException;
  }

  /**
   * Establishes a connection with a ZooKeeper service.
   */
  public boolean init() throws IOException {
    return init(null);
  }

  InitCallback initCallBack;
 
  /**
   * Establishes a connection with a cluster of ZooKeeper servers. Throws an
   * IOException on failure.
   */
  public synchronized boolean init(final InitCallback initCallback)
      throws IOException {
    Preconditions.checkState(this.zk == null, "zk not null in ZKClient.init");
    initCallBack = initCallback;
    final Retryable retry = new Retryable() {
      public boolean doTry() throws Exception {
        // Wait on this latch for a connection to complete
        // It's important that every try gets its own latch
        final CountDownLatch latch = new CountDownLatch(1);
        final Watcher watcher = new Watcher() {
          public void process(WatchedEvent event) {
            // Don't down the latch if we weren't the most recent attempt
            if (event.getState() == KeeperState.SyncConnected) {
              latch.countDown();
            }
          }
        };

        zk = new ZooKeeper(hosts, 5000, watcher);

        if (!latch.await(5, TimeUnit.SECONDS)) {
          throw new IOException("Could not connect to ZooKeeper!");
        }
        if (initCallback != null) {         
          initCallback.success(ZKClient.this);
        }
        return true;
      };
    };
    RetryHarness harness = new RetryHarness(retry, new FixedRetryPolicy(3),
        true);

    try {
      return harness.attempt();
    } catch (IOException i) {
      throw i;
    } catch (Exception e) {
      throw new IOException("Unexpected exception connecting to ZK", e);
    }
  }

  /**
   * Closes the connection to the ZooKeeper service
   */
  public synchronized void close() throws InterruptedException {
    if (this.zk != null) {
      this.getZK().close();
      this.zk = null;
    }
  }

  /**
   * Will make sure that a node exists, with a given path and data. If the node
   * already exists, will not try to set the data.
   */
  public boolean ensureExists(final String path, final byte[] data)
      throws KeeperException, IOException, InterruptedException {
    Preconditions.checkArgument(zk != null);
    final FixedRetryPolicy policy = new FixedRetryPolicy(3);
    if (zk.exists(path, false) != null) {
      return true;
    }
    ZKRetryable<Boolean> retry = new ZKRetryable<Boolean>() {
      public boolean doTry() throws Exception {
        result = true;
        try {
          zk.create(path, data, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
          return true;
        } catch (KeeperException k) {
          if (k.code() == Code.NODEEXISTS) {
            // Note that we could have this situation: try to create, succeed
            // but get CONNECTIONLOSS, reconnect, try to create, return false.
            // Until ZK-22 goes in there really isn't a much better way of doing
            // this
            result = false;
            return true; // true to denote success of operation
          }
          defaultHandleException(k);
        }
        return false;
      }
    };

    ZKRetryHarness harness = new ZKRetryHarness(retry, policy);
    harness.attempt();

    return retry.getResult();
  }

  /**
   * Ensure that the given path is deleted, whether or not we did it.
   */
  public void ensureDeleted(final String path, final int version)
      throws KeeperException, IOException, InterruptedException {
    Preconditions.checkArgument(zk != null);
    final FixedRetryPolicy policy = new FixedRetryPolicy(3);
    ZKRetryable<Void> retry = new ZKRetryable<Void>() {
      public boolean doTry() throws Exception {
        try {
          zk.delete(path, version);
          return true;
        } catch (KeeperException k) {
          if (k.code() == Code.NONODE) {
            return true;
          }
          defaultHandleException(k);
        }
        return false;
      }
    };

    ZKRetryHarness harness = new ZKRetryHarness(retry, policy);
    harness.attempt();
  }

  /**
   * Returns the underlying ZooKeeper object. Use only for methods that are not
   * captured by this class.
   *
   * Note that the returned ZooKeeper could be closed by another thread. This
   * method is not recommended for common use.
   */
  public synchronized ZooKeeper getZK() {
    Preconditions.checkState(zk != null, "zk is null in getZK");
    return zk;
  }

  /**
   * Returns the long suffix of a sequential znode
   */
  static public long extractSuffix(String prefix, String znode)
      throws NumberFormatException {
    // This pattern will match nodes created in ZK with the NODE_SEQUENTIAL
    // type. These nodes have a ten digit monotonic sequence number appended to
    // them, which we want to sort by.
    final Pattern pattern = Pattern.compile("(" + prefix
        + ")(\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d)$");
    Matcher matcher = pattern.matcher(znode);
    if (matcher.find()) {
      return Long.parseLong(matcher.group(2));
    } else {
      throw new NumberFormatException("Znode: " + znode
          + " does not have a numeric 10-digit suffix (Prefix was " + prefix
          + ")");
    }
  }

  /**
   * Returns the most recently created sequential child, just the node name. ZK
   * really should do this on the server side to save the O(n) traffic but until
   * it does, this is what you get.
   *
   * Returns null if the node does not exist or it has no children.
   */
  public String getLastSequentialChild(final String path, final String prefix,
      final boolean watch) throws IOException, KeeperException,
      InterruptedException {
    Preconditions.checkArgument(zk != null);
    final FixedRetryPolicy policy = new FixedRetryPolicy(3);

    ZKRetryable<String> retry = new ZKRetryable<String>() {
      public boolean doTry() throws Exception {
        result = null;
        List<String> children = null;
        try {
          children = zk.getChildren(path, watch);
        } catch (KeeperException k) {
          defaultHandleException(k);
          return false;
        }
        if (children.size() == 0) {
          return true;
        }

        List<Long> suffixes = new ArrayList<Long>();
        for (String s : children) {
          try {
            suffixes.add(extractSuffix(prefix, s));
          } catch (NumberFormatException n) {
            // Don't throw the exception because there might be
            // some nodes that weren't created sequentially - just ignore.
            LOG.warn("Couldn't parse " + s, n);
          }
        }
        if (suffixes.size() == 0) {
          return true;
        }
        // Sorting is overkill, but convenient.
        Collections.sort(suffixes, Collections.reverseOrder());
        result = String.format(prefix + "%010d", suffixes.get(0));
        return true;
      }
    };

    ZKRetryHarness harness = new ZKRetryHarness(retry, policy);
    if (harness.attempt()) {
      return retry.getResult();
    }
    return null;
  }

  // Implementations of common ZK APIs
  public List<String> getChildren(final String path, final boolean watch)
      throws IOException, KeeperException, InterruptedException {
    Preconditions.checkArgument(zk != null);
    final FixedRetryPolicy policy = new FixedRetryPolicy(3);
    ZKRetryable<List<String>> retry = new ZKRetryable<List<String>>() {
      public boolean doTry() throws Exception {
        try {
          result = zk.getChildren(path, watch);
        } catch (KeeperException k) {
          defaultHandleException(k);
        }
        return true;
      }
    };

    ZKRetryHarness harness = new ZKRetryHarness(retry, policy);
    if (harness.attempt()) {
      return retry.getResult();
    }
    return Collections.emptyList();
  }

  public byte[] getData(final String path, final boolean watch, final Stat stat)
      throws IOException, KeeperException, InterruptedException {
    Preconditions.checkArgument(zk != null);
    final FixedRetryPolicy policy = new FixedRetryPolicy(3);
    ZKRetryable<byte[]> retry = new ZKRetryable<byte[]>() {
      public boolean doTry() throws Exception {
        try {
          result = zk.getData(path, watch, stat);
        } catch (KeeperException k) {
          defaultHandleException(k);
        }
        return true;
      }
    };
    ZKRetryHarness harness = new ZKRetryHarness(retry, policy);
    if (harness.attempt()) {
      return retry.getResult();
    }
    return null;
  }

  public Stat setData(final String path, final byte[] data, final int version)
      throws IOException, KeeperException, InterruptedException {
    Preconditions.checkArgument(zk != null);
    final FixedRetryPolicy policy = new FixedRetryPolicy(3);
    ZKRetryable<Stat> retry = new ZKRetryable<Stat>() {
      public boolean doTry() throws Exception {
        try {
          result = zk.setData(path, data, version);
        } catch (KeeperException k) {
          defaultHandleException(k);
        }
        return true;
      }
    };
    ZKRetryHarness harness = new ZKRetryHarness(retry, policy);
    if (harness.attempt()) {
      return retry.getResult();
    }
    return null;
  }

  public String create(final String path, final byte[] data,
      final List<ACL> acls, final CreateMode mode) throws IOException,
      KeeperException, InterruptedException {
    Preconditions.checkArgument(zk != null);
    final FixedRetryPolicy policy = new FixedRetryPolicy(3);
    ZKRetryable<String> retry = new ZKRetryable<String>() {
      public boolean doTry() throws IOException, KeeperException,
          InterruptedException {
        try {
          result = zk.create(path, data, acls, mode);
        } catch (KeeperException k) {
          defaultHandleException(k);
          return false;
        }
        return true;
      }
    };
    ZKRetryHarness harness = new ZKRetryHarness(retry, policy);

    if (harness.attempt()) {
      return retry.getResult();
    }
    throw new IOException("Failed to execute create");
  }

  public void delete(final String path, final int version) throws IOException,
      KeeperException, InterruptedException {
    Preconditions.checkArgument(zk != null);
    final FixedRetryPolicy policy = new FixedRetryPolicy(3);
    ZKRetryable<Void> retry = new ZKRetryable<Void>() {
      public boolean doTry() throws IOException, KeeperException,
          InterruptedException {
        try {
          zk.delete(path, version);
        } catch (KeeperException k) {
          defaultHandleException(k);
        }
        return true;
      }
    };
    ZKRetryHarness harness = new ZKRetryHarness(retry, policy);
    harness.attempt();
  }

  public Stat exists(final String path, final boolean watch)
      throws IOException, KeeperException, InterruptedException {
    Preconditions.checkArgument(zk != null);
    final FixedRetryPolicy policy = new FixedRetryPolicy(3);
    ZKRetryable<Stat> retry = new ZKRetryable<Stat>() {
      public boolean doTry() throws IOException, KeeperException,
          InterruptedException {
        try {
          result = zk.exists(path, watch);
        } catch (KeeperException k) {
          defaultHandleException(k);
        }
        return true;
      }
    };
    ZKRetryHarness harness = new ZKRetryHarness(retry, policy);

    if (harness.attempt()) {
      return retry.getResult();
    }
    throw new IOException("Failed to execute exists call");
  }

  @Override
  public void process(WatchedEvent event) {
    LOG.info("Got watched event " + event);
  }
}
TOP

Related Classes of com.cloudera.flume.master.ZKClient$ZKRetryHarness

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.