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  }