# Run a V2 membership change that demotes the leader, using joint consensus and
# explicitly determining when to transition out of the joint config. Leadership
# is transferred by campaigning a designated voter in the new config once the
# old leader steps down. After the reconfiguration completes, we verify that the
# demoted leader cannot campaign to become leader.

# We'll turn this back on after the boilerplate.
log-level none
----
ok

# Run a V1 membership change that removes the leader.
# Bootstrap n1, n2, n3.
add-nodes 3 voters=(1,2,3) index=2
----
ok

campaign 1
----
ok

stabilize
----
ok

log-level debug
----
ok

raft-state
----
1: StateLeader (Voter) Term:1 Lead:1 LeadEpoch:1
2: StateFollower (Voter) Term:1 Lead:1 LeadEpoch:1
3: StateFollower (Voter) Term:1 Lead:1 LeadEpoch:1

# A removal without a demotion should be rejected.
propose-conf-change 1 v1=true
r1
----
INFO 1 ignoring conf change {ConfChangeRemoveNode 1 [] 0} at config voters=(1 2 3): voters must be demoted to learners before being removed

# Instead, demote n1 to a learner in a joint config.
propose-conf-change 1 v1=false transition=explicit
r1 l1
----
ok

stabilize log-level=none
----
ok

# Exit the joint config.
propose-conf-change 1
----
ok

raft-state
----
1: StateLeader (Voter) Term:1 Lead:1 LeadEpoch:1
2: StateFollower (Voter) Term:1 Lead:1 LeadEpoch:1
3: StateFollower (Voter) Term:1 Lead:1 LeadEpoch:1

# Propose an extra entry which will be sent out together with the conf change.
propose 1 foo
----
ok

# Send out the corresponding appends.
process-ready 1
----
Ready MustSync=true:
Entries:
1/6 EntryConfChangeV2
1/7 EntryNormal "foo"
Messages:
1->2 MsgApp Term:1 Log:1/5 Commit:5 Entries:[1/6 EntryConfChangeV2]
1->3 MsgApp Term:1 Log:1/5 Commit:5 Entries:[1/6 EntryConfChangeV2]
1->2 MsgApp Term:1 Log:1/6 Commit:5 Entries:[1/7 EntryNormal "foo"]
1->3 MsgApp Term:1 Log:1/6 Commit:5 Entries:[1/7 EntryNormal "foo"]

# Send response from n2 and n3 (which is enough to commit the entries so far
# next time n1 runs).
stabilize 2 3
----
> 2 receiving messages
  1->2 MsgApp Term:1 Log:1/5 Commit:5 Entries:[1/6 EntryConfChangeV2]
  1->2 MsgApp Term:1 Log:1/6 Commit:5 Entries:[1/7 EntryNormal "foo"]
> 3 receiving messages
  1->3 MsgApp Term:1 Log:1/5 Commit:5 Entries:[1/6 EntryConfChangeV2]
  1->3 MsgApp Term:1 Log:1/6 Commit:5 Entries:[1/7 EntryNormal "foo"]
> 2 handling Ready
  Ready MustSync=true:
  Entries:
  1/6 EntryConfChangeV2
  1/7 EntryNormal "foo"
  Messages:
  2->1 MsgAppResp Term:1 Log:0/6 Commit:5
  2->1 MsgAppResp Term:1 Log:0/7 Commit:5
> 3 handling Ready
  Ready MustSync=true:
  Entries:
  1/6 EntryConfChangeV2
  1/7 EntryNormal "foo"
  Messages:
  3->1 MsgAppResp Term:1 Log:0/6 Commit:5
  3->1 MsgAppResp Term:1 Log:0/7 Commit:5

# Put another entry in n1's log.
propose 1 bar
----
ok

# n1 applies the conf change, removing itself and stepping down. But it still
# has an uncommitted 'bar' entry in the log that it sends out appends for first.
stabilize 1
----
> 1 handling Ready
  Ready MustSync=true:
  Entries:
  1/8 EntryNormal "bar"
  Messages:
  1->2 MsgApp Term:1 Log:1/7 Commit:5 Entries:[1/8 EntryNormal "bar"]
  1->3 MsgApp Term:1 Log:1/7 Commit:5 Entries:[1/8 EntryNormal "bar"]
