github.com/adityamillind98/nomad@v0.11.8/command/agent/alloc_endpoint.go (about) 1 package agent 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io" 8 "net" 9 "net/http" 10 "strconv" 11 "strings" 12 13 "github.com/golang/snappy" 14 "github.com/gorilla/websocket" 15 "github.com/hashicorp/go-msgpack/codec" 16 cstructs "github.com/hashicorp/nomad/client/structs" 17 "github.com/hashicorp/nomad/nomad/structs" 18 "github.com/hashicorp/nomad/plugins/drivers" 19 ) 20 21 const ( 22 allocNotFoundErr = "allocation not found" 23 resourceNotFoundErr = "resource not found" 24 ) 25 26 func (s *HTTPServer) AllocsRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 27 if req.Method != "GET" { 28 return nil, CodedError(405, ErrInvalidMethod) 29 } 30 31 args := structs.AllocListRequest{} 32 if s.parse(resp, req, &args.Region, &args.QueryOptions) { 33 return nil, nil 34 } 35 36 var out structs.AllocListResponse 37 if err := s.agent.RPC("Alloc.List", &args, &out); err != nil { 38 return nil, err 39 } 40 41 setMeta(resp, &out.QueryMeta) 42 if out.Allocations == nil { 43 out.Allocations = make([]*structs.AllocListStub, 0) 44 } 45 for _, alloc := range out.Allocations { 46 alloc.SetEventDisplayMessages() 47 } 48 return out.Allocations, nil 49 } 50 51 func (s *HTTPServer) AllocSpecificRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 52 reqSuffix := strings.TrimPrefix(req.URL.Path, "/v1/allocation/") 53 54 // tokenize the suffix of the path to get the alloc id and find the action 55 // invoked on the alloc id 56 tokens := strings.Split(reqSuffix, "/") 57 if len(tokens) > 2 || len(tokens) < 1 { 58 return nil, CodedError(404, resourceNotFoundErr) 59 } 60 allocID := tokens[0] 61 62 if len(tokens) == 1 { 63 return s.allocGet(allocID, resp, req) 64 } 65 66 switch tokens[1] { 67 case "stop": 68 return s.allocStop(allocID, resp, req) 69 } 70 71 return nil, CodedError(404, resourceNotFoundErr) 72 } 73 74 func (s *HTTPServer) allocGet(allocID string, resp http.ResponseWriter, req *http.Request) (interface{}, error) { 75 if req.Method != "GET" { 76 return nil, CodedError(405, ErrInvalidMethod) 77 } 78 79 args := structs.AllocSpecificRequest{ 80 AllocID: allocID, 81 } 82 if s.parse(resp, req, &args.Region, &args.QueryOptions) { 83 return nil, nil 84 } 85 86 var out structs.SingleAllocResponse 87 if err := s.agent.RPC("Alloc.GetAlloc", &args, &out); err != nil { 88 return nil, err 89 } 90 91 setMeta(resp, &out.QueryMeta) 92 if out.Alloc == nil { 93 return nil, CodedError(404, "alloc not found") 94 } 95 96 // Decode the payload if there is any 97 alloc := out.Alloc 98 if alloc.Job != nil && len(alloc.Job.Payload) != 0 { 99 decoded, err := snappy.Decode(nil, alloc.Job.Payload) 100 if err != nil { 101 return nil, err 102 } 103 alloc = alloc.Copy() 104 alloc.Job.Payload = decoded 105 } 106 alloc.SetEventDisplayMessages() 107 108 return alloc, nil 109 } 110 111 func (s *HTTPServer) allocStop(allocID string, resp http.ResponseWriter, req *http.Request) (interface{}, error) { 112 if !(req.Method == "POST" || req.Method == "PUT") { 113 return nil, CodedError(405, ErrInvalidMethod) 114 } 115 116 sr := &structs.AllocStopRequest{ 117 AllocID: allocID, 118 } 119 s.parseWriteRequest(req, &sr.WriteRequest) 120 121 var out structs.AllocStopResponse 122 rpcErr := s.agent.RPC("Alloc.Stop", &sr, &out) 123 124 if rpcErr != nil { 125 if structs.IsErrUnknownAllocation(rpcErr) { 126 rpcErr = CodedError(404, allocNotFoundErr) 127 } 128 return nil, rpcErr 129 } 130 131 setIndex(resp, out.Index) 132 return &out, nil 133 } 134 135 func (s *HTTPServer) ClientAllocRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 136 reqSuffix := strings.TrimPrefix(req.URL.Path, "/v1/client/allocation/") 137 138 // tokenize the suffix of the path to get the alloc id and find the action 139 // invoked on the alloc id 140 tokens := strings.Split(reqSuffix, "/") 141 if len(tokens) != 2 { 142 return nil, CodedError(404, resourceNotFoundErr) 143 } 144 allocID := tokens[0] 145 switch tokens[1] { 146 case "stats": 147 return s.allocStats(allocID, resp, req) 148 case "exec": 149 return s.allocExec(allocID, resp, req) 150 case "snapshot": 151 if s.agent.client == nil { 152 return nil, clientNotRunning 153 } 154 return s.allocSnapshot(allocID, resp, req) 155 case "restart": 156 return s.allocRestart(allocID, resp, req) 157 case "gc": 158 return s.allocGC(allocID, resp, req) 159 case "signal": 160 return s.allocSignal(allocID, resp, req) 161 } 162 163 return nil, CodedError(404, resourceNotFoundErr) 164 } 165 166 func (s *HTTPServer) ClientGCRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 167 // Get the requested Node ID 168 requestedNode := req.URL.Query().Get("node_id") 169 170 // Build the request and parse the ACL token 171 args := structs.NodeSpecificRequest{ 172 NodeID: requestedNode, 173 } 174 s.parse(resp, req, &args.QueryOptions.Region, &args.QueryOptions) 175 176 // Determine the handler to use 177 useLocalClient, useClientRPC, useServerRPC := s.rpcHandlerForNode(requestedNode) 178 179 // Make the RPC 180 var reply structs.GenericResponse 181 var rpcErr error 182 if useLocalClient { 183 rpcErr = s.agent.Client().ClientRPC("Allocations.GarbageCollectAll", &args, &reply) 184 } else if useClientRPC { 185 rpcErr = s.agent.Client().RPC("ClientAllocations.GarbageCollectAll", &args, &reply) 186 } else if useServerRPC { 187 rpcErr = s.agent.Server().RPC("ClientAllocations.GarbageCollectAll", &args, &reply) 188 } else { 189 rpcErr = CodedError(400, "No local Node and node_id not provided") 190 } 191 192 if rpcErr != nil { 193 if structs.IsErrNoNodeConn(rpcErr) { 194 rpcErr = CodedError(404, rpcErr.Error()) 195 } 196 } 197 198 return nil, rpcErr 199 } 200 201 func (s *HTTPServer) allocRestart(allocID string, resp http.ResponseWriter, req *http.Request) (interface{}, error) { 202 // Build the request and parse the ACL token 203 args := structs.AllocRestartRequest{ 204 AllocID: allocID, 205 TaskName: "", 206 } 207 s.parse(resp, req, &args.QueryOptions.Region, &args.QueryOptions) 208 209 // Explicitly parse the body separately to disallow overriding AllocID in req Body. 210 var reqBody struct { 211 TaskName string 212 } 213 err := json.NewDecoder(req.Body).Decode(&reqBody) 214 if err != nil && err != io.EOF { 215 return nil, err 216 } 217 if reqBody.TaskName != "" { 218 args.TaskName = reqBody.TaskName 219 } 220 221 // Determine the handler to use 222 useLocalClient, useClientRPC, useServerRPC := s.rpcHandlerForAlloc(allocID) 223 224 // Make the RPC 225 var reply structs.GenericResponse 226 var rpcErr error 227 if useLocalClient { 228 rpcErr = s.agent.Client().ClientRPC("Allocations.Restart", &args, &reply) 229 } else if useClientRPC { 230 rpcErr = s.agent.Client().RPC("ClientAllocations.Restart", &args, &reply) 231 } else if useServerRPC { 232 rpcErr = s.agent.Server().RPC("ClientAllocations.Restart", &args, &reply) 233 } else { 234 rpcErr = CodedError(400, "No local Node and node_id not provided") 235 } 236 237 if rpcErr != nil { 238 if structs.IsErrNoNodeConn(rpcErr) || structs.IsErrUnknownAllocation(rpcErr) { 239 rpcErr = CodedError(404, rpcErr.Error()) 240 } 241 } 242 243 return reply, rpcErr 244 } 245 246 func (s *HTTPServer) allocGC(allocID string, resp http.ResponseWriter, req *http.Request) (interface{}, error) { 247 // Build the request and parse the ACL token 248 args := structs.AllocSpecificRequest{ 249 AllocID: allocID, 250 } 251 s.parse(resp, req, &args.QueryOptions.Region, &args.QueryOptions) 252 253 // Determine the handler to use 254 useLocalClient, useClientRPC, useServerRPC := s.rpcHandlerForAlloc(allocID) 255 256 // Make the RPC 257 var reply structs.GenericResponse 258 var rpcErr error 259 if useLocalClient { 260 rpcErr = s.agent.Client().ClientRPC("Allocations.GarbageCollect", &args, &reply) 261 } else if useClientRPC { 262 rpcErr = s.agent.Client().RPC("ClientAllocations.GarbageCollect", &args, &reply) 263 } else if useServerRPC { 264 rpcErr = s.agent.Server().RPC("ClientAllocations.GarbageCollect", &args, &reply) 265 } else { 266 rpcErr = CodedError(400, "No local Node and node_id not provided") 267 } 268 269 if rpcErr != nil { 270 if structs.IsErrNoNodeConn(rpcErr) || structs.IsErrUnknownAllocation(rpcErr) { 271 rpcErr = CodedError(404, rpcErr.Error()) 272 } 273 } 274 275 return nil, rpcErr 276 } 277 278 func (s *HTTPServer) allocSignal(allocID string, resp http.ResponseWriter, req *http.Request) (interface{}, error) { 279 if !(req.Method == "POST" || req.Method == "PUT") { 280 return nil, CodedError(405, ErrInvalidMethod) 281 } 282 283 // Build the request and parse the ACL token 284 args := structs.AllocSignalRequest{} 285 err := decodeBody(req, &args) 286 if err != nil { 287 return nil, CodedError(400, fmt.Sprintf("Failed to decode body: %v", err)) 288 } 289 s.parse(resp, req, &args.QueryOptions.Region, &args.QueryOptions) 290 args.AllocID = allocID 291 292 // Determine the handler to use 293 useLocalClient, useClientRPC, useServerRPC := s.rpcHandlerForAlloc(allocID) 294 295 // Make the RPC 296 var reply structs.GenericResponse 297 var rpcErr error 298 if useLocalClient { 299 rpcErr = s.agent.Client().ClientRPC("Allocations.Signal", &args, &reply) 300 } else if useClientRPC { 301 rpcErr = s.agent.Client().RPC("ClientAllocations.Signal", &args, &reply) 302 } else if useServerRPC { 303 rpcErr = s.agent.Server().RPC("ClientAllocations.Signal", &args, &reply) 304 } else { 305 rpcErr = CodedError(400, "No local Node and node_id not provided") 306 } 307 308 if rpcErr != nil { 309 if structs.IsErrNoNodeConn(rpcErr) || structs.IsErrUnknownAllocation(rpcErr) { 310 rpcErr = CodedError(404, rpcErr.Error()) 311 } 312 } 313 314 return reply, rpcErr 315 } 316 317 func (s *HTTPServer) allocSnapshot(allocID string, resp http.ResponseWriter, req *http.Request) (interface{}, error) { 318 var secret string 319 s.parseToken(req, &secret) 320 if !s.agent.Client().ValidateMigrateToken(allocID, secret) { 321 return nil, structs.ErrPermissionDenied 322 } 323 324 allocFS, err := s.agent.Client().GetAllocFS(allocID) 325 if err != nil { 326 return nil, fmt.Errorf(allocNotFoundErr) 327 } 328 if err := allocFS.Snapshot(resp); err != nil { 329 return nil, fmt.Errorf("error making snapshot: %v", err) 330 } 331 return nil, nil 332 } 333 334 func (s *HTTPServer) allocStats(allocID string, resp http.ResponseWriter, req *http.Request) (interface{}, error) { 335 336 // Build the request and parse the ACL token 337 task := req.URL.Query().Get("task") 338 args := cstructs.AllocStatsRequest{ 339 AllocID: allocID, 340 Task: task, 341 } 342 s.parse(resp, req, &args.QueryOptions.Region, &args.QueryOptions) 343 344 // Determine the handler to use 345 useLocalClient, useClientRPC, useServerRPC := s.rpcHandlerForAlloc(allocID) 346 347 // Make the RPC 348 var reply cstructs.AllocStatsResponse 349 var rpcErr error 350 if useLocalClient { 351 rpcErr = s.agent.Client().ClientRPC("Allocations.Stats", &args, &reply) 352 } else if useClientRPC { 353 rpcErr = s.agent.Client().RPC("ClientAllocations.Stats", &args, &reply) 354 } else if useServerRPC { 355 rpcErr = s.agent.Server().RPC("ClientAllocations.Stats", &args, &reply) 356 } else { 357 rpcErr = CodedError(400, "No local Node and node_id not provided") 358 } 359 360 if rpcErr != nil { 361 if structs.IsErrNoNodeConn(rpcErr) || structs.IsErrUnknownAllocation(rpcErr) { 362 rpcErr = CodedError(404, rpcErr.Error()) 363 } 364 } 365 366 return reply.Stats, rpcErr 367 } 368 369 func (s *HTTPServer) allocExec(allocID string, resp http.ResponseWriter, req *http.Request) (interface{}, error) { 370 // Build the request and parse the ACL token 371 task := req.URL.Query().Get("task") 372 cmdJsonStr := req.URL.Query().Get("command") 373 var command []string 374 err := json.Unmarshal([]byte(cmdJsonStr), &command) 375 if err != nil { 376 // this shouldn't happen, []string is always be serializable to json 377 return nil, fmt.Errorf("failed to marshal command into json: %v", err) 378 } 379 380 ttyB := false 381 if tty := req.URL.Query().Get("tty"); tty != "" { 382 ttyB, err = strconv.ParseBool(tty) 383 if err != nil { 384 return nil, fmt.Errorf("tty value is not a boolean: %v", err) 385 } 386 } 387 388 args := cstructs.AllocExecRequest{ 389 AllocID: allocID, 390 Task: task, 391 Cmd: command, 392 Tty: ttyB, 393 } 394 s.parse(resp, req, &args.QueryOptions.Region, &args.QueryOptions) 395 396 conn, err := s.wsUpgrader.Upgrade(resp, req, nil) 397 if err != nil { 398 return nil, fmt.Errorf("failed to upgrade connection: %v", err) 399 } 400 401 if err := readWsHandshake(conn.ReadJSON, req, &args.QueryOptions); err != nil { 402 conn.WriteMessage(websocket.CloseMessage, 403 websocket.FormatCloseMessage(toWsCode(400), err.Error())) 404 return nil, err 405 } 406 407 return s.execStreamImpl(conn, &args) 408 } 409 410 // readWsHandshake reads the websocket handshake message and sets 411 // query authentication token, if request requires a handshake 412 func readWsHandshake(readFn func(interface{}) error, req *http.Request, q *structs.QueryOptions) error { 413 414 // Avoid handshake if request doesn't require one 415 if hv := req.URL.Query().Get("ws_handshake"); hv == "" { 416 return nil 417 } else if h, err := strconv.ParseBool(hv); err != nil { 418 return fmt.Errorf("ws_handshake value is not a boolean: %v", err) 419 } else if !h { 420 return nil 421 } 422 423 var h wsHandshakeMessage 424 err := readFn(&h) 425 if err != nil { 426 return err 427 } 428 429 supportedWSHandshakeVersion := 1 430 if h.Version != supportedWSHandshakeVersion { 431 return fmt.Errorf("unexpected handshake value: %v", h.Version) 432 } 433 434 q.AuthToken = h.AuthToken 435 return nil 436 } 437 438 type wsHandshakeMessage struct { 439 Version int `json:"version"` 440 AuthToken string `json:"auth_token"` 441 } 442 443 func (s *HTTPServer) execStreamImpl(ws *websocket.Conn, args *cstructs.AllocExecRequest) (interface{}, error) { 444 allocID := args.AllocID 445 method := "Allocations.Exec" 446 447 // Get the correct handler 448 localClient, remoteClient, localServer := s.rpcHandlerForAlloc(allocID) 449 var handler structs.StreamingRpcHandler 450 var handlerErr error 451 if localClient { 452 handler, handlerErr = s.agent.Client().StreamingRpcHandler(method) 453 } else if remoteClient { 454 handler, handlerErr = s.agent.Client().RemoteStreamingRpcHandler(method) 455 } else if localServer { 456 handler, handlerErr = s.agent.Server().StreamingRpcHandler(method) 457 } 458 459 if handlerErr != nil { 460 return nil, CodedError(500, handlerErr.Error()) 461 } 462 463 // Create a pipe connecting the (possibly remote) handler to the http response 464 httpPipe, handlerPipe := net.Pipe() 465 decoder := codec.NewDecoder(httpPipe, structs.MsgpackHandle) 466 encoder := codec.NewEncoder(httpPipe, structs.MsgpackHandle) 467 468 // Create a goroutine that closes the pipe if the connection closes. 469 ctx, cancel := context.WithCancel(context.Background()) 470 go func() { 471 <-ctx.Done() 472 httpPipe.Close() 473 474 // don't close ws - wait to drain messages 475 }() 476 477 // Create a channel that decodes the results 478 errCh := make(chan HTTPCodedError, 2) 479 480 // stream response 481 go func() { 482 defer cancel() 483 484 // Send the request 485 if err := encoder.Encode(args); err != nil { 486 errCh <- CodedError(500, err.Error()) 487 return 488 } 489 490 go forwardExecInput(encoder, ws, errCh) 491 492 for { 493 select { 494 case <-ctx.Done(): 495 errCh <- nil 496 return 497 default: 498 } 499 500 var res cstructs.StreamErrWrapper 501 err := decoder.Decode(&res) 502 if isClosedError(err) { 503 ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) 504 errCh <- nil 505 return 506 } 507 508 if err != nil { 509 errCh <- CodedError(500, err.Error()) 510 return 511 } 512 decoder.Reset(httpPipe) 513 514 if err := res.Error; err != nil { 515 code := 500 516 if err.Code != nil { 517 code = int(*err.Code) 518 } 519 errCh <- CodedError(code, err.Error()) 520 return 521 } 522 523 if err := ws.WriteMessage(websocket.TextMessage, res.Payload); err != nil { 524 errCh <- CodedError(500, err.Error()) 525 return 526 } 527 } 528 }() 529 530 // start streaming request to streaming RPC - returns when streaming completes or errors 531 handler(handlerPipe) 532 // stop streaming background goroutines for streaming - but not websocket activity 533 cancel() 534 // retreieve any error and/or wait until goroutine stop and close errCh connection before 535 // closing websocket connection 536 codedErr := <-errCh 537 538 if isClosedError(codedErr) { 539 codedErr = nil 540 } else if codedErr != nil { 541 ws.WriteMessage(websocket.CloseMessage, 542 websocket.FormatCloseMessage(toWsCode(codedErr.Code()), codedErr.Error())) 543 } 544 ws.Close() 545 546 return nil, codedErr 547 } 548 549 func toWsCode(httpCode int) int { 550 switch httpCode { 551 case 500: 552 return websocket.CloseInternalServerErr 553 default: 554 // placeholder error code 555 return websocket.ClosePolicyViolation 556 } 557 } 558 559 func isClosedError(err error) bool { 560 if err == nil { 561 return false 562 } 563 564 return err == io.EOF || 565 err == io.ErrClosedPipe || 566 strings.Contains(err.Error(), "closed") || 567 strings.Contains(err.Error(), "EOF") 568 } 569 570 // forwardExecInput forwards exec input (e.g. stdin) from websocket connection 571 // to the streaming RPC connection to client 572 func forwardExecInput(encoder *codec.Encoder, ws *websocket.Conn, errCh chan<- HTTPCodedError) { 573 for { 574 sf := &drivers.ExecTaskStreamingRequestMsg{} 575 err := ws.ReadJSON(sf) 576 if err == io.EOF { 577 return 578 } 579 580 if err != nil { 581 errCh <- CodedError(500, err.Error()) 582 return 583 } 584 585 err = encoder.Encode(sf) 586 if err != nil { 587 errCh <- CodedError(500, err.Error()) 588 } 589 } 590 }