github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/command/agent/fs_endpoint_test.go (about) 1 package agent 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "math" 9 "net/http" 10 "net/http/httptest" 11 "os" 12 "path/filepath" 13 "reflect" 14 "strconv" 15 "testing" 16 "time" 17 18 "github.com/hashicorp/nomad/client/allocdir" 19 "github.com/hashicorp/nomad/testutil" 20 "github.com/ugorji/go/codec" 21 ) 22 23 func TestAllocDirFS_List_MissingParams(t *testing.T) { 24 httpTest(t, nil, func(s *TestServer) { 25 req, err := http.NewRequest("GET", "/v1/client/fs/ls/", nil) 26 if err != nil { 27 t.Fatalf("err: %v", err) 28 } 29 respW := httptest.NewRecorder() 30 31 _, err = s.Server.DirectoryListRequest(respW, req) 32 if err != allocIDNotPresentErr { 33 t.Fatalf("expected err: %v, actual: %v", allocIDNotPresentErr, err) 34 } 35 }) 36 } 37 38 func TestAllocDirFS_Stat_MissingParams(t *testing.T) { 39 httpTest(t, nil, func(s *TestServer) { 40 req, err := http.NewRequest("GET", "/v1/client/fs/stat/", nil) 41 if err != nil { 42 t.Fatalf("err: %v", err) 43 } 44 respW := httptest.NewRecorder() 45 46 _, err = s.Server.FileStatRequest(respW, req) 47 if err != allocIDNotPresentErr { 48 t.Fatalf("expected err: %v, actual: %v", allocIDNotPresentErr, err) 49 } 50 51 req, err = http.NewRequest("GET", "/v1/client/fs/stat/foo", nil) 52 if err != nil { 53 t.Fatalf("err: %v", err) 54 } 55 respW = httptest.NewRecorder() 56 57 _, err = s.Server.FileStatRequest(respW, req) 58 if err != fileNameNotPresentErr { 59 t.Fatalf("expected err: %v, actual: %v", allocIDNotPresentErr, err) 60 } 61 62 }) 63 } 64 65 func TestAllocDirFS_ReadAt_MissingParams(t *testing.T) { 66 httpTest(t, nil, func(s *TestServer) { 67 req, err := http.NewRequest("GET", "/v1/client/fs/readat/", nil) 68 if err != nil { 69 t.Fatalf("err: %v", err) 70 } 71 respW := httptest.NewRecorder() 72 73 _, err = s.Server.FileReadAtRequest(respW, req) 74 if err == nil { 75 t.Fatal("expected error") 76 } 77 78 req, err = http.NewRequest("GET", "/v1/client/fs/readat/foo", nil) 79 if err != nil { 80 t.Fatalf("err: %v", err) 81 } 82 respW = httptest.NewRecorder() 83 84 _, err = s.Server.FileReadAtRequest(respW, req) 85 if err == nil { 86 t.Fatal("expected error") 87 } 88 89 req, err = http.NewRequest("GET", "/v1/client/fs/readat/foo?path=/path/to/file", nil) 90 if err != nil { 91 t.Fatalf("err: %v", err) 92 } 93 respW = httptest.NewRecorder() 94 95 _, err = s.Server.FileReadAtRequest(respW, req) 96 if err == nil { 97 t.Fatal("expected error") 98 } 99 }) 100 } 101 102 type WriteCloseChecker struct { 103 io.WriteCloser 104 Closed bool 105 } 106 107 func (w *WriteCloseChecker) Close() error { 108 w.Closed = true 109 return w.WriteCloser.Close() 110 } 111 112 // This test checks, that even if the frame size has not been hit, a flush will 113 // periodically occur. 114 func TestStreamFramer_Flush(t *testing.T) { 115 // Create the stream framer 116 r, w := io.Pipe() 117 wrappedW := &WriteCloseChecker{WriteCloser: w} 118 hRate, bWindow := 100*time.Millisecond, 100*time.Millisecond 119 sf := NewStreamFramer(wrappedW, hRate, bWindow, 100) 120 sf.Run() 121 122 // Create a decoder 123 dec := codec.NewDecoder(r, jsonHandle) 124 125 f := "foo" 126 fe := "bar" 127 d := []byte{0xa} 128 o := int64(10) 129 130 // Start the reader 131 resultCh := make(chan struct{}) 132 go func() { 133 for { 134 var frame StreamFrame 135 if err := dec.Decode(&frame); err != nil { 136 t.Fatalf("failed to decode") 137 } 138 139 if frame.IsHeartbeat() { 140 continue 141 } 142 143 if reflect.DeepEqual(frame.Data, d) && frame.Offset == o && frame.File == f && frame.FileEvent == fe { 144 resultCh <- struct{}{} 145 return 146 } 147 148 } 149 }() 150 151 // Write only 1 byte so we do not hit the frame size 152 if err := sf.Send(f, fe, d, o); err != nil { 153 t.Fatalf("Send() failed %v", err) 154 } 155 156 select { 157 case <-resultCh: 158 case <-time.After(10 * time.Duration(testutil.TestMultiplier()) * bWindow): 159 t.Fatalf("failed to flush") 160 } 161 162 // Close the reader and wait. This should cause the runner to exit 163 if err := r.Close(); err != nil { 164 t.Fatalf("failed to close reader") 165 } 166 167 select { 168 case <-sf.ExitCh(): 169 case <-time.After(10 * time.Duration(testutil.TestMultiplier()) * hRate): 170 t.Fatalf("exit channel should close") 171 } 172 173 sf.Destroy() 174 if !wrappedW.Closed { 175 t.Fatalf("writer not closed") 176 } 177 } 178 179 // This test checks that frames will be batched till the frame size is hit (in 180 // the case that is before the flush). 181 func TestStreamFramer_Batch(t *testing.T) { 182 // Create the stream framer 183 r, w := io.Pipe() 184 wrappedW := &WriteCloseChecker{WriteCloser: w} 185 // Ensure the batch window doesn't get hit 186 hRate, bWindow := 100*time.Millisecond, 500*time.Millisecond 187 sf := NewStreamFramer(wrappedW, hRate, bWindow, 3) 188 sf.Run() 189 190 // Create a decoder 191 dec := codec.NewDecoder(r, jsonHandle) 192 193 f := "foo" 194 fe := "bar" 195 d := []byte{0xa, 0xb, 0xc} 196 o := int64(10) 197 198 // Start the reader 199 resultCh := make(chan struct{}) 200 go func() { 201 for { 202 var frame StreamFrame 203 if err := dec.Decode(&frame); err != nil { 204 t.Fatalf("failed to decode") 205 } 206 207 if frame.IsHeartbeat() { 208 continue 209 } 210 211 if reflect.DeepEqual(frame.Data, d) && frame.Offset == o && frame.File == f && frame.FileEvent == fe { 212 resultCh <- struct{}{} 213 return 214 } 215 } 216 }() 217 218 // Write only 1 byte so we do not hit the frame size 219 if err := sf.Send(f, fe, d[:1], o); err != nil { 220 t.Fatalf("Send() failed %v", err) 221 } 222 223 // Ensure we didn't get any data 224 select { 225 case <-resultCh: 226 t.Fatalf("Got data before frame size reached") 227 case <-time.After(bWindow / 2): 228 } 229 230 // Write the rest so we hit the frame size 231 if err := sf.Send(f, fe, d[1:], o); err != nil { 232 t.Fatalf("Send() failed %v", err) 233 } 234 235 // Ensure we get data 236 select { 237 case <-resultCh: 238 case <-time.After(10 * time.Duration(testutil.TestMultiplier()) * bWindow): 239 t.Fatalf("Did not receive data after batch size reached") 240 } 241 242 // Close the reader and wait. This should cause the runner to exit 243 if err := r.Close(); err != nil { 244 t.Fatalf("failed to close reader") 245 } 246 247 select { 248 case <-sf.ExitCh(): 249 case <-time.After(10 * time.Duration(testutil.TestMultiplier()) * hRate): 250 t.Fatalf("exit channel should close") 251 } 252 253 sf.Destroy() 254 if !wrappedW.Closed { 255 t.Fatalf("writer not closed") 256 } 257 } 258 259 func TestStreamFramer_Heartbeat(t *testing.T) { 260 // Create the stream framer 261 r, w := io.Pipe() 262 wrappedW := &WriteCloseChecker{WriteCloser: w} 263 hRate, bWindow := 100*time.Millisecond, 100*time.Millisecond 264 sf := NewStreamFramer(wrappedW, hRate, bWindow, 100) 265 sf.Run() 266 267 // Create a decoder 268 dec := codec.NewDecoder(r, jsonHandle) 269 270 // Start the reader 271 resultCh := make(chan struct{}) 272 go func() { 273 for { 274 var frame StreamFrame 275 if err := dec.Decode(&frame); err != nil { 276 t.Fatalf("failed to decode") 277 } 278 279 if frame.IsHeartbeat() { 280 resultCh <- struct{}{} 281 return 282 } 283 } 284 }() 285 286 select { 287 case <-resultCh: 288 case <-time.After(10 * time.Duration(testutil.TestMultiplier()) * hRate): 289 t.Fatalf("failed to heartbeat") 290 } 291 292 // Close the reader and wait. This should cause the runner to exit 293 if err := r.Close(); err != nil { 294 t.Fatalf("failed to close reader") 295 } 296 297 select { 298 case <-sf.ExitCh(): 299 case <-time.After(10 * time.Duration(testutil.TestMultiplier()) * hRate): 300 t.Fatalf("exit channel should close") 301 } 302 303 sf.Destroy() 304 if !wrappedW.Closed { 305 t.Fatalf("writer not closed") 306 } 307 } 308 309 // This test checks that frames are received in order 310 func TestStreamFramer_Order(t *testing.T) { 311 // Create the stream framer 312 r, w := io.Pipe() 313 wrappedW := &WriteCloseChecker{WriteCloser: w} 314 // Ensure the batch window doesn't get hit 315 hRate, bWindow := 100*time.Millisecond, 10*time.Millisecond 316 sf := NewStreamFramer(wrappedW, hRate, bWindow, 10) 317 sf.Run() 318 319 // Create a decoder 320 dec := codec.NewDecoder(r, jsonHandle) 321 322 files := []string{"1", "2", "3", "4", "5"} 323 input := bytes.NewBuffer(make([]byte, 0, 100000)) 324 for i := 0; i <= 1000; i++ { 325 str := strconv.Itoa(i) + "," 326 input.WriteString(str) 327 } 328 329 expected := bytes.NewBuffer(make([]byte, 0, 100000)) 330 for _, _ = range files { 331 expected.Write(input.Bytes()) 332 } 333 receivedBuf := bytes.NewBuffer(make([]byte, 0, 100000)) 334 335 // Start the reader 336 resultCh := make(chan struct{}) 337 go func() { 338 for { 339 var frame StreamFrame 340 if err := dec.Decode(&frame); err != nil { 341 t.Fatalf("failed to decode") 342 } 343 344 if frame.IsHeartbeat() { 345 continue 346 } 347 348 receivedBuf.Write(frame.Data) 349 350 if reflect.DeepEqual(expected, receivedBuf) { 351 resultCh <- struct{}{} 352 return 353 } 354 } 355 }() 356 357 // Send the data 358 b := input.Bytes() 359 shards := 10 360 each := len(b) / shards 361 for _, f := range files { 362 for i := 0; i < shards; i++ { 363 l, r := each*i, each*(i+1) 364 if i == shards-1 { 365 r = len(b) 366 } 367 368 if err := sf.Send(f, "", b[l:r], 0); err != nil { 369 t.Fatalf("Send() failed %v", err) 370 } 371 } 372 } 373 374 // Ensure we get data 375 select { 376 case <-resultCh: 377 case <-time.After(10 * time.Duration(testutil.TestMultiplier()) * bWindow): 378 if reflect.DeepEqual(expected, receivedBuf) { 379 got := receivedBuf.String() 380 want := expected.String() 381 t.Fatalf("Got %v; want %v", got, want) 382 } 383 } 384 385 // Close the reader and wait. This should cause the runner to exit 386 if err := r.Close(); err != nil { 387 t.Fatalf("failed to close reader") 388 } 389 390 select { 391 case <-sf.ExitCh(): 392 case <-time.After(10 * time.Duration(testutil.TestMultiplier()) * hRate): 393 t.Fatalf("exit channel should close") 394 } 395 396 sf.Destroy() 397 if !wrappedW.Closed { 398 t.Fatalf("writer not closed") 399 } 400 } 401 402 func TestHTTP_Stream_MissingParams(t *testing.T) { 403 httpTest(t, nil, func(s *TestServer) { 404 req, err := http.NewRequest("GET", "/v1/client/fs/stream/", nil) 405 if err != nil { 406 t.Fatalf("err: %v", err) 407 } 408 respW := httptest.NewRecorder() 409 410 _, err = s.Server.Stream(respW, req) 411 if err == nil { 412 t.Fatal("expected error") 413 } 414 415 req, err = http.NewRequest("GET", "/v1/client/fs/stream/foo", nil) 416 if err != nil { 417 t.Fatalf("err: %v", err) 418 } 419 respW = httptest.NewRecorder() 420 421 _, err = s.Server.Stream(respW, req) 422 if err == nil { 423 t.Fatal("expected error") 424 } 425 426 req, err = http.NewRequest("GET", "/v1/client/fs/stream/foo?path=/path/to/file", nil) 427 if err != nil { 428 t.Fatalf("err: %v", err) 429 } 430 respW = httptest.NewRecorder() 431 432 _, err = s.Server.Stream(respW, req) 433 if err == nil { 434 t.Fatal("expected error") 435 } 436 }) 437 } 438 439 // tempAllocDir returns a new alloc dir that is rooted in a temp dir. The caller 440 // should destroy the temp dir. 441 func tempAllocDir(t *testing.T) *allocdir.AllocDir { 442 dir, err := ioutil.TempDir("", "") 443 if err != nil { 444 t.Fatalf("TempDir() failed: %v", err) 445 } 446 447 if err := os.Chmod(dir, 0777); err != nil { 448 t.Fatalf("failed to chmod dir: %v", err) 449 } 450 451 return allocdir.NewAllocDir(dir) 452 } 453 454 type nopWriteCloser struct { 455 io.Writer 456 } 457 458 func (n nopWriteCloser) Close() error { 459 return nil 460 } 461 462 func TestHTTP_Stream_NoFile(t *testing.T) { 463 httpTest(t, nil, func(s *TestServer) { 464 // Get a temp alloc dir 465 ad := tempAllocDir(t) 466 defer os.RemoveAll(ad.AllocDir) 467 468 framer := NewStreamFramer(nopWriteCloser{ioutil.Discard}, streamHeartbeatRate, streamBatchWindow, streamFrameSize) 469 framer.Run() 470 defer framer.Destroy() 471 472 if err := s.Server.stream(0, "foo", ad, framer, nil); err == nil { 473 t.Fatalf("expected an error when streaming unknown file") 474 } 475 }) 476 } 477 478 func TestHTTP_Stream_Modify(t *testing.T) { 479 httpTest(t, nil, func(s *TestServer) { 480 // Get a temp alloc dir 481 ad := tempAllocDir(t) 482 defer os.RemoveAll(ad.AllocDir) 483 484 // Create a file in the temp dir 485 streamFile := "stream_file" 486 f, err := os.Create(filepath.Join(ad.AllocDir, streamFile)) 487 if err != nil { 488 t.Fatalf("Failed to create file: %v", err) 489 } 490 defer f.Close() 491 492 // Create a decoder 493 r, w := io.Pipe() 494 defer r.Close() 495 defer w.Close() 496 dec := codec.NewDecoder(r, jsonHandle) 497 498 data := []byte("helloworld") 499 500 // Start the reader 501 resultCh := make(chan struct{}) 502 go func() { 503 var collected []byte 504 for { 505 var frame StreamFrame 506 if err := dec.Decode(&frame); err != nil { 507 t.Fatalf("failed to decode: %v", err) 508 } 509 510 if frame.IsHeartbeat() { 511 continue 512 } 513 514 collected = append(collected, frame.Data...) 515 if reflect.DeepEqual(data, collected) { 516 resultCh <- struct{}{} 517 return 518 } 519 } 520 }() 521 522 // Write a few bytes 523 if _, err := f.Write(data[:3]); err != nil { 524 t.Fatalf("write failed: %v", err) 525 } 526 527 framer := NewStreamFramer(w, streamHeartbeatRate, streamBatchWindow, streamFrameSize) 528 framer.Run() 529 defer framer.Destroy() 530 531 // Start streaming 532 go func() { 533 if err := s.Server.stream(0, streamFile, ad, framer, nil); err != nil { 534 t.Fatalf("stream() failed: %v", err) 535 } 536 }() 537 538 // Sleep a little before writing more. This lets us check if the watch 539 // is working. 540 time.Sleep(1 * time.Duration(testutil.TestMultiplier()) * time.Second) 541 if _, err := f.Write(data[3:]); err != nil { 542 t.Fatalf("write failed: %v", err) 543 } 544 545 select { 546 case <-resultCh: 547 case <-time.After(10 * time.Duration(testutil.TestMultiplier()) * streamBatchWindow): 548 t.Fatalf("failed to send new data") 549 } 550 }) 551 } 552 553 func TestHTTP_Stream_Truncate(t *testing.T) { 554 httpTest(t, nil, func(s *TestServer) { 555 // Get a temp alloc dir 556 ad := tempAllocDir(t) 557 defer os.RemoveAll(ad.AllocDir) 558 559 // Create a file in the temp dir 560 streamFile := "stream_file" 561 streamFilePath := filepath.Join(ad.AllocDir, streamFile) 562 f, err := os.Create(streamFilePath) 563 if err != nil { 564 t.Fatalf("Failed to create file: %v", err) 565 } 566 defer f.Close() 567 568 // Create a decoder 569 r, w := io.Pipe() 570 defer r.Close() 571 defer w.Close() 572 dec := codec.NewDecoder(r, jsonHandle) 573 574 data := []byte("helloworld") 575 576 // Start the reader 577 truncateCh := make(chan struct{}) 578 dataPostTruncCh := make(chan struct{}) 579 go func() { 580 var collected []byte 581 for { 582 var frame StreamFrame 583 if err := dec.Decode(&frame); err != nil { 584 t.Fatalf("failed to decode: %v", err) 585 } 586 587 if frame.IsHeartbeat() { 588 continue 589 } 590 591 if frame.FileEvent == truncateEvent { 592 close(truncateCh) 593 } 594 595 collected = append(collected, frame.Data...) 596 if reflect.DeepEqual(data, collected) { 597 close(dataPostTruncCh) 598 return 599 } 600 } 601 }() 602 603 // Write a few bytes 604 if _, err := f.Write(data[:3]); err != nil { 605 t.Fatalf("write failed: %v", err) 606 } 607 608 framer := NewStreamFramer(w, streamHeartbeatRate, streamBatchWindow, streamFrameSize) 609 framer.Run() 610 defer framer.Destroy() 611 612 // Start streaming 613 go func() { 614 if err := s.Server.stream(0, streamFile, ad, framer, nil); err != nil { 615 t.Fatalf("stream() failed: %v", err) 616 } 617 }() 618 619 // Sleep a little before truncating. This lets us check if the watch 620 // is working. 621 time.Sleep(1 * time.Duration(testutil.TestMultiplier()) * time.Second) 622 if err := f.Truncate(0); err != nil { 623 t.Fatalf("truncate failed: %v", err) 624 } 625 if err := f.Sync(); err != nil { 626 t.Fatalf("sync failed: %v", err) 627 } 628 if err := f.Close(); err != nil { 629 t.Fatalf("failed to close file: %v", err) 630 } 631 632 f2, err := os.OpenFile(streamFilePath, os.O_RDWR, 0) 633 if err != nil { 634 t.Fatalf("failed to reopen file: %v", err) 635 } 636 defer f2.Close() 637 if _, err := f2.Write(data[3:5]); err != nil { 638 t.Fatalf("write failed: %v", err) 639 } 640 641 select { 642 case <-truncateCh: 643 case <-time.After(10 * time.Duration(testutil.TestMultiplier()) * streamBatchWindow): 644 t.Fatalf("did not receive truncate") 645 } 646 647 // Sleep a little before writing more. This lets us check if the watch 648 // is working. 649 time.Sleep(1 * time.Duration(testutil.TestMultiplier()) * time.Second) 650 if _, err := f2.Write(data[5:]); err != nil { 651 t.Fatalf("write failed: %v", err) 652 } 653 654 select { 655 case <-dataPostTruncCh: 656 case <-time.After(10 * time.Duration(testutil.TestMultiplier()) * streamBatchWindow): 657 t.Fatalf("did not receive post truncate data") 658 } 659 }) 660 } 661 662 func TestHTTP_Stream_Delete(t *testing.T) { 663 httpTest(t, nil, func(s *TestServer) { 664 // Get a temp alloc dir 665 ad := tempAllocDir(t) 666 defer os.RemoveAll(ad.AllocDir) 667 668 // Create a file in the temp dir 669 streamFile := "stream_file" 670 streamFilePath := filepath.Join(ad.AllocDir, streamFile) 671 f, err := os.Create(streamFilePath) 672 if err != nil { 673 t.Fatalf("Failed to create file: %v", err) 674 } 675 defer f.Close() 676 677 // Create a decoder 678 r, w := io.Pipe() 679 wrappedW := &WriteCloseChecker{WriteCloser: w} 680 defer r.Close() 681 defer w.Close() 682 dec := codec.NewDecoder(r, jsonHandle) 683 684 data := []byte("helloworld") 685 686 // Start the reader 687 deleteCh := make(chan struct{}) 688 go func() { 689 for { 690 var frame StreamFrame 691 if err := dec.Decode(&frame); err != nil { 692 t.Fatalf("failed to decode: %v", err) 693 } 694 695 if frame.IsHeartbeat() { 696 continue 697 } 698 699 if frame.FileEvent == deleteEvent { 700 close(deleteCh) 701 return 702 } 703 } 704 }() 705 706 // Write a few bytes 707 if _, err := f.Write(data[:3]); err != nil { 708 t.Fatalf("write failed: %v", err) 709 } 710 711 framer := NewStreamFramer(wrappedW, streamHeartbeatRate, streamBatchWindow, streamFrameSize) 712 framer.Run() 713 714 // Start streaming 715 go func() { 716 if err := s.Server.stream(0, streamFile, ad, framer, nil); err != nil { 717 t.Fatalf("stream() failed: %v", err) 718 } 719 }() 720 721 // Sleep a little before deleting. This lets us check if the watch 722 // is working. 723 time.Sleep(1 * time.Duration(testutil.TestMultiplier()) * time.Second) 724 if err := os.Remove(streamFilePath); err != nil { 725 t.Fatalf("delete failed: %v", err) 726 } 727 728 select { 729 case <-deleteCh: 730 case <-time.After(10 * time.Duration(testutil.TestMultiplier()) * streamBatchWindow): 731 t.Fatalf("did not receive delete") 732 } 733 734 framer.Destroy() 735 testutil.WaitForResult(func() (bool, error) { 736 return wrappedW.Closed, nil 737 }, func(err error) { 738 t.Fatalf("connection not closed") 739 }) 740 741 }) 742 } 743 744 func TestHTTP_Logs_NoFollow(t *testing.T) { 745 httpTest(t, nil, func(s *TestServer) { 746 // Get a temp alloc dir and create the log dir 747 ad := tempAllocDir(t) 748 defer os.RemoveAll(ad.AllocDir) 749 750 logDir := filepath.Join(ad.SharedDir, allocdir.LogDirName) 751 if err := os.MkdirAll(logDir, 0777); err != nil { 752 t.Fatalf("Failed to make log dir: %v", err) 753 } 754 755 // Create a series of log files in the temp dir 756 task := "foo" 757 logType := "stdout" 758 expected := []byte("012") 759 for i := 0; i < 3; i++ { 760 logFile := fmt.Sprintf("%s.%s.%d", task, logType, i) 761 logFilePath := filepath.Join(logDir, logFile) 762 err := ioutil.WriteFile(logFilePath, expected[i:i+1], 777) 763 if err != nil { 764 t.Fatalf("Failed to create file: %v", err) 765 } 766 } 767 768 // Create a decoder 769 r, w := io.Pipe() 770 wrappedW := &WriteCloseChecker{WriteCloser: w} 771 defer r.Close() 772 defer w.Close() 773 dec := codec.NewDecoder(r, jsonHandle) 774 775 var received []byte 776 777 // Start the reader 778 resultCh := make(chan struct{}) 779 go func() { 780 for { 781 var frame StreamFrame 782 if err := dec.Decode(&frame); err != nil { 783 if err == io.EOF { 784 t.Logf("EOF") 785 return 786 } 787 788 t.Fatalf("failed to decode: %v", err) 789 } 790 791 if frame.IsHeartbeat() { 792 continue 793 } 794 795 received = append(received, frame.Data...) 796 if reflect.DeepEqual(received, expected) { 797 close(resultCh) 798 return 799 } 800 } 801 }() 802 803 // Start streaming logs 804 go func() { 805 if err := s.Server.logs(false, 0, OriginStart, task, logType, ad, wrappedW); err != nil { 806 t.Fatalf("logs() failed: %v", err) 807 } 808 }() 809 810 select { 811 case <-resultCh: 812 case <-time.After(10 * time.Duration(testutil.TestMultiplier()) * streamBatchWindow): 813 t.Fatalf("did not receive data: got %q", string(received)) 814 } 815 816 testutil.WaitForResult(func() (bool, error) { 817 return wrappedW.Closed, nil 818 }, func(err error) { 819 t.Fatalf("connection not closed") 820 }) 821 822 }) 823 } 824 825 func TestHTTP_Logs_Follow(t *testing.T) { 826 httpTest(t, nil, func(s *TestServer) { 827 // Get a temp alloc dir and create the log dir 828 ad := tempAllocDir(t) 829 defer os.RemoveAll(ad.AllocDir) 830 831 logDir := filepath.Join(ad.SharedDir, allocdir.LogDirName) 832 if err := os.MkdirAll(logDir, 0777); err != nil { 833 t.Fatalf("Failed to make log dir: %v", err) 834 } 835 836 // Create a series of log files in the temp dir 837 task := "foo" 838 logType := "stdout" 839 expected := []byte("012345") 840 initialWrites := 3 841 842 writeToFile := func(index int, data []byte) { 843 logFile := fmt.Sprintf("%s.%s.%d", task, logType, index) 844 logFilePath := filepath.Join(logDir, logFile) 845 err := ioutil.WriteFile(logFilePath, data, 777) 846 if err != nil { 847 t.Fatalf("Failed to create file: %v", err) 848 } 849 } 850 for i := 0; i < initialWrites; i++ { 851 writeToFile(i, expected[i:i+1]) 852 } 853 854 // Create a decoder 855 r, w := io.Pipe() 856 wrappedW := &WriteCloseChecker{WriteCloser: w} 857 defer r.Close() 858 defer w.Close() 859 dec := codec.NewDecoder(r, jsonHandle) 860 861 var received []byte 862 863 // Start the reader 864 firstResultCh := make(chan struct{}) 865 fullResultCh := make(chan struct{}) 866 go func() { 867 for { 868 var frame StreamFrame 869 if err := dec.Decode(&frame); err != nil { 870 if err == io.EOF { 871 t.Logf("EOF") 872 return 873 } 874 875 t.Fatalf("failed to decode: %v", err) 876 } 877 878 if frame.IsHeartbeat() { 879 continue 880 } 881 882 received = append(received, frame.Data...) 883 if reflect.DeepEqual(received, expected[:initialWrites]) { 884 close(firstResultCh) 885 } else if reflect.DeepEqual(received, expected) { 886 close(fullResultCh) 887 return 888 } 889 } 890 }() 891 892 // Start streaming logs 893 go func() { 894 if err := s.Server.logs(true, 0, OriginStart, task, logType, ad, wrappedW); err != nil { 895 t.Fatalf("logs() failed: %v", err) 896 } 897 }() 898 899 select { 900 case <-firstResultCh: 901 case <-time.After(10 * time.Duration(testutil.TestMultiplier()) * streamBatchWindow): 902 t.Fatalf("did not receive data: got %q", string(received)) 903 } 904 905 // We got the first chunk of data, write out the rest to the next file 906 // at an index much ahead to check that it is following and detecting 907 // skips 908 skipTo := initialWrites + 10 909 writeToFile(skipTo, expected[initialWrites:]) 910 911 select { 912 case <-fullResultCh: 913 case <-time.After(10 * time.Duration(testutil.TestMultiplier()) * streamBatchWindow): 914 t.Fatalf("did not receive data: got %q", string(received)) 915 } 916 917 // Close the reader 918 r.Close() 919 920 testutil.WaitForResult(func() (bool, error) { 921 return wrappedW.Closed, nil 922 }, func(err error) { 923 t.Fatalf("connection not closed") 924 }) 925 }) 926 } 927 928 func TestLogs_findClosest(t *testing.T) { 929 task := "foo" 930 entries := []*allocdir.AllocFileInfo{ 931 { 932 Name: "foo.stdout.0", 933 Size: 100, 934 }, 935 { 936 Name: "foo.stdout.1", 937 Size: 100, 938 }, 939 { 940 Name: "foo.stdout.2", 941 Size: 100, 942 }, 943 { 944 Name: "foo.stdout.3", 945 Size: 100, 946 }, 947 { 948 Name: "foo.stderr.0", 949 Size: 100, 950 }, 951 { 952 Name: "foo.stderr.1", 953 Size: 100, 954 }, 955 { 956 Name: "foo.stderr.2", 957 Size: 100, 958 }, 959 } 960 961 cases := []struct { 962 Entries []*allocdir.AllocFileInfo 963 DesiredIdx int64 964 DesiredOffset int64 965 Task string 966 LogType string 967 ExpectedFile string 968 ExpectedIdx int64 969 ExpectedOffset int64 970 Error bool 971 }{ 972 // Test error cases 973 { 974 Entries: nil, 975 DesiredIdx: 0, 976 Task: task, 977 LogType: "stdout", 978 Error: true, 979 }, 980 { 981 Entries: entries[0:3], 982 DesiredIdx: 0, 983 Task: task, 984 LogType: "stderr", 985 Error: true, 986 }, 987 988 // Test begining cases 989 { 990 Entries: entries, 991 DesiredIdx: 0, 992 Task: task, 993 LogType: "stdout", 994 ExpectedFile: entries[0].Name, 995 ExpectedIdx: 0, 996 }, 997 { 998 // Desired offset should be ignored at edges 999 Entries: entries, 1000 DesiredIdx: 0, 1001 DesiredOffset: -100, 1002 Task: task, 1003 LogType: "stdout", 1004 ExpectedFile: entries[0].Name, 1005 ExpectedIdx: 0, 1006 ExpectedOffset: 0, 1007 }, 1008 { 1009 // Desired offset should be ignored at edges 1010 Entries: entries, 1011 DesiredIdx: 1, 1012 DesiredOffset: -1000, 1013 Task: task, 1014 LogType: "stdout", 1015 ExpectedFile: entries[0].Name, 1016 ExpectedIdx: 0, 1017 ExpectedOffset: 0, 1018 }, 1019 { 1020 Entries: entries, 1021 DesiredIdx: 0, 1022 Task: task, 1023 LogType: "stderr", 1024 ExpectedFile: entries[4].Name, 1025 ExpectedIdx: 0, 1026 }, 1027 { 1028 Entries: entries, 1029 DesiredIdx: 0, 1030 Task: task, 1031 LogType: "stdout", 1032 ExpectedFile: entries[0].Name, 1033 ExpectedIdx: 0, 1034 }, 1035 1036 // Test middle cases 1037 { 1038 Entries: entries, 1039 DesiredIdx: 1, 1040 Task: task, 1041 LogType: "stdout", 1042 ExpectedFile: entries[1].Name, 1043 ExpectedIdx: 1, 1044 }, 1045 { 1046 Entries: entries, 1047 DesiredIdx: 1, 1048 DesiredOffset: 10, 1049 Task: task, 1050 LogType: "stdout", 1051 ExpectedFile: entries[1].Name, 1052 ExpectedIdx: 1, 1053 ExpectedOffset: 10, 1054 }, 1055 { 1056 Entries: entries, 1057 DesiredIdx: 1, 1058 DesiredOffset: 110, 1059 Task: task, 1060 LogType: "stdout", 1061 ExpectedFile: entries[2].Name, 1062 ExpectedIdx: 2, 1063 ExpectedOffset: 10, 1064 }, 1065 { 1066 Entries: entries, 1067 DesiredIdx: 1, 1068 Task: task, 1069 LogType: "stderr", 1070 ExpectedFile: entries[5].Name, 1071 ExpectedIdx: 1, 1072 }, 1073 // Test end cases 1074 { 1075 Entries: entries, 1076 DesiredIdx: math.MaxInt64, 1077 Task: task, 1078 LogType: "stdout", 1079 ExpectedFile: entries[3].Name, 1080 ExpectedIdx: 3, 1081 }, 1082 { 1083 Entries: entries, 1084 DesiredIdx: math.MaxInt64, 1085 DesiredOffset: math.MaxInt64, 1086 Task: task, 1087 LogType: "stdout", 1088 ExpectedFile: entries[3].Name, 1089 ExpectedIdx: 3, 1090 ExpectedOffset: 100, 1091 }, 1092 { 1093 Entries: entries, 1094 DesiredIdx: math.MaxInt64, 1095 DesiredOffset: -10, 1096 Task: task, 1097 LogType: "stdout", 1098 ExpectedFile: entries[3].Name, 1099 ExpectedIdx: 3, 1100 ExpectedOffset: 90, 1101 }, 1102 { 1103 Entries: entries, 1104 DesiredIdx: math.MaxInt64, 1105 Task: task, 1106 LogType: "stderr", 1107 ExpectedFile: entries[6].Name, 1108 ExpectedIdx: 2, 1109 }, 1110 } 1111 1112 for i, c := range cases { 1113 entry, idx, offset, err := findClosest(c.Entries, c.DesiredIdx, c.DesiredOffset, c.Task, c.LogType) 1114 if err != nil { 1115 if !c.Error { 1116 t.Fatalf("case %d: Unexpected error: %v", i, err) 1117 } 1118 continue 1119 } 1120 1121 if entry.Name != c.ExpectedFile { 1122 t.Fatalf("case %d: Got file %q; want %q", i, entry.Name, c.ExpectedFile) 1123 } 1124 if idx != c.ExpectedIdx { 1125 t.Fatalf("case %d: Got index %d; want %d", i, idx, c.ExpectedIdx) 1126 } 1127 if offset != c.ExpectedOffset { 1128 t.Fatalf("case %d: Got offset %d; want %d", i, offset, c.ExpectedOffset) 1129 } 1130 } 1131 }