github.com/outbrain/consul@v1.4.5/agent/session_endpoint.go (about) 1 package agent 2 3 import ( 4 "fmt" 5 "net/http" 6 "strings" 7 "time" 8 9 "github.com/hashicorp/consul/agent/structs" 10 "github.com/hashicorp/consul/types" 11 ) 12 13 const ( 14 // lockDelayMinThreshold is used to convert a numeric lock 15 // delay value from nanoseconds to seconds if it is below this 16 // threshold. Users often send a value like 5, which they assume 17 // is seconds, but because Go uses nanosecond granularity, ends 18 // up being very small. If we see a value below this threshold, 19 // we multiply by time.Second 20 lockDelayMinThreshold = 1000 21 ) 22 23 // sessionCreateResponse is used to wrap the session ID 24 type sessionCreateResponse struct { 25 ID string 26 } 27 28 // SessionCreate is used to create a new session 29 func (s *HTTPServer) SessionCreate(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 30 // Default the session to our node + serf check + release session 31 // invalidate behavior. 32 args := structs.SessionRequest{ 33 Op: structs.SessionCreate, 34 Session: structs.Session{ 35 Node: s.agent.config.NodeName, 36 Checks: []types.CheckID{structs.SerfCheckID}, 37 LockDelay: 15 * time.Second, 38 Behavior: structs.SessionKeysRelease, 39 TTL: "", 40 }, 41 } 42 s.parseDC(req, &args.Datacenter) 43 s.parseToken(req, &args.Token) 44 45 // Handle optional request body 46 if req.ContentLength > 0 { 47 fixup := func(raw interface{}) error { 48 if err := FixupLockDelay(raw); err != nil { 49 return err 50 } 51 if err := FixupChecks(raw, &args.Session); err != nil { 52 return err 53 } 54 return nil 55 } 56 if err := decodeBody(req, &args.Session, fixup); err != nil { 57 resp.WriteHeader(http.StatusBadRequest) 58 fmt.Fprintf(resp, "Request decode failed: %v", err) 59 return nil, nil 60 } 61 } 62 63 // Create the session, get the ID 64 var out string 65 if err := s.agent.RPC("Session.Apply", &args, &out); err != nil { 66 return nil, err 67 } 68 69 // Format the response as a JSON object 70 return sessionCreateResponse{out}, nil 71 } 72 73 // FixupLockDelay is used to handle parsing the JSON body to session/create 74 // and properly parsing out the lock delay duration value. 75 func FixupLockDelay(raw interface{}) error { 76 rawMap, ok := raw.(map[string]interface{}) 77 if !ok { 78 return nil 79 } 80 var key string 81 for k := range rawMap { 82 if strings.ToLower(k) == "lockdelay" { 83 key = k 84 break 85 } 86 } 87 if key != "" { 88 val := rawMap[key] 89 // Convert a string value into an integer 90 if vStr, ok := val.(string); ok { 91 dur, err := time.ParseDuration(vStr) 92 if err != nil { 93 return err 94 } 95 if dur < lockDelayMinThreshold { 96 dur = dur * time.Second 97 } 98 rawMap[key] = dur 99 } 100 // Convert low value integers into seconds 101 if vNum, ok := val.(float64); ok { 102 dur := time.Duration(vNum) 103 if dur < lockDelayMinThreshold { 104 dur = dur * time.Second 105 } 106 rawMap[key] = dur 107 } 108 } 109 return nil 110 } 111 112 // FixupChecks is used to handle parsing the JSON body to default-add the Serf 113 // health check if they didn't specify any checks, but to allow an empty list 114 // to take out the Serf health check. This behavior broke when mapstructure was 115 // updated after 0.9.3, likely because we have a type wrapper around the string. 116 func FixupChecks(raw interface{}, s *structs.Session) error { 117 rawMap, ok := raw.(map[string]interface{}) 118 if !ok { 119 return nil 120 } 121 for k := range rawMap { 122 if strings.ToLower(k) == "checks" { 123 // If they supplied a checks key in the JSON, then 124 // remove the default entries and respect whatever they 125 // specified. 126 s.Checks = nil 127 return nil 128 } 129 } 130 return nil 131 } 132 133 // SessionDestroy is used to destroy an existing session 134 func (s *HTTPServer) SessionDestroy(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 135 args := structs.SessionRequest{ 136 Op: structs.SessionDestroy, 137 } 138 s.parseDC(req, &args.Datacenter) 139 s.parseToken(req, &args.Token) 140 141 // Pull out the session id 142 args.Session.ID = strings.TrimPrefix(req.URL.Path, "/v1/session/destroy/") 143 if args.Session.ID == "" { 144 resp.WriteHeader(http.StatusBadRequest) 145 fmt.Fprint(resp, "Missing session") 146 return nil, nil 147 } 148 149 var out string 150 if err := s.agent.RPC("Session.Apply", &args, &out); err != nil { 151 return nil, err 152 } 153 return true, nil 154 } 155 156 // SessionRenew is used to renew the TTL on an existing TTL session 157 func (s *HTTPServer) SessionRenew(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 158 args := structs.SessionSpecificRequest{} 159 if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done { 160 return nil, nil 161 } 162 163 // Pull out the session id 164 args.Session = strings.TrimPrefix(req.URL.Path, "/v1/session/renew/") 165 if args.Session == "" { 166 resp.WriteHeader(http.StatusBadRequest) 167 fmt.Fprint(resp, "Missing session") 168 return nil, nil 169 } 170 171 var out structs.IndexedSessions 172 if err := s.agent.RPC("Session.Renew", &args, &out); err != nil { 173 return nil, err 174 } else if out.Sessions == nil { 175 resp.WriteHeader(http.StatusNotFound) 176 fmt.Fprintf(resp, "Session id '%s' not found", args.Session) 177 return nil, nil 178 } 179 180 return out.Sessions, nil 181 } 182 183 // SessionGet is used to get info for a particular session 184 func (s *HTTPServer) SessionGet(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 185 args := structs.SessionSpecificRequest{} 186 if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done { 187 return nil, nil 188 } 189 190 // Pull out the session id 191 args.Session = strings.TrimPrefix(req.URL.Path, "/v1/session/info/") 192 if args.Session == "" { 193 resp.WriteHeader(http.StatusBadRequest) 194 fmt.Fprint(resp, "Missing session") 195 return nil, nil 196 } 197 198 var out structs.IndexedSessions 199 defer setMeta(resp, &out.QueryMeta) 200 if err := s.agent.RPC("Session.Get", &args, &out); err != nil { 201 return nil, err 202 } 203 204 // Use empty list instead of nil 205 if out.Sessions == nil { 206 out.Sessions = make(structs.Sessions, 0) 207 } 208 return out.Sessions, nil 209 } 210 211 // SessionList is used to list all the sessions 212 func (s *HTTPServer) SessionList(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 213 args := structs.DCSpecificRequest{} 214 if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done { 215 return nil, nil 216 } 217 218 var out structs.IndexedSessions 219 defer setMeta(resp, &out.QueryMeta) 220 if err := s.agent.RPC("Session.List", &args, &out); err != nil { 221 return nil, err 222 } 223 224 // Use empty list instead of nil 225 if out.Sessions == nil { 226 out.Sessions = make(structs.Sessions, 0) 227 } 228 return out.Sessions, nil 229 } 230 231 // SessionsForNode returns all the nodes belonging to a node 232 func (s *HTTPServer) SessionsForNode(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 233 args := structs.NodeSpecificRequest{} 234 if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done { 235 return nil, nil 236 } 237 238 // Pull out the node name 239 args.Node = strings.TrimPrefix(req.URL.Path, "/v1/session/node/") 240 if args.Node == "" { 241 resp.WriteHeader(http.StatusBadRequest) 242 fmt.Fprint(resp, "Missing node name") 243 return nil, nil 244 } 245 246 var out structs.IndexedSessions 247 defer setMeta(resp, &out.QueryMeta) 248 if err := s.agent.RPC("Session.NodeSessions", &args, &out); err != nil { 249 return nil, err 250 } 251 252 // Use empty list instead of nil 253 if out.Sessions == nil { 254 out.Sessions = make(structs.Sessions, 0) 255 } 256 return out.Sessions, nil 257 }