github.com/sym3tri/etcd@v0.2.1-0.20140422215517-a563d82f95d6/discovery/discovery.go (about)

     1  package discovery
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net/url"
     7  	"path"
     8  	"strings"
     9  	"time"
    10  
    11  	etcdErr "github.com/coreos/etcd/error"
    12  	"github.com/coreos/etcd/log"
    13  	"github.com/coreos/etcd/third_party/github.com/coreos/go-etcd/etcd"
    14  )
    15  
    16  const (
    17  	stateKey     = "_state"
    18  	startedState = "started"
    19  	defaultTTL   = 604800 // One week TTL
    20  )
    21  
    22  type Discoverer struct {
    23  	client       *etcd.Client
    24  	name         string
    25  	peer         string
    26  	prefix       string
    27  	discoveryURL string
    28  }
    29  
    30  var defaultDiscoverer *Discoverer
    31  
    32  func init() {
    33  	defaultDiscoverer = &Discoverer{}
    34  }
    35  
    36  func (d *Discoverer) Do(discoveryURL string, name string, peer string) (peers []string, err error) {
    37  	d.name = name
    38  	d.peer = peer
    39  	d.discoveryURL = discoveryURL
    40  
    41  	u, err := url.Parse(discoveryURL)
    42  
    43  	if err != nil {
    44  		return
    45  	}
    46  
    47  	// prefix is prepended to all keys for this discovery
    48  	d.prefix = strings.TrimPrefix(u.Path, "/v2/keys/")
    49  
    50  	// keep the old path in case we need to set the KeyPrefix below
    51  	oldPath := u.Path
    52  	u.Path = ""
    53  
    54  	// Connect to a scheme://host not a full URL with path
    55  	log.Infof("Discovery via %s using prefix %s.", u.String(), d.prefix)
    56  	d.client = etcd.NewClient([]string{u.String()})
    57  
    58  	if !strings.HasPrefix(oldPath, "/v2/keys") {
    59  		d.client.SetKeyPrefix("")
    60  	}
    61  
    62  	// Register this machine first and announce that we are a member of
    63  	// this cluster
    64  	err = d.heartbeat()
    65  	if err != nil {
    66  		return
    67  	}
    68  
    69  	// Start the very slow heartbeat to the cluster now in anticipation
    70  	// that everything is going to go alright now
    71  	go d.startHeartbeat()
    72  
    73  	// Attempt to take the leadership role, if there is no error we are it!
    74  	resp, err := d.client.Create(path.Join(d.prefix, stateKey), startedState, 0)
    75  
    76  	// Bail out on unexpected errors
    77  	if err != nil {
    78  		if clientErr, ok := err.(*etcd.EtcdError); !ok || clientErr.ErrorCode != etcdErr.EcodeNodeExist {
    79  			return nil, err
    80  		}
    81  	}
    82  
    83  	// If we got a response then the CAS was successful, we are leader
    84  	if resp != nil && resp.Node.Value == startedState {
    85  		// We are the leader, we have no peers
    86  		log.Infof("Discovery _state was empty, so this machine is the initial leader.")
    87  		return nil, nil
    88  	}
    89  
    90  	// Fall through to finding the other discovery peers
    91  	return d.findPeers()
    92  }
    93  
    94  func (d *Discoverer) findPeers() (peers []string, err error) {
    95  	resp, err := d.client.Get(path.Join(d.prefix), false, true)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  
   100  	node := resp.Node
   101  
   102  	if node == nil {
   103  		return nil, fmt.Errorf("%s key doesn't exist.", d.prefix)
   104  	}
   105  
   106  	for _, n := range node.Nodes {
   107  		// Skip our own entry in the list, there is no point
   108  		if strings.HasSuffix(n.Key, "/"+d.name) {
   109  			continue
   110  		}
   111  		peers = append(peers, n.Value)
   112  	}
   113  
   114  	if len(peers) == 0 {
   115  		return nil, errors.New("Discovery found an initialized cluster but no reachable peers are registered.")
   116  	}
   117  
   118  	log.Infof("Discovery found peers %v", peers)
   119  
   120  	return
   121  }
   122  
   123  func (d *Discoverer) startHeartbeat() {
   124  	// In case of errors we should attempt to heartbeat fairly frequently
   125  	heartbeatInterval := defaultTTL / 8
   126  	ticker := time.Tick(time.Second * time.Duration(heartbeatInterval))
   127  	for {
   128  		select {
   129  		case <-ticker:
   130  			err := d.heartbeat()
   131  			if err != nil {
   132  				log.Warnf("Discovery heartbeat failed: %v", err)
   133  			}
   134  		}
   135  	}
   136  }
   137  
   138  func (d *Discoverer) heartbeat() error {
   139  	_, err := d.client.Set(path.Join(d.prefix, d.name), d.peer, defaultTTL)
   140  	return err
   141  }
   142  
   143  func Do(discoveryURL string, name string, peer string) ([]string, error) {
   144  	return defaultDiscoverer.Do(discoveryURL, name, peer)
   145  }