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
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
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}
396