> 1 receiving messages
  2->1 MsgAppResp Term:1 Log:0/6 Commit:5
  2->1 MsgAppResp Term:1 Log:0/7 Commit:5
  3->1 MsgAppResp Term:1 Log:0/6 Commit:5
  3->1 MsgAppResp Term:1 Log:0/7 Commit:5
> 1 handling Ready
  Ready MustSync=true:
  HardState Term:1 Vote:1 Commit:7 Lead:1 LeadEpoch:1
  CommittedEntries:
  1/6 EntryConfChangeV2
  1/7 EntryNormal "foo"
  Messages:
  1->2 MsgApp Term:1 Log:1/8 Commit:6
  1->3 MsgApp Term:1 Log:1/8 Commit:6
  1->2 MsgApp Term:1 Log:1/8 Commit:7
  1->3 MsgApp Term:1 Log:1/8 Commit:7
  INFO 1 switched to configuration voters=(2 3) learners=(1)
  DEBUG 1 setting election elapsed to start from 3 ticks after store liveness support expired
  INFO 1 became follower at term 1
  DEBUG 1 reset election elapsed to 0
> 1 handling Ready
  Ready MustSync=true:
  State:StateFollower
  HardState Term:1 Vote:1 Commit:7 Lead:0 LeadEpoch:0

raft-state
----
1: StateFollower (Non-Voter) Term:1 Lead:0 LeadEpoch:0
2: StateFollower (Voter) Term:1 Lead:1 LeadEpoch:1
3: StateFollower (Voter) Term:1 Lead:1 LeadEpoch:1

# n2 responds, n3 doesn't yet. Quorum for 'bar' should not be reached...
stabilize 2
----
> 2 receiving messages
  1->2 MsgApp Term:1 Log:1/7 Commit:5 Entries:[1/8 EntryNormal "bar"]
  1->2 MsgApp Term:1 Log:1/8 Commit:6
  1->2 MsgApp Term:1 Log:1/8 Commit:7
> 2 handling Ready
  Ready MustSync=true:
  HardState Term:1 Vote:1 Commit:7 Lead:1 LeadEpoch:1
  Entries:
  1/8 EntryNormal "bar"
  CommittedEntries:
  1/6 EntryConfChangeV2
  1/7 EntryNormal "foo"
  Messages:
  2->1 MsgAppResp Term:1 Log:0/8 Commit:5
  2->1 MsgAppResp Term:1 Log:0/8 Commit:6
  2->1 MsgAppResp Term:1 Log:0/8 Commit:7
  INFO 2 switched to configuration voters=(2 3) learners=(1)

# ...because the old leader n1 ignores the append responses.
stabilize 1
----
> 1 receiving messages
  2->1 MsgAppResp Term:1 Log:0/8 Commit:5
  2->1 MsgAppResp Term:1 Log:0/8 Commit:6
  2->1 MsgAppResp Term:1 Log:0/8 Commit:7

# When n3 responds, quorum is reached and everything falls into place.
stabilize
----
> 3 receiving messages
  1->3 MsgApp Term:1 Log:1/7 Commit:5 Entries:[1/8 EntryNormal "bar"]
  1->3 MsgApp Term:1 Log:1/8 Commit:6
  1->3 MsgApp Term:1 Log:1/8 Commit:7
> 3 handling Ready
  Ready MustSync=true:
  HardState Term:1 Vote:1 Commit:7 Lead:1 LeadEpoch:1
  Entries:
  1/8 EntryNormal "bar"
  CommittedEntries:
  1/6 EntryConfChangeV2
  1/7 EntryNormal "foo"
  Messages:
  3->1 MsgAppResp Term:1 Log:0/8 Commit:5
  3->1 MsgAppResp Term:1 Log:0/8 Commit:6
  3->1 MsgAppResp Term:1 Log:0/8 Commit:7
  INFO 3 switched to configuration voters=(2 3) learners=(1)
> 1 receiving messages
  3->1 MsgAppResp Term:1 Log:0/8 Commit:5
  3->1 MsgAppResp Term:1 Log:0/8 Commit:6
  3->1 MsgAppResp Term:1 Log:0/8 Commit:7

# n1 can no longer propose.
propose 1 baz
----
INFO 1 no leader at term 1; dropping proposal
raft proposal dropped

# Nor can it campaign to become leader.
campaign 1
----
INFO 1 is unpromotable and can not campaign
