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