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 }