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 }