Skip to content

Commit

Permalink
Updated README and added IsDone to Event
Browse files Browse the repository at this point in the history
  • Loading branch information
thrawn01 committed May 10, 2019
1 parent d643280 commit 266ed3d
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 31 deletions.
43 changes: 20 additions & 23 deletions etcdutil/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ Use etcd for leader election if you have several instances of a service running
and you only want one of the service instances to preform a task.

`LeaderElection` starts a goroutine which performs an election and maintains a leader
while services join and leave the election. Calling `Stop()` will `Concede()` leadership if
the service currently has it.
while candidates join and leave the election. Calling `Close()` will concede leadership if
the service currently has it and will withdraw the candidate from the election.

```go

Expand All @@ -16,33 +16,29 @@ import (
func main() {
var wg holster.WaitGroup

hostname, err := os.Hostname()
if err != nil {
fmt.Fprintf(os.Stderr, "while obtaining hostname: %s\n", err)
return
}

client, err := etcdutil.NewClient(nil)
if err != nil {
fmt.Fprintf(os.Stderr, "while creating etcd client: %s\n", err)
return
}

ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()

// Preform an election called 'my-service' with hostname as the candidate name
e := etcdutil.NewElection(client, etcdutil.ElectionConfig{
// Start a leader election and attempt to become leader, only returns after
// determining the current leader.
election := etcdutil.NewElection(ctx, client, etcdutil.ElectionConfig{
Election: "my-service",
Candidate: hostname,
LeaderChannelSize: 10,
ResumeLeaderOnReconnect: true,
Candidate: "my-candidate",
EventObserver: func(e etcdutil.Event) {
leaderChan <- e
if e.IsDone {
close(leaderChan)
}
},
TTL: 10,
})

// Start the election, will block until a leader is elected
if err = e.Start(); err != nil {
fmt.Printf("during election start: %s\n", err)
os.Exit(1)
}

// Handle graceful shutdown
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt, os.Kill)
Expand All @@ -56,7 +52,8 @@ func main() {
if election.IsLeader() {
err := DoThing()
if err != nil {
// Have another instance DoThing(), we can't for some reason
// Have another instance run DoThing()
// since we can't for some reason.
election.Concede()
}
}
Expand All @@ -68,9 +65,9 @@ func main() {
})
wg.Wait()

// Or you can listen on a channel for leadership updates
for leader := range e.LeaderChan() {
fmt.Printf("Leader: %t\n", leader)
// Or you can pipe events to a channel
for leader := range leaderChan {
fmt.Printf("Leader: %v\n", leader)
}
}
```
Expand Down
16 changes: 13 additions & 3 deletions etcdutil/election.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,18 @@ type LeaderElector interface {
}

type Event struct {
IsLeader bool
LeaderKey string
// True if our candidate is leader
IsLeader bool
// True if the election is shutdown and
// no further events will follow.
IsDone bool
// Holds the current leader key
LeaderKey string
// Hold the current leaders data
LeaderData string
Err error
// If not nil, contains an error encountered
// while participating in the election.
Err error
}

type EventObserver func(Event)
Expand Down Expand Up @@ -360,6 +368,8 @@ func (e *Election) onLeaderChange(kv *mvccpb.KeyValue) {
}
event.LeaderKey = string(kv.Key)
event.LeaderData = string(kv.Value)
} else {
event.IsDone = true
}

for _, v := range e.observers {
Expand Down
15 changes: 10 additions & 5 deletions etcdutil/election_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@ func TestTwoCampaigns(t *testing.T) {
})
require.Nil(t, err)

c2Chan := make(chan bool, 5)
c2Chan := make(chan etcdutil.Event, 5)
c2, err := etcdutil.NewElection(ctx, client, etcdutil.ElectionConfig{
EventObserver: func(e etcdutil.Event) {
if err != nil {
t.Fatal(err.Error())
}
c2Chan <- e.IsLeader
c2Chan <- e
},
Election: "/my-election",
Candidate: "c2",
Expand All @@ -69,9 +69,14 @@ func TestTwoCampaigns(t *testing.T) {
assert.Equal(t, false, c1.IsLeader())

// Second campaign should become leader
assert.Equal(t, false, <-c2Chan)
assert.Equal(t, true, <-c2Chan)
e := <-c2Chan
assert.Equal(t, false, e.IsLeader)
e = <-c2Chan
assert.Equal(t, true, e.IsLeader)
assert.Equal(t, false, e.IsDone)

c2.Close()
assert.Equal(t, false, <-c2Chan)
e = <-c2Chan
assert.Equal(t, false, e.IsLeader)
assert.Equal(t, true, e.IsDone)
}

0 comments on commit 266ed3d

Please sign in to comment.