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  }