// Dispatch the command to leader func dispatch(c Command, w http.ResponseWriter, req *http.Request, etcd bool) error { if r.State() == raft.Leader { if body, err := r.Do(c); err != nil { return err } else { if body == nil { return etcdErr.NewError(300, "Empty result from raft") } else { body, _ := body.([]byte) w.WriteHeader(http.StatusOK) w.Write(body) return nil } } } else { leader := r.Leader() // current no leader if leader == "" { return etcdErr.NewError(300, "") } redirect(leader, etcd, w, req) return nil } return etcdErr.NewError(300, "") }
// Set the value of the key to the value if the given prevValue is equal to the value of the key func (s *Store) TestAndSet(key string, prevValue string, value string, expireTime time.Time, index uint64) ([]byte, error) { s.mutex.Lock() defer s.mutex.Unlock() // Update stats s.BasicStats.TestAndSets++ resp := s.internalGet(key) if resp == nil { if prevValue != "" { errmsg := fmt.Sprintf("TestAndSet: key not found and previousValue is not empty %s:%s ", key, prevValue) return nil, etcdErr.NewError(100, errmsg) } return s.internalSet(key, value, expireTime, index) } if resp.Value == prevValue { // If test succeed, do set return s.internalSet(key, value, expireTime, index) } else { // If fails, return err return nil, etcdErr.NewError(101, fmt.Sprintf("TestAndSet: %s!=%s", resp.Value, prevValue)) } }
// Set the value of the key to the value if the given prevValue is equal to the value of the key func (s *Store) TestAndSet(key string, prevValue string, value string, expireTime time.Time, index uint64) ([]byte, error) { s.mutex.Lock() defer s.mutex.Unlock() // Update stats s.BasicStats.TestAndSets++ resp := s.internalGet(key) if resp == nil { return nil, etcdErr.NewError(100, "testandset: "+key) } if resp.Value == prevValue { // If test success, do set return s.internalSet(key, value, expireTime, index) } else { // If fails, return err return nil, etcdErr.NewError(101, fmt.Sprintf("TestAndSet: %s!=%s", resp.Value, prevValue)) } }
// InternalGet gets the node of the given nodePath. func (s *store) internalGet(nodePath string) (*node, *etcdErr.Error) { nodePath = path.Clean(path.Join("/", nodePath)) walkFunc := func(parent *node, name string) (*node, *etcdErr.Error) { if !parent.IsDir() { err := etcdErr.NewError(etcdErr.EcodeNotDir, parent.Path, s.CurrentIndex) return nil, err } child, ok := parent.Children[name] if ok { return child, nil } return nil, etcdErr.NewError(etcdErr.EcodeKeyNotFound, path.Join(parent.Path, name), s.CurrentIndex) } f, err := s.walk(nodePath, walkFunc) if err != nil { return nil, err } return f, nil }
// Watches a given key prefix for changes. func WatchKeyHandler(w http.ResponseWriter, req *http.Request, s Server) error { var err error vars := mux.Vars(req) key := "/" + vars["key"] // Create a command to watch from a given index (default 0). var sinceIndex uint64 = 0 if req.Method == "POST" { sinceIndex, err = strconv.ParseUint(string(req.FormValue("index")), 10, 64) if err != nil { return etcdErr.NewError(203, "Watch From Index", s.Store().Index()) } } // Start the watcher on the store. watcher, err := s.Store().Watch(key, false, false, sinceIndex) if err != nil { return etcdErr.NewError(500, key, s.Store().Index()) } event := <-watcher.EventChan // Convert event to a response and write to client. w.WriteHeader(http.StatusOK) if req.Method == "HEAD" { return nil } b, _ := json.Marshal(event.Response(s.Store().Index())) w.Write(b) return nil }
// Update updates the value/ttl of the node. // If the node is a file, the value and the ttl can be updated. // If the node is a directory, only the ttl can be updated. func (s *store) Update(nodePath string, newValue string, expireTime time.Time) (*Event, error) { var err *etcdErr.Error s.worldLock.Lock() defer s.worldLock.Unlock() defer func() { if err == nil { s.Stats.Inc(UpdateSuccess) reportWriteSuccess(Update) return } s.Stats.Inc(UpdateFail) reportWriteFailure(Update) }() nodePath = path.Clean(path.Join("/", nodePath)) // we do not allow the user to change "/" if s.readonlySet.Contains(nodePath) { return nil, etcdErr.NewError(etcdErr.EcodeRootROnly, "/", s.CurrentIndex) } currIndex, nextIndex := s.CurrentIndex, s.CurrentIndex+1 n, err := s.internalGet(nodePath) if err != nil { // if the node does not exist, return error return nil, err } if n.IsDir() && len(newValue) != 0 { // if the node is a directory, we cannot update value to non-empty return nil, etcdErr.NewError(etcdErr.EcodeNotFile, nodePath, currIndex) } e := newEvent(Update, nodePath, nextIndex, n.CreatedIndex) e.EtcdIndex = nextIndex e.PrevNode = n.Repr(false, false, s.clock) eNode := e.Node n.Write(newValue, nextIndex) if n.IsDir() { eNode.Dir = true } else { // copy the value for safety newValueCopy := newValue eNode.Value = &newValueCopy } // update ttl n.UpdateTTL(expireTime) eNode.Expiration, eNode.TTL = n.expirationAndTTL(s.clock) s.WatcherHub.notify(e) s.CurrentIndex = nextIndex return e, nil }
// Sets the value for a given key. func SetKeyHandler(w http.ResponseWriter, req *http.Request, s Server) error { vars := mux.Vars(req) key := "/" + vars["key"] req.ParseForm() // Parse non-blank value. value := req.Form.Get("value") if len(value) == 0 { return etcdErr.NewError(200, "Set", s.Store().Index()) } // Convert time-to-live to an expiration time. expireTime, err := store.TTL(req.Form.Get("ttl")) if err != nil { return etcdErr.NewError(202, "Set", s.Store().Index()) } // If the "prevValue" is specified then test-and-set. Otherwise create a new key. var c raft.Command if prevValueArr, ok := req.Form["prevValue"]; ok { if len(prevValueArr[0]) > 0 { // test against previous value c = s.Store().CommandFactory().CreateCompareAndSwapCommand(key, value, prevValueArr[0], 0, expireTime) } else { // test against existence c = s.Store().CommandFactory().CreateCreateCommand(key, value, expireTime, false) } } else { c = s.Store().CommandFactory().CreateSetCommand(key, value, expireTime) } return s.Dispatch(c, w, req) }
// applyJoin attempts to join a machine to the cluster. func applyJoin(c *JoinCommand, context raft.Context) (uint64, error) { ps, _ := context.Server().Context().(*PeerServer) commitIndex := context.CommitIndex() // Make sure we're not getting a cached value from the registry. ps.registry.Invalidate(c.Name) // Check if the join command is from a previous peer, who lost all its previous log. if peerURL, ok := ps.registry.PeerURL(c.Name); ok { // If previous node restarts with different peer URL, // update its information. if peerURL != c.RaftURL { log.Infof("Rejoin with %v instead of %v from %v", c.RaftURL, peerURL, c.Name) if err := updatePeerURL(c, ps); err != nil { return 0, err } } if c.Name == context.Server().Name() { ps.removedInLog = false } return commitIndex, nil } // Check if the join command adds an instance that collides with existing one on peer URL. peerURLs := ps.registry.PeerURLs(ps.raftServer.Leader(), c.Name) for _, peerURL := range peerURLs { if peerURL == c.RaftURL { log.Warnf("%v tries to join the cluster with existing URL %v", c.Name, c.EtcdURL) return 0, etcdErr.NewError(etcdErr.EcodeExistingPeerAddr, c.EtcdURL, context.CommitIndex()) } } // Check peer number in the cluster count := ps.registry.Count() // ClusterConfig doesn't init until first machine is added if count > 0 && count >= ps.ClusterConfig().ActiveSize { log.Debug("Reject join request from ", c.Name) return 0, etcdErr.NewError(etcdErr.EcodeNoMorePeer, "", context.CommitIndex()) } // Add to shared peer registry. ps.registry.Register(c.Name, c.RaftURL, c.EtcdURL) // Add peer in raft if err := context.Server().AddPeer(c.Name, ""); err != nil { return 0, err } // Add peer stats if c.Name != ps.RaftServer().Name() { ps.followersStats.Followers[c.Name] = &raftFollowerStats{} ps.followersStats.Followers[c.Name].Latency.Minimum = 1 << 63 } if c.Name == context.Server().Name() { ps.removedInLog = false } return commitIndex, nil }
func (s *store) CompareAndSwap(nodePath string, prevValue string, prevIndex uint64, value string, expireTime time.Time) (*Event, error) { s.worldLock.Lock() defer s.worldLock.Unlock() nodePath = path.Clean(path.Join("/", nodePath)) // we do not allow the user to change "/" if s.readonlySet.Contains(nodePath) { return nil, etcdErr.NewError(etcdErr.EcodeRootROnly, "/", s.CurrentIndex) } n, err := s.internalGet(nodePath) if err != nil { s.Stats.Inc(CompareAndSwapFail) reportWriteFailure(CompareAndSwap) return nil, err } if n.IsDir() { // can only compare and swap file s.Stats.Inc(CompareAndSwapFail) reportWriteFailure(CompareAndSwap) return nil, etcdErr.NewError(etcdErr.EcodeNotFile, nodePath, s.CurrentIndex) } // If both of the prevValue and prevIndex are given, we will test both of them. // Command will be executed, only if both of the tests are successful. if ok, which := n.Compare(prevValue, prevIndex); !ok { cause := getCompareFailCause(n, which, prevValue, prevIndex) s.Stats.Inc(CompareAndSwapFail) reportWriteFailure(CompareAndSwap) return nil, etcdErr.NewError(etcdErr.EcodeTestFailed, cause, s.CurrentIndex) } // update etcd index s.CurrentIndex++ e := newEvent(CompareAndSwap, nodePath, s.CurrentIndex, n.CreatedIndex) e.EtcdIndex = s.CurrentIndex e.PrevNode = n.Repr(false, false, s.clock) eNode := e.Node // if test succeed, write the value n.Write(value, s.CurrentIndex) n.UpdateTTL(expireTime) // copy the value for safety valueCopy := value eNode.Value = &valueCopy eNode.Expiration, eNode.TTL = n.expirationAndTTL(s.clock) s.WatcherHub.notify(e) s.Stats.Inc(CompareAndSwapSuccess) reportWriteSuccess(CompareAndSwap) return e, nil }
// Remove function remove the node. func (n *node) Remove(dir, recursive bool, callback func(path string)) *etcdErr.Error { if n.IsDir() { if !dir { // cannot delete a directory without recursive set to true return etcdErr.NewError(etcdErr.EcodeNotFile, n.Path, n.store.CurrentIndex) } if len(n.Children) != 0 && !recursive { // cannot delete a directory if it is not empty and the operation // is not recursive return etcdErr.NewError(etcdErr.EcodeDirNotEmpty, n.Path, n.store.CurrentIndex) } } if !n.IsDir() { // key-value pair _, name := path.Split(n.Path) // find its parent and remove the node from the map if n.Parent != nil && n.Parent.Children[name] == n { delete(n.Parent.Children, name) } if callback != nil { callback(n.Path) } if !n.IsPermanent() { n.store.ttlKeyHeap.remove(n) } return nil } for _, child := range n.Children { // delete all children child.Remove(true, true, callback) } // delete self _, name := path.Split(n.Path) if n.Parent != nil && n.Parent.Children[name] == n { delete(n.Parent.Children, name) if callback != nil { callback(n.Path) } if !n.IsPermanent() { n.store.ttlKeyHeap.remove(n) } } return nil }
// acquireHandler attempts to acquire a lock on the given key. // The "key" parameter specifies the resource to lock. // The "value" parameter specifies a value to associate with the lock. // The "ttl" parameter specifies how long the lock will persist for. // The "timeout" parameter specifies how long the request should wait for the lock. func (h *handler) acquireHandler(w http.ResponseWriter, req *http.Request) error { h.client.SyncCluster() // Setup connection watcher. closeNotifier, _ := w.(http.CloseNotifier) closeChan := closeNotifier.CloseNotify() stopChan := make(chan bool) // Parse the lock "key". vars := mux.Vars(req) keypath := path.Join(prefix, vars["key"]) value := req.FormValue("value") // Parse "timeout" parameter. var timeout int var err error if req.FormValue("timeout") == "" { timeout = -1 } else if timeout, err = strconv.Atoi(req.FormValue("timeout")); err != nil { return etcdErr.NewError(etcdErr.EcodeTimeoutNaN, "Acquire", 0) } timeout = timeout + 1 // Parse TTL. ttl, err := strconv.Atoi(req.FormValue("ttl")) if err != nil { return etcdErr.NewError(etcdErr.EcodeTTLNaN, "Acquire", 0) } // If node exists then just watch it. Otherwise create the node and watch it. node, index, pos := h.findExistingNode(keypath, value) if index > 0 { if pos == 0 { // If lock is already acquired then update the TTL. h.client.Update(node.Key, node.Value, uint64(ttl)) } else { // Otherwise watch until it becomes acquired (or errors). err = h.watch(keypath, index, nil) } } else { index, err = h.createNode(keypath, value, ttl, closeChan, stopChan) } // Stop all goroutines. close(stopChan) // Check for an error. if err != nil { return err } // Write response. w.Write([]byte(strconv.Itoa(index))) return nil }
func (s *store) CompareAndDelete(nodePath string, prevValue string, prevIndex uint64) (*Event, error) { var err *etcdErr.Error s.worldLock.Lock() defer s.worldLock.Unlock() defer func() { if err == nil { s.Stats.Inc(CompareAndDeleteSuccess) reportWriteSuccess(CompareAndDelete) return } s.Stats.Inc(CompareAndDeleteFail) reportWriteFailure(CompareAndDelete) }() nodePath = path.Clean(path.Join("/", nodePath)) n, err := s.internalGet(nodePath) if err != nil { // if the node does not exist, return error return nil, err } if n.IsDir() { // can only compare and delete file return nil, etcdErr.NewError(etcdErr.EcodeNotFile, nodePath, s.CurrentIndex) } // If both of the prevValue and prevIndex are given, we will test both of them. // Command will be executed, only if both of the tests are successful. if ok, which := n.Compare(prevValue, prevIndex); !ok { cause := getCompareFailCause(n, which, prevValue, prevIndex) return nil, etcdErr.NewError(etcdErr.EcodeTestFailed, cause, s.CurrentIndex) } // update etcd index s.CurrentIndex++ e := newEvent(CompareAndDelete, nodePath, s.CurrentIndex, n.CreatedIndex) e.EtcdIndex = s.CurrentIndex e.PrevNode = n.Repr(false, false, s.clock) callback := func(path string) { // notify function // notify the watchers with deleted set true s.WatcherHub.notifyWatchers(e, path, true) } err = n.Remove(false, false, callback) if err != nil { return nil, err } s.WatcherHub.notify(e) return e, nil }
func (s *store) CompareAndSwap(nodePath string, prevValue string, prevIndex uint64, value string, expireTime time.Time) (*Event, error) { nodePath = path.Clean(path.Join("/", nodePath)) // we do not allow the user to change "/" if nodePath == "/" { return nil, etcdErr.NewError(etcdErr.EcodeRootROnly, "/", s.CurrentIndex) } s.worldLock.Lock() defer s.worldLock.Unlock() n, err := s.internalGet(nodePath) if err != nil { s.Stats.Inc(CompareAndSwapFail) return nil, err } if n.IsDir() { // can only compare and swap file s.Stats.Inc(CompareAndSwapFail) return nil, etcdErr.NewError(etcdErr.EcodeNotFile, nodePath, s.CurrentIndex) } // If both of the prevValue and prevIndex are given, we will test both of them. // Command will be executed, only if both of the tests are successful. if !n.Compare(prevValue, prevIndex) { cause := fmt.Sprintf("[%v != %v] [%v != %v]", prevValue, n.Value, prevIndex, n.ModifiedIndex) s.Stats.Inc(CompareAndSwapFail) return nil, etcdErr.NewError(etcdErr.EcodeTestFailed, cause, s.CurrentIndex) } // update etcd index s.CurrentIndex++ e := newEvent(CompareAndSwap, nodePath, s.CurrentIndex, n.CreatedIndex) e.PrevNode = n.Repr(false, false) eNode := e.Node // if test succeed, write the value n.Write(value, s.CurrentIndex) n.UpdateTTL(expireTime) // copy the value for safety valueCopy := ustrings.Clone(value) eNode.Value = &valueCopy eNode.Expiration, eNode.TTL = n.ExpirationAndTTL() s.WatcherHub.notify(e) s.Stats.Inc(CompareAndSwapSuccess) return e, nil }
// Update function updates the value/ttl of the node. // If the node is a file, the value and the ttl can be updated. // If the node is a directory, only the ttl can be updated. func (s *store) Update(nodePath string, newValue string, expireTime time.Time) (*Event, error) { nodePath = path.Clean(path.Join("/", nodePath)) // we do not allow the user to change "/" if nodePath == "/" { return nil, etcdErr.NewError(etcdErr.EcodeRootROnly, "/", s.CurrentIndex) } s.worldLock.Lock() defer s.worldLock.Unlock() currIndex, nextIndex := s.CurrentIndex, s.CurrentIndex+1 n, err := s.internalGet(nodePath) if err != nil { // if the node does not exist, return error s.Stats.Inc(UpdateFail) return nil, err } e := newEvent(Update, nodePath, nextIndex, n.CreatedIndex) eNode := e.Node if len(newValue) != 0 { if n.IsDir() { // if the node is a directory, we cannot update value s.Stats.Inc(UpdateFail) return nil, etcdErr.NewError(etcdErr.EcodeNotFile, nodePath, currIndex) } eNode.PrevValue = n.Value n.Write(newValue, nextIndex) eNode.Value = newValue } else { // do not update value eNode.Value = n.Value } // update ttl n.UpdateTTL(expireTime) eNode.Expiration, eNode.TTL = n.ExpirationAndTTL() s.WatcherHub.notify(e) s.Stats.Inc(UpdateSuccess) s.CurrentIndex = nextIndex return e, nil }
// Join a server to the cluster func (c *JoinCommandV1) Apply(context raft.Context) (interface{}, error) { ps, _ := context.Server().Context().(*PeerServer) b := make([]byte, 8) binary.PutUvarint(b, context.CommitIndex()) // Make sure we're not getting a cached value from the registry. ps.registry.Invalidate(c.Name) // Check if the join command is from a previous peer, who lost all its previous log. if peerURL, ok := ps.registry.PeerURL(c.Name); ok { // If previous node restarts with different peer URL, // update its information. if peerURL != c.RaftURL { log.Infof("Rejoin with %v instead of %v from %v", c.RaftURL, peerURL, c.Name) if err := c.updatePeerURL(ps); err != nil { return []byte{0}, err } } return b, nil } // Check if the join command adds an instance that collides with existing one on peer URL. peerURLs := ps.registry.PeerURLs(ps.raftServer.Leader(), c.Name) for _, peerURL := range peerURLs { if peerURL == c.RaftURL { log.Warnf("%v tries to join the cluster with existing URL %v", c.Name, c.EtcdURL) return []byte{0}, etcdErr.NewError(etcdErr.EcodeExistingPeerAddr, c.EtcdURL, context.CommitIndex()) } } // Check peer number in the cluster if ps.registry.PeerCount() >= ps.ClusterConfig().ActiveSize { log.Debug("Reject join request from ", c.Name) return []byte{0}, etcdErr.NewError(etcdErr.EcodeNoMorePeer, "", context.CommitIndex()) } // Add to shared peer registry. ps.registry.RegisterPeer(c.Name, c.RaftURL, c.EtcdURL) // Add peer in raft err := context.Server().AddPeer(c.Name, "") // Add peer stats if c.Name != ps.RaftServer().Name() { ps.followersStats.Followers[c.Name] = &raftFollowerStats{} ps.followersStats.Followers[c.Name].Latency.Minimum = 1 << 63 } return b, err }
// renewLockHandler attempts to update the TTL on an existing lock. // Returns a 200 OK if successful. Returns non-200 on error. func (h *handler) renewLockHandler(w http.ResponseWriter, req *http.Request) error { h.client.SyncCluster() // Read the lock path. vars := mux.Vars(req) keypath := path.Join(prefix, vars["key"]) // Parse new TTL parameter. ttl, err := strconv.Atoi(req.FormValue("ttl")) if err != nil { return etcdErr.NewError(etcdErr.EcodeTTLNaN, "Renew", 0) } // Read and set defaults for index and value. index := req.FormValue("index") value := req.FormValue("value") if len(index) == 0 && len(value) == 0 { return etcdErr.NewError(etcdErr.EcodeIndexOrValueRequired, "Renew", 0) } if len(index) == 0 { // If index is not specified then look it up by value. resp, err := h.client.Get(keypath, true, true) if err != nil { return err } nodes := lockNodes{resp.Node.Nodes} node, _ := nodes.FindByValue(value) if node == nil { return etcdErr.NewError(etcdErr.EcodeKeyNotFound, "Renew", 0) } index = path.Base(node.Key) } else if len(value) == 0 { // If value is not specified then default it to the previous value. resp, err := h.client.Get(path.Join(keypath, index), true, false) if err != nil { return err } value = resp.Node.Value } // Renew the lock, if it exists. if _, err = h.client.Update(path.Join(keypath, index), value, uint64(ttl)); err != nil { return err } return nil }
// Add function adds a node to the receiver node. // If the receiver is not a directory, a "Not A Directory" error will be returned. // If there is an existing node with the same name under the directory, a "Already Exist" // error will be returned func (n *node) Add(child *node) *etcdErr.Error { if !n.IsDir() { return etcdErr.NewError(etcdErr.EcodeNotDir, "", n.store.CurrentIndex) } _, name := path.Split(child.Path) if _, ok := n.Children[name]; ok { return etcdErr.NewError(etcdErr.EcodeNodeExist, "", n.store.CurrentIndex) } n.Children[name] = child return nil }
// deleteHandler remove a given leader. func (h *handler) deleteHandler(w http.ResponseWriter, req *http.Request) error { vars := mux.Vars(req) name := req.FormValue("name") if name == "" { return etcdErr.NewError(etcdErr.EcodeNameRequired, "Delete", 0) } // Proxy the request to the the lock service. u, err := url.Parse(fmt.Sprintf("%s/mod/v2/lock/%s", h.addr, vars["key"])) if err != nil { return err } q := u.Query() q.Set("value", name) u.RawQuery = q.Encode() r, err := http.NewRequest("DELETE", u.String(), nil) if err != nil { return err } // Read from the leader lock. resp, err := h.client.Do(r) if err != nil { return err } defer resp.Body.Close() w.WriteHeader(resp.StatusCode) io.Copy(w, resp.Body) return nil }
// Handler to return the basic stats of etcd func StatsHttpHandler(w http.ResponseWriter, req *http.Request) error { option := req.URL.Path[len("/v1/stats/"):] switch option { case "self": w.WriteHeader(http.StatusOK) w.Write(r.Stats()) case "leader": if r.State() == raft.Leader { w.Write(r.PeerStats()) } else { leader := r.Leader() // current no leader if leader == "" { return etcdErr.NewError(300, "") } redirect(leader, true, w, req) } case "store": w.WriteHeader(http.StatusOK) w.Write(etcdStore.Stats()) } return nil }
// Adds a server handler to the router. func (s *Server) handleFunc(r *mux.Router, path string, f func(http.ResponseWriter, *http.Request) error) *mux.Route { // Wrap the standard HandleFunc interface to pass in the server reference. return r.HandleFunc(path, func(w http.ResponseWriter, req *http.Request) { if req.Method == "HEAD" { w = &HEADResponseWriter{w} } // Log request. log.Debugf("[recv] %s %s %s [%s]", req.Method, s.URL(), req.URL.Path, req.RemoteAddr) // Forward request along if the server is a standby. if s.peerServer.Mode() == StandbyMode { if s.peerServer.standbyClientURL == "" { w.Header().Set("Content-Type", "application/json") etcdErr.NewError(402, "", 0).Write(w) return } uhttp.Redirect(s.peerServer.standbyClientURL, w, req) return } // Execute handler function and return error if necessary. if err := f(w, req); err != nil { if etcdErr, ok := err.(*etcdErr.Error); ok { log.Debug("Return error: ", (*etcdErr).Error()) w.Header().Set("Content-Type", "application/json") etcdErr.Write(w) } else { http.Error(w, err.Error(), http.StatusInternalServerError) } } }) }
// Delete deletes the node at the given path. // If the node is a directory, recursive must be true to delete it. func (s *store) Delete(nodePath string, dir, recursive bool) (*Event, error) { var err *etcdErr.Error s.worldLock.Lock() defer s.worldLock.Unlock() defer func() { if err == nil { s.Stats.Inc(DeleteSuccess) reportWriteSuccess(Delete) return } s.Stats.Inc(DeleteFail) reportWriteFailure(Delete) }() nodePath = path.Clean(path.Join("/", nodePath)) // we do not allow the user to change "/" if s.readonlySet.Contains(nodePath) { return nil, etcdErr.NewError(etcdErr.EcodeRootROnly, "/", s.CurrentIndex) } // recursive implies dir if recursive == true { dir = true } n, err := s.internalGet(nodePath) if err != nil { // if the node does not exist, return error return nil, err } nextIndex := s.CurrentIndex + 1 e := newEvent(Delete, nodePath, nextIndex, n.CreatedIndex) e.EtcdIndex = nextIndex e.PrevNode = n.Repr(false, false, s.clock) eNode := e.Node if n.IsDir() { eNode.Dir = true } callback := func(path string) { // notify function // notify the watchers with deleted set true s.WatcherHub.notifyWatchers(e, path, true) } err = n.Remove(dir, recursive, callback) if err != nil { return nil, err } // update etcd index s.CurrentIndex++ s.WatcherHub.notify(e) return e, nil }
// Join a server to the cluster func (c *JoinCommand) Apply(raftServer *raft.Server) (interface{}, error) { // check if the join command is from a previous machine, who lost all its previous log. response, _ := etcdStore.RawGet(path.Join("_etcd/machines", c.Name)) b := make([]byte, 8) binary.PutUvarint(b, raftServer.CommitIndex()) if response != nil { return b, nil } // check machine number in the cluster num := machineNum() if num == maxClusterSize { debug("Reject join request from ", c.Name) return []byte{0}, etcdErr.NewError(103, "") } addNameToURL(c.Name, c.RaftVersion, c.RaftURL, c.EtcdURL) // add peer in raft err := raftServer.AddPeer(c.Name, "") // add machine in etcd storage key := path.Join("_etcd/machines", c.Name) value := fmt.Sprintf("raft=%s&etcd=%s&raftVersion=%s", c.RaftURL, c.EtcdURL, c.RaftVersion) etcdStore.Set(key, value, time.Unix(0, 0), raftServer.CommitIndex()) return b, err }
// Join a server to the cluster func (c *JoinCommand) Apply(server raft.Server) (interface{}, error) { ps, _ := server.Context().(*PeerServer) b := make([]byte, 8) binary.PutUvarint(b, server.CommitIndex()) // Make sure we're not getting a cached value from the registry. ps.registry.Invalidate(c.Name) // Check if the join command is from a previous peer, who lost all its previous log. if _, ok := ps.registry.ClientURL(c.Name); ok { return b, nil } // Check peer number in the cluster if ps.registry.Count() == ps.MaxClusterSize { log.Debug("Reject join request from ", c.Name) return []byte{0}, etcdErr.NewError(etcdErr.EcodeNoMorePeer, "", server.CommitIndex()) } // Add to shared peer registry. ps.registry.Register(c.Name, c.RaftURL, c.EtcdURL) // Add peer in raft err := server.AddPeer(c.Name, "") // Add peer stats if c.Name != ps.RaftServer().Name() { ps.followersStats.Followers[c.Name] = &raftFollowerStats{} ps.followersStats.Followers[c.Name].Latency.Minimum = 1 << 63 } return b, err }
// Read function gets the value of the node. // If the receiver node is not a key-value pair, a "Not A File" error will be returned. func (n *node) Read() (string, *etcdErr.Error) { if n.IsDir() { return "", etcdErr.NewError(etcdErr.EcodeNotFile, "", n.store.CurrentIndex) } return n.Value, nil }
// scan function is enumerating events from the index in history and // stops till the first point where the key has identified prefix func (eh *EventHistory) scan(prefix string, index uint64) (*Event, *etcdErr.Error) { eh.rwl.RLock() defer eh.rwl.RUnlock() // the index should locate after the event history's StartIndex if index-eh.StartIndex < 0 { return nil, etcdErr.NewError(etcdErr.EcodeEventIndexCleared, fmt.Sprintf("the requested history has been cleared [%v/%v]", eh.StartIndex, index), 0) } // the index should locate before the size of the queue minus the duplicate count if index > eh.LastIndex { // future index return nil, nil } i := eh.Queue.Front for { e := eh.Queue.Events[i] if strings.HasPrefix(e.Key, prefix) && index <= e.Index() { // make sure we bypass the smaller one return e, nil } i = (i + 1) % eh.Queue.Capacity if i > eh.Queue.back() { return nil, nil } } }
// getIndexHandler retrieves the current lock index. // The "field" parameter specifies to read either the lock "index" or lock "value". func (h *handler) getIndexHandler(w http.ResponseWriter, req *http.Request) error { h.client.SyncCluster() vars := mux.Vars(req) keypath := path.Join(prefix, vars["key"]) field := req.FormValue("field") if len(field) == 0 { field = "value" } // Read all indices. resp, err := h.client.Get(keypath, true, true) if err != nil { return err } nodes := lockNodes{resp.Node.Nodes} // Write out the requested field. if node := nodes.First(); node != nil { switch field { case "index": w.Write([]byte(path.Base(node.Key))) case "value": w.Write([]byte(node.Value)) default: return etcdErr.NewError(etcdErr.EcodeInvalidField, "Get", 0) } } return nil }
func TestWriteError(t *testing.T) { // nil error should not panic rec := httptest.NewRecorder() r := new(http.Request) writeError(rec, r, nil) h := rec.Header() if len(h) > 0 { t.Fatalf("unexpected non-empty headers: %#v", h) } b := rec.Body.String() if len(b) > 0 { t.Fatalf("unexpected non-empty body: %q", b) } tests := []struct { err error wcode int wi string }{ { etcdErr.NewError(etcdErr.EcodeKeyNotFound, "/foo/bar", 123), http.StatusNotFound, "123", }, { etcdErr.NewError(etcdErr.EcodeTestFailed, "/foo/bar", 456), http.StatusPreconditionFailed, "456", }, { err: errors.New("something went wrong"), wcode: http.StatusInternalServerError, }, } for i, tt := range tests { rw := httptest.NewRecorder() writeError(rw, r, tt.err) if code := rw.Code; code != tt.wcode { t.Errorf("#%d: code=%d, want %d", i, code, tt.wcode) } if idx := rw.Header().Get("X-Etcd-Index"); idx != tt.wi { t.Errorf("#%d: X-Etcd-Index=%q, want %q", i, idx, tt.wi) } } }
// Add function adds a node to the receiver node. // If the receiver is not a directory, a "Not A Directory" error will be returned. // If there is a existing node with the same name under the directory, a "Already Exist" // error will be returned func (n *Node) Add(child *Node) *etcdErr.Error { if !n.IsDir() { return etcdErr.NewError(etcdErr.EcodeNotDir, "", n.store.Index()) } _, name := path.Split(child.Path) _, ok := n.Children[name] if ok { return etcdErr.NewError(etcdErr.EcodeNodeExist, "", n.store.Index()) } n.Children[name] = child return nil }
func (s *store) CompareAndSwap(nodePath string, prevValue string, prevIndex uint64, value string, expireTime time.Time) (*Event, error) { nodePath = path.Clean(path.Join("/", nodePath)) s.worldLock.Lock() defer s.worldLock.Unlock() n, err := s.internalGet(nodePath) if err != nil { s.Stats.Inc(CompareAndSwapFail) return nil, err } if n.IsDir() { // can only test and set file s.Stats.Inc(CompareAndSwapFail) return nil, etcdErr.NewError(etcdErr.EcodeNotFile, nodePath, s.CurrentIndex) } // If both of the prevValue and prevIndex are given, we will test both of them. // Command will be executed, only if both of the tests are successful. if (prevValue == "" || n.Value == prevValue) && (prevIndex == 0 || n.ModifiedIndex == prevIndex) { // update etcd index s.CurrentIndex++ e := newEvent(CompareAndSwap, nodePath, s.CurrentIndex) e.PrevValue = n.Value // if test succeed, write the value n.Write(value, s.CurrentIndex) n.UpdateTTL(expireTime) e.Value = value e.Expiration, e.TTL = n.ExpirationAndTTL() s.WatcherHub.notify(e) s.Stats.Inc(CompareAndSwapSuccess) return e, nil } cause := fmt.Sprintf("[%v != %v] [%v != %v]", prevValue, n.Value, prevIndex, n.ModifiedIndex) s.Stats.Inc(CompareAndSwapFail) return nil, etcdErr.NewError(etcdErr.EcodeTestFailed, cause, s.CurrentIndex) }
// Set Command Handler func SetHttpHandler(w http.ResponseWriter, req *http.Request) error { key := req.URL.Path[len("/v1/keys/"):] if store.CheckKeyword(key) { return etcdErr.NewError(400, "Set") } debugf("[recv] POST %v/v1/keys/%s [%s]", e.url, key, req.RemoteAddr) req.ParseForm() value := req.Form.Get("value") if len(value) == 0 { return etcdErr.NewError(200, "Set") } strDuration := req.Form.Get("ttl") expireTime, err := durationToExpireTime(strDuration) if err != nil { return etcdErr.NewError(202, "Set") } if prevValueArr, ok := req.Form["prevValue"]; ok && len(prevValueArr) > 0 { command := &TestAndSetCommand{ Key: key, Value: value, PrevValue: prevValueArr[0], ExpireTime: expireTime, } return dispatch(command, w, req, true) } else { command := &SetCommand{ Key: key, Value: value, ExpireTime: expireTime, } return dispatch(command, w, req, true) } }