Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

initial import

  • Loading branch information...
commit e71a4a1f32b05f5ff012e97f98ecae5b73855328 1 parent 69a649e
@benmur authored
Showing with 1,120 additions and 0 deletions.
  1. +20 −0 LICENSE
  2. +21 −0 build.sbt
  3. +44 −0 src/main/scala/net/benmur/riemann/client/Destination.scala
  4. +71 −0 src/main/scala/net/benmur/riemann/client/DomainObjects.scala
  5. +32 −0 src/main/scala/net/benmur/riemann/client/EventDSL.scala
  6. +21 −0 src/main/scala/net/benmur/riemann/client/EventSenderDSL.scala
  7. +105 −0 src/main/scala/net/benmur/riemann/client/ReliableIO.scala
  8. +16 −0 src/main/scala/net/benmur/riemann/client/RiemannClient.scala
  9. +63 −0 src/main/scala/net/benmur/riemann/client/Serializers.scala
  10. +47 −0 src/main/scala/net/benmur/riemann/client/UnreliableIO.scala
  11. +85 −0 src/test/scala/net/benmur/riemann/client/EventDSLTest.scala
  12. +50 −0 src/test/scala/net/benmur/riemann/client/EventSenderDSLTest.scala
  13. +89 −0 src/test/scala/net/benmur/riemann/client/ReliableIOTest.scala
  14. +76 −0 src/test/scala/net/benmur/riemann/client/RiemannClientWithDestinationAPITest.scala
  15. +238 −0 src/test/scala/net/benmur/riemann/client/SerializersTest.scala
  16. +43 −0 src/test/scala/net/benmur/riemann/client/UnreliableIOTest.scala
  17. +36 −0 src/test/scala/net/benmur/riemann/client/testingsupport/SerializersFixture.scala
  18. +63 −0 src/test/scala/net/benmur/riemann/client/testingsupport/TestingTransportSupport.scala
