github.com/Iqoqo/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 }