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 }