github.com/thomasobenaus/nomad@v0.11.1/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  			MinQuorum:               reply.MinQuorum,
   112  			ServerStabilizationTime: reply.ServerStabilizationTime,
   113  			EnableRedundancyZones:   reply.EnableRedundancyZones,
   114  			DisableUpgradeMigration: reply.DisableUpgradeMigration,
   115  			EnableCustomUpgrades:    reply.EnableCustomUpgrades,
   116  			CreateIndex:             reply.CreateIndex,
   117  			ModifyIndex:             reply.ModifyIndex,
   118  		}
   119  
   120  		return out, nil
   121  
   122  	case "PUT":
   123  		var args structs.AutopilotSetConfigRequest
   124  		s.parseWriteRequest(req, &args.WriteRequest)
   125  
   126  		var conf api.AutopilotConfiguration
   127  		if err := decodeBody(req, &conf); err != nil {
   128  			return nil, CodedError(http.StatusBadRequest, fmt.Sprintf("Error parsing autopilot config: %v", err))
   129  		}
   130  
   131  		args.Config = structs.AutopilotConfig{
   132  			CleanupDeadServers:      conf.CleanupDeadServers,
   133  			LastContactThreshold:    conf.LastContactThreshold,
   134  			MaxTrailingLogs:         conf.MaxTrailingLogs,
   135  			MinQuorum:               conf.MinQuorum,
   136  			ServerStabilizationTime: conf.ServerStabilizationTime,
   137  			EnableRedundancyZones:   conf.EnableRedundancyZones,
   138  			DisableUpgradeMigration: conf.DisableUpgradeMigration,
   139  			EnableCustomUpgrades:    conf.EnableCustomUpgrades,
   140  		}
   141  
   142  		// Check for cas value
   143  		params := req.URL.Query()
   144  		if _, ok := params["cas"]; ok {
   145  			casVal, err := strconv.ParseUint(params.Get("cas"), 10, 64)
   146  			if err != nil {
   147  				return nil, CodedError(http.StatusBadRequest, fmt.Sprintf("Error parsing cas value: %v", err))
   148  			}
   149  			args.Config.ModifyIndex = casVal
   150  			args.CAS = true
   151  		}
   152  
   153  		var reply bool
   154  		if err := s.agent.RPC("Operator.AutopilotSetConfiguration", &args, &reply); err != nil {
   155  			return nil, err
   156  		}
   157  
   158  		// Only use the out value if this was a CAS
   159  		if !args.CAS {
   160  			return true, nil
   161  		}
   162  		return reply, nil
   163  
   164  	default:
   165  		return nil, CodedError(404, ErrInvalidMethod)
   166  	}
   167  }
   168  
   169  // OperatorServerHealth is used to get the health of the servers in the given Region.
   170  func (s *HTTPServer) OperatorServerHealth(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   171  	if req.Method != "GET" {
   172  		return nil, CodedError(404, ErrInvalidMethod)
   173  	}
   174  
   175  	var args structs.GenericRequest
   176  	if done := s.parse(resp, req, &args.Region, &args.QueryOptions); done {
   177  		return nil, nil
   178  	}
   179  
   180  	var reply autopilot.OperatorHealthReply
   181  	if err := s.agent.RPC("Operator.ServerHealth", &args, &reply); err != nil {
   182  		return nil, err
   183  	}
   184  
   185  	// Reply with status 429 if something is unhealthy
   186  	if !reply.Healthy {
   187  		resp.WriteHeader(http.StatusTooManyRequests)
   188  	}
   189  
   190  	out := &api.OperatorHealthReply{
   191  		Healthy:          reply.Healthy,
   192  		FailureTolerance: reply.FailureTolerance,
   193  	}
   194  	for _, server := range reply.Servers {
   195  		out.Servers = append(out.Servers, api.ServerHealth{
   196  			ID:          server.ID,
   197  			Name:        server.Name,
   198  			Address:     server.Address,
   199  			Version:     server.Version,
   200  			Leader:      server.Leader,
   201  			SerfStatus:  server.SerfStatus.String(),
   202  			LastContact: server.LastContact,
   203  			LastTerm:    server.LastTerm,
   204  			LastIndex:   server.LastIndex,
   205  			Healthy:     server.Healthy,
   206  			Voter:       server.Voter,
   207  			StableSince: server.StableSince.Round(time.Second).UTC(),
   208  		})
   209  	}
   210  
   211  	return out, nil
   212  }
   213  
   214  // OperatorSchedulerConfiguration is used to inspect the current Scheduler configuration.
   215  // This supports the stale query mode in case the cluster doesn't have a leader.
   216  func (s *HTTPServer) OperatorSchedulerConfiguration(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   217  	// Switch on the method
   218  	switch req.Method {
   219  	case "GET":
   220  		return s.schedulerGetConfig(resp, req)
   221  
   222  	case "PUT", "POST":
   223  		return s.schedulerUpdateConfig(resp, req)
   224  
   225  	default:
   226  		return nil, CodedError(405, ErrInvalidMethod)
   227  	}
   228  }
   229  
   230  func (s *HTTPServer) schedulerGetConfig(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   231  	var args structs.GenericRequest
   232  	if done := s.parse(resp, req, &args.Region, &args.QueryOptions); done {
   233  		return nil, nil
   234  	}
   235  
   236  	var reply structs.SchedulerConfigurationResponse
   237  	if err := s.agent.RPC("Operator.SchedulerGetConfiguration", &args, &reply); err != nil {
   238  		return nil, err
   239  	}
   240  	setMeta(resp, &reply.QueryMeta)
   241  
   242  	return reply, nil
   243  }
   244  
   245  func (s *HTTPServer) schedulerUpdateConfig(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   246  	var args structs.SchedulerSetConfigRequest
   247  	s.parseWriteRequest(req, &args.WriteRequest)
   248  
   249  	var conf api.SchedulerConfiguration
   250  	if err := decodeBody(req, &conf); err != nil {
   251  		return nil, CodedError(http.StatusBadRequest, fmt.Sprintf("Error parsing scheduler config: %v", err))
   252  	}
   253  
   254  	args.Config = structs.SchedulerConfiguration{
   255  		PreemptionConfig: structs.PreemptionConfig{
   256  			SystemSchedulerEnabled:  conf.PreemptionConfig.SystemSchedulerEnabled,
   257  			BatchSchedulerEnabled:   conf.PreemptionConfig.BatchSchedulerEnabled,
   258  			ServiceSchedulerEnabled: conf.PreemptionConfig.ServiceSchedulerEnabled},
   259  	}
   260  
   261  	// Check for cas value
   262  	params := req.URL.Query()
   263  	if _, ok := params["cas"]; ok {
   264  		casVal, err := strconv.ParseUint(params.Get("cas"), 10, 64)
   265  		if err != nil {
   266  			return nil, CodedError(http.StatusBadRequest, fmt.Sprintf("Error parsing cas value: %v", err))
   267  		}
   268  		args.Config.ModifyIndex = casVal
   269  		args.CAS = true
   270  	}
   271  
   272  	var reply structs.SchedulerSetConfigurationResponse
   273  	if err := s.agent.RPC("Operator.SchedulerSetConfiguration", &args, &reply); err != nil {
   274  		return nil, err
   275  	}
   276  	setIndex(resp, reply.Index)
   277  	return reply, nil
   278  }