github.com/blixtra/nomad@v0.7.2-0.20171221000451-da9a1d7bb050/api/fs.go (about)

     1  package api
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"strconv"
     8  	"sync"
     9  	"time"
    10  )
    11  
    12  const (
    13  	// OriginStart and OriginEnd are the available parameters for the origin
    14  	// argument when streaming a file. They respectively offset from the start
    15  	// and end of a file.
    16  	OriginStart = "start"
    17  	OriginEnd   = "end"
    18  )
    19  
    20  // AllocFileInfo holds information about a file inside the AllocDir
    21  type AllocFileInfo struct {
    22  	Name     string
    23  	IsDir    bool
    24  	Size     int64
    25  	FileMode string
    26  	ModTime  time.Time
    27  }
    28  
    29  // StreamFrame is used to frame data of a file when streaming
    30  type StreamFrame struct {
    31  	Offset    int64  `json:",omitempty"`
    32  	Data      []byte `json:",omitempty"`
    33  	File      string `json:",omitempty"`
    34  	FileEvent string `json:",omitempty"`
    35  }
    36  
    37  // IsHeartbeat returns if the frame is a heartbeat frame
    38  func (s *StreamFrame) IsHeartbeat() bool {
    39  	return len(s.Data) == 0 && s.FileEvent == "" && s.File == "" && s.Offset == 0
    40  }
    41  
    42  // AllocFS is used to introspect an allocation directory on a Nomad client
    43  type AllocFS struct {
    44  	client *Client
    45  }
    46  
    47  // AllocFS returns an handle to the AllocFS endpoints
    48  func (c *Client) AllocFS() *AllocFS {
    49  	return &AllocFS{client: c}
    50  }
    51  
    52  // List is used to list the files at a given path of an allocation directory
    53  func (a *AllocFS) List(alloc *Allocation, path string, q *QueryOptions) ([]*AllocFileInfo, *QueryMeta, error) {
    54  	nodeClient, err := a.client.GetNodeClient(alloc.NodeID, q)
    55  	if err != nil {
    56  		return nil, nil, err
    57  	}
    58  
    59  	if q == nil {
    60  		q = &QueryOptions{}
    61  	}
    62  	if q.Params == nil {
    63  		q.Params = make(map[string]string)
    64  	}
    65  
    66  	q.Params["path"] = path
    67  
    68  	var resp []*AllocFileInfo
    69  	qm, err := nodeClient.query(fmt.Sprintf("/v1/client/fs/ls/%s", alloc.ID), &resp, q)
    70  	if err != nil {
    71  		return nil, nil, err
    72  	}
    73  
    74  	return resp, qm, nil
    75  }
    76  
    77  // Stat is used to stat a file at a given path of an allocation directory
    78  func (a *AllocFS) Stat(alloc *Allocation, path string, q *QueryOptions) (*AllocFileInfo, *QueryMeta, error) {
    79  	nodeClient, err := a.client.GetNodeClient(alloc.NodeID, q)
    80  	if err != nil {
    81  		return nil, nil, err
    82  	}
    83  
    84  	if q == nil {
    85  		q = &QueryOptions{}
    86  	}
    87  	if q.Params == nil {
    88  		q.Params = make(map[string]string)
    89  	}
    90  
    91  	q.Params["path"] = path
    92  
    93  	var resp AllocFileInfo
    94  	qm, err := nodeClient.query(fmt.Sprintf("/v1/client/fs/stat/%s", alloc.ID), &resp, q)
    95  	if err != nil {
    96  		return nil, nil, err
    97  	}
    98  	return &resp, qm, nil
    99  }
   100  
   101  // ReadAt is used to read bytes at a given offset until limit at the given path
   102  // in an allocation directory. If limit is <= 0, there is no limit.
   103  func (a *AllocFS) ReadAt(alloc *Allocation, path string, offset int64, limit int64, q *QueryOptions) (io.ReadCloser, error) {
   104  	nodeClient, err := a.client.GetNodeClient(alloc.NodeID, q)
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  
   109  	if q == nil {
   110  		q = &QueryOptions{}
   111  	}
   112  	if q.Params == nil {
   113  		q.Params = make(map[string]string)
   114  	}
   115  
   116  	q.Params["path"] = path
   117  	q.Params["offset"] = strconv.FormatInt(offset, 10)
   118  	q.Params["limit"] = strconv.FormatInt(limit, 10)
   119  
   120  	r, err := nodeClient.rawQuery(fmt.Sprintf("/v1/client/fs/readat/%s", alloc.ID), q)
   121  	if err != nil {
   122  		return nil, err
   123  	}
   124  	return r, nil
   125  }
   126  
   127  // Cat is used to read contents of a file at the given path in an allocation
   128  // directory
   129  func (a *AllocFS) Cat(alloc *Allocation, path string, q *QueryOptions) (io.ReadCloser, error) {
   130  	nodeClient, err := a.client.GetNodeClient(alloc.NodeID, q)
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  
   135  	if q == nil {
   136  		q = &QueryOptions{}
   137  	}
   138  	if q.Params == nil {
   139  		q.Params = make(map[string]string)
   140  	}
   141  
   142  	q.Params["path"] = path
   143  
   144  	r, err := nodeClient.rawQuery(fmt.Sprintf("/v1/client/fs/cat/%s", alloc.ID), q)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  	return r, nil
   149  }
   150  
   151  // Stream streams the content of a file blocking on EOF.
   152  // The parameters are:
   153  // * path: path to file to stream.
   154  // * offset: The offset to start streaming data at.
   155  // * origin: Either "start" or "end" and defines from where the offset is applied.
   156  // * cancel: A channel that when closed, streaming will end.
   157  //
   158  // The return value is a channel that will emit StreamFrames as they are read.
   159  func (a *AllocFS) Stream(alloc *Allocation, path, origin string, offset int64,
   160  	cancel <-chan struct{}, q *QueryOptions) (<-chan *StreamFrame, <-chan error) {
   161  
   162  	errCh := make(chan error, 1)
   163  	nodeClient, err := a.client.GetNodeClient(alloc.NodeID, q)
   164  	if err != nil {
   165  		errCh <- err
   166  		return nil, errCh
   167  	}
   168  
   169  	if q == nil {
   170  		q = &QueryOptions{}
   171  	}
   172  	if q.Params == nil {
   173  		q.Params = make(map[string]string)
   174  	}
   175  
   176  	q.Params["path"] = path
   177  	q.Params["offset"] = strconv.FormatInt(offset, 10)
   178  	q.Params["origin"] = origin
   179  
   180  	r, err := nodeClient.rawQuery(fmt.Sprintf("/v1/client/fs/stream/%s", alloc.ID), q)
   181  	if err != nil {
   182  		errCh <- err
   183  		return nil, errCh
   184  	}
   185  
   186  	// Create the output channel
   187  	frames := make(chan *StreamFrame, 10)
   188  
   189  	go func() {
   190  		// Close the body
   191  		defer r.Close()
   192  
   193  		// Create a decoder
   194  		dec := json.NewDecoder(r)
   195  
   196  		for {
   197  			// Check if we have been cancelled
   198  			select {
   199  			case <-cancel:
   200  				return
   201  			default:
   202  			}
   203  
   204  			// Decode the next frame
   205  			var frame StreamFrame
   206  			if err := dec.Decode(&frame); err != nil {
   207  				errCh <- err
   208  				close(frames)
   209  				return
   210  			}
   211  
   212  			// Discard heartbeat frames
   213  			if frame.IsHeartbeat() {
   214  				continue
   215  			}
   216  
   217  			frames <- &frame
   218  		}
   219  	}()
   220  
   221  	return frames, errCh
   222  }
   223  
   224  // Logs streams the content of a tasks logs blocking on EOF.
   225  // The parameters are:
   226  // * allocation: the allocation to stream from.
   227  // * follow: Whether the logs should be followed.
   228  // * task: the tasks name to stream logs for.
   229  // * logType: Either "stdout" or "stderr"
   230  // * origin: Either "start" or "end" and defines from where the offset is applied.
   231  // * offset: The offset to start streaming data at.
   232  // * cancel: A channel that when closed, streaming will end.
   233  //
   234  // The return value is a channel that will emit StreamFrames as they are read.
   235  func (a *AllocFS) Logs(alloc *Allocation, follow bool, task, logType, origin string,
   236  	offset int64, cancel <-chan struct{}, q *QueryOptions) (<-chan *StreamFrame, <-chan error) {
   237  
   238  	errCh := make(chan error, 1)
   239  	nodeClient, err := a.client.GetNodeClient(alloc.NodeID, q)
   240  	if err != nil {
   241  		errCh <- err
   242  		return nil, errCh
   243  	}
   244  
   245  	if q == nil {
   246  		q = &QueryOptions{}
   247  	}
   248  	if q.Params == nil {
   249  		q.Params = make(map[string]string)
   250  	}
   251  
   252  	q.Params["follow"] = strconv.FormatBool(follow)
   253  	q.Params["task"] = task
   254  	q.Params["type"] = logType
   255  	q.Params["origin"] = origin
   256  	q.Params["offset"] = strconv.FormatInt(offset, 10)
   257  
   258  	r, err := nodeClient.rawQuery(fmt.Sprintf("/v1/client/fs/logs/%s", alloc.ID), q)
   259  	if err != nil {
   260  		errCh <- err
   261  		return nil, errCh
   262  	}
   263  
   264  	// Create the output channel
   265  	frames := make(chan *StreamFrame, 10)
   266  
   267  	go func() {
   268  		// Close the body
   269  		defer r.Close()
   270  
   271  		// Create a decoder
   272  		dec := json.NewDecoder(r)
   273  
   274  		for {
   275  			// Check if we have been cancelled
   276  			select {
   277  			case <-cancel:
   278  				return
   279  			default:
   280  			}
   281  
   282  			// Decode the next frame
   283  			var frame StreamFrame
   284  			if err := dec.Decode(&frame); err != nil {
   285  				errCh <- err
   286  				close(frames)
   287  				return
   288  			}
   289  
   290  			// Discard heartbeat frames
   291  			if frame.IsHeartbeat() {
   292  				continue
   293  			}
   294  
   295  			frames <- &frame
   296  		}
   297  	}()
   298  
   299  	return frames, errCh
   300  }
   301  
   302  // FrameReader is used to convert a stream of frames into a read closer.
   303  type FrameReader struct {
   304  	frames   <-chan *StreamFrame
   305  	errCh    <-chan error
   306  	cancelCh chan struct{}
   307  
   308  	closedLock sync.Mutex
   309  	closed     bool
   310  
   311  	unblockTime time.Duration
   312  
   313  	frame       *StreamFrame
   314  	frameOffset int
   315  
   316  	byteOffset int
   317  }
   318  
   319  // NewFrameReader takes a channel of frames and returns a FrameReader which
   320  // implements io.ReadCloser
   321  func NewFrameReader(frames <-chan *StreamFrame, errCh <-chan error, cancelCh chan struct{}) *FrameReader {
   322  	return &FrameReader{
   323  		frames:   frames,
   324  		errCh:    errCh,
   325  		cancelCh: cancelCh,
   326  	}
   327  }
   328  
   329  // SetUnblockTime sets the time to unblock and return zero bytes read. If the
   330  // duration is unset or is zero or less, the read will block til data is read.
   331  func (f *FrameReader) SetUnblockTime(d time.Duration) {
   332  	f.unblockTime = d
   333  }
   334  
   335  // Offset returns the offset into the stream.
   336  func (f *FrameReader) Offset() int {
   337  	return f.byteOffset
   338  }
   339  
   340  // Read reads the data of the incoming frames into the bytes buffer. Returns EOF
   341  // when there are no more frames.
   342  func (f *FrameReader) Read(p []byte) (n int, err error) {
   343  	f.closedLock.Lock()
   344  	closed := f.closed
   345  	f.closedLock.Unlock()
   346  	if closed {
   347  		return 0, io.EOF
   348  	}
   349  
   350  	if f.frame == nil {
   351  		var unblock <-chan time.Time
   352  		if f.unblockTime.Nanoseconds() > 0 {
   353  			unblock = time.After(f.unblockTime)
   354  		}
   355  
   356  		select {
   357  		case frame, ok := <-f.frames:
   358  			if !ok {
   359  				return 0, io.EOF
   360  			}
   361  			f.frame = frame
   362  
   363  			// Store the total offset into the file
   364  			f.byteOffset = int(f.frame.Offset)
   365  		case <-unblock:
   366  			return 0, nil
   367  		case err := <-f.errCh:
   368  			return 0, err
   369  		case <-f.cancelCh:
   370  			return 0, io.EOF
   371  		}
   372  	}
   373  
   374  	// Copy the data out of the frame and update our offset
   375  	n = copy(p, f.frame.Data[f.frameOffset:])
   376  	f.frameOffset += n
   377  
   378  	// Clear the frame and its offset once we have read everything
   379  	if len(f.frame.Data) == f.frameOffset {
   380  		f.frame = nil
   381  		f.frameOffset = 0
   382  	}
   383  
   384  	return n, nil
   385  }
   386  
   387  // Close cancels the stream of frames
   388  func (f *FrameReader) Close() error {
   389  	f.closedLock.Lock()
   390  	defer f.closedLock.Unlock()
   391  	if f.closed {
   392  		return nil
   393  	}
   394  
   395  	close(f.cancelCh)
   396  	f.closed = true
   397  	return nil
   398  }