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  }