From 67f0de87b186a0afb6568fbf3bf3b344d59ff761 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Antonsson?= Date: Thu, 20 Sep 2012 21:50:35 +0200 Subject: [PATCH] Multi Node Testing Docs and Sample. See #2349 --- akka-docs/dev/multi-jvm-testing.rst | 15 +- akka-docs/dev/multi-node-testing.rst | 183 ++++++++++++++++++ akka-docs/experimental/index.rst | 2 +- .../images/akka-remote-testconductor.png | Bin 18288 -> 22235 bytes .../sample/multinode/MultiNodeSample.scala | 59 ++++++ .../sample/multinode/STMultiNodeSpec.scala | 25 +++ project/AkkaBuild.scala | 41 ++-- project/plugins.sbt | 9 +- 8 files changed, 311 insertions(+), 23 deletions(-) create mode 100644 akka-docs/dev/multi-node-testing.rst create mode 100644 akka-samples/akka-sample-multi-node/src/multi-jvm/scala/sample/multinode/MultiNodeSample.scala create mode 100644 akka-samples/akka-sample-multi-node/src/test/scala/sample/multinode/STMultiNodeSpec.scala diff --git a/akka-docs/dev/multi-jvm-testing.rst b/akka-docs/dev/multi-jvm-testing.rst index 7658324a74..77f3b5b626 100644 --- a/akka-docs/dev/multi-jvm-testing.rst +++ b/akka-docs/dev/multi-jvm-testing.rst @@ -2,7 +2,7 @@ .. _multi-jvm-testing: ################### - Multi-JVM Testing + Multi JVM Testing ################### Support for running applications (objects with main methods) and @@ -12,13 +12,13 @@ ScalaTest tests in multiple JVMs. Setup ===== -The multi-JVM testing is an sbt plugin that you can find here: +The multi JVM testing is an sbt plugin that you can find here: http://github.com/typesafehub/sbt-multi-jvm -You can add it as a plugin by adding the following to your project/plugins.sbt:: +You can add it as a plugin by adding the following to your project/plugins.sbt: - addSbtPlugin("com.typesafe.sbt" % "sbt-multi-jvm" % "0.3.0") +.. includecode:: ../../project/plugins.sbt#sbt-multi-jvm You can then add multi-JVM testing to ``project/Build.scala`` by including the ``MultiJvm`` settings and config. For example, here is an example of how the akka-remote-tests project adds @@ -223,3 +223,10 @@ classpath. Here is a similar example to the one above but using ScalaTest:: To run just these tests you would call ``multi-jvm:test-only sample.Spec`` at the sbt prompt. + +Multi Node Additions +==================== + +There has also been some additions made to the ``SbtMultiJvm`` plugin to accomodate the +:ref:`experimental ` module :ref:`multi node testing `, +described in that section. diff --git a/akka-docs/dev/multi-node-testing.rst b/akka-docs/dev/multi-node-testing.rst new file mode 100644 index 0000000000..b61fc8e5e9 --- /dev/null +++ b/akka-docs/dev/multi-node-testing.rst @@ -0,0 +1,183 @@ +.. _multi_node_testing: + +################### + Multi Node Testing +################### + +.. note:: This module is :ref:`experimental `. This document describes how to use the features + implemented so far. More features are coming in Akka Coltrane. Track progress of the Coltrane milestone in + `Assembla `_. + +Preparing Your Project for Multi Node Testing +============================================= + +The multi node testing is a separate jar file. Make sure that you have the following dependency in your project: + +.. parsed-literal:: + + "com.typesafe.akka" %% "akka-remote-tests-experimental" % "2.1-SNAPSHOT" + +If you are using the latest nightly build you should pick a timestamped Akka version from +``_. Don't use ``SNAPSHOT``. Note that the +Scala version |scalaVersion| is part of the artifactId. + +Multi Node Testing Concepts +=========================== + +Multi node testing in Akka consist of three main parts. + +* `The Test Conductor`_. that coordinates and controls the nodes under test. +* `The Multi Node Spec`_. that is a convenience wrapper for starting the ``TestConductor`` and letting all + nodes connect to it. +* `The SbtMultiJvm Plugin`_. that starts tests in multiple JVMs possibly on multiple machines. + +The Test Conductor +================== + +The basis for the multi node testing is the ``TestConductor``. It is an Akka Extension that plugs in to the +network stack and it is used to coordinate the nodes participating in the test and provides several features +including: + +* Node Address Lookup: Finding out the full path to another test node (No need to share configuration between + test nodes) +* Node Barrier Coordination: Waiting for other nodes at named barriers. +* Network Failure Injection: Throttling traffic, dropping packets, unplugging and plugging nodes back in. + +This is a schematic overview of the test conductor. + +.. image:: ../images/akka-remote-testconductor.png + +The test conductor server is responsible for coordinating barriers and sending commands to the test conductor +clients that act upon them, e.g. throttling network traffic to/from another client. + +The Multi Node Spec +=================== + +The Multi Node Spec consists of two parts. The ``MultiNodeConfig`` that is responsible for common +configuration and enumerating and naming the nodes under test. The ``MultiNodeSpec`` that contains all the +convenience functions for making the test nodes interact with each other. + +The setup of the ``MultiNodeSpec`` is configured through properties that you set on all JVMs that's going to run a +node under test. + +These are the available properties: + * ``multinode.max-nodes`` + + The maximum number of nodes that a test can have. + + * ``multinode.host`` + + The host name or IP for this node. Must be resolvable using InetAddress.getByName. + + * ``multinode.port`` + + The port number for this node. Defaults to 0 which will use a random port. + + * ``multinode.server-host`` + + The host name or IP for the server node. Must be resolvable using InetAddress.getByName. + + * ``multinode.server-port`` + + The port number for the server node. Defaults to 4711. + + * ``multinode.index`` + + The index of this node in the sequence of roles defined for the test. The index 0 is special and that machine + will be the server. All failure injection and throttling must be done from this node. + +The SbtMultiJvm Plugin +====================== + +The :ref:`SbtMultiJvm Plugin ` has been updated to be able to run multi node tests, by +automatically generating the relevant ``multinode.*`` properties. This means that you can easily run multinode tests +on a single machine by just running them as normal multi jvm tests. + +Multi Node Specific Additions ++++++++++++++++++++++++++++++ + +The plugin also has a number of new ``multi-node-*`` sbt tasks and settings to support running tests on multiple +machines. The necessary test classes and dependencies are packaged for distribution to other machines with +`SbtAssembly `_ into a jar file with a name on the format +``_--multi-jvm-assembly.jar`` + +.. note:: + + To be able to distribute and kick off the tests on multiple machines, it is assumed that both host and target + systems are POSIX like systems with `ssh` and `rsync` available. + +These are the available sbt multi-node settings: + * ``multiNodeHosts`` + + A sequence of hosts to use for running the test, on the form ``user@host:java`` where host is the only required + part. Will override settings from file. + + * ``multiNodeHostsFileName`` + + A file to use for reading in the hosts to use for running the test. One per line on the same format as above. + Defaults to ``multi-node-test.hosts`` in the base project directory. + + * ``multiNodeTargetDirName`` + + A name for the directory on the target machine, where to copy the jar file. Defaults to ``multi-node-test`` in + the base directory of the ssh user used to rsync the jar file. + + * ``multiNodeJavaName`` + + The name of the default Java executable on the target machines. Defaults to ``java``. + +Here are some examples of how you define hosts: + * ``localhost`` + + The current user on localhost using the default java. + + * ``user1@host1`` + + User ``user1`` on host ``host1`` with the default java. + + * ``user2@host2:/usr/lib/jvm/java-7-openjdk-amd64/bin/java`` + + User ``user2`` on host ``host2`` using java 7. + + * ``host3:/usr/lib/jvm/java-6-openjdk-amd64/bin/java`` + + The current user on host ``host3`` using java 6. + +Running the Test in Multi Node Mode ++++++++++++++++++++++++++++++++++++ + +To run all the multi node test in multi-node mode (i.e. distributing the jar files and rkicking off the tests +remotely) from inside sbt, use the ``multi-node-test`` task: + +.. code-block:: none + + multi-node-test + +To run individual tests use the ``multi-node-test-only`` task: + +.. code-block:: none + + multi-node-test-only akka.remote.RandomRoutedRemoteActor + +More than one test name can be listed to run multiple specific tests. Tab completion in sbt makes it easy to +complete the test names. + +A Multi Node Testing Example +============================ + +First we need some scaffolding to hook up the `MultiNodeSpec` with your favorite test framework. Lets define a trait +``STMultiNodeSpec`` that uses ScalaTest to start and stop ``MultiNodeSpec``. + +.. includecode:: ../../akka-samples/akka-sample-multi-node/src/test/scala/sample/multinode/STMultiNodeSpec.scala#example + +Then we need to define a configuration. Lets use two nodes ``"node1`` and ``"node2"`` and call it +``MultiNodeSampleConfig``. + +.. includecode:: ../../akka-samples/akka-sample-multi-node/src/multi-jvm/scala/sample/multinode/MultiNodeSample.scala + :include: package,config + +And then finally to the node test code. That starts the two nodes, and demostrates a barrier, and a remote actor +message send/receive. + +.. includecode:: ../../akka-samples/akka-sample-multi-node/src/multi-jvm/scala/sample/multinode/MultiNodeSample.scala + :include: package,spec diff --git a/akka-docs/experimental/index.rst b/akka-docs/experimental/index.rst index aaf7bab438..e9fdaf5935 100644 --- a/akka-docs/experimental/index.rst +++ b/akka-docs/experimental/index.rst @@ -24,4 +24,4 @@ of the module over time. :maxdepth: 2 ../cluster/index - + ../dev/multi-node-testing diff --git a/akka-docs/images/akka-remote-testconductor.png b/akka-docs/images/akka-remote-testconductor.png index b21353832670a12eb8130292416df62c6f1209a7..87710a4cc578fd8b734383395383cea9e1fadd0d 100644 GIT binary patch literal 22235 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 literal 18288 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 DUVNTH diff --git a/akka-samples/akka-sample-multi-node/src/multi-jvm/scala/sample/multinode/MultiNodeSample.scala b/akka-samples/akka-sample-multi-node/src/multi-jvm/scala/sample/multinode/MultiNodeSample.scala new file mode 100644 index 0000000000..c17bbde104 --- /dev/null +++ b/akka-samples/akka-sample-multi-node/src/multi-jvm/scala/sample/multinode/MultiNodeSample.scala @@ -0,0 +1,59 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ +//#package +package sample.multinode +//#package + +//#config +import akka.remote.testkit.MultiNodeConfig + +object MultiNodeSampleConfig extends MultiNodeConfig { + val node1 = role("node1") + val node2 = role("node2") +} +//#config + +//#spec +import akka.remote.testkit.MultiNodeSpec +import akka.testkit.ImplicitSender +import akka.actor.{Props, Actor} + +class MultiNodeSampleSpecMultiJvmNode1 extends MultiNodeSample +class MultiNodeSampleSpecMultiJvmNode2 extends MultiNodeSample + +class MultiNodeSample extends MultiNodeSpec(MultiNodeSampleConfig) + with STMultiNodeSpec with ImplicitSender { + + import MultiNodeSampleConfig._ + + def initialParticipants = 2 + + "A MultiNodeSample" must { + + "wait for all nodes to enter a barrier" in { + enterBarrier("startup") + } + + "send to and receive from a remote node" in { + runOn(node1) { + enterBarrier("deployed") + val ponger = system.actorFor(node(node2).toString + "user/ponger") + ponger ! "ping" + expectMsg("pong") + } + + runOn(node2) { + system.actorOf(Props(new Actor { + def receive = { + case "ping" => sender ! "pong" + } + }), "ponger") + enterBarrier("deployed") + } + + enterBarrier("finished") + } + } +} +//#spec diff --git a/akka-samples/akka-sample-multi-node/src/test/scala/sample/multinode/STMultiNodeSpec.scala b/akka-samples/akka-sample-multi-node/src/test/scala/sample/multinode/STMultiNodeSpec.scala new file mode 100644 index 0000000000..114b1559cd --- /dev/null +++ b/akka-samples/akka-sample-multi-node/src/test/scala/sample/multinode/STMultiNodeSpec.scala @@ -0,0 +1,25 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ +//#example +package sample.multinode + +//#imports +import org.scalatest.{ BeforeAndAfterAll, WordSpec } +import org.scalatest.matchers.MustMatchers +import akka.remote.testkit.MultiNodeSpecCallbacks +//#imports + +//#trait +/** + * Hooks up MultiNodeSpec with ScalaTest + */ +trait STMultiNodeSpec extends MultiNodeSpecCallbacks + with WordSpec with MustMatchers with BeforeAndAfterAll { + + override def beforeAll() = multiNodeSpecBeforeAll() + + override def afterAll() = multiNodeSpecAfterAll() +} +//#trait +//#example diff --git a/project/AkkaBuild.scala b/project/AkkaBuild.scala index 597c7a8b99..e7f742e933 100644 --- a/project/AkkaBuild.scala +++ b/project/AkkaBuild.scala @@ -286,60 +286,71 @@ object AkkaBuild extends Build { id = "akka-samples", base = file("akka-samples"), settings = parentSettings, - aggregate = Seq(camelSample, fsmSample, helloSample, helloKernelSample, remoteSample, clusterSample) + aggregate = Seq(camelSample, fsmSample, helloSample, helloKernelSample, remoteSample, clusterSample, multiNodeSample) ) lazy val camelSample = Project( id = "akka-sample-camel", base = file("akka-samples/akka-sample-camel"), dependencies = Seq(actor, camel), - settings = defaultSettings ++ Seq( - libraryDependencies ++= Dependencies.camelSample, - publishArtifact in Compile := false - ) + settings = sampleSettings ++ Seq(libraryDependencies ++= Dependencies.camelSample) ) lazy val fsmSample = Project( id = "akka-sample-fsm", base = file("akka-samples/akka-sample-fsm"), dependencies = Seq(actor), - settings = defaultSettings ++ Seq( publishArtifact in Compile := false ) + settings = sampleSettings ) lazy val helloSample = Project( id = "akka-sample-hello", base = file("akka-samples/akka-sample-hello"), dependencies = Seq(actor), - settings = defaultSettings ++ Seq( publishArtifact in Compile := false ) + settings = sampleSettings ) lazy val helloKernelSample = Project( id = "akka-sample-hello-kernel", base = file("akka-samples/akka-sample-hello-kernel"), dependencies = Seq(kernel), - settings = defaultSettings ++ Seq( publishArtifact in Compile := false ) + settings = sampleSettings ) lazy val remoteSample = Project( id = "akka-sample-remote", base = file("akka-samples/akka-sample-remote"), dependencies = Seq(actor, remote, kernel), - settings = defaultSettings ++ Seq( publishArtifact in Compile := false ) + settings = sampleSettings ) lazy val clusterSample = Project( id = "akka-sample-cluster-experimental", base = file("akka-samples/akka-sample-cluster"), dependencies = Seq(cluster, remoteTests % "compile;test->test;multi-jvm->multi-jvm", testkit % "test->test"), - settings = defaultSettings ++ multiJvmSettings ++ Seq( + settings = sampleSettings ++ multiJvmSettings ++ Seq( // disable parallel tests parallelExecution in Test := false, extraOptions in MultiJvm <<= (sourceDirectory in MultiJvm) { src => (name: String) => (src ** (name + ".conf")).get.headOption.map("-Dakka.config=" + _.absolutePath).toSeq }, scalatestOptions in MultiJvm := defaultMultiJvmScalatestOptions, - jvmOptions in MultiJvm := defaultMultiJvmOptions, - publishArtifact in Compile := false + jvmOptions in MultiJvm := defaultMultiJvmOptions + ) + ) configs (MultiJvm) + + lazy val multiNodeSample = Project( + id = "akka-sample-multi-node-experimental", + base = file("akka-samples/akka-sample-multi-node"), + dependencies = Seq(remoteTests % "test", testkit % "test"), + settings = sampleSettings ++ multiJvmSettings ++ Seq( + libraryDependencies ++= Dependencies.multiNodeSample, + // disable parallel tests + parallelExecution in Test := false, + extraOptions in MultiJvm <<= (sourceDirectory in MultiJvm) { src => + (name: String) => (src ** (name + ".conf")).get.headOption.map("-Dakka.config=" + _.absolutePath).toSeq + }, + jvmOptions in MultiJvm := defaultMultiJvmOptions ) ) configs (MultiJvm) @@ -377,6 +388,10 @@ object AkkaBuild extends Build { publishArtifact in Compile := false ) + lazy val sampleSettings = defaultSettings ++ Seq( + publishArtifact in Compile := false + ) + val excludeTestNames = SettingKey[Seq[String]]("exclude-test-names") val excludeTestTags = SettingKey[Set[String]]("exclude-test-tags") val includeTestTags = SettingKey[Set[String]]("include-test-tags") @@ -593,6 +608,8 @@ object Dependencies { val docs = Seq(Test.scalatest, Test.junit, Test.junitIntf) val zeroMQ = Seq(protobuf, zeroMQClient, Test.scalatest, Test.junit) + + val multiNodeSample = Seq(Test.scalatest) } object Dependency { diff --git a/project/plugins.sbt b/project/plugins.sbt index d5d8cbcc0c..89712ab978 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,7 +1,10 @@ resolvers += Classpaths.typesafeResolver +// these comment markers are for including code into the docs +//#sbt-multi-jvm addSbtPlugin("com.typesafe.sbt" % "sbt-multi-jvm" % "0.3.0") +//#sbt-multi-jvm addSbtPlugin("com.typesafe.sbtscalariform" % "sbtscalariform" % "0.4.0") @@ -9,10 +12,4 @@ addSbtPlugin("com.typesafe.sbtosgi" % "sbtosgi" % "0.3.0") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.1.3") -resolvers ++= Seq( - // needed for sbt-assembly, which comes with sbt-multi-jvm - Resolver.url("sbtonline", url("http://scalasbt.artifactoryonline.com/scalasbt/sbt-plugin-releases"))(Resolver.ivyStylePatterns), - "less is" at "http://repo.lessis.me", - "coda" at "http://repo.codahale.com") - // addSbtPlugin("me.lessis" % "ls-sbt" % "0.1.1")