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  }