1package api
2
3import (
4	"fmt"
5	"time"
6)
7
8const (
9	// SessionBehaviorRelease is the default behavior and causes
10	// all associated locks to be released on session invalidation.
11	SessionBehaviorRelease = "release"
12
13	// SessionBehaviorDelete is new in Consul 0.5 and changes the
14	// behavior to delete all associated locks on session invalidation.
15	// It can be used in a way similar to Ephemeral Nodes in ZooKeeper.
16	SessionBehaviorDelete = "delete"
17)
18
19// SessionEntry represents a session in consul
20type SessionEntry struct {
21	CreateIndex uint64
22	ID          string
23	Name        string
24	Node        string
25	Checks      []string
26	LockDelay   time.Duration
27	Behavior    string
28	TTL         string
29}
30
31// Session can be used to query the Session endpoints
32type Session struct {
33	c *Client
34}
35
36// Session returns a handle to the session endpoints
37func (c *Client) Session() *Session {
38	return &Session{c}
39}
40
41// CreateNoChecks is like Create but is used specifically to create
42// a session with no associated health checks.
43func (s *Session) CreateNoChecks(se *SessionEntry, q *WriteOptions) (string, *WriteMeta, error) {
44	body := make(map[string]interface{})
45	body["Checks"] = []string{}
46	if se != nil {
47		if se.Name != "" {
48			body["Name"] = se.Name
49		}
50		if se.Node != "" {
51			body["Node"] = se.Node
52		}
53		if se.LockDelay != 0 {
54			body["LockDelay"] = durToMsec(se.LockDelay)
55		}
56		if se.Behavior != "" {
57			body["Behavior"] = se.Behavior
58		}
59		if se.TTL != "" {
60			body["TTL"] = se.TTL
61		}
62	}
63	return s.create(body, q)
64
65}
66
67// Create makes a new session. Providing a session entry can
68// customize the session. It can also be nil to use defaults.
69func (s *Session) Create(se *SessionEntry, q *WriteOptions) (string, *WriteMeta, error) {
70	var obj interface{}
71	if se != nil {
72		body := make(map[string]interface{})
73		obj = body
74		if se.Name != "" {
75			body["Name"] = se.Name
76		}
77		if se.Node != "" {
78			body["Node"] = se.Node
79		}
80		if se.LockDelay != 0 {
81			body["LockDelay"] = durToMsec(se.LockDelay)
82		}
83		if len(se.Checks) > 0 {
84			body["Checks"] = se.Checks
85		}
86		if se.Behavior != "" {
87			body["Behavior"] = se.Behavior
88		}
89		if se.TTL != "" {
90			body["TTL"] = se.TTL
91		}
92	}
93	return s.create(obj, q)
94}
95
96func (s *Session) create(obj interface{}, q *WriteOptions) (string, *WriteMeta, error) {
97	var out struct{ ID string }
98	wm, err := s.c.write("/v1/session/create", obj, &out, q)
99	if err != nil {
100		return "", nil, err
101	}
102	return out.ID, wm, nil
103}
104
105// Destroy invalides a given session
106func (s *Session) Destroy(id string, q *WriteOptions) (*WriteMeta, error) {
107	wm, err := s.c.write("/v1/session/destroy/"+id, nil, nil, q)
108	if err != nil {
109		return nil, err
110	}
111	return wm, nil
112}
113
114// Renew renews the TTL on a given session
115func (s *Session) Renew(id string, q *WriteOptions) (*SessionEntry, *WriteMeta, error) {
116	var entries []*SessionEntry
117	wm, err := s.c.write("/v1/session/renew/"+id, nil, &entries, q)
118	if err != nil {
119		return nil, nil, err
120	}
121	if len(entries) > 0 {
122		return entries[0], wm, nil
123	}
124	return nil, wm, nil
125}
126
127// RenewPeriodic is used to periodically invoke Session.Renew on a
128// session until a doneCh is closed. This is meant to be used in a long running
129// goroutine to ensure a session stays valid.
130func (s *Session) RenewPeriodic(initialTTL string, id string, q *WriteOptions, doneCh chan struct{}) error {
131	ttl, err := time.ParseDuration(initialTTL)
132	if err != nil {
133		return err
134	}
135
136	waitDur := ttl / 2
137	lastRenewTime := time.Now()
138	var lastErr error
139	for {
140		if time.Since(lastRenewTime) > ttl {
141			return lastErr
142		}
143		select {
144		case <-time.After(waitDur):
145			entry, _, err := s.Renew(id, q)
146			if err != nil {
147				waitDur = time.Second
148				lastErr = err
149				continue
150			}
151			if entry == nil {
152				waitDur = time.Second
153				lastErr = fmt.Errorf("No SessionEntry returned")
154				continue
155			}
156
157			// Handle the server updating the TTL
158			ttl, _ = time.ParseDuration(entry.TTL)
159			waitDur = ttl / 2
160			lastRenewTime = time.Now()
161
162		case <-doneCh:
163			// Attempt a session destroy
164			s.Destroy(id, q)
165			return nil
166		}
167	}
168}
169
170// Info looks up a single session
171func (s *Session) Info(id string, q *QueryOptions) (*SessionEntry, *QueryMeta, error) {
172	var entries []*SessionEntry
173	qm, err := s.c.query("/v1/session/info/"+id, &entries, q)
174	if err != nil {
175		return nil, nil, err
176	}
177	if len(entries) > 0 {
178		return entries[0], qm, nil
179	}
180	return nil, qm, nil
181}
182
183// List gets sessions for a node
184func (s *Session) Node(node string, q *QueryOptions) ([]*SessionEntry, *QueryMeta, error) {
185	var entries []*SessionEntry
186	qm, err := s.c.query("/v1/session/node/"+node, &entries, q)
187	if err != nil {
188		return nil, nil, err
189	}
190	return entries, qm, nil
191}
192
193// List gets all active sessions
194func (s *Session) List(q *QueryOptions) ([]*SessionEntry, *QueryMeta, error) {
195	var entries []*SessionEntry
196	qm, err := s.c.query("/v1/session/list", &entries, q)
197	if err != nil {
198		return nil, nil, err
199	}
200	return entries, qm, nil
201}
202