PageRenderTime 92ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/finagle-memcached/src/test/scala/com/twitter/finagle/memcached/unit/ClientSpec.scala

http://github.com/twitter/finagle
Scala | 179 lines | 153 code | 23 blank | 3 comment | 5 complexity | 8d9ce6842415c30bf9bb0a3c2af4e2f9 MD5 | raw file
Possible License(s): Apache-2.0
  1. package com.twitter.finagle.memcached.unit
  2. import com.twitter.finagle.{Group, Service, ShardNotAvailableException}
  3. import com.twitter.finagle.memcached._
  4. import com.twitter.finagle.memcached.protocol._
  5. import com.twitter.hashing.KeyHasher
  6. import com.twitter.concurrent.Broker
  7. import com.twitter.util.{Await, Duration, Future}
  8. import org.jboss.netty.buffer.ChannelBuffers
  9. import org.specs.mock.Mockito
  10. import org.specs.SpecificationWithJUnit
  11. import scala.collection.{immutable, mutable}
  12. import _root_.java.io.{BufferedReader, InputStreamReader}
  13. class ClientSpec extends SpecificationWithJUnit with Mockito {
  14. "KetamaClient" should {
  15. // Test from Smile's KetamaNodeLocatorSpec.scala
  16. // Load known good results (key, hash(?), continuum ceiling(?), IP)
  17. val stream = getClass.getClassLoader.getResourceAsStream("ketama_results")
  18. val reader = new BufferedReader(new InputStreamReader(stream))
  19. val expected = new mutable.ListBuffer[Array[String]]
  20. var line: String = null
  21. do {
  22. line = reader.readLine
  23. if (line != null) {
  24. val segments = line.split(" ")
  25. segments.length mustEqual 4
  26. expected += segments
  27. }
  28. } while (line != null)
  29. expected.size mustEqual 99
  30. // Build Ketama client
  31. def newMock() = {
  32. val s = mock[Service[Command, Response]]
  33. s.close(any) returns Future.Done
  34. s
  35. }
  36. val clients = Map(
  37. CacheNode("10.0.1.1", 11211, 600) -> newMock(),
  38. CacheNode("10.0.1.2", 11211, 300) -> newMock(),
  39. CacheNode("10.0.1.3", 11211, 200) -> newMock(),
  40. CacheNode("10.0.1.4", 11211, 350) -> newMock(),
  41. CacheNode("10.0.1.5", 11211, 1000) -> newMock(),
  42. CacheNode("10.0.1.6", 11211, 800) -> newMock(),
  43. CacheNode("10.0.1.7", 11211, 950) -> newMock(),
  44. CacheNode("10.0.1.8", 11211, 100) -> newMock()
  45. )
  46. val mockBuilder =
  47. (node: CacheNode, k: KetamaClientKey, _: Broker[NodeHealth], _: (Int, Duration)) => clients.get(node).get
  48. val ketamaClient = new KetamaClient(Group(clients.keys.toSeq:_*), KeyHasher.KETAMA, 160, (Int.MaxValue, Duration.Zero), Some(mockBuilder))
  49. "pick the correct node" in {
  50. val ipToService = clients map { case (key, service) => key.host -> service } toMap
  51. val rng = new scala.util.Random
  52. for (testcase <- expected) {
  53. val mockClient = ketamaClient.clientOf(testcase(0))
  54. val expectedService = ipToService(testcase(3))
  55. val randomResponse = Number(rng.nextLong)
  56. expectedService.apply(any[Incr]) returns Future.value(randomResponse)
  57. Await.result(mockClient.incr("foo")).get mustEqual randomResponse.value
  58. }
  59. }
  60. "release" in {
  61. ketamaClient.release()
  62. clients.values foreach { client =>
  63. there was one(client).close(any)
  64. }
  65. }
  66. "ejects dead clients" in {
  67. val serviceA = smartMock[Service[Command,Response]]
  68. val serviceB = smartMock[Service[Command,Response]]
  69. val nodeA = CacheNode("10.0.1.1", 11211, 100)
  70. val nodeB = CacheNode("10.0.1.2", 11211, 100)
  71. val nodeKeyA = KetamaClientKey(nodeA.host, nodeA.port, nodeA.weight)
  72. val nodeKeyB = KetamaClientKey(nodeB.host, nodeB.port, nodeB.weight)
  73. val services = Map(
  74. nodeA -> serviceA,
  75. nodeB -> serviceB
  76. )
  77. val mutableGroup = Group.mutable(services.keys.toSeq:_*)
  78. val key = ChannelBuffers.wrappedBuffer("foo".getBytes)
  79. val value = smartMock[Value]
  80. value.key returns key
  81. serviceA(any) returns Future.value(Values(Seq(value)))
  82. var broker = new Broker[NodeHealth]
  83. val mockBuilder = (node: CacheNode, k: KetamaClientKey, internalBroker: Broker[NodeHealth], _: (Int, Duration)) => {
  84. broker = internalBroker
  85. services.get(node).get
  86. }
  87. val ketamaClient = new KetamaClient(mutableGroup, KeyHasher.KETAMA, 160, (Int.MaxValue, Duration.Zero), Some(mockBuilder))
  88. Await.result(ketamaClient.get("foo"))
  89. there was one(serviceA).apply(any)
  90. broker !! NodeMarkedDead(nodeKeyA)
  91. "goes to secondary if primary is down" in {
  92. serviceB(Get(Seq(key))) returns Future.value(Values(Seq(value)))
  93. Await.result(ketamaClient.get("foo"))
  94. there was one(serviceB).apply(any)
  95. }
  96. "throws ShardNotAvailableException when no nodes available" in {
  97. broker !! NodeMarkedDead(nodeKeyB)
  98. Await.result(ketamaClient.get("foo")) must throwA[ShardNotAvailableException]
  99. }
  100. "brings back the dead node" in {
  101. serviceA(any) returns Future.value(Values(Seq(value)))
  102. broker !! NodeRevived(nodeKeyA)
  103. Await.result(ketamaClient.get("foo"))
  104. there was two(serviceA).apply(any)
  105. }
  106. "primary leaves and rejoins" in {
  107. mutableGroup.update(immutable.Set(nodeB)) // nodeA leaves
  108. serviceB(Get(Seq(key))) returns Future.value(Values(Seq(value)))
  109. Await.result(ketamaClient.get("foo"))
  110. there was one(serviceB).apply(any)
  111. mutableGroup.update(immutable.Set(nodeA, nodeB)) // nodeA joins
  112. serviceA(Get(Seq(key))) returns Future.value(Values(Seq(value)))
  113. Await.result(ketamaClient.get("foo"))
  114. there was two(serviceA).apply(any)
  115. }
  116. }
  117. }
  118. "RubyMemCacheClient" should {
  119. val client1 = mock[Client]
  120. val client2 = mock[Client]
  121. val client3 = mock[Client]
  122. val rubyMemCacheClient = new RubyMemCacheClient(Seq(client1, client2, client3))
  123. "pick the correct node" in {
  124. rubyMemCacheClient.clientOf("apple") must be_==(client1)
  125. rubyMemCacheClient.clientOf("banana") must be_==(client2)
  126. rubyMemCacheClient.clientOf("cow") must be_==(client1)
  127. rubyMemCacheClient.clientOf("dog") must be_==(client1)
  128. rubyMemCacheClient.clientOf("elephant") must be_==(client3)
  129. }
  130. "release" in {
  131. rubyMemCacheClient.release()
  132. there was one(client1).release()
  133. there was one(client2).release()
  134. there was one(client3).release()
  135. }
  136. }
  137. "PHPMemCacheClient" should {
  138. val client1 = mock[Client]
  139. val client2 = mock[Client]
  140. val client3 = mock[Client]
  141. val phpMemCacheClient = new PHPMemCacheClient(Array(client1, client2, client3), KeyHasher.FNV1_32)
  142. "pick the correct node" in {
  143. phpMemCacheClient.clientOf("apple") must be_==(client3)
  144. phpMemCacheClient.clientOf("banana") must be_==(client1)
  145. phpMemCacheClient.clientOf("cow") must be_==(client3)
  146. phpMemCacheClient.clientOf("dog") must be_==(client2)
  147. phpMemCacheClient.clientOf("elephant") must be_==(client2)
  148. }
  149. "release" in {
  150. phpMemCacheClient.release()
  151. there was one(client1).release()
  152. there was one(client2).release()
  153. there was one(client3).release()
  154. }
  155. }
  156. }