Commit 35a090b5 authored by Anders Jensen Løvig's avatar Anders Jensen Løvig
Browse files

Election: next phase

parent 84f69800
Pipeline #21376 passed with stages
in 1 minute and 10 seconds
......@@ -68,7 +68,7 @@ func init() {
// Initialize election
servers := len(server.Config.Servers)
required := (servers + 1) / 2
required := (servers / 2) + 1
config := &election.Config{
ServerID: election.UniqueID(serialNumber.String()),
Voters: 5,
......@@ -92,7 +92,7 @@ var clientHandlers = map[string]network.MessageHandler{
if err != nil {
log.Println("ERROR: " + err.Error())
} else {
server.Election.HandleBallot(conn.SerialNumber(), &ballot)
go server.Election.HandleBallot(conn.SerialNumber(), &ballot)
}
return network.NewMessage(election.MsgBallotAck, nil)
},
......@@ -108,7 +108,7 @@ var serverHandlers = map[string]network.MessageHandler{
if err != nil {
log.Println("ERROR: " + err.Error())
} else {
server.Election.HandleTally("", &tally)
go server.Election.HandleTally("", &tally)
}
return nil
},
......@@ -141,7 +141,7 @@ func main() {
server.Server.Close()
}
func closeCallback() {
func closeCallback(reason election.Reason) {
log.Println("DEBUG: Close callback")
connections := 0
......@@ -171,11 +171,10 @@ func closeCallback() {
server.Done <- true
}
func tallyCallback() {
func tallyCallback(tally *election.Tally) {
log.Println("DEBUG: Tally callback")
log.Println("===================== Tallying =====================")
tally := server.Election.Tally()
server.Election.HandleTally("", tally)
// err := server.Client.Broadcast(network.NewMessage(election.MsgTally, tally))
......
......@@ -96,7 +96,7 @@ type Election struct {
// Called when the election is closed locally
closeCallback func(Reason)
// Called when a enough servers (Participants.RequiredServers) have closed
tallyCallback func()
tallyCallback func(*Tally)
// Called when the final result is available
resultCallback func(*Result)
}
......@@ -109,7 +109,7 @@ type Config struct {
RequiredServers int `json:"required"`
Deadline time.Time `json:"deadline"`
CloseCallback func(Reason) `json:"-"`
TallyCallback func() `json:"-"`
TallyCallback func(*Tally) `json:"-"`
ResultCallback func(*Result) `json:"-"`
}
......@@ -186,7 +186,10 @@ func (election *Election) nextPhase(reason Reason) {
switch election.Status.Phase {
case PhaseCollecting:
log.Println("Election closing, reason:", reason)
election.closeElection(reason)
election.Status.Closed = true
election.Status.ClosedServers[election.ServerID] = true
election.closeCallback(reason)
// If more than required servers have closed, then we can skip the next phase
// Else we have to wait for more servers to close
if len(election.Status.ClosedServers) < election.Participants.RequiredServers {
......@@ -199,47 +202,19 @@ func (election *Election) nextPhase(reason Reason) {
// Else enough servers to start tallying
fallthrough
case PhaseClosed:
// If the ballot box is empty, there is no need to
if election.ballotBox.Count() == 0 {
// If the ballot box is empty we can go directly to result
if election.ballotBox.Count() != 0 {
election.Status.Phase = PhaseTallying
tally := election.ballotBox.Tally()
election.tallyCallback(tally)
break
}
election.nextPhaseTally()
fallthrough
case PhaseTallying:
election.Status.Phase = PhaseResult
result := election.createResults()
election.resultCallback(result)
}
// switch election.Status.Phase {
// case PhaseCollecting:
// election.closeElection(reason)
// election.Status.Phase = PhaseClosed
// if !election.shouldTally() {
// log.Println("Waiting for enough servers to close election")
// } else {
// election.nextPhase(reason) // Recursion :D
// }
// case PhaseClosed:
// if election.ballotBox.Count() == 0 {
// // No votes
// election.Status.Phase = PhaseResult
// go election.resultCallback(nil)
// } else {
// election.Status.Phase = PhaseTallying
// go election.tallyCallback()
// }
// case PhaseTallying:
// election.Result = election.createResults()
// election.Status.Phase = PhaseResult
// go election.resultCallback(election.Result)
// }
}
func (election *Election) nextPhaseTally() {
// TODO
}
func (election *Election) closeElection(reason Reason) {
election.Status.Closed = true
election.Status.ClosedServers[election.ServerID] = true
go election.closeCallback(reason)
}
// HandleBallot puts ballot in the ballot box if ballot is valid.
......@@ -309,6 +284,10 @@ func (election *Election) HandleTally(serverID UniqueID, tally *Tally) {
}
func (election *Election) createResults() *Result {
if election.tallyBox.Count() == 0 {
return nil
}
return &Result{
Votes: election.ballotBox.Count(),
Tally: int(election.tallyBox.Combine().Int64()),
......
......@@ -122,13 +122,13 @@ func setupElection() *Election {
RequiredServers: 2,
Deadline: time.Now().Add(1 * time.Minute),
CloseCallback: func(reason Reason) {},
TallyCallback: func() {},
TallyCallback: func(tally *Tally) {},
ResultCallback: func(result *Result) {},
}
return NewElection(config)
}
func testCloseCondition(t *testing.T, expectedReason Reason, expectedPhase Phase, fun func(*Election)) {
func testCloseCondition(t *testing.T, expectedReason Reason, expectedPhase Phase, fun func(*Election)) *Election {
log.SetOutput(ioutil.Discard)
close := make(chan bool, 1)
timeout := make(chan bool, 1)
......@@ -160,24 +160,76 @@ func testCloseCondition(t *testing.T, expectedReason Reason, expectedPhase Phase
if e.Status.Phase != expectedPhase {
t.Errorf("Unexpected phase\n Expected: %s\n Actual: %s\n", expectedPhase, e.Status.Phase)
}
return e
}
func TestCloseDeadline(t *testing.T) {
testCloseCondition(t, ReasonDeadline, PhaseClosed, func(e *Election) {})
_ = testCloseCondition(t, ReasonDeadline, PhaseClosed, func(e *Election) {})
}
func TestCloseAgreement(t *testing.T) {
testCloseCondition(t, ReasonAgreement, PhaseTallying, func(e *Election) {
_ = testCloseCondition(t, ReasonAgreement, PhaseTallying, func(e *Election) {
ballots := CreateBallots(3, createXS(1), big.NewInt(1))
e.HandleBallot(UniqueID(strconv.Itoa(1)), ballots["1"])
e.HandleClosing("2")
e.HandleClosing("3")
})
}
func TestCloseAllVotes(t *testing.T) {
testCloseCondition(t, ReasonVotes, PhaseClosed, func(e *Election) {
_ = testCloseCondition(t, ReasonVotes, PhaseClosed, func(e *Election) {
for i := 1; i <= e.Participants.Voters; i++ {
ballots := CreateBallots(3, createXS(1), big.NewInt(1))
e.HandleBallot(UniqueID(strconv.Itoa(i)), ballots["1"])
}
})
}
func TestTallyDeadline(t *testing.T) {
_ = testCloseCondition(t, ReasonDeadline, PhaseTallying, func(e *Election) {
ballots := CreateBallots(3, createXS(1), big.NewInt(1))
e.HandleBallot(UniqueID(strconv.Itoa(1)), ballots["1"])
// By closing another server, we should go directly to tallying when
// deadline is reached
e.HandleClosing("2")
})
}
func TestNoVotesResult(t *testing.T) {
done := make(chan bool, 1)
timeout := make(chan bool, 1)
e := testCloseCondition(t, ReasonDeadline, PhaseClosed, func(e *Election) {})
e.resultCallback = func(result *Result) {
if result != nil {
t.Error("Got a result wile none was expected")
}
done <- true
}
go func() {
time.Sleep(2 * time.Second)
timeout <- true
}()
e.HandleClosing("2")
select {
case <-done:
case <-timeout:
t.Error("Timeout")
}
if e.Status.Phase != PhaseResult {
t.Errorf("Unexpected phase\n Expected: %s\n Actual: %s\n", PhaseResult, e.Status.Phase)
}
}
func TestTallyAfterClose(t *testing.T) {
_ = testCloseCondition(t, ReasonDeadline, PhaseTallying, func(e *Election) {
ballots := CreateBallots(3, createXS(1), big.NewInt(1))
e.HandleBallot(UniqueID(strconv.Itoa(1)), ballots["1"])
e.HandleClosing("2")
})
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment