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