github.com/superfly/nomad@v0.10.5-fly/command/agent/agent_endpoint.go (about)

     1  package agent
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"net"
    10  	"net/http"
    11  	"sort"
    12  	"strconv"
    13  	"strings"
    14  
    15  	"github.com/docker/docker/pkg/ioutils"
    16  	log "github.com/hashicorp/go-hclog"
    17  	"github.com/hashicorp/nomad/acl"
    18  	cstructs "github.com/hashicorp/nomad/client/structs"
    19  	"github.com/hashicorp/nomad/command/agent/pprof"
    20  	"github.com/hashicorp/nomad/nomad/structs"
    21  	"github.com/hashicorp/serf/serf"
    22  	"github.com/mitchellh/copystructure"
    23  	"github.com/ugorji/go/codec"
    24  )
    25  
    26  type Member struct {
    27  	Name        string
    28  	Addr        net.IP
    29  	Port        uint16
    30  	Tags        map[string]string
    31  	Status      string
    32  	ProtocolMin uint8
    33  	ProtocolMax uint8
    34  	ProtocolCur uint8
    35  	DelegateMin uint8
    36  	DelegateMax uint8
    37  	DelegateCur uint8
    38  }
    39  
    40  func nomadMember(m serf.Member) Member {
    41  	return Member{
    42  		Name:        m.Name,
    43  		Addr:        m.Addr,
    44  		Port:        m.Port,
    45  		Tags:        m.Tags,
    46  		Status:      m.Status.String(),
    47  		ProtocolMin: m.ProtocolMin,
    48  		ProtocolMax: m.ProtocolMax,
    49  		ProtocolCur: m.ProtocolCur,
    50  		DelegateMin: m.DelegateMin,
    51  		DelegateMax: m.DelegateMax,
    52  		DelegateCur: m.DelegateCur,
    53  	}
    54  }
    55  
    56  func (s *HTTPServer) AgentSelfRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
    57  	if req.Method != "GET" {
    58  		return nil, CodedError(405, ErrInvalidMethod)
    59  	}
    60  
    61  	var secret string
    62  	s.parseToken(req, &secret)
    63  
    64  	var aclObj *acl.ACL
    65  	var err error
    66  
    67  	// Get the member as a server
    68  	var member serf.Member
    69  	if srv := s.agent.Server(); srv != nil {
    70  		member = srv.LocalMember()
    71  		aclObj, err = srv.ResolveToken(secret)
    72  	} else {
    73  		// Not a Server; use the Client for token resolution
    74  		aclObj, err = s.agent.Client().ResolveToken(secret)
    75  	}
    76  
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  
    81  	// Check agent read permissions
    82  	if aclObj != nil && !aclObj.AllowAgentRead() {
    83  		return nil, structs.ErrPermissionDenied
    84  	}
    85  
    86  	self := agentSelf{
    87  		Member: nomadMember(member),
    88  		Stats:  s.agent.Stats(),
    89  	}
    90  	if ac, err := copystructure.Copy(s.agent.config); err != nil {
    91  		return nil, CodedError(500, err.Error())
    92  	} else {
    93  		self.Config = ac.(*Config)
    94  	}
    95  
    96  	if self.Config != nil && self.Config.Vault != nil && self.Config.Vault.Token != "" {
    97  		self.Config.Vault.Token = "<redacted>"
    98  	}
    99  
   100  	if self.Config != nil && self.Config.ACL != nil && self.Config.ACL.ReplicationToken != "" {
   101  		self.Config.ACL.ReplicationToken = "<redacted>"
   102  	}
   103  
   104  	if self.Config != nil && self.Config.Consul != nil && self.Config.Consul.Token != "" {
   105  		self.Config.Consul.Token = "<redacted>"
   106  	}
   107  
   108  	if self.Config != nil && self.Config.Telemetry != nil && self.Config.Telemetry.CirconusAPIToken != "" {
   109  		self.Config.Telemetry.CirconusAPIToken = "<redacted>"
   110  	}
   111  
   112  	return self, nil
   113  }
   114  
   115  func (s *HTTPServer) AgentJoinRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   116  	if req.Method != "PUT" && req.Method != "POST" {
   117  		return nil, CodedError(405, ErrInvalidMethod)
   118  	}
   119  	srv := s.agent.Server()
   120  	if srv == nil {
   121  		return nil, CodedError(501, ErrInvalidMethod)
   122  	}
   123  
   124  	// Get the join addresses
   125  	query := req.URL.Query()
   126  	addrs := query["address"]
   127  	if len(addrs) == 0 {
   128  		return nil, CodedError(400, "missing address to join")
   129  	}
   130  
   131  	// Attempt the join
   132  	num, err := srv.Join(addrs)
   133  	var errStr string
   134  	if err != nil {
   135  		errStr = err.Error()
   136  	}
   137  	return joinResult{num, errStr}, nil
   138  }
   139  
   140  func (s *HTTPServer) AgentMembersRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   141  	if req.Method != "GET" {
   142  		return nil, CodedError(405, ErrInvalidMethod)
   143  	}
   144  
   145  	args := &structs.GenericRequest{}
   146  	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
   147  		return nil, nil
   148  	}
   149  
   150  	var out structs.ServerMembersResponse
   151  	if err := s.agent.RPC("Status.Members", args, &out); err != nil {
   152  		return nil, err
   153  	}
   154  
   155  	return out, nil
   156  }
   157  
   158  func (s *HTTPServer) AgentMonitor(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   159  	// Get the provided loglevel.
   160  	logLevel := req.URL.Query().Get("log_level")
   161  	if logLevel == "" {
   162  		logLevel = "INFO"
   163  	}
   164  
   165  	if log.LevelFromString(logLevel) == log.NoLevel {
   166  		return nil, CodedError(400, fmt.Sprintf("Unknown log level: %s", logLevel))
   167  	}
   168  
   169  	logJSON := false
   170  	logJSONStr := req.URL.Query().Get("log_json")
   171  	if logJSONStr != "" {
   172  		parsed, err := strconv.ParseBool(logJSONStr)
   173  		if err != nil {
   174  			return nil, CodedError(400, fmt.Sprintf("Unknown option for log json: %v", err))
   175  		}
   176  		logJSON = parsed
   177  	}
   178  
   179  	plainText := false
   180  	plainTextStr := req.URL.Query().Get("plain")
   181  	if plainTextStr != "" {
   182  		parsed, err := strconv.ParseBool(plainTextStr)
   183  		if err != nil {
   184  			return nil, CodedError(400, fmt.Sprintf("Unknown option for plain: %v", err))
   185  		}
   186  		plainText = parsed
   187  	}
   188  
   189  	nodeID := req.URL.Query().Get("node_id")
   190  	// Build the request and parse the ACL token
   191  	args := cstructs.MonitorRequest{
   192  		NodeID:    nodeID,
   193  		ServerID:  req.URL.Query().Get("server_id"),
   194  		LogLevel:  logLevel,
   195  		LogJSON:   logJSON,
   196  		PlainText: plainText,
   197  	}
   198  
   199  	// if node and server were requested return error
   200  	if args.NodeID != "" && args.ServerID != "" {
   201  		return nil, CodedError(400, "Cannot target node and server simultaneously")
   202  	}
   203  
   204  	// Force the Content-Type to avoid Go's http.ResponseWriter from
   205  	// detecting an incorrect or unsafe one.
   206  	if plainText {
   207  		resp.Header().Set("Content-Type", "text/plain")
   208  	} else {
   209  		resp.Header().Set("Content-Type", "application/json")
   210  	}
   211  
   212  	s.parse(resp, req, &args.QueryOptions.Region, &args.QueryOptions)
   213  
   214  	// Make the RPC
   215  	var handler structs.StreamingRpcHandler
   216  	var handlerErr error
   217  	if nodeID != "" {
   218  		// Determine the handler to use
   219  		useLocalClient, useClientRPC, useServerRPC := s.rpcHandlerForNode(nodeID)
   220  		if useLocalClient {
   221  			handler, handlerErr = s.agent.Client().StreamingRpcHandler("Agent.Monitor")
   222  		} else if useClientRPC {
   223  			handler, handlerErr = s.agent.Client().RemoteStreamingRpcHandler("Agent.Monitor")
   224  		} else if useServerRPC {
   225  			handler, handlerErr = s.agent.Server().StreamingRpcHandler("Agent.Monitor")
   226  		} else {
   227  			handlerErr = CodedError(400, "No local Node and node_id not provided")
   228  		}
   229  		// No node id monitor current server/client
   230  	} else if srv := s.agent.Server(); srv != nil {
   231  		handler, handlerErr = srv.StreamingRpcHandler("Agent.Monitor")
   232  	} else {
   233  		handler, handlerErr = s.agent.Client().StreamingRpcHandler("Agent.Monitor")
   234  	}
   235  
   236  	if handlerErr != nil {
   237  		return nil, CodedError(500, handlerErr.Error())
   238  	}
   239  	httpPipe, handlerPipe := net.Pipe()
   240  	decoder := codec.NewDecoder(httpPipe, structs.MsgpackHandle)
   241  	encoder := codec.NewEncoder(httpPipe, structs.MsgpackHandle)
   242  
   243  	ctx, cancel := context.WithCancel(req.Context())
   244  	go func() {
   245  		<-ctx.Done()
   246  		httpPipe.Close()
   247  	}()
   248  
   249  	// Create an output that gets flushed on every write
   250  	output := ioutils.NewWriteFlusher(resp)
   251  
   252  	// create an error channel to handle errors
   253  	errCh := make(chan HTTPCodedError, 2)
   254  
   255  	// stream response
   256  	go func() {
   257  		defer cancel()
   258  
   259  		// Send the request
   260  		if err := encoder.Encode(args); err != nil {
   261  			errCh <- CodedError(500, err.Error())
   262  			return
   263  		}
   264  
   265  		for {
   266  			select {
   267  			case <-ctx.Done():
   268  				errCh <- nil
   269  				return
   270  			default:
   271  			}
   272  
   273  			var res cstructs.StreamErrWrapper
   274  			if err := decoder.Decode(&res); err != nil {
   275  				errCh <- CodedError(500, err.Error())
   276  				return
   277  			}
   278  			decoder.Reset(httpPipe)
   279  
   280  			if err := res.Error; err != nil {
   281  				if err.Code != nil {
   282  					errCh <- CodedError(int(*err.Code), err.Error())
   283  					return
   284  				}
   285  			}
   286  
   287  			if _, err := io.Copy(output, bytes.NewReader(res.Payload)); err != nil {
   288  				errCh <- CodedError(500, err.Error())
   289  				return
   290  			}
   291  		}
   292  	}()
   293  
   294  	handler(handlerPipe)
   295  	cancel()
   296  	codedErr := <-errCh
   297  
   298  	if codedErr != nil &&
   299  		(codedErr == io.EOF ||
   300  			strings.Contains(codedErr.Error(), "closed") ||
   301  			strings.Contains(codedErr.Error(), "EOF")) {
   302  		codedErr = nil
   303  	}
   304  	return nil, codedErr
   305  }
   306  
   307  func (s *HTTPServer) AgentForceLeaveRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   308  	if req.Method != "PUT" && req.Method != "POST" {
   309  		return nil, CodedError(405, ErrInvalidMethod)
   310  	}
   311  	srv := s.agent.Server()
   312  	if srv == nil {
   313  		return nil, CodedError(501, ErrInvalidMethod)
   314  	}
   315  
   316  	var secret string
   317  	s.parseToken(req, &secret)
   318  
   319  	// Check agent write permissions
   320  	if aclObj, err := s.agent.Server().ResolveToken(secret); err != nil {
   321  		return nil, err
   322  	} else if aclObj != nil && !aclObj.AllowAgentWrite() {
   323  		return nil, structs.ErrPermissionDenied
   324  	}
   325  
   326  	// Get the node to eject
   327  	node := req.URL.Query().Get("node")
   328  	if node == "" {
   329  		return nil, CodedError(400, "missing node to force leave")
   330  	}
   331  
   332  	// Attempt remove
   333  	err := srv.RemoveFailedNode(node)
   334  	return nil, err
   335  }
   336  
   337  func (s *HTTPServer) AgentPprofRequest(resp http.ResponseWriter, req *http.Request) ([]byte, error) {
   338  	path := strings.TrimPrefix(req.URL.Path, "/v1/agent/pprof/")
   339  	switch path {
   340  	case "":
   341  		// no root index route
   342  		return nil, CodedError(404, ErrInvalidMethod)
   343  	case "cmdline":
   344  		return s.agentPprof(pprof.CmdReq, resp, req)
   345  	case "profile":
   346  		return s.agentPprof(pprof.CPUReq, resp, req)
   347  	case "trace":
   348  		return s.agentPprof(pprof.TraceReq, resp, req)
   349  	default:
   350  		// Add profile to request
   351  		values := req.URL.Query()
   352  		values.Add("profile", path)
   353  		req.URL.RawQuery = values.Encode()
   354  
   355  		// generic pprof profile request
   356  		return s.agentPprof(pprof.LookupReq, resp, req)
   357  	}
   358  }
   359  
   360  func (s *HTTPServer) agentPprof(reqType pprof.ReqType, resp http.ResponseWriter, req *http.Request) ([]byte, error) {
   361  
   362  	// Parse query param int values
   363  	// Errors are dropped here and default to their zero values.
   364  	// This is to mimick the functionality that net/pprof implements.
   365  	seconds, _ := strconv.Atoi(req.URL.Query().Get("seconds"))
   366  	debug, _ := strconv.Atoi(req.URL.Query().Get("debug"))
   367  	gc, _ := strconv.Atoi(req.URL.Query().Get("gc"))
   368  
   369  	// default to 1 second
   370  	if seconds == 0 {
   371  		seconds = 1
   372  	}
   373  
   374  	// Create the request
   375  	args := &structs.AgentPprofRequest{
   376  		NodeID:   req.URL.Query().Get("node_id"),
   377  		Profile:  req.URL.Query().Get("profile"),
   378  		ServerID: req.URL.Query().Get("server_id"),
   379  		Debug:    debug,
   380  		GC:       gc,
   381  		ReqType:  reqType,
   382  		Seconds:  seconds,
   383  	}
   384  
   385  	// if node and server were requested return error
   386  	if args.NodeID != "" && args.ServerID != "" {
   387  		return nil, CodedError(400, "Cannot target node and server simultaneously")
   388  	}
   389  
   390  	s.parse(resp, req, &args.QueryOptions.Region, &args.QueryOptions)
   391  
   392  	var reply structs.AgentPprofResponse
   393  	var rpcErr error
   394  	if args.NodeID != "" {
   395  		// Make the RPC
   396  		localClient, remoteClient, localServer := s.rpcHandlerForNode(args.NodeID)
   397  		if localClient {
   398  			rpcErr = s.agent.Client().ClientRPC("Agent.Profile", &args, &reply)
   399  		} else if remoteClient {
   400  			rpcErr = s.agent.Client().RPC("Agent.Profile", &args, &reply)
   401  		} else if localServer {
   402  			rpcErr = s.agent.Server().RPC("Agent.Profile", &args, &reply)
   403  		}
   404  		// No node id, profile current server/client
   405  	} else if srv := s.agent.Server(); srv != nil {
   406  		rpcErr = srv.RPC("Agent.Profile", &args, &reply)
   407  	} else {
   408  		rpcErr = s.agent.Client().RPC("Agent.Profile", &args, &reply)
   409  	}
   410  
   411  	if rpcErr != nil {
   412  		return nil, rpcErr
   413  	}
   414  
   415  	// Set headers from profile request
   416  	for k, v := range reply.HTTPHeaders {
   417  		resp.Header().Set(k, v)
   418  	}
   419  
   420  	return reply.Payload, nil
   421  }
   422  
   423  // AgentServersRequest is used to query the list of servers used by the Nomad
   424  // Client for RPCs.  This endpoint can also be used to update the list of
   425  // servers for a given agent.
   426  func (s *HTTPServer) AgentServersRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   427  	switch req.Method {
   428  	case "PUT", "POST":
   429  		return s.updateServers(resp, req)
   430  	case "GET":
   431  		return s.listServers(resp, req)
   432  	default:
   433  		return nil, CodedError(405, ErrInvalidMethod)
   434  	}
   435  }
   436  
   437  func (s *HTTPServer) listServers(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   438  	client := s.agent.Client()
   439  	if client == nil {
   440  		return nil, CodedError(501, ErrInvalidMethod)
   441  	}
   442  
   443  	var secret string
   444  	s.parseToken(req, &secret)
   445  
   446  	// Check agent read permissions
   447  	if aclObj, err := s.agent.Client().ResolveToken(secret); err != nil {
   448  		return nil, err
   449  	} else if aclObj != nil && !aclObj.AllowAgentRead() {
   450  		return nil, structs.ErrPermissionDenied
   451  	}
   452  
   453  	peers := s.agent.client.GetServers()
   454  	sort.Strings(peers)
   455  	return peers, nil
   456  }
   457  
   458  func (s *HTTPServer) updateServers(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   459  	client := s.agent.Client()
   460  	if client == nil {
   461  		return nil, CodedError(501, ErrInvalidMethod)
   462  	}
   463  
   464  	// Get the servers from the request
   465  	servers := req.URL.Query()["address"]
   466  	if len(servers) == 0 {
   467  		return nil, CodedError(400, "missing server address")
   468  	}
   469  
   470  	var secret string
   471  	s.parseToken(req, &secret)
   472  
   473  	// Check agent write permissions
   474  	if aclObj, err := s.agent.Client().ResolveToken(secret); err != nil {
   475  		return nil, err
   476  	} else if aclObj != nil && !aclObj.AllowAgentWrite() {
   477  		return nil, structs.ErrPermissionDenied
   478  	}
   479  
   480  	// Set the servers list into the client
   481  	s.agent.logger.Trace("adding servers to the client's primary server list", "servers", servers, "path", "/v1/agent/servers", "method", "PUT")
   482  	if _, err := client.SetServers(servers); err != nil {
   483  		s.agent.logger.Error("failed adding servers to client's server list", "servers", servers, "error", err, "path", "/v1/agent/servers", "method", "PUT")
   484  		//TODO is this the right error to return?
   485  		return nil, CodedError(400, err.Error())
   486  	}
   487  	return nil, nil
   488  }
   489  
   490  // KeyringOperationRequest allows an operator to install/delete/use keys
   491  func (s *HTTPServer) KeyringOperationRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   492  	srv := s.agent.Server()
   493  	if srv == nil {
   494  		return nil, CodedError(501, ErrInvalidMethod)
   495  	}
   496  
   497  	var secret string
   498  	s.parseToken(req, &secret)
   499  
   500  	// Check agent write permissions
   501  	if aclObj, err := srv.ResolveToken(secret); err != nil {
   502  		return nil, err
   503  	} else if aclObj != nil && !aclObj.AllowAgentWrite() {
   504  		return nil, structs.ErrPermissionDenied
   505  	}
   506  
   507  	kmgr := srv.KeyManager()
   508  	var sresp *serf.KeyResponse
   509  	var err error
   510  
   511  	// Get the key from the req body
   512  	var args structs.KeyringRequest
   513  
   514  	//Get the op
   515  	op := strings.TrimPrefix(req.URL.Path, "/v1/agent/keyring/")
   516  
   517  	switch op {
   518  	case "list":
   519  		sresp, err = kmgr.ListKeys()
   520  	case "install":
   521  		if err := decodeBody(req, &args); err != nil {
   522  			return nil, CodedError(500, err.Error())
   523  		}
   524  		sresp, err = kmgr.InstallKey(args.Key)
   525  	case "use":
   526  		if err := decodeBody(req, &args); err != nil {
   527  			return nil, CodedError(500, err.Error())
   528  		}
   529  		sresp, err = kmgr.UseKey(args.Key)
   530  	case "remove":
   531  		if err := decodeBody(req, &args); err != nil {
   532  			return nil, CodedError(500, err.Error())
   533  		}
   534  		sresp, err = kmgr.RemoveKey(args.Key)
   535  	default:
   536  		return nil, CodedError(404, "resource not found")
   537  	}
   538  
   539  	if err != nil {
   540  		return nil, err
   541  	}
   542  	kresp := structs.KeyringResponse{
   543  		Messages: sresp.Messages,
   544  		Keys:     sresp.Keys,
   545  		NumNodes: sresp.NumNodes,
   546  	}
   547  	return kresp, nil
   548  }
   549  
   550  type agentSelf struct {
   551  	Config *Config                      `json:"config"`
   552  	Member Member                       `json:"member,omitempty"`
   553  	Stats  map[string]map[string]string `json:"stats"`
   554  }
   555  
   556  type joinResult struct {
   557  	NumJoined int    `json:"num_joined"`
   558  	Error     string `json:"error"`
   559  }
   560  
   561  func (s *HTTPServer) HealthRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   562  	if req.Method != "GET" {
   563  		return nil, CodedError(405, ErrInvalidMethod)
   564  	}
   565  
   566  	var args structs.GenericRequest
   567  	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
   568  		return nil, nil
   569  	}
   570  
   571  	health := healthResponse{}
   572  	getClient := true
   573  	getServer := true
   574  
   575  	// See if we're checking a specific agent type and default to failing
   576  	if healthType, ok := req.URL.Query()["type"]; ok {
   577  		getClient = false
   578  		getServer = false
   579  		for _, ht := range healthType {
   580  			switch ht {
   581  			case "client":
   582  				getClient = true
   583  				health.Client = &healthResponseAgent{
   584  					Ok:      false,
   585  					Message: "client not enabled",
   586  				}
   587  			case "server":
   588  				getServer = true
   589  				health.Server = &healthResponseAgent{
   590  					Ok:      false,
   591  					Message: "server not enabled",
   592  				}
   593  			}
   594  		}
   595  	}
   596  
   597  	// If we should check the client and it exists assume it's healthy
   598  	if client := s.agent.Client(); getClient && client != nil {
   599  		if len(client.GetServers()) == 0 {
   600  			health.Client = &healthResponseAgent{
   601  				Ok:      false,
   602  				Message: "no known servers",
   603  			}
   604  		} else {
   605  			health.Client = &healthResponseAgent{
   606  				Ok:      true,
   607  				Message: "ok",
   608  			}
   609  		}
   610  	}
   611  
   612  	// If we should check the server and it exists, see if there's a leader
   613  	if server := s.agent.Server(); getServer && server != nil {
   614  		health.Server = &healthResponseAgent{
   615  			Ok:      true,
   616  			Message: "ok",
   617  		}
   618  
   619  		leader := ""
   620  		if err := s.agent.RPC("Status.Leader", &args, &leader); err != nil {
   621  			health.Server.Ok = false
   622  			health.Server.Message = err.Error()
   623  		} else if leader == "" {
   624  			health.Server.Ok = false
   625  			health.Server.Message = "no leader"
   626  		}
   627  	}
   628  
   629  	if health.ok() {
   630  		return &health, nil
   631  	}
   632  
   633  	jsonResp, err := json.Marshal(&health)
   634  	if err != nil {
   635  		return nil, err
   636  	}
   637  	return nil, CodedError(500, string(jsonResp))
   638  }
   639  
   640  type healthResponse struct {
   641  	Client *healthResponseAgent `json:"client,omitempty"`
   642  	Server *healthResponseAgent `json:"server,omitempty"`
   643  }
   644  
   645  // ok returns true as long as neither Client nor Server have Ok=false.
   646  func (h healthResponse) ok() bool {
   647  	if h.Client != nil && !h.Client.Ok {
   648  		return false
   649  	}
   650  	if h.Server != nil && !h.Server.Ok {
   651  		return false
   652  	}
   653  	return true
   654  }
   655  
   656  type healthResponseAgent struct {
   657  	Ok      bool   `json:"ok"`
   658  	Message string `json:"message,omitempty"`
   659  }