github.com/hspak/nomad@v0.7.2-0.20180309000617-bc4ae22a39a5/nomad/client_fs_endpoint.go (about)

     1  package nomad
     2  
     3  import (
     4  	"errors"
     5  	"io"
     6  	"net"
     7  	"strings"
     8  	"time"
     9  
    10  	metrics "github.com/armon/go-metrics"
    11  	"github.com/hashicorp/nomad/acl"
    12  	cstructs "github.com/hashicorp/nomad/client/structs"
    13  	"github.com/hashicorp/nomad/helper"
    14  	"github.com/hashicorp/nomad/nomad/structs"
    15  	"github.com/ugorji/go/codec"
    16  )
    17  
    18  // FileSystem endpoint is used for accessing the logs and filesystem of
    19  // allocations from a Node.
    20  type FileSystem struct {
    21  	srv *Server
    22  }
    23  
    24  func (f *FileSystem) register() {
    25  	f.srv.streamingRpcs.Register("FileSystem.Logs", f.logs)
    26  	f.srv.streamingRpcs.Register("FileSystem.Stream", f.stream)
    27  }
    28  
    29  // handleStreamResultError is a helper for sending an error with a potential
    30  // error code. The transmission of the error is ignored if the error has been
    31  // generated by the closing of the underlying transport.
    32  func (f *FileSystem) handleStreamResultError(err error, code *int64, encoder *codec.Encoder) {
    33  	// Nothing to do as the conn is closed
    34  	if err == io.EOF || strings.Contains(err.Error(), "closed") {
    35  		return
    36  	}
    37  
    38  	// Attempt to send the error
    39  	encoder.Encode(&cstructs.StreamErrWrapper{
    40  		Error: cstructs.NewRpcError(err, code),
    41  	})
    42  }
    43  
    44  // forwardRegionStreamingRpc is used to make a streaming RPC to a different
    45  // region. It looks up the allocation in the remote region to determine what
    46  // remote server can route the request.
    47  func (f *FileSystem) forwardRegionStreamingRpc(conn io.ReadWriteCloser,
    48  	encoder *codec.Encoder, args interface{}, method, allocID string, qo *structs.QueryOptions) {
    49  	// Request the allocation from the target region
    50  	allocReq := &structs.AllocSpecificRequest{
    51  		AllocID:      allocID,
    52  		QueryOptions: *qo,
    53  	}
    54  	var allocResp structs.SingleAllocResponse
    55  	if err := f.srv.forwardRegion(qo.RequestRegion(), "Alloc.GetAlloc", allocReq, &allocResp); err != nil {
    56  		f.handleStreamResultError(err, nil, encoder)
    57  		return
    58  	}
    59  
    60  	if allocResp.Alloc == nil {
    61  		f.handleStreamResultError(structs.NewErrUnknownAllocation(allocID), helper.Int64ToPtr(404), encoder)
    62  		return
    63  	}
    64  
    65  	// Determine the Server that has a connection to the node.
    66  	srv, err := f.srv.serverWithNodeConn(allocResp.Alloc.NodeID, qo.RequestRegion())
    67  	if err != nil {
    68  		var code *int64
    69  		if structs.IsErrNoNodeConn(err) {
    70  			code = helper.Int64ToPtr(404)
    71  		}
    72  		f.handleStreamResultError(err, code, encoder)
    73  		return
    74  	}
    75  
    76  	// Get a connection to the server
    77  	srvConn, err := f.srv.streamingRpc(srv, method)
    78  	if err != nil {
    79  		f.handleStreamResultError(err, nil, encoder)
    80  		return
    81  	}
    82  	defer srvConn.Close()
    83  
    84  	// Send the request.
    85  	outEncoder := codec.NewEncoder(srvConn, structs.MsgpackHandle)
    86  	if err := outEncoder.Encode(args); err != nil {
    87  		f.handleStreamResultError(err, nil, encoder)
    88  		return
    89  	}
    90  
    91  	structs.Bridge(conn, srvConn)
    92  }
    93  
    94  // List is used to list the contents of an allocation's directory.
    95  func (f *FileSystem) List(args *cstructs.FsListRequest, reply *cstructs.FsListResponse) error {
    96  	// We only allow stale reads since the only potentially stale information is
    97  	// the Node registration and the cost is fairly high for adding another hope
    98  	// in the forwarding chain.
    99  	args.QueryOptions.AllowStale = true
   100  
   101  	// Potentially forward to a different region.
   102  	if done, err := f.srv.forward("FileSystem.List", args, args, reply); done {
   103  		return err
   104  	}
   105  	defer metrics.MeasureSince([]string{"nomad", "file_system", "list"}, time.Now())
   106  
   107  	// Check filesystem read permissions
   108  	if aclObj, err := f.srv.ResolveToken(args.AuthToken); err != nil {
   109  		return err
   110  	} else if aclObj != nil && !aclObj.AllowNsOp(args.Namespace, acl.NamespaceCapabilityReadFS) {
   111  		return structs.ErrPermissionDenied
   112  	}
   113  
   114  	// Verify the arguments.
   115  	if args.AllocID == "" {
   116  		return errors.New("missing allocation ID")
   117  	}
   118  
   119  	// Lookup the allocation
   120  	snap, err := f.srv.State().Snapshot()
   121  	if err != nil {
   122  		return err
   123  	}
   124  
   125  	alloc, err := snap.AllocByID(nil, args.AllocID)
   126  	if err != nil {
   127  		return err
   128  	}
   129  	if alloc == nil {
   130  		return structs.NewErrUnknownAllocation(args.AllocID)
   131  	}
   132  
   133  	// Get the connection to the client
   134  	state, ok := f.srv.getNodeConn(alloc.NodeID)
   135  	if !ok {
   136  		return findNodeConnAndForward(f.srv, snap, alloc.NodeID, "FileSystem.List", args, reply)
   137  	}
   138  
   139  	// Make the RPC
   140  	return NodeRpc(state.Session, "FileSystem.List", args, reply)
   141  }
   142  
   143  // Stat is used to stat a file in the allocation's directory.
   144  func (f *FileSystem) Stat(args *cstructs.FsStatRequest, reply *cstructs.FsStatResponse) error {
   145  	// We only allow stale reads since the only potentially stale information is
   146  	// the Node registration and the cost is fairly high for adding another hope
   147  	// in the forwarding chain.
   148  	args.QueryOptions.AllowStale = true
   149  
   150  	// Potentially forward to a different region.
   151  	if done, err := f.srv.forward("FileSystem.Stat", args, args, reply); done {
   152  		return err
   153  	}
   154  	defer metrics.MeasureSince([]string{"nomad", "file_system", "stat"}, time.Now())
   155  
   156  	// Check filesystem read permissions
   157  	if aclObj, err := f.srv.ResolveToken(args.AuthToken); err != nil {
   158  		return err
   159  	} else if aclObj != nil && !aclObj.AllowNsOp(args.Namespace, acl.NamespaceCapabilityReadFS) {
   160  		return structs.ErrPermissionDenied
   161  	}
   162  
   163  	// Verify the arguments.
   164  	if args.AllocID == "" {
   165  		return errors.New("missing allocation ID")
   166  	}
   167  
   168  	// Lookup the allocation
   169  	snap, err := f.srv.State().Snapshot()
   170  	if err != nil {
   171  		return err
   172  	}
   173  
   174  	alloc, err := snap.AllocByID(nil, args.AllocID)
   175  	if err != nil {
   176  		return err
   177  	}
   178  	if alloc == nil {
   179  		return structs.NewErrUnknownAllocation(args.AllocID)
   180  	}
   181  
   182  	// Get the connection to the client
   183  	state, ok := f.srv.getNodeConn(alloc.NodeID)
   184  	if !ok {
   185  		return findNodeConnAndForward(f.srv, snap, alloc.NodeID, "FileSystem.Stat", args, reply)
   186  	}
   187  
   188  	// Make the RPC
   189  	return NodeRpc(state.Session, "FileSystem.Stat", args, reply)
   190  }
   191  
   192  // stream is is used to stream the contents of file in an allocation's
   193  // directory.
   194  func (f *FileSystem) stream(conn io.ReadWriteCloser) {
   195  	defer conn.Close()
   196  	defer metrics.MeasureSince([]string{"nomad", "file_system", "stream"}, time.Now())
   197  
   198  	// Decode the arguments
   199  	var args cstructs.FsStreamRequest
   200  	decoder := codec.NewDecoder(conn, structs.MsgpackHandle)
   201  	encoder := codec.NewEncoder(conn, structs.MsgpackHandle)
   202  
   203  	if err := decoder.Decode(&args); err != nil {
   204  		f.handleStreamResultError(err, helper.Int64ToPtr(500), encoder)
   205  		return
   206  	}
   207  
   208  	// Check if we need to forward to a different region
   209  	if r := args.RequestRegion(); r != f.srv.Region() {
   210  		f.forwardRegionStreamingRpc(conn, encoder, &args, "FileSystem.Stream",
   211  			args.AllocID, &args.QueryOptions)
   212  		return
   213  	}
   214  
   215  	// Check node read permissions
   216  	if aclObj, err := f.srv.ResolveToken(args.AuthToken); err != nil {
   217  		f.handleStreamResultError(err, nil, encoder)
   218  		return
   219  	} else if aclObj != nil && !aclObj.AllowNsOp(args.Namespace, acl.NamespaceCapabilityReadFS) {
   220  		f.handleStreamResultError(structs.ErrPermissionDenied, nil, encoder)
   221  		return
   222  	}
   223  
   224  	// Verify the arguments.
   225  	if args.AllocID == "" {
   226  		f.handleStreamResultError(errors.New("missing AllocID"), helper.Int64ToPtr(400), encoder)
   227  		return
   228  	}
   229  
   230  	// Retrieve the allocation
   231  	snap, err := f.srv.State().Snapshot()
   232  	if err != nil {
   233  		f.handleStreamResultError(err, nil, encoder)
   234  		return
   235  	}
   236  
   237  	alloc, err := snap.AllocByID(nil, args.AllocID)
   238  	if err != nil {
   239  		f.handleStreamResultError(err, nil, encoder)
   240  		return
   241  	}
   242  	if alloc == nil {
   243  		f.handleStreamResultError(structs.NewErrUnknownAllocation(args.AllocID), helper.Int64ToPtr(404), encoder)
   244  		return
   245  	}
   246  	nodeID := alloc.NodeID
   247  
   248  	// Get the connection to the client either by forwarding to another server
   249  	// or creating a direct stream
   250  	var clientConn net.Conn
   251  	state, ok := f.srv.getNodeConn(nodeID)
   252  	if !ok {
   253  		// Determine the Server that has a connection to the node.
   254  		srv, err := f.srv.serverWithNodeConn(nodeID, f.srv.Region())
   255  		if err != nil {
   256  			var code *int64
   257  			if structs.IsErrNoNodeConn(err) {
   258  				code = helper.Int64ToPtr(404)
   259  			}
   260  			f.handleStreamResultError(err, code, encoder)
   261  		}
   262  
   263  		// Get a connection to the server
   264  		conn, err := f.srv.streamingRpc(srv, "FileSystem.Stream")
   265  		if err != nil {
   266  			f.handleStreamResultError(err, nil, encoder)
   267  			return
   268  		}
   269  
   270  		clientConn = conn
   271  	} else {
   272  		stream, err := NodeStreamingRpc(state.Session, "FileSystem.Stream")
   273  		if err != nil {
   274  			f.handleStreamResultError(err, nil, encoder)
   275  			return
   276  		}
   277  		clientConn = stream
   278  	}
   279  	defer clientConn.Close()
   280  
   281  	// Send the request.
   282  	outEncoder := codec.NewEncoder(clientConn, structs.MsgpackHandle)
   283  	if err := outEncoder.Encode(args); err != nil {
   284  		f.handleStreamResultError(err, nil, encoder)
   285  		return
   286  	}
   287  
   288  	structs.Bridge(conn, clientConn)
   289  	return
   290  }
   291  
   292  // logs is used to access an task's logs for a given allocation
   293  func (f *FileSystem) logs(conn io.ReadWriteCloser) {
   294  	defer conn.Close()
   295  	defer metrics.MeasureSince([]string{"nomad", "file_system", "logs"}, time.Now())
   296  
   297  	// Decode the arguments
   298  	var args cstructs.FsLogsRequest
   299  	decoder := codec.NewDecoder(conn, structs.MsgpackHandle)
   300  	encoder := codec.NewEncoder(conn, structs.MsgpackHandle)
   301  
   302  	if err := decoder.Decode(&args); err != nil {
   303  		f.handleStreamResultError(err, helper.Int64ToPtr(500), encoder)
   304  		return
   305  	}
   306  
   307  	// Check if we need to forward to a different region
   308  	if r := args.RequestRegion(); r != f.srv.Region() {
   309  		f.forwardRegionStreamingRpc(conn, encoder, &args, "FileSystem.Logs",
   310  			args.AllocID, &args.QueryOptions)
   311  		return
   312  	}
   313  
   314  	// Check node read permissions
   315  	if aclObj, err := f.srv.ResolveToken(args.AuthToken); err != nil {
   316  		f.handleStreamResultError(err, nil, encoder)
   317  		return
   318  	} else if aclObj != nil {
   319  		readfs := aclObj.AllowNsOp(args.QueryOptions.Namespace, acl.NamespaceCapabilityReadFS)
   320  		logs := aclObj.AllowNsOp(args.QueryOptions.Namespace, acl.NamespaceCapabilityReadLogs)
   321  		if !readfs && !logs {
   322  			f.handleStreamResultError(structs.ErrPermissionDenied, nil, encoder)
   323  			return
   324  		}
   325  	}
   326  
   327  	// Verify the arguments.
   328  	if args.AllocID == "" {
   329  		f.handleStreamResultError(errors.New("missing AllocID"), helper.Int64ToPtr(400), encoder)
   330  		return
   331  	}
   332  
   333  	// Retrieve the allocation
   334  	snap, err := f.srv.State().Snapshot()
   335  	if err != nil {
   336  		f.handleStreamResultError(err, nil, encoder)
   337  		return
   338  	}
   339  
   340  	alloc, err := snap.AllocByID(nil, args.AllocID)
   341  	if err != nil {
   342  		f.handleStreamResultError(err, nil, encoder)
   343  		return
   344  	}
   345  	if alloc == nil {
   346  		f.handleStreamResultError(structs.NewErrUnknownAllocation(args.AllocID), helper.Int64ToPtr(404), encoder)
   347  		return
   348  	}
   349  	nodeID := alloc.NodeID
   350  
   351  	// Get the connection to the client either by forwarding to another server
   352  	// or creating a direct stream
   353  	var clientConn net.Conn
   354  	state, ok := f.srv.getNodeConn(nodeID)
   355  	if !ok {
   356  		// Determine the Server that has a connection to the node.
   357  		srv, err := f.srv.serverWithNodeConn(nodeID, f.srv.Region())
   358  		if err != nil {
   359  			var code *int64
   360  			if structs.IsErrNoNodeConn(err) {
   361  				code = helper.Int64ToPtr(404)
   362  			}
   363  			f.handleStreamResultError(err, code, encoder)
   364  			return
   365  		}
   366  
   367  		// Get a connection to the server
   368  		conn, err := f.srv.streamingRpc(srv, "FileSystem.Logs")
   369  		if err != nil {
   370  			f.handleStreamResultError(err, nil, encoder)
   371  			return
   372  		}
   373  
   374  		clientConn = conn
   375  	} else {
   376  		stream, err := NodeStreamingRpc(state.Session, "FileSystem.Logs")
   377  		if err != nil {
   378  			f.handleStreamResultError(err, nil, encoder)
   379  			return
   380  		}
   381  		clientConn = stream
   382  	}
   383  	defer clientConn.Close()
   384  
   385  	// Send the request.
   386  	outEncoder := codec.NewEncoder(clientConn, structs.MsgpackHandle)
   387  	if err := outEncoder.Encode(args); err != nil {
   388  		f.handleStreamResultError(err, nil, encoder)
   389  		return
   390  	}
   391  
   392  	structs.Bridge(conn, clientConn)
   393  	return
   394  }