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