github.com/kardianos/nomad@v0.1.3-0.20151022182107-b13df73ee850/command/agent/http.go (about) 1 package agent 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "log" 8 "net" 9 "net/http" 10 "net/http/pprof" 11 "strconv" 12 "time" 13 14 "github.com/hashicorp/nomad/nomad/structs" 15 ) 16 17 const ( 18 // ErrInvalidMethod is used if the HTTP method is not supported 19 ErrInvalidMethod = "Invalid method" 20 21 // scadaHTTPAddr is the address associated with the 22 // HTTPServer. When populating an ACL token for a request, 23 // this is checked to switch between the ACLToken and 24 // AtlasACLToken 25 scadaHTTPAddr = "SCADA" 26 ) 27 28 // HTTPServer is used to wrap an Agent and expose it over an HTTP interface 29 type HTTPServer struct { 30 agent *Agent 31 mux *http.ServeMux 32 listener net.Listener 33 logger *log.Logger 34 addr string 35 } 36 37 // NewHTTPServer starts new HTTP server over the agent 38 func NewHTTPServer(agent *Agent, config *Config, logOutput io.Writer) (*HTTPServer, error) { 39 // Start the listener 40 ln, err := config.Listener("tcp", config.Addresses.HTTP, config.Ports.HTTP) 41 if err != nil { 42 return nil, fmt.Errorf("failed to start HTTP listener: %v", err) 43 } 44 45 // Create the mux 46 mux := http.NewServeMux() 47 48 // Create the server 49 srv := &HTTPServer{ 50 agent: agent, 51 mux: mux, 52 listener: ln, 53 logger: log.New(logOutput, "", log.LstdFlags), 54 addr: ln.Addr().String(), 55 } 56 srv.registerHandlers(config.EnableDebug) 57 58 // Start the server 59 go http.Serve(ln, mux) 60 return srv, nil 61 } 62 63 // newScadaHttp creates a new HTTP server wrapping the SCADA 64 // listener such that HTTP calls can be sent from the brokers. 65 func newScadaHttp(agent *Agent, list net.Listener) *HTTPServer { 66 // Create the mux 67 mux := http.NewServeMux() 68 69 // Create the server 70 srv := &HTTPServer{ 71 agent: agent, 72 mux: mux, 73 listener: list, 74 logger: agent.logger, 75 addr: scadaHTTPAddr, 76 } 77 srv.registerHandlers(false) // Never allow debug for SCADA 78 79 // Start the server 80 go http.Serve(list, mux) 81 return srv 82 } 83 84 // Shutdown is used to shutdown the HTTP server 85 func (s *HTTPServer) Shutdown() { 86 if s != nil { 87 s.logger.Printf("[DEBUG] http: Shutting down http server") 88 s.listener.Close() 89 } 90 } 91 92 // registerHandlers is used to attach our handlers to the mux 93 func (s *HTTPServer) registerHandlers(enableDebug bool) { 94 s.mux.HandleFunc("/v1/jobs", s.wrap(s.JobsRequest)) 95 s.mux.HandleFunc("/v1/job/", s.wrap(s.JobSpecificRequest)) 96 97 s.mux.HandleFunc("/v1/nodes", s.wrap(s.NodesRequest)) 98 s.mux.HandleFunc("/v1/node/", s.wrap(s.NodeSpecificRequest)) 99 100 s.mux.HandleFunc("/v1/allocations", s.wrap(s.AllocsRequest)) 101 s.mux.HandleFunc("/v1/allocation/", s.wrap(s.AllocSpecificRequest)) 102 103 s.mux.HandleFunc("/v1/evaluations", s.wrap(s.EvalsRequest)) 104 s.mux.HandleFunc("/v1/evaluation/", s.wrap(s.EvalSpecificRequest)) 105 106 s.mux.HandleFunc("/v1/agent/self", s.wrap(s.AgentSelfRequest)) 107 s.mux.HandleFunc("/v1/agent/join", s.wrap(s.AgentJoinRequest)) 108 s.mux.HandleFunc("/v1/agent/members", s.wrap(s.AgentMembersRequest)) 109 s.mux.HandleFunc("/v1/agent/force-leave", s.wrap(s.AgentForceLeaveRequest)) 110 s.mux.HandleFunc("/v1/agent/servers", s.wrap(s.AgentServersRequest)) 111 112 s.mux.HandleFunc("/v1/status/leader", s.wrap(s.StatusLeaderRequest)) 113 s.mux.HandleFunc("/v1/status/peers", s.wrap(s.StatusPeersRequest)) 114 115 if enableDebug { 116 s.mux.HandleFunc("/debug/pprof/", pprof.Index) 117 s.mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) 118 s.mux.HandleFunc("/debug/pprof/profile", pprof.Profile) 119 s.mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) 120 } 121 } 122 123 // HTTPCodedError is used to provide the HTTP error code 124 type HTTPCodedError interface { 125 error 126 Code() int 127 } 128 129 func CodedError(c int, s string) HTTPCodedError { 130 return &codedError{s, c} 131 } 132 133 type codedError struct { 134 s string 135 code int 136 } 137 138 func (e *codedError) Error() string { 139 return e.s 140 } 141 142 func (e *codedError) Code() int { 143 return e.code 144 } 145 146 // wrap is used to wrap functions to make them more convenient 147 func (s *HTTPServer) wrap(handler func(resp http.ResponseWriter, req *http.Request) (interface{}, error)) func(resp http.ResponseWriter, req *http.Request) { 148 f := func(resp http.ResponseWriter, req *http.Request) { 149 // Invoke the handler 150 reqURL := req.URL.String() 151 start := time.Now() 152 defer func() { 153 s.logger.Printf("[DEBUG] http: Request %v (%v)", reqURL, time.Now().Sub(start)) 154 }() 155 obj, err := handler(resp, req) 156 157 // Check for an error 158 HAS_ERR: 159 if err != nil { 160 s.logger.Printf("[ERR] http: Request %v, error: %v", reqURL, err) 161 code := 500 162 if http, ok := err.(HTTPCodedError); ok { 163 code = http.Code() 164 } 165 resp.WriteHeader(code) 166 resp.Write([]byte(err.Error())) 167 return 168 } 169 170 prettyPrint := false 171 if _, ok := req.URL.Query()["pretty"]; ok { 172 prettyPrint = true 173 } 174 175 // Write out the JSON object 176 if obj != nil { 177 var buf []byte 178 if prettyPrint { 179 buf, err = json.MarshalIndent(obj, "", " ") 180 } else { 181 buf, err = json.Marshal(obj) 182 } 183 if err != nil { 184 goto HAS_ERR 185 } 186 resp.Header().Set("Content-Type", "application/json") 187 resp.Write(buf) 188 } 189 } 190 return f 191 } 192 193 // decodeBody is used to decode a JSON request body 194 func decodeBody(req *http.Request, out interface{}) error { 195 dec := json.NewDecoder(req.Body) 196 return dec.Decode(&out) 197 } 198 199 // setIndex is used to set the index response header 200 func setIndex(resp http.ResponseWriter, index uint64) { 201 resp.Header().Set("X-Nomad-Index", strconv.FormatUint(index, 10)) 202 } 203 204 // setKnownLeader is used to set the known leader header 205 func setKnownLeader(resp http.ResponseWriter, known bool) { 206 s := "true" 207 if !known { 208 s = "false" 209 } 210 resp.Header().Set("X-Nomad-KnownLeader", s) 211 } 212 213 // setLastContact is used to set the last contact header 214 func setLastContact(resp http.ResponseWriter, last time.Duration) { 215 lastMsec := uint64(last / time.Millisecond) 216 resp.Header().Set("X-Nomad-LastContact", strconv.FormatUint(lastMsec, 10)) 217 } 218 219 // setMeta is used to set the query response meta data 220 func setMeta(resp http.ResponseWriter, m *structs.QueryMeta) { 221 setIndex(resp, m.Index) 222 setLastContact(resp, m.LastContact) 223 setKnownLeader(resp, m.KnownLeader) 224 } 225 226 // parseWait is used to parse the ?wait and ?index query params 227 // Returns true on error 228 func parseWait(resp http.ResponseWriter, req *http.Request, b *structs.QueryOptions) bool { 229 query := req.URL.Query() 230 if wait := query.Get("wait"); wait != "" { 231 dur, err := time.ParseDuration(wait) 232 if err != nil { 233 resp.WriteHeader(400) 234 resp.Write([]byte("Invalid wait time")) 235 return true 236 } 237 b.MaxQueryTime = dur 238 } 239 if idx := query.Get("index"); idx != "" { 240 index, err := strconv.ParseUint(idx, 10, 64) 241 if err != nil { 242 resp.WriteHeader(400) 243 resp.Write([]byte("Invalid index")) 244 return true 245 } 246 b.MinQueryIndex = index 247 } 248 return false 249 } 250 251 // parseConsistency is used to parse the ?stale query params. 252 func parseConsistency(req *http.Request, b *structs.QueryOptions) { 253 query := req.URL.Query() 254 if _, ok := query["stale"]; ok { 255 b.AllowStale = true 256 } 257 } 258 259 // parseRegion is used to parse the ?region query param 260 func (s *HTTPServer) parseRegion(req *http.Request, r *string) { 261 if other := req.URL.Query().Get("region"); other != "" { 262 *r = other 263 } else if *r == "" { 264 *r = s.agent.config.Region 265 } 266 } 267 268 // parse is a convenience method for endpoints that need to parse multiple flags 269 func (s *HTTPServer) parse(resp http.ResponseWriter, req *http.Request, r *string, b *structs.QueryOptions) bool { 270 s.parseRegion(req, r) 271 parseConsistency(req, b) 272 return parseWait(resp, req, b) 273 }