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