vitess.io/vitess@v0.16.2/go/vt/topo/consultopo/election.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package consultopo 18 19 import ( 20 "context" 21 "path" 22 23 "github.com/hashicorp/consul/api" 24 25 "vitess.io/vitess/go/vt/log" 26 "vitess.io/vitess/go/vt/topo" 27 ) 28 29 // NewLeaderParticipation is part of the topo.Server interface 30 func (s *Server) NewLeaderParticipation(name, id string) (topo.LeaderParticipation, error) { 31 return &consulLeaderParticipation{ 32 s: s, 33 name: name, 34 id: id, 35 stop: make(chan struct{}), 36 done: make(chan struct{}), 37 }, nil 38 } 39 40 // consulLeaderParticipation implements topo.LeaderParticipation. 41 // 42 // We use a key with name <global>/elections/<name> for the lock, 43 // that contains the id. 44 type consulLeaderParticipation struct { 45 // s is our parent consul topo Server 46 s *Server 47 48 // name is the name of this LeaderParticipation 49 name string 50 51 // id is the process's current id. 52 id string 53 54 // stop is a channel closed when Stop is called. 55 stop chan struct{} 56 57 // done is a channel closed when we're done processing the Stop 58 done chan struct{} 59 } 60 61 // WaitForLeadership is part of the topo.LeaderParticipation interface. 62 func (mp *consulLeaderParticipation) WaitForLeadership() (context.Context, error) { 63 64 electionPath := path.Join(mp.s.root, electionsPath, mp.name) 65 l, err := mp.s.client.LockOpts(&api.LockOptions{ 66 Key: electionPath, 67 Value: []byte(mp.id), 68 }) 69 if err != nil { 70 return nil, err 71 } 72 73 // If Stop was already called, mp.done is closed, so we are interrupted. 74 select { 75 case <-mp.done: 76 return nil, topo.NewError(topo.Interrupted, "Leadership") 77 default: 78 } 79 80 // Try to lock until mp.stop is closed. 81 lost, err := l.Lock(mp.stop) 82 if err != nil { 83 // We can't lock. See if it was because we got canceled. 84 select { 85 case <-mp.stop: 86 close(mp.done) 87 default: 88 } 89 return nil, err 90 } 91 92 // We have the lock, keep primaryship until we lose it. 93 lockCtx, lockCancel := context.WithCancel(context.Background()) 94 go func() { 95 select { 96 case <-lost: 97 lockCancel() 98 // We could have lost the lock. Per consul API, explicitly call Unlock to make sure that session will not be renewed. 99 if err := l.Unlock(); err != nil { 100 log.Errorf("Leader election(%v) Unlock failed: %v", mp.name, err) 101 } 102 case <-mp.stop: 103 // Stop was called. We stop the context first, 104 // so the running process is not thinking it 105 // is the primary any more, then we unlock. 106 lockCancel() 107 if err := l.Unlock(); err != nil { 108 log.Errorf("Leader election(%v) Unlock failed: %v", mp.name, err) 109 } 110 close(mp.done) 111 } 112 }() 113 114 return lockCtx, nil 115 } 116 117 // Stop is part of the topo.LeaderParticipation interface 118 func (mp *consulLeaderParticipation) Stop() { 119 close(mp.stop) 120 <-mp.done 121 } 122 123 // GetCurrentLeaderID is part of the topo.LeaderParticipation interface 124 func (mp *consulLeaderParticipation) GetCurrentLeaderID(ctx context.Context) (string, error) { 125 electionPath := path.Join(mp.s.root, electionsPath, mp.name) 126 pair, _, err := mp.s.kv.Get(electionPath, nil) 127 if err != nil { 128 return "", err 129 } 130 if pair == nil { 131 return "", nil 132 } 133 return string(pair.Value), nil 134 } 135 136 // WaitForNewLeader is part of the topo.LeaderParticipation interface 137 func (mp *consulLeaderParticipation) WaitForNewLeader(context.Context) (<-chan string, error) { 138 // This isn't implemented yet, but likely can be implemented using List 139 // with blocking logic on election path. 140 // See also how WatchRecursive could be implemented as well. 141 return nil, topo.NewError(topo.NoImplementation, "wait for leader not supported in Consul topo") 142 }