github.com/ilhicas/nomad@v0.11.8/command/agent/agent_endpoint.go (about) 1 package agent 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "io" 9 "net" 10 "net/http" 11 "sort" 12 "strconv" 13 "strings" 14 15 "github.com/docker/docker/pkg/ioutils" 16 log "github.com/hashicorp/go-hclog" 17 "github.com/hashicorp/go-msgpack/codec" 18 "github.com/hashicorp/nomad/acl" 19 cstructs "github.com/hashicorp/nomad/client/structs" 20 "github.com/hashicorp/nomad/command/agent/pprof" 21 "github.com/hashicorp/nomad/nomad/structs" 22 "github.com/hashicorp/serf/serf" 23 "github.com/mitchellh/copystructure" 24 ) 25 26 type Member struct { 27 Name string 28 Addr net.IP 29 Port uint16 30 Tags map[string]string 31 Status string 32 ProtocolMin uint8 33 ProtocolMax uint8 34 ProtocolCur uint8 35 DelegateMin uint8 36 DelegateMax uint8 37 DelegateCur uint8 38 } 39 40 func nomadMember(m serf.Member) Member { 41 return Member{ 42 Name: m.Name, 43 Addr: m.Addr, 44 Port: m.Port, 45 Tags: m.Tags, 46 Status: m.Status.String(), 47 ProtocolMin: m.ProtocolMin, 48 ProtocolMax: m.ProtocolMax, 49 ProtocolCur: m.ProtocolCur, 50 DelegateMin: m.DelegateMin, 51 DelegateMax: m.DelegateMax, 52 DelegateCur: m.DelegateCur, 53 } 54 } 55 56 func (s *HTTPServer) AgentSelfRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 57 if req.Method != "GET" { 58 return nil, CodedError(405, ErrInvalidMethod) 59 } 60 61 var secret string 62 s.parseToken(req, &secret) 63 64 var aclObj *acl.ACL 65 var err error 66 67 // Get the member as a server 68 var member serf.Member 69 if srv := s.agent.Server(); srv != nil { 70 member = srv.LocalMember() 71 aclObj, err = srv.ResolveToken(secret) 72 } else { 73 // Not a Server; use the Client for token resolution 74 aclObj, err = s.agent.Client().ResolveToken(secret) 75 } 76 77 if err != nil { 78 return nil, err 79 } 80 81 // Check agent read permissions 82 if aclObj != nil && !aclObj.AllowAgentRead() { 83 return nil, structs.ErrPermissionDenied 84 } 85 86 self := agentSelf{ 87 Member: nomadMember(member), 88 Stats: s.agent.Stats(), 89 } 90 if ac, err := copystructure.Copy(s.agent.config); err != nil { 91 return nil, CodedError(500, err.Error()) 92 } else { 93 self.Config = ac.(*Config) 94 } 95 96 if self.Config != nil && self.Config.Vault != nil && self.Config.Vault.Token != "" { 97 self.Config.Vault.Token = "<redacted>" 98 } 99 100 if self.Config != nil && self.Config.ACL != nil && self.Config.ACL.ReplicationToken != "" { 101 self.Config.ACL.ReplicationToken = "<redacted>" 102 } 103 104 if self.Config != nil && self.Config.Consul != nil && self.Config.Consul.Token != "" { 105 self.Config.Consul.Token = "<redacted>" 106 } 107 108 if self.Config != nil && self.Config.Telemetry != nil && self.Config.Telemetry.CirconusAPIToken != "" { 109 self.Config.Telemetry.CirconusAPIToken = "<redacted>" 110 } 111 112 return self, nil 113 } 114 115 func (s *HTTPServer) AgentJoinRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 116 if req.Method != "PUT" && req.Method != "POST" { 117 return nil, CodedError(405, ErrInvalidMethod) 118 } 119 srv := s.agent.Server() 120 if srv == nil { 121 return nil, CodedError(501, ErrInvalidMethod) 122 } 123 124 // Get the join addresses 125 query := req.URL.Query() 126 addrs := query["address"] 127 if len(addrs) == 0 { 128 return nil, CodedError(400, "missing address to join") 129 } 130 131 // Attempt the join 132 num, err := srv.Join(addrs) 133 var errStr string 134 if err != nil { 135 errStr = err.Error() 136 } 137 return joinResult{num, errStr}, nil 138 } 139 140 func (s *HTTPServer) AgentMembersRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 141 if req.Method != "GET" { 142 return nil, CodedError(405, ErrInvalidMethod) 143 } 144 145 args := &structs.GenericRequest{} 146 if s.parse(resp, req, &args.Region, &args.QueryOptions) { 147 return nil, nil 148 } 149 150 var out structs.ServerMembersResponse 151 if err := s.agent.RPC("Status.Members", args, &out); err != nil { 152 return nil, err 153 } 154 155 return out, nil 156 } 157 158 func (s *HTTPServer) AgentMonitor(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 159 // Get the provided loglevel. 160 logLevel := req.URL.Query().Get("log_level") 161 if logLevel == "" { 162 logLevel = "INFO" 163 } 164 165 if log.LevelFromString(logLevel) == log.NoLevel { 166 return nil, CodedError(400, fmt.Sprintf("Unknown log level: %s", logLevel)) 167 } 168 169 logJSON := false 170 logJSONStr := req.URL.Query().Get("log_json") 171 if logJSONStr != "" { 172 parsed, err := strconv.ParseBool(logJSONStr) 173 if err != nil { 174 return nil, CodedError(400, fmt.Sprintf("Unknown option for log json: %v", err)) 175 } 176 logJSON = parsed 177 } 178 179 plainText := false 180 plainTextStr := req.URL.Query().Get("plain") 181 if plainTextStr != "" { 182 parsed, err := strconv.ParseBool(plainTextStr) 183 if err != nil { 184 return nil, CodedError(400, fmt.Sprintf("Unknown option for plain: %v", err)) 185 } 186 plainText = parsed 187 } 188 189 nodeID := req.URL.Query().Get("node_id") 190 // Build the request and parse the ACL token 191 args := cstructs.MonitorRequest{ 192 NodeID: nodeID, 193 ServerID: req.URL.Query().Get("server_id"), 194 LogLevel: logLevel, 195 LogJSON: logJSON, 196 PlainText: plainText, 197 } 198 199 // if node and server were requested return error 200 if args.NodeID != "" && args.ServerID != "" { 201 return nil, CodedError(400, "Cannot target node and server simultaneously") 202 } 203 204 // Force the Content-Type to avoid Go's http.ResponseWriter from 205 // detecting an incorrect or unsafe one. 206 if plainText { 207 resp.Header().Set("Content-Type", "text/plain") 208 } else { 209 resp.Header().Set("Content-Type", "application/json") 210 } 211 212 s.parse(resp, req, &args.QueryOptions.Region, &args.QueryOptions) 213 214 // Make the RPC 215 var handler structs.StreamingRpcHandler 216 var handlerErr error 217 if nodeID != "" { 218 // Determine the handler to use 219 useLocalClient, useClientRPC, useServerRPC := s.rpcHandlerForNode(nodeID) 220 if useLocalClient { 221 handler, handlerErr = s.agent.Client().StreamingRpcHandler("Agent.Monitor") 222 } else if useClientRPC { 223 handler, handlerErr = s.agent.Client().RemoteStreamingRpcHandler("Agent.Monitor") 224 } else if useServerRPC { 225 handler, handlerErr = s.agent.Server().StreamingRpcHandler("Agent.Monitor") 226 } else { 227 handlerErr = CodedError(400, "No local Node and node_id not provided") 228 } 229 // No node id monitor current server/client 230 } else if srv := s.agent.Server(); srv != nil { 231 handler, handlerErr = srv.StreamingRpcHandler("Agent.Monitor") 232 } else { 233 handler, handlerErr = s.agent.Client().StreamingRpcHandler("Agent.Monitor") 234 } 235 236 if handlerErr != nil { 237 return nil, CodedError(500, handlerErr.Error()) 238 } 239 httpPipe, handlerPipe := net.Pipe() 240 decoder := codec.NewDecoder(httpPipe, structs.MsgpackHandle) 241 encoder := codec.NewEncoder(httpPipe, structs.MsgpackHandle) 242 243 ctx, cancel := context.WithCancel(req.Context()) 244 go func() { 245 <-ctx.Done() 246 httpPipe.Close() 247 }() 248 249 // Create an output that gets flushed on every write 250 output := ioutils.NewWriteFlusher(resp) 251 252 // create an error channel to handle errors 253 errCh := make(chan HTTPCodedError, 2) 254 255 // stream response 256 go func() { 257 defer cancel() 258 259 // Send the request 260 if err := encoder.Encode(args); err != nil { 261 errCh <- CodedError(500, err.Error()) 262 return 263 } 264 265 for { 266 select { 267 case <-ctx.Done(): 268 errCh <- nil 269 return 270 default: 271 } 272 273 var res cstructs.StreamErrWrapper 274 if err := decoder.Decode(&res); err != nil { 275 errCh <- CodedError(500, err.Error()) 276 return 277 } 278 decoder.Reset(httpPipe) 279 280 if err := res.Error; err != nil { 281 if err.Code != nil { 282 errCh <- CodedError(int(*err.Code), err.Error()) 283 return 284 } 285 } 286 287 if _, err := io.Copy(output, bytes.NewReader(res.Payload)); err != nil { 288 errCh <- CodedError(500, err.Error()) 289 return 290 } 291 } 292 }() 293 294 handler(handlerPipe) 295 cancel() 296 codedErr := <-errCh 297 298 if codedErr != nil && 299 (codedErr == io.EOF || 300 strings.Contains(codedErr.Error(), "closed") || 301 strings.Contains(codedErr.Error(), "EOF")) { 302 codedErr = nil 303 } 304 return nil, codedErr 305 } 306 307 func (s *HTTPServer) AgentForceLeaveRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 308 if req.Method != "PUT" && req.Method != "POST" { 309 return nil, CodedError(405, ErrInvalidMethod) 310 } 311 srv := s.agent.Server() 312 if srv == nil { 313 return nil, CodedError(501, ErrInvalidMethod) 314 } 315 316 var secret string 317 s.parseToken(req, &secret) 318 319 // Check agent write permissions 320 if aclObj, err := s.agent.Server().ResolveToken(secret); err != nil { 321 return nil, err 322 } else if aclObj != nil && !aclObj.AllowAgentWrite() { 323 return nil, structs.ErrPermissionDenied 324 } 325 326 // Get the node to eject 327 node := req.URL.Query().Get("node") 328 if node == "" { 329 return nil, CodedError(400, "missing node to force leave") 330 } 331 332 // Attempt remove 333 err := srv.RemoveFailedNode(node) 334 return nil, err 335 } 336 337 func (s *HTTPServer) AgentPprofRequest(resp http.ResponseWriter, req *http.Request) ([]byte, error) { 338 path := strings.TrimPrefix(req.URL.Path, "/v1/agent/pprof/") 339 switch path { 340 case "": 341 // no root index route 342 return nil, CodedError(404, ErrInvalidMethod) 343 case "cmdline": 344 return s.agentPprof(pprof.CmdReq, resp, req) 345 case "profile": 346 return s.agentPprof(pprof.CPUReq, resp, req) 347 case "trace": 348 return s.agentPprof(pprof.TraceReq, resp, req) 349 default: 350 // Add profile to request 351 values := req.URL.Query() 352 values.Add("profile", path) 353 req.URL.RawQuery = values.Encode() 354 355 // generic pprof profile request 356 return s.agentPprof(pprof.LookupReq, resp, req) 357 } 358 } 359 360 func (s *HTTPServer) agentPprof(reqType pprof.ReqType, resp http.ResponseWriter, req *http.Request) ([]byte, error) { 361 362 // Parse query param int values 363 // Errors are dropped here and default to their zero values. 364 // This is to mimick the functionality that net/pprof implements. 365 seconds, _ := strconv.Atoi(req.URL.Query().Get("seconds")) 366 debug, _ := strconv.Atoi(req.URL.Query().Get("debug")) 367 gc, _ := strconv.Atoi(req.URL.Query().Get("gc")) 368 369 // default to 1 second 370 if seconds == 0 { 371 seconds = 1 372 } 373 374 // Create the request 375 args := &structs.AgentPprofRequest{ 376 NodeID: req.URL.Query().Get("node_id"), 377 Profile: req.URL.Query().Get("profile"), 378 ServerID: req.URL.Query().Get("server_id"), 379 Debug: debug, 380 GC: gc, 381 ReqType: reqType, 382 Seconds: seconds, 383 } 384 385 // if node and server were requested return error 386 if args.NodeID != "" && args.ServerID != "" { 387 return nil, CodedError(400, "Cannot target node and server simultaneously") 388 } 389 390 s.parse(resp, req, &args.QueryOptions.Region, &args.QueryOptions) 391 392 var reply structs.AgentPprofResponse 393 var rpcErr error 394 if args.NodeID != "" { 395 // Make the RPC 396 localClient, remoteClient, localServer := s.rpcHandlerForNode(args.NodeID) 397 if localClient { 398 rpcErr = s.agent.Client().ClientRPC("Agent.Profile", &args, &reply) 399 } else if remoteClient { 400 rpcErr = s.agent.Client().RPC("Agent.Profile", &args, &reply) 401 } else if localServer { 402 rpcErr = s.agent.Server().RPC("Agent.Profile", &args, &reply) 403 } 404 // No node id, profile current server/client 405 } else if srv := s.agent.Server(); srv != nil { 406 rpcErr = srv.RPC("Agent.Profile", &args, &reply) 407 } else { 408 rpcErr = s.agent.Client().RPC("Agent.Profile", &args, &reply) 409 } 410 411 if rpcErr != nil { 412 return nil, rpcErr 413 } 414 415 // Set headers from profile request 416 for k, v := range reply.HTTPHeaders { 417 resp.Header().Set(k, v) 418 } 419 420 return reply.Payload, nil 421 } 422 423 // AgentServersRequest is used to query the list of servers used by the Nomad 424 // Client for RPCs. This endpoint can also be used to update the list of 425 // servers for a given agent. 426 func (s *HTTPServer) AgentServersRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 427 switch req.Method { 428 case "PUT", "POST": 429 return s.updateServers(resp, req) 430 case "GET": 431 return s.listServers(resp, req) 432 default: 433 return nil, CodedError(405, ErrInvalidMethod) 434 } 435 } 436 437 func (s *HTTPServer) listServers(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 438 client := s.agent.Client() 439 if client == nil { 440 return nil, CodedError(501, ErrInvalidMethod) 441 } 442 443 var secret string 444 s.parseToken(req, &secret) 445 446 // Check agent read permissions 447 if aclObj, err := s.agent.Client().ResolveToken(secret); err != nil { 448 return nil, err 449 } else if aclObj != nil && !aclObj.AllowAgentRead() { 450 return nil, structs.ErrPermissionDenied 451 } 452 453 peers := s.agent.client.GetServers() 454 sort.Strings(peers) 455 return peers, nil 456 } 457 458 func (s *HTTPServer) updateServers(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 459 client := s.agent.Client() 460 if client == nil { 461 return nil, CodedError(501, ErrInvalidMethod) 462 } 463 464 // Get the servers from the request 465 servers := req.URL.Query()["address"] 466 if len(servers) == 0 { 467 return nil, CodedError(400, "missing server address") 468 } 469 470 var secret string 471 s.parseToken(req, &secret) 472 473 // Check agent write permissions 474 if aclObj, err := s.agent.Client().ResolveToken(secret); err != nil { 475 return nil, err 476 } else if aclObj != nil && !aclObj.AllowAgentWrite() { 477 return nil, structs.ErrPermissionDenied 478 } 479 480 // Set the servers list into the client 481 s.agent.logger.Trace("adding servers to the client's primary server list", "servers", servers, "path", "/v1/agent/servers", "method", "PUT") 482 if _, err := client.SetServers(servers); err != nil { 483 s.agent.logger.Error("failed adding servers to client's server list", "servers", servers, "error", err, "path", "/v1/agent/servers", "method", "PUT") 484 //TODO is this the right error to return? 485 return nil, CodedError(400, err.Error()) 486 } 487 return nil, nil 488 } 489 490 // KeyringOperationRequest allows an operator to install/delete/use keys 491 func (s *HTTPServer) KeyringOperationRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 492 srv := s.agent.Server() 493 if srv == nil { 494 return nil, CodedError(501, ErrInvalidMethod) 495 } 496 497 var secret string 498 s.parseToken(req, &secret) 499 500 // Check agent write permissions 501 if aclObj, err := srv.ResolveToken(secret); err != nil { 502 return nil, err 503 } else if aclObj != nil && !aclObj.AllowAgentWrite() { 504 return nil, structs.ErrPermissionDenied 505 } 506 507 kmgr := srv.KeyManager() 508 var sresp *serf.KeyResponse 509 var err error 510 511 // Get the key from the req body 512 var args structs.KeyringRequest 513 514 //Get the op 515 op := strings.TrimPrefix(req.URL.Path, "/v1/agent/keyring/") 516 517 switch op { 518 case "list": 519 sresp, err = kmgr.ListKeys() 520 case "install": 521 if err := decodeBody(req, &args); err != nil { 522 return nil, CodedError(500, err.Error()) 523 } 524 sresp, err = kmgr.InstallKey(args.Key) 525 case "use": 526 if err := decodeBody(req, &args); err != nil { 527 return nil, CodedError(500, err.Error()) 528 } 529 sresp, err = kmgr.UseKey(args.Key) 530 case "remove": 531 if err := decodeBody(req, &args); err != nil { 532 return nil, CodedError(500, err.Error()) 533 } 534 sresp, err = kmgr.RemoveKey(args.Key) 535 default: 536 return nil, CodedError(404, "resource not found") 537 } 538 539 if err != nil { 540 return nil, err 541 } 542 kresp := structs.KeyringResponse{ 543 Messages: sresp.Messages, 544 Keys: sresp.Keys, 545 NumNodes: sresp.NumNodes, 546 } 547 return kresp, nil 548 } 549 550 type agentSelf struct { 551 Config *Config `json:"config"` 552 Member Member `json:"member,omitempty"` 553 Stats map[string]map[string]string `json:"stats"` 554 } 555 556 type joinResult struct { 557 NumJoined int `json:"num_joined"` 558 Error string `json:"error"` 559 } 560 561 func (s *HTTPServer) HealthRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 562 if req.Method != "GET" { 563 return nil, CodedError(405, ErrInvalidMethod) 564 } 565 566 var args structs.GenericRequest 567 if s.parse(resp, req, &args.Region, &args.QueryOptions) { 568 return nil, nil 569 } 570 571 health := healthResponse{} 572 getClient := true 573 getServer := true 574 575 // See if we're checking a specific agent type and default to failing 576 if healthType, ok := req.URL.Query()["type"]; ok { 577 getClient = false 578 getServer = false 579 for _, ht := range healthType { 580 switch ht { 581 case "client": 582 getClient = true 583 health.Client = &healthResponseAgent{ 584 Ok: false, 585 Message: "client not enabled", 586 } 587 case "server": 588 getServer = true 589 health.Server = &healthResponseAgent{ 590 Ok: false, 591 Message: "server not enabled", 592 } 593 } 594 } 595 } 596 597 // If we should check the client and it exists assume it's healthy 598 if client := s.agent.Client(); getClient && client != nil { 599 if len(client.GetServers()) == 0 { 600 health.Client = &healthResponseAgent{ 601 Ok: false, 602 Message: "no known servers", 603 } 604 } else { 605 health.Client = &healthResponseAgent{ 606 Ok: true, 607 Message: "ok", 608 } 609 } 610 } 611 612 // If we should check the server and it exists, see if there's a leader 613 if server := s.agent.Server(); getServer && server != nil { 614 health.Server = &healthResponseAgent{ 615 Ok: true, 616 Message: "ok", 617 } 618 619 leader := "" 620 if err := s.agent.RPC("Status.Leader", &args, &leader); err != nil { 621 health.Server.Ok = false 622 health.Server.Message = err.Error() 623 } else if leader == "" { 624 health.Server.Ok = false 625 health.Server.Message = "no leader" 626 } 627 } 628 629 if health.ok() { 630 return &health, nil 631 } 632 633 jsonResp, err := json.Marshal(&health) 634 if err != nil { 635 return nil, err 636 } 637 return nil, CodedError(500, string(jsonResp)) 638 } 639 640 type healthResponse struct { 641 Client *healthResponseAgent `json:"client,omitempty"` 642 Server *healthResponseAgent `json:"server,omitempty"` 643 } 644 645 // ok returns true as long as neither Client nor Server have Ok=false. 646 func (h healthResponse) ok() bool { 647 if h.Client != nil && !h.Client.Ok { 648 return false 649 } 650 if h.Server != nil && !h.Server.Ok { 651 return false 652 } 653 return true 654 } 655 656 type healthResponseAgent struct { 657 Ok bool `json:"ok"` 658 Message string `json:"message,omitempty"` 659 }