github.com/quite/nomad@v0.8.6/client/driver/mock_driver.go (about) 1 package driver 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io" 9 "log" 10 "os" 11 "strconv" 12 "strings" 13 "time" 14 15 "github.com/mitchellh/mapstructure" 16 17 "github.com/hashicorp/nomad/client/driver/logging" 18 dstructs "github.com/hashicorp/nomad/client/driver/structs" 19 cstructs "github.com/hashicorp/nomad/client/structs" 20 "github.com/hashicorp/nomad/nomad/structs" 21 ) 22 23 const ( 24 // ShutdownPeriodicAfter is a config key that can be used during tests to 25 // "stop" a previously-functioning driver, allowing for testing of periodic 26 // drivers and fingerprinters 27 ShutdownPeriodicAfter = "test.shutdown_periodic_after" 28 29 // ShutdownPeriodicDuration is a config option that can be used during tests 30 // to "stop" a previously functioning driver after the specified duration 31 // (specified in seconds) for testing of periodic drivers and fingerprinters. 32 ShutdownPeriodicDuration = "test.shutdown_periodic_duration" 33 34 mockDriverName = "driver.mock_driver" 35 ) 36 37 // MockDriverConfig is the driver configuration for the MockDriver 38 type MockDriverConfig struct { 39 40 // StartErr specifies the error that should be returned when starting the 41 // mock driver. 42 StartErr string `mapstructure:"start_error"` 43 44 // StartErrRecoverable marks the error returned is recoverable 45 StartErrRecoverable bool `mapstructure:"start_error_recoverable"` 46 47 // StartBlockFor specifies a duration in which to block before returning 48 StartBlockFor time.Duration `mapstructure:"start_block_for"` 49 50 // KillAfter is the duration after which the mock driver indicates the task 51 // has exited after getting the initial SIGINT signal 52 KillAfter time.Duration `mapstructure:"kill_after"` 53 54 // RunFor is the duration for which the fake task runs for. After this 55 // period the MockDriver responds to the task running indicating that the 56 // task has terminated 57 RunFor time.Duration `mapstructure:"run_for"` 58 59 // ExitCode is the exit code with which the MockDriver indicates the task 60 // has exited 61 ExitCode int `mapstructure:"exit_code"` 62 63 // ExitSignal is the signal with which the MockDriver indicates the task has 64 // been killed 65 ExitSignal int `mapstructure:"exit_signal"` 66 67 // ExitErrMsg is the error message that the task returns while exiting 68 ExitErrMsg string `mapstructure:"exit_err_msg"` 69 70 // SignalErr is the error message that the task returns if signalled 71 SignalErr string `mapstructure:"signal_error"` 72 73 // DriverIP will be returned as the DriverNetwork.IP from Start() 74 DriverIP string `mapstructure:"driver_ip"` 75 76 // DriverAdvertise will be returned as DriverNetwork.AutoAdvertise from 77 // Start(). 78 DriverAdvertise bool `mapstructure:"driver_advertise"` 79 80 // DriverPortMap will parse a label:number pair and return it in 81 // DriverNetwork.PortMap from Start(). 82 DriverPortMap string `mapstructure:"driver_port_map"` 83 84 // StdoutString is the string that should be sent to stdout 85 StdoutString string `mapstructure:"stdout_string"` 86 87 // StdoutRepeat is the number of times the output should be sent. 88 StdoutRepeat int `mapstructure:"stdout_repeat"` 89 90 // StdoutRepeatDur is the duration between repeated outputs. 91 StdoutRepeatDur time.Duration `mapstructure:"stdout_repeat_duration"` 92 } 93 94 // MockDriver is a driver which is used for testing purposes 95 type MockDriver struct { 96 DriverContext 97 98 cleanupFailNum int 99 100 // shutdownFingerprintTime is the time up to which the driver will be up 101 shutdownFingerprintTime time.Time 102 } 103 104 // NewMockDriver is a factory method which returns a new Mock Driver 105 func NewMockDriver(ctx *DriverContext) Driver { 106 md := &MockDriver{DriverContext: *ctx} 107 108 // if the shutdown configuration options are set, start the timer here. 109 // This config option defaults to false 110 if ctx.config != nil && ctx.config.ReadBoolDefault(ShutdownPeriodicAfter, false) { 111 duration, err := ctx.config.ReadInt(ShutdownPeriodicDuration) 112 if err != nil { 113 errMsg := fmt.Sprintf("unable to read config option for shutdown_periodic_duration %v, got err %s", duration, err.Error()) 114 panic(errMsg) 115 } 116 md.shutdownFingerprintTime = time.Now().Add(time.Second * time.Duration(duration)) 117 } 118 119 return md 120 } 121 122 func (d *MockDriver) Abilities() DriverAbilities { 123 return DriverAbilities{ 124 SendSignals: false, 125 Exec: true, 126 } 127 } 128 129 func (d *MockDriver) FSIsolation() cstructs.FSIsolation { 130 return cstructs.FSIsolationNone 131 } 132 133 func (d *MockDriver) Prestart(*ExecContext, *structs.Task) (*PrestartResponse, error) { 134 return nil, nil 135 } 136 137 // Start starts the mock driver 138 func (m *MockDriver) Start(ctx *ExecContext, task *structs.Task) (*StartResponse, error) { 139 var driverConfig MockDriverConfig 140 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 141 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 142 WeaklyTypedInput: true, 143 Result: &driverConfig, 144 }) 145 if err != nil { 146 return nil, err 147 } 148 if err := dec.Decode(task.Config); err != nil { 149 return nil, err 150 } 151 152 if driverConfig.StartBlockFor != 0 { 153 time.Sleep(driverConfig.StartBlockFor) 154 } 155 156 if driverConfig.StartErr != "" { 157 return nil, structs.NewRecoverableError(errors.New(driverConfig.StartErr), driverConfig.StartErrRecoverable) 158 } 159 160 // Create the driver network 161 net := &cstructs.DriverNetwork{ 162 IP: driverConfig.DriverIP, 163 AutoAdvertise: driverConfig.DriverAdvertise, 164 } 165 if raw := driverConfig.DriverPortMap; len(raw) > 0 { 166 parts := strings.Split(raw, ":") 167 if len(parts) != 2 { 168 return nil, fmt.Errorf("malformed port map: %q", raw) 169 } 170 port, err := strconv.Atoi(parts[1]) 171 if err != nil { 172 return nil, fmt.Errorf("malformed port map: %q -- error: %v", raw, err) 173 } 174 net.PortMap = map[string]int{parts[0]: port} 175 } 176 177 h := mockDriverHandle{ 178 ctx: ctx, 179 task: task, 180 taskName: task.Name, 181 runFor: driverConfig.RunFor, 182 killAfter: driverConfig.KillAfter, 183 killTimeout: task.KillTimeout, 184 exitCode: driverConfig.ExitCode, 185 exitSignal: driverConfig.ExitSignal, 186 stdoutString: driverConfig.StdoutString, 187 stdoutRepeat: driverConfig.StdoutRepeat, 188 stdoutRepeatDur: driverConfig.StdoutRepeatDur, 189 logger: m.logger, 190 doneCh: make(chan struct{}), 191 waitCh: make(chan *dstructs.WaitResult, 1), 192 } 193 if driverConfig.ExitErrMsg != "" { 194 h.exitErr = errors.New(driverConfig.ExitErrMsg) 195 } 196 if driverConfig.SignalErr != "" { 197 h.signalErr = fmt.Errorf(driverConfig.SignalErr) 198 } 199 m.logger.Printf("[DEBUG] driver.mock: starting task %q", task.Name) 200 go h.run() 201 202 return &StartResponse{Handle: &h, Network: net}, nil 203 } 204 205 // Cleanup deletes all keys except for Config.Options["cleanup_fail_on"] for 206 // Config.Options["cleanup_fail_num"] times. For failures it will return a 207 // recoverable error. 208 func (m *MockDriver) Cleanup(ctx *ExecContext, res *CreatedResources) error { 209 if res == nil { 210 panic("Cleanup should not be called with nil *CreatedResources") 211 } 212 213 var err error 214 failn, _ := strconv.Atoi(m.config.Options["cleanup_fail_num"]) 215 failk := m.config.Options["cleanup_fail_on"] 216 for k := range res.Resources { 217 if k == failk && m.cleanupFailNum < failn { 218 m.cleanupFailNum++ 219 err = structs.NewRecoverableError(fmt.Errorf("mock_driver failure on %q call %d/%d", k, m.cleanupFailNum, failn), true) 220 } else { 221 delete(res.Resources, k) 222 } 223 } 224 return err 225 } 226 227 // Validate validates the mock driver configuration 228 func (m *MockDriver) Validate(map[string]interface{}) error { 229 return nil 230 } 231 232 // Fingerprint fingerprints a node and returns if MockDriver is enabled 233 func (m *MockDriver) Fingerprint(req *cstructs.FingerprintRequest, resp *cstructs.FingerprintResponse) error { 234 switch { 235 // If the driver is configured to shut down after a period of time, and the 236 // current time is after the time which the node should shut down, simulate 237 // driver failure 238 case !m.shutdownFingerprintTime.IsZero() && time.Now().After(m.shutdownFingerprintTime): 239 resp.RemoveAttribute(mockDriverName) 240 default: 241 resp.AddAttribute(mockDriverName, "1") 242 resp.Detected = true 243 } 244 return nil 245 } 246 247 // When testing, poll for updates 248 func (m *MockDriver) Periodic() (bool, time.Duration) { 249 return true, 500 * time.Millisecond 250 } 251 252 // HealthCheck implements the interface for HealthCheck, and indicates the current 253 // health status of the mock driver. 254 func (m *MockDriver) HealthCheck(req *cstructs.HealthCheckRequest, resp *cstructs.HealthCheckResponse) error { 255 switch { 256 case !m.shutdownFingerprintTime.IsZero() && time.Now().After(m.shutdownFingerprintTime): 257 notHealthy := &structs.DriverInfo{ 258 Healthy: false, 259 HealthDescription: "not running", 260 UpdateTime: time.Now(), 261 } 262 resp.AddDriverInfo("mock_driver", notHealthy) 263 return nil 264 default: 265 healthy := &structs.DriverInfo{ 266 Healthy: true, 267 HealthDescription: "running", 268 UpdateTime: time.Now(), 269 } 270 resp.AddDriverInfo("mock_driver", healthy) 271 return nil 272 } 273 } 274 275 // GetHealthCheckInterval implements the interface for HealthCheck and indicates 276 // that mock driver should be checked periodically. Returns a boolean 277 // indicating if it should be checked, and the duration at which to do this 278 // check. 279 func (m *MockDriver) GetHealthCheckInterval(req *cstructs.HealthCheckIntervalRequest, resp *cstructs.HealthCheckIntervalResponse) error { 280 resp.Eligible = true 281 resp.Period = 1 * time.Second 282 return nil 283 } 284 285 // MockDriverHandle is a driver handler which supervises a mock task 286 type mockDriverHandle struct { 287 ctx *ExecContext 288 task *structs.Task 289 taskName string 290 runFor time.Duration 291 killAfter time.Duration 292 killTimeout time.Duration 293 exitCode int 294 exitSignal int 295 exitErr error 296 signalErr error 297 logger *log.Logger 298 stdoutString string 299 stdoutRepeat int 300 stdoutRepeatDur time.Duration 301 waitCh chan *dstructs.WaitResult 302 doneCh chan struct{} 303 } 304 305 type mockDriverID struct { 306 TaskName string 307 RunFor time.Duration 308 KillAfter time.Duration 309 KillTimeout time.Duration 310 ExitCode int 311 ExitSignal int 312 ExitErr error 313 SignalErr error 314 } 315 316 func (h *mockDriverHandle) ID() string { 317 id := mockDriverID{ 318 TaskName: h.taskName, 319 RunFor: h.runFor, 320 KillAfter: h.killAfter, 321 KillTimeout: h.killTimeout, 322 ExitCode: h.exitCode, 323 ExitSignal: h.exitSignal, 324 ExitErr: h.exitErr, 325 SignalErr: h.signalErr, 326 } 327 328 data, err := json.Marshal(id) 329 if err != nil { 330 h.logger.Printf("[ERR] driver.mock_driver: failed to marshal ID to JSON: %s", err) 331 } 332 return string(data) 333 } 334 335 // Open re-connects the driver to the running task 336 func (m *MockDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) { 337 id := &mockDriverID{} 338 if err := json.Unmarshal([]byte(handleID), id); err != nil { 339 return nil, fmt.Errorf("Failed to parse handle '%s': %v", handleID, err) 340 } 341 342 h := mockDriverHandle{ 343 taskName: id.TaskName, 344 runFor: id.RunFor, 345 killAfter: id.KillAfter, 346 killTimeout: id.KillTimeout, 347 exitCode: id.ExitCode, 348 exitSignal: id.ExitSignal, 349 exitErr: id.ExitErr, 350 signalErr: id.SignalErr, 351 logger: m.logger, 352 doneCh: make(chan struct{}), 353 waitCh: make(chan *dstructs.WaitResult, 1), 354 } 355 356 go h.run() 357 return &h, nil 358 } 359 360 func (h *mockDriverHandle) WaitCh() chan *dstructs.WaitResult { 361 return h.waitCh 362 } 363 364 func (h *mockDriverHandle) Exec(ctx context.Context, cmd string, args []string) ([]byte, int, error) { 365 h.logger.Printf("[DEBUG] driver.mock: Exec(%q, %q)", cmd, args) 366 return []byte(fmt.Sprintf("Exec(%q, %q)", cmd, args)), 0, nil 367 } 368 369 // TODO Implement when we need it. 370 func (h *mockDriverHandle) Update(task *structs.Task) error { 371 h.killTimeout = task.KillTimeout 372 return nil 373 } 374 375 // TODO Implement when we need it. 376 func (h *mockDriverHandle) Signal(s os.Signal) error { 377 return h.signalErr 378 } 379 380 // Kill kills a mock task 381 func (h *mockDriverHandle) Kill() error { 382 h.logger.Printf("[DEBUG] driver.mock: killing task %q after %s or kill timeout: %v", h.taskName, h.killAfter, h.killTimeout) 383 select { 384 case <-h.doneCh: 385 case <-time.After(h.killAfter): 386 select { 387 case <-h.doneCh: 388 // already closed 389 default: 390 close(h.doneCh) 391 } 392 case <-time.After(h.killTimeout): 393 h.logger.Printf("[DEBUG] driver.mock: terminating task %q", h.taskName) 394 select { 395 case <-h.doneCh: 396 // already closed 397 default: 398 close(h.doneCh) 399 } 400 } 401 return nil 402 } 403 404 // TODO Implement when we need it. 405 func (h *mockDriverHandle) Stats() (*cstructs.TaskResourceUsage, error) { 406 return nil, nil 407 } 408 409 // run waits for the configured amount of time and then indicates the task has 410 // terminated 411 func (h *mockDriverHandle) run() { 412 // Setup logging output 413 if h.stdoutString != "" { 414 go h.handleLogging() 415 } 416 417 timer := time.NewTimer(h.runFor) 418 defer timer.Stop() 419 for { 420 select { 421 case <-timer.C: 422 select { 423 case <-h.doneCh: 424 // already closed 425 default: 426 close(h.doneCh) 427 } 428 case <-h.doneCh: 429 h.logger.Printf("[DEBUG] driver.mock: finished running task %q", h.taskName) 430 h.waitCh <- dstructs.NewWaitResult(h.exitCode, h.exitSignal, h.exitErr) 431 return 432 } 433 } 434 } 435 436 // handleLogging handles logging stdout messages 437 func (h *mockDriverHandle) handleLogging() { 438 if h.stdoutString == "" { 439 return 440 } 441 442 // Setup a log rotator 443 logFileSize := int64(h.task.LogConfig.MaxFileSizeMB * 1024 * 1024) 444 lro, err := logging.NewFileRotator(h.ctx.TaskDir.LogDir, fmt.Sprintf("%v.stdout", h.taskName), 445 h.task.LogConfig.MaxFiles, logFileSize, h.logger) 446 if err != nil { 447 h.exitErr = err 448 close(h.doneCh) 449 h.logger.Printf("[ERR] mock_driver: failed to setup file rotator: %v", err) 450 return 451 } 452 defer lro.Close() 453 454 // Do initial write to stdout. 455 if _, err := io.WriteString(lro, h.stdoutString); err != nil { 456 h.exitErr = err 457 close(h.doneCh) 458 h.logger.Printf("[ERR] mock_driver: failed to write to stdout: %v", err) 459 return 460 } 461 462 for i := 0; i < h.stdoutRepeat; i++ { 463 select { 464 case <-h.doneCh: 465 return 466 case <-time.After(h.stdoutRepeatDur): 467 if _, err := io.WriteString(lro, h.stdoutString); err != nil { 468 h.exitErr = err 469 close(h.doneCh) 470 h.logger.Printf("[ERR] mock_driver: failed to write to stdout: %v", err) 471 return 472 } 473 } 474 } 475 }