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