View
20 LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2012 Rached Ben Mustapha
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
21 build.sbt
@@ -0,0 +1,21 @@
+name := "riemann-scala-client"
+
+version := "0.1"
+
+scalaVersion := "2.9.2"
+
+scalacOptions += "-deprecation"
+
+resolvers += "Clojars" at "http://clojars.org/repo"
+
+resolvers += "Akka" at "http://repo.akka.io/releases"
+
+libraryDependencies += "com.aphyr" % "riemann-java-client" % "0.0.6"
+
+libraryDependencies += "com.typesafe.akka" % "akka-actor" % "2.0.4"
+
+libraryDependencies += "org.scalatest" %% "scalatest" % "1.8" % "test"
+
+libraryDependencies += "org.scalamock" %% "scalamock-scalatest-support" % "latest.integration"
+
+libraryDependencies += "com.typesafe.akka" % "akka-testkit" % "2.0.4" % "test"
View
44 src/main/scala/net/benmur/riemann/client/Destination.scala
@@ -0,0 +1,44 @@
+package net.benmur.riemann.client
+
+import java.net.SocketAddress
+
+import scala.annotation.implicitNotFound
+import scala.collection.JavaConversions.iterableAsScalaIterable
+
+import akka.actor.ActorSystem
+import akka.dispatch.Future
+import akka.util.Timeout
+
+trait DestinationOps {
+ class DestinationBuilder[T <: TransportType](connectionBuilder: ConnectionBuilder[T])(implicit system: ActorSystem, timeout: Timeout) {
+ def to(where: SocketAddress): RiemannDestination[T] =
+ new RiemannDestination[T](EventPart(), connectionBuilder.buildConnection(where))
+ }
+
+ class RiemannDestination[T <: TransportType](baseEvent: EventPart, val connection: Connection[T])(implicit system: ActorSystem, timeout: Timeout)
+ extends Destination[T] {
+
+ def send(event: EventPart)(implicit messenger: SendOff[T]): Unit =
+ messenger.sendOff(connection, Write(
+ Serializers.serializeEventPartToProtoMsg(EventDSL.mergeEvents(baseEvent, event))))
+
+ def ask(event: EventPart)(implicit messenger: SendAndExpectFeedback[T]): Future[Either[RemoteError, List[EventPart]]] =
+ messenger.send(connection, Write(
+ Serializers.serializeEventPartToProtoMsg(EventDSL.mergeEvents(baseEvent, event))))
+
+ def send(events: Iterable[EventPart])(implicit messenger: SendOff[T]): Unit =
+ messenger.sendOff(connection, Write(
+ Serializers.serializeEventPartsToProtoMsg(events map (EventDSL.mergeEvents(baseEvent, _)))))
+
+ def ask(events: Iterable[EventPart])(implicit messenger: SendAndExpectFeedback[T]): Future[Either[RemoteError, List[EventPart]]] =
+ messenger.send(connection, Write(
+ Serializers.serializeEventPartsToProtoMsg(events map (EventDSL.mergeEvents(baseEvent, _)))))
+
+ def ask(query: Query)(implicit messenger: SendAndExpectFeedback[T]): Future[Either[RemoteError, List[EventPart]]] =
+ messenger.send(connection, Write(
+ Serializers.serializeQueryToProtoMsg(query)))
+
+ def withValues(event: EventPart): RiemannDestination[T] =
+ new RiemannDestination[T](EventDSL.mergeEvents(baseEvent, event), connection)
+ }
+}
View
71 src/main/scala/net/benmur/riemann/client/DomainObjects.scala
@@ -0,0 +1,71 @@
+package net.benmur.riemann.client
+
+import java.io.{ InputStream, OutputStream }
+import java.net.SocketAddress
+import scala.annotation.implicitNotFound
+import com.aphyr.riemann.Proto
+import akka.actor.ActorSystem
+import akka.dispatch.Future
+import akka.util.Timeout
+import scala.collection.mutable.WrappedArray
+
+case class EventPart(
+ host: Option[String] = None,
+ service: Option[String] = None,
+ state: Option[String] = None,
+ time: Option[Long] = None,
+ description: Option[String] = None,
+ tags: Iterable[String] = Nil,
+ metric: Option[AnyVal] = None,
+ ttl: Option[Float] = None)
+
+case class Query(q: String)
+
+case class Write(m: Proto.Msg)
+
+case class RemoteError(message: String) extends Throwable
+
+trait TransportType {
+ type SocketFactory
+}
+trait Reliable extends TransportType {
+ type SocketFactory = SocketAddress => ConnectedSocketWrapper
+}
+trait Unreliable extends TransportType {
+ type SocketFactory = SocketAddress => UnconnectedSocketWrapper
+}
+
+trait Connection[T <: TransportType]
+
+trait ConnectedSocketWrapper {
+ def inputStream: InputStream
+ def outputStream: OutputStream
+}
+
+trait UnconnectedSocketWrapper {
+ def send(data: WrappedArray[Byte]): Unit
+}
+
+trait Destination[T <: TransportType] {
+ def send(event: EventPart)(implicit messenger: SendOff[T]): Unit
+ def ask(event: EventPart)(implicit messenger: SendAndExpectFeedback[T]): Future[Either[RemoteError, List[EventPart]]]
+ def send(events: Iterable[EventPart])(implicit messenger: SendOff[T]): Unit
+ def ask(events: Iterable[EventPart])(implicit messenger: SendAndExpectFeedback[T]): Future[Either[RemoteError, List[EventPart]]]
+ def ask(query: Query)(implicit messenger: SendAndExpectFeedback[T]): Future[Either[RemoteError, List[EventPart]]]
+ def withValues(event: EventPart): Destination[T]
+}
+
+@implicitNotFound(msg = "No way of building a connection to Riemann of type ${T}.")
+trait ConnectionBuilder[T <: TransportType] {
+ def buildConnection(where: SocketAddress, factory: Option[T#SocketFactory] = None, dispatcherId: Option[String] = None)(implicit system: ActorSystem, timeout: Timeout): Connection[T]
+}
+
+@implicitNotFound(msg = "Connection type ${T} does not allow sending to Riemann because there is no implicit in scope returning a implementation of SendOff[${T}].")
+trait SendOff[T <: TransportType] {
+ def sendOff(connection: Connection[T], command: Write): Unit
+}
+
+@implicitNotFound(msg = "Connection type ${T} does not allow getting feedback from Riemann.")
+trait SendAndExpectFeedback[T <: TransportType] {
+ def send(connection: Connection[T], command: Write)(implicit system: ActorSystem, timeout: Timeout): Future[Either[RemoteError, List[EventPart]]]
+}
View
32 src/main/scala/net/benmur/riemann/client/EventDSL.scala
@@ -0,0 +1,32 @@
+package net.benmur.riemann.client
+
+trait EventDSL {
+ def mergeEvents(e: EventPart, overlay: EventPart) = EventPart(
+ overlay.host orElse e.host,
+ overlay.service orElse e.service,
+ overlay.state orElse e.state,
+ overlay.time orElse e.time,
+ overlay.description orElse e.description,
+ (overlay.tags.toSet ++ e.tags).toSeq.sorted,
+ overlay.metric orElse e.metric,
+ overlay.ttl orElse e.ttl)
+
+ class EventPartCombinator(e: EventPart) {
+ def |(overlay: EventPart) = mergeEvents(e, overlay)
+ }
+
+ implicit def eventPartToEventPartCombinator(e: EventPart) = new EventPartCombinator(e)
+
+ def host(s: String) = EventPart(host = Some(s))
+ def service(s: String) = EventPart(service = Some(s))
+ def state(s: String) = EventPart(state = Some(s))
+ def time(l: Long) = EventPart(time = Some(l))
+ def description(s: String) = EventPart(description = Some(s))
+ def tags(s: String*) = EventPart(tags = s)
+ def metric(m: Long) = EventPart(metric = Some(m))
+ def metric(m: Float) = EventPart(metric = Some(m))
+ def metric(m: Double) = EventPart(metric = Some(m))
+ def ttl(f: Float) = EventPart(ttl = Some(f))
+}
+
+object EventDSL extends EventDSL
View
21 src/main/scala/net/benmur/riemann/client/EventSenderDSL.scala
@@ -0,0 +1,21 @@
+package net.benmur.riemann.client
+
+trait EventSenderDSL {
+ class EventSenderOff[T <: TransportType](e: EventPart)(implicit messenger: SendOff[T]) {
+ def |>>(d: Destination[T]) = d send e
+ }
+
+ class EventSender[T <: TransportType](e: EventPart)(implicit messenger: SendAndExpectFeedback[T]) {
+ def |><(d: Destination[T]) = d ask e
+ }
+
+ class QuerySender[T <: TransportType](q: Query)(implicit messenger: SendAndExpectFeedback[T]) {
+ def |><(d: Destination[T]) = d ask q
+ }
+
+ implicit def event2EventSenderOff[T <: TransportType](e: EventPart)(implicit messenger: SendOff[T]) = new EventSenderOff[T](e)
+ implicit def event2EventSender[T <: TransportType](e: EventPart)(implicit messenger: SendAndExpectFeedback[T]) = new EventSender[T](e)
+ implicit def query2QuerySender[T <: TransportType](q: Query)(implicit messenger: SendAndExpectFeedback[T]) = new QuerySender[T](q)
+}
+
+object EventSenderDSL extends EventSenderDSL
View
105 src/main/scala/net/benmur/riemann/client/ReliableIO.scala
@@ -0,0 +1,105 @@
+package net.benmur.riemann.client
+
+import java.io.{ DataInputStream, DataOutputStream }
+import java.net.{ Socket, SocketAddress }
+import java.util.concurrent.atomic.AtomicLong
+import com.aphyr.riemann.Proto
+import akka.actor.{ Actor, ActorLogging, ActorSystem, Props, actorRef2Scala }
+import akka.dispatch.Future
+import akka.pattern.ask
+import akka.util.Timeout
+import akka.dispatch.Promise
+import akka.actor.OneForOneStrategy
+import akka.actor.SupervisorStrategy._
+import akka.util.duration._
+import akka.actor.ActorRef
+import java.net.SocketException
+
+trait ReliableIO {
+ private val nClients = new AtomicLong(0L) // FIXME this should be more global
+
+ private[this] class ReliableConnectionActor(where: SocketAddress, factory: Reliable#SocketFactory, dispatcherId: Option[String])(implicit system: ActorSystem) extends Actor {
+ override val supervisorStrategy = OneForOneStrategy(maxNrOfRetries = 36000, withinTimeRange = 1 hour) { // This needs to be more reasonable
+ case _ => Restart
+ }
+
+ val props = {
+ val p = Props(new TcpConnectionActor(where, factory))
+ if (dispatcherId.isEmpty) p else p.withDispatcher(dispatcherId.get)
+ }
+
+ val ioActor = context.actorOf(props, "io")
+
+ def receive = {
+ case message => ioActor forward message
+ }
+ }
+
+ implicit object ReliableSendAndExpectFeedback extends SendAndExpectFeedback[Reliable] {
+ def send(connection: Connection[Reliable], command: Write)(implicit system: ActorSystem, timeout: Timeout): Future[Either[RemoteError, List[EventPart]]] =
+ connection match {
+ case rc: ReliableConnection =>
+ (rc.ioActor ask command).mapTo[Either[RemoteError, List[EventPart]]]
+ case c =>
+ Promise.successful(Left(RemoteError(
+ "don't know how to send data to " + c.getClass.getName)))
+ }
+ }
+
+ implicit object ReliableSendOff extends SendOff[Reliable] {
+ def sendOff(connection: Connection[Reliable], command: Write): Unit = connection match {
+ case rc: ReliableConnection =>
+ rc.ioActor tell command
+ case c =>
+ System.err.println(
+ "don't know how to send data to " + c.getClass.getName)
+ }
+ }
+
+ class TcpConnectionActor(where: SocketAddress, factory: Reliable#SocketFactory) extends Actor with ActorLogging {
+ val connection = factory(where)
+ val outputStream = new DataOutputStream(connection.outputStream)
+ val inputStream = new DataInputStream(connection.inputStream)
+ println("actor init")
+ def receive = {
+ case Write(msg) =>
+ try {
+ val ab = msg.toByteArray
+ outputStream writeInt ab.length
+ outputStream write ab
+ outputStream.flush
+ val buf = Array.ofDim[Byte](inputStream.readInt())
+ inputStream.readFully(buf)
+ sender ! Serializers.unserializeProtoMsg(Proto.Msg.parseFrom(buf))
+ } catch {
+ case e: SocketException => throw e
+ case exception =>
+ log.error(exception, "could not send or receive data")
+ sender ! Left(RemoteError(exception.getMessage()))
+ }
+ }
+ }
+
+ val makeTcpConnection: Reliable#SocketFactory = (addr) => {
+ val socket = new Socket()
+ socket.connect(addr)
+ new ConnectedSocketWrapper {
+ override def outputStream = socket.getOutputStream()
+ override def inputStream = socket.getInputStream()
+ }
+ }
+
+ class ReliableConnection(val ioActor: ActorRef) extends Connection[Reliable]
+
+ implicit object TwoWayConnectionBuilder extends ConnectionBuilder[Reliable] {
+ def buildConnection(where: SocketAddress, factory: Option[Reliable#SocketFactory] = None, dispatcherId: Option[String])(implicit system: ActorSystem, timeout: Timeout): Connection[Reliable] = {
+ val props = {
+ val p = Props(new ReliableConnectionActor(where, factory getOrElse makeTcpConnection, dispatcherId))
+ if (dispatcherId.isEmpty) p else p.withDispatcher(dispatcherId.get)
+ }
+ new ReliableConnection(system.actorOf(props, "riemann-tcp-client-" + nClients.incrementAndGet))
+ }
+ }
+}
+
+object ReliableIO extends ReliableIO
View
16 src/main/scala/net/benmur/riemann/client/RiemannClient.scala
@@ -0,0 +1,16 @@
+package net.benmur.riemann.client
+
+import akka.actor.ActorSystem
+import akka.util.Timeout
+
+object RiemannClient
+ extends EventDSL
+ with EventSenderDSL
+ with Serializers
+ with ReliableIO
+ with UnreliableIO
+ with DestinationOps {
+
+ def riemannConnectAs[T <: TransportType](implicit connectionBuilder: ConnectionBuilder[T], system: ActorSystem, timeout: Timeout): DestinationBuilder[T] =
+ new DestinationBuilder[T](connectionBuilder)
+}
View
63 src/main/scala/net/benmur/riemann/client/Serializers.scala
@@ -0,0 +1,63 @@
+package net.benmur.riemann.client
+
+import scala.Option.option2Iterable
+import scala.collection.JavaConversions.{ asJavaIterable, iterableAsScalaIterable }
+
+import com.aphyr.riemann.Proto
+
+trait Serializers {
+ def serializeQueryToProtoMsg(q: Query) = Proto.Msg.newBuilder
+ .setQuery(Proto.Query.newBuilder().setString(q.q))
+ .build
+
+ def serializeEventPartToProtoMsg(e: EventPart) = serializeEventPartsToProtoMsg(Some(e))
+
+ def serializeEventPartsToProtoMsg(ei: Iterable[EventPart]) = Proto.Msg.newBuilder
+ .addAllEvents(ei map convertOneEventPart)
+ .build
+
+ def unserializeProtoMsg(m: Proto.Msg): Either[RemoteError, List[EventPart]] = m.hasOk match {
+ case true if m.getOk => Right(m.getEventsList map convertProtoEventToEventPart toList)
+ case true => Left(RemoteError(m.getError))
+ case false => Left(RemoteError("Response has no status"))
+ }
+
+ private def convertOneEventPart(e: EventPart) = {
+ val b = Proto.Event.newBuilder
+ e.host foreach (b.setHost(_))
+ e.service foreach (b.setService(_))
+ e.state foreach (b.setState(_))
+ e.time foreach (b.setTime(_))
+ e.description foreach (b.setDescription(_))
+ e.tags foreach (b.addTags(_))
+ e.metric foreach (_ match {
+ case value: Long => b.setMetricSint64(value)
+ case value: Double => b.setMetricD(value)
+ case value: Float => b.setMetricF(value)
+ case v => System.err.println("Warning: don't know what to do with value " + v)
+ })
+ e.ttl foreach (b.setTtl(_))
+ b.build
+ }
+
+ private def convertProtoEventToEventPart(e: Proto.Event) = EventPart(
+ host = if (e.hasHost) Some(e.getHost()) else None,
+ service = if (e.hasService) Some(e.getService) else None,
+ state = if (e.hasState) Some(e.getState) else None,
+ time = if (e.hasTime) Some(e.getTime) else None,
+ description = if (e.hasDescription) Some(e.getDescription) else None,
+ tags = if (e.getTagsList.isEmpty) List() else e.getTagsList.toList,
+ metric = extractMetric(e),
+ ttl = if (e.hasTtl) Some(e.getTtl) else None)
+
+ private def extractMetric(e: Proto.Event) =
+ if (e.hasMetricD)
+ Some(e.getMetricD)
+ else if (e.hasMetricF)
+ Some(e.getMetricF)
+ else if (e.hasMetricSint64)
+ Some(e.getMetricSint64)
+ else None
+}
+
+object Serializers extends Serializers
View
47 src/main/scala/net/benmur/riemann/client/UnreliableIO.scala
@@ -0,0 +1,47 @@
+package net.benmur.riemann.client
+
+import java.net.{ DatagramPacket, DatagramSocket, SocketAddress }
+import java.util.concurrent.atomic.AtomicLong
+import akka.actor.{ Actor, ActorSystem, Props }
+import akka.util.Timeout
+import scala.collection.mutable.WrappedArray
+
+trait UnreliableIO {
+ private val nClients = new AtomicLong(0L) // FIXME this should be more global
+
+ class UnreliableConnection(where: SocketAddress, factory: Unreliable#SocketFactory, dispatcherId: Option[String] = None)(implicit system: ActorSystem) extends Connection[Unreliable] {
+ val props = {
+ val p = Props(new UnconnectedConnectionActor(where, factory))
+ if (dispatcherId.isEmpty) p else p.withDispatcher(dispatcherId.get)
+ }
+ val ioActor = system.actorOf(props, "riemann-udp-client-" + nClients.incrementAndGet)
+ }
+
+ implicit object UnreliableSendOff extends SendOff[Unreliable] {
+ def sendOff(connection: Connection[Unreliable], command: Write): Unit = connection match {
+ case uc: UnreliableConnection => uc.ioActor tell command
+ case c => System.err.println("don't know how to send data to " + c.getClass.getName)
+ }
+ }
+
+ private[this] class UnconnectedConnectionActor(where: SocketAddress, factory: Unreliable#SocketFactory) extends Actor {
+ val connection = factory(where)
+ def receive = {
+ case Write(msg) => connection send msg.toByteArray
+ }
+ }
+
+ val makeUdpConnection: Unreliable#SocketFactory = (addr) => {
+ val dest = new DatagramSocket(addr)
+ new UnconnectedSocketWrapper {
+ override def send(data: WrappedArray[Byte]) = dest send new DatagramPacket(data.array, data.length)
+ }
+ }
+
+ implicit object OneWayConnectionBuilder extends ConnectionBuilder[Unreliable] {
+ implicit def buildConnection(where: SocketAddress, factory: Option[Unreliable#SocketFactory], dispatcherId: Option[String])(implicit system: ActorSystem, timeout: Timeout): Connection[Unreliable] =
+ new UnreliableConnection(where, factory getOrElse makeUdpConnection, dispatcherId)
+ }
+}
+
+object UnreliableIO extends UnreliableIO
View
85 src/test/scala/net/benmur/riemann/client/EventDSLTest.scala
@@ -0,0 +1,85 @@
+package net.benmur.riemann.client
+
+import org.scalatest.FunSuite
+
+class EventDSLTest extends FunSuite {
+ import EventDSL._
+
+ test("provide an EventPart builder function for host") {
+ expect(EventPart(host = Some("h"))) {
+ host("h")
+ }
+ }
+
+ test("provide an EventPart builder function for service") {
+ expect(EventPart(service = Some("se"))) {
+ service("se")
+ }
+ }
+
+ test("provide an EventPart builder function for state") {
+ expect(EventPart(state = Some("st"))) {
+ state("st")
+ }
+ }
+
+ test("provide an EventPart builder function for time") {
+ expect(EventPart(time = Some(1234L))) {
+ time(1234L)
+ }
+ }
+
+ test("provide an EventPart builder function for description") {
+ expect(EventPart(description = Some("d"))) {
+ description("d")
+ }
+ }
+
+ test("provide an EventPart builder function for tags") {
+ expect(EventPart(tags = Array("t1", "t2"))) {
+ EventDSL.tags("t1", "t2")
+ }
+ }
+
+ test("provide an EventPart builder function for metric (float)") {
+ expect(EventPart(metric = Some(1.12f))) {
+ metric(1.12f)
+ }
+ }
+
+ test("provide an EventPart builder function for metric (double)") {
+ expect(EventPart(metric = Some(1.12))) {
+ metric(1.12)
+ }
+ }
+
+ test("provide an EventPart builder function for metric (long)") {
+ expect(EventPart(metric = Some(112L))) {
+ metric(112L)
+ }
+ }
+
+ test("provide an EventPart builder function for ttl") {
+ expect(EventPart(ttl = Some(10))) {
+ ttl(10)
+ }
+ }
+
+ test("EventParts combination merges tags") {
+ expect(EventPart(tags = Array("tag1", "tag2", "tag3"))) {
+ EventDSL.tags("tag1", "tag2") | EventDSL.tags() | EventDSL.tags("tag3") | EventDSL.tags("tag1")
+ }
+ }
+
+ test("provide a method to combine EventParts") {
+ val expected = EventPart(host = Some("server"), service = Some("service-name"),
+ state = Some("ok"), time = Some(1234L), description = Some("descript"),
+ tags = Array("tag1", "tag2"), metric = Some(112L), ttl = Some(10L))
+
+ expect(expected) {
+ ttl(10) | metric(112) | EventDSL.tags("tag1", "tag2") | description("descript") |
+ time(1234L) | state("discarded-state") | state("ok") | service("service-name") |
+ host("server")
+ }
+ }
+}
View
50 src/test/scala/net/benmur/riemann/client/EventSenderDSLTest.scala
@@ -0,0 +1,50 @@
+package net.benmur.riemann.client
+
+import org.scalatest.{ BeforeAndAfterAll, FunSuite }
+import com.aphyr.riemann.Proto
+import akka.actor.ActorSystem
+import net.benmur.riemann.client.testingsupport.TestingTransportSupport
+
+class EventSenderDSLTest extends FunSuite with BeforeAndAfterAll {
+ import TestingTransportSupport._
+ import EventSenderDSL._
+
+ implicit val system = ActorSystem()
+
+ override def afterAll {
+ system.shutdown
+ }
+
+ def makeDestination = {
+ val conn = new TestingTransportConnection
+ val dest = new RiemannDestination[TestingTransport](EventPart(), conn)
+ (conn, dest)
+ }
+
+ test("DSL operator to send an event without expecting a result") {
+ val (conn, dest) = makeDestination
+
+ expect(Write(protoMsgEvent)) {
+ event |>> dest
+ conn.sentOff
+ }
+ }
+
+ test("DSL operator to send operator to send an event expecting a status") {
+ val (conn, dest) = makeDestination
+
+ expect(Write(protoMsgEvent)) {
+ event |>< dest
+ conn.sentExpect
+ }
+ }
+
+ test("DSL operator to send operator to send a query expecting a status") {
+ val (conn, dest) = makeDestination
+
+ expect(Write(protoMsgQuery)) {
+ Query("true") |>< dest
+ conn.sentExpect
+ }
+ }
+}
View
89 src/test/scala/net/benmur/riemann/client/ReliableIOTest.scala
@@ -0,0 +1,89 @@
+package net.benmur.riemann.client
+
+import org.scalatest.FunSuite
+import net.benmur.riemann.client.testingsupport.TestingTransportSupport
+import akka.actor.ActorSystem
+import java.net.InetSocketAddress
+import java.net.Socket
+import org.scalatest.BeforeAndAfterAll
+import org.scalamock.scalatest.MockFactory
+import org.scalamock.ProxyMockFactory
+import org.scalatest.matchers.ShouldMatchers
+import java.net.SocketAddress
+import akka.testkit.CallingThreadDispatcher
+import org.scalamock.annotation.mock
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.io.DataInputStream
+import java.io.DataOutputStream
+import com.aphyr.riemann.Proto
+import akka.dispatch.Await
+import akka.util.duration._
+
+class ReliableIOTest extends FunSuite
+ with BeforeAndAfterAll
+ with MockFactory
+ with ProxyMockFactory
+ with ShouldMatchers {
+
+ import ReliableIO._
+ import TestingTransportSupport._
+
+ implicit val system = ActorSystem()
+ val address = new InetSocketAddress(0)
+
+ override def afterAll {
+ system.shutdown
+ }
+
+ test("sending a protobuf Msg") {
+ val in = Array.ofDim[Byte](256)
+ val ios = new ByteArrayInputStream(in)
+ val oos = new ByteArrayOutputStream()
+
+ val wrapper = mock[ConnectedSocketWrapper]
+ wrapper expects 'outputStream returning oos once;
+ wrapper expects 'inputStream returning ios once
+
+ val socketFactory = mockFunction[SocketAddress, ConnectedSocketWrapper]
+ socketFactory expects address returning wrapper once
+
+ val conn = implicitly[ConnectionBuilder[Reliable]].buildConnection(address, Some(socketFactory), Some(CallingThreadDispatcher.Id))
+ implicitly[SendOff[Reliable]].sendOff(conn, Write(protoMsgEvent))
+
+ val out = oos.toByteArray
+ val outRef = protoMsgEvent.toByteArray
+ new DataInputStream(new ByteArrayInputStream(out)).readInt should be === outRef.length
+ out.slice(4, out.length) should be === outRef
+ }
+
+ test("sending a protobuf Msg, with feedback") {
+ val response = Proto.Msg.newBuilder.setOk(true).build
+ val responseBytes = response.toByteArray
+
+ val outBuilder = new ByteArrayOutputStream()
+ val outBuilderData = new DataOutputStream(outBuilder)
+ outBuilderData.writeInt(responseBytes.length)
+ outBuilderData.write(responseBytes)
+
+ val oos = new ByteArrayOutputStream()
+
+ val wrapper = mock[ConnectedSocketWrapper]
+ wrapper expects 'outputStream returning oos once;
+ wrapper expects 'inputStream returning new ByteArrayInputStream(outBuilder.toByteArray) once
+
+ val socketFactory = mockFunction[SocketAddress, ConnectedSocketWrapper]
+ socketFactory expects address returning wrapper once
+
+ val conn = implicitly[ConnectionBuilder[Reliable]].buildConnection(address, Some(socketFactory), Some(CallingThreadDispatcher.Id))
+ val respFuture = implicitly[SendAndExpectFeedback[Reliable]].send(conn, Write(protoMsgEvent))
+
+ val out = oos.toByteArray
+ val outRef = protoMsgEvent.toByteArray
+ new DataInputStream(new ByteArrayInputStream(out)).readInt should be === outRef.length
+ out.slice(4, out.length) should be === outRef
+
+ val resp = Await.result(respFuture, 1 second)
+ resp should be === Right(Nil)
+ }
+}
View
76 src/test/scala/net/benmur/riemann/client/RiemannClientWithDestinationAPITest.scala
@@ -0,0 +1,76 @@
+package net.benmur.riemann.client
+
+import org.scalatest.FunSuite
+import net.benmur.riemann.client.testingsupport.TestingTransportSupport
+import akka.actor.ActorSystem
+import org.scalatest.BeforeAndAfterAll
+import java.net.InetSocketAddress
+import org.scalatest.matchers.ShouldMatchers
+
+class RiemannClientWithDestinationAPITest extends FunSuite with BeforeAndAfterAll with ShouldMatchers {
+ import RiemannClient._
+ import TestingTransportSupport._
+
+ implicit val system = ActorSystem()
+ val address = new InetSocketAddress(0)
+
+ override def afterAll {
+ system.shutdown
+ }
+
+ test("entry point to create a connection (pristine state)") {
+ val dest = riemannConnectAs[TestingTransport] to address
+ val conn = dest.connection.asInstanceOf[TestingTransportConnection]
+
+ conn.where should be theSameInstanceAs address
+ conn.sentOff should be === null
+ }
+
+ test("entry point to create a connection (sending an event)") {
+ val dest = riemannConnectAs[TestingTransport] to address
+ val conn = dest.connection.asInstanceOf[TestingTransportConnection]
+
+ dest send event
+ conn.sentOff should be === Write(protoMsgEvent)
+ }
+
+ test("entry point to create a connection (sending multiple events)") {
+ val dest = riemannConnectAs[TestingTransport] to address
+ val conn = dest.connection.asInstanceOf[TestingTransportConnection]
+
+ dest send List(event, event2)
+ conn.sentOff should be === Write(protoMsgEvents)
+ }
+
+ test("entry point to create a connection (sending an event expecting feedback)") {
+ val dest = riemannConnectAs[TestingTransport] to address
+ val conn = dest.connection.asInstanceOf[TestingTransportConnection]
+
+ dest ask event
+ conn.sentExpect should be === Write(protoMsgEvent)
+ }
+
+ test("entry point to create a connection (sending multiple events expecting feedback)") {
+ val dest = riemannConnectAs[TestingTransport] to address
+ val conn = dest.connection.asInstanceOf[TestingTransportConnection]
+
+ dest ask List(event, event2)
+ conn.sentExpect should be === Write(protoMsgEvents)
+ }
+
+ test("entry point to create a connection (sending an query)") {
+ val dest = riemannConnectAs[TestingTransport] to address
+ val conn = dest.connection.asInstanceOf[TestingTransportConnection]
+
+ dest ask Query("true")
+ conn.sentExpect should be === Write(protoMsgQuery)
+ }
+
+ test("entry point to create a connection (sending an event, combining default EventPart values)") {
+ val dest = riemannConnectAs[TestingTransport] to address withValues (host("h") | service("s"))
+ val conn = dest.connection.asInstanceOf[TestingTransportConnection]
+
+ dest send state("ok")
+ conn.sentOff should be === Write(protoMsgEvent)
+ }
+}
View
238 src/test/scala/net/benmur/riemann/client/SerializersTest.scala
@@ -0,0 +1,238 @@
+package net.benmur.riemann.client
+import org.scalatest.FunSuite
+import com.aphyr.riemann.Proto
+import net.benmur.riemann.client.testingsupport.SerializersFixture
+import scala.collection.JavaConversions.asJavaIterable
+
+class SerializersTest extends FunSuite {
+ import Serializers._
+ import SerializersFixture._
+
+ test("out: convert a full EventPart to a protobuf Msg") {
+ val expected = Proto.Msg.newBuilder.addEvents(protobufEvent1).build
+
+ expect(expected) {
+ serializeEventPartToProtoMsg(event1)
+ }
+ }
+
+ test("out: convert an empty EventPart to a protobuf Msg") {
+ val expected = Proto.Msg.newBuilder.addEvents(
+ Proto.Event.newBuilder).build
+
+ expect(expected) {
+ serializeEventPartToProtoMsg(EventPart())
+ }
+ }
+
+ test("out: convert an EventPart with only host to a protobuf Msg") {
+ val expected = Proto.Msg.newBuilder.addEvents(
+ Proto.Event.newBuilder.setHost("host")).build
+
+ expect(expected) {
+ serializeEventPartToProtoMsg(EventPart(host = Some("host")))
+ }
+ }
+
+ test("out: convert an EventPart with only service to a protobuf Msg") {
+ val expected = Proto.Msg.newBuilder.addEvents(
+ Proto.Event.newBuilder.setService("service")).build
+
+ expect(expected) {
+ serializeEventPartToProtoMsg(EventPart(service = Some("service")))
+ }
+ }
+
+ test("out: convert an EventPart with only state to a protobuf Msg") {
+ val expected = Proto.Msg.newBuilder.addEvents(
+ Proto.Event.newBuilder.setState("state")).build
+
+ expect(expected) {
+ serializeEventPartToProtoMsg(EventPart(state = Some("state")))
+ }
+ }
+
+ test("out: convert an EventPart with only time to a protobuf Msg") {
+ val expected = Proto.Msg.newBuilder.addEvents(
+ Proto.Event.newBuilder.setTime(1234L)).build
+
+ expect(expected) {
+ serializeEventPartToProtoMsg(EventPart(time = Some(1234L)))
+ }
+ }
+
+ test("out: convert an EventPart with only description to a protobuf Msg") {
+ val expected = Proto.Msg.newBuilder.addEvents(
+ Proto.Event.newBuilder.setDescription("description")).build
+
+ expect(expected) {
+ serializeEventPartToProtoMsg(EventPart(description = Some("description")))
+ }
+ }
+
+ test("out: convert an EventPart with only tags to a protobuf Msg") {
+ val expected = Proto.Msg.newBuilder.addEvents(
+ Proto.Event.newBuilder.addAllTags(List("tag1"))).build
+
+ expect(expected) {
+ serializeEventPartToProtoMsg(EventPart(tags = List("tag1")))
+ }
+ }
+
+ test("out: convert an EventPart with only metric (long) to a protobuf Msg") {
+ val expected = Proto.Msg.newBuilder.addEvents(
+ Proto.Event.newBuilder.setMetricSint64(1234L)).build
+
+ expect(expected) {
+ serializeEventPartToProtoMsg(EventPart(metric = Some(1234L)))
+ }
+ }
+
+ test("out: convert an EventPart with only metric (double) to a protobuf Msg") {
+ val expected = Proto.Msg.newBuilder.addEvents(
+ Proto.Event.newBuilder.setMetricD(1234.9)).build
+
+ expect(expected) {
+ serializeEventPartToProtoMsg(EventPart(metric = Some(1234.9)))
+ }
+ }
+
+ test("out: convert an EventPart with only metric (float) to a protobuf Msg") {
+ val expected = Proto.Msg.newBuilder.addEvents(
+ Proto.Event.newBuilder.setMetricF(1234.9f)).build
+
+ expect(expected) {
+ serializeEventPartToProtoMsg(EventPart(metric = Some(1234.9f)))
+ }
+ }
+
+ test("out: convert an EventPart with only ttl to a protobuf Msg") {
+ val expected = Proto.Msg.newBuilder.addEvents(
+ Proto.Event.newBuilder.setTtl(1234L)).build
+
+ expect(expected) {
+ serializeEventPartToProtoMsg(EventPart(ttl = Some(1234L)))
+ }
+ }
+
+ test("out: convert an Iterable of full EventParts to a protobuf Msg") {
+ val expected = Proto.Msg.newBuilder.addEvents(protobufEvent1).addEvents(protobufEvent2).build
+
+ expect(expected) {
+ serializeEventPartsToProtoMsg(List(event1, event2))
+ }
+ }
+
+ test("out: convert a Query to a protobuf Msg") {
+ expect(Proto.Msg.newBuilder.setQuery(Proto.Query.newBuilder.setString("true")).build) {
+ serializeQueryToProtoMsg(Query("true"))
+ }
+ }
+
+ test("in: convert a protobuf Msg response with an ok status") {
+ expect(Right(Nil)) {
+ unserializeProtoMsg(Proto.Msg.newBuilder.setOk(true).build)
+ }
+ }
+
+ test("in: convert a protobuf Msg response with a non-ok status and an error message") {
+ expect(Left(RemoteError("meh"))) {
+ unserializeProtoMsg(Proto.Msg.newBuilder.setOk(false).setError("meh").build)
+ }
+ }
+
+ test("in: convert a failed Query result from a protobuf Msg with events") {
+ expect(Left(RemoteError("meh"))) {
+ unserializeProtoMsg(Proto.Msg.newBuilder.setOk(false).setError("meh").addEvents(protobufEvent1).build)
+ }
+ }
+
+ test("in: convert a successful Query result from a protobuf Msg to multiple EventParts") {
+ expect(Right(List(event1))) {
+ unserializeProtoMsg(Proto.Msg.newBuilder.setOk(true).addEvents(protobufEvent1).build)
+ }
+ }
+
+ test("in: convert Query result with missing ok from a protobuf Msg to RemoteError") {
+ expect(Left(RemoteError("Response has no status"))) {
+ unserializeProtoMsg(Proto.Msg.newBuilder.addEvents(protobufEvent1).build)
+ }
+ }
+
+ test("in: convert a protobuf Msg with empty Event to a Right(List(EventPart))") {
+ expect(Right(List(EventPart()))) {
+ unserializeProtoMsg(Proto.Msg.newBuilder.setOk(true).addEvents(
+ Proto.Event.newBuilder).build)
+ }
+ }
+
+ test("in: convert a protobuf Msg with Event with only host to a Right(List(EventPart))") {
+ expect(Right(List(EventPart(host = Some("host"))))) {
+ unserializeProtoMsg(Proto.Msg.newBuilder.setOk(true).addEvents(
+ Proto.Event.newBuilder.setHost("host")).build)
+ }
+ }
+
+ test("in: convert a protobuf Msg with Event with only service to a Right(List(EventPart))") {
+ expect(Right(List(EventPart(service = Some("service"))))) {
+ unserializeProtoMsg(Proto.Msg.newBuilder.setOk(true).addEvents(
+ Proto.Event.newBuilder.setService("service")).build)
+ }
+ }
+
+ test("in: convert a protobuf Msg with Event with only state to a Right(List(EventPart))") {
+ expect(Right(List(EventPart(state = Some("state"))))) {
+ unserializeProtoMsg(Proto.Msg.newBuilder.setOk(true).addEvents(
+ Proto.Event.newBuilder.setState("state")).build)
+ }
+ }
+
+ test("in: convert a protobuf Msg with Event with only time to a Right(List(EventPart))") {
+ expect(Right(List(EventPart(time = Some(1234L))))) {
+ unserializeProtoMsg(Proto.Msg.newBuilder.setOk(true).addEvents(
+ Proto.Event.newBuilder.setTime(1234L)).build)
+ }
+ }
+
+ test("in: convert a protobuf Msg with Event with only description to a Right(List(EventPart))") {
+ expect(Right(List(EventPart(description = Some("description"))))) {
+ unserializeProtoMsg(Proto.Msg.newBuilder.setOk(true).addEvents(
+ Proto.Event.newBuilder.setDescription("description")).build)
+ }
+ }
+
+ test("in: convert a protobuf Msg with Event with only tags to a Right(List(EventPart))") {
+ expect(Right(List(EventPart(tags = List("tag1", "tag2"))))) {
+ unserializeProtoMsg(Proto.Msg.newBuilder.setOk(true).addEvents(
+ Proto.Event.newBuilder.addAllTags(List("tag1", "tag2"))).build)
+ }
+ }
+
+ test("in: convert a protobuf Msg with Event with only metric (long) to a Right(List(EventPart))") {
+ expect(Right(List(EventPart(metric = Some(1234L))))) {
+ unserializeProtoMsg(Proto.Msg.newBuilder.setOk(true).addEvents(
+ Proto.Event.newBuilder.setMetricSint64(1234L)).build)
+ }
+ }
+
+ test("in: convert a protobuf Msg with Event with only metric (float) to a Right(List(EventPart))") {
+ expect(Right(List(EventPart(metric = Some(1234.0f))))) {
+ unserializeProtoMsg(Proto.Msg.newBuilder.setOk(true).addEvents(
+ Proto.Event.newBuilder.setMetricF(1234.0f)).build)
+ }
+ }
+
+ test("in: convert a protobuf Msg with Event with only metric (double) to a Right(List(EventPart))") {
+ expect(Right(List(EventPart(metric = Some(1234.1: Double))))) {
+ unserializeProtoMsg(Proto.Msg.newBuilder.setOk(true).addEvents(
+ Proto.Event.newBuilder.setMetricD(1234.1: Double)).build)
+ }
+ }
+
+ test("in: convert a protobuf Msg with Event with only ttl to a Right(List(EventPart))") {
+ expect(Right(List(EventPart(ttl = Some(1234L))))) {
+ unserializeProtoMsg(Proto.Msg.newBuilder.setOk(true).addEvents(
+ Proto.Event.newBuilder.setTtl(1234L)).build)
+ }
+ }
+}
View
43 src/test/scala/net/benmur/riemann/client/UnreliableIOTest.scala
@@ -0,0 +1,43 @@
+package net.benmur.riemann.client
+
+import java.net.{ InetSocketAddress, SocketAddress }
+
+import scala.annotation.implicitNotFound
+import scala.collection.mutable.WrappedArray
+
+import org.scalamock.ProxyMockFactory
+import org.scalamock.scalatest.MockFactory
+import org.scalatest.{ BeforeAndAfterAll, FunSuite }
+import org.scalatest.matchers.ShouldMatchers
+
+import akka.actor.ActorSystem
+import akka.testkit.CallingThreadDispatcher
+import net.benmur.riemann.client.testingsupport.TestingTransportSupport
+
+class UnreliableIOTest extends FunSuite
+ with BeforeAndAfterAll
+ with MockFactory
+ with ProxyMockFactory
+ with ShouldMatchers {
+
+ import UnreliableIO._
+ import TestingTransportSupport._
+
+ implicit val system = ActorSystem()
+ val address = new InetSocketAddress(0)
+
+ override def afterAll {
+ system.shutdown
+ }
+
+ test("send a protobuf Msg") {
+ val socket = mock[UnconnectedSocketWrapper]
+ socket expects 'send withArguments (WrappedArray.make(protoMsgEvent.toByteArray)) once
+
+ val socketFactory = mockFunction[SocketAddress, UnconnectedSocketWrapper]
+ socketFactory expects address returning socket once
+
+ val conn = implicitly[ConnectionBuilder[Unreliable]].buildConnection(address, Some(socketFactory), Some(CallingThreadDispatcher.Id))
+ implicitly[SendOff[Unreliable]].sendOff(conn, Write(protoMsgEvent))
+ }
+}
View
36 src/test/scala/net/benmur/riemann/client/testingsupport/SerializersFixture.scala
@@ -0,0 +1,36 @@
+package net.benmur.riemann.client.testingsupport
+import scala.collection.JavaConversions.asJavaIterable
+
+import com.aphyr.riemann.Proto
+
+import net.benmur.riemann.client.EventPart
+
+object SerializersFixture {
+ val event1 = EventPart(host = Some("server"), service = Some("service-name"),
+ state = Some("ok"), time = Some(1234L), description = Some("descript"),
+ tags = Array("tag1", "tag2"), metric = Some(112L), ttl = Some(10L))
+
+ val event2 = EventPart(host = Some("server2"), service = Some("service-name2"),
+ state = Some("crit"), time = Some(12340L), description = Some("descript2"),
+ tags = Array("tag3"), metric = Some(1120L), ttl = Some(100L))
+
+ val protobufEvent1 = Proto.Event.newBuilder
+ .setHost("server")
+ .setService("service-name")
+ .setState("ok")
+ .setTime(1234L)
+ .addAllTags(Seq("tag1", "tag2"))
+ .setTtl(10)
+ .setMetricSint64(112)
+ .setDescription("descript")
+
+ val protobufEvent2 = Proto.Event.newBuilder
+ .setHost("server2")
+ .setService("service-name2")
+ .setState("crit")
+ .setTime(12340L)
+ .addAllTags(Seq("tag3"))
+ .setTtl(100)
+ .setMetricSint64(1120)
+ .setDescription("descript2")
+}
View
63 src/test/scala/net/benmur/riemann/client/testingsupport/TestingTransportSupport.scala
@@ -0,0 +1,63 @@
+package net.benmur.riemann.client.testingsupport
+
+import akka.util.Timeout
+import akka.util.duration.intToDurationInt
+import net.benmur.riemann.client.{ Connection, EventSenderDSL, SendOff, TransportType, Write }
+import com.aphyr.riemann.Proto
+import net.benmur.riemann.client.DestinationOps
+import net.benmur.riemann.client.EventPart
+import akka.actor.ActorSystem
+import akka.dispatch.Future
+import net.benmur.riemann.client.SendAndExpectFeedback
+import akka.dispatch.Promise
+import net.benmur.riemann.client.RemoteError
+import java.net.SocketAddress
+import net.benmur.riemann.client.ConnectionBuilder
+import java.net.InetSocketAddress
+
+trait TestingTransportSupport {
+ import EventSenderDSL._
+
+ implicit val timeout = Timeout(1 millisecond)
+
+ implicit object TestingTransportSendOff extends SendOff[TestingTransport] {
+ def sendOff(connection: Connection[TestingTransport], command: Write): Unit = connection match {
+ case uc: TestingTransportConnection => uc.sentOff = command
+ }
+ }
+
+ implicit object TestingTransportSendAndExpectFeedback extends SendAndExpectFeedback[TestingTransport] {
+ def send(connection: Connection[TestingTransport], command: Write)(implicit system: ActorSystem, timeout: Timeout): Future[Either[RemoteError, List[EventPart]]] =
+ connection match {
+ case tc: TestingTransportConnection =>
+ tc.sentExpect = command
+ Promise.successful(Right(Nil))
+ case c => Promise.successful(Left(RemoteError("bad connection type")))
+ }
+ }
+
+ class TestingTransportConnection(val where: SocketAddress = new InetSocketAddress(0)) extends Connection[TestingTransport] {
+ var sentOff: Write = _
+ var sentExpect: Write = _
+ }
+
+ implicit object TestingTransportConnectionBuilder extends ConnectionBuilder[TestingTransport] {
+ implicit def buildConnection(where: SocketAddress, factory: Option[TestingTransport#SocketFactory] = None, dispatcherId: Option[String])(implicit system: ActorSystem, timeout: Timeout): Connection[TestingTransport] =
+ new TestingTransportConnection(where)
+ }
+
+ trait TestingTransport extends TransportType {
+ type SocketFactory = SocketAddress => Unit
+ }
+
+ val event = EventPart(host = Some("h"), service = Some("s"), state = Some("ok"))
+ val event2 = EventPart(host = Some("h"), service = Some("s2"), state = Some("ok"))
+ val protoQuery = Proto.Query.newBuilder().setString("true")
+ val protoEvent = Proto.Event.newBuilder().setHost("h").setService("s").setState("ok")
+ val protoEvent2 = Proto.Event.newBuilder().setHost("h").setService("s2").setState("ok")
+ val protoMsgEvent = Proto.Msg.newBuilder.addEvents(protoEvent).build
+ val protoMsgEvents = Proto.Msg.newBuilder.addEvents(protoEvent).addEvents(protoEvent2).build
+ val protoMsgQuery = Proto.Msg.newBuilder.setQuery(protoQuery).build
+}
+
+object TestingTransportSupport extends TestingTransportSupport with DestinationOps
Please sign in to comment.
Something went wrong with that request. Please try again.