github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/command/agent/fs_endpoint.go (about)

     1  package agent
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"math"
     8  	"net"
     9  	"net/http"
    10  	"os"
    11  	"path/filepath"
    12  	"sort"
    13  	"strconv"
    14  	"strings"
    15  	"sync"
    16  	"syscall"
    17  	"time"
    18  
    19  	"gopkg.in/tomb.v1"
    20  
    21  	"github.com/docker/docker/pkg/ioutils"
    22  	"github.com/hashicorp/nomad/client/allocdir"
    23  	"github.com/hpcloud/tail/watch"
    24  	"github.com/ugorji/go/codec"
    25  )
    26  
    27  var (
    28  	allocIDNotPresentErr  = fmt.Errorf("must provide a valid alloc id")
    29  	fileNameNotPresentErr = fmt.Errorf("must provide a file name")
    30  	taskNotPresentErr     = fmt.Errorf("must provide task name")
    31  	logTypeNotPresentErr  = fmt.Errorf("must provide log type (stdout/stderr)")
    32  	clientNotRunning      = fmt.Errorf("node is not running a Nomad Client")
    33  	invalidOrigin         = fmt.Errorf("origin must be start or end")
    34  )
    35  
    36  const (
    37  	// streamFrameSize is the maximum number of bytes to send in a single frame
    38  	streamFrameSize = 64 * 1024
    39  
    40  	// streamHeartbeatRate is the rate at which a heartbeat will occur to detect
    41  	// a closed connection without sending any additional data
    42  	streamHeartbeatRate = 1 * time.Second
    43  
    44  	// streamBatchWindow is the window in which file content is batched before
    45  	// being flushed if the frame size has not been hit.
    46  	streamBatchWindow = 200 * time.Millisecond
    47  
    48  	// nextLogCheckRate is the rate at which we check for a log entry greater
    49  	// than what we are watching for. This is to handle the case in which logs
    50  	// rotate faster than we can detect and we have to rely on a normal
    51  	// directory listing.
    52  	nextLogCheckRate = 100 * time.Millisecond
    53  
    54  	// deleteEvent and truncateEvent are the file events that can be sent in a
    55  	// StreamFrame
    56  	deleteEvent   = "file deleted"
    57  	truncateEvent = "file truncated"
    58  
    59  	// OriginStart and OriginEnd are the available parameters for the origin
    60  	// argument when streaming a file. They respectively offset from the start
    61  	// and end of a file.
    62  	OriginStart = "start"
    63  	OriginEnd   = "end"
    64  )
    65  
    66  func (s *HTTPServer) FsRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
    67  	if s.agent.client == nil {
    68  		return nil, clientNotRunning
    69  	}
    70  
    71  	path := strings.TrimPrefix(req.URL.Path, "/v1/client/fs/")
    72  	switch {
    73  	case strings.HasPrefix(path, "ls/"):
    74  		return s.DirectoryListRequest(resp, req)
    75  	case strings.HasPrefix(path, "stat/"):
    76  		return s.FileStatRequest(resp, req)
    77  	case strings.HasPrefix(path, "readat/"):
    78  		return s.FileReadAtRequest(resp, req)
    79  	case strings.HasPrefix(path, "cat/"):
    80  		return s.FileCatRequest(resp, req)
    81  	case strings.HasPrefix(path, "stream/"):
    82  		return s.Stream(resp, req)
    83  	case strings.HasPrefix(path, "logs/"):
    84  		return s.Logs(resp, req)
    85  	default:
    86  		return nil, CodedError(404, ErrInvalidMethod)
    87  	}
    88  }
    89  
    90  func (s *HTTPServer) DirectoryListRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
    91  	var allocID, path string
    92  
    93  	if allocID = strings.TrimPrefix(req.URL.Path, "/v1/client/fs/ls/"); allocID == "" {
    94  		return nil, allocIDNotPresentErr
    95  	}
    96  	if path = req.URL.Query().Get("path"); path == "" {
    97  		path = "/"
    98  	}
    99  	fs, err := s.agent.client.GetAllocFS(allocID)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  	return fs.List(path)
   104  }
   105  
   106  func (s *HTTPServer) FileStatRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   107  	var allocID, path string
   108  	if allocID = strings.TrimPrefix(req.URL.Path, "/v1/client/fs/stat/"); allocID == "" {
   109  		return nil, allocIDNotPresentErr
   110  	}
   111  	if path = req.URL.Query().Get("path"); path == "" {
   112  		return nil, fileNameNotPresentErr
   113  	}
   114  	fs, err := s.agent.client.GetAllocFS(allocID)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  	return fs.Stat(path)
   119  }
   120  
   121  func (s *HTTPServer) FileReadAtRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   122  	var allocID, path string
   123  	var offset, limit int64
   124  	var err error
   125  
   126  	q := req.URL.Query()
   127  
   128  	if allocID = strings.TrimPrefix(req.URL.Path, "/v1/client/fs/readat/"); allocID == "" {
   129  		return nil, allocIDNotPresentErr
   130  	}
   131  	if path = q.Get("path"); path == "" {
   132  		return nil, fileNameNotPresentErr
   133  	}
   134  
   135  	if offset, err = strconv.ParseInt(q.Get("offset"), 10, 64); err != nil {
   136  		return nil, fmt.Errorf("error parsing offset: %v", err)
   137  	}
   138  
   139  	// Parse the limit
   140  	if limitStr := q.Get("limit"); limitStr != "" {
   141  		if limit, err = strconv.ParseInt(limitStr, 10, 64); err != nil {
   142  			return nil, fmt.Errorf("error parsing limit: %v", err)
   143  		}
   144  	}
   145  
   146  	fs, err := s.agent.client.GetAllocFS(allocID)
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  
   151  	rc, err := fs.ReadAt(path, offset)
   152  	if limit > 0 {
   153  		rc = &ReadCloserWrapper{
   154  			Reader: io.LimitReader(rc, limit),
   155  			Closer: rc,
   156  		}
   157  	}
   158  
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  
   163  	io.Copy(resp, rc)
   164  	return nil, rc.Close()
   165  }
   166  
   167  // ReadCloserWrapper wraps a LimitReader so that a file is closed once it has been
   168  // read
   169  type ReadCloserWrapper struct {
   170  	io.Reader
   171  	io.Closer
   172  }
   173  
   174  func (s *HTTPServer) FileCatRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   175  	var allocID, path string
   176  	var err error
   177  
   178  	q := req.URL.Query()
   179  
   180  	if allocID = strings.TrimPrefix(req.URL.Path, "/v1/client/fs/cat/"); allocID == "" {
   181  		return nil, allocIDNotPresentErr
   182  	}
   183  	if path = q.Get("path"); path == "" {
   184  		return nil, fileNameNotPresentErr
   185  	}
   186  	fs, err := s.agent.client.GetAllocFS(allocID)
   187  	if err != nil {
   188  		return nil, err
   189  	}
   190  
   191  	fileInfo, err := fs.Stat(path)
   192  	if err != nil {
   193  		return nil, err
   194  	}
   195  	if fileInfo.IsDir {
   196  		return nil, fmt.Errorf("file %q is a directory", path)
   197  	}
   198  
   199  	r, err := fs.ReadAt(path, int64(0))
   200  	if err != nil {
   201  		return nil, err
   202  	}
   203  	io.Copy(resp, r)
   204  	return nil, r.Close()
   205  }
   206  
   207  // StreamFrame is used to frame data of a file when streaming
   208  type StreamFrame struct {
   209  	// Offset is the offset the data was read from
   210  	Offset int64 `json:",omitempty"`
   211  
   212  	// Data is the read data
   213  	Data []byte `json:",omitempty"`
   214  
   215  	// File is the file that the data was read from
   216  	File string `json:",omitempty"`
   217  
   218  	// FileEvent is the last file event that occurred that could cause the
   219  	// streams position to change or end
   220  	FileEvent string `json:",omitempty"`
   221  }
   222  
   223  // IsHeartbeat returns if the frame is a heartbeat frame
   224  func (s *StreamFrame) IsHeartbeat() bool {
   225  	return s.Offset == 0 && len(s.Data) == 0 && s.File == "" && s.FileEvent == ""
   226  }
   227  
   228  // StreamFramer is used to buffer and send frames as well as heartbeat.
   229  type StreamFramer struct {
   230  	out        io.WriteCloser
   231  	enc        *codec.Encoder
   232  	frameSize  int
   233  	heartbeat  *time.Ticker
   234  	flusher    *time.Ticker
   235  	shutdownCh chan struct{}
   236  	exitCh     chan struct{}
   237  
   238  	outbound chan *StreamFrame
   239  
   240  	// The mutex protects everything below
   241  	l sync.Mutex
   242  
   243  	// The current working frame
   244  	f    *StreamFrame
   245  	data *bytes.Buffer
   246  
   247  	// Captures whether the framer is running and any error that occurred to
   248  	// cause it to stop.
   249  	running bool
   250  	Err     error
   251  }
   252  
   253  // NewStreamFramer creates a new stream framer that will output StreamFrames to
   254  // the passed output.
   255  func NewStreamFramer(out io.WriteCloser, heartbeatRate, batchWindow time.Duration, frameSize int) *StreamFramer {
   256  	// Create a JSON encoder
   257  	enc := codec.NewEncoder(out, jsonHandle)
   258  
   259  	// Create the heartbeat and flush ticker
   260  	heartbeat := time.NewTicker(heartbeatRate)
   261  	flusher := time.NewTicker(batchWindow)
   262  
   263  	return &StreamFramer{
   264  		out:        out,
   265  		enc:        enc,
   266  		frameSize:  frameSize,
   267  		heartbeat:  heartbeat,
   268  		flusher:    flusher,
   269  		outbound:   make(chan *StreamFrame),
   270  		data:       bytes.NewBuffer(make([]byte, 0, 2*frameSize)),
   271  		shutdownCh: make(chan struct{}),
   272  		exitCh:     make(chan struct{}),
   273  	}
   274  }
   275  
   276  // Destroy is used to cleanup the StreamFramer and flush any pending frames
   277  func (s *StreamFramer) Destroy() {
   278  	s.l.Lock()
   279  	close(s.shutdownCh)
   280  	s.heartbeat.Stop()
   281  	s.flusher.Stop()
   282  	s.l.Unlock()
   283  
   284  	// Ensure things were flushed
   285  	if s.running {
   286  		<-s.exitCh
   287  	}
   288  	s.out.Close()
   289  }
   290  
   291  // Run starts a long lived goroutine that handles sending data as well as
   292  // heartbeating
   293  func (s *StreamFramer) Run() {
   294  	s.l.Lock()
   295  	defer s.l.Unlock()
   296  	if s.running {
   297  		return
   298  	}
   299  
   300  	s.running = true
   301  	go s.run()
   302  }
   303  
   304  // ExitCh returns a channel that will be closed when the run loop terminates.
   305  func (s *StreamFramer) ExitCh() <-chan struct{} {
   306  	return s.exitCh
   307  }
   308  
   309  // run is the internal run method. It exits if Destroy is called or an error
   310  // occurs, in which case the exit channel is closed.
   311  func (s *StreamFramer) run() {
   312  	// Store any error and mark it as not running
   313  	var err error
   314  	defer func() {
   315  		close(s.exitCh)
   316  
   317  		s.l.Lock()
   318  		close(s.outbound)
   319  		s.Err = err
   320  		s.running = false
   321  		s.l.Unlock()
   322  	}()
   323  
   324  	// Start a heartbeat/flusher go-routine. This is done seprately to avoid blocking
   325  	// the outbound channel.
   326  	go func() {
   327  		for {
   328  			select {
   329  			case <-s.exitCh:
   330  				return
   331  			case <-s.shutdownCh:
   332  				return
   333  			case <-s.flusher.C:
   334  				// Skip if there is nothing to flush
   335  				s.l.Lock()
   336  				if s.f == nil {
   337  					s.l.Unlock()
   338  					continue
   339  				}
   340  
   341  				// Read the data for the frame, and send it
   342  				s.f.Data = s.readData()
   343  				select {
   344  				case s.outbound <- s.f:
   345  					s.f = nil
   346  				case <-s.exitCh:
   347  				}
   348  				s.l.Unlock()
   349  			case <-s.heartbeat.C:
   350  				// Send a heartbeat frame
   351  				s.l.Lock()
   352  				select {
   353  				case s.outbound <- &StreamFrame{}:
   354  				default:
   355  				}
   356  				s.l.Unlock()
   357  			}
   358  		}
   359  	}()
   360  
   361  OUTER:
   362  	for {
   363  		select {
   364  		case <-s.shutdownCh:
   365  			break OUTER
   366  		case o := <-s.outbound:
   367  			// Send the frame
   368  			if err = s.enc.Encode(o); err != nil {
   369  				return
   370  			}
   371  		}
   372  	}
   373  
   374  	// Flush any existing frames
   375  FLUSH:
   376  	for {
   377  		select {
   378  		case o := <-s.outbound:
   379  			// Send the frame and then clear the current working frame
   380  			if err = s.enc.Encode(o); err != nil {
   381  				return
   382  			}
   383  		default:
   384  			break FLUSH
   385  		}
   386  	}
   387  
   388  	s.l.Lock()
   389  	if s.f != nil {
   390  		s.f.Data = s.readData()
   391  		s.enc.Encode(s.f)
   392  	}
   393  	s.l.Unlock()
   394  }
   395  
   396  // readData is a helper which reads the buffered data returning up to the frame
   397  // size of data. Must be called with the lock held. The returned value is
   398  // invalid on the next read or write into the StreamFramer buffer
   399  func (s *StreamFramer) readData() []byte {
   400  	// Compute the amount to read from the buffer
   401  	size := s.data.Len()
   402  	if size > s.frameSize {
   403  		size = s.frameSize
   404  	}
   405  	if size == 0 {
   406  		return nil
   407  	}
   408  	d := s.data.Next(size)
   409  	b := make([]byte, size)
   410  	copy(b, d)
   411  	return b
   412  }
   413  
   414  // Send creates and sends a StreamFrame based on the passed parameters. An error
   415  // is returned if the run routine hasn't run or encountered an error. Send is
   416  // asyncronous and does not block for the data to be transferred.
   417  func (s *StreamFramer) Send(file, fileEvent string, data []byte, offset int64) error {
   418  	s.l.Lock()
   419  	defer s.l.Unlock()
   420  
   421  	// If we are not running, return the error that caused us to not run or
   422  	// indicated that it was never started.
   423  	if !s.running {
   424  		if s.Err != nil {
   425  			return s.Err
   426  		}
   427  		return fmt.Errorf("StreamFramer not running")
   428  	}
   429  
   430  	// Check if not mergeable
   431  	if s.f != nil && (s.f.File != file || s.f.FileEvent != fileEvent) {
   432  		// Flush the old frame
   433  		f := *s.f
   434  		f.Data = s.readData()
   435  		select {
   436  		case <-s.exitCh:
   437  			return nil
   438  		case s.outbound <- &f:
   439  			s.f = nil
   440  		}
   441  	}
   442  
   443  	// Store the new data as the current frame.
   444  	if s.f == nil {
   445  		s.f = &StreamFrame{
   446  			Offset:    offset,
   447  			File:      file,
   448  			FileEvent: fileEvent,
   449  		}
   450  	}
   451  
   452  	// Write the data to the buffer
   453  	s.data.Write(data)
   454  
   455  	// Handle the delete case in which there is no data
   456  	if s.data.Len() == 0 && s.f.FileEvent != "" {
   457  		select {
   458  		case <-s.exitCh:
   459  			return nil
   460  		case s.outbound <- &StreamFrame{
   461  			Offset:    s.f.Offset,
   462  			File:      s.f.File,
   463  			FileEvent: s.f.FileEvent,
   464  		}:
   465  		}
   466  	}
   467  
   468  	// Flush till we are under the max frame size
   469  	for s.data.Len() >= s.frameSize {
   470  		// Create a new frame to send it
   471  		d := s.readData()
   472  		select {
   473  		case <-s.exitCh:
   474  			return nil
   475  		case s.outbound <- &StreamFrame{
   476  			Offset:    s.f.Offset,
   477  			File:      s.f.File,
   478  			FileEvent: s.f.FileEvent,
   479  			Data:      d,
   480  		}:
   481  		}
   482  	}
   483  
   484  	if s.data.Len() == 0 {
   485  		s.f = nil
   486  	}
   487  
   488  	return nil
   489  }
   490  
   491  // Stream streams the content of a file blocking on EOF.
   492  // The parameters are:
   493  // * path: path to file to stream.
   494  // * offset: The offset to start streaming data at, defaults to zero.
   495  // * origin: Either "start" or "end" and defines from where the offset is
   496  //           applied. Defaults to "start".
   497  func (s *HTTPServer) Stream(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   498  	var allocID, path string
   499  	var err error
   500  
   501  	q := req.URL.Query()
   502  
   503  	if allocID = strings.TrimPrefix(req.URL.Path, "/v1/client/fs/stream/"); allocID == "" {
   504  		return nil, allocIDNotPresentErr
   505  	}
   506  
   507  	if path = q.Get("path"); path == "" {
   508  		return nil, fileNameNotPresentErr
   509  	}
   510  
   511  	var offset int64
   512  	offsetString := q.Get("offset")
   513  	if offsetString != "" {
   514  		var err error
   515  		if offset, err = strconv.ParseInt(offsetString, 10, 64); err != nil {
   516  			return nil, fmt.Errorf("error parsing offset: %v", err)
   517  		}
   518  	}
   519  
   520  	origin := q.Get("origin")
   521  	switch origin {
   522  	case "start", "end":
   523  	case "":
   524  		origin = "start"
   525  	default:
   526  		return nil, invalidOrigin
   527  	}
   528  
   529  	fs, err := s.agent.client.GetAllocFS(allocID)
   530  	if err != nil {
   531  		return nil, err
   532  	}
   533  
   534  	fileInfo, err := fs.Stat(path)
   535  	if err != nil {
   536  		return nil, err
   537  	}
   538  	if fileInfo.IsDir {
   539  		return nil, fmt.Errorf("file %q is a directory", path)
   540  	}
   541  
   542  	// If offsetting from the end subtract from the size
   543  	if origin == "end" {
   544  		offset = fileInfo.Size - offset
   545  
   546  	}
   547  
   548  	// Create an output that gets flushed on every write
   549  	output := ioutils.NewWriteFlusher(resp)
   550  
   551  	// Create the framer
   552  	framer := NewStreamFramer(output, streamHeartbeatRate, streamBatchWindow, streamFrameSize)
   553  	framer.Run()
   554  	defer framer.Destroy()
   555  
   556  	err = s.stream(offset, path, fs, framer, nil)
   557  	if err != nil && err != syscall.EPIPE {
   558  		return nil, err
   559  	}
   560  
   561  	return nil, nil
   562  }
   563  
   564  // stream is the internal method to stream the content of a file. eofCancelCh is
   565  // used to cancel the stream if triggered while at EOF. If the connection is
   566  // broken an EPIPE error is returned
   567  func (s *HTTPServer) stream(offset int64, path string,
   568  	fs allocdir.AllocDirFS, framer *StreamFramer,
   569  	eofCancelCh chan error) error {
   570  
   571  	// Get the reader
   572  	f, err := fs.ReadAt(path, offset)
   573  	if err != nil {
   574  		return err
   575  	}
   576  	defer f.Close()
   577  
   578  	// Create a tomb to cancel watch events
   579  	t := tomb.Tomb{}
   580  	defer func() {
   581  		t.Kill(nil)
   582  		t.Done()
   583  	}()
   584  
   585  	// Create a variable to allow setting the last event
   586  	var lastEvent string
   587  
   588  	// Only create the file change watcher once. But we need to do it after we
   589  	// read and reach EOF.
   590  	var changes *watch.FileChanges
   591  
   592  	// Start streaming the data
   593  	data := make([]byte, streamFrameSize)
   594  OUTER:
   595  	for {
   596  		// Read up to the max frame size
   597  		n, readErr := f.Read(data)
   598  
   599  		// Update the offset
   600  		offset += int64(n)
   601  
   602  		// Return non-EOF errors
   603  		if readErr != nil && readErr != io.EOF {
   604  			return readErr
   605  		}
   606  
   607  		// Send the frame
   608  		if n != 0 {
   609  			if err := framer.Send(path, lastEvent, data[:n], offset); err != nil {
   610  
   611  				// Check if the connection has been closed
   612  				if err == io.ErrClosedPipe {
   613  					// The pipe check is for tests
   614  					return syscall.EPIPE
   615  				}
   616  
   617  				operr, ok := err.(*net.OpError)
   618  				if ok {
   619  					// The connection was closed by our peer
   620  					e := operr.Err.Error()
   621  					if strings.Contains(e, syscall.EPIPE.Error()) || strings.Contains(e, syscall.ECONNRESET.Error()) {
   622  						return syscall.EPIPE
   623  					}
   624  				}
   625  
   626  				return err
   627  			}
   628  		}
   629  
   630  		// Clear the last event
   631  		if lastEvent != "" {
   632  			lastEvent = ""
   633  		}
   634  
   635  		// Just keep reading
   636  		if readErr == nil {
   637  			continue
   638  		}
   639  
   640  		// If EOF is hit, wait for a change to the file
   641  		if changes == nil {
   642  			changes, err = fs.ChangeEvents(path, offset, &t)
   643  			if err != nil {
   644  				return err
   645  			}
   646  		}
   647  
   648  		for {
   649  			select {
   650  			case <-changes.Modified:
   651  				continue OUTER
   652  			case <-changes.Deleted:
   653  				return framer.Send(path, deleteEvent, nil, offset)
   654  			case <-changes.Truncated:
   655  				// Close the current reader
   656  				if err := f.Close(); err != nil {
   657  					return err
   658  				}
   659  
   660  				// Get a new reader at offset zero
   661  				offset = 0
   662  				var err error
   663  				f, err = fs.ReadAt(path, offset)
   664  				if err != nil {
   665  					return err
   666  				}
   667  				defer f.Close()
   668  
   669  				// Store the last event
   670  				lastEvent = truncateEvent
   671  				continue OUTER
   672  			case <-framer.ExitCh():
   673  				return nil
   674  			case err, ok := <-eofCancelCh:
   675  				if !ok {
   676  					return nil
   677  				}
   678  
   679  				return err
   680  			}
   681  		}
   682  	}
   683  
   684  	return nil
   685  }
   686  
   687  // Logs streams the content of a log blocking on EOF. The parameters are:
   688  // * task: task name to stream logs for.
   689  // * type: stdout/stderr to stream.
   690  // * follow: A boolean of whether to follow the logs.
   691  // * offset: The offset to start streaming data at, defaults to zero.
   692  // * origin: Either "start" or "end" and defines from where the offset is
   693  //           applied. Defaults to "start".
   694  func (s *HTTPServer) Logs(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   695  	var allocID, task, logType string
   696  	var follow bool
   697  	var err error
   698  
   699  	q := req.URL.Query()
   700  
   701  	if allocID = strings.TrimPrefix(req.URL.Path, "/v1/client/fs/logs/"); allocID == "" {
   702  		return nil, allocIDNotPresentErr
   703  	}
   704  
   705  	if task = q.Get("task"); task == "" {
   706  		return nil, taskNotPresentErr
   707  	}
   708  
   709  	if follow, err = strconv.ParseBool(q.Get("follow")); err != nil {
   710  		return nil, fmt.Errorf("Failed to parse follow field to boolean: %v", err)
   711  	}
   712  
   713  	logType = q.Get("type")
   714  	switch logType {
   715  	case "stdout", "stderr":
   716  	default:
   717  		return nil, logTypeNotPresentErr
   718  	}
   719  
   720  	var offset int64
   721  	offsetString := q.Get("offset")
   722  	if offsetString != "" {
   723  		var err error
   724  		if offset, err = strconv.ParseInt(offsetString, 10, 64); err != nil {
   725  			return nil, fmt.Errorf("error parsing offset: %v", err)
   726  		}
   727  	}
   728  
   729  	origin := q.Get("origin")
   730  	switch origin {
   731  	case "start", "end":
   732  	case "":
   733  		origin = "start"
   734  	default:
   735  		return nil, invalidOrigin
   736  	}
   737  
   738  	fs, err := s.agent.client.GetAllocFS(allocID)
   739  	if err != nil {
   740  		return nil, err
   741  	}
   742  
   743  	// Create an output that gets flushed on every write
   744  	output := ioutils.NewWriteFlusher(resp)
   745  
   746  	return nil, s.logs(follow, offset, origin, task, logType, fs, output)
   747  }
   748  
   749  func (s *HTTPServer) logs(follow bool, offset int64,
   750  	origin, task, logType string,
   751  	fs allocdir.AllocDirFS, output io.WriteCloser) error {
   752  
   753  	// Create the framer
   754  	framer := NewStreamFramer(output, streamHeartbeatRate, streamBatchWindow, streamFrameSize)
   755  	framer.Run()
   756  	defer framer.Destroy()
   757  
   758  	// Path to the logs
   759  	logPath := filepath.Join(allocdir.SharedAllocName, allocdir.LogDirName)
   760  
   761  	// nextIdx is the next index to read logs from
   762  	var nextIdx int64
   763  	switch origin {
   764  	case "start":
   765  		nextIdx = 0
   766  	case "end":
   767  		nextIdx = math.MaxInt64
   768  		offset *= -1
   769  	default:
   770  		return invalidOrigin
   771  	}
   772  
   773  	// Create a tomb to cancel watch events
   774  	t := tomb.Tomb{}
   775  	defer func() {
   776  		t.Kill(nil)
   777  		t.Done()
   778  	}()
   779  
   780  	for {
   781  		// Logic for picking next file is:
   782  		// 1) List log files
   783  		// 2) Pick log file closest to desired index
   784  		// 3) Open log file at correct offset
   785  		// 3a) No error, read contents
   786  		// 3b) If file doesn't exist, goto 1 as it may have been rotated out
   787  		entries, err := fs.List(logPath)
   788  		if err != nil {
   789  			return fmt.Errorf("failed to list entries: %v", err)
   790  		}
   791  
   792  		// If we are not following logs, determine the max index for the logs we are
   793  		// interested in so we can stop there.
   794  		maxIndex := int64(math.MaxInt64)
   795  		if !follow {
   796  			_, idx, _, err := findClosest(entries, maxIndex, 0, task, logType)
   797  			if err != nil {
   798  				return err
   799  			}
   800  			maxIndex = idx
   801  		}
   802  
   803  		logEntry, idx, openOffset, err := findClosest(entries, nextIdx, offset, task, logType)
   804  		if err != nil {
   805  			return err
   806  		}
   807  
   808  		var eofCancelCh chan error
   809  		exitAfter := false
   810  		if !follow && idx > maxIndex {
   811  			// Exceeded what was there initially so return
   812  			return nil
   813  		} else if !follow && idx == maxIndex {
   814  			// At the end
   815  			eofCancelCh = make(chan error)
   816  			close(eofCancelCh)
   817  			exitAfter = true
   818  		} else {
   819  			eofCancelCh = blockUntilNextLog(fs, &t, logPath, task, logType, idx+1)
   820  		}
   821  
   822  		p := filepath.Join(logPath, logEntry.Name)
   823  		err = s.stream(openOffset, p, fs, framer, eofCancelCh)
   824  
   825  		if err != nil {
   826  			// Check if there was an error where the file does not exist. That means
   827  			// it got rotated out from under us.
   828  			if os.IsNotExist(err) {
   829  				continue
   830  			}
   831  
   832  			// Check if the connection was closed
   833  			if err == syscall.EPIPE {
   834  				return nil
   835  			}
   836  
   837  			return fmt.Errorf("failed to stream %q: %v", p, err)
   838  		}
   839  
   840  		if exitAfter {
   841  			return nil
   842  		}
   843  
   844  		//Since we successfully streamed, update the overall offset/idx.
   845  		offset = int64(0)
   846  		nextIdx = idx + 1
   847  	}
   848  
   849  	return nil
   850  }
   851  
   852  // blockUntilNextLog returns a channel that will have data sent when the next
   853  // log index or anything greater is created.
   854  func blockUntilNextLog(fs allocdir.AllocDirFS, t *tomb.Tomb, logPath, task, logType string, nextIndex int64) chan error {
   855  	nextPath := filepath.Join(logPath, fmt.Sprintf("%s.%s.%d", task, logType, nextIndex))
   856  	next := make(chan error, 1)
   857  
   858  	go func() {
   859  		eofCancelCh, err := fs.BlockUntilExists(nextPath, t)
   860  		if err != nil {
   861  			next <- err
   862  			close(next)
   863  			return
   864  		}
   865  
   866  		scanCh := time.Tick(nextLogCheckRate)
   867  		for {
   868  			select {
   869  			case err := <-eofCancelCh:
   870  				next <- err
   871  				close(next)
   872  				return
   873  			case <-scanCh:
   874  				entries, err := fs.List(logPath)
   875  				if err != nil {
   876  					next <- fmt.Errorf("failed to list entries: %v", err)
   877  					close(next)
   878  					return
   879  				}
   880  
   881  				indexes, err := logIndexes(entries, task, logType)
   882  				if err != nil {
   883  					next <- err
   884  					close(next)
   885  					return
   886  				}
   887  
   888  				// Scan and see if there are any entries larger than what we are
   889  				// waiting for.
   890  				for _, entry := range indexes {
   891  					if entry.idx >= nextIndex {
   892  						next <- nil
   893  						close(next)
   894  						return
   895  					}
   896  				}
   897  			}
   898  		}
   899  	}()
   900  
   901  	return next
   902  }
   903  
   904  // indexTuple and indexTupleArray are used to find the correct log entry to
   905  // start streaming logs from
   906  type indexTuple struct {
   907  	idx   int64
   908  	entry *allocdir.AllocFileInfo
   909  }
   910  
   911  type indexTupleArray []indexTuple
   912  
   913  func (a indexTupleArray) Len() int           { return len(a) }
   914  func (a indexTupleArray) Less(i, j int) bool { return a[i].idx < a[j].idx }
   915  func (a indexTupleArray) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
   916  
   917  // logIndexes takes a set of entries and returns a indexTupleArray of
   918  // the desired log file entries. If the indexes could not be determined, an
   919  // error is returned.
   920  func logIndexes(entries []*allocdir.AllocFileInfo, task, logType string) (indexTupleArray, error) {
   921  	var indexes []indexTuple
   922  	prefix := fmt.Sprintf("%s.%s.", task, logType)
   923  	for _, entry := range entries {
   924  		if entry.IsDir {
   925  			continue
   926  		}
   927  
   928  		// If nothing was trimmed, then it is not a match
   929  		idxStr := strings.TrimPrefix(entry.Name, prefix)
   930  		if idxStr == entry.Name {
   931  			continue
   932  		}
   933  
   934  		// Convert to an int
   935  		idx, err := strconv.Atoi(idxStr)
   936  		if err != nil {
   937  			return nil, fmt.Errorf("failed to convert %q to a log index: %v", idxStr, err)
   938  		}
   939  
   940  		indexes = append(indexes, indexTuple{idx: int64(idx), entry: entry})
   941  	}
   942  
   943  	return indexTupleArray(indexes), nil
   944  }
   945  
   946  // findClosest takes a list of entries, the desired log index and desired log
   947  // offset (which can be negative, treated as offset from end), task name and log
   948  // type and returns the log entry, the log index, the offset to read from and a
   949  // potential error.
   950  func findClosest(entries []*allocdir.AllocFileInfo, desiredIdx, desiredOffset int64,
   951  	task, logType string) (*allocdir.AllocFileInfo, int64, int64, error) {
   952  
   953  	// Build the matching indexes
   954  	indexes, err := logIndexes(entries, task, logType)
   955  	if err != nil {
   956  		return nil, 0, 0, err
   957  	}
   958  	if len(indexes) == 0 {
   959  		return nil, 0, 0, fmt.Errorf("log entry for task %q and log type %q not found", task, logType)
   960  	}
   961  
   962  	// Binary search the indexes to get the desiredIdx
   963  	sort.Sort(indexTupleArray(indexes))
   964  	i := sort.Search(len(indexes), func(i int) bool { return indexes[i].idx >= desiredIdx })
   965  	l := len(indexes)
   966  	if i == l {
   967  		// Use the last index if the number is bigger than all of them.
   968  		i = l - 1
   969  	}
   970  
   971  	// Get to the correct offset
   972  	offset := desiredOffset
   973  	idx := int64(i)
   974  	for {
   975  		s := indexes[idx].entry.Size
   976  
   977  		// Base case
   978  		if offset == 0 {
   979  			break
   980  		} else if offset < 0 {
   981  			// Going backwards
   982  			if newOffset := s + offset; newOffset >= 0 {
   983  				// Current file works
   984  				offset = newOffset
   985  				break
   986  			} else if idx == 0 {
   987  				// Already at the end
   988  				offset = 0
   989  				break
   990  			} else {
   991  				// Try the file before
   992  				offset = newOffset
   993  				idx -= 1
   994  				continue
   995  			}
   996  		} else {
   997  			// Going forward
   998  			if offset <= s {
   999  				// Current file works
  1000  				break
  1001  			} else if idx == int64(l-1) {
  1002  				// Already at the end
  1003  				offset = s
  1004  				break
  1005  			} else {
  1006  				// Try the next file
  1007  				offset = offset - s
  1008  				idx += 1
  1009  				continue
  1010  			}
  1011  
  1012  		}
  1013  	}
  1014  
  1015  	return indexes[idx].entry, indexes[idx].idx, offset, nil
  1016  }