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 }