github.com/hernad/nomad@v1.6.112/drivers/shared/executor/grpc_client.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package executor
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  	"syscall"
    12  	"time"
    13  
    14  	"github.com/LK4D4/joincontext"
    15  	"github.com/golang/protobuf/ptypes"
    16  	hclog "github.com/hashicorp/go-hclog"
    17  	cstructs "github.com/hernad/nomad/client/structs"
    18  	"github.com/hernad/nomad/drivers/shared/executor/proto"
    19  	"github.com/hernad/nomad/helper/pluginutils/grpcutils"
    20  	"github.com/hernad/nomad/plugins/drivers"
    21  	dproto "github.com/hernad/nomad/plugins/drivers/proto"
    22  	"google.golang.org/grpc/codes"
    23  	"google.golang.org/grpc/status"
    24  )
    25  
    26  var _ Executor = (*grpcExecutorClient)(nil)
    27  
    28  type grpcExecutorClient struct {
    29  	client proto.ExecutorClient
    30  	logger hclog.Logger
    31  
    32  	// doneCtx is close when the plugin exits
    33  	doneCtx context.Context
    34  }
    35  
    36  func (c *grpcExecutorClient) Launch(cmd *ExecCommand) (*ProcessState, error) {
    37  	ctx := context.Background()
    38  	req := &proto.LaunchRequest{
    39  		Cmd:                cmd.Cmd,
    40  		Args:               cmd.Args,
    41  		Resources:          drivers.ResourcesToProto(cmd.Resources),
    42  		StdoutPath:         cmd.StdoutPath,
    43  		StderrPath:         cmd.StderrPath,
    44  		Env:                cmd.Env,
    45  		User:               cmd.User,
    46  		TaskDir:            cmd.TaskDir,
    47  		ResourceLimits:     cmd.ResourceLimits,
    48  		BasicProcessCgroup: cmd.BasicProcessCgroup,
    49  		NoPivotRoot:        cmd.NoPivotRoot,
    50  		Mounts:             drivers.MountsToProto(cmd.Mounts),
    51  		Devices:            drivers.DevicesToProto(cmd.Devices),
    52  		NetworkIsolation:   drivers.NetworkIsolationSpecToProto(cmd.NetworkIsolation),
    53  		DefaultPidMode:     cmd.ModePID,
    54  		DefaultIpcMode:     cmd.ModeIPC,
    55  		Capabilities:       cmd.Capabilities,
    56  	}
    57  	resp, err := c.client.Launch(ctx, req)
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  
    62  	ps, err := processStateFromProto(resp.Process)
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  	return ps, nil
    67  }
    68  
    69  func (c *grpcExecutorClient) Wait(ctx context.Context) (*ProcessState, error) {
    70  	// Join the passed context and the shutdown context
    71  	ctx, _ = joincontext.Join(ctx, c.doneCtx)
    72  
    73  	resp, err := c.client.Wait(ctx, &proto.WaitRequest{})
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	ps, err := processStateFromProto(resp.Process)
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  
    83  	return ps, nil
    84  }
    85  
    86  func (c *grpcExecutorClient) Shutdown(signal string, gracePeriod time.Duration) error {
    87  	ctx := context.Background()
    88  	req := &proto.ShutdownRequest{
    89  		Signal:      signal,
    90  		GracePeriod: gracePeriod.Nanoseconds(),
    91  	}
    92  	if _, err := c.client.Shutdown(ctx, req); err != nil {
    93  		return err
    94  	}
    95  
    96  	return nil
    97  }
    98  
    99  func (c *grpcExecutorClient) UpdateResources(r *drivers.Resources) error {
   100  	ctx := context.Background()
   101  	req := &proto.UpdateResourcesRequest{Resources: drivers.ResourcesToProto(r)}
   102  	if _, err := c.client.UpdateResources(ctx, req); err != nil {
   103  		return err
   104  	}
   105  
   106  	return nil
   107  }
   108  
   109  func (c *grpcExecutorClient) Version() (*ExecutorVersion, error) {
   110  	ctx := context.Background()
   111  	resp, err := c.client.Version(ctx, &proto.VersionRequest{})
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  	return &ExecutorVersion{Version: resp.Version}, nil
   116  }
   117  
   118  func (c *grpcExecutorClient) Stats(ctx context.Context, interval time.Duration) (<-chan *cstructs.TaskResourceUsage, error) {
   119  	stream, err := c.client.Stats(ctx, &proto.StatsRequest{
   120  		Interval: int64(interval),
   121  	})
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  
   126  	ch := make(chan *cstructs.TaskResourceUsage)
   127  	go c.handleStats(ctx, stream, ch)
   128  	return ch, nil
   129  }
   130  
   131  func (c *grpcExecutorClient) handleStats(ctx context.Context, stream proto.Executor_StatsClient, ch chan<- *cstructs.TaskResourceUsage) {
   132  	defer close(ch)
   133  	for {
   134  		resp, err := stream.Recv()
   135  		if ctx.Err() != nil {
   136  			// Context canceled; exit gracefully
   137  			return
   138  		}
   139  
   140  		if err == io.EOF ||
   141  			status.Code(err) == codes.Unavailable ||
   142  			status.Code(err) == codes.Canceled ||
   143  			err == context.Canceled {
   144  			c.logger.Trace("executor Stats stream closed", "msg", err)
   145  			return
   146  		} else if err != nil {
   147  			c.logger.Warn("failed to receive Stats executor RPC stream, closing stream", "error", err)
   148  			return
   149  		}
   150  
   151  		stats, err := drivers.TaskStatsFromProto(resp.Stats)
   152  		if err != nil {
   153  			c.logger.Error("failed to decode stats from RPC", "error", err, "stats", resp.Stats)
   154  			continue
   155  		}
   156  
   157  		select {
   158  		case ch <- stats:
   159  		case <-ctx.Done():
   160  			return
   161  		}
   162  	}
   163  }
   164  
   165  func (c *grpcExecutorClient) Signal(s os.Signal) error {
   166  	ctx := context.Background()
   167  	sig, ok := s.(syscall.Signal)
   168  	if !ok {
   169  		return fmt.Errorf("unsupported signal type: %q", s.String())
   170  	}
   171  	req := &proto.SignalRequest{
   172  		Signal: int32(sig),
   173  	}
   174  	if _, err := c.client.Signal(ctx, req); err != nil {
   175  		return err
   176  	}
   177  
   178  	return nil
   179  }
   180  
   181  func (c *grpcExecutorClient) Exec(deadline time.Time, cmd string, args []string) ([]byte, int, error) {
   182  	ctx := context.Background()
   183  	pbDeadline, err := ptypes.TimestampProto(deadline)
   184  	if err != nil {
   185  		return nil, 0, err
   186  	}
   187  	req := &proto.ExecRequest{
   188  		Deadline: pbDeadline,
   189  		Cmd:      cmd,
   190  		Args:     args,
   191  	}
   192  
   193  	resp, err := c.client.Exec(ctx, req)
   194  	if err != nil {
   195  		return nil, 0, err
   196  	}
   197  
   198  	return resp.Output, int(resp.ExitCode), nil
   199  }
   200  
   201  func (c *grpcExecutorClient) ExecStreaming(ctx context.Context,
   202  	command []string,
   203  	tty bool,
   204  	execStream drivers.ExecTaskStream) error {
   205  
   206  	err := c.execStreaming(ctx, command, tty, execStream)
   207  	if err != nil {
   208  		return grpcutils.HandleGrpcErr(err, c.doneCtx)
   209  	}
   210  	return nil
   211  }
   212  
   213  func (c *grpcExecutorClient) execStreaming(ctx context.Context,
   214  	command []string,
   215  	tty bool,
   216  	execStream drivers.ExecTaskStream) error {
   217  
   218  	stream, err := c.client.ExecStreaming(ctx)
   219  	if err != nil {
   220  		return err
   221  	}
   222  
   223  	err = stream.Send(&dproto.ExecTaskStreamingRequest{
   224  		Setup: &dproto.ExecTaskStreamingRequest_Setup{
   225  			Command: command,
   226  			Tty:     tty,
   227  		},
   228  	})
   229  	if err != nil {
   230  		return err
   231  	}
   232  
   233  	errCh := make(chan error, 1)
   234  	go func() {
   235  		for {
   236  			m, err := execStream.Recv()
   237  			if err == io.EOF {
   238  				return
   239  			} else if err != nil {
   240  				errCh <- err
   241  				return
   242  			}
   243  
   244  			if err := stream.Send(m); err != nil {
   245  				errCh <- err
   246  				return
   247  			}
   248  
   249  		}
   250  	}()
   251  
   252  	for {
   253  		select {
   254  		case err := <-errCh:
   255  			return err
   256  		default:
   257  		}
   258  
   259  		m, err := stream.Recv()
   260  		if err == io.EOF {
   261  			return nil
   262  		} else if err != nil {
   263  			return err
   264  		}
   265  
   266  		if err := execStream.Send(m); err != nil {
   267  			return err
   268  		}
   269  	}
   270  }