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