vitess.io/vitess@v0.16.2/go/vt/topo/etcd2topo/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 etcd2topo 18 19 import ( 20 "context" 21 "path" 22 23 clientv3 "go.etcd.io/etcd/client/v3" 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 &etcdLeaderParticipation{ 32 s: s, 33 name: name, 34 id: id, 35 stop: make(chan struct{}), 36 done: make(chan struct{}), 37 }, nil 38 } 39 40 // etcdLeaderParticipation implements topo.LeaderParticipation. 41 // 42 // We use a directory (in global election path, with the name) with 43 // ephemeral files in it, that contains the id. The oldest revision 44 // wins the election. 45 type etcdLeaderParticipation struct { 46 // s is our parent etcd topo Server 47 s *Server 48 49 // name is the name of this LeaderParticipation 50 name string 51 52 // id is the process's current id. 53 id string 54 55 // stop is a channel closed when Stop is called. 56 stop chan struct{} 57 58 // done is a channel closed when we're done processing the Stop 59 done chan struct{} 60 } 61 62 // WaitForLeadership is part of the topo.LeaderParticipation interface. 63 func (mp *etcdLeaderParticipation) WaitForLeadership() (context.Context, error) { 64 // If Stop was already called, mp.done is closed, so we are interrupted. 65 select { 66 case <-mp.done: 67 return nil, topo.NewError(topo.Interrupted, "Leadership") 68 default: 69 } 70 71 electionPath := path.Join(electionsPath, mp.name) 72 var ld topo.LockDescriptor 73 74 // We use a cancelable context here. If stop is closed, 75 // we just cancel that context. 76 lockCtx, lockCancel := context.WithCancel(context.Background()) 77 go func() { 78 select { 79 case <-mp.s.running: 80 return 81 case <-mp.stop: 82 } 83 if ld != nil { 84 if err := ld.Unlock(context.Background()); err != nil { 85 log.Errorf("failed to unlock electionPath %v: %v", electionPath, err) 86 } 87 } 88 lockCancel() 89 close(mp.done) 90 }() 91 92 // Try to get the primaryship, by getting a lock. 93 var err error 94 ld, err = mp.s.lock(lockCtx, electionPath, mp.id) 95 if err != nil { 96 // It can be that we were interrupted. 97 return nil, err 98 } 99 100 // We got the lock. Return the lockContext. If Stop() is called, 101 // it will cancel the lockCtx, and cancel the returned context. 102 return lockCtx, nil 103 } 104 105 // Stop is part of the topo.LeaderParticipation interface 106 func (mp *etcdLeaderParticipation) Stop() { 107 close(mp.stop) 108 <-mp.done 109 } 110 111 // GetCurrentLeaderID is part of the topo.LeaderParticipation interface 112 func (mp *etcdLeaderParticipation) GetCurrentLeaderID(ctx context.Context) (string, error) { 113 electionPath := path.Join(mp.s.root, electionsPath, mp.name) 114 115 // Get the keys in the directory, older first. 116 resp, err := mp.s.cli.Get(ctx, electionPath+"/", 117 clientv3.WithPrefix(), 118 clientv3.WithSort(clientv3.SortByModRevision, clientv3.SortAscend), 119 clientv3.WithLimit(1)) 120 if err != nil { 121 return "", convertError(err, electionPath) 122 } 123 if len(resp.Kvs) == 0 { 124 // No key starts with this prefix, means nobody is the primary. 125 return "", nil 126 } 127 return string(resp.Kvs[0].Value), nil 128 } 129 130 func (mp *etcdLeaderParticipation) WaitForNewLeader(ctx context.Context) (<-chan string, error) { 131 electionPath := path.Join(mp.s.root, electionsPath, mp.name) 132 133 notifications := make(chan string, 8) 134 ctx, cancel := context.WithCancel(ctx) 135 136 // Get the current leader 137 initial, err := mp.s.cli.Get(ctx, electionPath+"/", 138 clientv3.WithPrefix(), 139 clientv3.WithSort(clientv3.SortByModRevision, clientv3.SortAscend), 140 clientv3.WithLimit(1)) 141 if err != nil { 142 cancel() 143 return nil, err 144 } 145 146 if len(initial.Kvs) == 1 { 147 leader := initial.Kvs[0].Value 148 notifications <- string(leader) 149 } 150 151 // Create the Watcher. We start watching from the response we 152 // got, not from the file original version, as the server may 153 // not have that much history. 154 watcher := mp.s.cli.Watch(ctx, electionPath, clientv3.WithPrefix(), clientv3.WithRev(initial.Header.Revision)) 155 if watcher == nil { 156 cancel() 157 return nil, convertError(err, electionPath) 158 } 159 160 go func() { 161 defer cancel() 162 defer close(notifications) 163 for { 164 select { 165 case <-mp.s.running: 166 return 167 case <-mp.done: 168 return 169 case <-ctx.Done(): 170 return 171 case wresp, ok := <-watcher: 172 if !ok || wresp.Canceled { 173 return 174 } 175 176 currentLeader, err := mp.s.cli.Get(ctx, electionPath+"/", 177 clientv3.WithPrefix(), 178 clientv3.WithSort(clientv3.SortByModRevision, clientv3.SortAscend), 179 clientv3.WithLimit(1)) 180 if err != nil { 181 continue 182 } 183 if len(currentLeader.Kvs) != 1 { 184 continue 185 } 186 notifications <- string(currentLeader.Kvs[0].Value) 187 } 188 } 189 }() 190 191 return notifications, nil 192 }