github.com/ghodss/etcd@v0.3.1-0.20140417172404-cc329bfa55cb/store/node.go (about) 1 package store 2 3 import ( 4 "path" 5 "sort" 6 "time" 7 8 etcdErr "github.com/coreos/etcd/error" 9 ustrings "github.com/coreos/etcd/pkg/strings" 10 ) 11 12 // explanations of Compare function result 13 const ( 14 CompareMatch = 0 15 CompareIndexNotMatch = 1 16 CompareValueNotMatch = 2 17 CompareNotMatch = 3 18 ) 19 20 var Permanent time.Time 21 22 // node is the basic element in the store system. 23 // A key-value pair will have a string value 24 // A directory will have a children map 25 type node struct { 26 Path string 27 28 CreatedIndex uint64 29 ModifiedIndex uint64 30 31 Parent *node `json:"-"` // should not encode this field! avoid circular dependency. 32 33 ExpireTime time.Time 34 ACL string 35 Value string // for key-value pair 36 Children map[string]*node // for directory 37 38 // A reference to the store this node is attached to. 39 store *store 40 } 41 42 // newKV creates a Key-Value pair 43 func newKV(store *store, nodePath string, value string, createdIndex uint64, 44 parent *node, ACL string, expireTime time.Time) *node { 45 46 return &node{ 47 Path: nodePath, 48 CreatedIndex: createdIndex, 49 ModifiedIndex: createdIndex, 50 Parent: parent, 51 ACL: ACL, 52 store: store, 53 ExpireTime: expireTime, 54 Value: value, 55 } 56 } 57 58 // newDir creates a directory 59 func newDir(store *store, nodePath string, createdIndex uint64, parent *node, 60 ACL string, expireTime time.Time) *node { 61 62 return &node{ 63 Path: nodePath, 64 CreatedIndex: createdIndex, 65 ModifiedIndex: createdIndex, 66 Parent: parent, 67 ACL: ACL, 68 ExpireTime: expireTime, 69 Children: make(map[string]*node), 70 store: store, 71 } 72 } 73 74 // IsHidden function checks if the node is a hidden node. A hidden node 75 // will begin with '_' 76 // A hidden node will not be shown via get command under a directory 77 // For example if we have /foo/_hidden and /foo/notHidden, get "/foo" 78 // will only return /foo/notHidden 79 func (n *node) IsHidden() bool { 80 _, name := path.Split(n.Path) 81 82 return name[0] == '_' 83 } 84 85 // IsPermanent function checks if the node is a permanent one. 86 func (n *node) IsPermanent() bool { 87 // we use a uninitialized time.Time to indicate the node is a 88 // permanent one. 89 // the uninitialized time.Time should equal zero. 90 return n.ExpireTime.IsZero() 91 } 92 93 // IsDir function checks whether the node is a directory. 94 // If the node is a directory, the function will return true. 95 // Otherwise the function will return false. 96 func (n *node) IsDir() bool { 97 return !(n.Children == nil) 98 } 99 100 // Read function gets the value of the node. 101 // If the receiver node is not a key-value pair, a "Not A File" error will be returned. 102 func (n *node) Read() (string, *etcdErr.Error) { 103 if n.IsDir() { 104 return "", etcdErr.NewError(etcdErr.EcodeNotFile, "", n.store.Index()) 105 } 106 107 return n.Value, nil 108 } 109 110 // Write function set the value of the node to the given value. 111 // If the receiver node is a directory, a "Not A File" error will be returned. 112 func (n *node) Write(value string, index uint64) *etcdErr.Error { 113 if n.IsDir() { 114 return etcdErr.NewError(etcdErr.EcodeNotFile, "", n.store.Index()) 115 } 116 117 n.Value = value 118 n.ModifiedIndex = index 119 120 return nil 121 } 122 123 func (n *node) ExpirationAndTTL() (*time.Time, int64) { 124 if !n.IsPermanent() { 125 /* compute ttl as: 126 ceiling( (expireTime - timeNow) / nanosecondsPerSecond ) 127 which ranges from 1..n 128 rather than as: 129 ( (expireTime - timeNow) / nanosecondsPerSecond ) + 1 130 which ranges 1..n+1 131 */ 132 ttlN := n.ExpireTime.Sub(time.Now()) 133 ttl := ttlN / time.Second 134 if (ttlN % time.Second) > 0 { 135 ttl++ 136 } 137 return &n.ExpireTime, int64(ttl) 138 } 139 return nil, 0 140 } 141 142 // List function return a slice of nodes under the receiver node. 143 // If the receiver node is not a directory, a "Not A Directory" error will be returned. 144 func (n *node) List() ([]*node, *etcdErr.Error) { 145 if !n.IsDir() { 146 return nil, etcdErr.NewError(etcdErr.EcodeNotDir, "", n.store.Index()) 147 } 148 149 nodes := make([]*node, len(n.Children)) 150 151 i := 0 152 for _, node := range n.Children { 153 nodes[i] = node 154 i++ 155 } 156 157 return nodes, nil 158 } 159 160 // GetChild function returns the child node under the directory node. 161 // On success, it returns the file node 162 func (n *node) GetChild(name string) (*node, *etcdErr.Error) { 163 if !n.IsDir() { 164 return nil, etcdErr.NewError(etcdErr.EcodeNotDir, n.Path, n.store.Index()) 165 } 166 167 child, ok := n.Children[name] 168 169 if ok { 170 return child, nil 171 } 172 173 return nil, nil 174 } 175 176 // Add function adds a node to the receiver node. 177 // If the receiver is not a directory, a "Not A Directory" error will be returned. 178 // If there is a existing node with the same name under the directory, a "Already Exist" 179 // error will be returned 180 func (n *node) Add(child *node) *etcdErr.Error { 181 if !n.IsDir() { 182 return etcdErr.NewError(etcdErr.EcodeNotDir, "", n.store.Index()) 183 } 184 185 _, name := path.Split(child.Path) 186 187 _, ok := n.Children[name] 188 189 if ok { 190 return etcdErr.NewError(etcdErr.EcodeNodeExist, "", n.store.Index()) 191 } 192 193 n.Children[name] = child 194 195 return nil 196 } 197 198 // Remove function remove the node. 199 func (n *node) Remove(dir, recursive bool, callback func(path string)) *etcdErr.Error { 200 201 if n.IsDir() { 202 if !dir { 203 // cannot delete a directory without recursive set to true 204 return etcdErr.NewError(etcdErr.EcodeNotFile, n.Path, n.store.Index()) 205 } 206 207 if len(n.Children) != 0 && !recursive { 208 // cannot delete a directory if it is not empty and the operation 209 // is not recursive 210 return etcdErr.NewError(etcdErr.EcodeDirNotEmpty, n.Path, n.store.Index()) 211 } 212 } 213 214 if !n.IsDir() { // key-value pair 215 _, name := path.Split(n.Path) 216 217 // find its parent and remove the node from the map 218 if n.Parent != nil && n.Parent.Children[name] == n { 219 delete(n.Parent.Children, name) 220 } 221 222 if callback != nil { 223 callback(n.Path) 224 } 225 226 if !n.IsPermanent() { 227 n.store.ttlKeyHeap.remove(n) 228 } 229 230 return nil 231 } 232 233 for _, child := range n.Children { // delete all children 234 child.Remove(true, true, callback) 235 } 236 237 // delete self 238 _, name := path.Split(n.Path) 239 if n.Parent != nil && n.Parent.Children[name] == n { 240 delete(n.Parent.Children, name) 241 242 if callback != nil { 243 callback(n.Path) 244 } 245 246 if !n.IsPermanent() { 247 n.store.ttlKeyHeap.remove(n) 248 } 249 250 } 251 252 return nil 253 } 254 255 func (n *node) Repr(recurisive, sorted bool) *NodeExtern { 256 if n.IsDir() { 257 node := &NodeExtern{ 258 Key: n.Path, 259 Dir: true, 260 ModifiedIndex: n.ModifiedIndex, 261 CreatedIndex: n.CreatedIndex, 262 } 263 node.Expiration, node.TTL = n.ExpirationAndTTL() 264 265 if !recurisive { 266 return node 267 } 268 269 children, _ := n.List() 270 node.Nodes = make(NodeExterns, len(children)) 271 272 // we do not use the index in the children slice directly 273 // we need to skip the hidden one 274 i := 0 275 276 for _, child := range children { 277 278 if child.IsHidden() { // get will not list hidden node 279 continue 280 } 281 282 node.Nodes[i] = child.Repr(recurisive, sorted) 283 284 i++ 285 } 286 287 // eliminate hidden nodes 288 node.Nodes = node.Nodes[:i] 289 if sorted { 290 sort.Sort(node.Nodes) 291 } 292 293 return node 294 } 295 296 // since n.Value could be changed later, so we need to copy the value out 297 value := ustrings.Clone(n.Value) 298 node := &NodeExtern{ 299 Key: n.Path, 300 Value: &value, 301 ModifiedIndex: n.ModifiedIndex, 302 CreatedIndex: n.CreatedIndex, 303 } 304 node.Expiration, node.TTL = n.ExpirationAndTTL() 305 return node 306 } 307 308 func (n *node) UpdateTTL(expireTime time.Time) { 309 310 if !n.IsPermanent() { 311 if expireTime.IsZero() { 312 // from ttl to permanent 313 n.ExpireTime = expireTime 314 // remove from ttl heap 315 n.store.ttlKeyHeap.remove(n) 316 } else { 317 // update ttl 318 n.ExpireTime = expireTime 319 // update ttl heap 320 n.store.ttlKeyHeap.update(n) 321 } 322 323 } else { 324 if !expireTime.IsZero() { 325 // from permanent to ttl 326 n.ExpireTime = expireTime 327 // push into ttl heap 328 n.store.ttlKeyHeap.push(n) 329 } 330 } 331 } 332 333 // Compare function compares node index and value with provided ones. 334 // second result value explains result and equals to one of Compare.. constants 335 func (n *node) Compare(prevValue string, prevIndex uint64) (ok bool, which int) { 336 indexMatch := (prevIndex == 0 || n.ModifiedIndex == prevIndex) 337 valueMatch := (prevValue == "" || n.Value == prevValue) 338 ok = valueMatch && indexMatch 339 switch { 340 case valueMatch && indexMatch: 341 which = CompareMatch 342 case indexMatch && !valueMatch: 343 which = CompareValueNotMatch 344 case valueMatch && !indexMatch: 345 which = CompareIndexNotMatch 346 default: 347 which = CompareNotMatch 348 } 349 return 350 } 351 352 // Clone function clone the node recursively and return the new node. 353 // If the node is a directory, it will clone all the content under this directory. 354 // If the node is a key-value pair, it will clone the pair. 355 func (n *node) Clone() *node { 356 if !n.IsDir() { 357 return newKV(n.store, n.Path, n.Value, n.CreatedIndex, n.Parent, n.ACL, n.ExpireTime) 358 } 359 360 clone := newDir(n.store, n.Path, n.CreatedIndex, n.Parent, n.ACL, n.ExpireTime) 361 362 for key, child := range n.Children { 363 clone.Children[key] = child.Clone() 364 } 365 366 return clone 367 } 368 369 // recoverAndclean function help to do recovery. 370 // Two things need to be done: 1. recovery structure; 2. delete expired nodes 371 372 // If the node is a directory, it will help recover children's parent pointer and recursively 373 // call this function on its children. 374 // We check the expire last since we need to recover the whole structure first and add all the 375 // notifications into the event history. 376 func (n *node) recoverAndclean() { 377 if n.IsDir() { 378 for _, child := range n.Children { 379 child.Parent = n 380 child.store = n.store 381 child.recoverAndclean() 382 } 383 } 384 385 if !n.ExpireTime.IsZero() { 386 n.store.ttlKeyHeap.push(n) 387 } 388 389 }