-
Notifications
You must be signed in to change notification settings - Fork 2
/
rasputin.go
113 lines (97 loc) · 2.96 KB
/
rasputin.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package rasputin
import (
"context"
"fmt"
"log"
"time"
clientv3 "go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/client/v3/concurrency"
)
type Rasputin struct {
client *clientv3.Client
electionSession *concurrency.Session
election *concurrency.Election
ctx *context.Context
prefix string
val string
leadershipDuration time.Duration
currentLeaderKey []byte
statusCh chan bool
}
var isLeader bool = false
func Commission(client *clientv3.Client, leaseTimeToLive int, electionPrefix string, electionContext *context.Context, value string, leadershipDuration time.Duration) (*Rasputin, error) {
s, err := concurrency.NewSession(client, concurrency.WithTTL(leaseTimeToLive))
if err != nil {
return nil, err
}
e := concurrency.NewElection(s, electionPrefix)
statusCh := make(chan bool)
r := &Rasputin{
client: client,
electionSession: s,
election: e,
ctx: electionContext,
prefix: electionPrefix,
val: value,
leadershipDuration: leadershipDuration,
statusCh: statusCh,
}
go r.waitCleanup(electionContext)
go r.observe()
fmt.Println("Rasputin!")
return r, nil
}
// Observe leadership status changes and notify client via a channel
func (r *Rasputin) observe() {
resCh := r.election.Observe(*r.ctx)
for response := range resCh {
r.currentLeaderKey = response.Kvs[0].Key
// isLeader denots previous leadership status as it hasn't been updated yet
// (r.election.Key() == string(r.currentLeaderKey)) denotes current leadership status
if !isLeader && (r.election.Key() == string(r.currentLeaderKey)) {
r.statusCh <- true
}
if isLeader && (r.election.Key() != string(r.currentLeaderKey)) {
r.statusCh <- false
}
isLeader = (r.election.Key() == string(r.currentLeaderKey))
}
}
func (r *Rasputin) IsLeader() bool {
return isLeader
}
// Participate in leader election, statusCh will produce true when the current instance
// becomes a leader and false when the current instance loses leadership.
func (r *Rasputin) Participate() (<-chan bool, <-chan error) {
errCh := make(chan error)
go func() {
if err := r.election.Campaign(*r.ctx, r.val); err != nil {
errCh<- err
}
r.giveUpLeadershipAfterDelay()
}()
return r.statusCh, errCh
}
func (r *Rasputin) giveUpLeadershipAfterDelay() {
time.Sleep(r.leadershipDuration)
if err := r.election.Resign(*r.ctx); err != nil {
log.Printf("Failed to give up leadership due to error: %s", err)
}
// re-participate as a candidate after giving up leadership
r.Participate()
}
func (r *Rasputin) Resign() {
r.election.Resign(*r.ctx)
}
func (r *Rasputin) Close() {
log.Println("Closing rasputin, freeing resources")
r.client.Close()
r.electionSession.Close()
close(r.statusCh)
}
// Waits for context cancellation to cleanup resources
func (r *Rasputin) waitCleanup(ctx *context.Context) {
log.Println("Watching...")
<-(*ctx).Done()
r.Close()
}