Describe how to write cluster test, see #2437
This commit is contained in:
parent
b88f8e4b85
commit
7bb125385b
5 changed files with 160 additions and 41 deletions
|
|
@ -141,6 +141,8 @@ Be aware of that using auto-down implies that two separate clusters will
|
|||
automatically be formed in case of network partition. That might be
|
||||
desired by some applications but not by others.
|
||||
|
||||
.. _cluster_subscriber:
|
||||
|
||||
Subscribe to Cluster Events
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
|
@ -339,6 +341,64 @@ service nodes and 1 client::
|
|||
|
||||
.. note:: The above example, especially the last part, will be simplified when the cluster handles automatic actor partitioning.
|
||||
|
||||
|
||||
How to Test
|
||||
^^^^^^^^^^^
|
||||
|
||||
:ref:`multi_node_testing` is useful for testing cluster applications.
|
||||
|
||||
Set up your project according to the instructions in :ref:`multi_node_testing` and :ref:`multi_jvm_testing`, i.e.
|
||||
add the ``sbt-multi-jvm`` plugin and the dependency to ``akka-remote-tests-experimental``.
|
||||
|
||||
First, as described in :ref:`multi_node_testing`, we need some scaffolding to configure the ``MultiNodeSpec``.
|
||||
Define the participating roles and their :ref:`cluster_configuration` in an object extending ``MultiNodeConfig``:
|
||||
|
||||
.. includecode:: ../../akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala
|
||||
:include: MultiNodeConfig
|
||||
:exclude: router-lookup-config
|
||||
|
||||
Define one concrete test class for each role/node. These will be instantiated on the different nodes (JVMs). They can be
|
||||
implemented differently, but often they are the same and extend an abstract test class, as illustrated here.
|
||||
|
||||
.. includecode:: ../../akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala#concrete-tests
|
||||
|
||||
Note the naming convention of these classes. The name of the classes must end with ``MultiJvmNode1``, ``MultiJvmNode2``
|
||||
and so on. It's possible to define another suffix to be used by the ``sbt-multi-jvm``, but the default should be
|
||||
fine in most cases.
|
||||
|
||||
Then the abstract ``MultiNodeSpec``, which takes the ``MultiNodeConfig`` as constructor parameter.
|
||||
|
||||
.. includecode:: ../../akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala#abstract-test
|
||||
|
||||
Most of this can of course be extracted to a separate trait to avoid repeating this in all your tests. This example
|
||||
is using `Scalatest <http://www.scalatest.org/>`_, but similar can be done with other testing frameworks.
|
||||
|
||||
Typically you begin your test by starting up the cluster and let the members join, and create some actors.
|
||||
That can be done like this:
|
||||
|
||||
.. includecode:: ../../akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala#startup-cluster
|
||||
|
||||
From the test you interact with the cluster using the ``Cluster`` extension, e.g. ``join``.
|
||||
|
||||
.. includecode:: ../../akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala#join
|
||||
|
||||
Notice how the `testActor` from :ref:`testkit <akka-testkit>` is added as :ref:`subscriber <cluster_subscriber>`
|
||||
to cluster changes and then waiting for certain events, such as in this case all members becoming 'Up'.
|
||||
|
||||
The above code was running for all roles (JVMs). ``runOn`` is a convenient utility to declare that a certain block
|
||||
of code should only run for a specific role.
|
||||
|
||||
.. includecode:: ../../akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala#test-statsService
|
||||
|
||||
Once again we take advantage of the facilities in :ref:`testkit <akka-testkit>` to verify expected behavior.
|
||||
Here using ``testActor`` as sender (via ``ImplicitSender``) and verifing the reply with ``expectMsgPF``.
|
||||
|
||||
In the above code you can see ``node(third)``, which is useful facility to get the root actor reference of
|
||||
the actor system for a specific role. This can also be used to grab the ``akka.actor.Address`` of that node.
|
||||
|
||||
.. includecode:: ../../akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala#addresses
|
||||
|
||||
|
||||
.. _cluster_jmx:
|
||||
|
||||
JMX
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@ import scala.concurrent.util.duration._
|
|||
|
||||
import com.typesafe.config.ConfigFactory
|
||||
|
||||
import StatsSampleSpec.first
|
||||
import StatsSampleSpec.third
|
||||
import org.scalatest.BeforeAndAfterAll
|
||||
import org.scalatest.WordSpec
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
|
||||
import akka.actor.Props
|
||||
import akka.actor.RootActorPath
|
||||
import akka.cluster.Cluster
|
||||
|
|
@ -16,10 +18,9 @@ import akka.cluster.ClusterEvent.CurrentClusterState
|
|||
import akka.cluster.ClusterEvent.MemberUp
|
||||
import akka.remote.testkit.MultiNodeConfig
|
||||
import akka.remote.testkit.MultiNodeSpec
|
||||
import akka.remote.testkit.STMultiNodeSpec
|
||||
import akka.testkit.ImplicitSender
|
||||
|
||||
object StatsSampleSingleMasterSpec extends MultiNodeConfig {
|
||||
object StatsSampleSingleMasterSpecConfig extends MultiNodeConfig {
|
||||
// register the named roles (nodes) of the test
|
||||
val first = role("first")
|
||||
val second = role("second")
|
||||
|
|
@ -50,17 +51,21 @@ object StatsSampleSingleMasterSpec extends MultiNodeConfig {
|
|||
}
|
||||
|
||||
// need one concrete test class per node
|
||||
class StatsSampleSingleMasterMultiJvmNode1 extends StatsSampleSingleMasterSpec
|
||||
class StatsSampleSingleMasterMultiJvmNode2 extends StatsSampleSingleMasterSpec
|
||||
class StatsSampleSingleMasterMultiJvmNode3 extends StatsSampleSingleMasterSpec
|
||||
class StatsSampleSingleMasterSpecMultiJvmNode1 extends StatsSampleSingleMasterSpec
|
||||
class StatsSampleSingleMasterSpecMultiJvmNode2 extends StatsSampleSingleMasterSpec
|
||||
class StatsSampleSingleMasterSpecMultiJvmNode3 extends StatsSampleSingleMasterSpec
|
||||
|
||||
abstract class StatsSampleSingleMasterSpec extends MultiNodeSpec(StatsSampleSingleMasterSpec)
|
||||
with STMultiNodeSpec with ImplicitSender {
|
||||
abstract class StatsSampleSingleMasterSpec extends MultiNodeSpec(StatsSampleSingleMasterSpecConfig)
|
||||
with WordSpec with MustMatchers with BeforeAndAfterAll with ImplicitSender {
|
||||
|
||||
import StatsSampleSpec._
|
||||
import StatsSampleSingleMasterSpecConfig._
|
||||
|
||||
override def initialParticipants = roles.size
|
||||
|
||||
override def beforeAll() = multiNodeSpecBeforeAll()
|
||||
|
||||
override def afterAll() = multiNodeSpecAfterAll()
|
||||
|
||||
"The stats sample with single master" must {
|
||||
"illustrate how to startup cluster" in within(10 seconds) {
|
||||
Cluster(system).subscribe(testActor, classOf[MemberUp])
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package sample.cluster.stats
|
|||
|
||||
import language.postfixOps
|
||||
import scala.concurrent.util.duration._
|
||||
import com.typesafe.config.ConfigFactory
|
||||
|
||||
import akka.actor.Props
|
||||
import akka.actor.RootActorPath
|
||||
|
|
@ -11,12 +10,12 @@ import akka.cluster.Member
|
|||
import akka.cluster.MemberStatus
|
||||
import akka.cluster.ClusterEvent.CurrentClusterState
|
||||
import akka.cluster.ClusterEvent.MemberUp
|
||||
import akka.remote.testkit.MultiNodeConfig
|
||||
import akka.remote.testkit.MultiNodeSpec
|
||||
import akka.remote.testkit.STMultiNodeSpec
|
||||
import akka.testkit.ImplicitSender
|
||||
|
||||
object StatsSampleSpec extends MultiNodeConfig {
|
||||
//#MultiNodeConfig
|
||||
import akka.remote.testkit.MultiNodeConfig
|
||||
import com.typesafe.config.ConfigFactory
|
||||
|
||||
object StatsSampleSpecConfig extends MultiNodeConfig {
|
||||
// register the named roles (nodes) of the test
|
||||
val first = role("first")
|
||||
val second = role("second")
|
||||
|
|
@ -44,49 +43,95 @@ object StatsSampleSpec extends MultiNodeConfig {
|
|||
"""))
|
||||
|
||||
}
|
||||
//#MultiNodeConfig
|
||||
|
||||
//#concrete-tests
|
||||
// need one concrete test class per node
|
||||
class StatsSampleMultiJvmNode1 extends StatsSampleSpec
|
||||
class StatsSampleMultiJvmNode2 extends StatsSampleSpec
|
||||
class StatsSampleMultiJvmNode3 extends StatsSampleSpec
|
||||
class StatsSampleSpecMultiJvmNode1 extends StatsSampleSpec
|
||||
class StatsSampleSpecMultiJvmNode2 extends StatsSampleSpec
|
||||
class StatsSampleSpecMultiJvmNode3 extends StatsSampleSpec
|
||||
//#concrete-tests
|
||||
|
||||
abstract class StatsSampleSpec extends MultiNodeSpec(StatsSampleSpec)
|
||||
with STMultiNodeSpec with ImplicitSender {
|
||||
//#abstract-test
|
||||
import org.scalatest.BeforeAndAfterAll
|
||||
import org.scalatest.WordSpec
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import akka.remote.testkit.MultiNodeSpec
|
||||
import akka.testkit.ImplicitSender
|
||||
|
||||
import StatsSampleSpec._
|
||||
abstract class StatsSampleSpec extends MultiNodeSpec(StatsSampleSpecConfig)
|
||||
with WordSpec with MustMatchers with BeforeAndAfterAll
|
||||
with ImplicitSender {
|
||||
|
||||
import StatsSampleSpecConfig._
|
||||
|
||||
override def initialParticipants = roles.size
|
||||
|
||||
override def beforeAll() = multiNodeSpecBeforeAll()
|
||||
|
||||
override def afterAll() = multiNodeSpecAfterAll()
|
||||
|
||||
//#abstract-test
|
||||
|
||||
"The stats sample" must {
|
||||
|
||||
//#startup-cluster
|
||||
"illustrate how to startup cluster" in within(10 seconds) {
|
||||
Cluster(system).subscribe(testActor, classOf[MemberUp])
|
||||
expectMsgClass(classOf[CurrentClusterState])
|
||||
|
||||
Cluster(system) join node(first).address
|
||||
//#addresses
|
||||
val firstAddress = node(first).address
|
||||
val secondAddress = node(second).address
|
||||
val thirdAddress = node(third).address
|
||||
//#addresses
|
||||
|
||||
//#join
|
||||
Cluster(system) join firstAddress
|
||||
//#join
|
||||
|
||||
system.actorOf(Props[StatsWorker], "statsWorker")
|
||||
system.actorOf(Props[StatsService], "statsService")
|
||||
|
||||
expectMsgAllOf(
|
||||
MemberUp(Member(node(first).address, MemberStatus.Up)),
|
||||
MemberUp(Member(node(second).address, MemberStatus.Up)),
|
||||
MemberUp(Member(node(third).address, MemberStatus.Up)))
|
||||
MemberUp(Member(firstAddress, MemberStatus.Up)),
|
||||
MemberUp(Member(secondAddress, MemberStatus.Up)),
|
||||
MemberUp(Member(thirdAddress, MemberStatus.Up)))
|
||||
|
||||
Cluster(system).unsubscribe(testActor)
|
||||
|
||||
testConductor.enter("all-up")
|
||||
}
|
||||
//#startup-cluster
|
||||
|
||||
"show usage of the statsService" in within(5 seconds) {
|
||||
|
||||
val service = system.actorFor(RootActorPath(node(third).address) / "user" / "statsService")
|
||||
//#test-statsService
|
||||
"show usage of the statsService from one node" in within(5 seconds) {
|
||||
runOn(second) {
|
||||
val service = system.actorFor(node(third) / "user" / "statsService")
|
||||
service ! StatsJob("this is the text that will be analyzed")
|
||||
val meanWordLength = expectMsgPF() {
|
||||
case StatsResult(meanWordLength) ⇒ meanWordLength
|
||||
}
|
||||
meanWordLength must be(3.875 plusOrMinus 0.001)
|
||||
}
|
||||
|
||||
testConductor.enter("done-2")
|
||||
}
|
||||
//#test-statsService
|
||||
|
||||
"show usage of the statsService from all nodes" in within(5 seconds) {
|
||||
val service = system.actorFor(node(third) / "user" / "statsService")
|
||||
service ! StatsJob("this is the text that will be analyzed")
|
||||
val meanWordLength = expectMsgPF() {
|
||||
case StatsResult(meanWordLength) ⇒ meanWordLength
|
||||
}
|
||||
meanWordLength must be(3.875 plusOrMinus 0.001)
|
||||
|
||||
testConductor.enter("done")
|
||||
testConductor.enter("done-2")
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -5,14 +5,17 @@ import scala.concurrent.util.duration._
|
|||
|
||||
import com.typesafe.config.ConfigFactory
|
||||
|
||||
import org.scalatest.BeforeAndAfterAll
|
||||
import org.scalatest.WordSpec
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
|
||||
import akka.actor.Props
|
||||
import akka.cluster.Cluster
|
||||
import akka.remote.testkit.MultiNodeConfig
|
||||
import akka.remote.testkit.MultiNodeSpec
|
||||
import akka.remote.testkit.STMultiNodeSpec
|
||||
import akka.testkit.ImplicitSender
|
||||
|
||||
object TransformationSampleSpec extends MultiNodeConfig {
|
||||
object TransformationSampleSpecConfig extends MultiNodeConfig {
|
||||
// register the named roles (nodes) of the test
|
||||
val frontend1 = role("frontend1")
|
||||
val frontend2 = role("frontend2")
|
||||
|
|
@ -31,19 +34,23 @@ object TransformationSampleSpec extends MultiNodeConfig {
|
|||
}
|
||||
|
||||
// need one concrete test class per node
|
||||
class TransformationSampleMultiJvmNode1 extends TransformationSampleSpec
|
||||
class TransformationSampleMultiJvmNode2 extends TransformationSampleSpec
|
||||
class TransformationSampleMultiJvmNode3 extends TransformationSampleSpec
|
||||
class TransformationSampleMultiJvmNode4 extends TransformationSampleSpec
|
||||
class TransformationSampleMultiJvmNode5 extends TransformationSampleSpec
|
||||
class TransformationSampleSpecMultiJvmNode1 extends TransformationSampleSpec
|
||||
class TransformationSampleSpecMultiJvmNode2 extends TransformationSampleSpec
|
||||
class TransformationSampleSpecMultiJvmNode3 extends TransformationSampleSpec
|
||||
class TransformationSampleSpecMultiJvmNode4 extends TransformationSampleSpec
|
||||
class TransformationSampleSpecMultiJvmNode5 extends TransformationSampleSpec
|
||||
|
||||
abstract class TransformationSampleSpec extends MultiNodeSpec(TransformationSampleSpec)
|
||||
with STMultiNodeSpec with ImplicitSender {
|
||||
abstract class TransformationSampleSpec extends MultiNodeSpec(TransformationSampleSpecConfig)
|
||||
with WordSpec with MustMatchers with BeforeAndAfterAll with ImplicitSender {
|
||||
|
||||
import TransformationSampleSpec._
|
||||
import TransformationSampleSpecConfig._
|
||||
|
||||
override def initialParticipants = roles.size
|
||||
|
||||
override def beforeAll() = multiNodeSpecBeforeAll()
|
||||
|
||||
override def afterAll() = multiNodeSpecAfterAll()
|
||||
|
||||
"The transformation sample" must {
|
||||
"illustrate how to start first frontend" in {
|
||||
runOn(frontend1) {
|
||||
|
|
|
|||
|
|
@ -330,14 +330,14 @@ object AkkaBuild extends Build {
|
|||
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"),
|
||||
dependencies = Seq(cluster, remoteTests % "test", testkit % "test"),
|
||||
settings = defaultSettings ++ multiJvmSettings ++ Seq(
|
||||
libraryDependencies ++= Dependencies.clusterSample,
|
||||
// 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
|
||||
)
|
||||
|
|
@ -594,6 +594,8 @@ object Dependencies {
|
|||
val docs = Seq(Test.scalatest, Test.junit, Test.junitIntf)
|
||||
|
||||
val zeroMQ = Seq(protobuf, zeroMQClient, Test.scalatest, Test.junit)
|
||||
|
||||
val clusterSample = Seq(Test.scalatest)
|
||||
}
|
||||
|
||||
object Dependency {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue