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