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