blob: c3c87431cb871f8ea1e0af2a075620cd2372ba46 [file] [log] [blame]
khenaidood948f772021-08-11 17:49:24 -04001// 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
15package store
16
17import (
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
27const (
28 CompareMatch = iota
29 CompareIndexNotMatch
30 CompareValueNotMatch
31 CompareNotMatch
32)
33
34var 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
39type 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
56func 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
69func 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
86func (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.
93func (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.
103func (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.
109func (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.
119func (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
130func (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.
152func (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
170func (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
188func (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.
205func (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
257func (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
310func (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
339func (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.
359func (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.
383func (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}