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