github.com/ferranbt/nomad@v0.9.3-0.20190607002617-85c449b7667c/client/alloc_endpoint.go (about)

     1  package client
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     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/helper/uuid"
    16  	"github.com/hashicorp/nomad/nomad/structs"
    17  	nstructs "github.com/hashicorp/nomad/nomad/structs"
    18  	"github.com/hashicorp/nomad/plugins/drivers"
    19  	"github.com/ugorji/go/codec"
    20  )
    21  
    22  // Allocations endpoint is used for interacting with client allocations
    23  type Allocations struct {
    24  	c *Client
    25  }
    26  
    27  func NewAllocationsEndpoint(c *Client) *Allocations {
    28  	a := &Allocations{c: c}
    29  	a.c.streamingRpcs.Register("Allocations.Exec", a.exec)
    30  	return a
    31  }
    32  
    33  // GarbageCollectAll is used to garbage collect all allocations on a client.
    34  func (a *Allocations) GarbageCollectAll(args *nstructs.NodeSpecificRequest, reply *nstructs.GenericResponse) error {
    35  	defer metrics.MeasureSince([]string{"client", "allocations", "garbage_collect_all"}, time.Now())
    36  
    37  	// Check node write permissions
    38  	if aclObj, err := a.c.ResolveToken(args.AuthToken); err != nil {
    39  		return err
    40  	} else if aclObj != nil && !aclObj.AllowNodeWrite() {
    41  		return nstructs.ErrPermissionDenied
    42  	}
    43  
    44  	a.c.CollectAllAllocs()
    45  	return nil
    46  }
    47  
    48  // GarbageCollect is used to garbage collect an allocation on a client.
    49  func (a *Allocations) GarbageCollect(args *nstructs.AllocSpecificRequest, reply *nstructs.GenericResponse) error {
    50  	defer metrics.MeasureSince([]string{"client", "allocations", "garbage_collect"}, time.Now())
    51  
    52  	// Check submit job permissions
    53  	if aclObj, err := a.c.ResolveToken(args.AuthToken); err != nil {
    54  		return err
    55  	} else if aclObj != nil && !aclObj.AllowNsOp(args.Namespace, acl.NamespaceCapabilitySubmitJob) {
    56  		return nstructs.ErrPermissionDenied
    57  	}
    58  
    59  	if !a.c.CollectAllocation(args.AllocID) {
    60  		// Could not find alloc
    61  		return nstructs.NewErrUnknownAllocation(args.AllocID)
    62  	}
    63  
    64  	return nil
    65  }
    66  
    67  // Signal is used to send a signal to an allocation's tasks on a client.
    68  func (a *Allocations) Signal(args *nstructs.AllocSignalRequest, reply *nstructs.GenericResponse) error {
    69  	defer metrics.MeasureSince([]string{"client", "allocations", "signal"}, time.Now())
    70  
    71  	// Check alloc-lifecycle permissions
    72  	if aclObj, err := a.c.ResolveToken(args.AuthToken); err != nil {
    73  		return err
    74  	} else if aclObj != nil && !aclObj.AllowNsOp(args.Namespace, acl.NamespaceCapabilityAllocLifecycle) {
    75  		return nstructs.ErrPermissionDenied
    76  	}
    77  
    78  	return a.c.SignalAllocation(args.AllocID, args.Task, args.Signal)
    79  }
    80  
    81  // Restart is used to trigger a restart of an allocation or a subtask on a client.
    82  func (a *Allocations) Restart(args *nstructs.AllocRestartRequest, reply *nstructs.GenericResponse) error {
    83  	defer metrics.MeasureSince([]string{"client", "allocations", "restart"}, time.Now())
    84  
    85  	if aclObj, err := a.c.ResolveToken(args.AuthToken); err != nil {
    86  		return err
    87  	} else if aclObj != nil && !aclObj.AllowNsOp(args.Namespace, acl.NamespaceCapabilityAllocLifecycle) {
    88  		return nstructs.ErrPermissionDenied
    89  	}
    90  
    91  	return a.c.RestartAllocation(args.AllocID, args.TaskName)
    92  }
    93  
    94  // Stats is used to collect allocation statistics
    95  func (a *Allocations) Stats(args *cstructs.AllocStatsRequest, reply *cstructs.AllocStatsResponse) error {
    96  	defer metrics.MeasureSince([]string{"client", "allocations", "stats"}, time.Now())
    97  
    98  	// Check read job permissions
    99  	if aclObj, err := a.c.ResolveToken(args.AuthToken); err != nil {
   100  		return err
   101  	} else if aclObj != nil && !aclObj.AllowNsOp(args.Namespace, acl.NamespaceCapabilityReadJob) {
   102  		return nstructs.ErrPermissionDenied
   103  	}
   104  
   105  	clientStats := a.c.StatsReporter()
   106  	aStats, err := clientStats.GetAllocStats(args.AllocID)
   107  	if err != nil {
   108  		return err
   109  	}
   110  
   111  	stats, err := aStats.LatestAllocStats(args.Task)
   112  	if err != nil {
   113  		return err
   114  	}
   115  
   116  	reply.Stats = stats
   117  	return nil
   118  }
   119  
   120  // exec is used to execute command in a running task
   121  func (a *Allocations) exec(conn io.ReadWriteCloser) {
   122  	defer metrics.MeasureSince([]string{"client", "allocations", "exec"}, time.Now())
   123  	defer conn.Close()
   124  
   125  	execID := uuid.Generate()
   126  	decoder := codec.NewDecoder(conn, structs.MsgpackHandle)
   127  	encoder := codec.NewEncoder(conn, structs.MsgpackHandle)
   128  
   129  	code, err := a.execImpl(encoder, decoder, execID)
   130  	if err != nil {
   131  		a.c.logger.Info("task exec session ended with an error", "error", err, "code", code)
   132  		handleStreamResultError(err, code, encoder)
   133  		return
   134  	}
   135  
   136  	a.c.logger.Info("task exec session ended", "exec_id", execID)
   137  }
   138  
   139  func (a *Allocations) execImpl(encoder *codec.Encoder, decoder *codec.Decoder, execID string) (code *int64, err error) {
   140  
   141  	// Decode the arguments
   142  	var req cstructs.AllocExecRequest
   143  	if err := decoder.Decode(&req); err != nil {
   144  		return helper.Int64ToPtr(500), err
   145  	}
   146  
   147  	if a.c.GetConfig().DisableRemoteExec {
   148  		return nil, structs.ErrPermissionDenied
   149  	}
   150  
   151  	aclObj, token, err := a.c.resolveTokenAndACL(req.QueryOptions.AuthToken)
   152  	{
   153  		// log access
   154  		tokenName, tokenID := "", ""
   155  		if token != nil {
   156  			tokenName, tokenID = token.Name, token.AccessorID
   157  		}
   158  
   159  		a.c.logger.Info("task exec session starting",
   160  			"exec_id", execID,
   161  			"alloc_id", req.AllocID,
   162  			"task", req.Task,
   163  			"command", req.Cmd,
   164  			"tty", req.Tty,
   165  			"access_token_name", tokenName,
   166  			"access_token_id", tokenID,
   167  		)
   168  	}
   169  
   170  	// Check read permissions
   171  	if err != nil {
   172  		return nil, err
   173  	} else if aclObj != nil {
   174  		exec := aclObj.AllowNsOp(req.QueryOptions.Namespace, acl.NamespaceCapabilityAllocExec)
   175  		if !exec {
   176  			return nil, structs.ErrPermissionDenied
   177  		}
   178  	}
   179  
   180  	// Validate the arguments
   181  	if req.AllocID == "" {
   182  		return helper.Int64ToPtr(400), allocIDNotPresentErr
   183  	}
   184  	if req.Task == "" {
   185  		return helper.Int64ToPtr(400), taskNotPresentErr
   186  	}
   187  	if len(req.Cmd) == 0 {
   188  		return helper.Int64ToPtr(400), errors.New("command is not present")
   189  	}
   190  
   191  	ar, err := a.c.getAllocRunner(req.AllocID)
   192  	if err != nil {
   193  		code := helper.Int64ToPtr(500)
   194  		if structs.IsErrUnknownAllocation(err) {
   195  			code = helper.Int64ToPtr(404)
   196  		}
   197  
   198  		return code, err
   199  	}
   200  
   201  	capabilities, err := ar.GetTaskDriverCapabilities(req.Task)
   202  	if err != nil {
   203  		code := helper.Int64ToPtr(500)
   204  		if structs.IsErrUnknownAllocation(err) {
   205  			code = helper.Int64ToPtr(404)
   206  		}
   207  
   208  		return code, err
   209  	}
   210  
   211  	// check node access
   212  	if aclObj != nil && capabilities.FSIsolation == drivers.FSIsolationNone {
   213  		exec := aclObj.AllowNsOp(req.QueryOptions.Namespace, acl.NamespaceCapabilityAllocNodeExec)
   214  		if !exec {
   215  			return nil, structs.ErrPermissionDenied
   216  		}
   217  	}
   218  
   219  	allocState, err := a.c.GetAllocState(req.AllocID)
   220  	if err != nil {
   221  		code := helper.Int64ToPtr(500)
   222  		if structs.IsErrUnknownAllocation(err) {
   223  			code = helper.Int64ToPtr(404)
   224  		}
   225  
   226  		return code, err
   227  	}
   228  
   229  	// Check that the task is there
   230  	taskState := allocState.TaskStates[req.Task]
   231  	if taskState == nil {
   232  		return helper.Int64ToPtr(400), fmt.Errorf("unknown task name %q", req.Task)
   233  	}
   234  
   235  	if taskState.StartedAt.IsZero() {
   236  		return helper.Int64ToPtr(404), fmt.Errorf("task %q not started yet.", req.Task)
   237  	}
   238  
   239  	ctx, cancel := context.WithCancel(context.Background())
   240  	defer cancel()
   241  
   242  	h := ar.GetTaskExecHandler(req.Task)
   243  	if h == nil {
   244  		return helper.Int64ToPtr(404), fmt.Errorf("task %q is not running.", req.Task)
   245  	}
   246  
   247  	err = h(ctx, req.Cmd, req.Tty, newExecStream(decoder, encoder))
   248  	if err != nil {
   249  		code := helper.Int64ToPtr(500)
   250  		return code, err
   251  	}
   252  
   253  	return nil, nil
   254  }
   255  
   256  // newExecStream returns a new exec stream as expected by drivers that interpolate with RPC streaming format
   257  func newExecStream(decoder *codec.Decoder, encoder *codec.Encoder) drivers.ExecTaskStream {
   258  	buf := new(bytes.Buffer)
   259  	return &execStream{
   260  		decoder: decoder,
   261  
   262  		buf:        buf,
   263  		encoder:    encoder,
   264  		frameCodec: codec.NewEncoder(buf, structs.JsonHandle),
   265  	}
   266  }
   267  
   268  type execStream struct {
   269  	decoder *codec.Decoder
   270  
   271  	encoder    *codec.Encoder
   272  	buf        *bytes.Buffer
   273  	frameCodec *codec.Encoder
   274  }
   275  
   276  // Send sends driver output response across RPC mechanism using cstructs.StreamErrWrapper
   277  func (s *execStream) Send(m *drivers.ExecTaskStreamingResponseMsg) error {
   278  	s.buf.Reset()
   279  	s.frameCodec.Reset(s.buf)
   280  
   281  	s.frameCodec.MustEncode(m)
   282  	return s.encoder.Encode(cstructs.StreamErrWrapper{
   283  		Payload: s.buf.Bytes(),
   284  	})
   285  }
   286  
   287  // Recv returns next exec user input from the RPC to be passed to driver exec handler
   288  func (s *execStream) Recv() (*drivers.ExecTaskStreamingRequestMsg, error) {
   289  	req := drivers.ExecTaskStreamingRequestMsg{}
   290  	err := s.decoder.Decode(&req)
   291  	return &req, err
   292  }