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  }