
com.twitter.zk.ZNode.scala Maven / Gradle / Ivy
package com.twitter.zk
import scala.collection.JavaConverters._
import scala.collection.{Seq, Set}
import org.apache.zookeeper.common.PathUtils
import org.apache.zookeeper.data.{ACL, Stat}
import org.apache.zookeeper.{CreateMode, KeeperException, WatchedEvent}
import com.twitter.concurrent.{Broker, Offer}
import com.twitter.util.{Future, Return, Throw, Try}
/**
* A handle to a ZNode attached to a ZkClient
*/
trait ZNode {
/** Absolute path of ZNode */
val path: String
protected[zk] val zkClient: ZkClient
protected[this] lazy val log = zkClient.log
override def hashCode = path.hashCode
override def toString = "ZNode(%s)".format(path)
/** ZNodes are equal if they share a path. */
override def equals(other: Any) = other match {
case z @ ZNode(_) => (z.hashCode == hashCode)
case _ => false
}
/*
* Helpers
*/
/** Return the ZkClient associated with this node. */
def client = zkClient
/** Get a child node. */
def apply(child: String): ZNode = ZNode(zkClient, childPath(child))
/** Build a ZNode with its metadata. */
def apply(stat: Stat): ZNode.Exists = ZNode.Exists(this, stat)
/** Build a ZNode with its metadata and children. */
def apply(stat: Stat, children: Seq[String]): ZNode.Children = ZNode.Children(this, stat, children)
/** Build a ZNode with its metadata and data. */
def apply(stat: Stat, bytes: Array[Byte]): ZNode.Data = ZNode.Data(this, stat, bytes)
/** The 'basename' of the ZNode path. */
lazy val name: String = path.lastIndexOf('/') match {
case i if (i == -1 || i == path.length - 1) => ""
case i => path.substring(i + 1)
}
/** The parent node. The root node is its own parent. */
lazy val parent: ZNode = ZNode(zkClient, parentPath)
lazy val parentPath: String = path.lastIndexOf('/') match {
case i if (i <= 0) => "/"
case i => path.substring(0, i)
}
/** The absolute path of a child */
def childPath(child: String): String = path match {
case path if (!path.endsWith("/")) => path + "/" + child
case path => path + child
}
/** Create a copy of this ZNode with an alternate ZkClient. */
def withZkClient(zk: ZkClient): ZNode = ZNode(zk, path)
/*
* Remote node operations
*/
/**
* Create this ZNode; or if a child name is specified create that child.
*/
def create(
data: Array[Byte] = Array.empty[Byte],
acls: Seq[ACL] = zkClient.acl,
mode: CreateMode = zkClient.mode,
child: Option[String] = None): Future[ZNode] = {
val creatingPath = child map { "%s/%s".format(path, _) } getOrElse path
zkClient.retrying { zk =>
val result = new StringCallbackPromise
zk.create(creatingPath, data, acls.asJava, mode, result, null)
result map { newPath => zkClient(newPath) }
}
}
/** Returns a Future that is satisfied with this ZNode */
def delete(version: Int = 0): Future[ZNode] = zkClient.retrying { zk =>
val result = new UnitCallbackPromise
zk.delete(path, version, result, null)
result map { _ => this }
}
/** Returns a Future that is satisfied with this ZNode with its metadata and data */
def setData(data: Array[Byte], version: Int): Future[ZNode.Data] = zkClient.retrying { zk =>
val result = new ExistsCallbackPromise(this)
zk.setData(path, data, version, result, null)
result map { _.apply(data) }
}
/** Returns a Future that is satisfied with a reference to this ZNode */
def sync(): Future[ZNode] = zkClient.retrying { zk =>
val result = new UnitCallbackPromise
zk.sync(path, result, null)
result map { _ => this }
}
/** Provides access to this node's children. */
val getChildren: ZOp[ZNode.Children] = new ZOp[ZNode.Children] {
import LiftableFuture._
/** Get this ZNode with its metadata and children */
def apply(): Future[ZNode.Children] = zkClient.retrying { zk =>
val result = new ChildrenCallbackPromise(ZNode.this)
zk.getChildren(path, false, result, null)
result
}
/**
* Get a ZNode with its metadata and children; and install a watch for changes.
*
* The returned ZNode.Watch encapsulates the return value from a ZNode operation and the
* watch that will fire when a ZNode operation completes. If the ZNode does not exist, the
* result will be a Throw containing a KeeperException.NoNodeExists, though the watch will
* fire when an event occurs. If any other errors occur when fetching the ZNode, the returned
* Future will error without returning a Watch.
*/
def watch() = zkClient.retrying { zk =>
val result = new ChildrenCallbackPromise(ZNode.this)
val update = new EventPromise
zk.getChildren(path, update, result, null)
result.liftNoNode map { ZNode.Watch(_, update) }
}
}
/** Provides access to this node's data. */
val getData: ZOp[ZNode.Data] = new ZOp[ZNode.Data] {
import LiftableFuture._
/** Get this node's data */
def apply(): Future[ZNode.Data] = zkClient.retrying { zk =>
val result = new DataCallbackPromise(ZNode.this)
zk.getData(path, false, result, null)
result
}
/**
* Get this node's metadata and data; and install a watch for changes.
*
* The returned ZNode.Watch encapsulates the return value from a ZNode operation and the
* watch that will fire when a ZNode operation completes. If the ZNode does not exist, the
* result will be a Throw containing a KeeperException.NoNodeExists, though the watch will
* fire when an event occurs. If any other errors occur when fetching the ZNode, the returned
* Future will error without returning a Watch.
*/
def watch() = zkClient.retrying { zk =>
val result = new DataCallbackPromise(ZNode.this)
val update = new EventPromise
zk.getData(path, update, result, null)
result.liftNoNode map { ZNode.Watch(_, update) }
}
}
/** Provides access to this node's metadata. */
val exists: ZOp[ZNode.Exists] = new ZOp[ZNode.Exists] {
import LiftableFuture._
/** Get this node's metadata. */
def apply() = zkClient.retrying { zk =>
val result = new ExistsCallbackPromise(ZNode.this)
zk.exists(path, false, result, null)
result
}
/** Get this node's metadata and watch for updates */
def watch() = zkClient.retrying { zk =>
val result = new ExistsCallbackPromise(ZNode.this)
val update = new EventPromise
zk.exists(path, update, result, null)
result.liftNoNode.map { ZNode.Watch(_, update) }
}
}
/**
* Continuously watch all nodes in this subtree for child updates.
*
* A ZNode.TreeUpdate is offered for each node in the tree.
*
* If this node is deleted and it had children, an offer is sent indicating that this
* node no longer has children. A watch is maintained on deleted nodes so that if the
* parent node is not monitored, the monitor continues to work when the node is restored.
*
* If an authorization failure or session expiration is encountered, the monitor will be lost
* silently. To detect these situations, receive events from ZkClient.monitorSession().
*/
def monitorTree(): Offer[ZNode.TreeUpdate] = {
val broker = new Broker[ZNode.TreeUpdate]
/** Pipe events from a subtree's monitor to this broker. */
def pipeSubTreeUpdates(next: Offer[ZNode.TreeUpdate]) {
next.sync().flatMap(broker ! _).onSuccess { _ => pipeSubTreeUpdates(next) }
}
/** Monitor a watch on this node. */
def monitorWatch(watch: Future[ZNode.Watch[ZNode.Children]], knownChildren: Set[ZNode]) {
log.debug("monitoring %s with %d known children", path, knownChildren.size)
watch onFailure { e =>
// An error occurred and there's not really anything we can do about it.
log.error(e, "%s: watch could not be established".format(path))
} onSuccess {
// When a node is fetched with a watch, send a ZNode.TreeUpdate on the broker, and start
// monitoring
case ZNode.Watch(Return(zparent), eventUpdate) => {
val children = zparent.children.toSet
val treeUpdate = ZNode.TreeUpdate(zparent,
added = children -- knownChildren,
removed = knownChildren -- children)
log.debug("updating %s with %d children", path, treeUpdate.added.size)
broker send(treeUpdate) sync() onSuccess { _ =>
log.debug("updated %s with %d children", path, treeUpdate.added.size)
treeUpdate.added foreach { z =>
pipeSubTreeUpdates(z.monitorTree())
}
eventUpdate onSuccess { event =>
log.debug("event received on %s: %s", path, event)
} onSuccess {
case MonitorableEvent() => monitorWatch(zparent.getChildren.watch(), children)
case event => log.debug("Unmonitorable event: %s: %s", path, event)
}
}
}
case ZNode.Watch(Throw(ZNode.Error(_path)), eventUpdate) => {
// Tell the broker about the children we lost; otherwise, if there were no children,
// this deletion should be reflected in a watch on the parent node, if one exists.
if (knownChildren.size > 0) {
broker send(ZNode.TreeUpdate(this, removed = knownChildren)) sync()
} else {
Future.Done
} onSuccess { _ =>
eventUpdate onSuccess {
case MonitorableEvent() => monitorWatch(parent.getChildren.watch(), Set.empty[ZNode])
case event => log.debug("Unmonitorable event: %s: %s", path, event)
}
}
}
}
}
// Initially, we don't know about any children for the node.
monitorWatch(getChildren.watch(), Set.empty[ZNode])
broker.recv
}
/** AuthFailed and Expired are unmonitorable. Everything else can be resumed. */
protected[this] object MonitorableEvent {
def unapply(event: WatchedEvent) = event match {
case StateEvent.AuthFailed() => false
case StateEvent.Expired() => false
case _ => true
}
}
}
/**
* ZNode utilities and return types.
*/
object ZNode {
/** Build a ZNode */
def apply(zk: ZkClient, _path: String) = new ZNode {
PathUtils.validatePath(_path)
protected[zk] val zkClient = zk
val path = _path
}
/** matcher */
def unapply(znode: ZNode) = Some(znode.path)
/** A matcher for KeeperExceptions that have a non-null path. */
object Error {
def unapply(ke: KeeperException) = Option(ke.getPath)
}
/** A ZNode with its Stat metadata. */
trait Exists extends ZNode {
val stat: Stat
override def equals(other: Any) = other match {
case Exists(p, s) => (p == path && s == stat)
case o => super.equals(o)
}
def apply(children: Seq[String]): ZNode.Children = apply(stat, children)
def apply(bytes: Array[Byte]): ZNode.Data = apply(stat, bytes)
}
object Exists {
def apply(znode: ZNode, _stat: Stat) = new Exists {
val path = znode.path
protected[zk] val zkClient = znode.zkClient
val stat = _stat
}
def apply(znode: Exists): Exists = apply(znode, znode.stat)
def unapply(znode: Exists) = Some((znode.path, znode.stat))
}
/** A ZNode with its Stat metadata and children znodes. */
trait Children extends Exists {
val stat: Stat
val children: Seq[ZNode]
override def equals(other: Any) = other match {
case Children(p, s, c) => (p == path && s == stat && c == children)
case o => super.equals(o)
}
}
object Children {
def apply(znode: Exists, _children: Seq[ZNode]): Children = new Children {
val path = znode.path
protected[zk] val zkClient = znode.zkClient
val stat = znode.stat
val children = _children
}
def apply(znode: ZNode, stat: Stat, children: Seq[String]): Children = {
apply(Exists(znode, stat), children.map(znode.apply))
}
def unapply(z: Children) = Some((z.path, z.stat, z.children))
}
/** A ZNode with its Stat metadata and data. */
trait Data extends Exists {
val stat: Stat
val bytes: Array[Byte]
override def equals(other: Any) = other match {
case Data(p, s, b) => (p == path && s == stat && b == bytes)
case o => super.equals(o)
}
}
object Data {
def apply(znode: ZNode, _stat: Stat, _bytes: Array[Byte]) = new Data {
val path = znode.path
protected[zk] val zkClient = znode.zkClient
val stat = _stat
val bytes = _bytes
}
def apply(znode: Exists, bytes: Array[Byte]): Data = apply(znode, znode.stat, bytes)
def unapply(znode: Data) = Some((znode.path, znode.stat, znode.bytes))
}
case class Watch[T <: Exists](result: Try[T], update: Future[WatchedEvent]) {
/** Map this Watch to one of another type. */
def map[V <: Exists](toV: T => V): Watch[V] = new Watch(result.map(toV), update)
}
/** Describes an update to a node's children. */
case class TreeUpdate(
parent: ZNode,
added: Set[ZNode] = Set.empty[ZNode],
removed: Set[ZNode] = Set.empty[ZNode])
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy