go.etcd.io/etcd@v3.3.27+incompatible/etcdctl/ctlv3/command/elect_command.go (about)

     1  // Copyright 2016 The etcd Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package command
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"os"
    21  	"os/signal"
    22  	"syscall"
    23  
    24  	"github.com/coreos/etcd/clientv3"
    25  	"github.com/coreos/etcd/clientv3/concurrency"
    26  
    27  	"github.com/spf13/cobra"
    28  )
    29  
    30  var (
    31  	electListen bool
    32  )
    33  
    34  // NewElectCommand returns the cobra command for "elect".
    35  func NewElectCommand() *cobra.Command {
    36  	cmd := &cobra.Command{
    37  		Use:   "elect <election-name> [proposal]",
    38  		Short: "Observes and participates in leader election",
    39  		Run:   electCommandFunc,
    40  	}
    41  	cmd.Flags().BoolVarP(&electListen, "listen", "l", false, "observation mode")
    42  	return cmd
    43  }
    44  
    45  func electCommandFunc(cmd *cobra.Command, args []string) {
    46  	if len(args) != 1 && len(args) != 2 {
    47  		ExitWithError(ExitBadArgs, errors.New("elect takes one election name argument and an optional proposal argument."))
    48  	}
    49  	c := mustClientFromCmd(cmd)
    50  
    51  	var err error
    52  	if len(args) == 1 {
    53  		if !electListen {
    54  			ExitWithError(ExitBadArgs, errors.New("no proposal argument but -l not set"))
    55  		}
    56  		err = observe(c, args[0])
    57  	} else {
    58  		if electListen {
    59  			ExitWithError(ExitBadArgs, errors.New("proposal given but -l is set"))
    60  		}
    61  		err = campaign(c, args[0], args[1])
    62  	}
    63  	if err != nil {
    64  		ExitWithError(ExitError, err)
    65  	}
    66  }
    67  
    68  func observe(c *clientv3.Client, election string) error {
    69  	s, err := concurrency.NewSession(c)
    70  	if err != nil {
    71  		return err
    72  	}
    73  	e := concurrency.NewElection(s, election)
    74  	ctx, cancel := context.WithCancel(context.TODO())
    75  
    76  	donec := make(chan struct{})
    77  	sigc := make(chan os.Signal, 1)
    78  	signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM)
    79  	go func() {
    80  		<-sigc
    81  		cancel()
    82  	}()
    83  
    84  	go func() {
    85  		for resp := range e.Observe(ctx) {
    86  			display.Get(resp)
    87  		}
    88  		close(donec)
    89  	}()
    90  
    91  	<-donec
    92  
    93  	select {
    94  	case <-ctx.Done():
    95  	default:
    96  		return errors.New("elect: observer lost")
    97  	}
    98  
    99  	return nil
   100  }
   101  
   102  func campaign(c *clientv3.Client, election string, prop string) error {
   103  	s, err := concurrency.NewSession(c)
   104  	if err != nil {
   105  		return err
   106  	}
   107  	e := concurrency.NewElection(s, election)
   108  	ctx, cancel := context.WithCancel(context.TODO())
   109  
   110  	donec := make(chan struct{})
   111  	sigc := make(chan os.Signal, 1)
   112  	signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM)
   113  	go func() {
   114  		<-sigc
   115  		cancel()
   116  		close(donec)
   117  	}()
   118  
   119  	if err = e.Campaign(ctx, prop); err != nil {
   120  		return err
   121  	}
   122  
   123  	// print key since elected
   124  	resp, err := c.Get(ctx, e.Key())
   125  	if err != nil {
   126  		return err
   127  	}
   128  	display.Get(*resp)
   129  
   130  	select {
   131  	case <-donec:
   132  	case <-s.Done():
   133  		return errors.New("elect: session expired")
   134  	}
   135  
   136  	return e.Resign(context.TODO())
   137  }