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