github.com/hhrutter/nomad@v0.6.0-rc2.0.20170723054333-80c4b03f0705/client/driver/mock_driver.go (about) 1 //+build nomad_test 2 3 package driver 4 5 import ( 6 "context" 7 "encoding/json" 8 "errors" 9 "fmt" 10 "log" 11 "os" 12 "strconv" 13 "time" 14 15 "github.com/mitchellh/mapstructure" 16 17 "github.com/hashicorp/nomad/client/config" 18 dstructs "github.com/hashicorp/nomad/client/driver/structs" 19 "github.com/hashicorp/nomad/client/fingerprint" 20 cstructs "github.com/hashicorp/nomad/client/structs" 21 "github.com/hashicorp/nomad/nomad/structs" 22 ) 23 24 // Add the mock driver to the list of builtin drivers 25 func init() { 26 BuiltinDrivers["mock_driver"] = NewMockDriver 27 } 28 29 // MockDriverConfig is the driver configuration for the MockDriver 30 type MockDriverConfig struct { 31 32 // StartErr specifies the error that should be returned when starting the 33 // mock driver. 34 StartErr string `mapstructure:"start_error"` 35 36 // StartErrRecoverable marks the error returned is recoverable 37 StartErrRecoverable bool `mapstructure:"start_error_recoverable"` 38 39 // StartBlockFor specifies a duration in which to block before returning 40 StartBlockFor time.Duration `mapstructure:"start_block_for"` 41 42 // KillAfter is the duration after which the mock driver indicates the task 43 // has exited after getting the initial SIGINT signal 44 KillAfter time.Duration `mapstructure:"kill_after"` 45 46 // RunFor is the duration for which the fake task runs for. After this 47 // period the MockDriver responds to the task running indicating that the 48 // task has terminated 49 RunFor time.Duration `mapstructure:"run_for"` 50 51 // ExitCode is the exit code with which the MockDriver indicates the task 52 // has exited 53 ExitCode int `mapstructure:"exit_code"` 54 55 // ExitSignal is the signal with which the MockDriver indicates the task has 56 // been killed 57 ExitSignal int `mapstructure:"exit_signal"` 58 59 // ExitErrMsg is the error message that the task returns while exiting 60 ExitErrMsg string `mapstructure:"exit_err_msg"` 61 62 // SignalErr is the error message that the task returns if signalled 63 SignalErr string `mapstructure:"signal_error"` 64 } 65 66 // MockDriver is a driver which is used for testing purposes 67 type MockDriver struct { 68 DriverContext 69 fingerprint.StaticFingerprinter 70 71 cleanupFailNum int 72 } 73 74 // NewMockDriver is a factory method which returns a new Mock Driver 75 func NewMockDriver(ctx *DriverContext) Driver { 76 return &MockDriver{DriverContext: *ctx} 77 } 78 79 func (d *MockDriver) Abilities() DriverAbilities { 80 return DriverAbilities{ 81 SendSignals: false, 82 Exec: true, 83 } 84 } 85 86 func (d *MockDriver) FSIsolation() cstructs.FSIsolation { 87 return cstructs.FSIsolationNone 88 } 89 90 func (d *MockDriver) Prestart(*ExecContext, *structs.Task) (*PrestartResponse, error) { 91 return nil, nil 92 } 93 94 // Start starts the mock driver 95 func (m *MockDriver) Start(ctx *ExecContext, task *structs.Task) (*StartResponse, error) { 96 var driverConfig MockDriverConfig 97 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 98 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 99 WeaklyTypedInput: true, 100 Result: &driverConfig, 101 }) 102 if err != nil { 103 return nil, err 104 } 105 if err := dec.Decode(task.Config); err != nil { 106 return nil, err 107 } 108 109 if driverConfig.StartBlockFor != 0 { 110 time.Sleep(driverConfig.StartBlockFor) 111 } 112 113 if driverConfig.StartErr != "" { 114 return nil, structs.NewRecoverableError(errors.New(driverConfig.StartErr), driverConfig.StartErrRecoverable) 115 } 116 117 h := mockDriverHandle{ 118 taskName: task.Name, 119 runFor: driverConfig.RunFor, 120 killAfter: driverConfig.KillAfter, 121 killTimeout: task.KillTimeout, 122 exitCode: driverConfig.ExitCode, 123 exitSignal: driverConfig.ExitSignal, 124 logger: m.logger, 125 doneCh: make(chan struct{}), 126 waitCh: make(chan *dstructs.WaitResult, 1), 127 } 128 if driverConfig.ExitErrMsg != "" { 129 h.exitErr = errors.New(driverConfig.ExitErrMsg) 130 } 131 if driverConfig.SignalErr != "" { 132 h.signalErr = fmt.Errorf(driverConfig.SignalErr) 133 } 134 m.logger.Printf("[DEBUG] driver.mock: starting task %q", task.Name) 135 go h.run() 136 return &StartResponse{Handle: &h}, nil 137 } 138 139 // Cleanup deletes all keys except for Config.Options["cleanup_fail_on"] for 140 // Config.Options["cleanup_fail_num"] times. For failures it will return a 141 // recoverable error. 142 func (m *MockDriver) Cleanup(ctx *ExecContext, res *CreatedResources) error { 143 if res == nil { 144 panic("Cleanup should not be called with nil *CreatedResources") 145 } 146 147 var err error 148 failn, _ := strconv.Atoi(m.config.Options["cleanup_fail_num"]) 149 failk := m.config.Options["cleanup_fail_on"] 150 for k := range res.Resources { 151 if k == failk && m.cleanupFailNum < failn { 152 m.cleanupFailNum++ 153 err = structs.NewRecoverableError(fmt.Errorf("mock_driver failure on %q call %d/%d", k, m.cleanupFailNum, failn), true) 154 } else { 155 delete(res.Resources, k) 156 } 157 } 158 return err 159 } 160 161 // Validate validates the mock driver configuration 162 func (m *MockDriver) Validate(map[string]interface{}) error { 163 return nil 164 } 165 166 // Fingerprint fingerprints a node and returns if MockDriver is enabled 167 func (m *MockDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) { 168 node.Attributes["driver.mock_driver"] = "1" 169 return true, nil 170 } 171 172 // MockDriverHandle is a driver handler which supervises a mock task 173 type mockDriverHandle struct { 174 taskName string 175 runFor time.Duration 176 killAfter time.Duration 177 killTimeout time.Duration 178 exitCode int 179 exitSignal int 180 exitErr error 181 signalErr error 182 logger *log.Logger 183 waitCh chan *dstructs.WaitResult 184 doneCh chan struct{} 185 } 186 187 type mockDriverID struct { 188 TaskName string 189 RunFor time.Duration 190 KillAfter time.Duration 191 KillTimeout time.Duration 192 ExitCode int 193 ExitSignal int 194 ExitErr error 195 SignalErr error 196 } 197 198 func (h *mockDriverHandle) ID() string { 199 id := mockDriverID{ 200 TaskName: h.taskName, 201 RunFor: h.runFor, 202 KillAfter: h.killAfter, 203 KillTimeout: h.killTimeout, 204 ExitCode: h.exitCode, 205 ExitSignal: h.exitSignal, 206 ExitErr: h.exitErr, 207 SignalErr: h.signalErr, 208 } 209 210 data, err := json.Marshal(id) 211 if err != nil { 212 h.logger.Printf("[ERR] driver.mock_driver: failed to marshal ID to JSON: %s", err) 213 } 214 return string(data) 215 } 216 217 // Open re-connects the driver to the running task 218 func (m *MockDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) { 219 id := &mockDriverID{} 220 if err := json.Unmarshal([]byte(handleID), id); err != nil { 221 return nil, fmt.Errorf("Failed to parse handle '%s': %v", handleID, err) 222 } 223 224 h := mockDriverHandle{ 225 taskName: id.TaskName, 226 runFor: id.RunFor, 227 killAfter: id.KillAfter, 228 killTimeout: id.KillTimeout, 229 exitCode: id.ExitCode, 230 exitSignal: id.ExitSignal, 231 exitErr: id.ExitErr, 232 signalErr: id.SignalErr, 233 logger: m.logger, 234 doneCh: make(chan struct{}), 235 waitCh: make(chan *dstructs.WaitResult, 1), 236 } 237 238 go h.run() 239 return &h, nil 240 } 241 242 func (h *mockDriverHandle) WaitCh() chan *dstructs.WaitResult { 243 return h.waitCh 244 } 245 246 func (h *mockDriverHandle) Exec(ctx context.Context, cmd string, args []string) ([]byte, int, error) { 247 h.logger.Printf("[DEBUG] driver.mock: Exec(%q, %q)", cmd, args) 248 return []byte(fmt.Sprintf("Exec(%q, %q)", cmd, args)), 0, nil 249 } 250 251 // TODO Implement when we need it. 252 func (h *mockDriverHandle) Update(task *structs.Task) error { 253 h.killTimeout = task.KillTimeout 254 return nil 255 } 256 257 // TODO Implement when we need it. 258 func (h *mockDriverHandle) Signal(s os.Signal) error { 259 return h.signalErr 260 } 261 262 // Kill kills a mock task 263 func (h *mockDriverHandle) Kill() error { 264 h.logger.Printf("[DEBUG] driver.mock: killing task %q after kill timeout: %v", h.taskName, h.killTimeout) 265 select { 266 case <-h.doneCh: 267 case <-time.After(h.killAfter): 268 close(h.doneCh) 269 case <-time.After(h.killTimeout): 270 h.logger.Printf("[DEBUG] driver.mock: terminating task %q", h.taskName) 271 close(h.doneCh) 272 } 273 return nil 274 } 275 276 // TODO Implement when we need it. 277 func (h *mockDriverHandle) Stats() (*cstructs.TaskResourceUsage, error) { 278 return nil, nil 279 } 280 281 // run waits for the configured amount of time and then indicates the task has 282 // terminated 283 func (h *mockDriverHandle) run() { 284 timer := time.NewTimer(h.runFor) 285 defer timer.Stop() 286 for { 287 select { 288 case <-timer.C: 289 close(h.doneCh) 290 case <-h.doneCh: 291 h.logger.Printf("[DEBUG] driver.mock: finished running task %q", h.taskName) 292 h.waitCh <- dstructs.NewWaitResult(h.exitCode, h.exitSignal, h.exitErr) 293 return 294 } 295 } 296 }