github.com/decred/dcrlnd@v0.7.6/cluster/etcd_elector.go (about)

     1  //go:build kvdb_etcd
     2  // +build kvdb_etcd
     3  
     4  package cluster
     5  
     6  import (
     7  	"context"
     8  	"time"
     9  
    10  	"github.com/decred/dcrlnd/kvdb/etcd"
    11  	"go.etcd.io/etcd/client/pkg/v3/transport"
    12  	clientv3 "go.etcd.io/etcd/client/v3"
    13  	"go.etcd.io/etcd/client/v3/concurrency"
    14  	"go.etcd.io/etcd/client/v3/namespace"
    15  )
    16  
    17  const (
    18  	// etcdConnectionTimeout is the timeout until successful connection to
    19  	// the etcd instance.
    20  	etcdConnectionTimeout = 10 * time.Second
    21  )
    22  
    23  // Enforce that etcdLeaderElector implements the LeaderElector interface.
    24  var _ LeaderElector = (*etcdLeaderElector)(nil)
    25  
    26  // etcdLeaderElector is an implemetation of LeaderElector using etcd as the
    27  // election governor.
    28  type etcdLeaderElector struct {
    29  	id       string
    30  	ctx      context.Context
    31  	cli      *clientv3.Client
    32  	session  *concurrency.Session
    33  	election *concurrency.Election
    34  }
    35  
    36  // newEtcdLeaderElector constructs a new etcdLeaderElector.
    37  func newEtcdLeaderElector(ctx context.Context, id, electionPrefix string,
    38  	leaderSessionTTL int, cfg *etcd.Config) (*etcdLeaderElector, error) {
    39  
    40  	clientCfg := clientv3.Config{
    41  		Context:     ctx,
    42  		Endpoints:   []string{cfg.Host},
    43  		DialTimeout: etcdConnectionTimeout,
    44  		Username:    cfg.User,
    45  		Password:    cfg.Pass,
    46  	}
    47  
    48  	if !cfg.DisableTLS {
    49  		tlsInfo := transport.TLSInfo{
    50  			CertFile:           cfg.CertFile,
    51  			KeyFile:            cfg.KeyFile,
    52  			InsecureSkipVerify: cfg.InsecureSkipVerify,
    53  		}
    54  
    55  		tlsConfig, err := tlsInfo.ClientConfig()
    56  		if err != nil {
    57  			return nil, err
    58  		}
    59  
    60  		clientCfg.TLS = tlsConfig
    61  	}
    62  
    63  	cli, err := clientv3.New(clientCfg)
    64  	if err != nil {
    65  		log.Errorf("Unable to connect to etcd: %v", err)
    66  		return nil, err
    67  	}
    68  
    69  	// Apply the namespace.
    70  	cli.KV = namespace.NewKV(cli.KV, cfg.Namespace)
    71  	cli.Watcher = namespace.NewWatcher(cli.Watcher, cfg.Namespace)
    72  	cli.Lease = namespace.NewLease(cli.Lease, cfg.Namespace)
    73  	log.Infof("Applied namespace to leader elector: %v", cfg.Namespace)
    74  
    75  	session, err := concurrency.NewSession(
    76  		cli, concurrency.WithTTL(leaderSessionTTL),
    77  	)
    78  	if err != nil {
    79  		log.Errorf("Unable to start new leader election session: %v",
    80  			err)
    81  		return nil, err
    82  	}
    83  
    84  	return &etcdLeaderElector{
    85  		id:      id,
    86  		ctx:     ctx,
    87  		cli:     cli,
    88  		session: session,
    89  		election: concurrency.NewElection(
    90  			session, electionPrefix,
    91  		),
    92  	}, nil
    93  }
    94  
    95  // Leader returns the leader value for the current election.
    96  func (e *etcdLeaderElector) Leader(ctx context.Context) (string, error) {
    97  	resp, err := e.election.Leader(ctx)
    98  	if err != nil {
    99  		return "", err
   100  	}
   101  
   102  	return string(resp.Kvs[0].Value), nil
   103  }
   104  
   105  // Campaign will start a new leader election campaign. Campaign will block until
   106  // the elector context is canceled or the the caller is elected as the leader.
   107  func (e *etcdLeaderElector) Campaign(ctx context.Context) error {
   108  	return e.election.Campaign(ctx, e.id)
   109  }
   110  
   111  // Resign resigns the leader role allowing other election members to take
   112  // the place.
   113  func (e *etcdLeaderElector) Resign() error {
   114  	return e.election.Resign(context.Background())
   115  }