github.com/mongey/nomad@v0.5.2/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 := net.ResolveTCPAddr("tcp", config.normalizedAddrs.HTTP)
    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  	s.mux.HandleFunc("/v1/client/gc", s.wrap(s.ClientGCRequest))
   160  
   161  	s.mux.HandleFunc("/v1/agent/self", s.wrap(s.AgentSelfRequest))
   162  	s.mux.HandleFunc("/v1/agent/join", s.wrap(s.AgentJoinRequest))
   163  	s.mux.HandleFunc("/v1/agent/members", s.wrap(s.AgentMembersRequest))
   164  	s.mux.HandleFunc("/v1/agent/force-leave", s.wrap(s.AgentForceLeaveRequest))
   165  	s.mux.HandleFunc("/v1/agent/servers", s.wrap(s.AgentServersRequest))
   166  	s.mux.HandleFunc("/v1/agent/keyring/", s.wrap(s.KeyringOperationRequest))
   167  
   168  	s.mux.HandleFunc("/v1/regions", s.wrap(s.RegionListRequest))
   169  
   170  	s.mux.HandleFunc("/v1/status/leader", s.wrap(s.StatusLeaderRequest))
   171  	s.mux.HandleFunc("/v1/status/peers", s.wrap(s.StatusPeersRequest))
   172  
   173  	s.mux.HandleFunc("/v1/system/gc", s.wrap(s.GarbageCollectRequest))
   174  	s.mux.HandleFunc("/v1/system/reconcile/summaries", s.wrap(s.ReconcileJobSummaries))
   175  
   176  	if enableDebug {
   177  		s.mux.HandleFunc("/debug/pprof/", pprof.Index)
   178  		s.mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
   179  		s.mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
   180  		s.mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
   181  	}
   182  }
   183  
   184  // HTTPCodedError is used to provide the HTTP error code
   185  type HTTPCodedError interface {
   186  	error
   187  	Code() int
   188  }
   189  
   190  func CodedError(c int, s string) HTTPCodedError {
   191  	return &codedError{s, c}
   192  }
   193  
   194  type codedError struct {
   195  	s    string
   196  	code int
   197  }
   198  
   199  func (e *codedError) Error() string {
   200  	return e.s
   201  }
   202  
   203  func (e *codedError) Code() int {
   204  	return e.code
   205  }
   206  
   207  // wrap is used to wrap functions to make them more convenient
   208  func (s *HTTPServer) wrap(handler func(resp http.ResponseWriter, req *http.Request) (interface{}, error)) func(resp http.ResponseWriter, req *http.Request) {
   209  	f := func(resp http.ResponseWriter, req *http.Request) {
   210  		setHeaders(resp, s.agent.config.HTTPAPIResponseHeaders)
   211  		// Invoke the handler
   212  		reqURL := req.URL.String()
   213  		start := time.Now()
   214  		defer func() {
   215  			s.logger.Printf("[DEBUG] http: Request %v (%v)", reqURL, time.Now().Sub(start))
   216  		}()
   217  		obj, err := handler(resp, req)
   218  
   219  		// Check for an error
   220  	HAS_ERR:
   221  		if err != nil {
   222  			s.logger.Printf("[ERR] http: Request %v, error: %v", reqURL, err)
   223  			code := 500
   224  			if http, ok := err.(HTTPCodedError); ok {
   225  				code = http.Code()
   226  			}
   227  			resp.WriteHeader(code)
   228  			resp.Write([]byte(err.Error()))
   229  			return
   230  		}
   231  
   232  		prettyPrint := false
   233  		if v, ok := req.URL.Query()["pretty"]; ok {
   234  			if len(v) > 0 && (len(v[0]) == 0 || v[0] != "0") {
   235  				prettyPrint = true
   236  			}
   237  		}
   238  
   239  		// Write out the JSON object
   240  		if obj != nil {
   241  			var buf bytes.Buffer
   242  			if prettyPrint {
   243  				enc := codec.NewEncoder(&buf, jsonHandlePretty)
   244  				err = enc.Encode(obj)
   245  				if err == nil {
   246  					buf.Write([]byte("\n"))
   247  				}
   248  			} else {
   249  				enc := codec.NewEncoder(&buf, jsonHandle)
   250  				err = enc.Encode(obj)
   251  			}
   252  			if err != nil {
   253  				goto HAS_ERR
   254  			}
   255  			resp.Header().Set("Content-Type", "application/json")
   256  			resp.Write(buf.Bytes())
   257  		}
   258  	}
   259  	return f
   260  }
   261  
   262  // decodeBody is used to decode a JSON request body
   263  func decodeBody(req *http.Request, out interface{}) error {
   264  	dec := json.NewDecoder(req.Body)
   265  	return dec.Decode(&out)
   266  }
   267  
   268  // setIndex is used to set the index response header
   269  func setIndex(resp http.ResponseWriter, index uint64) {
   270  	resp.Header().Set("X-Nomad-Index", strconv.FormatUint(index, 10))
   271  }
   272  
   273  // setKnownLeader is used to set the known leader header
   274  func setKnownLeader(resp http.ResponseWriter, known bool) {
   275  	s := "true"
   276  	if !known {
   277  		s = "false"
   278  	}
   279  	resp.Header().Set("X-Nomad-KnownLeader", s)
   280  }
   281  
   282  // setLastContact is used to set the last contact header
   283  func setLastContact(resp http.ResponseWriter, last time.Duration) {
   284  	lastMsec := uint64(last / time.Millisecond)
   285  	resp.Header().Set("X-Nomad-LastContact", strconv.FormatUint(lastMsec, 10))
   286  }
   287  
   288  // setMeta is used to set the query response meta data
   289  func setMeta(resp http.ResponseWriter, m *structs.QueryMeta) {
   290  	setIndex(resp, m.Index)
   291  	setLastContact(resp, m.LastContact)
   292  	setKnownLeader(resp, m.KnownLeader)
   293  }
   294  
   295  // setHeaders is used to set canonical response header fields
   296  func setHeaders(resp http.ResponseWriter, headers map[string]string) {
   297  	for field, value := range headers {
   298  		resp.Header().Set(http.CanonicalHeaderKey(field), value)
   299  	}
   300  }
   301  
   302  // parseWait is used to parse the ?wait and ?index query params
   303  // Returns true on error
   304  func parseWait(resp http.ResponseWriter, req *http.Request, b *structs.QueryOptions) bool {
   305  	query := req.URL.Query()
   306  	if wait := query.Get("wait"); wait != "" {
   307  		dur, err := time.ParseDuration(wait)
   308  		if err != nil {
   309  			resp.WriteHeader(400)
   310  			resp.Write([]byte("Invalid wait time"))
   311  			return true
   312  		}
   313  		b.MaxQueryTime = dur
   314  	}
   315  	if idx := query.Get("index"); idx != "" {
   316  		index, err := strconv.ParseUint(idx, 10, 64)
   317  		if err != nil {
   318  			resp.WriteHeader(400)
   319  			resp.Write([]byte("Invalid index"))
   320  			return true
   321  		}
   322  		b.MinQueryIndex = index
   323  	}
   324  	return false
   325  }
   326  
   327  // parseConsistency is used to parse the ?stale query params.
   328  func parseConsistency(req *http.Request, b *structs.QueryOptions) {
   329  	query := req.URL.Query()
   330  	if _, ok := query["stale"]; ok {
   331  		b.AllowStale = true
   332  	}
   333  }
   334  
   335  // parsePrefix is used to parse the ?prefix query param
   336  func parsePrefix(req *http.Request, b *structs.QueryOptions) {
   337  	query := req.URL.Query()
   338  	if prefix := query.Get("prefix"); prefix != "" {
   339  		b.Prefix = prefix
   340  	}
   341  }
   342  
   343  // parseRegion is used to parse the ?region query param
   344  func (s *HTTPServer) parseRegion(req *http.Request, r *string) {
   345  	if other := req.URL.Query().Get("region"); other != "" {
   346  		*r = other
   347  	} else if *r == "" {
   348  		*r = s.agent.config.Region
   349  	}
   350  }
   351  
   352  // parse is a convenience method for endpoints that need to parse multiple flags
   353  func (s *HTTPServer) parse(resp http.ResponseWriter, req *http.Request, r *string, b *structs.QueryOptions) bool {
   354  	s.parseRegion(req, r)
   355  	parseConsistency(req, b)
   356  	parsePrefix(req, b)
   357  	return parseWait(resp, req, b)
   358  }