github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/drivers/mock/driver.go (about) 1 package mock 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "io/ioutil" 8 "math/rand" 9 "strconv" 10 "strings" 11 "sync" 12 "time" 13 14 hclog "github.com/hashicorp/go-hclog" 15 "github.com/hashicorp/nomad/drivers/shared/eventer" 16 "github.com/hashicorp/nomad/helper/pluginutils/loader" 17 "github.com/hashicorp/nomad/nomad/structs" 18 "github.com/hashicorp/nomad/plugins/base" 19 "github.com/hashicorp/nomad/plugins/drivers" 20 "github.com/hashicorp/nomad/plugins/shared/hclspec" 21 pstructs "github.com/hashicorp/nomad/plugins/shared/structs" 22 ) 23 24 const ( 25 // pluginName is the name of the plugin 26 pluginName = "mock_driver" 27 28 // fingerprintPeriod is the interval at which the driver will send fingerprint responses 29 fingerprintPeriod = 500 * time.Millisecond 30 31 // taskHandleVersion is the version of task handle which this driver sets 32 // and understands how to decode driver state 33 taskHandleVersion = 1 34 ) 35 36 var ( 37 // PluginID is the mock driver plugin metadata registered in the plugin 38 // catalog. 39 PluginID = loader.PluginID{ 40 Name: pluginName, 41 PluginType: base.PluginTypeDriver, 42 } 43 44 // PluginConfig is the mock driver factory function registered in the 45 // plugin catalog. 46 PluginConfig = &loader.InternalPluginConfig{ 47 Config: map[string]interface{}{}, 48 Factory: func(l hclog.Logger) interface{} { return NewMockDriver(l) }, 49 } 50 51 // pluginInfo is the response returned for the PluginInfo RPC 52 pluginInfo = &base.PluginInfoResponse{ 53 Type: base.PluginTypeDriver, 54 PluginApiVersions: []string{drivers.ApiVersion010}, 55 PluginVersion: "0.1.0", 56 Name: pluginName, 57 } 58 59 // configSpec is the hcl specification returned by the ConfigSchema RPC 60 configSpec = hclspec.NewObject(map[string]*hclspec.Spec{ 61 "fs_isolation": hclspec.NewDefault( 62 hclspec.NewAttr("fs_isolation", "string", false), 63 hclspec.NewLiteral(fmt.Sprintf("%q", drivers.FSIsolationNone)), 64 ), 65 "shutdown_periodic_after": hclspec.NewDefault( 66 hclspec.NewAttr("shutdown_periodic_after", "bool", false), 67 hclspec.NewLiteral("false"), 68 ), 69 "shutdown_periodic_duration": hclspec.NewAttr("shutdown_periodic_duration", "number", false), 70 }) 71 72 // taskConfigSpec is the hcl specification for the driver config section of 73 // a task within a job. It is returned in the TaskConfigSchema RPC 74 taskConfigSpec = hclspec.NewObject(map[string]*hclspec.Spec{ 75 "start_error": hclspec.NewAttr("start_error", "string", false), 76 "start_error_recoverable": hclspec.NewAttr("start_error_recoverable", "bool", false), 77 "start_block_for": hclspec.NewAttr("start_block_for", "string", false), 78 "kill_after": hclspec.NewAttr("kill_after", "string", false), 79 "plugin_exit_after": hclspec.NewAttr("plugin_exit_after", "string", false), 80 "driver_ip": hclspec.NewAttr("driver_ip", "string", false), 81 "driver_advertise": hclspec.NewAttr("driver_advertise", "bool", false), 82 "driver_port_map": hclspec.NewAttr("driver_port_map", "string", false), 83 84 "run_for": hclspec.NewAttr("run_for", "string", false), 85 "exit_code": hclspec.NewAttr("exit_code", "number", false), 86 "exit_signal": hclspec.NewAttr("exit_signal", "number", false), 87 "exit_err_msg": hclspec.NewAttr("exit_err_msg", "string", false), 88 "signal_error": hclspec.NewAttr("signal_error", "string", false), 89 "stdout_string": hclspec.NewAttr("stdout_string", "string", false), 90 "stdout_repeat": hclspec.NewAttr("stdout_repeat", "number", false), 91 "stdout_repeat_duration": hclspec.NewAttr("stdout_repeat_duration", "string", false), 92 "stderr_string": hclspec.NewAttr("stderr_string", "string", false), 93 "stderr_repeat": hclspec.NewAttr("stderr_repeat", "number", false), 94 "stderr_repeat_duration": hclspec.NewAttr("stderr_repeat_duration", "string", false), 95 96 "exec_command": hclspec.NewBlock("exec_command", false, hclspec.NewObject(map[string]*hclspec.Spec{ 97 "run_for": hclspec.NewAttr("run_for", "string", false), 98 "exit_code": hclspec.NewAttr("exit_code", "number", false), 99 "exit_signal": hclspec.NewAttr("exit_signal", "number", false), 100 "exit_err_msg": hclspec.NewAttr("exit_err_msg", "string", false), 101 "signal_error": hclspec.NewAttr("signal_error", "string", false), 102 "stdout_string": hclspec.NewAttr("stdout_string", "string", false), 103 "stdout_repeat": hclspec.NewAttr("stdout_repeat", "number", false), 104 "stdout_repeat_duration": hclspec.NewAttr("stdout_repeat_duration", "string", false), 105 "stderr_string": hclspec.NewAttr("stderr_string", "string", false), 106 "stderr_repeat": hclspec.NewAttr("stderr_repeat", "number", false), 107 "stderr_repeat_duration": hclspec.NewAttr("stderr_repeat_duration", "string", false), 108 })), 109 }) 110 ) 111 112 // Driver is a mock DriverPlugin implementation 113 type Driver struct { 114 // eventer is used to handle multiplexing of TaskEvents calls such that an 115 // event can be broadcast to all callers 116 eventer *eventer.Eventer 117 118 // capabilities is returned by the Capabilities RPC and indicates what 119 // optional features this driver supports 120 capabilities *drivers.Capabilities 121 122 // config is the driver configuration set by the SetConfig RPC 123 config *Config 124 125 // tasks is the in memory datastore mapping taskIDs to mockDriverHandles 126 tasks *taskStore 127 128 // ctx is the context for the driver. It is passed to other subsystems to 129 // coordinate shutdown 130 ctx context.Context 131 132 // signalShutdown is called when the driver is shutting down and cancels the 133 // ctx passed to any subsystems 134 signalShutdown context.CancelFunc 135 136 shutdownFingerprintTime time.Time 137 138 // lastDriverTaskConfig is the last *drivers.TaskConfig passed to StartTask 139 lastDriverTaskConfig *drivers.TaskConfig 140 141 // lastTaskConfig is the last decoded *TaskConfig created by StartTask 142 lastTaskConfig *TaskConfig 143 144 // lastMu guards access to last[Driver]TaskConfig 145 lastMu sync.Mutex 146 147 // logger will log to the Nomad agent 148 logger hclog.Logger 149 } 150 151 // NewMockDriver returns a new DriverPlugin implementation 152 func NewMockDriver(logger hclog.Logger) drivers.DriverPlugin { 153 ctx, cancel := context.WithCancel(context.Background()) 154 logger = logger.Named(pluginName) 155 156 capabilities := &drivers.Capabilities{ 157 SendSignals: true, 158 Exec: true, 159 FSIsolation: drivers.FSIsolationNone, 160 } 161 162 return &Driver{ 163 eventer: eventer.NewEventer(ctx, logger), 164 capabilities: capabilities, 165 config: &Config{}, 166 tasks: newTaskStore(), 167 ctx: ctx, 168 signalShutdown: cancel, 169 logger: logger, 170 } 171 } 172 173 // Config is the configuration for the driver that applies to all tasks 174 type Config struct { 175 FSIsolation string `codec:"fs_isolation"` 176 177 // ShutdownPeriodicAfter is a toggle that can be used during tests to 178 // "stop" a previously-functioning driver, allowing for testing of periodic 179 // drivers and fingerprinters 180 ShutdownPeriodicAfter bool `codec:"shutdown_periodic_after"` 181 182 // ShutdownPeriodicDuration is a option that can be used during tests 183 // to "stop" a previously functioning driver after the specified duration 184 // for testing of periodic drivers and fingerprinters. 185 ShutdownPeriodicDuration time.Duration `codec:"shutdown_periodic_duration"` 186 } 187 188 type Command struct { 189 // RunFor is the duration for which the fake task runs for. After this 190 // period the MockDriver responds to the task running indicating that the 191 // task has terminated 192 RunFor string `codec:"run_for"` 193 runForDuration time.Duration 194 195 // ExitCode is the exit code with which the MockDriver indicates the task 196 // has exited 197 ExitCode int `codec:"exit_code"` 198 199 // ExitSignal is the signal with which the MockDriver indicates the task has 200 // been killed 201 ExitSignal int `codec:"exit_signal"` 202 203 // ExitErrMsg is the error message that the task returns while exiting 204 ExitErrMsg string `codec:"exit_err_msg"` 205 206 // SignalErr is the error message that the task returns if signalled 207 SignalErr string `codec:"signal_error"` 208 209 // StdoutString is the string that should be sent to stdout 210 StdoutString string `codec:"stdout_string"` 211 212 // StdoutRepeat is the number of times the output should be sent. 213 StdoutRepeat int `codec:"stdout_repeat"` 214 215 // StdoutRepeatDur is the duration between repeated outputs. 216 StdoutRepeatDur string `codec:"stdout_repeat_duration"` 217 stdoutRepeatDuration time.Duration 218 219 // StderrString is the string that should be sent to stderr 220 StderrString string `codec:"stderr_string"` 221 222 // StderrRepeat is the number of times the errput should be sent. 223 StderrRepeat int `codec:"stderr_repeat"` 224 225 // StderrRepeatDur is the duration between repeated errputs. 226 StderrRepeatDur string `codec:"stderr_repeat_duration"` 227 stderrRepeatDuration time.Duration 228 } 229 230 // TaskConfig is the driver configuration of a task within a job 231 type TaskConfig struct { 232 Command 233 234 ExecCommand *Command `codec:"exec_command"` 235 236 // PluginExitAfter is the duration after which the mock driver indicates the 237 // plugin has exited via the WaitTask call. 238 PluginExitAfter string `codec:"plugin_exit_after"` 239 pluginExitAfterDuration time.Duration 240 241 // StartErr specifies the error that should be returned when starting the 242 // mock driver. 243 StartErr string `codec:"start_error"` 244 245 // StartErrRecoverable marks the error returned is recoverable 246 StartErrRecoverable bool `codec:"start_error_recoverable"` 247 248 // StartBlockFor specifies a duration in which to block before returning 249 StartBlockFor string `codec:"start_block_for"` 250 startBlockForDuration time.Duration 251 252 // KillAfter is the duration after which the mock driver indicates the task 253 // has exited after getting the initial SIGINT signal 254 KillAfter string `codec:"kill_after"` 255 killAfterDuration time.Duration 256 257 // DriverIP will be returned as the DriverNetwork.IP from Start() 258 DriverIP string `codec:"driver_ip"` 259 260 // DriverAdvertise will be returned as DriverNetwork.AutoAdvertise from 261 // Start(). 262 DriverAdvertise bool `codec:"driver_advertise"` 263 264 // DriverPortMap will parse a label:number pair and return it in 265 // DriverNetwork.PortMap from Start(). 266 DriverPortMap string `codec:"driver_port_map"` 267 } 268 269 type MockTaskState struct { 270 StartedAt time.Time 271 } 272 273 func (d *Driver) PluginInfo() (*base.PluginInfoResponse, error) { 274 return pluginInfo, nil 275 } 276 277 func (d *Driver) ConfigSchema() (*hclspec.Spec, error) { 278 return configSpec, nil 279 } 280 281 func (d *Driver) SetConfig(cfg *base.Config) error { 282 var config Config 283 if len(cfg.PluginConfig) != 0 { 284 if err := base.MsgPackDecode(cfg.PluginConfig, &config); err != nil { 285 return err 286 } 287 } 288 289 d.config = &config 290 if d.config.ShutdownPeriodicAfter { 291 d.shutdownFingerprintTime = time.Now().Add(d.config.ShutdownPeriodicDuration) 292 } 293 294 isolation := config.FSIsolation 295 if isolation != "" { 296 d.capabilities.FSIsolation = drivers.FSIsolation(isolation) 297 } 298 299 return nil 300 } 301 302 func (d *Driver) TaskConfigSchema() (*hclspec.Spec, error) { 303 return taskConfigSpec, nil 304 } 305 306 func (d *Driver) Capabilities() (*drivers.Capabilities, error) { 307 return d.capabilities, nil 308 } 309 310 func (d *Driver) Fingerprint(ctx context.Context) (<-chan *drivers.Fingerprint, error) { 311 ch := make(chan *drivers.Fingerprint) 312 go d.handleFingerprint(ctx, ch) 313 return ch, nil 314 } 315 316 func (d *Driver) handleFingerprint(ctx context.Context, ch chan *drivers.Fingerprint) { 317 ticker := time.NewTimer(0) 318 for { 319 select { 320 case <-ctx.Done(): 321 return 322 case <-d.ctx.Done(): 323 return 324 case <-ticker.C: 325 ticker.Reset(fingerprintPeriod) 326 ch <- d.buildFingerprint() 327 } 328 } 329 } 330 331 func (d *Driver) buildFingerprint() *drivers.Fingerprint { 332 var health drivers.HealthState 333 var desc string 334 attrs := map[string]*pstructs.Attribute{} 335 if !d.shutdownFingerprintTime.IsZero() && time.Now().After(d.shutdownFingerprintTime) { 336 health = drivers.HealthStateUndetected 337 desc = "disabled" 338 } else { 339 health = drivers.HealthStateHealthy 340 attrs["driver.mock"] = pstructs.NewBoolAttribute(true) 341 desc = drivers.DriverHealthy 342 } 343 344 return &drivers.Fingerprint{ 345 Attributes: attrs, 346 Health: health, 347 HealthDescription: desc, 348 } 349 } 350 351 func (d *Driver) RecoverTask(handle *drivers.TaskHandle) error { 352 if handle == nil { 353 return fmt.Errorf("handle cannot be nil") 354 } 355 356 // Unmarshall the driver state and create a new handle 357 var taskState MockTaskState 358 if err := handle.GetDriverState(&taskState); err != nil { 359 d.logger.Error("failed to decode task state from handle", "error", err, "task_id", handle.Config.ID) 360 return fmt.Errorf("failed to decode task state from handle: %v", err) 361 } 362 363 driverCfg, err := parseDriverConfig(handle.Config) 364 if err != nil { 365 d.logger.Error("failed to parse driver config from handle", "error", err, "task_id", handle.Config.ID, "config", hclog.Fmt("%+v", handle.Config)) 366 return fmt.Errorf("failed to parse driver config from handle: %v", err) 367 } 368 369 // Remove the plugin exit time if set 370 driverCfg.pluginExitAfterDuration = 0 371 372 // Correct the run_for time based on how long it has already been running 373 now := time.Now() 374 driverCfg.runForDuration = driverCfg.runForDuration - now.Sub(taskState.StartedAt) 375 376 h := newTaskHandle(handle.Config, driverCfg, d.logger) 377 h.Recovered = true 378 d.tasks.Set(handle.Config.ID, h) 379 go h.run() 380 return nil 381 } 382 383 func (c *Command) parseDurations() error { 384 var err error 385 if c.runForDuration, err = parseDuration(c.RunFor); err != nil { 386 return fmt.Errorf("run_for %v not a valid duration: %v", c.RunFor, err) 387 } 388 389 if c.stdoutRepeatDuration, err = parseDuration(c.StdoutRepeatDur); err != nil { 390 return fmt.Errorf("stdout_repeat_duration %v not a valid duration: %v", c.stdoutRepeatDuration, err) 391 } 392 393 if c.stderrRepeatDuration, err = parseDuration(c.StderrRepeatDur); err != nil { 394 return fmt.Errorf("stderr_repeat_duration %v not a valid duration: %v", c.stderrRepeatDuration, err) 395 } 396 397 return nil 398 } 399 400 func parseDriverConfig(cfg *drivers.TaskConfig) (*TaskConfig, error) { 401 var driverConfig TaskConfig 402 if err := cfg.DecodeDriverConfig(&driverConfig); err != nil { 403 return nil, err 404 } 405 406 var err error 407 if driverConfig.startBlockForDuration, err = parseDuration(driverConfig.StartBlockFor); err != nil { 408 return nil, fmt.Errorf("start_block_for %v not a valid duration: %v", driverConfig.StartBlockFor, err) 409 } 410 411 if driverConfig.pluginExitAfterDuration, err = parseDuration(driverConfig.PluginExitAfter); err != nil { 412 return nil, fmt.Errorf("plugin_exit_after %v not a valid duration: %v", driverConfig.PluginExitAfter, err) 413 } 414 415 if err = driverConfig.parseDurations(); err != nil { 416 return nil, err 417 } 418 419 if driverConfig.ExecCommand != nil { 420 if err = driverConfig.ExecCommand.parseDurations(); err != nil { 421 return nil, err 422 } 423 } 424 425 return &driverConfig, nil 426 } 427 428 func newTaskHandle(cfg *drivers.TaskConfig, driverConfig *TaskConfig, logger hclog.Logger) *taskHandle { 429 killCtx, killCancel := context.WithCancel(context.Background()) 430 h := &taskHandle{ 431 taskConfig: cfg, 432 command: driverConfig.Command, 433 execCommand: driverConfig.ExecCommand, 434 pluginExitAfter: driverConfig.pluginExitAfterDuration, 435 killAfter: driverConfig.killAfterDuration, 436 logger: logger.With("task_name", cfg.Name), 437 waitCh: make(chan interface{}), 438 killCh: killCtx.Done(), 439 kill: killCancel, 440 startedAt: time.Now(), 441 } 442 return h 443 } 444 445 func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drivers.DriverNetwork, error) { 446 driverConfig, err := parseDriverConfig(cfg) 447 if err != nil { 448 return nil, nil, err 449 } 450 451 if driverConfig.startBlockForDuration != 0 { 452 time.Sleep(driverConfig.startBlockForDuration) 453 } 454 455 // Store last configs 456 d.lastMu.Lock() 457 d.lastDriverTaskConfig = cfg 458 d.lastTaskConfig = driverConfig 459 d.lastMu.Unlock() 460 461 if driverConfig.StartErr != "" { 462 return nil, nil, structs.NewRecoverableError(errors.New(driverConfig.StartErr), driverConfig.StartErrRecoverable) 463 } 464 465 // Create the driver network 466 net := &drivers.DriverNetwork{ 467 IP: driverConfig.DriverIP, 468 AutoAdvertise: driverConfig.DriverAdvertise, 469 } 470 if raw := driverConfig.DriverPortMap; len(raw) > 0 { 471 parts := strings.Split(raw, ":") 472 if len(parts) != 2 { 473 return nil, nil, fmt.Errorf("malformed port map: %q", raw) 474 } 475 port, err := strconv.Atoi(parts[1]) 476 if err != nil { 477 return nil, nil, fmt.Errorf("malformed port map: %q -- error: %v", raw, err) 478 } 479 net.PortMap = map[string]int{parts[0]: port} 480 } 481 482 h := newTaskHandle(cfg, driverConfig, d.logger) 483 driverState := MockTaskState{ 484 StartedAt: h.startedAt, 485 } 486 handle := drivers.NewTaskHandle(taskHandleVersion) 487 handle.Config = cfg 488 if err := handle.SetDriverState(&driverState); err != nil { 489 d.logger.Error("failed to start task, error setting driver state", "error", err, "task_name", cfg.Name) 490 return nil, nil, fmt.Errorf("failed to set driver state: %v", err) 491 } 492 493 d.tasks.Set(cfg.ID, h) 494 495 d.logger.Debug("starting task", "task_name", cfg.Name) 496 go h.run() 497 return handle, net, nil 498 499 } 500 501 func (d *Driver) WaitTask(ctx context.Context, taskID string) (<-chan *drivers.ExitResult, error) { 502 handle, ok := d.tasks.Get(taskID) 503 if !ok { 504 return nil, drivers.ErrTaskNotFound 505 } 506 507 ch := make(chan *drivers.ExitResult) 508 go d.handleWait(ctx, handle, ch) 509 510 return ch, nil 511 512 } 513 func (d *Driver) handleWait(ctx context.Context, handle *taskHandle, ch chan *drivers.ExitResult) { 514 defer close(ch) 515 516 select { 517 case <-ctx.Done(): 518 return 519 case <-d.ctx.Done(): 520 return 521 case <-handle.waitCh: 522 ch <- handle.exitResult 523 } 524 } 525 func (d *Driver) StopTask(taskID string, timeout time.Duration, signal string) error { 526 h, ok := d.tasks.Get(taskID) 527 if !ok { 528 return drivers.ErrTaskNotFound 529 } 530 531 d.logger.Debug("killing task", "task_name", h.taskConfig.Name, "kill_after", h.killAfter) 532 533 select { 534 case <-h.waitCh: 535 d.logger.Debug("not killing task: already exited", "task_name", h.taskConfig.Name) 536 case <-time.After(h.killAfter): 537 d.logger.Debug("killing task due to kill_after", "task_name", h.taskConfig.Name) 538 h.kill() 539 } 540 return nil 541 } 542 543 func (d *Driver) DestroyTask(taskID string, force bool) error { 544 handle, ok := d.tasks.Get(taskID) 545 if !ok { 546 return drivers.ErrTaskNotFound 547 } 548 549 if handle.IsRunning() && !force { 550 return fmt.Errorf("cannot destroy running task") 551 } 552 553 d.tasks.Delete(taskID) 554 return nil 555 } 556 557 func (d *Driver) InspectTask(taskID string) (*drivers.TaskStatus, error) { 558 h, ok := d.tasks.Get(taskID) 559 if !ok { 560 return nil, drivers.ErrTaskNotFound 561 } 562 563 return h.TaskStatus(), nil 564 565 } 566 567 func (d *Driver) TaskStats(ctx context.Context, taskID string, interval time.Duration) (<-chan *drivers.TaskResourceUsage, error) { 568 ch := make(chan *drivers.TaskResourceUsage) 569 go d.handleStats(ctx, ch) 570 return ch, nil 571 } 572 573 func (d *Driver) handleStats(ctx context.Context, ch chan<- *drivers.TaskResourceUsage) { 574 timer := time.NewTimer(0) 575 for { 576 select { 577 case <-timer.C: 578 // Generate random value for the memory usage 579 s := &drivers.TaskResourceUsage{ 580 ResourceUsage: &drivers.ResourceUsage{ 581 MemoryStats: &drivers.MemoryStats{ 582 RSS: rand.Uint64(), 583 Measured: []string{"RSS"}, 584 }, 585 }, 586 Timestamp: time.Now().UTC().UnixNano(), 587 } 588 select { 589 case <-ctx.Done(): 590 return 591 case ch <- s: 592 default: 593 } 594 case <-ctx.Done(): 595 return 596 } 597 } 598 } 599 600 func (d *Driver) TaskEvents(ctx context.Context) (<-chan *drivers.TaskEvent, error) { 601 return d.eventer.TaskEvents(ctx) 602 } 603 604 func (d *Driver) SignalTask(taskID string, signal string) error { 605 h, ok := d.tasks.Get(taskID) 606 if !ok { 607 return drivers.ErrTaskNotFound 608 } 609 610 if h.command.SignalErr == "" { 611 return nil 612 } 613 614 return errors.New(h.command.SignalErr) 615 } 616 617 func (d *Driver) ExecTask(taskID string, cmd []string, timeout time.Duration) (*drivers.ExecTaskResult, error) { 618 h, ok := d.tasks.Get(taskID) 619 if !ok { 620 return nil, drivers.ErrTaskNotFound 621 } 622 623 res := drivers.ExecTaskResult{ 624 Stdout: []byte(fmt.Sprintf("Exec(%q, %q)", h.taskConfig.Name, cmd)), 625 ExitResult: &drivers.ExitResult{}, 626 } 627 return &res, nil 628 } 629 630 var _ drivers.ExecTaskStreamingDriver = (*Driver)(nil) 631 632 func (d *Driver) ExecTaskStreaming(ctx context.Context, taskID string, execOpts *drivers.ExecOptions) (*drivers.ExitResult, error) { 633 h, ok := d.tasks.Get(taskID) 634 if !ok { 635 return nil, drivers.ErrTaskNotFound 636 } 637 638 d.logger.Info("executing task", "command", h.execCommand, "task_id", taskID) 639 640 if h.execCommand == nil { 641 return nil, errors.New("no exec command is configured") 642 } 643 644 cancelCh := make(chan struct{}) 645 exitTimer := make(chan time.Time) 646 647 cmd := *h.execCommand 648 if len(execOpts.Command) == 1 && execOpts.Command[0] == "showinput" { 649 stdin, _ := ioutil.ReadAll(execOpts.Stdin) 650 cmd = Command{ 651 RunFor: "1ms", 652 StdoutString: fmt.Sprintf("TTY: %v\nStdin:\n%s\n", 653 execOpts.Tty, 654 stdin, 655 ), 656 } 657 } 658 659 return runCommand(cmd, execOpts.Stdout, execOpts.Stderr, cancelCh, exitTimer, d.logger), nil 660 } 661 662 // GetTaskConfig is unique to the mock driver and for testing purposes only. It 663 // returns the *drivers.TaskConfig passed to StartTask and the decoded 664 // *mock.TaskConfig created by the last StartTask call. 665 func (d *Driver) GetTaskConfig() (*drivers.TaskConfig, *TaskConfig) { 666 d.lastMu.Lock() 667 defer d.lastMu.Unlock() 668 return d.lastDriverTaskConfig, d.lastTaskConfig 669 } 670 671 // GetHandle is unique to the mock driver and for testing purposes only. It 672 // returns the handle of the given task ID 673 func (d *Driver) GetHandle(taskID string) *taskHandle { 674 h, _ := d.tasks.Get(taskID) 675 return h 676 } 677 678 func (d *Driver) Shutdown() { 679 d.signalShutdown() 680 } 681 682 func (d *Driver) CreateNetwork(allocID string) (*drivers.NetworkIsolationSpec, error) { 683 return nil, nil 684 } 685 686 func (d *Driver) DestroyNetwork(allocID string, spec *drivers.NetworkIsolationSpec) error { 687 return nil 688 }