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  }