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 }