vitess.io/vitess@v0.16.2/go/vt/topo/k8stopo/election.go (about)

     1  /*
     2  Copyright 2020 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 k8stopo
    18  
    19  import (
    20  	"context"
    21  	"path"
    22  
    23  	"vitess.io/vitess/go/vt/log"
    24  	"vitess.io/vitess/go/vt/topo"
    25  )
    26  
    27  const electionsPath = "elections"
    28  
    29  // NewLeaderParticipation is part of the topo.Server interface
    30  func (s *Server) NewLeaderParticipation(name, id string) (topo.LeaderParticipation, error) {
    31  	return &kubernetesLeaderParticipation{
    32  		s:    s,
    33  		name: name,
    34  		id:   id,
    35  		stop: make(chan struct{}),
    36  		done: make(chan struct{}),
    37  	}, nil
    38  }
    39  
    40  // kubernetesLeaderParticipation 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 kubernetesLeaderParticipation struct {
    46  	// s is our parent kubernetes 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  func (mp *kubernetesLeaderParticipation) getElectionPath() string {
    63  	return path.Join(mp.s.root, electionsPath, mp.name)
    64  }
    65  
    66  // WaitForLeadership is part of the topo.LeaderParticipation interface.
    67  func (mp *kubernetesLeaderParticipation) WaitForLeadership() (context.Context, error) {
    68  	// If Stop was already called, mp.done is closed, so we are interrupted.
    69  	select {
    70  	case <-mp.done:
    71  		return nil, topo.NewError(topo.Interrupted, "Leadership")
    72  	default:
    73  	}
    74  
    75  	electionPath := mp.getElectionPath()
    76  	var ld topo.LockDescriptor
    77  
    78  	// We use a cancelable context here. If stop is closed,
    79  	// we just cancel that context.
    80  	lockCtx, lockCancel := context.WithCancel(context.Background())
    81  	go func() {
    82  		<-mp.stop
    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, true)
    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 *kubernetesLeaderParticipation) Stop() {
   107  	close(mp.stop)
   108  	<-mp.done
   109  }
   110  
   111  // GetCurrentLeaderID is part of the topo.LeaderParticipation interface
   112  func (mp *kubernetesLeaderParticipation) GetCurrentLeaderID(ctx context.Context) (string, error) {
   113  	id, _, err := mp.s.Get(ctx, mp.getElectionPath())
   114  	if err != nil {
   115  		// NoNode means nobody is the primary
   116  		if topo.IsErrType(err, topo.NoNode) {
   117  			return "", nil
   118  		}
   119  		return "", err
   120  	}
   121  	return string(id), nil
   122  }
   123  
   124  // WaitForNewLeader is part of the topo.LeaderParticipation interface
   125  func (mp *kubernetesLeaderParticipation) WaitForNewLeader(context.Context) (<-chan string, error) {
   126  	// Kubernetes doesn't seem to provide a primitive that watches a prefix
   127  	// or directory, so this likely can never be implemented.
   128  	return nil, topo.NewError(topo.NoImplementation, "wait for leader not supported in K8s topo")
   129  }