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