github.com/diptanu/nomad@v0.5.7-0.20170516172507-d72e86cbe3d9/command/agent/http.go (about)

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