github.com/zoomfoo/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/nomad/client_fs_endpoint.go (about)

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