github.com/ferranbt/nomad@v0.9.3-0.20190607002617-85c449b7667c/plugins/drivers/client.go (about) 1 package drivers 2 3 import ( 4 "context" 5 "errors" 6 "io" 7 "time" 8 9 "github.com/LK4D4/joincontext" 10 "github.com/golang/protobuf/ptypes" 11 hclog "github.com/hashicorp/go-hclog" 12 cstructs "github.com/hashicorp/nomad/client/structs" 13 "github.com/hashicorp/nomad/helper/pluginutils/grpcutils" 14 "github.com/hashicorp/nomad/nomad/structs" 15 "github.com/hashicorp/nomad/plugins/base" 16 "github.com/hashicorp/nomad/plugins/drivers/proto" 17 "github.com/hashicorp/nomad/plugins/shared/hclspec" 18 pstructs "github.com/hashicorp/nomad/plugins/shared/structs" 19 sproto "github.com/hashicorp/nomad/plugins/shared/structs/proto" 20 "google.golang.org/grpc/status" 21 ) 22 23 var _ DriverPlugin = &driverPluginClient{} 24 25 type driverPluginClient struct { 26 *base.BasePluginClient 27 28 client proto.DriverClient 29 logger hclog.Logger 30 31 // doneCtx is closed when the plugin exits 32 doneCtx context.Context 33 } 34 35 func (d *driverPluginClient) TaskConfigSchema() (*hclspec.Spec, error) { 36 req := &proto.TaskConfigSchemaRequest{} 37 38 resp, err := d.client.TaskConfigSchema(d.doneCtx, req) 39 if err != nil { 40 return nil, grpcutils.HandleGrpcErr(err, d.doneCtx) 41 } 42 43 return resp.Spec, nil 44 } 45 46 func (d *driverPluginClient) Capabilities() (*Capabilities, error) { 47 req := &proto.CapabilitiesRequest{} 48 49 resp, err := d.client.Capabilities(d.doneCtx, req) 50 if err != nil { 51 return nil, grpcutils.HandleGrpcErr(err, d.doneCtx) 52 } 53 54 caps := &Capabilities{} 55 if resp.Capabilities != nil { 56 caps.SendSignals = resp.Capabilities.SendSignals 57 caps.Exec = resp.Capabilities.Exec 58 59 switch resp.Capabilities.FsIsolation { 60 case proto.DriverCapabilities_NONE: 61 caps.FSIsolation = FSIsolationNone 62 case proto.DriverCapabilities_CHROOT: 63 caps.FSIsolation = FSIsolationChroot 64 case proto.DriverCapabilities_IMAGE: 65 caps.FSIsolation = FSIsolationImage 66 default: 67 caps.FSIsolation = FSIsolationNone 68 } 69 } 70 71 return caps, nil 72 } 73 74 // Fingerprint the driver, return a chan that will be pushed to periodically and on changes to health 75 func (d *driverPluginClient) Fingerprint(ctx context.Context) (<-chan *Fingerprint, error) { 76 req := &proto.FingerprintRequest{} 77 78 // Join the passed context and the shutdown context 79 joinedCtx, _ := joincontext.Join(ctx, d.doneCtx) 80 81 stream, err := d.client.Fingerprint(joinedCtx, req) 82 if err != nil { 83 return nil, grpcutils.HandleReqCtxGrpcErr(err, ctx, d.doneCtx) 84 } 85 86 ch := make(chan *Fingerprint, 1) 87 go d.handleFingerprint(ctx, ch, stream) 88 89 return ch, nil 90 } 91 92 func (d *driverPluginClient) handleFingerprint(reqCtx context.Context, ch chan *Fingerprint, stream proto.Driver_FingerprintClient) { 93 defer close(ch) 94 for { 95 pb, err := stream.Recv() 96 if err != nil { 97 if err != io.EOF { 98 ch <- &Fingerprint{ 99 Err: grpcutils.HandleReqCtxGrpcErr(err, reqCtx, d.doneCtx), 100 } 101 } 102 103 // End the stream 104 return 105 } 106 107 f := &Fingerprint{ 108 Attributes: pstructs.ConvertProtoAttributeMap(pb.Attributes), 109 Health: healthStateFromProto(pb.Health), 110 HealthDescription: pb.HealthDescription, 111 } 112 113 select { 114 case <-reqCtx.Done(): 115 return 116 case ch <- f: 117 } 118 } 119 } 120 121 // RecoverTask does internal state recovery to be able to control the task of 122 // the given TaskHandle 123 func (d *driverPluginClient) RecoverTask(h *TaskHandle) error { 124 req := &proto.RecoverTaskRequest{Handle: taskHandleToProto(h)} 125 126 _, err := d.client.RecoverTask(d.doneCtx, req) 127 return grpcutils.HandleGrpcErr(err, d.doneCtx) 128 } 129 130 // StartTask starts execution of a task with the given TaskConfig. A TaskHandle 131 // is returned to the caller that can be used to recover state of the task, 132 // should the driver crash or exit prematurely. 133 func (d *driverPluginClient) StartTask(c *TaskConfig) (*TaskHandle, *DriverNetwork, error) { 134 req := &proto.StartTaskRequest{ 135 Task: taskConfigToProto(c), 136 } 137 138 resp, err := d.client.StartTask(d.doneCtx, req) 139 if err != nil { 140 st := status.Convert(err) 141 if len(st.Details()) > 0 { 142 if rec, ok := st.Details()[0].(*sproto.RecoverableError); ok { 143 return nil, nil, structs.NewRecoverableError(err, rec.Recoverable) 144 } 145 } 146 return nil, nil, grpcutils.HandleGrpcErr(err, d.doneCtx) 147 } 148 149 var net *DriverNetwork 150 if resp.NetworkOverride != nil { 151 net = &DriverNetwork{ 152 PortMap: map[string]int{}, 153 IP: resp.NetworkOverride.Addr, 154 AutoAdvertise: resp.NetworkOverride.AutoAdvertise, 155 } 156 for k, v := range resp.NetworkOverride.PortMap { 157 net.PortMap[k] = int(v) 158 } 159 } 160 161 return taskHandleFromProto(resp.Handle), net, nil 162 } 163 164 // WaitTask returns a channel that will have an ExitResult pushed to it once when the task 165 // exits on its own or is killed. If WaitTask is called after the task has exited, the channel 166 // will immedialy return the ExitResult. WaitTask can be called multiple times for 167 // the same task without issue. 168 func (d *driverPluginClient) WaitTask(ctx context.Context, id string) (<-chan *ExitResult, error) { 169 ch := make(chan *ExitResult) 170 go d.handleWaitTask(ctx, id, ch) 171 return ch, nil 172 } 173 174 func (d *driverPluginClient) handleWaitTask(ctx context.Context, id string, ch chan *ExitResult) { 175 defer close(ch) 176 var result ExitResult 177 req := &proto.WaitTaskRequest{ 178 TaskId: id, 179 } 180 181 // Join the passed context and the shutdown context 182 joinedCtx, _ := joincontext.Join(ctx, d.doneCtx) 183 184 resp, err := d.client.WaitTask(joinedCtx, req) 185 if err != nil { 186 result.Err = grpcutils.HandleReqCtxGrpcErr(err, ctx, d.doneCtx) 187 } else { 188 result.ExitCode = int(resp.Result.ExitCode) 189 result.Signal = int(resp.Result.Signal) 190 result.OOMKilled = resp.Result.OomKilled 191 if len(resp.Err) > 0 { 192 result.Err = errors.New(resp.Err) 193 } 194 } 195 ch <- &result 196 } 197 198 // StopTask stops the task with the given taskID. A timeout and signal can be 199 // given to control a graceful termination of the task. The driver will send the 200 // given signal to the task and wait for the given timeout for it to exit. If the 201 // task does not exit within the timeout it will be forcefully killed. 202 func (d *driverPluginClient) StopTask(taskID string, timeout time.Duration, signal string) error { 203 req := &proto.StopTaskRequest{ 204 TaskId: taskID, 205 Timeout: ptypes.DurationProto(timeout), 206 Signal: signal, 207 } 208 209 _, err := d.client.StopTask(d.doneCtx, req) 210 return grpcutils.HandleGrpcErr(err, d.doneCtx) 211 } 212 213 // DestroyTask removes the task from the driver's in memory state. The task 214 // cannot be running unless force is set to true. If force is set to true the 215 // driver will forcefully terminate the task before removing it. 216 func (d *driverPluginClient) DestroyTask(taskID string, force bool) error { 217 req := &proto.DestroyTaskRequest{ 218 TaskId: taskID, 219 Force: force, 220 } 221 222 _, err := d.client.DestroyTask(d.doneCtx, req) 223 return grpcutils.HandleGrpcErr(err, d.doneCtx) 224 } 225 226 // InspectTask returns status information for a task 227 func (d *driverPluginClient) InspectTask(taskID string) (*TaskStatus, error) { 228 req := &proto.InspectTaskRequest{TaskId: taskID} 229 230 resp, err := d.client.InspectTask(d.doneCtx, req) 231 if err != nil { 232 return nil, grpcutils.HandleGrpcErr(err, d.doneCtx) 233 } 234 235 status, err := taskStatusFromProto(resp.Task) 236 if err != nil { 237 return nil, err 238 } 239 240 if resp.Driver != nil { 241 status.DriverAttributes = resp.Driver.Attributes 242 } 243 if resp.NetworkOverride != nil { 244 status.NetworkOverride = &DriverNetwork{ 245 PortMap: map[string]int{}, 246 IP: resp.NetworkOverride.Addr, 247 AutoAdvertise: resp.NetworkOverride.AutoAdvertise, 248 } 249 for k, v := range resp.NetworkOverride.PortMap { 250 status.NetworkOverride.PortMap[k] = int(v) 251 } 252 } 253 254 return status, nil 255 } 256 257 // TaskStats returns resource usage statistics for the task 258 func (d *driverPluginClient) TaskStats(ctx context.Context, taskID string, interval time.Duration) (<-chan *cstructs.TaskResourceUsage, error) { 259 req := &proto.TaskStatsRequest{ 260 TaskId: taskID, 261 CollectionInterval: ptypes.DurationProto(interval), 262 } 263 ctx, _ = joincontext.Join(ctx, d.doneCtx) 264 stream, err := d.client.TaskStats(ctx, req) 265 if err != nil { 266 st := status.Convert(err) 267 if len(st.Details()) > 0 { 268 if rec, ok := st.Details()[0].(*sproto.RecoverableError); ok { 269 return nil, structs.NewRecoverableError(err, rec.Recoverable) 270 } 271 } 272 return nil, err 273 } 274 275 ch := make(chan *cstructs.TaskResourceUsage, 1) 276 go d.handleStats(ctx, ch, stream) 277 278 return ch, nil 279 } 280 281 func (d *driverPluginClient) handleStats(ctx context.Context, ch chan<- *cstructs.TaskResourceUsage, stream proto.Driver_TaskStatsClient) { 282 defer close(ch) 283 for { 284 resp, err := stream.Recv() 285 if ctx.Err() != nil { 286 // Context canceled; exit gracefully 287 return 288 } 289 290 if err != nil { 291 if err != io.EOF { 292 d.logger.Error("error receiving stream from TaskStats driver RPC, closing stream", "error", err) 293 } 294 295 // End of stream 296 return 297 } 298 299 stats, err := TaskStatsFromProto(resp.Stats) 300 if err != nil { 301 d.logger.Error("failed to decode stats from RPC", "error", err, "stats", resp.Stats) 302 continue 303 } 304 305 select { 306 case ch <- stats: 307 case <-ctx.Done(): 308 } 309 } 310 } 311 312 // TaskEvents returns a channel that will receive events from the driver about all 313 // tasks such as lifecycle events, terminal errors, etc. 314 func (d *driverPluginClient) TaskEvents(ctx context.Context) (<-chan *TaskEvent, error) { 315 req := &proto.TaskEventsRequest{} 316 317 // Join the passed context and the shutdown context 318 joinedCtx, _ := joincontext.Join(ctx, d.doneCtx) 319 320 stream, err := d.client.TaskEvents(joinedCtx, req) 321 if err != nil { 322 return nil, grpcutils.HandleReqCtxGrpcErr(err, ctx, d.doneCtx) 323 } 324 325 ch := make(chan *TaskEvent, 1) 326 go d.handleTaskEvents(ctx, ch, stream) 327 return ch, nil 328 } 329 330 func (d *driverPluginClient) handleTaskEvents(reqCtx context.Context, ch chan *TaskEvent, stream proto.Driver_TaskEventsClient) { 331 defer close(ch) 332 for { 333 ev, err := stream.Recv() 334 if err != nil { 335 if err != io.EOF { 336 ch <- &TaskEvent{ 337 Err: grpcutils.HandleReqCtxGrpcErr(err, reqCtx, d.doneCtx), 338 } 339 } 340 341 // End the stream 342 return 343 } 344 345 timestamp, _ := ptypes.Timestamp(ev.Timestamp) 346 event := &TaskEvent{ 347 TaskID: ev.TaskId, 348 AllocID: ev.AllocId, 349 TaskName: ev.TaskName, 350 Annotations: ev.Annotations, 351 Message: ev.Message, 352 Timestamp: timestamp, 353 } 354 select { 355 case <-reqCtx.Done(): 356 return 357 case ch <- event: 358 } 359 } 360 } 361 362 // SignalTask will send the given signal to the specified task 363 func (d *driverPluginClient) SignalTask(taskID string, signal string) error { 364 req := &proto.SignalTaskRequest{ 365 TaskId: taskID, 366 Signal: signal, 367 } 368 _, err := d.client.SignalTask(d.doneCtx, req) 369 return grpcutils.HandleGrpcErr(err, d.doneCtx) 370 } 371 372 // ExecTask will run the given command within the execution context of the task. 373 // The driver will wait for the given timeout for the command to complete before 374 // terminating it. The stdout and stderr of the command will be return to the caller, 375 // along with other exit information such as exit code. 376 func (d *driverPluginClient) ExecTask(taskID string, cmd []string, timeout time.Duration) (*ExecTaskResult, error) { 377 req := &proto.ExecTaskRequest{ 378 TaskId: taskID, 379 Command: cmd, 380 Timeout: ptypes.DurationProto(timeout), 381 } 382 383 resp, err := d.client.ExecTask(d.doneCtx, req) 384 if err != nil { 385 return nil, grpcutils.HandleGrpcErr(err, d.doneCtx) 386 } 387 388 result := &ExecTaskResult{ 389 Stdout: resp.Stdout, 390 Stderr: resp.Stderr, 391 ExitResult: exitResultFromProto(resp.Result), 392 } 393 394 return result, nil 395 } 396 397 var _ ExecTaskStreamingRawDriver = (*driverPluginClient)(nil) 398 399 func (d *driverPluginClient) ExecTaskStreamingRaw(ctx context.Context, 400 taskID string, 401 command []string, 402 tty bool, 403 execStream ExecTaskStream) error { 404 405 stream, err := d.client.ExecTaskStreaming(ctx) 406 if err != nil { 407 return grpcutils.HandleGrpcErr(err, d.doneCtx) 408 } 409 410 err = stream.Send(&proto.ExecTaskStreamingRequest{ 411 Setup: &proto.ExecTaskStreamingRequest_Setup{ 412 TaskId: taskID, 413 Command: command, 414 Tty: tty, 415 }, 416 }) 417 if err != nil { 418 return grpcutils.HandleGrpcErr(err, d.doneCtx) 419 } 420 421 errCh := make(chan error, 1) 422 423 go func() { 424 for { 425 m, err := execStream.Recv() 426 if err == io.EOF { 427 return 428 } else if err != nil { 429 errCh <- err 430 return 431 } 432 433 if err := stream.Send(m); err != nil { 434 errCh <- err 435 return 436 } 437 438 } 439 }() 440 441 for { 442 select { 443 case err := <-errCh: 444 return err 445 default: 446 } 447 448 m, err := stream.Recv() 449 if err == io.EOF { 450 // Once we get to the end of stream successfully, we can ignore errCh: 451 // e.g. input write failures after process terminates shouldn't cause method to fail 452 return nil 453 } else if err != nil { 454 return err 455 } 456 457 if err := execStream.Send(m); err != nil { 458 return err 459 } 460 } 461 }