github.com/kjdelisle/consul@v1.4.5/agent/operator_endpoint.go (about)

     1  package agent
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"strconv"
     7  	"time"
     8  
     9  	"github.com/hashicorp/consul/agent/consul/autopilot"
    10  	"github.com/hashicorp/consul/agent/structs"
    11  	"github.com/hashicorp/consul/api"
    12  	multierror "github.com/hashicorp/go-multierror"
    13  	"github.com/hashicorp/raft"
    14  )
    15  
    16  // OperatorRaftConfiguration is used to inspect the current Raft configuration.
    17  // This supports the stale query mode in case the cluster doesn't have a leader.
    18  func (s *HTTPServer) OperatorRaftConfiguration(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
    19  	var args structs.DCSpecificRequest
    20  	if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
    21  		return nil, nil
    22  	}
    23  
    24  	var reply structs.RaftConfigurationResponse
    25  	if err := s.agent.RPC("Operator.RaftGetConfiguration", &args, &reply); err != nil {
    26  		return nil, err
    27  	}
    28  
    29  	return reply, nil
    30  }
    31  
    32  // OperatorRaftPeer supports actions on Raft peers. Currently we only support
    33  // removing peers by address.
    34  func (s *HTTPServer) OperatorRaftPeer(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
    35  	var args structs.RaftRemovePeerRequest
    36  	s.parseDC(req, &args.Datacenter)
    37  	s.parseToken(req, &args.Token)
    38  
    39  	params := req.URL.Query()
    40  	_, hasID := params["id"]
    41  	if hasID {
    42  		args.ID = raft.ServerID(params.Get("id"))
    43  	}
    44  	_, hasAddress := params["address"]
    45  	if hasAddress {
    46  		args.Address = raft.ServerAddress(params.Get("address"))
    47  	}
    48  
    49  	if !hasID && !hasAddress {
    50  		resp.WriteHeader(http.StatusBadRequest)
    51  		fmt.Fprint(resp, "Must specify either ?id with the server's ID or ?address with IP:port of peer to remove")
    52  		return nil, nil
    53  	}
    54  	if hasID && hasAddress {
    55  		resp.WriteHeader(http.StatusBadRequest)
    56  		fmt.Fprint(resp, "Must specify only one of ?id or ?address")
    57  		return nil, nil
    58  	}
    59  
    60  	var reply struct{}
    61  	method := "Operator.RaftRemovePeerByID"
    62  	if hasAddress {
    63  		method = "Operator.RaftRemovePeerByAddress"
    64  	}
    65  	if err := s.agent.RPC(method, &args, &reply); err != nil {
    66  		return nil, err
    67  	}
    68  
    69  	return nil, nil
    70  }
    71  
    72  type keyringArgs struct {
    73  	Key         string
    74  	Token       string
    75  	RelayFactor uint8
    76  }
    77  
    78  // OperatorKeyringEndpoint handles keyring operations (install, list, use, remove)
    79  func (s *HTTPServer) OperatorKeyringEndpoint(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
    80  	var args keyringArgs
    81  	if req.Method == "POST" || req.Method == "PUT" || req.Method == "DELETE" {
    82  		if err := decodeBody(req, &args, nil); err != nil {
    83  			resp.WriteHeader(http.StatusBadRequest)
    84  			fmt.Fprintf(resp, "Request decode failed: %v", err)
    85  			return nil, nil
    86  		}
    87  	}
    88  	s.parseToken(req, &args.Token)
    89  
    90  	// Parse relay factor
    91  	if relayFactor := req.URL.Query().Get("relay-factor"); relayFactor != "" {
    92  		n, err := strconv.Atoi(relayFactor)
    93  		if err != nil {
    94  			resp.WriteHeader(http.StatusBadRequest)
    95  			fmt.Fprintf(resp, "Error parsing relay factor: %v", err)
    96  			return nil, nil
    97  		}
    98  
    99  		args.RelayFactor, err = ParseRelayFactor(n)
   100  		if err != nil {
   101  			resp.WriteHeader(http.StatusBadRequest)
   102  			fmt.Fprintf(resp, "Invalid relay factor: %v", err)
   103  			return nil, nil
   104  		}
   105  	}
   106  
   107  	// Switch on the method
   108  	switch req.Method {
   109  	case "GET":
   110  		return s.KeyringList(resp, req, &args)
   111  	case "POST":
   112  		return s.KeyringInstall(resp, req, &args)
   113  	case "PUT":
   114  		return s.KeyringUse(resp, req, &args)
   115  	case "DELETE":
   116  		return s.KeyringRemove(resp, req, &args)
   117  	default:
   118  		return nil, MethodNotAllowedError{req.Method, []string{"GET", "POST", "PUT", "DELETE"}}
   119  	}
   120  }
   121  
   122  // KeyringInstall is used to install a new gossip encryption key into the cluster
   123  func (s *HTTPServer) KeyringInstall(resp http.ResponseWriter, req *http.Request, args *keyringArgs) (interface{}, error) {
   124  	responses, err := s.agent.InstallKey(args.Key, args.Token, args.RelayFactor)
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  
   129  	return nil, keyringErrorsOrNil(responses.Responses)
   130  }
   131  
   132  // KeyringList is used to list the keys installed in the cluster
   133  func (s *HTTPServer) KeyringList(resp http.ResponseWriter, req *http.Request, args *keyringArgs) (interface{}, error) {
   134  	responses, err := s.agent.ListKeys(args.Token, args.RelayFactor)
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  
   139  	return responses.Responses, keyringErrorsOrNil(responses.Responses)
   140  }
   141  
   142  // KeyringRemove is used to list the keys installed in the cluster
   143  func (s *HTTPServer) KeyringRemove(resp http.ResponseWriter, req *http.Request, args *keyringArgs) (interface{}, error) {
   144  	responses, err := s.agent.RemoveKey(args.Key, args.Token, args.RelayFactor)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  
   149  	return nil, keyringErrorsOrNil(responses.Responses)
   150  }
   151  
   152  // KeyringUse is used to change the primary gossip encryption key
   153  func (s *HTTPServer) KeyringUse(resp http.ResponseWriter, req *http.Request, args *keyringArgs) (interface{}, error) {
   154  	responses, err := s.agent.UseKey(args.Key, args.Token, args.RelayFactor)
   155  	if err != nil {
   156  		return nil, err
   157  	}
   158  
   159  	return nil, keyringErrorsOrNil(responses.Responses)
   160  }
   161  
   162  func keyringErrorsOrNil(responses []*structs.KeyringResponse) error {
   163  	var errs error
   164  	for _, response := range responses {
   165  		if response.Error != "" {
   166  			pool := response.Datacenter + " (LAN)"
   167  			if response.WAN {
   168  				pool = "WAN"
   169  			}
   170  			errs = multierror.Append(errs, fmt.Errorf("%s error: %s", pool, response.Error))
   171  			for key, message := range response.Messages {
   172  				errs = multierror.Append(errs, fmt.Errorf("%s: %s", key, message))
   173  			}
   174  		}
   175  	}
   176  	return errs
   177  }
   178  
   179  // OperatorAutopilotConfiguration is used to inspect the current Autopilot configuration.
   180  // This supports the stale query mode in case the cluster doesn't have a leader.
   181  func (s *HTTPServer) OperatorAutopilotConfiguration(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   182  	// Switch on the method
   183  	switch req.Method {
   184  	case "GET":
   185  		var args structs.DCSpecificRequest
   186  		if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
   187  			return nil, nil
   188  		}
   189  
   190  		var reply autopilot.Config
   191  		if err := s.agent.RPC("Operator.AutopilotGetConfiguration", &args, &reply); err != nil {
   192  			return nil, err
   193  		}
   194  
   195  		out := api.AutopilotConfiguration{
   196  			CleanupDeadServers:      reply.CleanupDeadServers,
   197  			LastContactThreshold:    api.NewReadableDuration(reply.LastContactThreshold),
   198  			MaxTrailingLogs:         reply.MaxTrailingLogs,
   199  			ServerStabilizationTime: api.NewReadableDuration(reply.ServerStabilizationTime),
   200  			RedundancyZoneTag:       reply.RedundancyZoneTag,
   201  			DisableUpgradeMigration: reply.DisableUpgradeMigration,
   202  			UpgradeVersionTag:       reply.UpgradeVersionTag,
   203  			CreateIndex:             reply.CreateIndex,
   204  			ModifyIndex:             reply.ModifyIndex,
   205  		}
   206  
   207  		return out, nil
   208  
   209  	case "PUT":
   210  		var args structs.AutopilotSetConfigRequest
   211  		s.parseDC(req, &args.Datacenter)
   212  		s.parseToken(req, &args.Token)
   213  
   214  		var conf api.AutopilotConfiguration
   215  		durations := NewDurationFixer("lastcontactthreshold", "serverstabilizationtime")
   216  		if err := decodeBody(req, &conf, durations.FixupDurations); err != nil {
   217  			resp.WriteHeader(http.StatusBadRequest)
   218  			fmt.Fprintf(resp, "Error parsing autopilot config: %v", err)
   219  			return nil, nil
   220  		}
   221  
   222  		args.Config = autopilot.Config{
   223  			CleanupDeadServers:      conf.CleanupDeadServers,
   224  			LastContactThreshold:    conf.LastContactThreshold.Duration(),
   225  			MaxTrailingLogs:         conf.MaxTrailingLogs,
   226  			ServerStabilizationTime: conf.ServerStabilizationTime.Duration(),
   227  			RedundancyZoneTag:       conf.RedundancyZoneTag,
   228  			DisableUpgradeMigration: conf.DisableUpgradeMigration,
   229  			UpgradeVersionTag:       conf.UpgradeVersionTag,
   230  		}
   231  
   232  		// Check for cas value
   233  		params := req.URL.Query()
   234  		if _, ok := params["cas"]; ok {
   235  			casVal, err := strconv.ParseUint(params.Get("cas"), 10, 64)
   236  			if err != nil {
   237  				resp.WriteHeader(http.StatusBadRequest)
   238  				fmt.Fprintf(resp, "Error parsing cas value: %v", err)
   239  				return nil, nil
   240  			}
   241  			args.Config.ModifyIndex = casVal
   242  			args.CAS = true
   243  		}
   244  
   245  		var reply bool
   246  		if err := s.agent.RPC("Operator.AutopilotSetConfiguration", &args, &reply); err != nil {
   247  			return nil, err
   248  		}
   249  
   250  		// Only use the out value if this was a CAS
   251  		if !args.CAS {
   252  			return true, nil
   253  		}
   254  		return reply, nil
   255  
   256  	default:
   257  		return nil, MethodNotAllowedError{req.Method, []string{"GET", "PUT"}}
   258  	}
   259  }
   260  
   261  // OperatorServerHealth is used to get the health of the servers in the local DC
   262  func (s *HTTPServer) OperatorServerHealth(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   263  	var args structs.DCSpecificRequest
   264  	if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
   265  		return nil, nil
   266  	}
   267  
   268  	var reply autopilot.OperatorHealthReply
   269  	if err := s.agent.RPC("Operator.ServerHealth", &args, &reply); err != nil {
   270  		return nil, err
   271  	}
   272  
   273  	// Reply with status 429 if something is unhealthy
   274  	if !reply.Healthy {
   275  		resp.WriteHeader(http.StatusTooManyRequests)
   276  	}
   277  
   278  	out := &api.OperatorHealthReply{
   279  		Healthy:          reply.Healthy,
   280  		FailureTolerance: reply.FailureTolerance,
   281  	}
   282  	for _, server := range reply.Servers {
   283  		out.Servers = append(out.Servers, api.ServerHealth{
   284  			ID:          server.ID,
   285  			Name:        server.Name,
   286  			Address:     server.Address,
   287  			Version:     server.Version,
   288  			Leader:      server.Leader,
   289  			SerfStatus:  server.SerfStatus.String(),
   290  			LastContact: api.NewReadableDuration(server.LastContact),
   291  			LastTerm:    server.LastTerm,
   292  			LastIndex:   server.LastIndex,
   293  			Healthy:     server.Healthy,
   294  			Voter:       server.Voter,
   295  			StableSince: server.StableSince.Round(time.Second).UTC(),
   296  		})
   297  	}
   298  
   299  	return out, nil
   300  }