├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── core └── src │ ├── main │ └── scala │ │ └── com │ │ └── twitter │ │ └── finagle │ │ └── exp │ │ └── zookeeper │ │ ├── Packet.scala │ │ ├── Request.scala │ │ ├── Response.scala │ │ ├── Transaction.scala │ │ ├── ZooKeeper.scala │ │ ├── ZookeeperDefs.scala │ │ ├── ZookeeperException.scala │ │ ├── client │ │ ├── Params.scala │ │ ├── PreProcessService.scala │ │ ├── RepDispatcher.scala │ │ ├── ZkDispatcher.scala │ │ ├── ZkRichClient.scala │ │ └── managers │ │ │ ├── AutoLinkManager.scala │ │ │ ├── ClientManager.scala │ │ │ └── ReadOnlyManager.scala │ │ ├── connection │ │ ├── Connection.scala │ │ ├── ConnectionManager.scala │ │ └── HostProvider.scala │ │ ├── data │ │ ├── ACL.scala │ │ ├── Data.scala │ │ ├── Id.scala │ │ └── Stat.scala │ │ ├── session │ │ ├── Session.scala │ │ └── SessionManager.scala │ │ ├── transport │ │ ├── Bufs.scala │ │ ├── Netty3.scala │ │ └── ZkTransport.scala │ │ ├── utils │ │ └── PathUtils.scala │ │ └── watcher │ │ ├── Watch.scala │ │ └── WatcherManager.scala │ └── test │ └── scala │ └── com │ └── twitter │ └── finagle │ └── exp │ └── zookeeper │ └── unit │ ├── ACLTest.scala │ ├── DispatchingTest.scala │ ├── HostProviderTest.scala │ ├── PathTest.scala │ ├── PreProcessServiceTest.scala │ ├── ReqPacketEncodingTest.scala │ ├── ResponseDecodingTest.scala │ ├── WatcherManagerTest.scala │ ├── session │ ├── SessionManagerTest.scala │ └── SessionTest.scala │ └── transport │ ├── BufTransportTest.scala │ └── ZkTransportTest.scala ├── example └── src │ └── main │ └── scala │ └── com │ └── twitter │ └── finagle │ └── exp │ └── zookeeper │ └── example │ └── Locks.scala ├── integration └── src │ ├── main │ └── resources │ │ ├── scripts │ │ ├── runQuorumMode.sh │ │ ├── runStandaloneMode.sh │ │ ├── stopAndCleanQuorumMode.sh │ │ └── stopAndCleanStandaloneMode.sh │ │ └── testConfig.csv │ └── test │ └── scala │ └── com │ └── twitter │ └── finagle │ └── exp │ └── zookeeper │ └── integration │ ├── quorum │ ├── QuorumIntegrationConfig.scala │ ├── v3_4 │ │ ├── command │ │ │ ├── CRUDTest.scala │ │ │ └── WatcherTest.scala │ │ └── extra │ │ │ └── EnsembleTest.scala │ └── v3_5 │ │ └── command │ │ ├── GetConfigTest.scala │ │ └── ReconfigTest.scala │ └── standalone │ ├── StandaloneIntegrationConfig.scala │ ├── v3_4 │ ├── command │ │ ├── AclTest.scala │ │ ├── AuthTest.scala │ │ ├── CRUDTest.scala │ │ ├── ChrootTest.scala │ │ ├── RichClientTest.scala │ │ ├── TransactionTest.scala │ │ └── WatcherTest.scala │ └── extra │ │ ├── ConnectionManagerTest.scala │ │ ├── ConnectionTest.scala │ │ ├── HostProviderTest.scala │ │ ├── LocalSessionUpdateTest.scala │ │ ├── ReconnectionTest.scala │ │ └── StressTest.scala │ └── v3_5 │ └── command │ ├── CheckWatcherTest.scala │ ├── CheckWatchesTest.scala │ ├── Create2Test.scala │ ├── GetConfigTest.scala │ ├── RemoveAllWatchesTest.scala │ └── RemoveWatchesTest.scala └── project ├── Build.scala ├── IntegrationTest.scala └── build.properties /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | dist/ 3 | project/boot/ 4 | project/plugins/project/ 5 | project/plugins/src_managed/ 6 | *.log 7 | *.tmproj 8 | lib_managed/ 9 | *.swp 10 | *.iml 11 | .idea/ 12 | .idea/* 13 | .DS_Store 14 | .ensime 15 | .ivyjars 16 | sbt 17 | out/ 18 | sbt-launch.jar -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | scala: 3 | - 2.10.5 4 | - 2.11.6 5 | 6 | jdk: 7 | - oraclejdk7 8 | - oraclejdk8 9 | - openjdk7 10 | 11 | script: 12 | - sbt ++$TRAVIS_SCALA_VERSION core/test 13 | - sbt ++$TRAVIS_SCALA_VERSION runTests 14 | 15 | before_install: 16 | - sudo chmod +x integration/src/main/resources/scripts/runQuorumMode.sh 17 | - sudo chmod +x integration/src/main/resources/scripts/runStandaloneMode.sh 18 | - sudo chmod +x integration/src/main/resources/scripts/stopAndCleanQuorumMode.sh 19 | - sudo chmod +x integration/src/main/resources/scripts/stopAndCleanStandaloneMode.sh 20 | 21 | notifications: 22 | email: false 23 | -------------------------------------------------------------------------------- /core/src/main/scala/com/twitter/finagle/exp/zookeeper/Packet.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper 2 | 3 | import com.twitter.io.Buf 4 | 5 | /** 6 | * A ReqPacket is a simple abstraction designed to represent what a request 7 | * can be. A ReqPacket can be composed of a Header and/or a Request, or None. 8 | * Examples: 9 | * - connect : ReqPacket(None, Some(Request)) 10 | * - closeSession : ReqPacket(Some(RequestHeader), None) 11 | * - create : ReqPacket(Some(RequestHeader), Some(Request)) 12 | * - configureDispatcher : ReqPacket(None, Some(Request)) 13 | * 14 | * @param header optionally Some(RequestHeader) or None 15 | * @param request optionally Some(Request) or None 16 | */ 17 | private[finagle] 18 | case class ReqPacket(header: Option[RequestHeader], request: Option[Request]) { 19 | def buf: Buf = if (header.isDefined) { 20 | request match { 21 | case Some(req) => header.get.buf.concat(req.buf) 22 | case None => header.get.buf 23 | } 24 | } else { 25 | request match { 26 | case Some(req: ConnectRequest) => req.buf 27 | case _ => throw new IllegalArgumentException( 28 | "Packet not allowed : no header + request") 29 | } 30 | } 31 | } 32 | 33 | /** 34 | * Used to represent a response, composed by an optional response's error code 35 | * and an Option[Response]. 36 | * 37 | * @param err request's error code 38 | * @param response an optional Response 39 | */ 40 | private[finagle] 41 | case class RepPacket(err: Option[Int], response: Option[Response]) -------------------------------------------------------------------------------- /core/src/main/scala/com/twitter/finagle/exp/zookeeper/Response.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper 2 | 3 | import com.twitter.finagle.exp.zookeeper.data.{ACL, Stat} 4 | import com.twitter.finagle.exp.zookeeper.transport._ 5 | import com.twitter.finagle.exp.zookeeper.watcher.Watcher 6 | import com.twitter.io.Buf 7 | import com.twitter.util._ 8 | import com.twitter.util.TimeConversions._ 9 | 10 | /** 11 | * Mother of all responses, used to describe what a response can do. 12 | */ 13 | sealed trait GlobalRep 14 | /** 15 | * Unique response, used to answer to a Request 16 | */ 17 | sealed trait Response extends GlobalRep 18 | /** 19 | * OpResult is used to compose a Transaction response. 20 | * A Transaction response can be composed by OpResult only. 21 | */ 22 | sealed trait OpResult extends GlobalRep 23 | /** 24 | * Special case of Response, a Response header. 25 | */ 26 | private[finagle] trait RepHeader extends GlobalRep 27 | /** 28 | * Describes what a response decoder must do 29 | * 30 | * @tparam T Response type 31 | */ 32 | sealed trait GlobalRepDecoder[T <: GlobalRep] { 33 | def unapply(buffer: Buf): Option[(T, Buf)] 34 | def apply(buffer: Buf): Try[(T, Buf)] = unapply(buffer) match { 35 | case Some((rep, rem)) => Return((rep, rem)) 36 | case None => Throw(ZkDecodingException("Error while decoding")) 37 | } 38 | } 39 | 40 | case class ConnectResponse( 41 | protocolVersion: Int, 42 | timeOut: Duration, 43 | sessionId: Long, 44 | passwd: Array[Byte], 45 | isRO: Boolean 46 | ) extends Response 47 | 48 | case class CreateResponse(path: String) extends Response with OpResult 49 | 50 | case class Create2Response( 51 | path: String, 52 | stat: Stat) extends Response with OpResult 53 | 54 | case class ExistsResponse( 55 | stat: Option[Stat], 56 | watcher: Option[Watcher] 57 | ) extends Response 58 | 59 | case class ErrorResponse(exception: ZookeeperException) extends OpResult 60 | 61 | case class EmptyResponse() extends Response with OpResult 62 | 63 | case class GetACLResponse(acl: Seq[ACL], stat: Stat) extends Response 64 | 65 | case class GetChildrenResponse( 66 | children: Seq[String], 67 | watcher: Option[Watcher] 68 | ) extends Response 69 | 70 | case class GetChildren2Response( 71 | children: Seq[String], 72 | stat: Stat, 73 | watcher: Option[Watcher] 74 | ) extends Response 75 | 76 | case class GetDataResponse( 77 | data: Array[Byte], 78 | stat: Stat, 79 | watcher: Option[Watcher] 80 | ) extends Response 81 | 82 | case class SetACLResponse(stat: Stat) extends Response 83 | 84 | case class SetDataResponse(stat: Stat) extends Response with OpResult 85 | 86 | case class SyncResponse(path: String) extends Response 87 | 88 | case class ReplyHeader(xid: Int, zxid: Long, err: Int) extends RepHeader 89 | 90 | case class TransactionResponse(responseList: Seq[OpResult]) extends Response 91 | 92 | case class WatchEvent(typ: Int, state: Int, path: String) extends Response 93 | 94 | 95 | /** 96 | * Decoders 97 | */ 98 | private[finagle] 99 | object ConnectResponse extends GlobalRepDecoder[ConnectResponse] { 100 | def unapply(buf: Buf): Option[(ConnectResponse, Buf)] = buf match { 101 | case Buf.U32BE(protocolVersion, 102 | Buf.U32BE(timeOut, 103 | Buf.U64BE(sessionId, 104 | BufArray(passwd, 105 | BufBool(isRO, 106 | rem 107 | ))))) => 108 | Some( 109 | ConnectResponse( 110 | protocolVersion, 111 | timeOut.milliseconds, 112 | sessionId, 113 | passwd, 114 | Option(isRO).getOrElse(false)), 115 | rem) 116 | case _ => None 117 | } 118 | } 119 | 120 | private[finagle] 121 | object CreateResponse extends GlobalRepDecoder[CreateResponse] { 122 | def unapply(buf: Buf): Option[(CreateResponse, Buf)] = buf match { 123 | case BufString(path, rem) => Some(CreateResponse(path), rem) 124 | case _ => None 125 | } 126 | } 127 | 128 | private[finagle] 129 | object Create2Response extends GlobalRepDecoder[Create2Response] { 130 | def unapply(buf: Buf): Option[(Create2Response, Buf)] = buf match { 131 | case BufString(path, Stat(stat, rem)) => 132 | Some(Create2Response(path, stat), rem) 133 | case _ => None 134 | } 135 | } 136 | 137 | private[finagle] 138 | object ErrorResponse extends GlobalRepDecoder[ErrorResponse] { 139 | def unapply(buf: Buf): Option[(ErrorResponse, Buf)] = buf match { 140 | case Buf.U32BE(err, rem) => 141 | Some(ErrorResponse(ZookeeperException.create( 142 | "Exception during the transaction:", err)), rem) 143 | case _ => None 144 | } 145 | } 146 | 147 | private[finagle] 148 | object ExistsResponse extends GlobalRepDecoder[ExistsResponse] { 149 | def unapply(buf: Buf): Option[(ExistsResponse, Buf)] = buf match { 150 | case Stat(stat, rem) => Some(ExistsResponse(Some(stat), None), rem) 151 | case _ => None 152 | } 153 | } 154 | 155 | private[finagle] 156 | object GetACLResponse extends GlobalRepDecoder[GetACLResponse] { 157 | def unapply(buf: Buf): Option[(GetACLResponse, Buf)] = buf match { 158 | case BufSeqACL(acl, Stat(stat, _)) => Some(GetACLResponse(acl, stat), buf) 159 | case _ => None 160 | } 161 | } 162 | 163 | private[finagle] 164 | object GetChildrenResponse extends GlobalRepDecoder[GetChildrenResponse] { 165 | def unapply(buf: Buf): Option[(GetChildrenResponse, Buf)] = buf match { 166 | case BufSeqString(children, _) => 167 | Some(GetChildrenResponse(children, None), buf) 168 | case _ => None 169 | } 170 | } 171 | 172 | private[finagle] 173 | object GetChildren2Response extends GlobalRepDecoder[GetChildren2Response] { 174 | def unapply(buf: Buf): Option[(GetChildren2Response, Buf)] = buf match { 175 | case BufSeqString(children, Stat(stat, _)) => 176 | Some(GetChildren2Response(children, stat, None), buf) 177 | case _ => None 178 | } 179 | } 180 | 181 | private[finagle] 182 | object GetDataResponse extends GlobalRepDecoder[GetDataResponse] { 183 | def unapply(buf: Buf): Option[(GetDataResponse, Buf)] = buf match { 184 | case BufArray(data, Stat(stat, rem)) => 185 | Some(GetDataResponse(data, stat, None), rem) 186 | case _ => None 187 | } 188 | } 189 | 190 | private[finagle] 191 | object ReplyHeader extends GlobalRepDecoder[ReplyHeader] { 192 | def unapply(buf: Buf): Option[(ReplyHeader, Buf)] = buf match { 193 | case Buf.U32BE(xid, Buf.U64BE(zxid, Buf.U32BE(err, rem))) => 194 | Some(ReplyHeader(xid, zxid, err), rem) 195 | case _ => None 196 | } 197 | } 198 | 199 | private[finagle] 200 | object SetACLResponse extends GlobalRepDecoder[SetACLResponse] { 201 | def unapply(buf: Buf): Option[(SetACLResponse, Buf)] = buf match { 202 | case Stat(stat, rem) => Some(SetACLResponse(stat), rem) 203 | case _ => None 204 | } 205 | } 206 | 207 | private[finagle] 208 | object SetDataResponse extends GlobalRepDecoder[SetDataResponse] { 209 | def unapply(buf: Buf): Option[(SetDataResponse, Buf)] = buf match { 210 | case Stat(stat, rem) => Some(SetDataResponse(stat), rem) 211 | case _ => None 212 | } 213 | } 214 | 215 | private[finagle] 216 | object SyncResponse extends GlobalRepDecoder[SyncResponse] { 217 | def unapply(buf: Buf): Option[(SyncResponse, Buf)] = buf match { 218 | case BufString(path, rem) => Some(SyncResponse(path), rem) 219 | case _ => None 220 | } 221 | } 222 | 223 | private[finagle] 224 | object TransactionResponse extends GlobalRepDecoder[TransactionResponse] { 225 | def unapply(buf: Buf): Option[(TransactionResponse, Buf)] = { 226 | Transaction.decode(Seq.empty[OpResult], buf) match { 227 | case (opList: Seq[OpResult], buf: Buf) => 228 | Some((TransactionResponse(opList), buf)) 229 | case _ => None 230 | } 231 | } 232 | } 233 | 234 | private[finagle] 235 | object WatchEvent extends GlobalRepDecoder[WatchEvent] { 236 | def unapply(buf: Buf): Option[(WatchEvent, Buf)] = buf match { 237 | case Buf.U32BE(typ, Buf.U32BE(state, BufString(path, rem))) => 238 | Some(new WatchEvent(typ, state, path), rem) 239 | case _ => None 240 | } 241 | } -------------------------------------------------------------------------------- /core/src/main/scala/com/twitter/finagle/exp/zookeeper/Transaction.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper 2 | 3 | import com.twitter.finagle.exp.zookeeper.data.ACL 4 | import com.twitter.finagle.exp.zookeeper.transport._ 5 | import com.twitter.finagle.exp.zookeeper.utils.PathUtils 6 | import com.twitter.finagle.exp.zookeeper.ZookeeperDefs.OpCode 7 | import com.twitter.io.Buf 8 | import com.twitter.util.{Return, Throw, Try} 9 | import scala.annotation.tailrec 10 | 11 | /** 12 | * A MultiHeader is used to describe an operation 13 | * 14 | * @param typ type of the operation 15 | * @param state state 16 | * @param err error 17 | */ 18 | private[finagle] case class MultiHeader(typ: Int, state: Boolean, err: Int) 19 | extends ReqHeader with RepHeader { 20 | def buf: Buf = Buf.Empty 21 | .concat(Buf.U32BE(typ)) 22 | .concat(BufBool(state)) 23 | .concat(Buf.U32BE(err)) 24 | } 25 | 26 | private[finagle] object MultiHeader extends { 27 | def unapply(buf: Buf): Option[(MultiHeader, Buf)] = buf match { 28 | case Buf.U32BE(typ, BufBool(done, Buf.U32BE(err, rem))) => 29 | Some(MultiHeader(typ, done, err), rem) 30 | case _ => None 31 | } 32 | } 33 | 34 | private[finagle] object Transaction { 35 | /** 36 | * Should decode a multi operation request (ie Transaction) by 37 | * decomposing the buf until we find the "end multiheader" acting 38 | * as a delimiter. 39 | * 40 | * @param results the OpResult sequence to use 41 | * @param buf the Buf to read 42 | * @return (Seq[OpResult], Buf) 43 | */ 44 | @tailrec 45 | def decode( 46 | results: Seq[OpResult], 47 | buf: Buf 48 | ): (Seq[OpResult], Buf) = { 49 | 50 | val MultiHeader(header, opBuf) = buf 51 | if (header.state) (results, opBuf) 52 | else { 53 | val (res, rem) = header.typ match { 54 | case OpCode.CREATE => 55 | val CreateResponse(rep, rem) = opBuf 56 | (CreateResponse(rep.path), rem) 57 | 58 | case OpCode.CREATE2 => 59 | val Create2Response(rep, rem) = opBuf 60 | (Create2Response(rep.path, rep.stat), rem) 61 | 62 | case OpCode.DELETE => (new EmptyResponse, opBuf) 63 | 64 | case OpCode.SET_DATA => 65 | val SetDataResponse(rep, rem) = opBuf 66 | (SetDataResponse(rep.stat), rem) 67 | 68 | case OpCode.CHECK => (new EmptyResponse, opBuf) 69 | 70 | case OpCode.ERROR => 71 | val ErrorResponse(rep, rem) = opBuf 72 | (ErrorResponse(rep.exception), rem) 73 | 74 | case _ => throw new IllegalArgumentException( 75 | "Unsupported type of operation result while decoding Transaction") 76 | } 77 | decode(results :+ res, rem) 78 | } 79 | } 80 | 81 | /** 82 | * Should prepare a Transaction request by checking each operation : 83 | * check ACL, prepend chroot and validate path. 84 | * 85 | * @param opList the operation list to prepare 86 | * @param chroot the client's chroot 87 | * @return configured operation list 88 | */ 89 | def prepareAndCheck( 90 | opList: Seq[OpRequest], 91 | chroot: String 92 | ): Try[Seq[OpRequest]] = { 93 | 94 | Try.collect(opList map { 95 | case op: CreateRequest => 96 | ACL.check(op.aclList) 97 | val finalPath = PathUtils.prependChroot(op.path, chroot) 98 | PathUtils.validatePath(finalPath, op.createMode) 99 | Return(CreateRequest(finalPath, op.data, op.aclList, op.createMode)) 100 | 101 | case op: Create2Request => 102 | ACL.check(op.aclList) 103 | val finalPath = PathUtils.prependChroot(op.path, chroot) 104 | PathUtils.validatePath(finalPath, op.createMode) 105 | Return(Create2Request(finalPath, op.data, op.aclList, op.createMode)) 106 | 107 | case op: DeleteRequest => 108 | val finalPath = PathUtils.prependChroot(op.path, chroot) 109 | PathUtils.validatePath(finalPath) 110 | Return(DeleteRequest(finalPath, op.version)) 111 | 112 | case op: SetDataRequest => 113 | require(op.data.size < 1048576, 114 | "The maximum allowable size of " + 115 | "the data array is 1 MB (1,048,576 bytes)") 116 | val finalPath = PathUtils.prependChroot(op.path, chroot) 117 | PathUtils.validatePath(finalPath) 118 | Return(SetDataRequest(finalPath, op.data, op.version)) 119 | 120 | case op: CheckVersionRequest => 121 | val finalPath = PathUtils.prependChroot(op.path, chroot) 122 | PathUtils.validatePath(finalPath) 123 | Return(CheckVersionRequest(finalPath, op.version)) 124 | 125 | case _ => 126 | Throw(new IllegalArgumentException("Element is not an Op")) 127 | }) 128 | } 129 | 130 | /** 131 | * Should correctly format each operation response path by removing chroot 132 | * from the returned path. 133 | * 134 | * @param opList the operation result to format 135 | * @param chroot the client's chroot 136 | * @return correctly formatted operation list 137 | */ 138 | def formatPath(opList: Seq[OpResult], chroot: String): Seq[OpResult] = 139 | opList map { 140 | case op: Create2Response => 141 | Create2Response(op.path.substring(chroot.length), op.stat) 142 | case op: CreateResponse => 143 | CreateResponse(op.path.substring(chroot.length)) 144 | case op: OpResult => op 145 | } 146 | } -------------------------------------------------------------------------------- /core/src/main/scala/com/twitter/finagle/exp/zookeeper/ZooKeeper.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper 2 | 3 | import com.twitter.finagle.client._ 4 | import com.twitter.finagle.dispatch.PipeliningDispatcher 5 | import com.twitter.finagle.exp.zookeeper.client.Params.{AutoReconnect, ZkConfiguration} 6 | import com.twitter.finagle.exp.zookeeper.client.{Params, ZkClient, ZkDispatcher} 7 | import com.twitter.finagle.exp.zookeeper.transport.{BufTransport, NettyTrans, ZkTransport, ZookeeperTransporter} 8 | import com.twitter.finagle.transport.Transport 9 | import com.twitter.finagle.{Client, Name, Service, ServiceFactory, Stack} 10 | import com.twitter.io.Buf 11 | import com.twitter.util.Duration 12 | import com.twitter.util.TimeConversions._ 13 | import org.jboss.netty.buffer.ChannelBuffer 14 | 15 | /** 16 | * -- ZkDispatcher 17 | * The zookeeper protocol allows the server to send notifications 18 | * with model is not basically supported by Finagle, that's the reason 19 | * why we are using a dedicated dispatcher based on SerialClientDispatcher and 20 | * GenSerialClientDispatcher. 21 | * 22 | * -- ZkTransport 23 | * We need to frame Rep here, that's why we use ZkTransport 24 | * which is responsible framing Buf and converting to Buf. 25 | * It is also reading the request by copying the corresponding buffer in a Buf. 26 | */ 27 | trait ZookeeperRichClient {self: com.twitter.finagle.Client[ReqPacket, RepPacket] => 28 | val params: Stack.Params 29 | def newRichClient(dest: String, label: String): ZkClient = { 30 | val ZkConfiguration( 31 | autoWatch, 32 | canRO, 33 | chRoot, 34 | sessTime 35 | ) = params[ZkConfiguration] 36 | 37 | val AutoReconnect( 38 | autoReco, 39 | autoRw, 40 | prevS, 41 | tba, 42 | tblc, 43 | mcr, 44 | mra 45 | ) = params[AutoReconnect] 46 | 47 | ZkClient( 48 | autowatchReset = autoWatch, 49 | autoRecon = autoReco, 50 | canBeReadOnly = canRO, 51 | chrootPath = chRoot, 52 | hostList = dest, 53 | maxConsecRetries = mcr, 54 | maxReconAttempts = mra, 55 | serviceLabel = Some(label), 56 | sessTimeout = sessTime, 57 | timeBtwnAttempts = tba, 58 | timeBtwnLinkCheck = tblc, 59 | timeBtwnRwSrch = autoRw, 60 | timeBtwnPrevSrch = prevS 61 | ) 62 | } 63 | 64 | def newRichClient(dest: String): ZkClient = { 65 | val ZkConfiguration( 66 | autoWatch, 67 | canRO, 68 | chRoot, 69 | sessTime 70 | ) = params[ZkConfiguration] 71 | 72 | val AutoReconnect( 73 | autoReco, 74 | autoRw, 75 | prevS, 76 | tba, 77 | tblc, 78 | mcr, 79 | mra 80 | ) = params[AutoReconnect] 81 | 82 | ZkClient( 83 | autowatchReset = autoWatch, 84 | autoRecon = autoReco, 85 | canBeReadOnly = canRO, 86 | chrootPath = chRoot, 87 | hostList = dest, 88 | maxConsecRetries = mcr, 89 | maxReconAttempts = mra, 90 | serviceLabel = None, 91 | sessTimeout = sessTime, 92 | timeBtwnAttempts = tba, 93 | timeBtwnLinkCheck = tblc, 94 | timeBtwnRwSrch = autoRw, 95 | timeBtwnPrevSrch = prevS 96 | ) 97 | } 98 | } 99 | 100 | object Zookeeper extends Client[ReqPacket, RepPacket] with ZookeeperRichClient { 101 | 102 | case class Client( 103 | stack: Stack[ServiceFactory[ReqPacket, RepPacket]] = StackClient.newStack, 104 | params: Stack.Params = StackClient.defaultParams + DefaultPool.Param( 105 | low = 0, high = 1, bufferSize = 0, 106 | idleTime = Duration.Top, 107 | maxWaiters = Int.MaxValue) 108 | ) extends StdStackClient[ReqPacket, RepPacket, Client] with ZookeeperRichClient { 109 | protected def copy1( 110 | stack: Stack[ServiceFactory[ReqPacket, RepPacket]] = this.stack, 111 | params: Stack.Params = this.params 112 | ): Client = copy(stack, params) 113 | 114 | type In = ChannelBuffer 115 | type Out = ChannelBuffer 116 | protected def newTransporter() = ZookeeperTransporter(params) 117 | protected def newDispatcher( 118 | transport: Transport[ChannelBuffer, ChannelBuffer] 119 | ): Service[ReqPacket, RepPacket] = 120 | new ZkDispatcher(new ZkTransport(transport)) 121 | 122 | def withAutoReconnect( 123 | autoRwServerSearch: Option[Duration] = Some(1.minute), 124 | preventiveSearch: Option[Duration] = Some(10.minutes), 125 | timeBetweenAttempts: Duration = 30.seconds, 126 | timeBetweenLinkCheck: Option[Duration] = Some(30.seconds), 127 | maxConsecutiveRetries: Int = 10, 128 | maxReconnectAttempts: Int = 5 129 | ) = configured( 130 | Params.AutoReconnect( 131 | true, 132 | autoRwServerSearch, 133 | preventiveSearch, 134 | timeBetweenAttempts, 135 | timeBetweenLinkCheck, 136 | maxConsecutiveRetries, 137 | maxReconnectAttempts 138 | ) 139 | ) 140 | 141 | def withZkConfiguration( 142 | autoWatchReset: Boolean = true, 143 | canReadOnly: Boolean = true, 144 | chroot: String = "", 145 | sessionTimeout: Duration = 3000.milliseconds 146 | ) = configured( 147 | Params.ZkConfiguration( 148 | autoWatchReset, 149 | canReadOnly, 150 | chroot, 151 | sessionTimeout 152 | ) 153 | ) 154 | } 155 | 156 | val client = Client() 157 | val params = client.params 158 | 159 | def newClient(dest: Name, label: String): ServiceFactory[ReqPacket, RepPacket] = 160 | Client(StackClient.newStack, Stack.Params.empty).newClient(dest, label) 161 | 162 | def newService(dest: Name, label: String): Service[ReqPacket, RepPacket] = 163 | Client(StackClient.newStack, Stack.Params.empty).newService(dest, label) 164 | } 165 | 166 | /** 167 | * Simple client is used to send isro request ( not framed request ) 168 | * and also to send connect and close requests to old servers (version < 3.4.0) 169 | * ( read-only mode not supported ) 170 | * This is basically a Buf client, sending Buf and receiving Buf 171 | */ 172 | private[finagle] object SimpleClient extends DefaultClient[Buf, Buf]( 173 | name = "isroClient", 174 | endpointer = Bridge[Buf, Buf, Buf, Buf]( 175 | NettyTrans(_, _) map { new BufTransport(_) }, 176 | newDispatcher = new PipeliningDispatcher(_) 177 | ) 178 | ) 179 | 180 | private[finagle] object BufClient extends Client[Buf, Buf] { 181 | override def newClient(dest: Name, label: String): ServiceFactory[Buf, Buf] = 182 | SimpleClient.newClient(dest, label) 183 | 184 | override def newService(dest: Name, label: String): Service[Buf, Buf] = 185 | SimpleClient.newService(dest, label) 186 | 187 | def newSimpleClient(dest: String): ServiceFactory[Buf, Buf] = 188 | SimpleClient.newClient(dest) 189 | } 190 | -------------------------------------------------------------------------------- /core/src/main/scala/com/twitter/finagle/exp/zookeeper/ZookeeperDefs.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper 2 | 3 | object ZookeeperDefs { 4 | val CONFIG_NODE = "/zookeeper/config" 5 | 6 | /* Defines the creation mode of a znode*/ 7 | object CreateMode { 8 | val PERSISTENT = 0 9 | val EPHEMERAL = 1 10 | val PERSISTENT_SEQUENTIAL = 2 11 | val EPHEMERAL_SEQUENTIAL = 3 12 | } 13 | 14 | /* XID to identify the request type */ 15 | private[finagle] object OpCode { 16 | val NOTIFICATION = 0 17 | val CREATE = 1 18 | val DELETE = 2 19 | val EXISTS = 3 20 | val GET_DATA = 4 21 | val SET_DATA = 5 22 | val GET_ACL = 6 23 | val SET_ACL = 7 24 | val GET_CHILDREN = 8 25 | val SYNC = 9 26 | val PING = 11 27 | val GET_CHILDREN2 = 12 28 | val CHECK = 13 29 | val MULTI = 14 30 | val CREATE2 = 15 31 | val RECONFIG = 16 32 | val CHECK_WATCHES = 17 33 | val REMOVE_WATCHES = 18 34 | val AUTH = 100 35 | val SET_WATCHES = 101 36 | val SASL = 102 37 | val CREATE_SESSION = -10 38 | val CLOSE_SESSION = -11 39 | val ERROR = -1 40 | } 41 | } -------------------------------------------------------------------------------- /core/src/main/scala/com/twitter/finagle/exp/zookeeper/client/Params.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.client 2 | 3 | import com.twitter.finagle.Stack 4 | import com.twitter.util.Duration 5 | import com.twitter.util.TimeConversions._ 6 | 7 | object Params { 8 | case class ZkConfiguration( 9 | autoWatchReset: Boolean, 10 | canReadOnly: Boolean, 11 | chroot: String, 12 | sessionTimeout: Duration 13 | ) 14 | implicit object ZkConfiguration extends Stack.Param[ZkConfiguration] { 15 | def default: ZkConfiguration = 16 | ZkConfiguration( 17 | autoWatchReset = true, 18 | canReadOnly = true, 19 | chroot = "", 20 | sessionTimeout = 3000.milliseconds 21 | ) 22 | } 23 | 24 | case class AutoReconnect( 25 | autoReconnect: Boolean, 26 | autoRwServerSearch: Option[Duration], 27 | preventiveSearch: Option[Duration], 28 | timeBetweenAttempts: Duration, 29 | timeBetweenLinkCheck: Option[Duration], 30 | maxConsecutiveRetries: Int, 31 | maxReconnectAttempts: Int 32 | ) 33 | implicit object AutoReconnect extends Stack.Param[AutoReconnect] { 34 | def default: AutoReconnect = AutoReconnect( 35 | autoReconnect = false, 36 | autoRwServerSearch = None, 37 | preventiveSearch = None, 38 | timeBetweenAttempts = Duration.Bottom, 39 | timeBetweenLinkCheck = None, 40 | maxConsecutiveRetries = 1, 41 | maxReconnectAttempts = 1 42 | ) 43 | } 44 | } -------------------------------------------------------------------------------- /core/src/main/scala/com/twitter/finagle/exp/zookeeper/client/PreProcessService.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.client 2 | 3 | import com.twitter.concurrent.{AsyncSemaphore, Permit} 4 | import com.twitter.finagle.exp.zookeeper.session.Session.States 5 | import com.twitter.finagle.{CancelledRequestException, Service} 6 | import com.twitter.finagle.exp.zookeeper._ 7 | import com.twitter.finagle.exp.zookeeper.client.managers.ClientManager 8 | import com.twitter.finagle.exp.zookeeper.connection.ConnectionManager 9 | import com.twitter.finagle.exp.zookeeper.session.SessionManager 10 | import com.twitter.finagle.util.DefaultTimer 11 | import com.twitter.util._ 12 | import java.util.concurrent.atomic.AtomicBoolean 13 | 14 | /** 15 | * LocalService is used to send ZooKeeper request to the endpoint. 16 | * In client life, connection or session changes can happen, 17 | * during those moments we are not able to send request because 18 | * either the connection is down or the session is not established. 19 | * For these reasons, during connection/reconnection/changeHost 20 | * we can lock the service until the situation is back to normal. 21 | */ 22 | class PreProcessService( 23 | connectionManager: ConnectionManager, 24 | sessionManager: SessionManager, 25 | zkClient: ZkClient with ClientManager 26 | ) extends Service[Request, RepPacket] { 27 | 28 | private[this] val semaphore = new AsyncSemaphore(1) 29 | private[this] var permit: Option[Permit] = None 30 | private[this] val cancelAllRequests: AtomicBoolean = new AtomicBoolean(false) 31 | 32 | /** 33 | * Should send a request to the dispatcher, this request will be checked 34 | * by isROCheck before, to make sure this is not a Write operation and 35 | * that we are currently on read-only mode. Then the connection and 36 | * session are possibly checked with tryCheckLink depending if we 37 | * are already trying to reconnect. Next the request is prepared : 38 | * transformed to a ReqPacket, by adding opCode and xid. It is finally 39 | * sent to the service owned by the current connection object. 40 | * 41 | * @param req a Request 42 | * @return a Future[RepPacket] in response to the request 43 | */ 44 | def apply(req: Request): Future[RepPacket] = { 45 | isROCheck(req) match { 46 | case Return(unit) => 47 | zkClient.tryCheckLink() 48 | if (sessionManager.session.state == States.CLOSED) 49 | throw new ZookeeperException("Not connected to a server!") 50 | 51 | val p = new Promise[RepPacket] 52 | 53 | semaphore.acquire() respond { 54 | case Return(requestPermit) => 55 | if (cancelAllRequests.get()) { 56 | p.updateIfEmpty(Throw( 57 | new CancelledRequestException)) 58 | requestPermit.release() 59 | } 60 | else sendRequest(req, p, requestPermit) 61 | 62 | case Throw(exc) => p.setException(exc) 63 | } 64 | p 65 | 66 | case Throw(exc) => Future.exception(exc) 67 | } 68 | } 69 | 70 | private[this] def sendRequest( 71 | req: Request, 72 | p: Promise[RepPacket], 73 | requestPermit: Permit 74 | ): Future[Unit] = { 75 | implicit val timer = DefaultTimer.twitter 76 | val preparedReq = prepareRequest(req) 77 | connectionManager.connection.get.serve(preparedReq) 78 | .raiseWithin(sessionManager.session.negotiatedTimeout * 2 / 3) 79 | .respond { 80 | case Return(rep) => p.setValue(rep) 81 | 82 | case Throw(exc) => exc match { 83 | case exc1: TimeoutException => 84 | zkClient.stopJob() before { 85 | if (zkClient.autoReconnect) zkClient.reconnectWithSession().unit 86 | else Future.Done 87 | } before { 88 | p.updateIfEmpty(Throw(exc1)) 89 | Future.Done 90 | } 91 | 92 | case _ => p.updateIfEmpty(Throw(exc)) 93 | } 94 | }.unit ensure requestPermit.release() 95 | } 96 | 97 | /** 98 | * Should lock the service until unlockServe() is called, it's done 99 | * simply by acquiring the semaphore permit. 100 | * 101 | * @return Future.Done 102 | */ 103 | private[finagle] def lockService(): Future[Unit] = this.synchronized { 104 | if (permit.isDefined) Future.Done 105 | else semaphore.acquire() flatMap { perm => 106 | permit = Some(perm) 107 | Future.Done 108 | } 109 | } 110 | 111 | /** 112 | * Should unlock the service after lockServe() was called, it's done 113 | * simply by releasing the semaphore permit. 114 | * 115 | */ 116 | private[finagle] def unlockService(): Unit = this.synchronized { 117 | if (permit.isDefined) { 118 | permit.get.release() 119 | permit = None 120 | } 121 | } 122 | 123 | /** 124 | * Should cancel all the pending requests and lock the service. 125 | * After unlocking the service, it remains opened so that each 126 | * requests sent in a non-connected state will receive a 127 | * CancelledRequestException caused by "not connected to a server". 128 | * 129 | * @return Future.Done when the action is completed 130 | */ 131 | private[finagle] def flushService(): Future[Unit] = this.synchronized { 132 | cancelAllRequests.set(true) 133 | unlockService() 134 | lockService() 135 | } ensure cancelAllRequests.set(false) 136 | 137 | /** 138 | * Should prepare a request by adding xid and request's op code 139 | * 140 | * @param req the request to prepare 141 | * @return a ReqPacket 142 | */ 143 | private[this] def prepareRequest(req: Request): ReqPacket = 144 | Request.toReqPacket(req, sessionManager.session.nextXid) 145 | 146 | /** 147 | * Should check if the request is a write operation and throw an 148 | * exception if the server is in Read Only mode, because only read 149 | * operations are allowed during this state. 150 | * 151 | * @param req the request to test 152 | * @return 153 | */ 154 | private[this] def isROCheck(req: Request): Try[Unit] = { 155 | lazy val isro = sessionManager.session.isRO.get() 156 | 157 | def testRO(): Try[Unit] = 158 | if (isro) actOnRO() 159 | else Return.Unit 160 | 161 | def actOnRO(): Try[Unit] = { 162 | Throw(NotReadOnlyException( 163 | "Server is in ReadOnly mode, write requests are not allowed")) 164 | } 165 | 166 | req match { 167 | case req: CreateRequest => testRO() 168 | case req: Create2Request => testRO() 169 | case req: DeleteRequest => testRO() 170 | case req: ReconfigRequest => testRO() 171 | case req: SetACLRequest => testRO() 172 | case req: SetDataRequest => testRO() 173 | case req: SyncRequest => testRO() 174 | case req: TransactionRequest => testRO() 175 | case req: Request => Return.Unit 176 | } 177 | } 178 | } -------------------------------------------------------------------------------- /core/src/main/scala/com/twitter/finagle/exp/zookeeper/client/ZkDispatcher.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.client 2 | 3 | import com.twitter.finagle.exp.zookeeper._ 4 | import com.twitter.finagle.dispatch.GenSerialClientDispatcher 5 | import com.twitter.finagle.transport.Transport 6 | import com.twitter.io.Buf 7 | import com.twitter.util._ 8 | 9 | class ZkDispatcher(trans: Transport[Buf, Buf]) 10 | extends GenSerialClientDispatcher[ReqPacket, RepPacket, Buf, Buf](trans) { 11 | val responseMatcher = new RepDispatcher(trans) 12 | 13 | protected def dispatch(req: ReqPacket, p: Promise[RepPacket]): Future[Unit] = { 14 | responseMatcher.write(req) respond { resp => 15 | p.updateIfEmpty(resp) 16 | } 17 | }.unit 18 | } -------------------------------------------------------------------------------- /core/src/main/scala/com/twitter/finagle/exp/zookeeper/client/managers/AutoLinkManager.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.client.managers 2 | 3 | import com.twitter.finagle.exp.zookeeper.client.ZkClient 4 | import com.twitter.finagle.exp.zookeeper.session.Session.States 5 | import com.twitter.finagle.exp.zookeeper.{SessionMovedException, ZookeeperException} 6 | import com.twitter.finagle.util.DefaultTimer 7 | import com.twitter.util.{Duration, TimerTask, Future} 8 | import java.util.concurrent.atomic.AtomicBoolean 9 | 10 | private[finagle] trait AutoLinkManager {self: ZkClient with ClientManager => 11 | 12 | private[this] val canCheckLink = new AtomicBoolean(false) 13 | implicit val timer = DefaultTimer.twitter 14 | /** 15 | * Start the loop which will check connection and session 16 | * every timeBetweenLinkCheck 17 | * 18 | */ 19 | def startStateLoop(): Unit = { 20 | if (autoReconnect) { 21 | this.synchronized { 22 | canCheckLink.set(true) 23 | if (!CheckLingScheduler.isRunning) { 24 | CheckLingScheduler(timeBetweenLinkCheck.get)(tryCheckLink()) 25 | } 26 | } 27 | } 28 | } 29 | 30 | /** 31 | * Stop the state loop by cancelling its scheduler task 32 | */ 33 | def stopStateLoop() { 34 | canCheckLink.set(false) 35 | CheckLingScheduler.stop() 36 | } 37 | 38 | /** 39 | * Try to check connection and session if we are not currently 40 | * trying to reconnect. 41 | */ 42 | private[finagle] def tryCheckLink(): Unit = 43 | if (canCheckLink.get() && autoReconnect) checkLink() 44 | 45 | /** 46 | * Check session state before checking connection state 47 | * 48 | * @param tries number of reconnect attempts 49 | * @return Future.Done or exception 50 | */ 51 | private[this] def checkLink(tries: Int = 0): Future[Unit] = this.synchronized { 52 | checkSession() rescue { 53 | case exc: SessionMovedException => Future.exception(exc) 54 | 55 | case exc: ZookeeperException => 56 | if (tries < maxReconnectAttempts) 57 | checkLink(tries + 1).delayed(timeBetweenAttempts) 58 | else Future.exception(exc) 59 | 60 | case exc: Throwable => Future.exception(exc) 61 | 62 | } before checkConnection() rescue { 63 | case exc: ZookeeperException => 64 | if (tries < maxReconnectAttempts) 65 | checkLink(tries + 1).delayed(timeBetweenAttempts) 66 | else Future.exception(exc) 67 | 68 | case exc: Throwable => Future.exception(exc) 69 | } 70 | } rescue { case exc => 71 | // we want to make sure everything is stopped and cleaned 72 | // if reconnection fails. 73 | disconnect() rescue { case excp => Future.Done } 74 | Future.exception(exc) 75 | } 76 | 77 | /** 78 | * To check that connection is still valid 79 | * 80 | * @return Future.Done or exception 81 | */ 82 | private[this] def checkConnection(): Future[Unit] = 83 | connectionManager.connection match { 84 | case Some(connect) => 85 | if (!connect.isValid.get()) { 86 | ZkClient.logger.warning( 87 | s"Connection to ${ 88 | connectionManager.currentHost.getOrElse("") 89 | } has failed, reconnecting with session..." 90 | ) 91 | stopJob() before reconnectWithSession().unit 92 | } 93 | else Future.Done 94 | 95 | case None => Future.Done 96 | } 97 | 98 | /** 99 | * This method is called to make sure the connection is still alive. 100 | * If it's not then it can try to reconnect or create a new 101 | * session if the current one has expired. 102 | * It won't connect if the client has never connected 103 | */ 104 | private[this] def checkSession(): Future[Unit] = 105 | sessionManager.session.state match { 106 | case States.CONNECTION_LOSS 107 | | States.NOT_CONNECTED => 108 | ZkClient.logger.warning( 109 | s"Connection loss with ${ 110 | connectionManager.currentHost.getOrElse("Unknown host") 111 | } reconnecting with session...") 112 | stopJob() before reconnectWithSession().unit 113 | 114 | case States.AUTH_FAILED => stopJob() rescue { case exc => Future.Done } 115 | 116 | case States.SESSION_MOVED => 117 | ZkClient.logger.warning("Session with %s has moved, disconnecting." 118 | .format(connectionManager.currentHost)) 119 | stopJob() before Future.exception(SessionMovedException( 120 | "Session has moved to another server")) 121 | 122 | case States.SESSION_EXPIRED => 123 | ZkClient.logger.warning( 124 | s"Session with ${ 125 | connectionManager.currentHost.getOrElse("Unknown host") 126 | } has expired, reconnecting without session...") 127 | stopJob() before reconnectWithoutSession().unit 128 | 129 | case _ => Future.Done 130 | } 131 | 132 | /** 133 | * PreventiveSearchScheduler is used to find a suitable server to reconnect 134 | * to in case of server failure. 135 | */ 136 | private[this] object CheckLingScheduler { 137 | /** 138 | * currentTask - the last scheduled timer's task 139 | */ 140 | private[this] var currentTask: Option[TimerTask] = None 141 | 142 | def apply(period: Duration)(f: => Unit) { 143 | currentTask = Some(DefaultTimer.twitter.schedule(period)(f)) 144 | } 145 | 146 | def isRunning: Boolean = { 147 | currentTask.isDefined 148 | } 149 | 150 | def stop() { 151 | if (currentTask.isDefined) currentTask.get.cancel() 152 | currentTask = None 153 | } 154 | 155 | def updateTimer(period: Duration)(f: => Unit) = { 156 | stop() 157 | apply(period)(f) 158 | } 159 | } 160 | } -------------------------------------------------------------------------------- /core/src/main/scala/com/twitter/finagle/exp/zookeeper/client/managers/ReadOnlyManager.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.client.managers 2 | 3 | import com.twitter.finagle.CancelledRequestException 4 | import com.twitter.finagle.exp.zookeeper.client.ZkClient 5 | import com.twitter.util.{Future, Promise, Return, Throw} 6 | 7 | private[finagle] trait ReadOnlyManager {self: ZkClient with ClientManager => 8 | private[this] var rwSearchOp: Option[Future[Unit]] = None 9 | 10 | /** 11 | * Should search a RW mode server and connect to it. 12 | * 13 | * @return Future.Done 14 | */ 15 | private[this] def findAndConnectRwServer(): Future[Unit] = { 16 | var interrupt = false 17 | var search: Future[String] = new Promise[String]() 18 | val p = new Promise[Unit]() 19 | p.setInterruptHandler { 20 | case exc: CancelledRequestException => 21 | interrupt = true 22 | search.raise(new CancelledRequestException) 23 | p.setDone() 24 | case exc => 25 | p.updateIfEmpty(Throw(exc)) 26 | } 27 | 28 | ZkClient.logger.info( 29 | "Client has started looking for a Read-Write server in background.") 30 | 31 | if (!interrupt) { 32 | search = connectionManager.hostProvider.findRwServer(timeBetweenRwSrch.get) 33 | search transform { 34 | case Return(server) if sessionManager.session.isRO.get() && !interrupt => 35 | if (sessionManager.session.hasFakeSessionId.get) { 36 | ZkClient.logger.info( 37 | "Client has found a Read-Write server" + 38 | s" at $server, now reconnecting to it" + 39 | " without session.") 40 | reconnectWithoutSession(Some(server)).unit 41 | } 42 | else { 43 | ZkClient.logger.info( 44 | "Client has found a Read-Write server" + 45 | s" at $server, now reconnecting to it" + 46 | " with session.") 47 | reconnectWithSession(Some(server)).unit 48 | } 49 | 50 | case _ => Future.Done 51 | } 52 | 53 | p.updateIfEmpty(Return(search)) 54 | } 55 | else p.setDone() 56 | 57 | p 58 | } 59 | 60 | /** 61 | * Should start to search a RW mode server. 62 | * 63 | * @return Future.Done 64 | */ 65 | private[finagle] def startRwSearch(): Future[Unit] = 66 | stopRwSearch() onSuccess { _ => 67 | rwSearchOp = Some(findAndConnectRwServer()) 68 | } 69 | 70 | /** 71 | * Should stop Rw mode server search. 72 | * 73 | * @return Future.Done 74 | */ 75 | private[finagle] def stopRwSearch(): Future[Unit] = 76 | if (rwSearchOp.isDefined) { 77 | rwSearchOp.get.raise(new CancelledRequestException) 78 | rwSearchOp.get 79 | } 80 | else Future.Done 81 | } -------------------------------------------------------------------------------- /core/src/main/scala/com/twitter/finagle/exp/zookeeper/connection/Connection.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.connection 2 | 3 | import com.twitter.finagle.Status 4 | import com.twitter.finagle.exp.zookeeper.{RepPacket, ReqPacket} 5 | import com.twitter.finagle.{Service, ServiceFactory} 6 | import com.twitter.util.{Duration, Future, Time} 7 | import java.util.concurrent.atomic.AtomicBoolean 8 | 9 | /** 10 | * Connection manages a ServiceFactory, in charge of serving requests to server 11 | * 12 | * @param serviceFactory current connection to server 13 | */ 14 | class Connection(serviceFactory: ServiceFactory[ReqPacket, RepPacket]) { 15 | val isValid = new AtomicBoolean(true) 16 | private[this] var service: Future[Service[ReqPacket, RepPacket]] = 17 | serviceFactory.apply() 18 | 19 | /** 20 | * Close current service and ServiceFactory 21 | * 22 | * @return Future.Done 23 | */ 24 | def close(): Future[Unit] = { 25 | service flatMap { svc => 26 | isValid.set(false) 27 | if (svc.isAvailable && serviceFactory.isAvailable) { 28 | svc.close() before serviceFactory.close() 29 | } else if (svc.isAvailable && !serviceFactory.isAvailable) { 30 | svc.close() 31 | } else if (!svc.isAvailable && serviceFactory.isAvailable) { 32 | serviceFactory.close() 33 | } else { 34 | Future.Done 35 | } 36 | } 37 | } 38 | 39 | def close(time: Time): Future[Unit] = { 40 | service flatMap { svc => 41 | isValid.set(false) 42 | if (svc.isAvailable && serviceFactory.isAvailable) { 43 | svc.close(time) before serviceFactory.close(time) 44 | } else if (svc.isAvailable && !serviceFactory.isAvailable) { 45 | svc.close(time) 46 | } else if (!svc.isAvailable && serviceFactory.isAvailable) { 47 | serviceFactory.close(time) 48 | } else { 49 | Future.Done 50 | } 51 | } 52 | } 53 | 54 | def close(duration: Duration): Future[Unit] = { 55 | service flatMap { svc => 56 | isValid.set(false) 57 | if (svc.isAvailable && serviceFactory.isAvailable) { 58 | svc.close(duration) before serviceFactory.close(duration) 59 | } else if (svc.isAvailable && !serviceFactory.isAvailable) { 60 | svc.close(duration) 61 | } else if (!svc.isAvailable && serviceFactory.isAvailable) { 62 | serviceFactory.close(duration) 63 | } else { 64 | Future.Done 65 | } 66 | } 67 | } 68 | 69 | def serviceFactoryStatus: Status = serviceFactory.status 70 | def isServiceFactoryAvailable: Boolean = serviceFactory.isAvailable 71 | def isServiceAvailable: Future[Boolean] = service flatMap 72 | (svc => Future(svc.isAvailable)) 73 | def newService(): Future[Unit] = this.synchronized { 74 | serviceFactory.apply() flatMap { serv => 75 | service = Future(serv) 76 | Future.Done 77 | } 78 | } 79 | def serve(req: ReqPacket): Future[RepPacket] = service flatMap (_(req)) 80 | } 81 | 82 | private[finagle] object Connection { 83 | class NoConnectionAvailable(msg: String) extends RuntimeException(msg) 84 | } 85 | -------------------------------------------------------------------------------- /core/src/main/scala/com/twitter/finagle/exp/zookeeper/connection/ConnectionManager.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.connection 2 | 3 | import com.twitter.finagle.{ServiceFactory, Status} 4 | import com.twitter.finagle.exp.zookeeper._ 5 | import com.twitter.finagle.exp.zookeeper.client.ZkClient 6 | import com.twitter.finagle.exp.zookeeper.connection.HostUtilities.ServerNotAvailable 7 | import com.twitter.util._ 8 | import java.util.concurrent.atomic.AtomicBoolean 9 | 10 | /** 11 | * The connection manager is supposed to handle a connection 12 | * between the client and an endpoint from the host list 13 | */ 14 | class ConnectionManager( 15 | dest: String, 16 | label: Option[String], 17 | canBeRo: Boolean, 18 | timeForPreventive: Option[Duration], 19 | timeForRoMode: Option[Duration] 20 | ) { 21 | 22 | @volatile var connection: Option[Connection] = None 23 | val isInitiated = new AtomicBoolean(false) 24 | private[this] var activeHost: Option[String] = None 25 | private[finagle] val hostProvider = 26 | new HostProvider(dest, canBeRo, timeForPreventive, timeForRoMode) 27 | 28 | type SearchMethod = String => Future[ServiceFactory[ReqPacket, RepPacket]] 29 | 30 | /** 31 | * Return the currently connected host 32 | * 33 | * @return Some(host) or None if not connected 34 | */ 35 | def currentHost: Option[String] = activeHost 36 | 37 | /** 38 | * To close connection manager, the current connexion, stop preventive 39 | * search and stop rw server search. 40 | * 41 | * @return a Future.Done or Exception 42 | */ 43 | def close(): Future[Unit] = { 44 | if (connection.isDefined) { 45 | isInitiated.set(false) 46 | hostProvider.stopPreventiveSearch() 47 | connection.get.close() 48 | } else { 49 | isInitiated.set(false) 50 | hostProvider.stopPreventiveSearch() 51 | Future.Done 52 | } 53 | } 54 | 55 | /** 56 | * To close connection manager, the current connexion, stop preventive 57 | * search and stop rw server search. 58 | * 59 | * @return a Future.Done or Exception 60 | */ 61 | def close(deadline: Time): Future[Unit] = { 62 | isInitiated.set(false) 63 | if (connection.isDefined) connection.get.close(deadline) 64 | else Future.Done 65 | } 66 | 67 | /** 68 | * Should connect to a server with its address. 69 | * 70 | * @param server the server address 71 | */ 72 | private[this] def connect(server: String): Future[Unit] = this.synchronized { 73 | if (connection.isDefined) connection.get.close() 74 | activeHost = Some(server) 75 | connection = Some(new Connection(newServiceFactory(server))) 76 | isInitiated.set(true) 77 | 78 | ZkClient.logger.info(s"Now connected to $server") 79 | 80 | Status.whenOpen(connection.get.serviceFactoryStatus) 81 | } 82 | 83 | /** 84 | * Find a server, and connect to it, priority to RW server, 85 | * then RO server and finally not RO server. 86 | * 87 | * @return a Future.Done or Exception 88 | */ 89 | def findAndConnect(hostList: Option[Seq[String]] = None): Future[Unit] = 90 | hostProvider.findServer(hostList) transform { 91 | case Return(server) => connect(server) 92 | case Throw(exc) => Future.exception(exc) 93 | } 94 | 95 | /** 96 | * Creates a new service factory. 97 | * @param server the server to connect to 98 | * @return a brand new service factory 99 | */ 100 | def newServiceFactory(server: String): ServiceFactory[ReqPacket, RepPacket] = 101 | if (label.isDefined) Zookeeper.newClient(server, label.get) 102 | else Zookeeper.newClient(server) 103 | 104 | /** 105 | * Test a server with isro request and connect request, then connect to it 106 | * if testing is successful. 107 | * 108 | * @param host a host to test 109 | * @return Future.Done or Exception 110 | */ 111 | def testAndConnect(host: String): Future[Unit] = 112 | hostProvider.testHost(host) transform { 113 | case Return(available) => 114 | if (available) { 115 | hostProvider.addHost(host) 116 | connect(host) 117 | } 118 | else Future.exception(new ServerNotAvailable( 119 | "%s is not available for connection".format(host))) 120 | case Throw(exc) => Future.exception(exc) 121 | } 122 | 123 | /** 124 | * Should say if we have a service factory 125 | * 126 | * @return Future[Boolean] 127 | */ 128 | private[finagle] def hasAvailableServiceFactory: Boolean = { 129 | connection match { 130 | case Some(connect) => 131 | connect.isServiceFactoryAvailable && connect.isValid.get() 132 | case None => false 133 | } 134 | } 135 | 136 | /** 137 | * Should say if we have a valid Service 138 | * 139 | * @return Future[Boolean] 140 | */ 141 | private[finagle] def hasAvailableService: Future[Boolean] = { 142 | connection match { 143 | case Some(connect) => 144 | if (connect.isServiceFactoryAvailable 145 | && connect.isValid.get()) connect.isServiceAvailable 146 | else Future(false) 147 | case None => Future(false) 148 | } 149 | } 150 | 151 | /** 152 | * Initiates connection Manager on client creation. 153 | * 154 | * @return Future.Done or Exception 155 | */ 156 | def initConnectionManager(): Future[Unit] = 157 | if (!isInitiated.get()) 158 | hostProvider.findServer() flatMap { server => 159 | connect(server).before(Future(hostProvider.startPreventiveSearch())).unit 160 | } 161 | else Future.exception( 162 | new RuntimeException("ConnectionManager is already initiated")) 163 | } 164 | -------------------------------------------------------------------------------- /core/src/main/scala/com/twitter/finagle/exp/zookeeper/data/ACL.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.data 2 | 3 | import com.twitter.finagle.exp.zookeeper.connection.HostUtilities 4 | import com.twitter.io.Buf 5 | 6 | case class ACL(perms: Int, id: Id) extends Data { 7 | def buf: Buf = Buf.Empty 8 | .concat(Buf.U32BE(perms)) 9 | .concat(id.buf) 10 | } 11 | 12 | object ACL extends DataDecoder[ACL] { 13 | def apply(perm: Int, scheme: String, id: String): ACL = 14 | new ACL(perm, new Id(scheme, id)) 15 | def apply(perm: Int, id: String) = parseACL(id + perm)(0) 16 | 17 | def unapply(buf: Buf): Option[(ACL, Buf)] = { 18 | val Buf.U32BE(perms, Id(id, rem)) = buf 19 | Some(ACL(perms, id), rem) 20 | } 21 | 22 | /** 23 | * Check an ACL list 24 | * @param aclList the ACL list to check 25 | */ 26 | def check(aclList: Seq[ACL]): Unit = { 27 | aclList.map { acl => 28 | acl.id.scheme match { 29 | case "world" => if (acl.id.data.toUpperCase != "ANYONE") 30 | throw new IllegalArgumentException( 31 | "ACL malformed exception (suggested: ((world:anyone), perms)") 32 | 33 | case "auth" => if (acl.id.data != "") 34 | throw new IllegalArgumentException( 35 | "ACL: Auth malformed exception (suggested: ((auth:), perms)") 36 | 37 | case "digest" => 38 | if (acl.id.data.split(':').length != 2) 39 | throw new IllegalArgumentException( 40 | "ACL: digest malformed exception") 41 | 42 | case "ip" => 43 | if (!HostUtilities.testIP(acl.id.data)) 44 | throw new IllegalArgumentException( 45 | "ACL: IP is malformed") 46 | 47 | case _ => throw new IllegalArgumentException( 48 | "ACL scheme not supported") 49 | } 50 | } 51 | } 52 | 53 | def check(acl: ACL): Unit = check(Seq(acl)) 54 | 55 | /** 56 | * ACL list from a String 57 | * @param str a String composed by scheme:id:perm separated by commas 58 | * @return a Seq[ACL] 59 | */ 60 | def parseACL(str: String): Seq[ACL] = { 61 | val aclTab = str.split(",").map(acl => acl.trim) 62 | val aclList: Array[ACL] = new Array[ACL](aclTab.length) 63 | 64 | for (i <- 0 until aclTab.length) { 65 | 66 | val firstColon = aclTab(i).indexOf(":") 67 | val lastColon = aclTab(i).lastIndexOf(":") 68 | if (firstColon == -1 || lastColon == -1 || firstColon == lastColon) 69 | throw new IllegalArgumentException( 70 | "does not have the form scheme:id:perm") 71 | 72 | val permStr = aclTab(i).substring(lastColon + 1).trim 73 | val schemeStr = aclTab(i).substring(0, firstColon).trim 74 | val dataStr = aclTab(i).substring(firstColon + 1, lastColon).trim 75 | 76 | if (permStr.isEmpty || schemeStr.isEmpty) 77 | throw new IllegalArgumentException( 78 | "does not have the form scheme:id:perm") 79 | if (!permStr.filter(char => !Set('r', 'w', 'c', 'd', 'a') 80 | .contains(char)) 81 | .isEmpty) 82 | throw new IllegalArgumentException( 83 | "does not have the form scheme:id:perm") 84 | val newACL = new ACL( 85 | Perms.permFromString(permStr), 86 | new Id(schemeStr, dataStr)) 87 | 88 | aclList(i) = newACL 89 | } 90 | aclList 91 | } 92 | 93 | /** 94 | * Permissions associated to an ACL 95 | */ 96 | object Perms { 97 | val READ: Int = 1 << 0 98 | val WRITE: Int = 1 << 1 99 | val CREATE: Int = 1 << 2 100 | val DELETE: Int = 1 << 3 101 | val ADMIN: Int = 1 << 4 102 | val ALL: Int = READ | WRITE | CREATE | DELETE | ADMIN 103 | val CREATE_DELETE = CREATE | DELETE 104 | val READ_WRITE = READ | WRITE 105 | 106 | /** 107 | * To get permission from a String 108 | * @param permString a string composed of characters r,w,c,d,a 109 | * @return the permission's Int representation 110 | */ 111 | def permFromString(permString: String): Int = { 112 | var perm: Int = 0 113 | permString.map { 114 | case 'r' => perm = perm | Perms.READ 115 | case 'w' => perm = perm | Perms.WRITE 116 | case 'c' => perm = perm | Perms.CREATE 117 | case 'd' => perm = perm | Perms.DELETE 118 | case 'a' => perm = perm | Perms.ADMIN 119 | case _ => throw new IllegalArgumentException( 120 | "this character is not supported for perms") 121 | } 122 | perm 123 | } 124 | } 125 | } -------------------------------------------------------------------------------- /core/src/main/scala/com/twitter/finagle/exp/zookeeper/data/Data.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.data 2 | 3 | import com.twitter.finagle.exp.zookeeper.ZkDecodingException 4 | import com.twitter.io.Buf 5 | import com.twitter.util.{Throw, Return, Try} 6 | 7 | private[finagle] trait Data { 8 | def buf: Buf 9 | } 10 | 11 | private[finagle] trait DataDecoder[T <: Data] { 12 | def unapply(buffer: Buf): Option[(T, Buf)] 13 | def apply(buffer: Buf): Try[(T, Buf)] = unapply(buffer) match { 14 | case Some((rep, rem)) => Return((rep, rem)) 15 | case None => Throw(ZkDecodingException("Error while decoding data")) 16 | } 17 | } -------------------------------------------------------------------------------- /core/src/main/scala/com/twitter/finagle/exp/zookeeper/data/Id.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.data 2 | 3 | import com.twitter.finagle.exp.zookeeper.data.ACL.Perms 4 | import com.twitter.finagle.exp.zookeeper.transport.{BufArray, BufString} 5 | import com.twitter.io.Buf 6 | 7 | case class Id(scheme: String, data: String) extends Data { 8 | def buf: Buf = Buf.Empty 9 | .concat(BufString(scheme)) 10 | .concat(BufString(data)) 11 | } 12 | case class Auth(scheme: String, data: Array[Byte]) extends Data { 13 | def buf: Buf = Buf.Empty 14 | .concat(BufString(scheme)) 15 | .concat(BufArray(data)) 16 | } 17 | 18 | object Id extends DataDecoder[Id] { 19 | def unapply(buf: Buf): Option[(Id, Buf)] = buf match { 20 | case BufString(scheme, BufString(id, rem)) => Some(Id(scheme, id), rem) 21 | case _ => None 22 | } 23 | } 24 | 25 | /** 26 | * A set of basic ids 27 | */ 28 | object Ids { 29 | /** 30 | * This Id represents anyone. 31 | */ 32 | val ANYONE_ID_UNSAFE = new Id("world", "anyone") 33 | 34 | /** 35 | * This Id is only usable to set ACLs. It will get substituted with the 36 | * Id's the client authenticated with. 37 | */ 38 | val AUTH_IDS = new Id("auth", "") 39 | 40 | /** 41 | * This is a completely open ACL . 42 | */ 43 | val OPEN_ACL_UNSAFE = Seq[ACL](ACL(Perms.ALL, ANYONE_ID_UNSAFE)) 44 | 45 | /** 46 | * This ACL gives the creators authentication id's all permissions. 47 | */ 48 | val CREATOR_ALL_ACL = Seq[ACL](ACL(Perms.ALL, AUTH_IDS)) 49 | 50 | /** 51 | * This ACL gives the world the ability to read. 52 | */ 53 | val READ_ACL_UNSAFE = Seq[ACL](ACL(Perms.READ, ANYONE_ID_UNSAFE)) 54 | } -------------------------------------------------------------------------------- /core/src/main/scala/com/twitter/finagle/exp/zookeeper/data/Stat.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.data 2 | 3 | import com.twitter.io.Buf 4 | 5 | /** 6 | * 7 | * @param czxid The zxid of the change that caused this znode to be created. 8 | * @param mzxid The zxid of the change that last modified this znode. 9 | * @param ctime The time in milliseconds from epoch when this znode was created. 10 | * @param mtime The time in milliseconds from epoch when this znode was last modified. 11 | * @param version The number of changes to the data of this znode. 12 | * @param cversion The number of changes to the children of this znode. 13 | * @param aversion The number of changes to the ACL of this znode. 14 | * @param ephemeralOwner The session id of the owner of this znode if the znode is an ephemeral node. If it is not an ephemeral node, it will be zero. 15 | * @param dataLength The length of the data field of this znode. 16 | * @param numChildren The number of children of this znode. 17 | * @param pzxid Last modified children. 18 | */ 19 | case class Stat( 20 | czxid: Long, 21 | mzxid: Long, 22 | ctime: Long, 23 | mtime: Long, 24 | version: Int, 25 | cversion: Int, 26 | aversion: Int, 27 | ephemeralOwner: Long, 28 | dataLength: Int, 29 | numChildren: Int, 30 | pzxid: Long) extends Data { 31 | def buf: Buf = Buf.Empty 32 | .concat(Buf.U64BE(czxid)) 33 | .concat(Buf.U64BE(mzxid)) 34 | .concat(Buf.U64BE(ctime)) 35 | .concat(Buf.U64BE(mtime)) 36 | .concat(Buf.U32BE(version)) 37 | .concat(Buf.U32BE(cversion)) 38 | .concat(Buf.U32BE(aversion)) 39 | .concat(Buf.U64BE(ephemeralOwner)) 40 | .concat(Buf.U32BE(dataLength)) 41 | .concat(Buf.U32BE(numChildren)) 42 | .concat(Buf.U64BE(pzxid)) 43 | } 44 | 45 | private[finagle] object Stat extends DataDecoder[Stat] { 46 | def unapply(buf: Buf): Option[(Stat, Buf)] = buf match { 47 | case Buf.U64BE(czxid, 48 | Buf.U64BE(mzxid, 49 | Buf.U64BE(ctime, 50 | Buf.U64BE(mtime, 51 | Buf.U32BE(version, 52 | Buf.U32BE(cversion, 53 | Buf.U32BE(aversion, 54 | Buf.U64BE(ephemeralOwner, 55 | Buf.U32BE(dataLength, 56 | Buf.U32BE(numChildren, 57 | Buf.U64BE(pzxid, 58 | rem 59 | ))))))))))) => 60 | Some( 61 | Stat( 62 | czxid, 63 | mzxid, 64 | ctime, 65 | mtime, 66 | version, 67 | cversion, 68 | aversion, 69 | ephemeralOwner, 70 | dataLength, 71 | numChildren, 72 | pzxid), 73 | rem) 74 | case _ => None 75 | } 76 | } -------------------------------------------------------------------------------- /core/src/main/scala/com/twitter/finagle/exp/zookeeper/session/Session.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.session 2 | 3 | import com.twitter.finagle.exp.zookeeper._ 4 | import com.twitter.finagle.exp.zookeeper.client.ZkClient 5 | import com.twitter.finagle.exp.zookeeper.session.Session._ 6 | import com.twitter.finagle.util.DefaultTimer 7 | import com.twitter.util._ 8 | import java.util 9 | import java.util.concurrent.atomic.{AtomicReference, AtomicLong, AtomicInteger, AtomicBoolean} 10 | import com.twitter.util.TimeConversions._ 11 | 12 | /** 13 | * A Session contains ZooKeeper Session Ids and is in charge of sending 14 | * ping requests depending on negotiated session timeout 15 | * 16 | * @param sessionID ZooKeeper session ID 17 | * @param sessionPassword ZooKeeper session password 18 | * @param sessionTimeout requested session timeout 19 | * @param negotiateTimeout negotiated session timeout 20 | * @param isRO if the session is currently read only 21 | */ 22 | class Session( 23 | sessionID: Long = 0L, 24 | sessionPassword: Array[Byte] = Array[Byte](16), 25 | sessionTimeout: Duration = 0.milliseconds, 26 | var negotiateTimeout: Duration = 0.milliseconds, 27 | var isRO: AtomicBoolean = new AtomicBoolean(false), 28 | var pingSender: Option[PingSender] = None 29 | ) { 30 | private[finagle] 31 | var currentState = 32 | new AtomicReference[States.ConnectionState](States.NOT_CONNECTED) 33 | private[finagle] val hasSessionClosed = new AtomicBoolean(false) 34 | private[finagle] var hasFakeSessionId = new AtomicBoolean(true) 35 | private[finagle] val lastZxid = new AtomicLong(0L) 36 | private[this] val xid = new AtomicInteger(2) 37 | 38 | def isReadOnly: Boolean = this.isRO.get() 39 | def id: Long = sessionID 40 | def password: Array[Byte] = sessionPassword 41 | 42 | /** 43 | * Ping every 1/3 of timeout, connect to a new host 44 | * if no response at (lastRequestTime) + 2/3 of timeout 45 | */ 46 | private[this] def pingTimeout: Duration = negotiateTimeout * 1 / 3 47 | def diseredTimeout: Duration = sessionTimeout 48 | def negotiatedTimeout: Duration = negotiateTimeout 49 | def state: States.ConnectionState = currentState.get() 50 | 51 | /** 52 | * Determines if we can use init or reinit 53 | * 54 | * @return true or exception 55 | */ 56 | private[finagle] def canConnect: Boolean = currentState.get() match { 57 | case States.NOT_CONNECTED | 58 | States.CLOSED | 59 | States.SESSION_EXPIRED | 60 | States.SESSION_MOVED | 61 | States.CONNECTION_LOSS => true 62 | case _ => false 63 | } 64 | 65 | /** 66 | * Determines if we can close the session 67 | * 68 | * @return true or exception 69 | */ 70 | private[finagle] def canClose: Boolean = currentState.get() match { 71 | case States.CONNECTING | 72 | States.ASSOCIATING | 73 | States.CLOSED | 74 | States.NOT_CONNECTED => false 75 | case _ => true 76 | } 77 | 78 | /** 79 | * Determines if we can reconnect to a server. 80 | * @return whether or not we can reconnect 81 | */ 82 | private[finagle] def canReconnect: Boolean = currentState.get() match { 83 | case States.CONNECTING | 84 | States.ASSOCIATING => false 85 | case _ => true 86 | } 87 | 88 | /** 89 | * Called after the close session response decoding 90 | */ 91 | private[finagle] def close() { 92 | stopPing() 93 | hasSessionClosed.set(true) 94 | currentState.set(States.CLOSED) 95 | } 96 | 97 | /** 98 | * To get the next xid 99 | * @return the next unique XID 100 | */ 101 | private[finagle] def nextXid: Int = xid.getAndIncrement 102 | 103 | /** 104 | * Use init just after session creation 105 | */ 106 | private[finagle] def init() { 107 | if (!PingScheduler.isRunning) { 108 | if (!pingSender.isDefined) 109 | throw new RuntimeException("Ping sender not defined in Session") 110 | xid.set(2) 111 | lastZxid.set(0L) 112 | startPing() 113 | hasSessionClosed.set(false) 114 | if (isRO.get()) { 115 | currentState.set(States.CONNECTED_READONLY) 116 | ZkClient.logger.info("Server is in Read Only mode") 117 | } 118 | else { 119 | currentState.set(States.CONNECTED) 120 | ZkClient.logger.info("Server is in Read-Write mode") 121 | hasFakeSessionId.set(false) 122 | } 123 | } else throw new RuntimeException( 124 | "Pinger is not initiated or PingScheduler is already running in Session") 125 | } 126 | 127 | /** 128 | * Reinitialize session with a connect response and a function sending ping 129 | * 130 | * @param connectResponse a connect response 131 | * @param pingSender a function sending ping and receiving response 132 | */ 133 | private[finagle] def reinit( 134 | connectResponse: ConnectResponse, 135 | pingSender: PingSender 136 | ): Try[Unit] = Try { 137 | assert(connectResponse.sessionId == sessionID) 138 | assert(util.Arrays.equals(connectResponse.passwd, password)) 139 | 140 | stopPing() 141 | hasSessionClosed.set(false) 142 | isRO.set(connectResponse.isRO) 143 | if (isRO.get()) { 144 | currentState.set(States.CONNECTED_READONLY) 145 | ZkClient.logger.info("Server is in Read Only mode") 146 | } 147 | else { 148 | currentState.set(States.CONNECTED) 149 | ZkClient.logger.info("Server is in Read-Write mode") 150 | hasFakeSessionId.set(false) 151 | } 152 | this.pingSender = Some(pingSender) 153 | negotiateTimeout = connectResponse.timeOut 154 | startPing() 155 | xid.set(2) 156 | ZkClient.logger.info( 157 | s"Reconnected to session with ID: ${ connectResponse.sessionId }") 158 | } 159 | 160 | private[finagle] def stop() { 161 | stopPing() 162 | } 163 | 164 | /** 165 | * This is how we send ping every x milliseconds 166 | */ 167 | private[this] 168 | def startPing(): Unit = PingScheduler(pingTimeout)(pingSender.get()) 169 | 170 | private[this] 171 | def stopPing(): Unit = PingScheduler.stop() 172 | 173 | 174 | /** 175 | * PingScheduler is used to send Ping Request to the server 176 | * every x milliseconds, if the connection is alive. If we issue 177 | * a connection loss then the current task is cancelled and a new one 178 | * is created after the reconnection succeeded. 179 | */ 180 | private[finagle] object PingScheduler { 181 | 182 | /** 183 | * currentTask - the last scheduled timer's task 184 | */ 185 | var currentTask: Option[TimerTask] = None 186 | var elapsedTime: Stopwatch.Elapsed = () => Duration.Bottom 187 | 188 | def apply(period: Duration)(f: => Unit) { 189 | currentTask = Some(DefaultTimer.twitter.schedule(period)(f)) 190 | elapsedTime = Stopwatch.start() 191 | } 192 | 193 | def canSendPing(f: => Unit): Unit = { 194 | if (elapsedTime() >= pingTimeout) () => f 195 | } 196 | 197 | def isRunning: Boolean = { 198 | currentTask.isDefined 199 | } 200 | 201 | def stop() { 202 | if (currentTask.isDefined) currentTask.get.cancel() 203 | currentTask = None 204 | } 205 | 206 | def updateTimer(period: Duration)(f: => Unit) = { 207 | stop() 208 | apply(period)(f) 209 | } 210 | 211 | def receivedEvent(): Unit = { 212 | elapsedTime = Stopwatch.start() 213 | } 214 | } 215 | } 216 | 217 | object Session { 218 | type PingSender = () => Future[Unit] 219 | 220 | class SessionAlreadyEstablished(msg: String) extends RuntimeException(msg) 221 | 222 | class NoSessionEstablished(msg: String) extends RuntimeException(msg) 223 | 224 | /** 225 | * States describes every possible states of the connection 226 | */ 227 | object States extends Enumeration { 228 | type ConnectionState = Value 229 | val CONNECTING, ASSOCIATING, CONNECTED, CONNECTED_READONLY, 230 | CLOSED, AUTH_FAILED, NOT_CONNECTED, CONNECTION_LOSS, SESSION_EXPIRED, 231 | SESSION_MOVED, SASL_AUTHENTICATED = Value 232 | } 233 | } -------------------------------------------------------------------------------- /core/src/main/scala/com/twitter/finagle/exp/zookeeper/session/SessionManager.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.session 2 | 3 | import com.twitter.finagle.exp.zookeeper.client.ZkClient 4 | import com.twitter.finagle.exp.zookeeper.session.Session.{PingSender, States} 5 | import com.twitter.finagle.exp.zookeeper.{ConnectRequest, ConnectResponse, ReplyHeader, WatchEvent} 6 | import com.twitter.util.{Duration, Try} 7 | import java.util.concurrent.atomic.AtomicBoolean 8 | 9 | /** 10 | * Session manager is used to manage sessions during client life 11 | */ 12 | class SessionManager(canBeRo: Boolean) { 13 | 14 | @volatile private[finagle] var session: Session = new Session() 15 | 16 | /** 17 | * Build a connect request to create a new session 18 | * 19 | * @return a customized ConnectResponse 20 | */ 21 | def buildConnectRequest(sessionTimeout: Duration): ConnectRequest = { 22 | ConnectRequest( 23 | 0, 24 | 0L, 25 | sessionTimeout, 26 | 0L, 27 | Array[Byte](16), 28 | canBeRo 29 | ) 30 | } 31 | 32 | /** 33 | * Build a reconnect request depending if RO mode is allowed by user, 34 | * and if current session has a fake session ID ( never connected 35 | * to RW server) 36 | * 37 | * @param sessionTimeout an optional timeout for the session 38 | * @return a customized ConnectResponse 39 | */ 40 | def buildReconnectRequest( 41 | sessionTimeout: Option[Duration] = None 42 | ): ConnectRequest = { 43 | val sessionId = { 44 | if (session.hasFakeSessionId.get) 0 45 | else session.id 46 | } 47 | val sessTimeout = sessionTimeout getOrElse session.diseredTimeout 48 | 49 | ConnectRequest( 50 | 0, 51 | session.lastZxid.get(), 52 | sessTimeout, 53 | sessionId, 54 | session.password, 55 | canBeRo 56 | ) 57 | } 58 | 59 | def canCloseSession: Boolean = session.canClose 60 | def canCreateSession: Boolean = session.canConnect 61 | def canReconnect: Boolean = session.canReconnect 62 | 63 | /** 64 | * To close current session and clean session manager 65 | */ 66 | def closeAndClean() { 67 | session.close() 68 | } 69 | 70 | /** 71 | * Used to create a fresh new Session from the connect response. 72 | * Use cases : connection, reconnection with new Session 73 | * 74 | * @param conRep connect Response 75 | * @param sessionTimeout connect request session timeout 76 | * @param pinger function to send ping request 77 | * @return Unit 78 | */ 79 | def newSession( 80 | conRep: ConnectResponse, 81 | sessionTimeout: Duration, 82 | pinger: PingSender) { 83 | ZkClient.logger.info( 84 | "Connected to session with ID: %d".format(conRep.sessionId)) 85 | 86 | session.stop() 87 | session = new Session( 88 | conRep.sessionId, 89 | conRep.passwd, 90 | sessionTimeout, 91 | conRep.timeOut, 92 | new AtomicBoolean(conRep.isRO), 93 | Some(pinger)) 94 | 95 | session.init() 96 | } 97 | 98 | /** 99 | * Here we are parsing the header's error field 100 | * and changing the connection state if required 101 | * then the ZXID is updated. 102 | * 103 | * @param header request's header 104 | */ 105 | def parseHeader(header: ReplyHeader) { 106 | header.err match { 107 | case 0 => // Ok error code 108 | case -4 => 109 | session.currentState.set(States.CONNECTION_LOSS) 110 | session.stop() 111 | ZkClient.logger.warning("Received CONNECTION_LOSS event from server") 112 | case -112 => 113 | session.currentState.set(States.SESSION_EXPIRED) 114 | session.stop() 115 | ZkClient.logger.warning("Session %d has expired".format(session.id)) 116 | case -115 => 117 | session.currentState.set(States.AUTH_FAILED) 118 | session.stop() 119 | ZkClient.logger.warning("Authentication to server has failed. " + 120 | "Connection closed by server.") 121 | case -118 => session.currentState.set(States.SESSION_MOVED) 122 | session.stop() 123 | ZkClient.logger.warning("Session has moved to another server") 124 | case _ => 125 | } 126 | if (header.zxid > 0) session.lastZxid.set(header.zxid) 127 | } 128 | 129 | /** 130 | * Here we are parsing the watchEvent's state field 131 | * and changing the connection state if required 132 | * 133 | * @param event a request header 134 | */ 135 | def parseWatchEvent(event: WatchEvent) { 136 | event.state match { 137 | case -112 => 138 | session.stop() 139 | session.currentState.set(States.SESSION_EXPIRED) 140 | ZkClient.logger.warning("Session %d has expired".format(session.id)) 141 | case 0 => 142 | session.stop() 143 | session.currentState.set(States.NOT_CONNECTED) 144 | ZkClient.logger.warning("Received NOT_CONNECTED event from server") 145 | case 3 => 146 | session.isRO.compareAndSet(true, false) 147 | session.hasFakeSessionId.compareAndSet(true, false) 148 | if (session.currentState.get != States.CONNECTED) { 149 | session.currentState.set(States.CONNECTED) 150 | ZkClient.logger.info("Server is now in Read-Write mode") 151 | } 152 | case 4 => 153 | session.currentState.set(States.AUTH_FAILED) 154 | session.stop() 155 | ZkClient.logger.warning("Authentication to server has failed. " + 156 | "Connection closed by server.") 157 | case 5 => 158 | session.isRO.compareAndSet(false, true) 159 | if (session.currentState.get != States.CONNECTED_READONLY) { 160 | session.currentState.set(States.CONNECTED_READONLY) 161 | ZkClient.logger.info("Server is now in Read Only mode") 162 | } 163 | case 6 => 164 | session.currentState.set(States.SASL_AUTHENTICATED) 165 | ZkClient.logger.info("SASL authentication confirmed by server") 166 | case _ => 167 | } 168 | } 169 | 170 | /** 171 | * Used to reconnect with the same session Ids 172 | * Use cases : session reconnection after connection loss, 173 | * reconnection to RW mode server. 174 | * 175 | * @param conReq connect response 176 | * @param pinger function to send ping request 177 | * @return Try[Unit] 178 | */ 179 | def reinit( 180 | conReq: ConnectResponse, 181 | pinger: PingSender 182 | ): Try[Unit] = { session.reinit(conReq, pinger) } 183 | } -------------------------------------------------------------------------------- /core/src/main/scala/com/twitter/finagle/exp/zookeeper/transport/Bufs.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.transport 2 | 3 | import com.twitter.finagle.exp.zookeeper.data.{Id, ACL} 4 | import com.twitter.io.Buf 5 | 6 | private[finagle] object BufArray { 7 | def toBytes(buf: Buf): Array[Byte] = { 8 | val bytes = new Array[Byte](buf.length) 9 | buf.write(bytes, 0) 10 | bytes 11 | } 12 | 13 | def apply(a: Array[Byte]): Buf = { 14 | val arrBuf = Buf.ByteArray(a) 15 | Buf.U32BE(arrBuf.length).concat(arrBuf) 16 | } 17 | 18 | def unapply(buf: Buf): Option[(Array[Byte], Buf)] = { 19 | val Buf.U32BE(len, rem) = buf 20 | Some(toBytes(rem.slice(0, len)), rem.slice(len, rem.length)) 21 | } 22 | } 23 | 24 | private[finagle] object BufBool { 25 | def apply(b: Boolean): Buf = { 26 | Buf.ByteArray((if (b) 1 else 0).toByte) 27 | } 28 | 29 | def unapply(buf: Buf): Option[(Boolean, Buf)] = { 30 | val bytes = new Array[Byte](1) 31 | buf.slice(0, 1).write(bytes, 0) 32 | val rem = buf.slice(1, buf.length) 33 | 34 | if (bytes(0) < 0) None else Some(bytes(0) != 0, rem) 35 | } 36 | } 37 | 38 | private[finagle] object BufSeq { 39 | def apply[T](s: Seq[T], toBuf: T => Buf): Buf = 40 | s.foldLeft(Buf.U32BE(s.size)) { (b, i) => b.concat(toBuf(i)) } 41 | 42 | def unapply[T](x: (Buf, Buf => Option[(T, Buf)])): Option[(Seq[T], Buf)] = { 43 | val (buf, fromBuf) = x 44 | 45 | var rem: Buf = Buf.Empty 46 | val Buf.U32BE(len, r) = buf 47 | rem = r 48 | 49 | val items = (0 until len) flatMap { _ => 50 | fromBuf(rem) map { case (i, j) => 51 | rem = j 52 | i 53 | } 54 | } 55 | 56 | Some(items, rem) 57 | } 58 | } 59 | 60 | private[finagle] object BufString { 61 | def apply(s: String): Buf = { 62 | val strBuf = Buf.Utf8(s) 63 | Buf.U32BE(strBuf.length).concat(strBuf) 64 | } 65 | 66 | def unapply(buf: Buf): Option[(String, Buf)] = { 67 | val Buf.U32BE(len, rem) = buf 68 | val Buf.Utf8(str) = rem.slice(0, len) 69 | Some(str, rem.slice(len, rem.length)) 70 | } 71 | } 72 | 73 | private[finagle] object BufSeqACL { 74 | def apply(s: Seq[ACL]): Buf = BufSeq[ACL](s, _.buf) 75 | def unapply(buf: Buf): Option[(Seq[ACL], Buf)] = 76 | BufSeq.unapply[ACL]((buf, ACL.unapply)) 77 | } 78 | 79 | private[finagle] object BufSeqId { 80 | def apply(s: Seq[Id]): Buf = BufSeq[Id](s, _.buf) 81 | def unapply(buf: Buf): Option[(Seq[Id], Buf)] = 82 | BufSeq.unapply[Id]((buf, Id.unapply)) 83 | } 84 | 85 | private[finagle] object BufSeqString { 86 | def apply(s: Seq[String]): Buf = BufSeq[String](s, BufString.apply) 87 | def unapply(buf: Buf): Option[(Seq[String], Buf)] = 88 | BufSeq.unapply[String]((buf, BufString.unapply)) 89 | } -------------------------------------------------------------------------------- /core/src/main/scala/com/twitter/finagle/exp/zookeeper/transport/Netty3.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.transport 2 | 3 | import com.twitter.finagle.Stack 4 | import com.twitter.finagle.netty3.Netty3Transporter 5 | import org.jboss.netty.buffer.ChannelBuffer 6 | import org.jboss.netty.channel._ 7 | 8 | /** 9 | * A Netty3 pipeline that is responsible for framing network 10 | * traffic in terms of mysql logical packets. 11 | */ 12 | 13 | object PipelineFactory extends ChannelPipelineFactory { 14 | def getPipeline = Channels.pipeline() 15 | } 16 | 17 | /** 18 | * Responsible for the transport layer plumbing required to produce 19 | * a Transport[Packet, Packet]. The current implementation uses 20 | * Netty3. 21 | */ 22 | 23 | object NettyTrans extends Netty3Transporter[ChannelBuffer, ChannelBuffer]( 24 | "zookeeper", PipelineFactory) 25 | 26 | object ZookeeperTransporter { 27 | def apply(params: Stack.Params) = 28 | Netty3Transporter[ChannelBuffer, ChannelBuffer](PipelineFactory, params) 29 | } -------------------------------------------------------------------------------- /core/src/main/scala/com/twitter/finagle/exp/zookeeper/transport/ZkTransport.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.transport 2 | 3 | import com.twitter.finagle.Status 4 | import com.twitter.finagle.netty3.ChannelBufferBuf 5 | import com.twitter.finagle.transport.Transport 6 | import com.twitter.io.Buf 7 | import com.twitter.util.{Future, Time} 8 | import java.io.IOException 9 | import java.net.SocketAddress 10 | import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} 11 | 12 | /** 13 | * ZkTransport: 14 | * - on writing : frame outgoing Buf, convert to ChannelBuffer and 15 | * write on given next Transport. 16 | * - on reading : read packet size, and copy corresponding buffer into Buf 17 | * @param trans ChannelBuffer Transport 18 | */ 19 | private[finagle] class ZkTransport(trans: Transport[ChannelBuffer, ChannelBuffer]) 20 | extends Transport[Buf, Buf] { 21 | 22 | @volatile var buf = Buf.Empty 23 | def close(deadline: Time): Future[Unit] = trans.close(deadline) 24 | def status: Status = trans.status 25 | def localAddress: SocketAddress = trans.localAddress 26 | val maxBuffer: Int = 4096 * 1024 27 | val onClose: Future[Throwable] = trans.onClose 28 | def remoteAddress: SocketAddress = trans.remoteAddress 29 | private[finagle] val peerCertificate = None 30 | 31 | def read(): Future[Buf] = this.synchronized { 32 | read(4) flatMap { 33 | case Buf.U32BE(len, _) if len < 0 || len >= maxBuffer => 34 | // Emptying buffer before throwing 35 | buf = Buf.Empty 36 | Future.exception(new IOException("Packet len" + len + " is out of range!")) 37 | case Buf.U32BE(len, _) => read(len) 38 | } 39 | } 40 | 41 | def read(len: Int): Future[Buf] = 42 | if (buf.length < len) { 43 | trans.read flatMap { chanBuf => 44 | buf = buf.concat(ChannelBufferBuf(chanBuf)) 45 | read(len) 46 | } 47 | } else { 48 | val out = buf.slice(0, len) 49 | buf = buf.slice(len, buf.length) 50 | Future.value(out) 51 | } 52 | 53 | def write(req: Buf): Future[Unit] = { 54 | val framedReq = Buf.U32BE(req.length).concat(req) 55 | val bytes = new Array[Byte](framedReq.length) 56 | framedReq.write(bytes, 0) 57 | trans.write(ChannelBuffers.wrappedBuffer(bytes)) 58 | } 59 | } 60 | 61 | /** 62 | * BufTransport: 63 | * - on writing : convert to ChannelBuffer and write on given next Transport. 64 | * - on reading : copy ChannelBuffer to Buf 65 | * @param trans ChannelBuffer Transport 66 | */ 67 | private[finagle] class BufTransport(trans: Transport[ChannelBuffer, ChannelBuffer]) 68 | extends Transport[Buf, Buf] { 69 | 70 | def close(deadline: Time): Future[Unit] = trans.close(deadline) 71 | def status: Status = trans.status 72 | def localAddress: SocketAddress = trans.localAddress 73 | val onClose: Future[Throwable] = trans.onClose 74 | def remoteAddress: SocketAddress = trans.remoteAddress 75 | private[finagle] val peerCertificate = None 76 | 77 | def read(): Future[Buf] = 78 | trans.read flatMap { chanBuf => Future(ChannelBufferBuf(chanBuf)) } 79 | 80 | def write(req: Buf): Future[Unit] = { 81 | val bytes = new Array[Byte](req.length) 82 | req.write(bytes, 0) 83 | trans.write(ChannelBuffers.wrappedBuffer(bytes)) 84 | } 85 | } -------------------------------------------------------------------------------- /core/src/main/scala/com/twitter/finagle/exp/zookeeper/utils/PathUtils.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.utils 2 | 3 | import com.twitter.finagle.exp.zookeeper.ZookeeperDefs.CreateMode 4 | 5 | object PathUtils { 6 | 7 | def validatePath(path: String, createMod: Int): Unit = { 8 | if (createMod == 0 || createMod == 1 || createMod == 2 || createMod == 3) { 9 | if (createMod == CreateMode.EPHEMERAL_SEQUENTIAL 10 | || createMod == CreateMode.PERSISTENT_SEQUENTIAL) 11 | validatePath(path + 1) 12 | 13 | else validatePath(path) 14 | 15 | } else throw new IllegalArgumentException("Create mode is not correct") 16 | } 17 | 18 | def validatePath(path: String): Unit = { 19 | if (path == null) { 20 | throw new IllegalArgumentException("Path cannot be null") 21 | } 22 | if (path.length == 0) { 23 | throw new IllegalArgumentException("Path length must be > 0") 24 | } 25 | if (path.charAt(0) != '/') { 26 | throw new IllegalArgumentException("Path must start with / character") 27 | } 28 | if (path.length == 1) { 29 | return 30 | } 31 | if (path.charAt(path.length - 1) == '/') { 32 | throw new IllegalArgumentException("Path must not end with / character") 33 | } 34 | 35 | var reason: String = "" 36 | var lastc: Char = '/' 37 | val chars = path.replaceFirst("/", "").toList 38 | var index = 0 39 | 40 | for (char <- chars) { 41 | if (char == 0) { 42 | reason = "null character not allowed @" + index 43 | throw new IllegalArgumentException("Invalid path string \"" + path + "\" caused by " + reason) 44 | } 45 | else if (char == '/' && lastc == '/') { 46 | reason = "empty node name specified @" + index 47 | throw new IllegalArgumentException("Invalid path string \"" + path + "\" caused by " + reason) 48 | } 49 | else if (char == '.' && lastc == '.') { 50 | if (chars(index - 2) == '/' && ((index + 1 == chars.length) || chars(index + 1) == '/')) { 51 | reason = "relative paths not allowed @" + index 52 | throw new IllegalArgumentException("Invalid path string \"" + path + "\" caused by " + reason) 53 | } 54 | } 55 | else if (char == '.') { 56 | if (chars(index - 1) == '/' && ((index + 1 == chars.length) || chars(index + 1) == '/')) { 57 | reason = "relative paths not allowed @" + index 58 | throw new IllegalArgumentException("Invalid path string \"" + path + "\" caused by " + reason) 59 | } 60 | } 61 | else if (char > '\u0000' && char < '\u001f' || char > '\u007f' && char < '\u009F' || char > '\ud800' && char < '\uf8ff' || char > '\ufff0' && char < '\uffff') { 62 | reason = "invalid character @" + index 63 | throw new IllegalArgumentException("Invalid path string \"" + path + "\" caused by " + reason) 64 | } 65 | lastc = char 66 | index += 1 67 | } 68 | } 69 | 70 | def prependChroot(path: String, chroot: String): String = 71 | if (chroot.trim.isEmpty) path 72 | else { 73 | if (path == "/") chroot else chroot + path 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /core/src/main/scala/com/twitter/finagle/exp/zookeeper/watcher/Watch.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.watcher 2 | 3 | import com.twitter.finagle.exp.zookeeper.WatchEvent 4 | import com.twitter.util.Future 5 | 6 | /** 7 | * A watcher represents a Watch which is a Future[WatchEvent] and a path, 8 | * corresponding to the znode. 9 | * 10 | * @param path path to the watched znode 11 | * @param typ watchManager map type (child, data, exists) 12 | * @param event a Future[WatchEvent) 13 | */ 14 | case class Watcher( 15 | path: String, 16 | private[finagle] val typ: Int, 17 | event: Future[WatchEvent] 18 | ) 19 | 20 | private[finagle] object Watch { 21 | object EventType { 22 | val NONE = -1 23 | val NODE_CREATED = 1 24 | val NODE_DELETED = 2 25 | val NODE_DATA_CHANGED = 3 26 | val NODE_CHILDREN_CHANGED = 4 27 | val DATA_WATCH_REMOVED = 5 28 | val CHILD_WATCH_REMOVED = 6 29 | 30 | def getEvent(code: Int): String = code match { 31 | case -1 => "Node" 32 | case 1 => "Node created" 33 | case 2 => "Node deleted" 34 | case 3 => "Node data changed" 35 | case 4 => "Node children changed" 36 | case 5 => "Data watch removed" 37 | case 6 => "Child watch removed" 38 | } 39 | } 40 | 41 | /** 42 | * Used to classify map in three categories 43 | */ 44 | object WatcherType { 45 | val CHILDREN = 1 46 | val DATA = 2 47 | val ANY = 3 48 | } 49 | 50 | /** 51 | * Used to describe in which map we are holding the watch 52 | */ 53 | private[finagle] object WatcherMapType { 54 | val data = 1 55 | val exists = 2 56 | val children = 3 57 | } 58 | 59 | object EventState { 60 | /** Unused, this state is never generated by the server */ 61 | @Deprecated 62 | val UNKNOWN = -1 63 | /** The client is in the disconnected state - it is not connected 64 | * to any server in the ensemble. */ 65 | val DISCONNECTED = 0 66 | /** Unused, this state is never generated by the server */ 67 | @Deprecated 68 | val NO_SYNC_CONNECTED = 1 69 | /** The client is in the connected state - it is connected 70 | * to a server in the ensemble (one of the servers specified 71 | * in the host connection parameter during ZooKeeper client 72 | * creation). */ 73 | val SYNC_CONNECTED = 3 74 | /** 75 | * Auth failed state 76 | */ 77 | val AUTH_FAILED = 4 78 | /** 79 | * The client is connected to a read-only server, that is the 80 | * server which is not currently connected to the majority. 81 | * The only operations allowed after receiving this state is 82 | * read operations. 83 | * This state is generated for read-only clients only since 84 | * read/write clients aren't allowed to connect to r/o servers. 85 | */ 86 | val CONNECTED_READ_ONLY = 5 87 | /** 88 | * SaslAuthenticated: 89 | * used to notify clients that they are SASL-authenticated, 90 | * so that they can perform Zookeeper actions with their 91 | * SASL-authorized permissions. 92 | */ 93 | val SASL_AUTHENTICATED = 6 94 | /** The serving cluster has expired this session. The ZooKeeper 95 | * client connection (the session) is no longer valid. You must 96 | * create a new client connection (instantiate a new ZooKeeper 97 | * instance) if you with to access the ensemble. */ 98 | val EXPIRED = -112 99 | 100 | def getState(code: Int): String = code match { 101 | case -1 => "Unknown" 102 | case 0 => "Disconnected" 103 | case 1 => "No sync connected" 104 | case 3 => "Sync connected" 105 | case 4 => "Auth Failed" 106 | case 5 => "Connected read only" 107 | case 6 => "SASL authenticated" 108 | case -112 => "expired" 109 | case _ => throw new IllegalArgumentException( 110 | "Match error: not supported watch state code") 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /core/src/test/scala/com/twitter/finagle/exp/zookeeper/unit/ACLTest.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.unit 2 | 3 | import com.twitter.finagle.exp.zookeeper.data.ACL.Perms 4 | import com.twitter.finagle.exp.zookeeper.data.{Id, ACL, Ids} 5 | import org.junit.runner.RunWith 6 | import org.scalatest.FunSuite 7 | import org.scalatest.junit.JUnitRunner 8 | 9 | @RunWith(classOf[JUnitRunner]) 10 | class ACLTest extends FunSuite { 11 | 12 | test("ACL world is correct") { 13 | ACL.check(Ids.OPEN_ACL_UNSAFE) 14 | } 15 | 16 | test("ACL (world, me) is not correct") { 17 | intercept[IllegalArgumentException] { 18 | val acl = ACL(31, Id("world", "me")) 19 | ACL.check(acl) 20 | } 21 | } 22 | 23 | test("ACL (samba:anyone) is not correct") { 24 | intercept[IllegalArgumentException] { 25 | val acl = ACL(31, Id("samba", "anyone")) 26 | ACL.check(acl) 27 | } 28 | } 29 | 30 | test("ACL ip:192.168.10.1 is correct") { 31 | val acl = ACL(31, Id("ip", "192.168.10.1")) 32 | ACL.check(acl) 33 | } 34 | 35 | test("ACL ip:192.10.1 is not correct") { 36 | intercept[IllegalArgumentException] { 37 | val acl = ACL(31, Id("ip", "192.10.1")) 38 | ACL.check(acl) 39 | } 40 | } 41 | 42 | test("ACL ip:192.10.10.e is not correct") { 43 | intercept[IllegalArgumentException] { 44 | val acl = ACL(31, Id("ip", "192.10.10.e")) 45 | ACL.check(acl) 46 | } 47 | } 48 | 49 | test("ACL (auth:) is correct") { 50 | val acl = ACL(31, Id("auth", "")) 51 | ACL.check(acl) 52 | } 53 | 54 | test("ACL (auth:terminator) is not correct") { 55 | intercept[IllegalArgumentException] { 56 | val acl = ACL(31, Id("auth", "terminator")) 57 | ACL.check(acl) 58 | } 59 | } 60 | 61 | test("ACL (digest:paul:paulpwd) is correct") { 62 | val acl = ACL(31, Id("digest", "paul:paulpwd")) 63 | ACL.check(acl) 64 | } 65 | 66 | test("ACL (digest:paul;paulpwd) is not correct") { 67 | intercept[IllegalArgumentException] { 68 | val acl = ACL(31, new Id("digest", "paul;paulpwd")) 69 | ACL.check(acl) 70 | } 71 | } 72 | 73 | test("Permission from string rwcda works") { 74 | val perms = ACL.Perms.permFromString("rwcda") 75 | assert(perms === 31) 76 | } 77 | 78 | test("Permission from string hello should fail") { 79 | intercept[IllegalArgumentException] { 80 | ACL.Perms.permFromString("hello") 81 | } 82 | } 83 | 84 | test("Parse ACL should work") { 85 | val aclList = ACL.parseACL("world:anyone:rw") 86 | assert(aclList(0).perms === Perms.READ_WRITE) 87 | assert(aclList(0).id.scheme === "world") 88 | assert(aclList(0).id.data === "anyone") 89 | 90 | ACL.check(aclList) 91 | 92 | val aclList2 = ACL.parseACL("ip:192.168.1.0:rwcda, world:anyone:cd," + 93 | "auth::rw,digest:toto:pwd:cd, ip:10.0.0.1:cd") 94 | assert(aclList2(0).perms === Perms.ALL) 95 | assert(aclList2(0).id.scheme === "ip") 96 | assert(aclList2(0).id.data === "192.168.1.0") 97 | assert(aclList2(1).perms === Perms.CREATE_DELETE) 98 | assert(aclList2(1).id.scheme === "world") 99 | assert(aclList2(1).id.data === "anyone") 100 | assert(aclList2(2).perms === Perms.READ_WRITE) 101 | assert(aclList2(2).id.scheme === "auth") 102 | assert(aclList2(2).id.data === "") 103 | assert(aclList2(3).perms === Perms.CREATE_DELETE) 104 | assert(aclList2(3).id.scheme === "digest") 105 | assert(aclList2(3).id.data === "toto:pwd") 106 | assert(aclList2(4).perms === Perms.CREATE_DELETE) 107 | assert(aclList2(4).id.scheme === "ip") 108 | assert(aclList2(4).id.data === "10.0.0.1") 109 | 110 | ACL.check(aclList2) 111 | } 112 | 113 | test("Parse ACL should not work") { 114 | intercept[IllegalArgumentException] { 115 | ACL.parseACL("worldanyone:rw") 116 | } 117 | intercept[IllegalArgumentException] { 118 | ACL.parseACL("world:anyone:how") 119 | } 120 | intercept[IllegalArgumentException] { 121 | ACL.parseACL("world:anyone:31") 122 | } 123 | intercept[IllegalArgumentException] { 124 | ACL.parseACL("world:anyone:rw, ip:10.0.1.10:auth:") 125 | } 126 | intercept[IllegalArgumentException] { 127 | ACL.parseACL("world:anyone:rwip:10.0.0.1") 128 | } 129 | intercept[IllegalArgumentException] { 130 | ACL.parseACL("worldanyone") 131 | } 132 | intercept[IllegalArgumentException] { 133 | ACL.parseACL("world:anyone") 134 | } 135 | intercept[IllegalArgumentException] { 136 | ACL.parseACL("auth:rw") 137 | } 138 | intercept[IllegalArgumentException] { 139 | ACL.parseACL(":rw:") 140 | } 141 | intercept[IllegalArgumentException] { 142 | ACL.parseACL("rw:") 143 | } 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /core/src/test/scala/com/twitter/finagle/exp/zookeeper/unit/DispatchingTest.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.unit 2 | 3 | import com.twitter.finagle.exp.zookeeper.ZookeeperDefs.OpCode 4 | import com.twitter.finagle.exp.zookeeper._ 5 | import com.twitter.finagle.exp.zookeeper.client.ZkDispatcher 6 | import com.twitter.finagle.exp.zookeeper.connection.ConnectionManager 7 | import com.twitter.finagle.exp.zookeeper.session.SessionManager 8 | import com.twitter.finagle.exp.zookeeper.transport.BufString 9 | import com.twitter.finagle.exp.zookeeper.watcher.{Watch, WatcherManager} 10 | import com.twitter.finagle.transport.Transport 11 | import com.twitter.finagle.{CancelledRequestException, ChannelClosedException} 12 | import com.twitter.io.Buf 13 | import com.twitter.util.{Await, Future, Promise} 14 | import org.junit.runner.RunWith 15 | import org.mockito.Matchers._ 16 | import org.mockito.Mockito 17 | import org.mockito.Mockito.{times, verify, when} 18 | import org.scalatest.FunSuite 19 | import org.scalatest.junit.JUnitRunner 20 | import org.scalatest.mock.MockitoSugar 21 | 22 | @RunWith(classOf[JUnitRunner]) 23 | class DispatchingTest extends FunSuite with MockitoSugar { 24 | trait TestHelper { 25 | val deleteReq = ReqPacket( 26 | Some(RequestHeader(3, OpCode.DELETE)), 27 | Some(DeleteRequest("/zookeeper/worker1/job1", 2)) 28 | ) 29 | val deleteRep = Future( 30 | Buf.Empty 31 | .concat(Buf.U32BE(3)) 32 | .concat(Buf.U64BE(124)) 33 | .concat(Buf.U32BE(0)) 34 | ) 35 | val notifRep = Future( 36 | Buf.Empty 37 | .concat(Buf.U32BE(-1)) 38 | .concat(Buf.U64BE(134)) 39 | .concat(Buf.U32BE(0)) 40 | .concat(Buf.U32BE(Watch.EventType.NODE_CREATED)) 41 | .concat(Buf.U32BE(Watch.EventState.SYNC_CONNECTED)) 42 | .concat(BufString("/zookeeper")) 43 | ) 44 | 45 | val transport = mock[Transport[Buf, Buf]] 46 | val dispatcher = Mockito.spy(new ZkDispatcher(transport)) 47 | 48 | val connectionManager = new ConnectionManager( 49 | "127.0.0.1:2181", 50 | None, 51 | false, 52 | None, 53 | None) 54 | val sessionManager = new SessionManager(false) 55 | val watchManager: WatcherManager = new WatcherManager("", false) 56 | val configDispatcher = ReqPacket(None, Some(ConfigureRequest( 57 | connectionManager, 58 | sessionManager, 59 | watchManager 60 | ))) 61 | } 62 | 63 | test("Basic req dispatching") { 64 | new TestHelper { 65 | Await.ready(dispatcher(configDispatcher)) 66 | 67 | when(transport.write(any[Buf])) thenReturn Future.Done 68 | when(transport.read()) thenReturn deleteRep thenReturn Promise[Buf]() 69 | 70 | assert(Await.result(dispatcher(deleteReq)) == RepPacket(Some(0), None)) 71 | verify(dispatcher, times(2)).apply(any[ReqPacket]) 72 | verify(transport).write(any[Buf]) 73 | } 74 | } 75 | 76 | test("Basic notification dispatching") { 77 | new TestHelper { 78 | when(transport.write(any[Buf])) thenReturn Future.Done 79 | when(transport.read()) thenReturn deleteRep thenReturn notifRep thenReturn 80 | Promise[Buf]() 81 | 82 | val watcher = watchManager.registerWatcher("/zookeeper", Watch.WatcherMapType.data) 83 | Await.ready(dispatcher(configDispatcher)) 84 | assert(Await.result(dispatcher(deleteReq)) == RepPacket(Some(0), None)) 85 | 86 | val rep = Await.result(watcher.event) 87 | assert(rep.path === "/zookeeper") 88 | assert(rep.state === Watch.EventState.SYNC_CONNECTED) 89 | verify(transport, times(3)).read() 90 | verify(dispatcher, times(2)).apply(any[ReqPacket]) 91 | } 92 | } 93 | 94 | test("Request and notifications dispatching") { 95 | new TestHelper { 96 | Await.ready(dispatcher(configDispatcher)) 97 | 98 | val watcher = watchManager.registerWatcher("/zookeeper", Watch.WatcherMapType.data) 99 | when(transport.write(any[Buf])) thenReturn Future.Done 100 | when(transport.read()) thenReturn deleteRep thenReturn notifRep thenReturn 101 | Promise[Buf]() 102 | 103 | assert(Await.result(dispatcher(deleteReq)) == RepPacket(Some(0), None)) 104 | val rep = Await.result(watcher.event) 105 | assert(rep.path === "/zookeeper") 106 | assert(rep.state === Watch.EventState.SYNC_CONNECTED) 107 | } 108 | } 109 | 110 | test("multi requests with errors") { 111 | new TestHelper { 112 | Await.ready(dispatcher(configDispatcher)) 113 | val wrongRep = Future( 114 | Buf.Empty 115 | .concat(Buf.U32BE(16)) 116 | .concat(Buf.U64BE(124)) 117 | .concat(Buf.U32BE(0)) 118 | ) 119 | when(transport.write(any[Buf])) thenReturn Future.Done 120 | when(transport.read()) thenReturn deleteRep thenReturn wrongRep thenReturn 121 | Promise[Buf]() 122 | assert(Await.result(dispatcher(deleteReq)) == RepPacket(Some(0), None)) 123 | } 124 | } 125 | 126 | test("request should fail the dispatcher when writing") { 127 | new TestHelper { 128 | Await.ready(dispatcher(configDispatcher)) 129 | when(transport.write(any[Buf])) thenReturn { 130 | Future.exception(new ChannelClosedException()) 131 | } 132 | intercept[CancelledRequestException] { 133 | Await.result(dispatcher(deleteReq)) 134 | } 135 | } 136 | } 137 | 138 | test("request should fail the dispatcher when reading") { 139 | new TestHelper { 140 | Await.ready(dispatcher(configDispatcher)) 141 | when(transport.write(any[Buf])) thenReturn Future.Done 142 | when(transport.read()) thenReturn 143 | Future.exception(new ChannelClosedException()) 144 | 145 | intercept[CancelledRequestException] { 146 | Await.result(dispatcher(deleteReq)) 147 | } 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /core/src/test/scala/com/twitter/finagle/exp/zookeeper/unit/HostProviderTest.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.unit 2 | 3 | import com.twitter.finagle.exp.zookeeper.connection.{HostProvider, HostUtilities} 4 | import org.junit.runner.RunWith 5 | import org.scalatest.FunSuite 6 | import org.scalatest.junit.JUnitRunner 7 | 8 | @RunWith(classOf[JUnitRunner]) 9 | class HostProviderTest extends FunSuite { 10 | test("should add some hosts") { 11 | val hostProvider = new HostProvider("127.0.0.1:2181", true, None, None) 12 | hostProvider.addHost("10.0.0.1:2181,10.0.0.10:2181,192.168.0.1:2181") 13 | assert(hostProvider.serverList.contains("10.0.0.1:2181")) 14 | assert(hostProvider.serverList.contains("10.0.0.10:2181")) 15 | assert(hostProvider.serverList.contains("192.168.0.1:2181")) 16 | intercept[IllegalArgumentException] { 17 | hostProvider.addHost("this is not legal") 18 | } 19 | intercept[IllegalArgumentException] { 20 | hostProvider.addHost("10.0.1:2181") 21 | } 22 | intercept[IllegalArgumentException] { 23 | hostProvider.addHost("10.0.0.1:2181;127.0.0.1:2181") 24 | } 25 | intercept[IllegalArgumentException] { 26 | hostProvider.addHost("127.0.0.j1:2181") 27 | } 28 | intercept[IllegalArgumentException] { 29 | hostProvider.addHost("127.0.0.1") 30 | } 31 | } 32 | 33 | test("should shuffle the host list") { 34 | val seq = Seq("10.0.0.1:2181", "10.0.0.10:2181", "192.168.0.1:2181", 35 | "192.168.0.10:2181", "192.168.0.4:2181", "192.168.0.3:2181") 36 | val seq2 = HostUtilities.shuffleSeq(seq) 37 | assert(seq != seq2) 38 | } 39 | 40 | test("Test IPv6") { 41 | HostUtilities.testIpAddress("2001:cdba:0000:0000:0000:0000:3257:9652:2181") 42 | HostUtilities.testIpAddress("2607:f0d0:1002:51::4:2181") 43 | } 44 | } -------------------------------------------------------------------------------- /core/src/test/scala/com/twitter/finagle/exp/zookeeper/unit/PathTest.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.unit 2 | 3 | import org.junit.runner.RunWith 4 | import org.scalatest.FunSuite 5 | import com.twitter.finagle.exp.zookeeper.utils.PathUtils 6 | import org.scalatest.junit.JUnitRunner 7 | 8 | @RunWith(classOf[JUnitRunner]) 9 | class PathTest extends FunSuite { 10 | test("validate paths") { 11 | PathUtils.validatePath("/") 12 | PathUtils.validatePath("/zookeeper") 13 | PathUtils.validatePath("/zk01") 14 | PathUtils.validatePath("/zookeeper/edd-001") 15 | PathUtils.validatePath("/zookeeper/test/hello") 16 | } 17 | 18 | test("not validate paths") { 19 | intercept[IllegalArgumentException] { 20 | PathUtils.validatePath("zookeeper/test/hello") 21 | } 22 | intercept[IllegalArgumentException] { 23 | PathUtils.validatePath("/zookeeper//hello") 24 | } 25 | intercept[IllegalArgumentException] { 26 | PathUtils.validatePath("/zookeeper//hello") 27 | } 28 | intercept[IllegalArgumentException] { 29 | PathUtils.validatePath("") 30 | } 31 | intercept[IllegalArgumentException] { 32 | PathUtils.validatePath("zk") 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /core/src/test/scala/com/twitter/finagle/exp/zookeeper/unit/PreProcessServiceTest.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.unit 2 | 3 | import com.twitter.finagle.CancelledRequestException 4 | import com.twitter.finagle.exp.zookeeper._ 5 | import com.twitter.finagle.exp.zookeeper.client.{ZkClient, PreProcessService} 6 | import com.twitter.finagle.exp.zookeeper.client.managers.{ClientManager, AutoLinkManager} 7 | import com.twitter.finagle.exp.zookeeper.connection.{Connection, ConnectionManager} 8 | import com.twitter.finagle.exp.zookeeper.data.Ids 9 | import com.twitter.finagle.exp.zookeeper.session.SessionManager 10 | import com.twitter.util.{Await, Future} 11 | import org.scalatest.FunSuite 12 | import org.scalatest.FunSuite 13 | import org.scalatest.mock.MockitoSugar 14 | import org.mockito.Matchers._ 15 | import org.mockito.Mockito 16 | import org.mockito.Mockito.{times, verify, when} 17 | import org.mockito.invocation.InvocationOnMock 18 | import org.scalatest.FunSuite 19 | import org.scalatest.mock.MockitoSugar 20 | import org.junit.runner.RunWith 21 | import org.mockito.Matchers._ 22 | import org.mockito.Mockito 23 | import org.mockito.Mockito.{times, verify, when} 24 | import org.scalatest.FunSuite 25 | import org.scalatest.junit.JUnitRunner 26 | import org.scalatest.mock.MockitoSugar 27 | 28 | @RunWith(classOf[JUnitRunner]) 29 | class PreProcessServiceTest extends FunSuite with MockitoSugar { 30 | trait HelperTrait { 31 | val con = mock[Connection] 32 | val conMgnr = new ConnectionManager("127.0.0.1:2181", None, false, None, None) 33 | conMgnr.connection = Some(con) 34 | val sessMngr = new SessionManager(false) 35 | val autoMngr = mock[ZkClient with ClientManager] 36 | val preProcess = Mockito.spy(new PreProcessService(conMgnr, sessMngr, autoMngr)) 37 | } 38 | 39 | test("send a request correctly") { 40 | new HelperTrait { 41 | when(con.serve(any[ReqPacket])) thenReturn Future(RepPacket(None, None)) 42 | preProcess.unlockService() 43 | preProcess(DeleteRequest("/zookeeper/test", -1)) 44 | verify(con).serve(any[ReqPacket]) 45 | } 46 | } 47 | 48 | test("send a request and get an exception") { 49 | new HelperTrait { 50 | when(con.serve(any[ReqPacket])) thenReturn Future.exception(new CancelledRequestException()) 51 | preProcess.unlockService() 52 | preProcess(DeleteRequest("/zookeeper/test", -1)) 53 | verify(con).serve(any[ReqPacket]) 54 | } 55 | } 56 | 57 | test("send a request and wait until permit is available") { 58 | new HelperTrait { 59 | when(con.serve(any[ReqPacket])) thenReturn Future.exception(new CancelledRequestException()) 60 | preProcess.lockService() 61 | preProcess(DeleteRequest("/zookeeper/test", -1)) 62 | preProcess.unlockService() 63 | verify(con).serve(any[ReqPacket]) 64 | verify(preProcess).unlockService() 65 | } 66 | } 67 | 68 | test("lock the service") { 69 | new HelperTrait { 70 | when(con.serve(any[ReqPacket])) thenReturn Future.exception(new CancelledRequestException()) 71 | preProcess.lockService() 72 | preProcess(DeleteRequest("/zookeeper/test", -1)) 73 | verify(con, times(0)).serve(any[ReqPacket]) 74 | verify(preProcess).lockService() 75 | } 76 | } 77 | 78 | test("unlock the service") { 79 | new HelperTrait { 80 | when(con.serve(any[ReqPacket])) thenReturn Future.exception(new CancelledRequestException()) 81 | preProcess.lockService() 82 | preProcess(DeleteRequest("/zookeeper/test", -1)) 83 | preProcess(DeleteRequest("/zookeeper/test", -1)) 84 | preProcess(DeleteRequest("/zookeeper/test", -1)) 85 | verify(con, times(0)).serve(any[ReqPacket]) 86 | verify(preProcess).lockService() 87 | preProcess.unlockService() 88 | verify(con, times(3)).serve(any[ReqPacket]) 89 | } 90 | } 91 | 92 | test("should throw an exception will sending a write request in RO mode") { 93 | new HelperTrait { 94 | when(con.serve(any[ReqPacket])) thenReturn Future.exception(new CancelledRequestException()) 95 | preProcess.unlockService() 96 | sessMngr.session.isRO.set(true) 97 | intercept[NotReadOnlyException] { 98 | Await.result(preProcess(CreateRequest("", "h".getBytes, Ids.OPEN_ACL_UNSAFE, 1))) 99 | } 100 | intercept[NotReadOnlyException] { 101 | Await.result(preProcess(Create2Request("", "h".getBytes, Ids.OPEN_ACL_UNSAFE, 1))) 102 | } 103 | intercept[NotReadOnlyException] { 104 | Await.result(preProcess(DeleteRequest("", 1))) 105 | } 106 | intercept[NotReadOnlyException] { 107 | Await.result(preProcess(ReconfigRequest("", "h", "", 1L))) 108 | } 109 | intercept[NotReadOnlyException] { 110 | Await.result(preProcess(SetACLRequest("", Ids.OPEN_ACL_UNSAFE, 1))) 111 | } 112 | intercept[NotReadOnlyException] { 113 | Await.result(preProcess(SetDataRequest("", "h".getBytes, 1))) 114 | } 115 | intercept[NotReadOnlyException] { 116 | Await.result(preProcess(SyncRequest(""))) 117 | } 118 | intercept[NotReadOnlyException] { 119 | Await.result(preProcess(TransactionRequest(Seq.empty[OpRequest]))) 120 | } 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /core/src/test/scala/com/twitter/finagle/exp/zookeeper/unit/WatcherManagerTest.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.unit 2 | 3 | import com.twitter.finagle.exp.zookeeper.WatchEvent 4 | import com.twitter.finagle.exp.zookeeper.watcher.{Watch, WatcherManager} 5 | import com.twitter.util.Await 6 | import org.junit.runner.RunWith 7 | import org.scalatest.FunSuite 8 | import org.scalatest.junit.JUnitRunner 9 | 10 | @RunWith(classOf[JUnitRunner]) 11 | class WatcherManagerTest extends FunSuite { 12 | trait HelperTrait { 13 | val watchMngr = new WatcherManager("", false) 14 | val watchEvent = WatchEvent(Watch.EventType.NODE_DATA_CHANGED 15 | , Watch.EventState.SYNC_CONNECTED, "/zookeeper/test") 16 | } 17 | 18 | test("should register some watchers") { 19 | new HelperTrait { 20 | watchMngr.registerWatcher("/zookeeper/test", Watch.WatcherMapType.data) 21 | watchMngr.registerWatcher("/zookeeper/test", Watch.WatcherMapType.children) 22 | watchMngr.registerWatcher("/zookeeper/test", Watch.WatcherMapType.exists) 23 | assert(watchMngr.getDataWatchers.contains("/zookeeper/test")) 24 | assert(watchMngr.getChildrenWatchers.contains("/zookeeper/test")) 25 | assert(watchMngr.getExistsWatchers.contains("/zookeeper/test")) 26 | } 27 | } 28 | 29 | test("should register a watcher and trigger") { 30 | new HelperTrait { 31 | val watcher = watchMngr.registerWatcher( 32 | "/zookeeper/test", Watch.WatcherMapType.data) 33 | assert(watchMngr.getDataWatchers.contains("/zookeeper/test")) 34 | watchMngr.process(watchEvent) 35 | val rep = Await.result(watcher.event) 36 | assert(rep.path === "/zookeeper/test") 37 | assert(rep.state === Watch.EventState.SYNC_CONNECTED) 38 | } 39 | } 40 | 41 | test("should register a watcher and trigger with a chroot path") { 42 | new HelperTrait { 43 | override val watchMngr = new WatcherManager("/zookeeper", false) 44 | val watcher = watchMngr.registerWatcher( 45 | "/test", Watch.WatcherMapType.data) 46 | assert(watchMngr.getDataWatchers.contains("/test")) 47 | watchMngr.process(watchEvent) 48 | val rep = Await.result(watcher.event) 49 | assert(rep.path === "/test") 50 | assert(rep.state === Watch.EventState.SYNC_CONNECTED) 51 | } 52 | } 53 | 54 | test("should register a watcher and call isDefined") { 55 | new HelperTrait { 56 | val watcher = watchMngr.registerWatcher( 57 | "/zookeeper/test", Watch.WatcherMapType.data) 58 | assert(watchMngr.isWatcherDefined(watcher)) 59 | } 60 | } 61 | 62 | test("should remove a watcher and call isDefined") { 63 | new HelperTrait { 64 | val watcher = watchMngr.registerWatcher( 65 | "/zookeeper/test", Watch.WatcherMapType.data) 66 | assert(watchMngr.isWatcherDefined(watcher)) 67 | watchMngr.removeWatcher(watcher) 68 | assert(!watchMngr.isWatcherDefined(watcher)) 69 | } 70 | } 71 | 72 | test("should remove some watchers and call isDefined") { 73 | new HelperTrait { 74 | val watcher = watchMngr.registerWatcher( 75 | "/zookeeper/test", Watch.WatcherMapType.data) 76 | val watcher2 = watchMngr.registerWatcher( 77 | "/zookeeper/test", Watch.WatcherMapType.children) 78 | val watcher3 = watchMngr.registerWatcher( 79 | "/zookeeper/test", Watch.WatcherMapType.exists) 80 | assert(watchMngr.isWatcherDefined(watcher)) 81 | assert(watchMngr.isWatcherDefined(watcher2)) 82 | assert(watchMngr.isWatcherDefined(watcher3)) 83 | watchMngr.removeWatchers("/zookeeper/test", Watch.WatcherType.CHILDREN) 84 | assert(!watchMngr.isWatcherDefined(watcher2)) 85 | watchMngr.removeWatchers("/zookeeper/test", Watch.WatcherType.DATA) 86 | assert(!watchMngr.isWatcherDefined(watcher)) 87 | assert(!watchMngr.isWatcherDefined(watcher3)) 88 | 89 | val watcher4 = watchMngr.registerWatcher( 90 | "/zookeeper/test", Watch.WatcherMapType.data) 91 | val watcher5 = watchMngr.registerWatcher( 92 | "/zookeeper/test", Watch.WatcherMapType.children) 93 | val watcher6 = watchMngr.registerWatcher( 94 | "/zookeeper/test", Watch.WatcherMapType.exists) 95 | watchMngr.removeWatchers("/zookeeper/test", Watch.WatcherType.ANY) 96 | assert(!watchMngr.isWatcherDefined(watcher4)) 97 | assert(!watchMngr.isWatcherDefined(watcher5)) 98 | assert(!watchMngr.isWatcherDefined(watcher6)) 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /core/src/test/scala/com/twitter/finagle/exp/zookeeper/unit/session/SessionManagerTest.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.unit.session 2 | 3 | import java.util.concurrent.atomic.AtomicBoolean 4 | 5 | import com.twitter.finagle.exp.zookeeper.watcher.Watch 6 | import com.twitter.finagle.exp.zookeeper.{WatchEvent, ReplyHeader, ConnectResponse} 7 | import com.twitter.finagle.exp.zookeeper.session.Session.States 8 | import com.twitter.finagle.exp.zookeeper.session.{Session, SessionManager} 9 | import com.twitter.util.Future 10 | import org.junit.runner.RunWith 11 | import org.scalatest.FunSuite 12 | import com.twitter.util.TimeConversions._ 13 | import org.scalatest.junit.JUnitRunner 14 | 15 | @RunWith(classOf[JUnitRunner]) 16 | class SessionManagerTest extends FunSuite { 17 | test("should build a good connectRequest") { 18 | val sessionManager = new SessionManager(true) 19 | val conReq = sessionManager.buildConnectRequest(3000.milliseconds) 20 | assert(conReq.lastZxidSeen === 0l) 21 | assert(conReq.protocolVersion === 0) 22 | assert(conReq.sessionTimeout === 3000.milliseconds) 23 | assert(conReq.canBeRO === true) 24 | } 25 | 26 | test("should build a good reconnectRequest with a fake session") { 27 | val sessionManager = new SessionManager(true) 28 | sessionManager.session.hasFakeSessionId.set(true) 29 | sessionManager.session.lastZxid.set(123556L) 30 | val conReq = sessionManager.buildReconnectRequest(Some(3000.milliseconds)) 31 | assert(conReq.lastZxidSeen === 123556L) 32 | assert(conReq.protocolVersion === 0) 33 | assert(conReq.sessionId === 0L) 34 | assert(conReq.sessionTimeout === 3000.milliseconds) 35 | assert(conReq.canBeRO === true) 36 | } 37 | 38 | test("should build a good reconnectRequest with a real session") { 39 | val sessionManager = new SessionManager(true) 40 | sessionManager.session = new Session( 41 | sessionID = 12315641L, 42 | sessionTimeout = 3000.milliseconds, 43 | negotiateTimeout = 2000.milliseconds, 44 | pingSender = Some(() => Future(println("ping"))) 45 | ) 46 | sessionManager.session.hasFakeSessionId.set(false) 47 | val conReq = sessionManager.buildReconnectRequest(Some(3000.milliseconds)) 48 | assert(conReq.lastZxidSeen === 0L) 49 | assert(conReq.protocolVersion === 0) 50 | assert(conReq.sessionId === 12315641L) 51 | assert(conReq.sessionTimeout === 3000.milliseconds) 52 | assert(conReq.canBeRO === true) 53 | } 54 | 55 | test("should create a new session correctly") { 56 | val sessionManager = new SessionManager(true) 57 | val conRep = ConnectResponse( 58 | 0, 59 | 2000.milliseconds, 60 | 12315641L, 61 | Array[Byte](16), 62 | true 63 | ) 64 | sessionManager.newSession(conRep, 3000.milliseconds, () => Future.Done) 65 | assert(sessionManager.session.PingScheduler.isRunning) 66 | assert(sessionManager.session.nextXid === 2) 67 | assert(sessionManager.session.nextXid === 3) 68 | assert(sessionManager.session.lastZxid.get() === 0L) 69 | assert(sessionManager.session.isReadOnly === true) 70 | assert(sessionManager.session.hasSessionClosed.get() === false) 71 | assert(sessionManager.session.currentState.get() === States.CONNECTED_READONLY) 72 | assert(sessionManager.session.hasFakeSessionId.get() === true) 73 | } 74 | 75 | test("should parse a header") { 76 | val header = ReplyHeader(12, 124, -4) 77 | val sessionManager = new SessionManager(true) 78 | sessionManager.parseHeader(header) 79 | assert(!sessionManager.session.PingScheduler.isRunning) 80 | assert(sessionManager.session.currentState.get() === States.CONNECTION_LOSS) 81 | 82 | val header2 = ReplyHeader(12, 124, -112) 83 | val sessionManager2 = new SessionManager(true) 84 | sessionManager2.parseHeader(header2) 85 | assert(!sessionManager2.session.PingScheduler.isRunning) 86 | assert(sessionManager2.session.currentState.get() === States.SESSION_EXPIRED) 87 | 88 | val header3 = ReplyHeader(12, 124, -115) 89 | val sessionManager3 = new SessionManager(true) 90 | sessionManager3.parseHeader(header3) 91 | assert(sessionManager3.session.currentState.get() === States.AUTH_FAILED) 92 | 93 | val header4 = ReplyHeader(12, 124, -118) 94 | val sessionManager4 = new SessionManager(true) 95 | sessionManager4.parseHeader(header4) 96 | assert(!sessionManager4.session.PingScheduler.isRunning) 97 | assert(sessionManager4.session.currentState.get() === States.SESSION_MOVED) 98 | } 99 | 100 | test("should parse a WatchEvent") { 101 | val watchEvent = WatchEvent(Watch.EventType.NONE, Watch.EventState.EXPIRED, "") 102 | val sessionManager = new SessionManager(true) 103 | sessionManager.parseWatchEvent(watchEvent) 104 | assert(!sessionManager.session.PingScheduler.isRunning) 105 | assert(sessionManager.session.currentState.get() === States.SESSION_EXPIRED) 106 | 107 | val watchEvent2 = WatchEvent(Watch.EventType.NONE, Watch.EventState.DISCONNECTED, "") 108 | val sessionManager2 = new SessionManager(true) 109 | sessionManager2.parseWatchEvent(watchEvent2) 110 | assert(!sessionManager2.session.PingScheduler.isRunning) 111 | assert(sessionManager2.session.currentState.get() === States.NOT_CONNECTED) 112 | 113 | 114 | val watchEvent3 = WatchEvent(Watch.EventType.NONE, Watch.EventState.SYNC_CONNECTED, "") 115 | val sessionManager3 = new SessionManager(true) 116 | sessionManager3.parseWatchEvent(watchEvent3) 117 | assert(sessionManager3.session.currentState.get() === States.CONNECTED) 118 | 119 | 120 | val watchEvent4 = WatchEvent(Watch.EventType.NONE, Watch.EventState.CONNECTED_READ_ONLY, "") 121 | val sessionManager4 = new SessionManager(true) 122 | sessionManager4.parseWatchEvent(watchEvent4) 123 | assert(sessionManager4.session.currentState.get() === States.CONNECTED_READONLY) 124 | } 125 | 126 | test("should reinit") { 127 | val sessionManager = new SessionManager(true) 128 | sessionManager.session = new Session( 129 | sessionID = 12315641L, 130 | sessionTimeout = 3000.milliseconds, 131 | negotiateTimeout = 2000.milliseconds, 132 | isRO = new AtomicBoolean(true), 133 | pingSender = Some(() => Future.Done) 134 | ) 135 | sessionManager.session.init() 136 | assert(sessionManager.session.PingScheduler.isRunning) 137 | assert(sessionManager.session.nextXid === 2) 138 | assert(sessionManager.session.nextXid === 3) 139 | assert(sessionManager.session.lastZxid.get() === 0L) 140 | assert(sessionManager.session.hasSessionClosed.get() === false) 141 | assert(sessionManager.session.isReadOnly === true) 142 | assert(sessionManager.session.currentState.get() === States.CONNECTED_READONLY) 143 | assert(sessionManager.session.hasFakeSessionId.get() === true) 144 | 145 | val conRep2 = ConnectResponse( 146 | 0, 147 | 2000.milliseconds, 148 | 12315641L, 149 | Array[Byte](16), 150 | isRO = false 151 | ) 152 | sessionManager.session.reinit(conRep2, () => Future.Done) 153 | assert(sessionManager.session.PingScheduler.isRunning) 154 | assert(sessionManager.session.nextXid === 2) 155 | assert(sessionManager.session.nextXid === 3) 156 | assert(sessionManager.session.lastZxid.get() === 0L) 157 | assert(sessionManager.session.isReadOnly === false) 158 | assert(sessionManager.session.hasSessionClosed.get() === false) 159 | assert(sessionManager.session.currentState.get() === States.CONNECTED) 160 | assert(sessionManager.session.hasFakeSessionId.get() === false) 161 | } 162 | } -------------------------------------------------------------------------------- /core/src/test/scala/com/twitter/finagle/exp/zookeeper/unit/session/SessionTest.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.unit.session 2 | 3 | import java.util.concurrent.atomic.AtomicBoolean 4 | 5 | import com.twitter.finagle.exp.zookeeper.ConnectResponse 6 | import com.twitter.finagle.exp.zookeeper.session.Session 7 | import com.twitter.finagle.exp.zookeeper.session.Session.States 8 | import com.twitter.util.TimeConversions._ 9 | import com.twitter.util.{Future, Throw} 10 | import org.junit.runner.RunWith 11 | import org.scalatest.FunSuite 12 | import org.scalatest.junit.JUnitRunner 13 | 14 | @RunWith(classOf[JUnitRunner]) 15 | class SessionTest extends FunSuite { 16 | test("should init correctly") { 17 | val session = new Session( 18 | sessionID = 12315641L, 19 | sessionTimeout = 3000.milliseconds, 20 | negotiateTimeout = 2000.milliseconds, 21 | pingSender = Some(() => Future.Done) 22 | ) 23 | session.init() 24 | assert(session.PingScheduler.isRunning) 25 | assert(session.nextXid === 2) 26 | assert(session.nextXid === 3) 27 | assert(session.lastZxid.get() === 0L) 28 | assert(session.isReadOnly === false) 29 | assert(session.hasSessionClosed.get() === false) 30 | assert(session.currentState.get() === States.CONNECTED) 31 | assert(session.hasFakeSessionId.get() === false) 32 | 33 | val session2 = new Session( 34 | sessionID = 12315641L, 35 | sessionTimeout = 3000.milliseconds, 36 | negotiateTimeout = 2000.milliseconds, 37 | isRO = new AtomicBoolean(true), 38 | pingSender = Some(() => Future.Done) 39 | ) 40 | session2.init() 41 | assert(session2.PingScheduler.isRunning) 42 | assert(session2.nextXid === 2) 43 | assert(session2.nextXid === 3) 44 | assert(session2.lastZxid.get() === 0L) 45 | assert(session2.isReadOnly === true) 46 | assert(session2.hasSessionClosed.get() === false) 47 | assert(session2.currentState.get() === States.CONNECTED_READONLY) 48 | assert(session2.hasFakeSessionId.get() === true) 49 | } 50 | 51 | test("should not init") { 52 | val session = new Session() 53 | intercept[RuntimeException] { 54 | session.init() 55 | } 56 | val session2 = new Session( 57 | sessionID = 12315641L, 58 | sessionTimeout = 3000.milliseconds, 59 | negotiateTimeout = 2000.milliseconds, 60 | pingSender = Some(() => Future.Done) 61 | ) 62 | session2.init() 63 | intercept[RuntimeException] { 64 | session.init() 65 | } 66 | } 67 | 68 | test("should reinit the session") { 69 | val session = new Session( 70 | sessionID = 12315641L, 71 | sessionTimeout = 3000.milliseconds, 72 | negotiateTimeout = 2000.milliseconds, 73 | pingSender = Some(() => Future.Done) 74 | ) 75 | session.init() 76 | assert(session.PingScheduler.isRunning) 77 | assert(session.nextXid === 2) 78 | assert(session.nextXid === 3) 79 | assert(session.lastZxid.get() === 0L) 80 | assert(session.isReadOnly === false) 81 | assert(session.hasSessionClosed.get() === false) 82 | assert(session.currentState.get() === States.CONNECTED) 83 | assert(session.hasFakeSessionId.get() === false) 84 | val conRep = ConnectResponse( 85 | 0, 86 | 2000.milliseconds, 87 | 12315641L, 88 | Array[Byte](16), 89 | true 90 | ) 91 | session.reinit(conRep, () => Future.Done) 92 | assert(session.PingScheduler.isRunning) 93 | assert(session.nextXid === 2) 94 | assert(session.nextXid === 3) 95 | assert(session.lastZxid.get() === 0L) 96 | assert(session.isReadOnly === true) 97 | assert(session.hasSessionClosed.get() === false) 98 | assert(session.currentState.get() === States.CONNECTED_READONLY) 99 | assert(session.hasFakeSessionId.get() === false) 100 | 101 | val session2 = new Session( 102 | sessionID = 12315641L, 103 | sessionTimeout = 3000.milliseconds, 104 | negotiateTimeout = 2000.milliseconds, 105 | isRO = new AtomicBoolean(true), 106 | pingSender = Some(() => Future.Done) 107 | ) 108 | session2.init() 109 | assert(session2.PingScheduler.isRunning) 110 | assert(session2.nextXid === 2) 111 | assert(session2.nextXid === 3) 112 | assert(session2.lastZxid.get() === 0L) 113 | assert(session2.hasSessionClosed.get() === false) 114 | assert(session2.isReadOnly === true) 115 | assert(session2.currentState.get() === States.CONNECTED_READONLY) 116 | assert(session2.hasFakeSessionId.get() === true) 117 | 118 | val conRep2 = ConnectResponse( 119 | 0, 120 | 2000.milliseconds, 121 | 12315641L, 122 | Array[Byte](16), 123 | isRO = false 124 | ) 125 | session2.reinit(conRep2, () => Future.Done) 126 | assert(session2.PingScheduler.isRunning) 127 | assert(session2.nextXid === 2) 128 | assert(session2.nextXid === 3) 129 | assert(session2.lastZxid.get() === 0L) 130 | assert(session2.isReadOnly === false) 131 | assert(session2.hasSessionClosed.get() === false) 132 | assert(session2.currentState.get() === States.CONNECTED) 133 | assert(session2.hasFakeSessionId.get() === false) 134 | } 135 | 136 | test("should not reinit the session") { 137 | val session = new Session( 138 | sessionID = 12315641L, 139 | sessionTimeout = 3000.milliseconds, 140 | negotiateTimeout = 2000.milliseconds, 141 | pingSender = Some(() => Future.Done) 142 | ) 143 | session.init() 144 | val conRep = ConnectResponse( 145 | 0, 146 | 2000.milliseconds, 147 | 1782315641L, 148 | Array[Byte](16), 149 | true 150 | ) 151 | session.reinit(conRep, () => Future.Done) match { 152 | case Throw(exc) => 153 | assert(exc.isInstanceOf[AssertionError]) 154 | case _ => 155 | } 156 | } 157 | 158 | test("should close correctly") { 159 | val session = new Session( 160 | sessionID = 12315641L, 161 | sessionTimeout = 3000.milliseconds, 162 | negotiateTimeout = 2000.milliseconds, 163 | pingSender = Some(() => Future.Done) 164 | ) 165 | session.init() 166 | session.close() 167 | assert(!session.PingScheduler.isRunning) 168 | assert(session.hasSessionClosed.get() === true) 169 | assert(session.currentState.get() === States.CLOSED) 170 | } 171 | } -------------------------------------------------------------------------------- /core/src/test/scala/com/twitter/finagle/exp/zookeeper/unit/transport/BufTransportTest.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.unit.transport 2 | 3 | import com.twitter.finagle.exp.zookeeper.DeleteRequest 4 | import com.twitter.finagle.exp.zookeeper.transport.BufTransport 5 | import com.twitter.finagle.transport.Transport 6 | import com.twitter.io.Buf 7 | import com.twitter.util.{Await, Future} 8 | import java.util 9 | import org.jboss.netty.buffer.{ChannelBuffer, ChannelBuffers} 10 | import org.junit.runner.RunWith 11 | import org.mockito.Matchers._ 12 | import org.mockito.Mockito 13 | import org.mockito.Mockito.{times, verify, when} 14 | import org.mockito.invocation.InvocationOnMock 15 | import org.mockito.stubbing.Answer 16 | import org.scalatest.FunSuite 17 | import org.scalatest.junit.JUnitRunner 18 | import org.scalatest.mock.MockitoSugar 19 | 20 | @RunWith(classOf[JUnitRunner]) 21 | class BufTransportTest extends FunSuite with MockitoSugar { 22 | trait TestHelper { 23 | val deleteReq = DeleteRequest("/zookeeper/node", -1) 24 | val cbTransport = mock[Transport[ChannelBuffer, ChannelBuffer]] 25 | val zkTransport = Mockito.spy(new BufTransport(cbTransport)) 26 | } 27 | 28 | test("should convert Buf to ChannelBuffer") { 29 | new TestHelper { 30 | when(cbTransport.write(any[ChannelBuffer])) thenAnswer { 31 | new Answer[Future[Unit]] { 32 | override def answer(invocation: InvocationOnMock): Future[Unit] = { 33 | val cb = invocation.getArguments.head.asInstanceOf[ChannelBuffer] 34 | val bytes = new Array[Byte](deleteReq.buf.length) 35 | Buf.Empty 36 | .concat(deleteReq.buf) 37 | .write(bytes, 0) 38 | if (util.Arrays.equals(bytes, cb.array())) Future.Done 39 | else Future.exception(new RuntimeException("bad conversion")) 40 | } 41 | } 42 | } 43 | 44 | Await.result(zkTransport.write(deleteReq.buf)) 45 | Await.result(zkTransport.write(deleteReq.buf)) 46 | verify(zkTransport, times(2)).write(any[Buf]) 47 | verify(cbTransport, times(2)).write(any[ChannelBuffer]) 48 | } 49 | } 50 | 51 | test("should convert one rep from ChannelBuffer to buf") { 52 | new TestHelper { 53 | when(cbTransport.read()) thenReturn { 54 | val bytes = new Array[Byte](deleteReq.buf.length) 55 | Buf.Empty 56 | .concat(deleteReq.buf) 57 | .write(bytes, 0) 58 | Future(ChannelBuffers.wrappedBuffer(bytes)) 59 | } 60 | 61 | val falseRep = DeleteRequest("/woot/cat", -1) 62 | val rep = Await.result(zkTransport.read()) 63 | verify(cbTransport).read() 64 | verify(zkTransport).read() 65 | assert(rep != falseRep.buf) 66 | assert(rep === deleteReq.buf) 67 | } 68 | } 69 | 70 | test("should not convert ChannelBuffer to buf") { 71 | new TestHelper { 72 | when(cbTransport.read()) thenReturn { 73 | val bytes = new Array[Byte](deleteReq.buf.length + 4) 74 | Buf.Empty 75 | .concat(Buf.U32BE(deleteReq.buf.length)) 76 | .concat(deleteReq.buf) 77 | .write(bytes, 0) 78 | Future(ChannelBuffers.wrappedBuffer(bytes)) 79 | } 80 | 81 | val rep = Await.result(zkTransport.read()) 82 | verify(zkTransport).read() 83 | verify(cbTransport).read() 84 | assert(rep != deleteReq.buf) 85 | } 86 | } 87 | 88 | test("should convert 2 rep from ChannelBuffer to buf") { 89 | new TestHelper { 90 | when(cbTransport.read()) thenReturn { 91 | val bytes = new Array[Byte](deleteReq.buf.length * 2) 92 | Buf.Empty 93 | .concat(deleteReq.buf) 94 | .concat(deleteReq.buf) 95 | .write(bytes, 0) 96 | Future(ChannelBuffers.wrappedBuffer(bytes)) 97 | } 98 | 99 | val rep = Await.result(zkTransport.read()) 100 | 101 | assert(rep === deleteReq.buf.concat(deleteReq.buf)) 102 | verify(cbTransport).read() 103 | verify(zkTransport).read() 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /core/src/test/scala/com/twitter/finagle/exp/zookeeper/unit/transport/ZkTransportTest.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.unit.transport 2 | 3 | import com.twitter.finagle.exp.zookeeper.{GetDataRequest, DeleteRequest} 4 | import com.twitter.finagle.exp.zookeeper.transport.ZkTransport 5 | import com.twitter.finagle.transport.Transport 6 | import com.twitter.io.Buf 7 | import com.twitter.util._ 8 | import java.io.IOException 9 | import java.util 10 | import org.jboss.netty.buffer.{ChannelBuffer, ChannelBuffers} 11 | import org.junit.runner.RunWith 12 | import org.mockito.Matchers._ 13 | import org.mockito.Mockito 14 | import org.mockito.Mockito.{times, verify, when} 15 | import org.mockito.invocation.InvocationOnMock 16 | import org.mockito.stubbing.Answer 17 | import org.scalatest.FunSuite 18 | import org.scalatest.junit.JUnitRunner 19 | import org.scalatest.mock.MockitoSugar 20 | 21 | @RunWith(classOf[JUnitRunner]) 22 | class ZkTransportTest extends FunSuite with MockitoSugar { 23 | trait TestHelper { 24 | val deleteReq = DeleteRequest("/zookeeper/node", -1) 25 | val cbTransport = mock[Transport[ChannelBuffer, ChannelBuffer]] 26 | val zkTransport = Mockito.spy(new ZkTransport(cbTransport)) 27 | } 28 | 29 | test("should convert Buf to ChannelBuffer") { 30 | new TestHelper { 31 | when(cbTransport.write(any[ChannelBuffer])) thenAnswer { 32 | new Answer[Future[Unit]] { 33 | override def answer(invocation: InvocationOnMock): Future[Unit] = { 34 | val cb = invocation.getArguments.head.asInstanceOf[ChannelBuffer] 35 | val bytes = new Array[Byte](deleteReq.buf.length + 4) 36 | Buf.Empty 37 | .concat(Buf.U32BE(deleteReq.buf.length)) 38 | .concat(deleteReq.buf) 39 | .write(bytes, 0) 40 | if (util.Arrays.equals(bytes, cb.array())) Future.Done 41 | else Future.exception(new RuntimeException("bad conversion")) 42 | } 43 | } 44 | } 45 | 46 | Await.result(zkTransport.write(deleteReq.buf)) 47 | Await.result(zkTransport.write(deleteReq.buf)) 48 | verify(zkTransport, times(2)).write(any[Buf]) 49 | verify(cbTransport, times(2)).write(any[ChannelBuffer]) 50 | } 51 | } 52 | 53 | test("should convert one rep from ChannelBuffer to buf") { 54 | new TestHelper { 55 | when(cbTransport.read()) thenReturn { 56 | val bytes = new Array[Byte](deleteReq.buf.length + 4) 57 | Buf.Empty 58 | .concat(Buf.U32BE(deleteReq.buf.length)) 59 | .concat(deleteReq.buf) 60 | .write(bytes, 0) 61 | Future(ChannelBuffers.wrappedBuffer(bytes)) 62 | } 63 | 64 | val falseRep = DeleteRequest("/woot/cat", -1) 65 | val rep = Await.result(zkTransport.read()) 66 | verify(cbTransport).read() 67 | verify(zkTransport).read() 68 | verify(zkTransport, times(3)).read(any[Int]) 69 | assert(rep != falseRep.buf) 70 | assert(rep === deleteReq.buf) 71 | } 72 | } 73 | 74 | test("should not convert ChannelBuffer to buf") { 75 | new TestHelper { 76 | when(cbTransport.read()) thenReturn { 77 | val bytes = new Array[Byte](deleteReq.buf.length + 4) 78 | Buf.Empty 79 | .concat(deleteReq.buf) 80 | .write(bytes, 0) 81 | Future(ChannelBuffers.wrappedBuffer(bytes)) 82 | } 83 | 84 | val rep = Await.result(zkTransport.read()) 85 | verify(zkTransport).read() 86 | verify(zkTransport, times(3)).read(any[Int]) 87 | verify(cbTransport).read() 88 | assert(rep != deleteReq.buf) 89 | } 90 | } 91 | 92 | test("should throw an IOException for Buf > 1 MB") { 93 | new TestHelper { 94 | when(cbTransport.read()) thenReturn { 95 | val bytes = new Array[Byte](4097 * 1024) 96 | Future(ChannelBuffers 97 | .wrappedBuffer(bytes map (_ => 1.toByte))) 98 | } 99 | 100 | intercept[IOException] { 101 | Await.result(zkTransport.read()) 102 | } 103 | verify(zkTransport).read() 104 | verify(zkTransport, times(2)).read(any[Int]) 105 | verify(cbTransport).read() 106 | } 107 | } 108 | 109 | test("should convert 2 rep from ChannelBuffer to buf") { 110 | new TestHelper { 111 | when(cbTransport.read()) thenReturn { 112 | val bytes = new Array[Byte]((deleteReq.buf.length + 4) * 2) 113 | Buf.Empty 114 | .concat(Buf.U32BE(deleteReq.buf.length)) 115 | .concat(deleteReq.buf) 116 | .concat(Buf.U32BE(deleteReq.buf.length)) 117 | .concat(deleteReq.buf) 118 | .write(bytes, 0) 119 | Future(ChannelBuffers.wrappedBuffer(bytes)) 120 | } 121 | 122 | val falseRep = DeleteRequest("/woot/cat", -1) 123 | val rep = Await.result(zkTransport.read()) 124 | verify(cbTransport).read() 125 | 126 | assert(rep != falseRep.buf) 127 | assert(rep === deleteReq.buf) 128 | assert(Await.result(zkTransport.read()) === deleteReq.buf) 129 | verify(cbTransport).read() 130 | verify(zkTransport, times(2)).read() 131 | verify(zkTransport, times(5)).read(any[Int]) 132 | } 133 | } 134 | 135 | test("should convert one good and one bad rep") { 136 | new TestHelper { 137 | when(cbTransport.read()) thenReturn { 138 | val bytes = new Array[Byte](4097 * 1024) 139 | Future(ChannelBuffers 140 | .wrappedBuffer(bytes map (_ => 1.toByte))) 141 | } thenReturn { 142 | val bytes = new Array[Byte](deleteReq.buf.length + 4) 143 | Buf.Empty 144 | .concat(Buf.U32BE(deleteReq.buf.length)) 145 | .concat(deleteReq.buf) 146 | .write(bytes, 0) 147 | Future(ChannelBuffers.wrappedBuffer(bytes)) 148 | } 149 | 150 | intercept[IOException] { 151 | Await.result(zkTransport.read()) 152 | } 153 | val rep = Await.result(zkTransport.read()) 154 | verify(cbTransport, times(2)).read() 155 | verify(zkTransport, times(2)).read() 156 | verify(zkTransport, times(5)).read(any[Int]) 157 | assert(rep === deleteReq.buf) 158 | } 159 | } 160 | 161 | test("should transform correctly few reads") { 162 | val getData = GetDataRequest("/zookeeper/worker1/job2", false) 163 | new TestHelper { 164 | when(cbTransport.read()) thenReturn { 165 | val bytes = new Array[Byte](getData.buf.length + 4) 166 | Buf.Empty 167 | .concat(Buf.U32BE(getData.buf.length)) 168 | .concat(getData.buf) 169 | .write(bytes, 0) 170 | Future(ChannelBuffers.wrappedBuffer(bytes)) 171 | } thenReturn { 172 | val bytes = new Array[Byte](deleteReq.buf.length + 4) 173 | Buf.Empty 174 | .concat(Buf.U32BE(deleteReq.buf.length)) 175 | .concat(deleteReq.buf) 176 | .write(bytes, 0) 177 | Future(ChannelBuffers.wrappedBuffer(bytes)) 178 | } thenReturn { 179 | val bytes = new Array[Byte](getData.buf.length + 4) 180 | Buf.Empty 181 | .concat(Buf.U32BE(getData.buf.length)) 182 | .concat(getData.buf) 183 | .write(bytes, 0) 184 | Future(ChannelBuffers.wrappedBuffer(bytes)) 185 | } thenReturn { 186 | val bytes = new Array[Byte](deleteReq.buf.length + 4) 187 | Buf.Empty 188 | .concat(Buf.U32BE(deleteReq.buf.length)) 189 | .concat(deleteReq.buf) 190 | .write(bytes, 0) 191 | Future(ChannelBuffers.wrappedBuffer(bytes)) 192 | } 193 | 194 | assert(Await.result(zkTransport.read()) === getData.buf) 195 | assert(Await.result(zkTransport.read()) === deleteReq.buf) 196 | assert(Await.result(zkTransport.read()) === getData.buf) 197 | assert(Await.result(zkTransport.read()) === deleteReq.buf) 198 | verify(cbTransport, times(4)).read() 199 | verify(zkTransport, times(4)).read() 200 | verify(zkTransport, times(12)).read(any[Int]) 201 | } 202 | } 203 | } -------------------------------------------------------------------------------- /example/src/main/scala/com/twitter/finagle/exp/zookeeper/example/Locks.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.example 2 | 3 | import com.twitter.finagle.exp.zookeeper.{ConnectionLossException, NoNodeException} 4 | import com.twitter.finagle.exp.zookeeper.ZookeeperDefs.CreateMode 5 | import com.twitter.finagle.exp.zookeeper.client.ZkClient 6 | import com.twitter.finagle.exp.zookeeper.data.Ids 7 | import com.twitter.finagle.util.DefaultTimer 8 | import com.twitter.util.{Throw, Return, Duration, Future} 9 | 10 | /** 11 | * Lock recipe with finagle-zookeeper. 12 | * 13 | * @param timeout duration between each retry attempts 14 | * @param rootLockPath base root path for locks 15 | * @param lockNodePrefix prefix name for lock nodes 16 | * @param client a connected ZkClient 17 | */ 18 | class Locks( 19 | timeout: Duration, 20 | rootLockPath: String, 21 | lockNodePrefix: String 22 | )(implicit client: ZkClient) { 23 | 24 | implicit val timer = DefaultTimer.twitter 25 | val lockPathModel = rootLockPath + "/" + lockNodePrefix + "-" 26 | // In this example we suppose you are already connected to a server 27 | // Session management is up to the caller 28 | 29 | // Create a node for all the lock nodes 30 | def initiate(): Future[Unit] = 31 | client.create( 32 | rootLockPath, 33 | "root node for locks".getBytes, 34 | Ids.OPEN_ACL_UNSAFE, 35 | CreateMode.PERSISTENT 36 | ).unit 37 | 38 | // Remove the base lock node 39 | def clean(): Future[Unit] = client.delete(rootLockPath, -1) 40 | 41 | def lockAndAct[A](f: => A): Future[A] = { 42 | lock() flatMap { lockpath => 43 | val res = f 44 | releaseLock(lockpath) before Future(res) 45 | } 46 | } 47 | 48 | def lock(): Future[String] = 49 | client.create( 50 | lockPathModel, 51 | "".getBytes, 52 | Ids.OPEN_ACL_UNSAFE, 53 | CreateMode.EPHEMERAL_SEQUENTIAL 54 | ) flatMap obtainLeadership 55 | 56 | def releaseLock(lockNode: String): Future[Unit] = client.delete(lockNode, -1) 57 | 58 | /** 59 | * It will check that the node we created has the lowest id, if not 60 | * it will wait that the next lowest id node change. 61 | * 62 | * @param lockNode the created lock node 63 | * @return 64 | */ 65 | private[this] def obtainLeadership(lockNode: String): Future[String] = { 66 | client.getChildren(rootLockPath) transform { 67 | case Return(getChildren) => 68 | val createId = lockNode.drop(lockPathModel.size).toInt 69 | // ids of all the children 70 | val ids = getChildren.children.map { _.drop(lockNodePrefix.size + 1).toInt } 71 | 72 | if (ids.min == createId) Future(lockNode) 73 | else { 74 | client.exists( 75 | path = lockPathModel + ids.filter { _ < createId }.max, 76 | watch = true 77 | ) flatMap { ex => 78 | if (ex.stat.isDefined) 79 | ex.watcher.get.event.unit before obtainLeadership(lockNode) 80 | 81 | else { 82 | // we don't need a watcher if node does not exist 83 | client.watcherManager.removeWatcher(ex.watcher.get) 84 | obtainLeadership(lockNode).delayed(timeout) 85 | } 86 | } rescue { 87 | // Temporary lock node does not exist, maybe we just reconnected to a server 88 | // calling lock while create a new ephemeral lock and call obtainLeadership 89 | case ex: NoNodeException => lock() 90 | 91 | // if autoreconnect is enabled, will try to reconnect 92 | // otherwise exception is propagated to the caller 93 | case ex: ConnectionLossException => 94 | if (client.autoReconnect) obtainLeadership(lockNode) 95 | else throw ex 96 | 97 | case ex => throw ex 98 | } 99 | } 100 | 101 | // base lock node does not exist, will create and retry to obtain leadership 102 | case Throw(ex: NoNodeException) => 103 | initiate() before obtainLeadership(lockNode) 104 | 105 | // we have lost the connection while getChildren 106 | // if autoreconnect is enabled, will try to reconnect 107 | // otherwise exception is propagated to the caller 108 | case Throw(ex: ConnectionLossException) => 109 | if (client.autoReconnect) obtainLeadership(lockNode) 110 | else throw ex 111 | 112 | case Throw(ex) => throw ex 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /integration/src/main/resources/scripts/runQuorumMode.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | HERE=$(pwd) 4 | S1ROOT=$HERE/s1 5 | S2ROOT=$HERE/s2 6 | S3ROOT=$HERE/s3 7 | RELEASEDIR=$HERE/releases 8 | ZOOKEEPER_VERSION=$1 9 | 10 | echo "Installing Zookeeper" 11 | >&2 echo "Installing Zookeeper v"$ZOOKEEPER_VERSION "(quorum mode)" 12 | 13 | # Server 1 14 | mkdir $S1ROOT 15 | mkdir -p $S1ROOT/bin 16 | mkdir -p $S1ROOT/data 17 | echo 1 > $S1ROOT/data/myid 18 | 19 | # Server 2 20 | mkdir $S2ROOT 21 | mkdir -p $S2ROOT/bin 22 | mkdir -p $S2ROOT/data 23 | echo 2 > $S2ROOT/data/myid 24 | 25 | # Server 3 26 | mkdir $S3ROOT 27 | mkdir -p $S3ROOT/bin 28 | mkdir -p $S3ROOT/data 29 | echo 3 > $S3ROOT/data/myid 30 | 31 | # Download and build zookeeper version 32 | if [ ! -d $RELEASEDIR/ ]; then 33 | mkdir -p $RELEASEDIR 34 | fi 35 | 36 | cd $RELEASEDIR 37 | 38 | if [ ! -f $RELEASEDIR/release-$ZOOKEEPER_VERSION.tar.gz ]; then 39 | wget $2 40 | if [ -d zookeeper-$ZOOKEEPER_VERSION ]; then 41 | rm -rf zookeeper-$ZOOKEEPER_VERSION 42 | fi 43 | fi 44 | 45 | # Extract and build 46 | if [ ! -d zookeeper-$ZOOKEEPER_VERSION ]; then 47 | tar -zxf release-$ZOOKEEPER_VERSION.tar.gz 48 | mv zookeeper-release-$ZOOKEEPER_VERSION zookeeper-$ZOOKEEPER_VERSION 49 | cd zookeeper-$ZOOKEEPER_VERSION/ 50 | ant package 51 | cp build/zookeeper-$ZOOKEEPER_VERSION.jar zookeeper-$ZOOKEEPER_VERSION.jar 52 | cp -R build/lib/ lib 53 | fi 54 | 55 | # Configure S1 56 | cd $RELEASEDIR/zookeeper-$ZOOKEEPER_VERSION/ 57 | cp -R ../zookeeper-$ZOOKEEPER_VERSION $S1ROOT/bin/zookeeper 58 | chmod a+x $S1ROOT/bin/zookeeper/bin/zkServer.sh 59 | cd $S1ROOT/bin/zookeeper/conf 60 | cat > zoo.cfg < zoo.cfg < zoo.cfg <&2 echo "Finished installing v"$ZOOKEEPER_VERSION "(quorum mode)" 104 | >&2 echo "Starting Zookeeper v"$ZOOKEEPER_VERSION "(quorum mode)" 105 | 106 | >&2 echo "Starting server 1" 107 | cd $S1ROOT/bin/zookeeper/bin/ 108 | ./zkServer.sh start 109 | 110 | >&2 echo "Starting server 2" 111 | cd $S2ROOT/bin/zookeeper/bin/ 112 | ./zkServer.sh start 113 | 114 | >&2 echo "Starting server 3" 115 | cd $S3ROOT/bin/zookeeper/bin/ 116 | ./zkServer.sh start 117 | sleep 5 -------------------------------------------------------------------------------- /integration/src/main/resources/scripts/runStandaloneMode.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | HERE=$(pwd) 4 | BIN=$HERE/bin 5 | DATADIR=$HERE/data 6 | RELEASEDIR=$HERE/releases 7 | ZOOKEEPER_VERSION=$1 8 | 9 | >&2 echo "Installing Zookeeper v"$ZOOKEEPER_VERSION "(standalone mode)" 10 | mkdir -p $BIN 11 | mkdir -p $DATADIR 12 | if [ ! -d $RELEASEDIR/ ]; then 13 | mkdir -p $RELEASEDIR 14 | fi 15 | 16 | cd $RELEASEDIR 17 | 18 | if [ ! -f $RELEASEDIR/release-$ZOOKEEPER_VERSION.tar.gz ]; then 19 | wget $2 20 | if [ -d zookeeper-$ZOOKEEPER_VERSION ]; then 21 | rm -rf zookeeper-$ZOOKEEPER_VERSION 22 | fi 23 | fi 24 | 25 | if [ ! -d zookeeper-$ZOOKEEPER_VERSION ]; then 26 | tar -zxf release-$ZOOKEEPER_VERSION.tar.gz 27 | mv zookeeper-release-$ZOOKEEPER_VERSION zookeeper-$ZOOKEEPER_VERSION 28 | cd zookeeper-$ZOOKEEPER_VERSION/ 29 | ant package 30 | cp build/zookeeper-$ZOOKEEPER_VERSION.jar zookeeper-$ZOOKEEPER_VERSION.jar 31 | cp -R build/lib/ lib 32 | fi 33 | 34 | cd $RELEASEDIR/zookeeper-$ZOOKEEPER_VERSION/ 35 | cp -R ../zookeeper-$ZOOKEEPER_VERSION $BIN/zookeeper 36 | chmod a+x $BIN/zookeeper/bin/zkServer.sh 37 | cd $BIN/zookeeper/conf 38 | cat > zoo.cfg <&2 echo "Finished installing v"$ZOOKEEPER_VERSION "(standalone mode)" 46 | >&2 echo "Starting Zookeeper v"$ZOOKEEPER_VERSION "(standalone mode)" 47 | cd $BIN/zookeeper/bin/ 48 | ./zkServer.sh start 49 | sleep 5 -------------------------------------------------------------------------------- /integration/src/main/resources/scripts/stopAndCleanQuorumMode.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | HERE=$(pwd) 4 | S1ROOT=$HERE/s1 5 | S2ROOT=$HERE/s2 6 | S3ROOT=$HERE/s3 7 | ZOOKEEPER_VERSION=$1 8 | 9 | >&2 echo "Stopping server 1" 10 | cd $S1ROOT/bin/zookeeper/bin/ 11 | ./zkServer.sh stop 12 | sleep 5 13 | >&2 echo "Removing temp directories for server 1" 14 | rm -rf $S1ROOT 15 | 16 | >&2 echo "Stopping server 2" 17 | cd $S2ROOT/bin/zookeeper/bin/ 18 | ./zkServer.sh stop 19 | sleep 5 20 | >&2 echo "Removing temp directories for server 2" 21 | rm -rf $S2ROOT 22 | 23 | >&2 echo "Stopping server 3" 24 | cd $S3ROOT/bin/zookeeper/bin/ 25 | ./zkServer.sh stop 26 | sleep 10 27 | >&2 echo "Removing temp directories for server 3" 28 | rm -rf $S3ROOT -------------------------------------------------------------------------------- /integration/src/main/resources/scripts/stopAndCleanStandaloneMode.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | HERE=$(pwd) 4 | BIN=$HERE/bin 5 | DATADIR=$HERE/data 6 | ZOOKEEPER_VERSION=$1 7 | 8 | >&2 echo "Stopping server v"$ZOOKEEPER_VERSION "(standalone mode)" 9 | cd $BIN/zookeeper/bin/ 10 | ./zkServer.sh stop 11 | sleep 10 12 | >&2 echo "Removing temp directories" 13 | rm -rf $BIN 14 | rm -rf $DATADIR -------------------------------------------------------------------------------- /integration/src/main/resources/testConfig.csv: -------------------------------------------------------------------------------- 1 | 3.4.5;standalone quorum;v3_4 2 | 3.4.6;standalone quorum;v3_4 3 | 3.5.0;standalone quorum;v3_4 v3_5 -------------------------------------------------------------------------------- /integration/src/test/scala/com/twitter/finagle/exp/zookeeper/integration/quorum/QuorumIntegrationConfig.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.integration.quorum 2 | 3 | import com.twitter.finagle.exp.zookeeper.Zookeeper 4 | import com.twitter.finagle.exp.zookeeper.client.ZkClient 5 | import com.twitter.util.TimeConversions._ 6 | import com.twitter.util.{Await, Duration} 7 | import java.net.{BindException, ServerSocket} 8 | import org.scalatest.FunSuite 9 | 10 | trait QuorumIntegrationConfig extends FunSuite { 11 | val ipAddress: String = "127.0.0.1" 12 | val timeOut: Duration = 3000.milliseconds 13 | 14 | def isPortAvailable(port: Int): Boolean = try { 15 | val socket = new ServerSocket(port) 16 | socket.close() 17 | true 18 | } catch { 19 | case e: BindException => false 20 | } 21 | 22 | var client1: Option[ZkClient] = None 23 | var client2: Option[ZkClient] = None 24 | var client3: Option[ZkClient] = None 25 | 26 | def newClients() { 27 | assume(!isPortAvailable(2181), "A server is required for integration tests, see IntegrationConfig") 28 | assume(!isPortAvailable(2182), "A server is required for integration tests, see IntegrationConfig") 29 | assume(!isPortAvailable(2183), "A server is required for integration tests, see IntegrationConfig") 30 | client1 = { 31 | if (!isPortAvailable(2181)) 32 | Some( 33 | Zookeeper.client 34 | .withAutoReconnect() 35 | .withZkConfiguration(sessionTimeout = timeOut) 36 | .newRichClient(ipAddress + ":" + 2181) 37 | ) 38 | else 39 | None 40 | } 41 | client2 = { 42 | if (!isPortAvailable(2182)) 43 | Some( 44 | Zookeeper.client 45 | .withAutoReconnect() 46 | .withZkConfiguration(sessionTimeout = timeOut) 47 | .newRichClient(ipAddress + ":" + 2182) 48 | ) 49 | else 50 | None 51 | } 52 | client3 = { 53 | if (!isPortAvailable(2183)) 54 | Some( 55 | Zookeeper.client 56 | .withAutoReconnect() 57 | .withZkConfiguration(sessionTimeout = timeOut) 58 | .newRichClient(ipAddress + ":" + 2183) 59 | ) 60 | else 61 | None 62 | } 63 | } 64 | 65 | def connectClients() { 66 | Await.result { 67 | client1.get.connect() before client2.get.connect() before 68 | client3.get.connect() 69 | } 70 | } 71 | 72 | def disconnectClients() { 73 | Await.result { 74 | client1.get.disconnect() before client2.get.disconnect() before 75 | client3.get.disconnect() 76 | } 77 | } 78 | 79 | def closeServices() { 80 | Await.result { 81 | client1.get.close() before client2.get.close() before 82 | client3.get.close() 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /integration/src/test/scala/com/twitter/finagle/exp/zookeeper/integration/quorum/v3_4/command/CRUDTest.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.integration.quorum.v3_4.command 2 | 3 | import com.twitter.finagle.exp.zookeeper.NoNodeException 4 | import com.twitter.finagle.exp.zookeeper.ZookeeperDefs.CreateMode 5 | import com.twitter.finagle.exp.zookeeper.data.Ids 6 | import com.twitter.finagle.exp.zookeeper.integration.quorum.QuorumIntegrationConfig 7 | import com.twitter.util.Await 8 | import java.util 9 | import org.junit.runner.RunWith 10 | import org.scalatest.FunSuite 11 | import org.scalatest.junit.JUnitRunner 12 | 13 | @RunWith(classOf[JUnitRunner]) 14 | class CRUDTest extends FunSuite with QuorumIntegrationConfig { 15 | test("get the same node on all the clients") { 16 | newClients() 17 | connectClients() 18 | 19 | val ret = Await.result { 20 | for { 21 | _ <- client1.get.create( 22 | "/node1", 23 | "HELLO".getBytes, 24 | Ids.OPEN_ACL_UNSAFE, 25 | CreateMode.EPHEMERAL 26 | ) 27 | get1 <- client1.get.getData("/node1") 28 | _ <- client2.get.sync("/node1") 29 | get2 <- client2.get.getData("/node1") 30 | _ <- client3.get.sync("/node1") 31 | get3 <- client3.get.getData("/node1") 32 | } yield (get1, get2, get3) 33 | } 34 | 35 | val (get1, get2, get3) = ret 36 | assert(get1.stat === get2.stat) 37 | assert(get2.stat === get3.stat) 38 | 39 | disconnectClients() 40 | closeServices() 41 | } 42 | 43 | test("set node") { 44 | newClients() 45 | connectClients() 46 | 47 | val ret = Await.result { 48 | for { 49 | _ <- client1.get.create( 50 | "/node1", 51 | "HELLO".getBytes, 52 | Ids.OPEN_ACL_UNSAFE, 53 | CreateMode.EPHEMERAL 54 | ) 55 | _ <- client2.get.setData("/node1", "CHANGED".getBytes, -1) 56 | get <- client2.get.getData("/node1") 57 | _ <- client3.get.setData("/node1", "CHANGED".getBytes, -1) 58 | _ <- client3.get.sync("/node1") 59 | get1 <- client3.get.getData("/node1") 60 | } yield (get, get1) 61 | } 62 | 63 | val (get, get1) = ret 64 | assert(util.Arrays.equals(get.data, get1.data)) 65 | 66 | disconnectClients() 67 | closeServices() 68 | } 69 | 70 | test("create, getData, setData, delete") { 71 | newClients() 72 | connectClients() 73 | 74 | val ret = Await.result { 75 | for { 76 | _ <- client1.get.create( 77 | "/node1", 78 | "HELLO".getBytes, 79 | Ids.OPEN_ACL_UNSAFE, 80 | CreateMode.EPHEMERAL 81 | ) 82 | _ <- client2.get.setData("/node1", "CHANGED".getBytes, -1) 83 | _ <- client1.get.sync("/node1") 84 | get <- client1.get.getData("/node1") 85 | _ <- client3.get.setData("/node1", "CHANGED".getBytes, -1) 86 | _ <- client3.get.sync("/node1") 87 | get1 <- client3.get.getData("/node1") 88 | _ <- client3.get.delete("/node1", -1) 89 | } yield (get, get1) 90 | } 91 | 92 | val (get, get1) = ret 93 | assert(util.Arrays.equals(get.data, get1.data)) 94 | 95 | intercept[NoNodeException] { 96 | Await.result { 97 | client1.get.getData("/node1") 98 | } 99 | } 100 | 101 | disconnectClients() 102 | closeServices() 103 | } 104 | } -------------------------------------------------------------------------------- /integration/src/test/scala/com/twitter/finagle/exp/zookeeper/integration/quorum/v3_4/command/WatcherTest.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.integration.quorum.v3_4.command 2 | 3 | import com.twitter.finagle.exp.zookeeper.ZookeeperDefs.CreateMode 4 | import com.twitter.finagle.exp.zookeeper.data.Ids 5 | import com.twitter.finagle.exp.zookeeper.integration.quorum.QuorumIntegrationConfig 6 | import com.twitter.finagle.exp.zookeeper.watcher.Watch 7 | import com.twitter.util.Await 8 | import org.junit.runner.RunWith 9 | import org.scalatest.FunSuite 10 | import org.scalatest.junit.JUnitRunner 11 | 12 | @RunWith(classOf[JUnitRunner]) 13 | class WatcherTest extends FunSuite with QuorumIntegrationConfig { 14 | test("Basic watcher") { 15 | newClients() 16 | connectClients() 17 | 18 | val res = for { 19 | _ <- client1.get.create("/zookeeper/test", "HELLO".getBytes, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL) 20 | exist <- client2.get.exists("/zookeeper/test", true) 21 | setdata <- client3.get.setData("/zookeeper/test", "CHANGE IS GOOD1".getBytes, -1) 22 | } yield (exist, setdata) 23 | 24 | val (exists, setData) = Await.result(res) 25 | Await.result(exists.watcher.get.event) 26 | 27 | exists.watcher.get.event onSuccess { rep => 28 | assert(rep.typ === Watch.EventType.NODE_DATA_CHANGED) 29 | assert(rep.state === Watch.EventState.SYNC_CONNECTED) 30 | assert(rep.path === "/zookeeper/test") 31 | } 32 | 33 | disconnectClients() 34 | closeServices() 35 | } 36 | 37 | test("Create, getData with watches , SetData") { 38 | newClients() 39 | connectClients() 40 | 41 | val res = for { 42 | _ <- client1.get.create("/zookeeper/test", "HELLO".getBytes, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL) 43 | _ <- client3.get.sync("/zookeeper/test") 44 | getdata <- client3.get.getData("/zookeeper/test", true) 45 | _ <- client2.get.setData("/zookeeper/test", "CHANGE IS GOOD1".getBytes, -1) 46 | } yield getdata 47 | 48 | 49 | val ret = Await.result(res) 50 | Await.result(ret.watcher.get.event) 51 | ret.watcher.get.event onSuccess { rep => 52 | assert(rep.typ === Watch.EventType.NODE_DATA_CHANGED) 53 | assert(rep.state === Watch.EventState.SYNC_CONNECTED) 54 | assert(rep.path === "/zookeeper/test") 55 | } 56 | 57 | disconnectClients() 58 | closeServices() 59 | } 60 | 61 | test("Create, getChildren with watches , delete child") { 62 | newClients() 63 | connectClients() 64 | 65 | val res = for { 66 | _ <- client3.get.create("/zookeeper/test", "HELLO".getBytes, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT) 67 | _ <- client3.get.create("/zookeeper/test/hello", "HELLO".getBytes, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL) 68 | _ <- client2.get.sync("/zookeeper/test") 69 | getchild <- client2.get.getChildren("/zookeeper/test", true) 70 | _ <- client3.get.delete("/zookeeper/test/hello", -1) 71 | _ <- client1.get.delete("/zookeeper/test", -1) 72 | } yield getchild 73 | 74 | 75 | val ret = Await.result(res) 76 | Await.result(ret.watcher.get.event) 77 | ret.watcher.get.event onSuccess { rep => 78 | assert(rep.typ === Watch.EventType.NODE_CHILDREN_CHANGED) 79 | assert(rep.state === Watch.EventState.SYNC_CONNECTED) 80 | assert(rep.path === "/zookeeper/test") 81 | } 82 | 83 | disconnectClients() 84 | closeServices() 85 | } 86 | 87 | test("Create, getChildren2 with watches , delete child") { 88 | newClients() 89 | connectClients() 90 | 91 | val res = for { 92 | _ <- client2.get.create("/zookeeper/test", "HELLO".getBytes, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT) 93 | _ <- client1.get.create("/zookeeper/test/hello", "HELLO".getBytes, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL) 94 | _ <- client3.get.sync("/zookeeper/test") 95 | getchild <- client3.get.getChildren2("/zookeeper/test", true) 96 | _ <- client1.get.delete("/zookeeper/test/hello", -1) 97 | _ <- client1.get.delete("/zookeeper/test", -1) 98 | } yield getchild 99 | 100 | 101 | val ret = Await.result(res) 102 | Await.result(ret.watcher.get.event) 103 | ret.watcher.get.event onSuccess { rep => 104 | assert(rep.typ === Watch.EventType.NODE_CHILDREN_CHANGED) 105 | assert(rep.state === Watch.EventState.SYNC_CONNECTED) 106 | assert(rep.path === "/zookeeper/test") 107 | } 108 | 109 | disconnectClients() 110 | closeServices() 111 | } 112 | 113 | test("Create, getChildren(watcher on parent) exists(watcher on child) , delete child") { 114 | newClients() 115 | connectClients() 116 | 117 | val res = for { 118 | _ <- client1.get.create("/zookeeper/test", "HELLO".getBytes, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT) 119 | _ <- client2.get.create("/zookeeper/test/hello", "HELLO".getBytes, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL) 120 | _ <- client3.get.create("/zookeeper/test/hella", "HELLO".getBytes, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL) 121 | _ <- client2.get.sync("/zookeeper/test") 122 | getchild <- client2.get.getChildren("/zookeeper/test", true) 123 | exist <- client1.get.exists("/zookeeper/test/hello", true) 124 | _ <- client2.get.sync("/zookeeper/test/hella") 125 | getdata <- client2.get.getData("/zookeeper/test/hella", true) 126 | _ <- client3.get.delete("/zookeeper/test/hello", -1) 127 | _ <- client1.get.sync("/zookeeper/test") 128 | _ <- client1.get.getChildren("/zookeeper/test", true) 129 | _ <- client2.get.delete("/zookeeper/test/hella", -1) 130 | _ <- client1.get.delete("/zookeeper/test", -1) 131 | } yield (getchild, exist) 132 | 133 | 134 | val (getChildrenRep, existsRep) = Await.result(res) 135 | 136 | Await.result(existsRep.watcher.get.event) 137 | existsRep.watcher.get.event onSuccess { rep => 138 | assert(rep.typ === Watch.EventType.NODE_DELETED) 139 | assert(rep.state === Watch.EventState.SYNC_CONNECTED) 140 | assert(rep.path === "/zookeeper/test/hello") 141 | } 142 | 143 | Await.result(getChildrenRep.watcher.get.event) 144 | getChildrenRep.watcher.get.event onSuccess { rep => 145 | assert(rep.typ === Watch.EventType.NODE_CHILDREN_CHANGED) 146 | assert(rep.state === Watch.EventState.SYNC_CONNECTED) 147 | assert(rep.path === "/zookeeper/test") 148 | } 149 | 150 | disconnectClients() 151 | closeServices() 152 | } 153 | } -------------------------------------------------------------------------------- /integration/src/test/scala/com/twitter/finagle/exp/zookeeper/integration/quorum/v3_4/extra/EnsembleTest.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.integration.quorum.v3_4.extra 2 | 3 | import com.twitter.finagle.exp.zookeeper.integration.quorum.QuorumIntegrationConfig 4 | import com.twitter.util.Await 5 | import org.junit.runner.RunWith 6 | import org.scalatest.FunSuite 7 | import org.scalatest.junit.JUnitRunner 8 | 9 | @RunWith(classOf[JUnitRunner]) 10 | class EnsembleTest extends FunSuite with QuorumIntegrationConfig { 11 | test("change host") { 12 | newClients() 13 | connectClients() 14 | 15 | val oldCli = client1.get.connectionManager.currentHost.get 16 | Await.ready{ 17 | client1.get.changeHost(Some("127.0.0.1:2182")) 18 | } 19 | assert(client1.get.connectionManager.currentHost.isDefined) 20 | assert(client1.get.connectionManager.currentHost.get === "127.0.0.1:2182") 21 | assert(client1.get.connectionManager.currentHost.get != oldCli) 22 | 23 | val oldCli2 = client2.get.connectionManager.currentHost.get 24 | Await.ready{ 25 | client2.get.changeHost(Some("127.0.0.1:2181")) 26 | } 27 | assert(client2.get.connectionManager.currentHost.isDefined) 28 | assert(client2.get.connectionManager.currentHost.get === "127.0.0.1:2181") 29 | assert(client2.get.connectionManager.currentHost.get != oldCli2) 30 | 31 | val oldCli3 = client3.get.connectionManager.currentHost.get 32 | Await.ready{ 33 | client3.get.changeHost(Some("127.0.0.1:2182")) 34 | } 35 | assert(client3.get.connectionManager.currentHost.isDefined) 36 | assert(client3.get.connectionManager.currentHost.get === "127.0.0.1:2182") 37 | assert(client3.get.connectionManager.currentHost.get != oldCli3) 38 | 39 | disconnectClients() 40 | closeServices() 41 | } 42 | 43 | test("add hosts") { 44 | newClients() 45 | connectClients() 46 | 47 | client1.get.addHosts("127.0.0.1:2183") 48 | assert(client1.get.connectionManager.hostProvider.serverList.contains("127.0.0.1:2183")) 49 | val oldCli = client1.get.connectionManager.currentHost.get 50 | Await.ready{ 51 | client1.get.changeHost() 52 | } 53 | assert(client1.get.connectionManager.currentHost.isDefined) 54 | assert(client1.get.connectionManager.currentHost.get === "127.0.0.1:2183") 55 | assert(client1.get.connectionManager.currentHost.get != oldCli) 56 | 57 | disconnectClients() 58 | closeServices() 59 | } 60 | 61 | test("remove hosts") { 62 | newClients() 63 | connectClients() 64 | 65 | client1.get.addHosts("127.0.0.1:2183") 66 | val oldCli = client1.get.connectionManager.currentHost.get 67 | Await.result{ 68 | client1.get.removeHosts("127.0.0.1:2181") 69 | } 70 | assert(client1.get.connectionManager.currentHost.isDefined) 71 | assert(client1.get.connectionManager.currentHost.get === "127.0.0.1:2183") 72 | assert(client1.get.connectionManager.currentHost.get != oldCli) 73 | assert(!client1.get.connectionManager.hostProvider.serverList.contains("127.0.0.1:2181")) 74 | 75 | disconnectClients() 76 | closeServices() 77 | } 78 | } -------------------------------------------------------------------------------- /integration/src/test/scala/com/twitter/finagle/exp/zookeeper/integration/quorum/v3_5/command/GetConfigTest.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.integration.quorum.v3_5.command 2 | 3 | import java.util 4 | 5 | import com.twitter.finagle.exp.zookeeper.integration.quorum.QuorumIntegrationConfig 6 | import com.twitter.util.Await 7 | import org.scalatest.FunSuite 8 | 9 | class GetConfigTest extends FunSuite with QuorumIntegrationConfig { 10 | test("Basic get config") { 11 | newClients() 12 | connectClients() 13 | 14 | val rep = Await.result(client1.get.getConfig()) 15 | assert(rep.data.size > 0) 16 | 17 | disconnectClients() 18 | closeServices() 19 | } 20 | 21 | test("All clients have the same configuration") { 22 | newClients() 23 | connectClients() 24 | 25 | val rep = Await.result(client1.get.getConfig()) 26 | val rep2 = Await.result(client2.get.getConfig()) 27 | val rep3 = Await.result(client3.get.getConfig()) 28 | 29 | assert(util.Arrays.equals(rep.data, rep2.data)) 30 | assert(util.Arrays.equals(rep3.data, rep2.data)) 31 | 32 | disconnectClients() 33 | closeServices() 34 | } 35 | } -------------------------------------------------------------------------------- /integration/src/test/scala/com/twitter/finagle/exp/zookeeper/integration/quorum/v3_5/command/ReconfigTest.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.integration.quorum.v3_5.command 2 | 3 | import com.twitter.finagle.exp.zookeeper.integration.quorum.QuorumIntegrationConfig 4 | import org.scalatest.FunSuite 5 | 6 | class ReconfigTest extends FunSuite with QuorumIntegrationConfig { 7 | ignore("TODO reconfig") { 8 | newClients() 9 | connectClients() 10 | 11 | // TODO ReconfigTest.java 12 | disconnectClients() 13 | closeServices() 14 | } 15 | } -------------------------------------------------------------------------------- /integration/src/test/scala/com/twitter/finagle/exp/zookeeper/integration/standalone/StandaloneIntegrationConfig.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.integration.standalone 2 | 3 | import com.twitter.finagle.exp.zookeeper.Zookeeper 4 | import com.twitter.finagle.exp.zookeeper.client.ZkClient 5 | import com.twitter.util.TimeConversions._ 6 | import com.twitter.util.{Await, Duration} 7 | import java.net.{BindException, ServerSocket} 8 | import org.scalatest.FunSuite 9 | 10 | trait StandaloneIntegrationConfig extends FunSuite { 11 | val ipAddress: String = "127.0.0.1" 12 | val port: Int = 2181 13 | val timeOut: Duration = 3000.milliseconds 14 | 15 | def isPortAvailable: Boolean = try { 16 | val socket = new ServerSocket(port) 17 | socket.close() 18 | true 19 | } catch { 20 | case e: BindException => false 21 | } 22 | 23 | var client: Option[ZkClient] = None 24 | 25 | def newClient() { 26 | assume(!isPortAvailable, "A server is required for integration tests, see IntegrationConfig") 27 | client = { 28 | if (!isPortAvailable) 29 | Some( 30 | Zookeeper.client 31 | .withAutoReconnect() 32 | .withZkConfiguration(sessionTimeout = timeOut) 33 | .newRichClient(ipAddress + ":" + port) 34 | ) 35 | else 36 | None 37 | } 38 | } 39 | 40 | def connect() = { Await.result(client.get.connect()) } 41 | def disconnect() = { Await.result(client.get.disconnect()) } 42 | } -------------------------------------------------------------------------------- /integration/src/test/scala/com/twitter/finagle/exp/zookeeper/integration/standalone/v3_4/command/AclTest.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.integration.standalone.v3_4.command 2 | 3 | import com.twitter.finagle.exp.zookeeper.ZookeeperDefs.CreateMode 4 | import com.twitter.finagle.exp.zookeeper.data.ACL.Perms 5 | import com.twitter.finagle.exp.zookeeper.data.{ACL, Id, Ids} 6 | import com.twitter.finagle.exp.zookeeper.integration.standalone.StandaloneIntegrationConfig 7 | import com.twitter.finagle.exp.zookeeper.{InvalidAclException, NoAuthException} 8 | import com.twitter.util.Await 9 | import org.junit.runner.RunWith 10 | import org.scalatest.FunSuite 11 | import org.scalatest.junit.JUnitRunner 12 | 13 | @RunWith(classOf[JUnitRunner]) 14 | class AclTest extends FunSuite with StandaloneIntegrationConfig { 15 | test("Basic add auth") { 16 | newClient() 17 | connect() 18 | 19 | client.get.addAuth("digest", "pat:test".getBytes) 20 | client.get.setACL("/", Ids.CREATOR_ALL_ACL, -1) 21 | 22 | disconnect() 23 | Await.ready(client.get.close()) 24 | } 25 | 26 | test("acl count") { 27 | newClient() 28 | connect() 29 | 30 | val acls = Seq[ACL]( 31 | ACL(Perms.READ, Ids.ANYONE_ID_UNSAFE), 32 | ACL(Perms.ALL, Ids.AUTH_IDS), 33 | ACL(Perms.READ, Ids.ANYONE_ID_UNSAFE), 34 | ACL(Perms.ALL, Ids.AUTH_IDS) 35 | ) 36 | 37 | val rep = for { 38 | _ <- client.get.addAuth("digest", "pat:test".getBytes) 39 | _ <- client.get.setACL("/", Ids.CREATOR_ALL_ACL, -1) 40 | _ <- client.get.create("/path", "hello".getBytes, acls, CreateMode.EPHEMERAL) 41 | acl <- client.get.getACL("/path") 42 | } yield acl 43 | 44 | assert(Await.result(rep).acl.size === 2) 45 | 46 | disconnect() 47 | Await.ready(client.get.close()) 48 | } 49 | 50 | test("root acl is correct") { 51 | newClient() 52 | connect() 53 | 54 | Await.result( 55 | for { 56 | _ <- client.get.addAuth("digest", "pat:test".getBytes) 57 | _ <- client.get.setACL("/", Ids.CREATOR_ALL_ACL, -1) 58 | _ <- client.get.getData("/") 59 | } yield None 60 | ) 61 | 62 | disconnect() 63 | Await.ready(client.get.close()) 64 | 65 | newClient() 66 | connect() 67 | 68 | intercept[NoAuthException] { 69 | Await.result(client.get.getData("/")) 70 | } 71 | intercept[InvalidAclException] { 72 | Await.result(client.get.create("/apps", "hello".getBytes, Ids.CREATOR_ALL_ACL, 73 | CreateMode.PERSISTENT)) 74 | } 75 | 76 | Await.result(client.get.addAuth("digest", "world:anyone".getBytes)) 77 | 78 | intercept[NoAuthException] { 79 | Await.result(client.get.create("/apps", "hello".getBytes, Ids.CREATOR_ALL_ACL, 80 | CreateMode.PERSISTENT)) 81 | } 82 | 83 | disconnect() 84 | connect() 85 | 86 | Await.result( 87 | for { 88 | _ <- client.get.addAuth("digest", "pat:test".getBytes) 89 | _ <- client.get.getData("/") 90 | _ <- client.get.create( 91 | "/apps", 92 | "hello".getBytes, 93 | Ids.CREATOR_ALL_ACL, 94 | CreateMode.PERSISTENT 95 | ) 96 | _ <- client.get.delete("/apps", -1) 97 | _ <- client.get.setACL("/", Ids.OPEN_ACL_UNSAFE, -1) 98 | } yield None 99 | ) 100 | 101 | disconnect() 102 | connect() 103 | 104 | Await.result( 105 | client.get.getData("/").unit before 106 | client.get.create( 107 | "/apps", 108 | "hello".getBytes, 109 | Ids.OPEN_ACL_UNSAFE, 110 | CreateMode.PERSISTENT 111 | ) 112 | ) 113 | 114 | intercept[InvalidAclException] { 115 | Await.result(client.get.create( 116 | "/apps", 117 | "hello".getBytes, 118 | Ids.CREATOR_ALL_ACL, 119 | CreateMode.PERSISTENT)) 120 | } 121 | 122 | Await.ready( 123 | for { 124 | _ <- client.get.delete("/apps", -1) 125 | _ <- client.get.addAuth("digest", "world:anyone".getBytes) 126 | _ <- client.get.create( 127 | "/apps", "hello".getBytes, Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT) 128 | _ <- client.get.disconnect() 129 | _ <- client.get.connect() 130 | _ <- client.get.delete("/apps", -1) 131 | } yield None 132 | ) 133 | 134 | disconnect() 135 | Await.ready(client.get.close()) 136 | } 137 | 138 | test("global acl test") { 139 | newClient() 140 | connect() 141 | 142 | intercept[InvalidAclException] { 143 | Await.result { 144 | client.get.create( 145 | "/acltest", 146 | "".getBytes, 147 | Ids.CREATOR_ALL_ACL, 148 | CreateMode.PERSISTENT) 149 | } 150 | } 151 | 152 | val acls = Seq[ACL]( 153 | ACL(Perms.ALL | Perms.ADMIN, Ids.AUTH_IDS), 154 | ACL(Perms.ALL | Perms.ADMIN, Id("ip", "127.0.0.1/8")) 155 | ) 156 | 157 | intercept[IllegalArgumentException] { 158 | Await.result { 159 | client.get.create("/acltest", "".getBytes, acls, CreateMode.PERSISTENT) 160 | } 161 | } 162 | 163 | Await.result { 164 | client.get.addAuth("digest", "ben:passwd".getBytes) before 165 | client.get.create("/acltest", "".getBytes, Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT) 166 | } 167 | 168 | disconnect() 169 | Await.ready(client.get.close()) 170 | 171 | newClient() 172 | connect() 173 | 174 | Await.result { 175 | client.get.addAuth("digest", "ben:passwd2".getBytes) 176 | } 177 | 178 | intercept[NoAuthException] { 179 | Await.result { 180 | client.get.getData("/acltest") 181 | } 182 | } 183 | 184 | Await.result { 185 | client.get.addAuth("digest", "ben:passwd".getBytes) before 186 | client.get.getData("/acltest").unit before 187 | client.get.setACL("/acltest", Ids.OPEN_ACL_UNSAFE, -1) 188 | } 189 | 190 | disconnect() 191 | Await.ready(client.get.close()) 192 | 193 | newClient() 194 | connect() 195 | 196 | val rep = Await.result { 197 | for { 198 | _ <- client.get.getData("/acltest") 199 | aclret <- client.get.getACL("/acltest") 200 | } yield aclret 201 | } 202 | 203 | assert(rep.acl.size === 1) 204 | assert(rep.acl === Ids.OPEN_ACL_UNSAFE) 205 | 206 | Await.ready { 207 | client.get.delete("/acltest", -1) 208 | } 209 | 210 | disconnect() 211 | Await.ready(client.get.close()) 212 | } 213 | } -------------------------------------------------------------------------------- /integration/src/test/scala/com/twitter/finagle/exp/zookeeper/integration/standalone/v3_4/command/AuthTest.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.integration.standalone.v3_4.command 2 | 3 | import com.twitter.finagle.exp.zookeeper.ZookeeperDefs.CreateMode 4 | import com.twitter.finagle.exp.zookeeper.data.Ids 5 | import com.twitter.finagle.exp.zookeeper.integration.standalone.StandaloneIntegrationConfig 6 | import com.twitter.finagle.exp.zookeeper.{AuthFailedException, NoAuthException} 7 | import com.twitter.util.Await 8 | import org.junit.runner.RunWith 9 | import org.scalatest.FunSuite 10 | import org.scalatest.junit.JUnitRunner 11 | 12 | @RunWith(classOf[JUnitRunner]) 13 | class AuthTest extends FunSuite with StandaloneIntegrationConfig { 14 | test("Bad Auth Notifies Watch") { 15 | newClient() 16 | connect() 17 | 18 | intercept[AuthFailedException] { 19 | Await.result(client.get.addAuth("FOO", "BAR".getBytes)) 20 | } 21 | 22 | Await.ready(client.get.close()) 23 | } 24 | 25 | test("Bad auth and other commands") { 26 | newClient() 27 | connect() 28 | 29 | intercept[Exception] { 30 | Await.result(client.get.exists("/foobar")) 31 | } 32 | 33 | intercept[Exception] { 34 | Await.result(client.get.getData("/path1")) 35 | } 36 | 37 | disconnect() 38 | Await.ready(client.get.close()) 39 | } 40 | 41 | test("Auth super test 1") { 42 | newClient() 43 | connect() 44 | 45 | Await.result { 46 | for { 47 | _ <- client.get.addAuth("digest", "pat:pass".getBytes) 48 | create <- client.get.create( 49 | "/path1", 50 | "".getBytes, 51 | Ids.CREATOR_ALL_ACL, 52 | CreateMode.PERSISTENT 53 | ) 54 | } yield create 55 | } 56 | 57 | disconnect() 58 | Await.ready(client.get.close()) 59 | 60 | newClient() 61 | connect() 62 | 63 | Await.result { 64 | for { 65 | _ <- client.get.addAuth("digest", "pat:pass".getBytes) 66 | _ <- client.get.getData("/path1") 67 | acl <- client.get.setACL("/path1", Ids.READ_ACL_UNSAFE, -1) 68 | _ <- client.get.sync("/path1") 69 | _ <- client.get.delete("/path1", -1) 70 | } yield acl 71 | } 72 | 73 | disconnect() 74 | Await.ready(client.get.close()) 75 | } 76 | 77 | test("Auth super test 2") { 78 | newClient() 79 | connect() 80 | 81 | val rep = Await.result { 82 | for { 83 | _ <- client.get.addAuth("digest", "pat:pass".getBytes) 84 | create <- client.get.create("/path10", "h".getBytes, Ids.CREATOR_ALL_ACL, 85 | CreateMode.PERSISTENT) 86 | } yield create 87 | } 88 | 89 | assert(rep === "/path10") 90 | disconnect() 91 | Await.ready(client.get.close()) 92 | 93 | newClient() 94 | connect() 95 | 96 | intercept[NoAuthException] { 97 | Await.result { 98 | client.get.getData("/path10") 99 | } 100 | } 101 | 102 | disconnect() 103 | Await.ready(client.get.close()) 104 | 105 | newClient() 106 | connect() 107 | 108 | intercept[NoAuthException] { 109 | Await.result { 110 | client.get.addAuth("digest", "pat:pass2".getBytes) before 111 | client.get.getData("/path10") 112 | } 113 | } 114 | 115 | disconnect() 116 | Await.ready(client.get.close()) 117 | 118 | newClient() 119 | connect() 120 | 121 | intercept[NoAuthException] { 122 | Await.result { 123 | client.get.addAuth("digest", "super:test2".getBytes) before 124 | client.get.getData("/path10") 125 | } 126 | } 127 | 128 | disconnect() 129 | Await.ready(client.get.close()) 130 | 131 | newClient() 132 | connect() 133 | 134 | Await.result { 135 | client.get.addAuth("digest", "pat:pass".getBytes) before 136 | client.get.getData("/path10").unit before client.get.delete("/path10", -1) 137 | } 138 | 139 | disconnect() 140 | Await.ready(client.get.close()) 141 | } 142 | } -------------------------------------------------------------------------------- /integration/src/test/scala/com/twitter/finagle/exp/zookeeper/integration/standalone/v3_4/command/ChrootTest.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.integration.standalone.v3_4.command 2 | 3 | import java.util 4 | 5 | import com.twitter.finagle.exp.zookeeper.Zookeeper 6 | import com.twitter.finagle.exp.zookeeper.ZookeeperDefs.CreateMode 7 | import com.twitter.finagle.exp.zookeeper.data.Ids 8 | import com.twitter.finagle.exp.zookeeper.integration.standalone.StandaloneIntegrationConfig 9 | import com.twitter.util.Await 10 | import org.junit.runner.RunWith 11 | import org.scalatest.FunSuite 12 | import org.scalatest.junit.JUnitRunner 13 | 14 | @RunWith(classOf[JUnitRunner]) 15 | class ChrootTest extends FunSuite with StandaloneIntegrationConfig { 16 | test("Chroot works") { 17 | val clientWCh = Some( 18 | Zookeeper.client 19 | .withAutoReconnect() 20 | .withZkConfiguration(chroot = "/ch1") 21 | .newRichClient(ipAddress + ":" + port) 22 | ) 23 | val client = Some( 24 | Zookeeper.client 25 | .withAutoReconnect() 26 | .newRichClient(ipAddress + ":" + port) 27 | ) 28 | 29 | Await.ready(client.get.connect()) 30 | Await.result(client.get.create( 31 | "/ch1", 32 | "hello".getBytes, 33 | Ids.OPEN_ACL_UNSAFE, 34 | CreateMode.PERSISTENT 35 | )) 36 | 37 | Await.ready(clientWCh.get.connect()) 38 | 39 | val rep = for { 40 | _ <- clientWCh.get.create( 41 | "/ch2", 42 | "hello".getBytes, 43 | Ids.OPEN_ACL_UNSAFE, 44 | CreateMode.PERSISTENT) 45 | exists <- client.get.exists("/ch1", true) 46 | exists2 <- client.get.exists("/ch1/ch2", true) 47 | existsW <- clientWCh.get.exists("/ch2", true) 48 | getChildren <- client.get.getChildren("/ch1", true) 49 | getChildrenW <- clientWCh.get.getChildren("/", true) 50 | _ <- client.get.setData("/ch1", "HELLO".getBytes, -1) 51 | _ <- clientWCh.get.setData("/ch2", "HELLO1".getBytes, -1) 52 | getData <- client.get.getData("/ch1", false) 53 | getData2 <- client.get.getData("/ch1/ch2", false) 54 | getDataW <- clientWCh.get.getData("/ch2", false) 55 | _ <- clientWCh.get.delete("/ch2", -1) 56 | _ <- client.get.delete("/ch1", -1) 57 | } yield (exists, exists2, existsW, getChildren, 58 | getChildrenW, getData, 59 | getData2, getDataW) 60 | 61 | val (exists, exists2, existsW, getChildren, 62 | getChildrenW, getData, 63 | getData2, getDataW) = Await.result(rep) 64 | 65 | assert(exists.stat.isDefined) 66 | assert(exists.watcher.isDefined) 67 | assert(exists2.stat.isDefined) 68 | assert(exists2.watcher.isDefined) 69 | assert(existsW.stat.isDefined) 70 | assert(existsW.watcher.isDefined) 71 | 72 | Await.ready(exists.watcher.get.event) 73 | Await.ready(exists2.watcher.get.event) 74 | Await.ready(existsW.watcher.get.event) 75 | 76 | assert(util.Arrays.equals(getData.data, "HELLO".getBytes)) 77 | assert(util.Arrays.equals(getData2.data, "HELLO1".getBytes)) 78 | assert(util.Arrays.equals(getDataW.data, "HELLO1".getBytes)) 79 | 80 | assert(getChildren.watcher.isDefined) 81 | assert(getChildrenW.watcher.isDefined) 82 | 83 | Await.ready(getChildren.watcher.get.event) 84 | Await.ready(getChildrenW.watcher.get.event) 85 | 86 | Await.ready(clientWCh.get.disconnect()) 87 | Await.ready(client.get.disconnect()) 88 | 89 | Await.ready(clientWCh.get.close()) 90 | Await.ready(client.get.close()) 91 | } 92 | } -------------------------------------------------------------------------------- /integration/src/test/scala/com/twitter/finagle/exp/zookeeper/integration/standalone/v3_4/command/TransactionTest.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.integration.standalone.v3_4.command 2 | 3 | import com.twitter.finagle.exp.zookeeper.ZookeeperDefs.CreateMode 4 | import com.twitter.finagle.exp.zookeeper._ 5 | import com.twitter.finagle.exp.zookeeper.data.Ids 6 | import com.twitter.finagle.exp.zookeeper.integration.standalone.StandaloneIntegrationConfig 7 | import com.twitter.util.Await 8 | import org.junit.runner.RunWith 9 | import org.scalatest.FunSuite 10 | import org.scalatest.junit.JUnitRunner 11 | 12 | @RunWith(classOf[JUnitRunner]) 13 | class TransactionTest extends FunSuite with StandaloneIntegrationConfig { 14 | test("Small create Transaction works") { 15 | newClient() 16 | connect() 17 | 18 | val opList = Seq( 19 | CreateRequest( 20 | "/zookeeper/hello", "TRANS".getBytes, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL), 21 | CreateRequest( 22 | "/zookeeper/world", "TRANS".getBytes, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL)) 23 | 24 | val res = client.get.transaction(opList) 25 | val finalRep = Await.result(res) 26 | 27 | assert(finalRep.responseList(0).asInstanceOf[CreateResponse].path === "/zookeeper/hello") 28 | assert(finalRep.responseList(1).asInstanceOf[CreateResponse].path === "/zookeeper/world") 29 | 30 | disconnect() 31 | Await.ready(client.get.close()) 32 | } 33 | 34 | test("Create and delete") { 35 | newClient() 36 | connect() 37 | 38 | val opList = Seq( 39 | CreateRequest( 40 | "/zookeeper/hello", "TRANS".getBytes, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL), 41 | DeleteRequest("/zookeeper/hello", -1)) 42 | 43 | val res = client.get.transaction(opList) 44 | val finalRep = Await.result(res) 45 | 46 | assert(finalRep.responseList(0).asInstanceOf[CreateResponse].path === "/zookeeper/hello") 47 | 48 | disconnect() 49 | Await.ready(client.get.close()) 50 | } 51 | 52 | test("Create , set and delete with error") { 53 | newClient() 54 | connect() 55 | 56 | val opList = Seq( 57 | CreateRequest( 58 | "/zookeeper/hello", "TRANS".getBytes, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL), 59 | SetDataRequest("/zookeeper/hello", "changing".getBytes, -1), 60 | DeleteRequest("/zookeeper/hell", -1) 61 | ) 62 | 63 | val res = client.get.transaction(opList) 64 | val finalRep = Await.result(res) 65 | 66 | assert(finalRep.responseList(0).asInstanceOf[ErrorResponse].exception.isInstanceOf[OkException]) 67 | assert(finalRep.responseList(1).asInstanceOf[ErrorResponse].exception.isInstanceOf[OkException]) 68 | assert(finalRep.responseList(2).asInstanceOf[ErrorResponse].exception.isInstanceOf[NoNodeException]) 69 | 70 | disconnect() 71 | Await.ready(client.get.close()) 72 | } 73 | 74 | test("Create, checkVersion") { 75 | newClient() 76 | connect() 77 | 78 | val opList = Seq( 79 | CreateRequest( 80 | "/zookeeper/hello", "TRANS".getBytes, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL), 81 | CheckVersionRequest("/zookeeper/hello", 0) 82 | ) 83 | 84 | val res = client.get.transaction(opList) 85 | val finalRep = Await.result(res) 86 | 87 | assert(finalRep.responseList(0).isInstanceOf[CreateResponse]) 88 | assert(finalRep.responseList(1).isInstanceOf[EmptyResponse]) 89 | 90 | disconnect() 91 | Await.ready(client.get.close()) 92 | } 93 | 94 | test("Create, set and checkVersion") { 95 | newClient() 96 | connect() 97 | 98 | val opList = Seq( 99 | CreateRequest( 100 | "/zookeeper/hello", "TRANS".getBytes, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL), 101 | SetDataRequest("/zookeeper/hello", "changing".getBytes, -1), 102 | CheckVersionRequest("/zookeeper/hello", 1) 103 | ) 104 | 105 | val res = client.get.transaction(opList) 106 | val finalRep = Await.result(res) 107 | 108 | assert(finalRep.responseList(0).isInstanceOf[CreateResponse]) 109 | assert(finalRep.responseList(1).isInstanceOf[SetDataResponse]) 110 | assert(finalRep.responseList(2).isInstanceOf[EmptyResponse]) 111 | 112 | disconnect() 113 | Await.ready(client.get.close()) 114 | } 115 | 116 | test("Create, set(x12), checkVersion and delete") { 117 | newClient() 118 | connect() 119 | 120 | val opList = Seq( 121 | CreateRequest( 122 | "/zookeeper/hello", "TRANS".getBytes, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL), 123 | SetDataRequest("/zookeeper/hello", "changing".getBytes, -1), 124 | SetDataRequest("/zookeeper/hello", "chang257ing".getBytes, -1), 125 | SetDataRequest("/zookeeper/hello", "chang5ig".getBytes, -1), 126 | SetDataRequest("/zookeeper/hello", "chan1257ing".getBytes, -1), 127 | SetDataRequest("/zookeeper/hello", "chai4n42g".getBytes, -1), 128 | SetDataRequest("/zookeeper/hello", "cha7ngng".getBytes, -1), 129 | SetDataRequest("/zookeeper/hello", "chan542ing".getBytes, -1), 130 | SetDataRequest("/zookeeper/hello", "cha8i4ng".getBytes, -1), 131 | SetDataRequest("/zookeeper/hello", "chi752ng".getBytes, -1), 132 | SetDataRequest("/zookeeper/hello", "cha64nng".getBytes, -1), 133 | SetDataRequest("/zookeeper/hello", "ch7ann5g".getBytes, -1), 134 | CheckVersionRequest("/zookeeper/hello", 11), 135 | DeleteRequest("/zookeeper/hello", 11)) 136 | 137 | val res = client.get.transaction(opList) 138 | val finalRep = Await.result(res) 139 | 140 | assert(finalRep.responseList(0).isInstanceOf[CreateResponse]) 141 | assert(finalRep.responseList(5).isInstanceOf[SetDataResponse]) 142 | assert(finalRep.responseList(12).isInstanceOf[EmptyResponse]) 143 | assert(finalRep.responseList(13).isInstanceOf[EmptyResponse]) 144 | 145 | disconnect() 146 | Await.ready(client.get.close()) 147 | } 148 | 149 | test("Create, set and checkVersion with error") { 150 | newClient() 151 | connect() 152 | 153 | val opList = Seq( 154 | CreateRequest( 155 | "/zookeeper/hello", "TRANS".getBytes, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT), 156 | SetDataRequest("/zookeeper/hello", "changing".getBytes, -1), 157 | CheckVersionRequest("/zookeeper/hello", 1), 158 | CreateRequest( 159 | "/zookeeper/hello", "TRANS".getBytes, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL), 160 | SetDataRequest("/zookeeper/hello", "changing".getBytes, -1) 161 | ) 162 | 163 | val res = client.get.transaction(opList) 164 | val finalRep = Await.result(res) 165 | 166 | assert(finalRep.responseList(3).isInstanceOf[ErrorResponse]) 167 | assert(finalRep.responseList(3) 168 | .asInstanceOf[ErrorResponse] 169 | .exception.isInstanceOf[NodeExistsException]) 170 | 171 | disconnect() 172 | Await.ready(client.get.close()) 173 | } 174 | } -------------------------------------------------------------------------------- /integration/src/test/scala/com/twitter/finagle/exp/zookeeper/integration/standalone/v3_4/extra/ConnectionManagerTest.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.integration.standalone.v3_4.extra 2 | 3 | import com.twitter.finagle.exp.zookeeper.connection.ConnectionManager 4 | import com.twitter.finagle.exp.zookeeper.connection.HostUtilities.ServerNotAvailable 5 | import com.twitter.finagle.exp.zookeeper.integration.standalone.StandaloneIntegrationConfig 6 | import com.twitter.util.Await 7 | import com.twitter.util.TimeConversions._ 8 | import org.junit.runner.RunWith 9 | import org.scalatest.FunSuite 10 | import org.scalatest.junit.JUnitRunner 11 | 12 | @RunWith(classOf[JUnitRunner]) 13 | class ConnectionManagerTest extends FunSuite with StandaloneIntegrationConfig { 14 | trait HelperTrait { 15 | val connectionManager = new ConnectionManager( 16 | ipAddress + ":" + port, 17 | None, 18 | true, 19 | Some(1.minute), 20 | Some(1.minute) 21 | ) 22 | } 23 | 24 | test("should close the connection manager") { 25 | new HelperTrait { 26 | val ret = Await.result { 27 | connectionManager.initConnectionManager() before 28 | connectionManager.close() before 29 | connectionManager.hasAvailableService 30 | } 31 | assert(ret === false) 32 | Await.result(connectionManager.close()) 33 | } 34 | } 35 | 36 | test("test should connect to a server") { 37 | new HelperTrait { 38 | val ret = Await.result { 39 | connectionManager.initConnectionManager() before 40 | connectionManager.close() before 41 | connectionManager.findAndConnect() before 42 | connectionManager.hasAvailableService 43 | } 44 | assert(ret === true) 45 | Await.result(connectionManager.close()) 46 | } 47 | } 48 | 49 | test("should find and connect to a server") { 50 | new HelperTrait { 51 | val ret = Await.result { 52 | connectionManager.initConnectionManager() before 53 | connectionManager.findAndConnect() before 54 | connectionManager.hasAvailableService 55 | } 56 | assert(ret === true) 57 | Await.result(connectionManager.close()) 58 | } 59 | } 60 | 61 | test("should test and connect to a server") { 62 | new HelperTrait { 63 | val ret = Await.result { 64 | connectionManager.initConnectionManager() before 65 | connectionManager.testAndConnect(ipAddress + ":" + port) before 66 | connectionManager.hasAvailableService 67 | } 68 | assert(ret === true) 69 | Await.result(connectionManager.close()) 70 | } 71 | } 72 | 73 | test("should test and not connect to a server") { 74 | new HelperTrait { 75 | intercept[ServerNotAvailable] { 76 | Await.result { 77 | connectionManager.initConnectionManager() before 78 | connectionManager.testAndConnect("3.4.5.6:7777") 79 | } 80 | } 81 | Await.result(connectionManager.close()) 82 | } 83 | } 84 | 85 | test("should call hasAvailableService") { 86 | new HelperTrait { 87 | val ret = Await.result { 88 | connectionManager.initConnectionManager() before 89 | connectionManager.hasAvailableService 90 | } 91 | assert(ret === true) 92 | Await.result(connectionManager.close()) 93 | val ret2 = Await.result { 94 | connectionManager.close() before 95 | connectionManager.hasAvailableService 96 | } 97 | assert(ret2 === false) 98 | Await.result(connectionManager.close()) 99 | } 100 | } 101 | 102 | test("should init connection manager") { 103 | new HelperTrait { 104 | val ret = Await.result { 105 | connectionManager.initConnectionManager() before 106 | connectionManager.hasAvailableService 107 | } 108 | assert(ret === true) 109 | Await.result(connectionManager.close()) 110 | } 111 | } 112 | } -------------------------------------------------------------------------------- /integration/src/test/scala/com/twitter/finagle/exp/zookeeper/integration/standalone/v3_4/extra/ConnectionTest.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.integration.standalone.v3_4.extra 2 | 3 | import com.twitter.finagle.ChannelWriteException 4 | import com.twitter.finagle.exp.zookeeper.ZookeeperDefs.{CreateMode, OpCode} 5 | import com.twitter.finagle.exp.zookeeper._ 6 | import com.twitter.finagle.exp.zookeeper.data.Ids 7 | import com.twitter.io.Buf 8 | import com.twitter.io.Buf.ByteArray 9 | import com.twitter.util.TimeConversions._ 10 | import com.twitter.util.{Await, Future} 11 | import org.junit.runner.RunWith 12 | import org.scalatest.FunSuite 13 | import org.scalatest.junit.JUnitRunner 14 | 15 | @RunWith(classOf[JUnitRunner]) 16 | class ConnectionTest extends FunSuite { 17 | /* Configure your server here */ 18 | val ipAddress: String = "127.0.0.1" 19 | val port: Int = 2181 20 | val timeOut: Long = 1000 21 | 22 | test("Server is in rw mode") { 23 | val client = BufClient.newSimpleClient(ipAddress + ":" + port) 24 | val service = client.apply() 25 | val rep = service flatMap (_.apply( 26 | ByteArray("isro".getBytes) 27 | )) 28 | 29 | val res = Await.result(rep) 30 | val Buf.Utf8(str) = res.slice(0, res.length) 31 | assert(str === "rw") 32 | Await.ready(client.close()) 33 | } 34 | 35 | test("Connection using BufClient") { 36 | try { 37 | val client = BufClient.newClient(ipAddress + ":" + port) 38 | val service = client.apply() 39 | def serve(buf: Buf): Future[Unit] = service flatMap (_.apply(buf).unit) 40 | 41 | val conReq = ReqPacket( 42 | None, 43 | Some(new ConnectRequest( 44 | 0, 45 | 0L, 46 | 5000.milliseconds, 47 | 0L, 48 | Array[Byte](16), 49 | canBeRO = true))) 50 | 51 | val closeReq = ReqPacket(Some(RequestHeader(1, OpCode.CLOSE_SESSION)), None) 52 | 53 | val rep = serve(Buf.U32BE(conReq.buf.length).concat(conReq.buf)) before { 54 | serve(Buf.U32BE(closeReq.buf.length).concat(closeReq.buf)) 55 | } 56 | 57 | Await.result(rep) 58 | Await.result(client.close()) 59 | } 60 | catch { 61 | case exc: ChannelWriteException => 62 | throw exc 63 | } 64 | } 65 | 66 | test("Test with 2 servers unavailable") { 67 | val client = Zookeeper.newRichClient( 68 | "127.0.0.1:2121,127.0.0.1:2151," + ipAddress + ":" + port) 69 | Await.result(client.connect()) 70 | 71 | val res = for { 72 | _ <- client.create("/zookeeper/test", "HELLO".getBytes, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL) 73 | exi <- client.exists("/zookeeper/test", watch = false) 74 | set <- client.setData("/zookeeper/test", "CHANGE IS GOOD1".getBytes, -1) 75 | get <- client.getData("/zookeeper/test", watch = false) 76 | sync <- client.sync("/zookeeper") 77 | } yield (exi, set, get, sync) 78 | 79 | val ret = Await.result(res) 80 | ret._1 match { 81 | case rep: ExistsResponse => assert(rep.stat.get.dataLength === "HELLO".getBytes.length) 82 | case _ => throw new RuntimeException("Test failed") 83 | } 84 | assert(ret._2.dataLength === "CHANGE IS GOOD1".getBytes.length) 85 | assert(ret._3.data === "CHANGE IS GOOD1".getBytes) 86 | assert(ret._4 === "/zookeeper") 87 | 88 | Await.ready(client.disconnect()) 89 | Await.ready(client.close()) 90 | } 91 | } -------------------------------------------------------------------------------- /integration/src/test/scala/com/twitter/finagle/exp/zookeeper/integration/standalone/v3_4/extra/HostProviderTest.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.integration.standalone.v3_4.extra 2 | 3 | import com.twitter.finagle.exp.zookeeper.connection.HostProvider 4 | import com.twitter.finagle.exp.zookeeper.integration.standalone.StandaloneIntegrationConfig 5 | import com.twitter.util.Await 6 | import com.twitter.util.TimeConversions._ 7 | import org.junit.runner.RunWith 8 | import org.scalatest.FunSuite 9 | import org.scalatest.junit.JUnitRunner 10 | 11 | @RunWith(classOf[JUnitRunner]) 12 | class HostProviderTest extends FunSuite with StandaloneIntegrationConfig { 13 | trait HelperTrait { 14 | val hostProvider = new HostProvider( 15 | ipAddress + ":" + port, 16 | true, 17 | Some(1.minute), 18 | Some(1.minute) 19 | ) 20 | } 21 | 22 | test("should test a host") { 23 | new HelperTrait { 24 | val ret = Await.result { 25 | hostProvider.testHost(ipAddress + ":" + port) 26 | } 27 | assert(ret === true) 28 | } 29 | } 30 | 31 | test("should test a host or find a new one") { 32 | new HelperTrait { 33 | val ret = Await.result { 34 | hostProvider.testOrFind(ipAddress + ":" + port) 35 | } 36 | assert(ret === ipAddress + ":" + port) 37 | val ret2 = Await.result { 38 | hostProvider.testOrFind("1.2.3.4:9999") 39 | } 40 | assert(ret2 === ipAddress + ":" + port) 41 | } 42 | } 43 | 44 | test("should find a host from a server list or no") { 45 | new HelperTrait { 46 | val ret = Await.result { 47 | hostProvider.findServer(Some(Seq(ipAddress + ":" + port))) 48 | } 49 | assert(ret === ipAddress + ":" + port) 50 | 51 | val ret2 = Await.result { 52 | hostProvider.findServer(None) 53 | } 54 | assert(ret2 === ipAddress + ":" + port) 55 | } 56 | } 57 | 58 | test("test rw mode server search") { 59 | new HelperTrait { 60 | val ret = Await.result { 61 | hostProvider.findRwServer(10.milliseconds) 62 | } 63 | assert(ret === ipAddress + ":" + port) 64 | } 65 | } 66 | 67 | test("should test a server with isro request") { 68 | new HelperTrait { 69 | val ret = Await.result { 70 | hostProvider.withIsroRequest(ipAddress + ":" + port) 71 | } 72 | assert(ret === ipAddress + ":" + port) 73 | } 74 | } 75 | 76 | test("should test a server with connect request") { 77 | new HelperTrait { 78 | val ret = Await.result { 79 | hostProvider.withConnectRequest(ipAddress + ":" + port) 80 | } 81 | assert(ret === ipAddress + ":" + port) 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /integration/src/test/scala/com/twitter/finagle/exp/zookeeper/integration/standalone/v3_4/extra/LocalSessionUpdateTest.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.integration.standalone.v3_4.extra 2 | 3 | import com.twitter.finagle.exp.zookeeper.ZookeeperDefs.CreateMode 4 | import com.twitter.finagle.exp.zookeeper.data.Ids 5 | import com.twitter.finagle.exp.zookeeper.integration.standalone.StandaloneIntegrationConfig 6 | import com.twitter.util.Await 7 | import org.junit.runner.RunWith 8 | import org.scalatest.FunSuite 9 | import org.scalatest.junit.JUnitRunner 10 | 11 | @RunWith(classOf[JUnitRunner]) 12 | class LocalSessionUpdateTest extends FunSuite with StandaloneIntegrationConfig { 13 | test("updates local session") { 14 | newClient() 15 | connect() 16 | 17 | val res = for { 18 | _ <- client.get.create("/node1", "HELLO".getBytes, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL) 19 | _ <- client.get.create("/node2", "HELLO".getBytes, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL) 20 | exi <- client.get.exists("/node1", watch = false) 21 | exi2 <- client.get.exists("/node2", watch = false) 22 | } yield (exi, exi2) 23 | 24 | val (exi, exi2) = Await.result(res) 25 | 26 | assert(exi2.stat.get.czxid - exi.stat.get.czxid === 1L) 27 | 28 | disconnect() 29 | Await.ready(client.get.close()) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /integration/src/test/scala/com/twitter/finagle/exp/zookeeper/integration/standalone/v3_4/extra/ReconnectionTest.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.integration.standalone.v3_4.extra 2 | 3 | import com.twitter.finagle.exp.zookeeper.ZookeeperDefs.CreateMode 4 | import com.twitter.finagle.exp.zookeeper.data.Ids 5 | import com.twitter.finagle.exp.zookeeper.integration.standalone.StandaloneIntegrationConfig 6 | import com.twitter.finagle.exp.zookeeper.{AuthFailedException, ExistsResponse} 7 | import com.twitter.util.Await 8 | import org.junit.runner.RunWith 9 | import org.scalatest.FunSuite 10 | import org.scalatest.junit.JUnitRunner 11 | 12 | @RunWith(classOf[JUnitRunner]) 13 | class ReconnectionTest extends FunSuite with StandaloneIntegrationConfig { 14 | test("Client connection") { 15 | newClient() 16 | connect() 17 | 18 | Await.result(client.get.changeHost()) 19 | Await.result(client.get.changeHost(Some("127.0.0.1:2181"))) 20 | 21 | val res = for { 22 | _ <- client.get.create("/zookeeper/test", "HELLO".getBytes, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL) 23 | exi <- client.get.exists("/zookeeper/test", watch = false) 24 | set <- client.get.setData("/zookeeper/test", "CHANGE IS GOOD1".getBytes, -1) 25 | get <- client.get.getData("/zookeeper/test", watch = false) 26 | sync <- client.get.sync("/zookeeper") 27 | } yield (exi, set, get, sync) 28 | 29 | val ret = Await.result(res) 30 | ret._1 match { 31 | case rep: ExistsResponse => assert(rep.stat.get.dataLength === "HELLO".getBytes.length) 32 | case _ => throw new RuntimeException("Test failed") 33 | } 34 | assert(ret._2.dataLength === "CHANGE IS GOOD1".getBytes.length) 35 | assert(ret._3.data === "CHANGE IS GOOD1".getBytes) 36 | assert(ret._4 === "/zookeeper") 37 | 38 | disconnect() 39 | Await.ready(client.get.close()) 40 | } 41 | 42 | test("connect-disconnect tests") { 43 | newClient() 44 | 45 | for (i <- 0 until 50) { 46 | connect() 47 | disconnect() 48 | } 49 | 50 | Await.ready(client.get.close()) 51 | } 52 | 53 | test("auth failed reconnection") { 54 | newClient() 55 | connect() 56 | 57 | intercept[AuthFailedException] { 58 | Await.result(client.get.addAuth("FOO", "BAR".getBytes)) 59 | } 60 | 61 | connect() 62 | 63 | val rep = Await.result { 64 | client.get.create("/hello", "".getBytes, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL) 65 | } 66 | 67 | assert(rep === "/hello") 68 | 69 | val disconnect = client.get.disconnect() before client.get.close() 70 | Await.ready(disconnect) 71 | } 72 | } -------------------------------------------------------------------------------- /integration/src/test/scala/com/twitter/finagle/exp/zookeeper/integration/standalone/v3_4/extra/StressTest.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.integration.standalone.v3_4.extra 2 | 3 | import com.twitter.finagle.exp.zookeeper.ZookeeperDefs.CreateMode 4 | import com.twitter.finagle.exp.zookeeper.data.Ids 5 | import com.twitter.finagle.exp.zookeeper.integration.standalone.StandaloneIntegrationConfig 6 | import com.twitter.finagle.exp.zookeeper.watcher.Watch.{EventState, EventType} 7 | import com.twitter.util.{Await, Future} 8 | import org.junit.runner.RunWith 9 | import org.scalatest.FunSuite 10 | import org.scalatest.junit.JUnitRunner 11 | 12 | @RunWith(classOf[JUnitRunner]) 13 | class StressTest extends FunSuite with StandaloneIntegrationConfig { 14 | ignore("intensive test, run manually : watcher test") { 15 | newClient() 16 | connect() 17 | 18 | val ret = Await.result { 19 | Future.collect { 20 | 0 until 1000 map { i => 21 | for { 22 | _ <- client.get.create( 23 | "/foo-" + i, ("foodata" + i).getBytes, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT 24 | ) 25 | getdata <- client.get.getData("/foo-" + i, true) 26 | exists <- client.get.exists("/foo-" + i, true) 27 | setdata <- client.get.setData("/foo-" + i, ("foodata2-" + i).getBytes, -1) 28 | setdata2 <- client.get.setData("/foo-" + i, ("foodata3-" + i).getBytes, -1) 29 | } yield (getdata, exists, setdata, setdata2) 30 | } 31 | } 32 | } 33 | 34 | ret map { grpRep => 35 | val (getdata, exists, stat, stat2) = grpRep 36 | assert(getdata.watcher.get.event.isDefined) 37 | assert(Await.result(getdata.watcher.get.event).typ === EventType.NODE_DATA_CHANGED) 38 | assert(Await.result(getdata.watcher.get.event).state === EventState.SYNC_CONNECTED) 39 | assert(exists.watcher.get.event.isDefined) 40 | assert(Await.result(exists.watcher.get.event).typ === EventType.NODE_DATA_CHANGED) 41 | assert(Await.result(exists.watcher.get.event).state === EventState.SYNC_CONNECTED) 42 | } 43 | 44 | Await.result { 45 | Future.collect { 46 | 0 until 1000 map { i => 47 | for { 48 | _ <- client.get.delete("/foo-" + i, -1) 49 | } yield None 50 | } 51 | } 52 | } 53 | 54 | disconnect() 55 | Await.result(client.get.close()) 56 | } 57 | 58 | ignore("intensive test, run manually : connect-disconnect test") { 59 | newClient() 60 | 61 | for (i <- 0 until 500) { 62 | connect() 63 | disconnect() 64 | } 65 | 66 | Await.ready(client.get.close()) 67 | } 68 | 69 | ignore("intensive test, run manually : change host test") { 70 | newClient() 71 | connect() 72 | 73 | for (i <- 0 until 500) { 74 | Await.result(client.get.changeHost()) 75 | } 76 | 77 | disconnect() 78 | Await.ready(client.get.close()) 79 | } 80 | } -------------------------------------------------------------------------------- /integration/src/test/scala/com/twitter/finagle/exp/zookeeper/integration/standalone/v3_5/command/CheckWatcherTest.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.integration.standalone.v3_5.command 2 | 3 | import com.twitter.finagle.exp.zookeeper.ZookeeperDefs.CreateMode 4 | import com.twitter.finagle.exp.zookeeper.data.Ids 5 | import com.twitter.finagle.exp.zookeeper.integration.standalone.StandaloneIntegrationConfig 6 | import com.twitter.util.Await 7 | import org.scalatest.FunSuite 8 | 9 | class CheckWatcherTest extends FunSuite with StandaloneIntegrationConfig { 10 | test("Should check a watcher without error") { 11 | newClient() 12 | connect() 13 | 14 | Await.result { 15 | for { 16 | exists <- client.get.exists("/hello", true) 17 | check <- client.get.checkWatcher(exists.watcher.get) 18 | } yield check 19 | } 20 | 21 | disconnect() 22 | Await.ready(client.get.close()) 23 | } 24 | 25 | test("Should not check a watcher correctly") { 26 | newClient() 27 | connect() 28 | 29 | intercept[Exception] { 30 | Await.result { 31 | for { 32 | exists <- client.get.exists("/hello", true) 33 | create <- client.get.create("/hello", "".getBytes, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL) 34 | check <- client.get.checkWatcher(exists.watcher.get) 35 | } yield check 36 | } 37 | } 38 | 39 | disconnect() 40 | Await.ready(client.get.close()) 41 | } 42 | } -------------------------------------------------------------------------------- /integration/src/test/scala/com/twitter/finagle/exp/zookeeper/integration/standalone/v3_5/command/CheckWatchesTest.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.integration.standalone.v3_5.command 2 | 3 | import com.twitter.finagle.exp.zookeeper.ZookeeperDefs.CreateMode 4 | import com.twitter.finagle.exp.zookeeper.data.Ids 5 | import com.twitter.finagle.exp.zookeeper.integration.standalone.StandaloneIntegrationConfig 6 | import com.twitter.finagle.exp.zookeeper.watcher.Watch.WatcherType 7 | import com.twitter.util.Await 8 | import org.scalatest.FunSuite 9 | 10 | class CheckWatchesTest extends FunSuite with StandaloneIntegrationConfig { 11 | test("Check correct exists watch") { 12 | newClient() 13 | connect() 14 | 15 | Await.result { 16 | for { 17 | _ <- client.get.exists("/hello", true) 18 | check <- client.get.checkWatches("/hello", WatcherType.DATA) 19 | } yield check 20 | } 21 | 22 | disconnect() 23 | Await.ready(client.get.close()) 24 | } 25 | 26 | test("Check correct getData watch") { 27 | newClient() 28 | connect() 29 | 30 | Await.result { 31 | for { 32 | _ <- client.get.create("/hello", "".getBytes, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL) 33 | _ <- client.get.getData("/hello", true) 34 | check <- client.get.checkWatches("/hello", WatcherType.DATA) 35 | } yield check 36 | } 37 | 38 | disconnect() 39 | Await.ready(client.get.close()) 40 | } 41 | 42 | test("Check correct getChildren watch") { 43 | newClient() 44 | connect() 45 | 46 | Await.result { 47 | for { 48 | _ <- client.get.create("/hello", "".getBytes, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL) 49 | _ <- client.get.getChildren("/hello", true) 50 | check <- client.get.checkWatches("/hello", WatcherType.CHILDREN) 51 | } yield check 52 | } 53 | 54 | disconnect() 55 | Await.ready(client.get.close()) 56 | } 57 | 58 | test("Check correct getChildren2 watch") { 59 | newClient() 60 | connect() 61 | 62 | Await.result { 63 | for { 64 | _ <- client.get.create("/hello", "".getBytes, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL) 65 | _ <- client.get.getChildren2("/hello", true) 66 | check <- client.get.checkWatches("/hello", WatcherType.CHILDREN) 67 | } yield check 68 | } 69 | 70 | disconnect() 71 | Await.ready(client.get.close()) 72 | } 73 | 74 | test("Check correct getChildren watch with Watcher type Any") { 75 | newClient() 76 | connect() 77 | 78 | Await.result { 79 | for { 80 | _ <- client.get.create("/hello", "".getBytes, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL) 81 | _ <- client.get.getChildren("/hello", true) 82 | check <- client.get.checkWatches("/hello", WatcherType.ANY) 83 | } yield check 84 | } 85 | 86 | disconnect() 87 | Await.ready(client.get.close()) 88 | } 89 | 90 | test("Check correct watches with watcher type Any") { 91 | newClient() 92 | connect() 93 | 94 | val ret = Await.result { 95 | for { 96 | _ <- client.get.create("/hello", "".getBytes, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL) 97 | _ <- client.get.getChildren("/hello", true) 98 | _ <- client.get.getData("/hello", true) 99 | check <- client.get.checkWatches("/hello", WatcherType.CHILDREN) 100 | check1 <- client.get.checkWatches("/hello", WatcherType.DATA) 101 | check2 <- client.get.checkWatches("/hello", WatcherType.ANY) 102 | } yield (check, check1, check2) 103 | } 104 | 105 | val (check, check1, check2) = ret 106 | 107 | disconnect() 108 | Await.ready(client.get.close()) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /integration/src/test/scala/com/twitter/finagle/exp/zookeeper/integration/standalone/v3_5/command/Create2Test.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.integration.standalone.v3_5.command 2 | 3 | import com.twitter.finagle.exp.zookeeper.NodeExistsException 4 | import com.twitter.finagle.exp.zookeeper.ZookeeperDefs.CreateMode 5 | import com.twitter.finagle.exp.zookeeper.data.Ids 6 | import com.twitter.finagle.exp.zookeeper.integration.standalone.StandaloneIntegrationConfig 7 | import com.twitter.util.Await 8 | import org.scalatest.FunSuite 9 | 10 | class Create2Test extends FunSuite with StandaloneIntegrationConfig { 11 | test("Create2 is equivalent to create + getData") { 12 | newClient() 13 | connect() 14 | 15 | val rep1 = Await.result { 16 | client.get.create2("/hello", 17 | "".getBytes, 18 | Ids.OPEN_ACL_UNSAFE, 19 | CreateMode.EPHEMERAL 20 | ) 21 | } 22 | 23 | val rep2 = Await.result(client.get.getData("/hello")) 24 | 25 | assert(rep1.stat === rep2.stat) 26 | 27 | disconnect() 28 | Await.ready(client.get.close()) 29 | } 30 | 31 | test("Create2 on existing node") { 32 | newClient() 33 | connect() 34 | 35 | intercept[NodeExistsException] { 36 | Await.result { 37 | client.get.create2("/hello", 38 | "".getBytes, 39 | Ids.OPEN_ACL_UNSAFE, 40 | CreateMode.EPHEMERAL 41 | ).unit before 42 | client.get.create2("/hello", 43 | "".getBytes, 44 | Ids.OPEN_ACL_UNSAFE, 45 | CreateMode.EPHEMERAL 46 | ) 47 | } 48 | } 49 | 50 | disconnect() 51 | Await.ready(client.get.close()) 52 | } 53 | } -------------------------------------------------------------------------------- /integration/src/test/scala/com/twitter/finagle/exp/zookeeper/integration/standalone/v3_5/command/GetConfigTest.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.integration.standalone.v3_5.command 2 | 3 | import com.twitter.finagle.exp.zookeeper.ZookeeperDefs 4 | import com.twitter.finagle.exp.zookeeper.integration.standalone.StandaloneIntegrationConfig 5 | import com.twitter.util.Await 6 | import org.scalatest.FunSuite 7 | 8 | class GetConfigTest extends FunSuite with StandaloneIntegrationConfig { 9 | test("getConfig on standalone server is useless") { 10 | // We can't reconfigure a standalone server because we can't change 11 | // the mode during the session, only ensembles are reconfigurable 12 | newClient() 13 | connect() 14 | 15 | val rep = Await.result { 16 | client.get.sync(ZookeeperDefs.CONFIG_NODE).unit before 17 | client.get.getConfig() 18 | } 19 | 20 | assert(rep.data.length === 0) 21 | 22 | disconnect() 23 | Await.ready(client.get.close()) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /integration/src/test/scala/com/twitter/finagle/exp/zookeeper/integration/standalone/v3_5/command/RemoveAllWatchesTest.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.integration.standalone.v3_5.command 2 | 3 | import com.twitter.finagle.exp.zookeeper.ZookeeperDefs.CreateMode 4 | import com.twitter.finagle.exp.zookeeper.data.Ids 5 | import com.twitter.finagle.exp.zookeeper.integration.standalone.StandaloneIntegrationConfig 6 | import com.twitter.finagle.exp.zookeeper.watcher.Watch.WatcherType 7 | import com.twitter.util.Await 8 | import org.scalatest.FunSuite 9 | 10 | class RemoveAllWatchesTest extends FunSuite with StandaloneIntegrationConfig { 11 | test("Remove a correct watch") { 12 | newClient() 13 | connect() 14 | 15 | val rep = Await.result { 16 | for { 17 | exists <- client.get.exists("/hello", true) 18 | remove <- client.get.removeAllWatches("/hello", WatcherType.DATA, false) 19 | create <- client.get.create( 20 | "/hello", 21 | "".getBytes, 22 | Ids.OPEN_ACL_UNSAFE, 23 | CreateMode.EPHEMERAL 24 | ) 25 | } yield exists 26 | } 27 | 28 | assert(!rep.watcher.get.event.isDefined) 29 | 30 | disconnect() 31 | Await.ready(client.get.close()) 32 | } 33 | 34 | test("Remove a getData and exists watches") { 35 | newClient() 36 | connect() 37 | 38 | val rep = Await.result { 39 | for { 40 | _ <- client.get.create( 41 | "/hello", 42 | "".getBytes, 43 | Ids.OPEN_ACL_UNSAFE, 44 | CreateMode.PERSISTENT 45 | ) 46 | exists <- client.get.exists("/hello", true) 47 | getdata <- client.get.getData("/hello", true) 48 | getchildren <- client.get.getChildren("/hello", true) 49 | _ <- client.get.removeAllWatches("/hello", WatcherType.DATA, false) 50 | _ <- client.get.create( 51 | "/hello/1", 52 | "".getBytes, 53 | Ids.OPEN_ACL_UNSAFE, 54 | CreateMode.EPHEMERAL 55 | ) 56 | _ <- client.get.delete("/hello/1", -1) 57 | _ <- client.get.delete("/hello", -1) 58 | } yield (exists, getdata, getchildren) 59 | } 60 | 61 | val (exists, getdata, children) = rep 62 | assert(!exists.watcher.get.event.isDefined) 63 | assert(!getdata.watcher.get.event.isDefined) 64 | assert(children.watcher.get.event.isDefined) 65 | 66 | disconnect() 67 | Await.ready(client.get.close()) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /integration/src/test/scala/com/twitter/finagle/exp/zookeeper/integration/standalone/v3_5/command/RemoveWatchesTest.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.finagle.exp.zookeeper.integration.standalone.v3_5.command 2 | 3 | import com.twitter.finagle.exp.zookeeper.ZookeeperDefs.CreateMode 4 | import com.twitter.finagle.exp.zookeeper.data.Ids 5 | import com.twitter.finagle.exp.zookeeper.integration.standalone.StandaloneIntegrationConfig 6 | import com.twitter.util.Await 7 | import org.scalatest.FunSuite 8 | 9 | class RemoveWatchesTest extends FunSuite with StandaloneIntegrationConfig { 10 | // TODO RemoveWatchesTest.java 11 | test("Remove a correct watch") { 12 | newClient() 13 | connect() 14 | 15 | val rep = Await.result { 16 | for { 17 | exists <- client.get.exists("/hello", true) 18 | remove <- client.get.removeWatches(exists.watcher.get, false) 19 | create <- client.get.create( 20 | "/hello", 21 | "".getBytes, 22 | Ids.OPEN_ACL_UNSAFE, 23 | CreateMode.EPHEMERAL 24 | ) 25 | } yield exists 26 | } 27 | 28 | assert(!rep.watcher.get.event.isDefined) 29 | 30 | disconnect() 31 | Await.ready(client.get.close()) 32 | } 33 | 34 | test("Not remove a non-existing watcher") { 35 | newClient() 36 | connect() 37 | 38 | intercept[Exception] { 39 | Await.result { 40 | for { 41 | exists <- client.get.exists("/hello", true) 42 | create <- client.get.create( 43 | "/hello", 44 | "".getBytes, 45 | Ids.OPEN_ACL_UNSAFE, 46 | CreateMode.EPHEMERAL 47 | ) 48 | remove <- client.get.removeWatches(exists.watcher.get, false) 49 | } yield remove 50 | } 51 | } 52 | 53 | disconnect() 54 | Await.ready(client.get.close()) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /project/Build.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Keys._ 3 | 4 | object finaglezk extends Build { 5 | val finagleVersion = "6.25.0" 6 | val clientVersion = "0.2.0" 7 | 8 | lazy val root = Project( 9 | id = "finagle-ZooKeeper", 10 | base = file("."), 11 | settings = buildSettings 12 | ).aggregate(core, integration, example) 13 | 14 | lazy val core = Project( 15 | id = "core", 16 | base = file("core"), 17 | settings = baseSettings 18 | ) 19 | 20 | lazy val example = Project( 21 | id = "example", 22 | base = file("example") 23 | ).dependsOn(core) 24 | 25 | lazy val integration = Project( 26 | id = "integration", 27 | base = file("integration"), 28 | settings = testSettings 29 | ).dependsOn(core, example) 30 | 31 | lazy val baseSettings = Seq( 32 | libraryDependencies ++= Seq( 33 | "org.scalatest" %% "scalatest" % "2.2.4", 34 | "com.twitter" %% "finagle-core" % finagleVersion, 35 | "junit" % "junit" % "4.12", 36 | "org.mockito" % "mockito-all" % "1.10.19" % "test" 37 | ) 38 | ) 39 | 40 | lazy val buildSettings = Seq( 41 | name := "finagle-ZooKeeper", 42 | organization := "com.twitter", 43 | version := clientVersion, 44 | scalaVersion := "2.10.5", 45 | crossScalaVersions := Seq("2.10.5", "2.11.6"), 46 | scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature") 47 | ) 48 | 49 | lazy val runTests = taskKey[Unit]("Runs configurations and tests") 50 | lazy val testSettings = Seq( 51 | runTests := IntegrationTest.integrationTestTask(testOnly in Test).value, 52 | parallelExecution := false 53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /project/IntegrationTest.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Def.Initialize 3 | import complete.DefaultParsers._ 4 | 5 | object IntegrationTest { 6 | case class Config( 7 | version: String, 8 | modes: Seq[String], 9 | packages: Seq[String] 10 | ) 11 | 12 | /** 13 | * Parse the testConfig.csv file. Line example : 14 | * 3.5.0;standalone quorum;v3_4 v3_5 15 | */ 16 | lazy val fileParser: Initialize[Task[Seq[Config]]] = Def.task { 17 | val path = System.getProperty("user.dir") + 18 | "/integration/src/main/resources/testConfig.csv" 19 | val source = scala.io.Source.fromFile(path) 20 | val configs = source.getLines().toSeq map { line => 21 | val Array(version, modes, packages) = line.split(";") 22 | Config( 23 | version, 24 | modes.split(" "), 25 | packages.split(" ") 26 | ) 27 | } 28 | 29 | configs 30 | } 31 | 32 | /** 33 | * Calls the shell script in charge of configuring the server/quorum 34 | * @return Unit 35 | */ 36 | def configEnv: Initialize[InputTask[Unit]] = Def.inputTask { 37 | val args: Seq[String] = spaceDelimited("").parsed 38 | 39 | val Seq(version, mode) = args 40 | val baseURL = "https://github.com/apache/zookeeper/archive/release-" 41 | val URL = baseURL + version + ".tar.gz" 42 | 43 | mode match { 44 | case "quorum" => 45 | ("sh " 46 | + System.getProperty("user.dir") 47 | + "/integration/src/main/resources/scripts/runQuorumMode.sh " 48 | + s"$version $URL" 49 | ).!! 50 | 51 | case "standalone" => 52 | ("sh " 53 | + System.getProperty("user.dir") 54 | + "/integration/src/main/resources/scripts/runStandaloneMode.sh " 55 | + s"$version $URL" 56 | ).!! 57 | } 58 | } 59 | 60 | /** 61 | * Check if the server or quorum is available by sending the four letter command. 62 | * Consists of executing `echo mntr | nc 127.0.0.1 2181` and parsing the response. 63 | * If we are in standalone mode, it will check that the response contains "standalone". 64 | * If we are in quorum mode, it will check that we have one "leader" and two "follower". 65 | * 66 | * @return Unit 67 | */ 68 | def checkEnv: Initialize[InputTask[Unit]] = Def.inputTask { 69 | val args: Seq[String] = spaceDelimited("").parsed 70 | val Seq(mode) = args 71 | 72 | def recursiveCheckEnv(mode: String): Unit = { 73 | mode match { 74 | case "quorum" => 75 | if (isQuorumAvailable) Unit 76 | else { 77 | Thread.sleep(2000) 78 | recursiveCheckEnv(mode) 79 | } 80 | 81 | case "standalone" => 82 | if (isServerAvailable) Unit 83 | else { 84 | Thread.sleep(2000) 85 | recursiveCheckEnv(mode) 86 | } 87 | } 88 | } 89 | 90 | def sendMntrCommand(ip: String, port: String): String = { 91 | try { ("echo mntr" #| s"nc $ip $port").!! } 92 | catch { 93 | case _: Throwable => 94 | Thread.sleep(2000) 95 | sendMntrCommand(ip, port) 96 | } 97 | } 98 | 99 | def isServerAvailable: Boolean = { 100 | val rep = sendMntrCommand("127.0.0.1", "2181") 101 | rep.contains("standalone") 102 | } 103 | 104 | def isQuorumAvailable: Boolean = { 105 | def evalServer(ip: String, port: String): Int = { 106 | val rep = sendMntrCommand(ip, port) 107 | if (rep.contains("leader")) 2 108 | else if (rep.contains("follower")) 1 109 | else 0 110 | } 111 | 112 | val total = Seq( 113 | ("127.0.0.1", "2181"), 114 | ("127.0.0.1", "2182"), 115 | ("127.0.0.1", "2183") 116 | ).foldLeft(0) { (acc, ad) => acc + evalServer(ad._1, ad._2) } 117 | 118 | if (total == 4) true 119 | else false 120 | } 121 | 122 | recursiveCheckEnv(mode) 123 | } 124 | 125 | def cleanEnv(mode: String, version: String): Unit = { 126 | mode match { 127 | case "quorum" => 128 | ("sh " 129 | + System.getProperty("user.dir") 130 | + "/integration/src/main/resources/scripts/stopAndCleanQuorumMode.sh " 131 | + version 132 | ).!! 133 | 134 | case "standalone" => 135 | ("sh " 136 | + System.getProperty("user.dir") 137 | + "/integration/src/main/resources/scripts/stopAndCleanStandaloneMode.sh " 138 | + version 139 | ).!! 140 | } 141 | } 142 | 143 | def runTests( 144 | testRunner: InputKey[Unit], 145 | mode: String, 146 | packages: Seq[String] 147 | ): Initialize[Task[Unit]] = { 148 | val packagesTasks = packages map { packge => 149 | testRunner.toTask( 150 | s" com.twitter.finagle.exp.zookeeper.integration.$mode.$packge.*" 151 | ) 152 | } 153 | 154 | sequence(packagesTasks: _*) 155 | } 156 | 157 | def sequence(tasks: Initialize[Task[Unit]]*): Initialize[Task[Unit]] = 158 | tasks match { 159 | case Seq() => Def.task { () } 160 | case Seq(x, xs@_*) => Def.taskDyn { val _ = x.value; sequence(xs: _*) } 161 | } 162 | 163 | def integrationTestTask( 164 | testRunner: InputKey[Unit] 165 | ): Def.Initialize[Task[Unit]] = Def.taskDyn { 166 | val tasks = fileParser.value flatMap { config => 167 | config.modes map { mode => 168 | sequence( 169 | configEnv.toTask(s" ${ config.version } $mode"), 170 | checkEnv.toTask(s" $mode"), 171 | runTests(testRunner, mode, config.packages) andFinally { 172 | cleanEnv(mode, config.version) 173 | } 174 | ) 175 | } 176 | } 177 | 178 | Def.task { 179 | sequence(tasks: _*).value 180 | } 181 | } 182 | } -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.8 2 | --------------------------------------------------------------------------------