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