github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/command/agent/http.go (about) 1 package agent 2 3 import ( 4 "bytes" 5 "crypto/tls" 6 "encoding/json" 7 "fmt" 8 "io" 9 "log" 10 "net" 11 "net/http" 12 "net/http/pprof" 13 "strconv" 14 "time" 15 16 "github.com/NYTimes/gziphandler" 17 "github.com/hashicorp/nomad/helper/tlsutil" 18 "github.com/hashicorp/nomad/nomad/structs" 19 "github.com/ugorji/go/codec" 20 ) 21 22 const ( 23 // ErrInvalidMethod is used if the HTTP method is not supported 24 ErrInvalidMethod = "Invalid method" 25 26 // scadaHTTPAddr is the address associated with the 27 // HTTPServer. When populating an ACL token for a request, 28 // this is checked to switch between the ACLToken and 29 // AtlasACLToken 30 scadaHTTPAddr = "SCADA" 31 ) 32 33 var ( 34 // jsonHandle and jsonHandlePretty are the codec handles to JSON encode 35 // structs. The pretty handle will add indents for easier human consumption. 36 jsonHandle = &codec.JsonHandle{} 37 jsonHandlePretty = &codec.JsonHandle{Indent: 4} 38 ) 39 40 // HTTPServer is used to wrap an Agent and expose it over an HTTP interface 41 type HTTPServer struct { 42 agent *Agent 43 mux *http.ServeMux 44 listener net.Listener 45 logger *log.Logger 46 addr string 47 } 48 49 // NewHTTPServer starts new HTTP server over the agent 50 func NewHTTPServer(agent *Agent, config *Config, logOutput io.Writer) (*HTTPServer, error) { 51 // Start the listener 52 lnAddr, err := agent.getHTTPAddr(true) 53 if err != nil { 54 return nil, err 55 } 56 ln, err := config.Listener("tcp", lnAddr.IP.String(), lnAddr.Port) 57 if err != nil { 58 return nil, fmt.Errorf("failed to start HTTP listener: %v", err) 59 } 60 61 // If TLS is enabled, wrap the listener with a TLS listener 62 if config.TLSConfig.EnableHTTP { 63 tlsConf := &tlsutil.Config{ 64 VerifyIncoming: false, 65 VerifyOutgoing: true, 66 VerifyServerHostname: config.TLSConfig.VerifyServerHostname, 67 CAFile: config.TLSConfig.CAFile, 68 CertFile: config.TLSConfig.CertFile, 69 KeyFile: config.TLSConfig.KeyFile, 70 } 71 tlsConfig, err := tlsConf.IncomingTLSConfig() 72 if err != nil { 73 return nil, err 74 } 75 ln = tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, tlsConfig) 76 } 77 78 // Create the mux 79 mux := http.NewServeMux() 80 81 // Create the server 82 srv := &HTTPServer{ 83 agent: agent, 84 mux: mux, 85 listener: ln, 86 logger: agent.logger, 87 addr: ln.Addr().String(), 88 } 89 srv.registerHandlers(config.EnableDebug) 90 91 // Start the server 92 go http.Serve(ln, gziphandler.GzipHandler(mux)) 93 return srv, nil 94 } 95 96 // newScadaHttp creates a new HTTP server wrapping the SCADA 97 // listener such that HTTP calls can be sent from the brokers. 98 func newScadaHttp(agent *Agent, list net.Listener) *HTTPServer { 99 // Create the mux 100 mux := http.NewServeMux() 101 102 // Create the server 103 srv := &HTTPServer{ 104 agent: agent, 105 mux: mux, 106 listener: list, 107 logger: agent.logger, 108 addr: scadaHTTPAddr, 109 } 110 srv.registerHandlers(false) // Never allow debug for SCADA 111 112 // Start the server 113 go http.Serve(list, gziphandler.GzipHandler(mux)) 114 return srv 115 } 116 117 // tcpKeepAliveListener sets TCP keep-alive timeouts on accepted 118 // connections. It's used by NewHttpServer so 119 // dead TCP connections eventually go away. 120 type tcpKeepAliveListener struct { 121 *net.TCPListener 122 } 123 124 func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { 125 tc, err := ln.AcceptTCP() 126 if err != nil { 127 return 128 } 129 tc.SetKeepAlive(true) 130 tc.SetKeepAlivePeriod(30 * time.Second) 131 return tc, nil 132 } 133 134 // Shutdown is used to shutdown the HTTP server 135 func (s *HTTPServer) Shutdown() { 136 if s != nil { 137 s.logger.Printf("[DEBUG] http: Shutting down http server") 138 s.listener.Close() 139 } 140 } 141 142 // registerHandlers is used to attach our handlers to the mux 143 func (s *HTTPServer) registerHandlers(enableDebug bool) { 144 s.mux.HandleFunc("/v1/jobs", s.wrap(s.JobsRequest)) 145 s.mux.HandleFunc("/v1/job/", s.wrap(s.JobSpecificRequest)) 146 147 s.mux.HandleFunc("/v1/nodes", s.wrap(s.NodesRequest)) 148 s.mux.HandleFunc("/v1/node/", s.wrap(s.NodeSpecificRequest)) 149 150 s.mux.HandleFunc("/v1/allocations", s.wrap(s.AllocsRequest)) 151 s.mux.HandleFunc("/v1/allocation/", s.wrap(s.AllocSpecificRequest)) 152 153 s.mux.HandleFunc("/v1/evaluations", s.wrap(s.EvalsRequest)) 154 s.mux.HandleFunc("/v1/evaluation/", s.wrap(s.EvalSpecificRequest)) 155 156 s.mux.HandleFunc("/v1/client/fs/", s.wrap(s.FsRequest)) 157 s.mux.HandleFunc("/v1/client/stats", s.wrap(s.ClientStatsRequest)) 158 s.mux.HandleFunc("/v1/client/allocation/", s.wrap(s.ClientAllocRequest)) 159 160 s.mux.HandleFunc("/v1/agent/self", s.wrap(s.AgentSelfRequest)) 161 s.mux.HandleFunc("/v1/agent/join", s.wrap(s.AgentJoinRequest)) 162 s.mux.HandleFunc("/v1/agent/members", s.wrap(s.AgentMembersRequest)) 163 s.mux.HandleFunc("/v1/agent/force-leave", s.wrap(s.AgentForceLeaveRequest)) 164 s.mux.HandleFunc("/v1/agent/servers", s.wrap(s.AgentServersRequest)) 165 s.mux.HandleFunc("/v1/agent/keyring/", s.wrap(s.KeyringOperationRequest)) 166 167 s.mux.HandleFunc("/v1/regions", s.wrap(s.RegionListRequest)) 168 169 s.mux.HandleFunc("/v1/status/leader", s.wrap(s.StatusLeaderRequest)) 170 s.mux.HandleFunc("/v1/status/peers", s.wrap(s.StatusPeersRequest)) 171 172 s.mux.HandleFunc("/v1/system/gc", s.wrap(s.GarbageCollectRequest)) 173 s.mux.HandleFunc("/v1/system/reconcile/summaries", s.wrap(s.ReconcileJobSummaries)) 174 175 if enableDebug { 176 s.mux.HandleFunc("/debug/pprof/", pprof.Index) 177 s.mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) 178 s.mux.HandleFunc("/debug/pprof/profile", pprof.Profile) 179 s.mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) 180 } 181 } 182 183 // HTTPCodedError is used to provide the HTTP error code 184 type HTTPCodedError interface { 185 error 186 Code() int 187 } 188 189 func CodedError(c int, s string) HTTPCodedError { 190 return &codedError{s, c} 191 } 192 193 type codedError struct { 194 s string 195 code int 196 } 197 198 func (e *codedError) Error() string { 199 return e.s 200 } 201 202 func (e *codedError) Code() int { 203 return e.code 204 } 205 206 // wrap is used to wrap functions to make them more convenient 207 func (s *HTTPServer) wrap(handler func(resp http.ResponseWriter, req *http.Request) (interface{}, error)) func(resp http.ResponseWriter, req *http.Request) { 208 f := func(resp http.ResponseWriter, req *http.Request) { 209 setHeaders(resp, s.agent.config.HTTPAPIResponseHeaders) 210 // Invoke the handler 211 reqURL := req.URL.String() 212 start := time.Now() 213 defer func() { 214 s.logger.Printf("[DEBUG] http: Request %v (%v)", reqURL, time.Now().Sub(start)) 215 }() 216 obj, err := handler(resp, req) 217 218 // Check for an error 219 HAS_ERR: 220 if err != nil { 221 s.logger.Printf("[ERR] http: Request %v, error: %v", reqURL, err) 222 code := 500 223 if http, ok := err.(HTTPCodedError); ok { 224 code = http.Code() 225 } 226 resp.WriteHeader(code) 227 resp.Write([]byte(err.Error())) 228 return 229 } 230 231 prettyPrint := false 232 if v, ok := req.URL.Query()["pretty"]; ok { 233 if len(v) > 0 && (len(v[0]) == 0 || v[0] != "0") { 234 prettyPrint = true 235 } 236 } 237 238 // Write out the JSON object 239 if obj != nil { 240 var buf bytes.Buffer 241 if prettyPrint { 242 enc := codec.NewEncoder(&buf, jsonHandlePretty) 243 err = enc.Encode(obj) 244 if err == nil { 245 buf.Write([]byte("\n")) 246 } 247 } else { 248 enc := codec.NewEncoder(&buf, jsonHandle) 249 err = enc.Encode(obj) 250 } 251 if err != nil { 252 goto HAS_ERR 253 } 254 resp.Header().Set("Content-Type", "application/json") 255 resp.Write(buf.Bytes()) 256 } 257 } 258 return f 259 } 260 261 // decodeBody is used to decode a JSON request body 262 func decodeBody(req *http.Request, out interface{}) error { 263 dec := json.NewDecoder(req.Body) 264 return dec.Decode(&out) 265 } 266 267 // setIndex is used to set the index response header 268 func setIndex(resp http.ResponseWriter, index uint64) { 269 resp.Header().Set("X-Nomad-Index", strconv.FormatUint(index, 10)) 270 } 271 272 // setKnownLeader is used to set the known leader header 273 func setKnownLeader(resp http.ResponseWriter, known bool) { 274 s := "true" 275 if !known { 276 s = "false" 277 } 278 resp.Header().Set("X-Nomad-KnownLeader", s) 279 } 280 281 // setLastContact is used to set the last contact header 282 func setLastContact(resp http.ResponseWriter, last time.Duration) { 283 lastMsec := uint64(last / time.Millisecond) 284 resp.Header().Set("X-Nomad-LastContact", strconv.FormatUint(lastMsec, 10)) 285 } 286 287 // setMeta is used to set the query response meta data 288 func setMeta(resp http.ResponseWriter, m *structs.QueryMeta) { 289 setIndex(resp, m.Index) 290 setLastContact(resp, m.LastContact) 291 setKnownLeader(resp, m.KnownLeader) 292 } 293 294 // setHeaders is used to set canonical response header fields 295 func setHeaders(resp http.ResponseWriter, headers map[string]string) { 296 for field, value := range headers { 297 resp.Header().Set(http.CanonicalHeaderKey(field), value) 298 } 299 } 300 301 // parseWait is used to parse the ?wait and ?index query params 302 // Returns true on error 303 func parseWait(resp http.ResponseWriter, req *http.Request, b *structs.QueryOptions) bool { 304 query := req.URL.Query() 305 if wait := query.Get("wait"); wait != "" { 306 dur, err := time.ParseDuration(wait) 307 if err != nil { 308 resp.WriteHeader(400) 309 resp.Write([]byte("Invalid wait time")) 310 return true 311 } 312 b.MaxQueryTime = dur 313 } 314 if idx := query.Get("index"); idx != "" { 315 index, err := strconv.ParseUint(idx, 10, 64) 316 if err != nil { 317 resp.WriteHeader(400) 318 resp.Write([]byte("Invalid index")) 319 return true 320 } 321 b.MinQueryIndex = index 322 } 323 return false 324 } 325 326 // parseConsistency is used to parse the ?stale query params. 327 func parseConsistency(req *http.Request, b *structs.QueryOptions) { 328 query := req.URL.Query() 329 if _, ok := query["stale"]; ok { 330 b.AllowStale = true 331 } 332 } 333 334 // parsePrefix is used to parse the ?prefix query param 335 func parsePrefix(req *http.Request, b *structs.QueryOptions) { 336 query := req.URL.Query() 337 if prefix := query.Get("prefix"); prefix != "" { 338 b.Prefix = prefix 339 } 340 } 341 342 // parseRegion is used to parse the ?region query param 343 func (s *HTTPServer) parseRegion(req *http.Request, r *string) { 344 if other := req.URL.Query().Get("region"); other != "" { 345 *r = other 346 } else if *r == "" { 347 *r = s.agent.config.Region 348 } 349 } 350 351 // parse is a convenience method for endpoints that need to parse multiple flags 352 func (s *HTTPServer) parse(resp http.ResponseWriter, req *http.Request, r *string, b *structs.QueryOptions) bool { 353 s.parseRegion(req, r) 354 parseConsistency(req, b) 355 parsePrefix(req, b) 356 return parseWait(resp, req, b) 357 }