github.com/bigcommerce/nomad@v0.9.3-bc/command/agent/operator_endpoint.go (about)

     1  package agent
     2  
     3  import (
     4  	"net/http"
     5  	"strings"
     6  
     7  	"fmt"
     8  	"strconv"
     9  	"time"
    10  
    11  	"github.com/hashicorp/consul/agent/consul/autopilot"
    12  	"github.com/hashicorp/nomad/api"
    13  	"github.com/hashicorp/nomad/nomad/structs"
    14  	"github.com/hashicorp/raft"
    15  )
    16  
    17  func (s *HTTPServer) OperatorRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
    18  	path := strings.TrimPrefix(req.URL.Path, "/v1/operator/raft/")
    19  	switch {
    20  	case strings.HasPrefix(path, "configuration"):
    21  		return s.OperatorRaftConfiguration(resp, req)
    22  	case strings.HasPrefix(path, "peer"):
    23  		return s.OperatorRaftPeer(resp, req)
    24  	default:
    25  		return nil, CodedError(404, ErrInvalidMethod)
    26  	}
    27  }
    28  
    29  // OperatorRaftConfiguration is used to inspect the current Raft configuration.
    30  // This supports the stale query mode in case the cluster doesn't have a leader.
    31  func (s *HTTPServer) OperatorRaftConfiguration(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
    32  	if req.Method != "GET" {
    33  		resp.WriteHeader(http.StatusMethodNotAllowed)
    34  		return nil, nil
    35  	}
    36  
    37  	var args structs.GenericRequest
    38  	if done := s.parse(resp, req, &args.Region, &args.QueryOptions); done {
    39  		return nil, nil
    40  	}
    41  
    42  	var reply structs.RaftConfigurationResponse
    43  	if err := s.agent.RPC("Operator.RaftGetConfiguration", &args, &reply); err != nil {
    44  		return nil, err
    45  	}
    46  
    47  	return reply, nil
    48  }
    49  
    50  // OperatorRaftPeer supports actions on Raft peers. Currently we only support
    51  // removing peers by address.
    52  func (s *HTTPServer) OperatorRaftPeer(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
    53  	if req.Method != "DELETE" {
    54  		return nil, CodedError(404, ErrInvalidMethod)
    55  	}
    56  
    57  	params := req.URL.Query()
    58  	_, hasID := params["id"]
    59  	_, hasAddress := params["address"]
    60  
    61  	if !hasID && !hasAddress {
    62  		return nil, CodedError(http.StatusBadRequest, "Must specify either ?id with the server's ID or ?address with IP:port of peer to remove")
    63  	}
    64  	if hasID && hasAddress {
    65  		return nil, CodedError(http.StatusBadRequest, "Must specify only one of ?id or ?address")
    66  	}
    67  
    68  	if hasID {
    69  		var args structs.RaftPeerByIDRequest
    70  		s.parseWriteRequest(req, &args.WriteRequest)
    71  
    72  		var reply struct{}
    73  		args.ID = raft.ServerID(params.Get("id"))
    74  		if err := s.agent.RPC("Operator.RaftRemovePeerByID", &args, &reply); err != nil {
    75  			return nil, err
    76  		}
    77  	} else {
    78  		var args structs.RaftPeerByAddressRequest
    79  		s.parseWriteRequest(req, &args.WriteRequest)
    80  
    81  		var reply struct{}
    82  		args.Address = raft.ServerAddress(params.Get("address"))
    83  		if err := s.agent.RPC("Operator.RaftRemovePeerByAddress", &args, &reply); err != nil {
    84  			return nil, err
    85  		}
    86  	}
    87  
    88  	return nil, nil
    89  }
    90  
    91  // OperatorAutopilotConfiguration is used to inspect the current Autopilot configuration.
    92  // This supports the stale query mode in case the cluster doesn't have a leader.
    93  func (s *HTTPServer) OperatorAutopilotConfiguration(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
    94  	// Switch on the method
    95  	switch req.Method {
    96  	case "GET":
    97  		var args structs.GenericRequest
    98  		if done := s.parse(resp, req, &args.Region, &args.QueryOptions); done {
    99  			return nil, nil
   100  		}
   101  
   102  		var reply structs.AutopilotConfig
   103  		if err := s.agent.RPC("Operator.AutopilotGetConfiguration", &args, &reply); err != nil {
   104  			return nil, err
   105  		}
   106  
   107  		out := api.AutopilotConfiguration{
   108  			CleanupDeadServers:      reply.CleanupDeadServers,
   109  			LastContactThreshold:    reply.LastContactThreshold,
   110  			MaxTrailingLogs:         reply.MaxTrailingLogs,
   111  			ServerStabilizationTime: reply.ServerStabilizationTime,
   112  			EnableRedundancyZones:   reply.EnableRedundancyZones,
   113  			DisableUpgradeMigration: reply.DisableUpgradeMigration,
   114  			EnableCustomUpgrades:    reply.EnableCustomUpgrades,
   115  			CreateIndex:             reply.CreateIndex,
   116  			ModifyIndex:             reply.ModifyIndex,
   117  		}
   118  
   119  		return out, nil
   120  
   121  	case "PUT":
   122  		var args structs.AutopilotSetConfigRequest
   123  		s.parseWriteRequest(req, &args.WriteRequest)
   124  
   125  		var conf api.AutopilotConfiguration
   126  		if err := decodeBody(req, &conf); err != nil {
   127  			return nil, CodedError(http.StatusBadRequest, fmt.Sprintf("Error parsing autopilot config: %v", err))
   128  		}
   129  
   130  		args.Config = structs.AutopilotConfig{
   131  			CleanupDeadServers:      conf.CleanupDeadServers,
   132  			LastContactThreshold:    conf.LastContactThreshold,
   133  			MaxTrailingLogs:         conf.MaxTrailingLogs,
   134  			ServerStabilizationTime: conf.ServerStabilizationTime,
   135  			EnableRedundancyZones:   conf.EnableRedundancyZones,
   136  			DisableUpgradeMigration: conf.DisableUpgradeMigration,
   137  			EnableCustomUpgrades:    conf.EnableCustomUpgrades,
   138  		}
   139  
   140  		// Check for cas value
   141  		params := req.URL.Query()
   142  		if _, ok := params["cas"]; ok {
   143  			casVal, err := strconv.ParseUint(params.Get("cas"), 10, 64)
   144  			if err != nil {
   145  				return nil, CodedError(http.StatusBadRequest, fmt.Sprintf("Error parsing cas value: %v", err))
   146  			}
   147  			args.Config.ModifyIndex = casVal
   148  			args.CAS = true
   149  		}
   150  
   151  		var reply bool
   152  		if err := s.agent.RPC("Operator.AutopilotSetConfiguration", &args, &reply); err != nil {
   153  			return nil, err
   154  		}
   155  
   156  		// Only use the out value if this was a CAS
   157  		if !args.CAS {
   158  			return true, nil
   159  		}
   160  		return reply, nil
   161  
   162  	default:
   163  		return nil, CodedError(404, ErrInvalidMethod)
   164  	}
   165  }
   166  
   167  // OperatorServerHealth is used to get the health of the servers in the given Region.
   168  func (s *HTTPServer) OperatorServerHealth(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   169  	if req.Method != "GET" {
   170  		return nil, CodedError(404, ErrInvalidMethod)
   171  	}
   172  
   173  	var args structs.GenericRequest
   174  	if done := s.parse(resp, req, &args.Region, &args.QueryOptions); done {
   175  		return nil, nil
   176  	}
   177  
   178  	var reply autopilot.OperatorHealthReply
   179  	if err := s.agent.RPC("Operator.ServerHealth", &args, &reply); err != nil {
   180  		return nil, err
   181  	}
   182  
   183  	// Reply with status 429 if something is unhealthy
   184  	if !reply.Healthy {
   185  		resp.WriteHeader(http.StatusTooManyRequests)
   186  	}
   187  
   188  	out := &api.OperatorHealthReply{
   189  		Healthy:          reply.Healthy,
   190  		FailureTolerance: reply.FailureTolerance,
   191  	}
   192  	for _, server := range reply.Servers {
   193  		out.Servers = append(out.Servers, api.ServerHealth{
   194  			ID:          server.ID,
   195  			Name:        server.Name,
   196  			Address:     server.Address,
   197  			Version:     server.Version,
   198  			Leader:      server.Leader,
   199  			SerfStatus:  server.SerfStatus.String(),
   200  			LastContact: server.LastContact,
   201  			LastTerm:    server.LastTerm,
   202  			LastIndex:   server.LastIndex,
   203  			Healthy:     server.Healthy,
   204  			Voter:       server.Voter,
   205  			StableSince: server.StableSince.Round(time.Second).UTC(),
   206  		})
   207  	}
   208  
   209  	return out, nil
   210  }
   211  
   212  // OperatorSchedulerConfiguration is used to inspect the current Scheduler configuration.
   213  // This supports the stale query mode in case the cluster doesn't have a leader.
   214  func (s *HTTPServer) OperatorSchedulerConfiguration(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   215  	// Switch on the method
   216  	switch req.Method {
   217  	case "GET":
   218  		return s.schedulerGetConfig(resp, req)
   219  
   220  	case "PUT", "POST":
   221  		return s.schedulerUpdateConfig(resp, req)
   222  
   223  	default:
   224  		return nil, CodedError(405, ErrInvalidMethod)
   225  	}
   226  }
   227  
   228  func (s *HTTPServer) schedulerGetConfig(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   229  	var args structs.GenericRequest
   230  	if done := s.parse(resp, req, &args.Region, &args.QueryOptions); done {
   231  		return nil, nil
   232  	}
   233  
   234  	var reply structs.SchedulerConfigurationResponse
   235  	if err := s.agent.RPC("Operator.SchedulerGetConfiguration", &args, &reply); err != nil {
   236  		return nil, err
   237  	}
   238  	setMeta(resp, &reply.QueryMeta)
   239  
   240  	return reply, nil
   241  }
   242  
   243  func (s *HTTPServer) schedulerUpdateConfig(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   244  	var args structs.SchedulerSetConfigRequest
   245  	s.parseWriteRequest(req, &args.WriteRequest)
   246  
   247  	var conf api.SchedulerConfiguration
   248  	if err := decodeBody(req, &conf); err != nil {
   249  		return nil, CodedError(http.StatusBadRequest, fmt.Sprintf("Error parsing scheduler config: %v", err))
   250  	}
   251  
   252  	args.Config = structs.SchedulerConfiguration{
   253  		PreemptionConfig: structs.PreemptionConfig{
   254  			SystemSchedulerEnabled:  conf.PreemptionConfig.SystemSchedulerEnabled,
   255  			BatchSchedulerEnabled:   conf.PreemptionConfig.BatchSchedulerEnabled,
   256  			ServiceSchedulerEnabled: conf.PreemptionConfig.ServiceSchedulerEnabled},
   257  	}
   258  
   259  	// Check for cas value
   260  	params := req.URL.Query()
   261  	if _, ok := params["cas"]; ok {
   262  		casVal, err := strconv.ParseUint(params.Get("cas"), 10, 64)
   263  		if err != nil {
   264  			return nil, CodedError(http.StatusBadRequest, fmt.Sprintf("Error parsing cas value: %v", err))
   265  		}
   266  		args.Config.ModifyIndex = casVal
   267  		args.CAS = true
   268  	}
   269  
   270  	var reply structs.SchedulerSetConfigurationResponse
   271  	if err := s.agent.RPC("Operator.SchedulerSetConfiguration", &args, &reply); err != nil {
   272  		return nil, err
   273  	}
   274  	setIndex(resp, reply.Index)
   275  	return reply, nil
   276  }