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