Repository navigation
Verify removal and add of new node incarnation in multi-dc, #23585 #23595
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -130,7 +130,8 @@ import scala.util.Random | |
| } | ||
|
|
||
| def dcMembers: SortedSet[Member] = | ||
| members.filter(_.dataCenter == selfDc) | ||
| if (latestGossip.isMultiDc) members.filter(_.dataCenter == selfDc) | ||
| else members | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
|
|
||
| def isLeader(node: UniqueAddress): Boolean = | ||
| leader.contains(node) | ||
|
|
@@ -159,17 +160,14 @@ import scala.util.Random | |
| def isInSameDc(node: UniqueAddress): Boolean = | ||
| node == selfUniqueAddress || latestGossip.member(node).dataCenter == selfDc | ||
|
|
||
| def membersInSameDc: immutable.SortedSet[Member] = | ||
| members.filter(_.dataCenter == selfDc) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good, seemed duplicated |
||
|
|
||
| def validNodeForGossip(node: UniqueAddress): Boolean = | ||
| node != selfUniqueAddress && | ||
| ((isInSameDc(node) && isReachableExcludingDownedObservers(node)) || | ||
| // if cross DC we need to check pairwise unreachable observation | ||
| overview.reachability.isReachable(selfUniqueAddress, node)) | ||
|
|
||
| def youngestMember: Member = { | ||
| val mbrs = membersInSameDc | ||
| val mbrs = dcMembers | ||
| require(mbrs.nonEmpty, "No youngest when no members") | ||
| mbrs.maxBy(m ⇒ if (m.upNumber == Int.MaxValue) 0 else m.upNumber) | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,20 +3,27 @@ | |
| */ | ||
| package akka.cluster | ||
|
|
||
| import akka.cluster.ClusterEvent.{ CurrentClusterState, DataCenterReachabilityEvent, ReachableDataCenter, UnreachableDataCenter } | ||
| import akka.cluster.ClusterEvent._ | ||
| import akka.remote.testconductor.RoleName | ||
| import akka.remote.testkit.{ MultiNodeConfig, MultiNodeSpec } | ||
| import akka.remote.transport.ThrottlerTransportAdapter.Direction | ||
| import akka.testkit.TestProbe | ||
| import com.typesafe.config.ConfigFactory | ||
|
|
||
| import scala.concurrent.duration._ | ||
| import akka.actor.ActorSystem | ||
| import akka.actor.Props | ||
| import akka.actor.Actor | ||
| import akka.actor.Deploy | ||
| import akka.actor.RootActorPath | ||
| import scala.concurrent.Await | ||
|
|
||
| object MultiDcSplitBrainMultiJvmSpec extends MultiNodeConfig { | ||
| val first = role("first") | ||
| val second = role("second") | ||
| val third = role("third") | ||
| val fourth = role("fourth") | ||
| val fifth = role("fifth") | ||
|
|
||
| commonConfig(ConfigFactory.parseString( | ||
| """ | ||
|
|
@@ -30,14 +37,19 @@ object MultiDcSplitBrainMultiJvmSpec extends MultiNodeConfig { | |
| heartbeat-interval = 1s | ||
| } | ||
| } | ||
| akka.cluster { | ||
| gossip-interval = 1s | ||
| leader-actions-interval = 1s | ||
| auto-down-unreachable-after = 1s | ||
| } | ||
| """).withFallback(MultiNodeClusterSpec.clusterConfig)) | ||
|
|
||
| nodeConfig(first, second)(ConfigFactory.parseString( | ||
| """ | ||
| akka.cluster.multi-data-center.self-data-center = "dc1" | ||
| """)) | ||
|
|
||
| nodeConfig(third, fourth)(ConfigFactory.parseString( | ||
| nodeConfig(third, fourth, fifth)(ConfigFactory.parseString( | ||
| """ | ||
| akka.cluster.multi-data-center.self-data-center = "dc2" | ||
| """)) | ||
|
|
@@ -49,6 +61,7 @@ class MultiDcSplitBrainMultiJvmNode1 extends MultiDcSplitBrainSpec | |
| class MultiDcSplitBrainMultiJvmNode2 extends MultiDcSplitBrainSpec | ||
| class MultiDcSplitBrainMultiJvmNode3 extends MultiDcSplitBrainSpec | ||
| class MultiDcSplitBrainMultiJvmNode4 extends MultiDcSplitBrainSpec | ||
| class MultiDcSplitBrainMultiJvmNode5 extends MultiDcSplitBrainSpec | ||
|
|
||
| abstract class MultiDcSplitBrainSpec | ||
| extends MultiNodeSpec(MultiDcSplitBrainMultiJvmSpec) | ||
|
|
@@ -57,7 +70,7 @@ abstract class MultiDcSplitBrainSpec | |
| import MultiDcSplitBrainMultiJvmSpec._ | ||
|
|
||
| val dc1 = List(first, second) | ||
| val dc2 = List(third, fourth) | ||
| val dc2 = List(third, fourth, fifth) | ||
| var barrierCounter = 0 | ||
|
|
||
| def splitDataCenters(notMembers: Set[RoleName]): Unit = { | ||
|
|
@@ -136,7 +149,7 @@ abstract class MultiDcSplitBrainSpec | |
|
|
||
| "be able to have a data center member join while there is inter data center split" in within(20.seconds) { | ||
| // introduce a split between data centers | ||
| splitDataCenters(notMembers = Set(fourth)) | ||
| splitDataCenters(notMembers = Set(fourth, fifth)) | ||
|
|
||
| runOn(fourth) { | ||
| cluster.join(third) | ||
|
|
@@ -152,7 +165,7 @@ abstract class MultiDcSplitBrainSpec | |
| } | ||
| enterBarrier("dc2-join-completed") | ||
|
|
||
| unsplitDataCenters(notMembers = Set.empty) | ||
| unsplitDataCenters(notMembers = Set(fifth)) | ||
|
|
||
| runOn(dc1: _*) { | ||
| awaitAssert(clusterView.members.collect { | ||
|
|
@@ -164,7 +177,7 @@ abstract class MultiDcSplitBrainSpec | |
| } | ||
|
|
||
| "be able to have data center member leave while there is inter data center split" in within(20.seconds) { | ||
| splitDataCenters(notMembers = Set.empty) | ||
| splitDataCenters(notMembers = Set(fifth)) | ||
|
|
||
| runOn(fourth) { | ||
| cluster.leave(fourth) | ||
|
|
@@ -175,13 +188,104 @@ abstract class MultiDcSplitBrainSpec | |
| } | ||
| enterBarrier("node-4-left") | ||
|
|
||
| unsplitDataCenters(notMembers = Set(fourth)) | ||
| unsplitDataCenters(notMembers = Set(fourth, fifth)) | ||
|
|
||
| runOn(first, second) { | ||
| awaitAssert(clusterView.members.filter(_.address == address(fourth)) should ===(Set.empty)) | ||
| } | ||
| enterBarrier("inter-data-center-split-2-done") | ||
| } | ||
|
|
||
| "be able to have data center member restart (same host:port) while there is inter data center split" in within(40.seconds) { | ||
| val subscribeProbe = TestProbe() | ||
| runOn(first, second, third, fifth) { | ||
| Cluster(system).subscribe(subscribeProbe.ref, InitialStateAsSnapshot, classOf[MemberUp], classOf[MemberRemoved]) | ||
| subscribeProbe.expectMsgType[CurrentClusterState] | ||
| } | ||
| enterBarrier("subscribed") | ||
| runOn(fifth) { | ||
| Cluster(system).join(third) | ||
| } | ||
| var fifthOriginalUniqueAddress: Option[UniqueAddress] = None | ||
| runOn(first, second, third, fifth) { | ||
| awaitAssert(clusterView.members.collect { | ||
| case m if m.dataCenter == "dc2" && m.status == MemberStatus.Up ⇒ m.address | ||
| } should ===(Set(address(third), address(fifth)))) | ||
| fifthOriginalUniqueAddress = clusterView.members.collectFirst { case m if m.address == address(fifth) ⇒ m.uniqueAddress } | ||
| } | ||
| enterBarrier("fifth-joined") | ||
|
|
||
| splitDataCenters(notMembers = Set(fourth)) | ||
|
|
||
| runOn(fifth) { | ||
| Cluster(system).shutdown() | ||
| } | ||
| runOn(third) { | ||
| awaitAssert(clusterView.members.collect { | ||
| case m if m.dataCenter == "dc2" ⇒ m.address | ||
| } should ===(Set(address(third)))) | ||
| } | ||
| enterBarrier("fifth-removed") | ||
|
|
||
| runOn(fifth) { | ||
| // we can't use any multi-jvm test facilities on fifth after this, because have to shutdown | ||
| // actor system to be able to start new with same port | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wish we had a better way to multi-jvm-test restarts :/ |
||
| val thirdAddress = address(third) | ||
| enterBarrier("fifth-waiting-for-termination") | ||
| Await.ready(system.whenTerminated, remaining) | ||
|
|
||
| val restartedSystem = ActorSystem( | ||
| system.name, | ||
| ConfigFactory.parseString(s""" | ||
| akka.remote.netty.tcp.port = ${Cluster(system).selfAddress.port.get} | ||
| akka.remote.artery.canonical.port = ${Cluster(system).selfAddress.port.get} | ||
| akka.coordinated-shutdown.terminate-actor-system = on | ||
| """).withFallback(system.settings.config)) | ||
| Cluster(restartedSystem).join(thirdAddress) | ||
| Await.ready(restartedSystem.whenTerminated, remaining) | ||
| } | ||
|
|
||
| // no multi-jvm test facilities on fifth after this | ||
| val remainingRoles = roles.filterNot(_ == fifth) | ||
|
|
||
| runOn(remainingRoles: _*) { | ||
| enterBarrier("fifth-waiting-for-termination") | ||
| } | ||
|
|
||
| runOn(first) { | ||
| for (dc1Node ← dc1; dc2Node ← dc2) { | ||
| testConductor.passThrough(dc1Node, dc2Node, Direction.Both).await | ||
| } | ||
| testConductor.shutdown(fifth) | ||
| } | ||
| runOn(remainingRoles: _*) { | ||
| enterBarrier("fifth-restarted") | ||
| } | ||
|
|
||
| runOn(first, second, third) { | ||
| awaitAssert(clusterView.members.collectFirst { | ||
| case m if m.dataCenter == "dc2" && m.address == fifthOriginalUniqueAddress.get.address ⇒ m.uniqueAddress | ||
| } should not be (fifthOriginalUniqueAddress)) // different uid | ||
| subscribeProbe.expectMsgType[MemberUp].member.uniqueAddress should ===(fifthOriginalUniqueAddress.get) | ||
| subscribeProbe.expectMsgType[MemberRemoved].member.uniqueAddress should ===(fifthOriginalUniqueAddress.get) | ||
| subscribeProbe.expectMsgType[MemberUp].member.address should ===(fifthOriginalUniqueAddress.get.address) | ||
| } | ||
| runOn(remainingRoles: _*) { | ||
| enterBarrier("fifth-re-joined") | ||
| } | ||
| runOn(first) { | ||
| // to shutdown the restartedSystem on fifth | ||
| Cluster(system).leave(fifthOriginalUniqueAddress.get.address) | ||
| } | ||
| runOn(first, second, third) { | ||
| awaitAssert(clusterView.members.map(_.address) should ===(Set(address(first), address(second), address(third)))) | ||
| } | ||
| runOn(remainingRoles: _*) { | ||
| Thread.sleep(5000) // FIXME remove | ||
| enterBarrier("restarted-fifth-removed") | ||
| } | ||
|
|
||
| } | ||
|
|
||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MemberRemoved must be published before MemberUp, e.g. when restarted
in other DC.