github.com/decred/dcrlnd@v0.7.6/cluster/etcd_elector.go (about) 1 //go:build kvdb_etcd 2 // +build kvdb_etcd 3 4 package cluster 5 6 import ( 7 "context" 8 "time" 9 10 "github.com/decred/dcrlnd/kvdb/etcd" 11 "go.etcd.io/etcd/client/pkg/v3/transport" 12 clientv3 "go.etcd.io/etcd/client/v3" 13 "go.etcd.io/etcd/client/v3/concurrency" 14 "go.etcd.io/etcd/client/v3/namespace" 15 ) 16 17 const ( 18 // etcdConnectionTimeout is the timeout until successful connection to 19 // the etcd instance. 20 etcdConnectionTimeout = 10 * time.Second 21 ) 22 23 // Enforce that etcdLeaderElector implements the LeaderElector interface. 24 var _ LeaderElector = (*etcdLeaderElector)(nil) 25 26 // etcdLeaderElector is an implemetation of LeaderElector using etcd as the 27 // election governor. 28 type etcdLeaderElector struct { 29 id string 30 ctx context.Context 31 cli *clientv3.Client 32 session *concurrency.Session 33 election *concurrency.Election 34 } 35 36 // newEtcdLeaderElector constructs a new etcdLeaderElector. 37 func newEtcdLeaderElector(ctx context.Context, id, electionPrefix string, 38 leaderSessionTTL int, cfg *etcd.Config) (*etcdLeaderElector, error) { 39 40 clientCfg := clientv3.Config{ 41 Context: ctx, 42 Endpoints: []string{cfg.Host}, 43 DialTimeout: etcdConnectionTimeout, 44 Username: cfg.User, 45 Password: cfg.Pass, 46 } 47 48 if !cfg.DisableTLS { 49 tlsInfo := transport.TLSInfo{ 50 CertFile: cfg.CertFile, 51 KeyFile: cfg.KeyFile, 52 InsecureSkipVerify: cfg.InsecureSkipVerify, 53 } 54 55 tlsConfig, err := tlsInfo.ClientConfig() 56 if err != nil { 57 return nil, err 58 } 59 60 clientCfg.TLS = tlsConfig 61 } 62 63 cli, err := clientv3.New(clientCfg) 64 if err != nil { 65 log.Errorf("Unable to connect to etcd: %v", err) 66 return nil, err 67 } 68 69 // Apply the namespace. 70 cli.KV = namespace.NewKV(cli.KV, cfg.Namespace) 71 cli.Watcher = namespace.NewWatcher(cli.Watcher, cfg.Namespace) 72 cli.Lease = namespace.NewLease(cli.Lease, cfg.Namespace) 73 log.Infof("Applied namespace to leader elector: %v", cfg.Namespace) 74 75 session, err := concurrency.NewSession( 76 cli, concurrency.WithTTL(leaderSessionTTL), 77 ) 78 if err != nil { 79 log.Errorf("Unable to start new leader election session: %v", 80 err) 81 return nil, err 82 } 83 84 return &etcdLeaderElector{ 85 id: id, 86 ctx: ctx, 87 cli: cli, 88 session: session, 89 election: concurrency.NewElection( 90 session, electionPrefix, 91 ), 92 }, nil 93 } 94 95 // Leader returns the leader value for the current election. 96 func (e *etcdLeaderElector) Leader(ctx context.Context) (string, error) { 97 resp, err := e.election.Leader(ctx) 98 if err != nil { 99 return "", err 100 } 101 102 return string(resp.Kvs[0].Value), nil 103 } 104 105 // Campaign will start a new leader election campaign. Campaign will block until 106 // the elector context is canceled or the the caller is elected as the leader. 107 func (e *etcdLeaderElector) Campaign(ctx context.Context) error { 108 return e.election.Campaign(ctx, e.id) 109 } 110 111 // Resign resigns the leader role allowing other election members to take 112 // the place. 113 func (e *etcdLeaderElector) Resign() error { 114 return e.election.Resign(context.Background()) 115 }