// 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 }
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 }
// 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) { 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) } 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) reportWriteFailure(Update) return nil, err } e := newEvent(Update, nodePath, nextIndex, n.CreatedIndex) e.EtcdIndex = nextIndex e.PrevNode = n.Repr(false, false, s.clock) eNode := e.Node if n.IsDir() && len(newValue) != 0 { // if the node is a directory, we cannot update value to non-empty s.Stats.Inc(UpdateFail) reportWriteFailure(Update) return nil, etcdErr.NewError(etcdErr.EcodeNotFile, nodePath, currIndex) } 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.Stats.Inc(UpdateSuccess) reportWriteSuccess(Update) s.CurrentIndex = nextIndex 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 }
func (s *store) CompareAndDelete(nodePath string, prevValue string, prevIndex uint64) (*Event, error) { nodePath = path.Clean(path.Join("/", nodePath)) s.worldLock.Lock() defer s.worldLock.Unlock() n, err := s.internalGet(nodePath) if err != nil { // if the node does not exist, return error s.Stats.Inc(CompareAndDeleteFail) reportWriteFailure(CompareAndDelete) return nil, err } if n.IsDir() { // can only compare and delete file s.Stats.Inc(CompareAndSwapFail) reportWriteFailure(CompareAndDelete) 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(CompareAndDeleteFail) reportWriteFailure(CompareAndDelete) 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) s.Stats.Inc(CompareAndDeleteSuccess) reportWriteSuccess(CompareAndDelete) return e, nil }
// 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 }
// 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) _, ok := n.Children[name] if ok { return etcdErr.NewError(etcdErr.EcodeNodeExist, "", n.store.CurrentIndex) } n.Children[name] = child 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) } } }
// 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) { 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) } // recursive implies dir if recursive == true { dir = true } n, err := s.internalGet(nodePath) if err != nil { // if the node does not exist, return error s.Stats.Inc(DeleteFail) reportWriteFailure(Delete) 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 { s.Stats.Inc(DeleteFail) reportWriteFailure(Delete) return nil, err } // update etcd index s.CurrentIndex++ s.WatcherHub.notify(e) s.Stats.Inc(DeleteSuccess) reportWriteSuccess(Delete) return e, nil }
// Write function set the value of the node to the given value. // If the receiver node is a directory, a "Not A File" error will be returned. func (n *node) Write(value string, index uint64) *etcdErr.Error { if n.IsDir() { return etcdErr.NewError(etcdErr.EcodeNotFile, "", n.store.CurrentIndex) } n.Value = value n.ModifiedIndex = index return nil }
// GetChild function returns the child node under the directory node. // On success, it returns the file node func (n *node) GetChild(name string) (*node, *etcdErr.Error) { if !n.IsDir() { return nil, etcdErr.NewError(etcdErr.EcodeNotDir, n.Path, n.store.CurrentIndex) } child, ok := n.Children[name] if ok { return child, nil } return nil, nil }
// List function return a slice of nodes under the receiver node. // If the receiver node is not a directory, a "Not A Directory" error will be returned. func (n *node) List() ([]*node, *etcdErr.Error) { if !n.IsDir() { return nil, etcdErr.NewError(etcdErr.EcodeNotDir, "", n.store.CurrentIndex) } nodes := make([]*node, len(n.Children)) i := 0 for _, node := range n.Children { nodes[i] = node i++ } return nodes, nil }
// scan enumerates events from the index history and stops at the first point // where the key matches. func (eh *EventHistory) scan(key string, recursive bool, index uint64) (*Event, *etcdErr.Error) { eh.rwl.RLock() defer eh.rwl.RUnlock() // index should be after the event history's StartIndex if index < eh.StartIndex { return nil, etcdErr.NewError(etcdErr.EcodeEventIndexCleared, fmt.Sprintf("the requested history has been cleared [%v/%v]", eh.StartIndex, index), 0) } // the index should come before the size of the queue minus the duplicate count if index > eh.LastIndex { // future index return nil, nil } offset := index - eh.StartIndex i := (eh.Queue.Front + int(offset)) % eh.Queue.Capacity for { e := eh.Queue.Events[i] ok := (e.Node.Key == key) if recursive { // add tailing slash key := path.Clean(key) if key[len(key)-1] != '/' { key = key + "/" } ok = ok || strings.HasPrefix(e.Node.Key, key) } if ok { return e, nil } i = (i + 1) % eh.Queue.Capacity if i == eh.Queue.Back { return nil, nil } } }
// checkDir will check whether the component is a directory under parent node. // If it is a directory, this function will return the pointer to that node. // If it does not exist, this function will create a new directory and return the pointer to that node. // If it is a file, this function will return error. func (s *store) checkDir(parent *node, dirName string) (*node, *etcdErr.Error) { node, ok := parent.Children[dirName] if ok { if node.IsDir() { return node, nil } return nil, etcdErr.NewError(etcdErr.EcodeNotDir, node.Path, s.CurrentIndex) } n := newDir(s, path.Join(parent.Path, dirName), s.CurrentIndex+1, parent, Permanent) parent.Children[dirName] = n return n, nil }
// writeKeyError logs and writes the given Error to the ResponseWriter. // If Error is not an etcdErr, the error will be converted to an etcd error. func writeKeyError(w http.ResponseWriter, err error) { if err == nil { return } switch e := err.(type) { case *etcdErr.Error: e.WriteTo(w) default: switch err { case etcdserver.ErrTimeoutDueToLeaderFail, etcdserver.ErrTimeoutDueToConnectionLost: plog.Error(err) default: plog.Errorf("got unexpected response error (%v)", err) } ee := etcdErr.NewError(etcdErr.EcodeRaftInternal, err.Error(), 0) ee.WriteTo(w) } }
func writeKeyNoAuth(w http.ResponseWriter) { e := etcdErr.NewError(etcdErr.EcodeUnauthorized, "Insufficient credentials", 0) e.WriteTo(w) }
func (s *store) internalCreate(nodePath string, dir bool, value string, unique, replace bool, expireTime time.Time, action string) (*Event, error) { currIndex, nextIndex := s.CurrentIndex, s.CurrentIndex+1 if unique { // append unique item under the node path nodePath += "/" + fmt.Sprintf("%020s", strconv.FormatUint(nextIndex, 10)) } 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, "/", currIndex) } // Assume expire times that are way in the past are // This can occur when the time is serialized to JS if expireTime.Before(minExpireTime) { expireTime = Permanent } dirName, nodeName := path.Split(nodePath) // walk through the nodePath, create dirs and get the last directory node d, err := s.walk(dirName, s.checkDir) if err != nil { s.Stats.Inc(SetFail) reportWriteFailure(action) err.Index = currIndex return nil, err } e := newEvent(action, nodePath, nextIndex, nextIndex) eNode := e.Node n, _ := d.GetChild(nodeName) // force will try to replace an existing file if n != nil { if replace { if n.IsDir() { return nil, etcdErr.NewError(etcdErr.EcodeNotFile, nodePath, currIndex) } e.PrevNode = n.Repr(false, false, s.clock) n.Remove(false, false, nil) } else { return nil, etcdErr.NewError(etcdErr.EcodeNodeExist, nodePath, currIndex) } } if !dir { // create file // copy the value for safety valueCopy := value eNode.Value = &valueCopy n = newKV(s, nodePath, value, nextIndex, d, expireTime) } else { // create directory eNode.Dir = true n = newDir(s, nodePath, nextIndex, d, expireTime) } // we are sure d is a directory and does not have the children with name n.Name d.Add(n) // node with TTL if !n.IsPermanent() { s.ttlKeyHeap.push(n) eNode.Expiration, eNode.TTL = n.expirationAndTTL(s.clock) } s.CurrentIndex = nextIndex return e, nil }